From faa2512f68943cbc1c8bf6a240ba27a20ad4ce86 Mon Sep 17 00:00:00 2001 From: Jacob Peterson Date: Mon, 27 Apr 2020 12:43:17 -0600 Subject: [PATCH 001/271] Add new market data API endpoints --- .../alpaca/{bar => market_data}/bar.json | 2 +- .../alpaca/market_data/last_quote.json | 34 ++++++++++ .../market_data/last_quote_response.json | 18 ++++++ .../alpaca/market_data/last_trade.json | 38 +++++++++++ .../market_data/last_trade_response.json | 18 ++++++ .../mainstringargs/alpaca/AlpacaAPI.java | 64 ++++++++++++++++++- .../alpaca/AlpacaConstants.java | 9 +++ 7 files changed, 179 insertions(+), 4 deletions(-) rename schema_json/alpaca/{bar => market_data}/bar.json (84%) create mode 100644 schema_json/alpaca/market_data/last_quote.json create mode 100644 schema_json/alpaca/market_data/last_quote_response.json create mode 100644 schema_json/alpaca/market_data/last_trade.json create mode 100644 schema_json/alpaca/market_data/last_trade_response.json diff --git a/schema_json/alpaca/bar/bar.json b/schema_json/alpaca/market_data/bar.json similarity index 84% rename from schema_json/alpaca/bar/bar.json rename to schema_json/alpaca/market_data/bar.json index 682776c8..36cfcf3d 100644 --- a/schema_json/alpaca/bar/bar.json +++ b/schema_json/alpaca/market_data/bar.json @@ -1,6 +1,6 @@ { "type": "object", - "title": "See https://docs.alpaca.markets/api-documentation/api-v2/market-data/bars/", + "title": "See https://alpaca.markets/docs/api-documentation/api-v2/market-data/bars/", "properties": { "t": { "existingJavaType": "java.lang.Long", diff --git a/schema_json/alpaca/market_data/last_quote.json b/schema_json/alpaca/market_data/last_quote.json new file mode 100644 index 00000000..9d8f183a --- /dev/null +++ b/schema_json/alpaca/market_data/last_quote.json @@ -0,0 +1,34 @@ +{ + "type": "object", + "title": "See https://alpaca.markets/docs/api-documentation/api-v2/market-data/last-quote/", + "properties": { + "askprice": { + "existingJavaType": "java.lang.Double", + "title": "The current ask price" + }, + "asksize": { + "existingJavaType": "java.lang.Long", + "title": "The current ask size" + }, + "askexchange": { + "existingJavaType": "java.lang.Integer", + "title": "The exchange code of the ask quote" + }, + "bidprice": { + "existingJavaType": "java.lang.Double", + "title": "The current bid price" + }, + "bidsize": { + "existingJavaType": "java.lang.Long", + "title": "The current bid size" + }, + "bidexchange": { + "existingJavaType": "java.lang.Integer", + "title": "The exchange code of the bid quote" + }, + "timestamp": { + "existingJavaType": "java.lang.Long", + "title": "Epoch timestamp in milliseconds" + } + } +} diff --git a/schema_json/alpaca/market_data/last_quote_response.json b/schema_json/alpaca/market_data/last_quote_response.json new file mode 100644 index 00000000..39117197 --- /dev/null +++ b/schema_json/alpaca/market_data/last_quote_response.json @@ -0,0 +1,18 @@ +{ + "type": "object", + "title": "See https://alpaca.markets/docs/api-documentation/api-v2/market-data/last-quote/", + "properties": { + "status": { + "existingJavaType": "java.lang.String", + "title": "Status of this requests response" + }, + "symbol": { + "existingJavaType": "java.lang.String", + "title": "Symbol that was evaluated from the request" + }, + "last": { + "existingJavaType": "io.github.mainstringargs.domain.alpaca.market_data.LastQuote", + "title": "The Last Quote object" + } + } +} diff --git a/schema_json/alpaca/market_data/last_trade.json b/schema_json/alpaca/market_data/last_trade.json new file mode 100644 index 00000000..af6051e8 --- /dev/null +++ b/schema_json/alpaca/market_data/last_trade.json @@ -0,0 +1,38 @@ +{ + "type": "object", + "title": "See https://alpaca.markets/docs/api-documentation/api-v2/market-data/last-trade/", + "properties": { + "price": { + "existingJavaType": "java.lang.Double", + "title": "Price of the trade" + }, + "size": { + "existingJavaType": "java.lang.Long", + "title": "Last trade volume size" + }, + "exchange": { + "existingJavaType": "java.lang.Integer", + "title": "Exchange code where the last trade was made" + }, + "cond1": { + "existingJavaType": "java.lang.Integer", + "title": "Condition 1 of the trade" + }, + "cond2": { + "existingJavaType": "java.lang.Integer", + "title": "Condition 2 of the trade" + }, + "cond3": { + "existingJavaType": "java.lang.Integer", + "title": "Condition 3 of the trade" + }, + "cond4": { + "existingJavaType": "java.lang.Integer", + "title": "Condition 4 of the trade" + }, + "timestamp": { + "existingJavaType": "java.lang.Long", + "title": "Epoch timestamp in milliseconds" + } + } +} diff --git a/schema_json/alpaca/market_data/last_trade_response.json b/schema_json/alpaca/market_data/last_trade_response.json new file mode 100644 index 00000000..56e0d5f6 --- /dev/null +++ b/schema_json/alpaca/market_data/last_trade_response.json @@ -0,0 +1,18 @@ +{ + "type": "object", + "title": "See https://alpaca.markets/docs/api-documentation/api-v2/market-data/last-trade/", + "properties": { + "status": { + "existingJavaType": "java.lang.String", + "title": "Status of this requests response" + }, + "symbol": { + "existingJavaType": "java.lang.String", + "title": "Symbol that was evaluated from the request" + }, + "last": { + "existingJavaType": "io.github.mainstringargs.domain.alpaca.market_data.LastTrade", + "title": "The Last Trade object" + } + } +} diff --git a/src/main/java/io/github/mainstringargs/alpaca/AlpacaAPI.java b/src/main/java/io/github/mainstringargs/alpaca/AlpacaAPI.java index d7a31b2e..46b5c424 100644 --- a/src/main/java/io/github/mainstringargs/alpaca/AlpacaAPI.java +++ b/src/main/java/io/github/mainstringargs/alpaca/AlpacaAPI.java @@ -30,9 +30,11 @@ import io.github.mainstringargs.domain.alpaca.accountactivities.TradeActivity; import io.github.mainstringargs.domain.alpaca.accountconfiguration.AccountConfiguration; import io.github.mainstringargs.domain.alpaca.asset.Asset; -import io.github.mainstringargs.domain.alpaca.bar.Bar; import io.github.mainstringargs.domain.alpaca.calendar.Calendar; import io.github.mainstringargs.domain.alpaca.clock.Clock; +import io.github.mainstringargs.domain.alpaca.marketdata.Bar; +import io.github.mainstringargs.domain.alpaca.marketdata.LastQuoteResponse; +import io.github.mainstringargs.domain.alpaca.marketdata.LastTradeResponse; import io.github.mainstringargs.domain.alpaca.order.CancelledOrder; import io.github.mainstringargs.domain.alpaca.order.Order; import io.github.mainstringargs.domain.alpaca.portfoliohistory.PortfolioHistory; @@ -1418,7 +1420,8 @@ public Clock getClock() throws AlpacaAPIRequestException { * @param after Filter bars after this time. Cannot be used with start. * @param until Filter bars before this time. Cannot be used with end. * - * @return the bars + * @return An object with a key for each symbol and the Bars object as the values. Note that it returns status 200 + * with an empty object if no requested symbol is found. * * @throws AlpacaAPIRequestException the alpaca API exception * @see Bars @@ -1445,7 +1448,8 @@ public Map> getBars(BarsTimeFrame timeframe, String symbo * @param after Filter bars after this time. Cannot be used with start. * @param until Filter bars before this time. Cannot be used with end. * - * @return the bars + * @return @return An object with a key for each symbol and the Bars object as the values. Note that it returns + * status 200 with an empty object if no requested symbol is found. * * @throws AlpacaAPIRequestException the alpaca API exception * @see Bars @@ -1499,6 +1503,60 @@ public Map> getBars(BarsTimeFrame timeframe, String[] sym return alpacaRequest.getResponseObject(response, mapType); } + /** + * Retrieves the last trade for the requested symbol. + * + * @param symbol A stock ticker symbol to retrieve the last trade of + * + * @return the last trade response object + * + * @throws AlpacaAPIRequestException the alpaca api request exception + * @see Last Trade + */ + public LastTradeResponse getLastTrade(String symbol) throws AlpacaAPIRequestException { + Preconditions.checkNotNull(symbol); + + AlpacaRequestBuilder urlBuilder = new AlpacaRequestBuilder(baseDataUrl, AlpacaConstants.VERSION_1_ENDPOINT, + AlpacaConstants.LAST_ENDPOINT, + AlpacaConstants.STOCKS_ENDPOINT, + symbol); + + HttpResponse response = alpacaRequest.invokeGet(urlBuilder); + + if (response.getStatus() != 200) { + throw new AlpacaAPIRequestException(response); + } + + return alpacaRequest.getResponseObject(response, LastTradeResponse.class); + } + + /** + * Retrieves the last quote for the requested symbol. + * + * @param symbol A stock ticker symbol to retrieve the last trade of + * + * @return the last quote response object + * + * @throws AlpacaAPIRequestException the alpaca api request exception + * @see Date: Tue, 28 Apr 2020 11:34:58 -0600 Subject: [PATCH 002/271] Refactor and start WS Data API --- .../market_data/last_quote_response.json | 2 +- .../market_data/last_trade_response.json | 2 +- .../aggregate/aggregate_minute_message.json | 53 +++ .../authorization/authorization_data.json | 14 + .../authorization/authorization_message.json | 13 + .../streaming/listening/listening_data.json | 14 + .../listening/listening_message.json | 13 + .../market_data_stream_data_message.json | 17 + .../streaming/market_data_stream_message.json | 7 + .../market_data_stream_status_message.json | 13 + .../streaming/quote/quote_message.json | 52 +++ .../streaming/trade/trade_message.json | 37 ++ .../account/account_update.json | 0 .../account/account_update_message.json | 4 +- .../alpaca_stream_message.json | 2 +- .../authorization/authorization_data.json | 0 .../authorization/authorization_message.json | 4 +- .../listening/listening_data.json | 0 .../listening/listening_message.json | 4 +- .../trade/trade_update.json | 0 .../trade/trade_update_message.json | 4 +- .../AbstractWebsocketClientEndpoint.java | 2 +- .../mainstringargs/alpaca/AlpacaAPI.java | 4 +- .../mainstringargs/alpaca/AlpacaExample.java | 4 +- .../client/AlpacaWebsocketClient.java | 48 +-- .../client/AlpacaWebsocketClientEndpoint.java | 2 +- .../listener/AlpacaStreamListener.java | 6 +- .../listener/AlpacaStreamListenerAdapter.java | 8 +- .../message/AlpacaStreamMessageType.java | 6 +- .../client/MarketDataWebsocketClient.java | 340 ++++++++++++++++++ .../MarketDataWebsocketClientEndpoint.java | 45 +++ .../listener/MarketDataStreamListener.java | 22 ++ .../MarketDataStreamListenerAdapter.java | 69 ++++ .../message/MarketDataStreamMessageType.java | 58 +++ .../client/PolygonWebsocketClient.java | 6 +- 35 files changed, 821 insertions(+), 54 deletions(-) create mode 100644 schema_json/alpaca/market_data/streaming/aggregate/aggregate_minute_message.json create mode 100644 schema_json/alpaca/market_data/streaming/authorization/authorization_data.json create mode 100644 schema_json/alpaca/market_data/streaming/authorization/authorization_message.json create mode 100644 schema_json/alpaca/market_data/streaming/listening/listening_data.json create mode 100644 schema_json/alpaca/market_data/streaming/listening/listening_message.json create mode 100644 schema_json/alpaca/market_data/streaming/market_data_stream_data_message.json create mode 100644 schema_json/alpaca/market_data/streaming/market_data_stream_message.json create mode 100644 schema_json/alpaca/market_data/streaming/market_data_stream_status_message.json create mode 100644 schema_json/alpaca/market_data/streaming/quote/quote_message.json create mode 100644 schema_json/alpaca/market_data/streaming/trade/trade_message.json rename schema_json/alpaca/{websocket => streaming}/account/account_update.json (100%) rename schema_json/alpaca/{websocket => streaming}/account/account_update_message.json (85%) rename schema_json/alpaca/{websocket => streaming}/alpaca_stream_message.json (90%) rename schema_json/alpaca/{websocket => streaming}/authorization/authorization_data.json (100%) rename schema_json/alpaca/{websocket => streaming}/authorization/authorization_message.json (83%) rename schema_json/alpaca/{websocket => streaming}/listening/listening_data.json (100%) rename schema_json/alpaca/{websocket => streaming}/listening/listening_message.json (85%) rename schema_json/alpaca/{websocket => streaming}/trade/trade_update.json (100%) rename schema_json/alpaca/{websocket => streaming}/trade/trade_update_message.json (85%) rename src/main/java/io/github/mainstringargs/alpaca/websocket/{ => broker}/client/AlpacaWebsocketClient.java (90%) rename src/main/java/io/github/mainstringargs/alpaca/websocket/{ => broker}/client/AlpacaWebsocketClientEndpoint.java (95%) rename src/main/java/io/github/mainstringargs/alpaca/websocket/{ => broker}/listener/AlpacaStreamListener.java (72%) rename src/main/java/io/github/mainstringargs/alpaca/websocket/{ => broker}/listener/AlpacaStreamListenerAdapter.java (74%) rename src/main/java/io/github/mainstringargs/alpaca/websocket/{ => broker}/message/AlpacaStreamMessageType.java (90%) create mode 100644 src/main/java/io/github/mainstringargs/alpaca/websocket/marketdata/client/MarketDataWebsocketClient.java create mode 100644 src/main/java/io/github/mainstringargs/alpaca/websocket/marketdata/client/MarketDataWebsocketClientEndpoint.java create mode 100644 src/main/java/io/github/mainstringargs/alpaca/websocket/marketdata/listener/MarketDataStreamListener.java create mode 100644 src/main/java/io/github/mainstringargs/alpaca/websocket/marketdata/listener/MarketDataStreamListenerAdapter.java create mode 100644 src/main/java/io/github/mainstringargs/alpaca/websocket/marketdata/message/MarketDataStreamMessageType.java diff --git a/schema_json/alpaca/market_data/last_quote_response.json b/schema_json/alpaca/market_data/last_quote_response.json index 39117197..fefa8c5d 100644 --- a/schema_json/alpaca/market_data/last_quote_response.json +++ b/schema_json/alpaca/market_data/last_quote_response.json @@ -11,7 +11,7 @@ "title": "Symbol that was evaluated from the request" }, "last": { - "existingJavaType": "io.github.mainstringargs.domain.alpaca.market_data.LastQuote", + "existingJavaType": "io.github.mainstringargs.domain.alpaca.marketdata.LastQuote", "title": "The Last Quote object" } } diff --git a/schema_json/alpaca/market_data/last_trade_response.json b/schema_json/alpaca/market_data/last_trade_response.json index 56e0d5f6..e745db21 100644 --- a/schema_json/alpaca/market_data/last_trade_response.json +++ b/schema_json/alpaca/market_data/last_trade_response.json @@ -11,7 +11,7 @@ "title": "Symbol that was evaluated from the request" }, "last": { - "existingJavaType": "io.github.mainstringargs.domain.alpaca.market_data.LastTrade", + "existingJavaType": "io.github.mainstringargs.domain.alpaca.marketdata.LastTrade", "title": "The Last Trade object" } } diff --git a/schema_json/alpaca/market_data/streaming/aggregate/aggregate_minute_message.json b/schema_json/alpaca/market_data/streaming/aggregate/aggregate_minute_message.json new file mode 100644 index 00000000..391edef8 --- /dev/null +++ b/schema_json/alpaca/market_data/streaming/aggregate/aggregate_minute_message.json @@ -0,0 +1,53 @@ +{ + "type": "object", + "extends": { + "existingJavaType": "io.github.mainstringargs.domain.alpaca.marketdata.streaming.MarketDataStreamDataMessage" + }, + "title": "See https://alpaca.markets/docs/api-documentation/api-v2/market-data/streaming/", + "properties": { + "v": { + "existingJavaType": "java.lang.Long", + "title": "Tick volume (shares)" + }, + "av": { + "existingJavaType": "java.lang.Long", + "title": "Accumulated volume (Today)" + }, + "op": { + "existingJavaType": "java.lang.Double", + "title": "Official open price of the bar" + }, + "vw": { + "existingJavaType": "java.lang.Double", + "title": "VWAP (Volume Weighted Average Price)" + }, + "o": { + "existingJavaType": "java.lang.Double", + "title": "Bar open price" + }, + "h": { + "existingJavaType": "java.lang.Double", + "title": "Bar high price" + }, + "l": { + "existingJavaType": "java.lang.Double", + "title": "Bar low price" + }, + "c": { + "existingJavaType": "java.lang.Double", + "title": "Bar close price" + }, + "a": { + "existingJavaType": "java.lang.Double", + "title": "Average price of the bar (Tick Average / VWAP Price)" + }, + "s": { + "existingJavaType": "java.lang.Long", + "title": "Epoch time at the beginning of the window in milliseconds" + }, + "e": { + "existingJavaType": "java.lang.Long", + "title": "Epoch time at the ending of the window in milliseconds" + } + } +} diff --git a/schema_json/alpaca/market_data/streaming/authorization/authorization_data.json b/schema_json/alpaca/market_data/streaming/authorization/authorization_data.json new file mode 100644 index 00000000..444fe929 --- /dev/null +++ b/schema_json/alpaca/market_data/streaming/authorization/authorization_data.json @@ -0,0 +1,14 @@ +{ + "type": "object", + "title": "See https://alpaca.markets/docs/api-documentation/api-v2/market-data/streaming/", + "properties": { + "status": { + "existingJavaType": "java.lang.String", + "title": "The status." + }, + "action": { + "existingJavaType": "java.lang.String", + "title": "The desired action." + } + } +} diff --git a/schema_json/alpaca/market_data/streaming/authorization/authorization_message.json b/schema_json/alpaca/market_data/streaming/authorization/authorization_message.json new file mode 100644 index 00000000..77c3818b --- /dev/null +++ b/schema_json/alpaca/market_data/streaming/authorization/authorization_message.json @@ -0,0 +1,13 @@ +{ + "type": "object", + "title": "See https://alpaca.markets/docs/api-documentation/api-v2/market-data/streaming/", + "extends": { + "existingJavaType": "io.github.mainstringargs.domain.alpaca.marketdata.streaming.MarketDataStreamStatusMessage" + }, + "properties": { + "data": { + "existingJavaType": "io.github.mainstringargs.domain.alpaca.marketdata.streaming.authorization.AuthorizationData", + "title": "The data" + } + } +} diff --git a/schema_json/alpaca/market_data/streaming/listening/listening_data.json b/schema_json/alpaca/market_data/streaming/listening/listening_data.json new file mode 100644 index 00000000..a04bdd08 --- /dev/null +++ b/schema_json/alpaca/market_data/streaming/listening/listening_data.json @@ -0,0 +1,14 @@ +{ + "type": "object", + "title": "See https://alpaca.markets/docs/api-documentation/api-v2/market-data/streaming/", + "properties": { + "streams": { + "existingJavaType": "java.util.ArrayList", + "title": "The streams listened to" + }, + "error": { + "existingJavaType": "java.util.ArrayList", + "title": "The error, if one exists" + } + } +} diff --git a/schema_json/alpaca/market_data/streaming/listening/listening_message.json b/schema_json/alpaca/market_data/streaming/listening/listening_message.json new file mode 100644 index 00000000..c751c71a --- /dev/null +++ b/schema_json/alpaca/market_data/streaming/listening/listening_message.json @@ -0,0 +1,13 @@ +{ + "type": "object", + "title": "See https://docs.alpaca.markets/api-documentation/api-v2/streaming/", + "extends": { + "existingJavaType": "io.github.mainstringargs.domain.alpaca.marketdata.streaming.MarketDataStreamStatusMessage" + }, + "properties": { + "data": { + "existingJavaType": "io.github.mainstringargs.domain.alpaca.marketdata.streaming.listening.ListeningData", + "title": "The data" + } + } +} diff --git a/schema_json/alpaca/market_data/streaming/market_data_stream_data_message.json b/schema_json/alpaca/market_data/streaming/market_data_stream_data_message.json new file mode 100644 index 00000000..2ac43fb5 --- /dev/null +++ b/schema_json/alpaca/market_data/streaming/market_data_stream_data_message.json @@ -0,0 +1,17 @@ +{ + "type": "object", + "extends": { + "existingJavaType": "io.github.mainstringargs.domain.alpaca.marketdata.streaming.MarketDataStreamMessage" + }, + "title": "See https://alpaca.markets/docs/api-documentation/api-v2/market-data/streaming/", + "properties": { + "ev": { + "existingJavaType": "io.github.mainstringargs.alpaca.websocket.marketdata.message.MarketDataStreamMessageType", + "title": "The event type" + }, + "sym": { + "existingJavaType": "java.lang.String", + "title": "The symbol" + } + } +} diff --git a/schema_json/alpaca/market_data/streaming/market_data_stream_message.json b/schema_json/alpaca/market_data/streaming/market_data_stream_message.json new file mode 100644 index 00000000..ca623f9a --- /dev/null +++ b/schema_json/alpaca/market_data/streaming/market_data_stream_message.json @@ -0,0 +1,7 @@ +{ + "type": "object", + "javaInterfaces": [ + "io.github.mainstringargs.abstracts.websocket.message.StreamMessage" + ], + "title": "See https://alpaca.markets/docs/api-documentation/api-v2/market-data/streaming/" +} diff --git a/schema_json/alpaca/market_data/streaming/market_data_stream_status_message.json b/schema_json/alpaca/market_data/streaming/market_data_stream_status_message.json new file mode 100644 index 00000000..56f4a237 --- /dev/null +++ b/schema_json/alpaca/market_data/streaming/market_data_stream_status_message.json @@ -0,0 +1,13 @@ +{ + "type": "object", + "extends": { + "existingJavaType": "io.github.mainstringargs.domain.alpaca.marketdata.streaming.MarketDataStreamMessage" + }, + "title": "See https://alpaca.markets/docs/api-documentation/api-v2/market-data/streaming/", + "properties": { + "stream": { + "existingJavaType": "io.github.mainstringargs.alpaca.websocket.marketdata.message.MarketDataStreamMessageType", + "title": "The stream type" + } + } +} diff --git a/schema_json/alpaca/market_data/streaming/quote/quote_message.json b/schema_json/alpaca/market_data/streaming/quote/quote_message.json new file mode 100644 index 00000000..038e8152 --- /dev/null +++ b/schema_json/alpaca/market_data/streaming/quote/quote_message.json @@ -0,0 +1,52 @@ +{ + "type": "object", + "extends": { + "existingJavaType": "io.github.mainstringargs.domain.alpaca.marketdata.streaming.MarketDataStreamDataMessage" + }, + "title": "See https://alpaca.markets/docs/api-documentation/api-v2/market-data/streaming/", + "properties": { + "T": { + "existingJavaType": "java.lang.String", + "javaName": "ticker", + "title": "The ticker" + }, + "x": { + "existingJavaType": "java.lang.String", + "javaName": "bx", + "title": "Bix Exchange ID" + }, + "p": { + "existingJavaType": "java.lang.Double", + "javaName": "bp", + "title": "Bid Price" + }, + "s": { + "existingJavaType": "java.lang.Integer", + "javaName": "bs", + "title": "Bid Size" + }, + "X": { + "existingJavaType": "java.lang.String", + "javaName": "ax", + "title": "Ask Exchange ID" + }, + "P": { + "existingJavaType": "java.lang.Double", + "javaName": "ap", + "title": "Ask Price" + }, + "S": { + "existingJavaType": "java.lang.Integer", + "javaName": "as", + "title": "Ask Size" + }, + "c": { + "existingJavaType": "java.util.ArrayList", + "title": "Quote condition flags" + }, + "t": { + "existingJavaType": "java.lang.Long", + "title": "Quote Timestamp ( Unix MS )" + } + } +} diff --git a/schema_json/alpaca/market_data/streaming/trade/trade_message.json b/schema_json/alpaca/market_data/streaming/trade/trade_message.json new file mode 100644 index 00000000..55d5a15d --- /dev/null +++ b/schema_json/alpaca/market_data/streaming/trade/trade_message.json @@ -0,0 +1,37 @@ +{ + "type": "object", + "extends": { + "existingJavaType": "io.github.mainstringargs.domain.alpaca.marketdata.streaming.MarketDataStreamDataMessage" + }, + "title": "See https://alpaca.markets/docs/api-documentation/api-v2/market-data/streaming/", + "properties": { + "i": { + "existingJavaType": "java.lang.Integer", + "title": "Trade ID" + }, + "x": { + "existingJavaType": "java.lang.String", + "title": "Exchange code where the trade occurred" + }, + "p": { + "existingJavaType": "java.lang.Double", + "title": "Trade price" + }, + "s": { + "existingJavaType": "java.lang.Integer", + "title": "Trade size (shares)" + }, + "t": { + "existingJavaType": "java.lang.Long", + "title": "Epoch timestamp in milliseconds" + }, + "c": { + "existingJavaType": "java.util.ArrayList", + "title": "Trade condition flags" + }, + "z": { + "existingJavaType": "java.lang.Integer", + "title": "Tape ID ( 1=A 2=B 3=C)" + } + } +} diff --git a/schema_json/alpaca/websocket/account/account_update.json b/schema_json/alpaca/streaming/account/account_update.json similarity index 100% rename from schema_json/alpaca/websocket/account/account_update.json rename to schema_json/alpaca/streaming/account/account_update.json diff --git a/schema_json/alpaca/websocket/account/account_update_message.json b/schema_json/alpaca/streaming/account/account_update_message.json similarity index 85% rename from schema_json/alpaca/websocket/account/account_update_message.json rename to schema_json/alpaca/streaming/account/account_update_message.json index 0a68a12c..61c70909 100644 --- a/schema_json/alpaca/websocket/account/account_update_message.json +++ b/schema_json/alpaca/streaming/account/account_update_message.json @@ -1,12 +1,12 @@ { "type": "object", "extends": { - "existingJavaType": "io.github.mainstringargs.domain.alpaca.websocket.AlpacaStreamMessage" + "existingJavaType": "io.github.mainstringargs.domain.alpaca.streaming.AlpacaStreamMessage" }, "title": "See https://docs.alpaca.markets/api-documentation/api-v2/streaming/", "properties": { "data": { - "existingJavaType": "io.github.mainstringargs.domain.alpaca.websocket.account.AccountUpdate" + "existingJavaType": "io.github.mainstringargs.domain.alpaca.streaming.account.AccountUpdate" } } } diff --git a/schema_json/alpaca/websocket/alpaca_stream_message.json b/schema_json/alpaca/streaming/alpaca_stream_message.json similarity index 90% rename from schema_json/alpaca/websocket/alpaca_stream_message.json rename to schema_json/alpaca/streaming/alpaca_stream_message.json index d1d0cca4..1853f5ee 100644 --- a/schema_json/alpaca/websocket/alpaca_stream_message.json +++ b/schema_json/alpaca/streaming/alpaca_stream_message.json @@ -6,7 +6,7 @@ "title": "See https://docs.alpaca.markets/api-documentation/api-v2/streaming/", "properties": { "stream": { - "existingJavaType": "io.github.mainstringargs.alpaca.websocket.message.AlpacaStreamMessageType", + "existingJavaType": "io.github.mainstringargs.alpaca.websocket.broker.message.AlpacaStreamMessageType", "title": "The stream type" } } diff --git a/schema_json/alpaca/websocket/authorization/authorization_data.json b/schema_json/alpaca/streaming/authorization/authorization_data.json similarity index 100% rename from schema_json/alpaca/websocket/authorization/authorization_data.json rename to schema_json/alpaca/streaming/authorization/authorization_data.json diff --git a/schema_json/alpaca/websocket/authorization/authorization_message.json b/schema_json/alpaca/streaming/authorization/authorization_message.json similarity index 83% rename from schema_json/alpaca/websocket/authorization/authorization_message.json rename to schema_json/alpaca/streaming/authorization/authorization_message.json index 04e7e88a..78724c97 100644 --- a/schema_json/alpaca/websocket/authorization/authorization_message.json +++ b/schema_json/alpaca/streaming/authorization/authorization_message.json @@ -2,11 +2,11 @@ "type": "object", "title": "See https://docs.alpaca.markets/api-documentation/api-v2/streaming/", "extends": { - "existingJavaType": "io.github.mainstringargs.domain.alpaca.websocket.AlpacaStreamMessage" + "existingJavaType": "io.github.mainstringargs.domain.alpaca.streaming.AlpacaStreamMessage" }, "properties": { "data": { - "existingJavaType": "io.github.mainstringargs.domain.alpaca.websocket.authorization.AuthorizationData", + "existingJavaType": "io.github.mainstringargs.domain.alpaca.streaming.authorization.AuthorizationData", "title": "The data" } } diff --git a/schema_json/alpaca/websocket/listening/listening_data.json b/schema_json/alpaca/streaming/listening/listening_data.json similarity index 100% rename from schema_json/alpaca/websocket/listening/listening_data.json rename to schema_json/alpaca/streaming/listening/listening_data.json diff --git a/schema_json/alpaca/websocket/listening/listening_message.json b/schema_json/alpaca/streaming/listening/listening_message.json similarity index 85% rename from schema_json/alpaca/websocket/listening/listening_message.json rename to schema_json/alpaca/streaming/listening/listening_message.json index 013a80fb..4aa3eb19 100644 --- a/schema_json/alpaca/websocket/listening/listening_message.json +++ b/schema_json/alpaca/streaming/listening/listening_message.json @@ -2,11 +2,11 @@ "type": "object", "title": "See https://docs.alpaca.markets/api-documentation/api-v2/streaming/", "extends": { - "existingJavaType": "io.github.mainstringargs.domain.alpaca.websocket.AlpacaStreamMessage" + "existingJavaType": "io.github.mainstringargs.domain.alpaca.streaming.AlpacaStreamMessage" }, "properties": { "data": { - "existingJavaType": "io.github.mainstringargs.domain.alpaca.websocket.listening.ListeningData", + "existingJavaType": "io.github.mainstringargs.domain.alpaca.streaming.listening.ListeningData", "title": "The data" } } diff --git a/schema_json/alpaca/websocket/trade/trade_update.json b/schema_json/alpaca/streaming/trade/trade_update.json similarity index 100% rename from schema_json/alpaca/websocket/trade/trade_update.json rename to schema_json/alpaca/streaming/trade/trade_update.json diff --git a/schema_json/alpaca/websocket/trade/trade_update_message.json b/schema_json/alpaca/streaming/trade/trade_update_message.json similarity index 85% rename from schema_json/alpaca/websocket/trade/trade_update_message.json rename to schema_json/alpaca/streaming/trade/trade_update_message.json index 7a7498ce..8c07eec5 100644 --- a/schema_json/alpaca/websocket/trade/trade_update_message.json +++ b/schema_json/alpaca/streaming/trade/trade_update_message.json @@ -1,12 +1,12 @@ { "type": "object", "extends": { - "existingJavaType": "io.github.mainstringargs.domain.alpaca.websocket.AlpacaStreamMessage" + "existingJavaType": "io.github.mainstringargs.domain.alpaca.streaming.AlpacaStreamMessage" }, "title": "See https://docs.alpaca.markets/api-documentation/api-v2/streaming/", "properties": { "data": { - "existingJavaType": "io.github.mainstringargs.domain.alpaca.websocket.trade.TradeUpdate" + "existingJavaType": "io.github.mainstringargs.domain.alpaca.streaming.trade.TradeUpdate" } } } diff --git a/src/main/java/io/github/mainstringargs/abstracts/websocket/client/AbstractWebsocketClientEndpoint.java b/src/main/java/io/github/mainstringargs/abstracts/websocket/client/AbstractWebsocketClientEndpoint.java index 4a099656..6fac5910 100644 --- a/src/main/java/io/github/mainstringargs/abstracts/websocket/client/AbstractWebsocketClientEndpoint.java +++ b/src/main/java/io/github/mainstringargs/abstracts/websocket/client/AbstractWebsocketClientEndpoint.java @@ -25,7 +25,7 @@ public abstract class AbstractWebsocketClientEndpoint { private static final Logger LOGGER = LogManager.getLogger(AbstractWebsocketClientEndpoint.class); /** The Websocket client. */ - private WebsocketClient websocketClient; + private final WebsocketClient websocketClient; /** The Endpoint uri. */ private final URI endpointURI; diff --git a/src/main/java/io/github/mainstringargs/alpaca/AlpacaAPI.java b/src/main/java/io/github/mainstringargs/alpaca/AlpacaAPI.java index 46b5c424..ecdf4eaf 100644 --- a/src/main/java/io/github/mainstringargs/alpaca/AlpacaAPI.java +++ b/src/main/java/io/github/mainstringargs/alpaca/AlpacaAPI.java @@ -22,8 +22,8 @@ import io.github.mainstringargs.alpaca.rest.AlpacaRequest; import io.github.mainstringargs.alpaca.rest.AlpacaRequestBuilder; import io.github.mainstringargs.alpaca.rest.exception.AlpacaAPIRequestException; -import io.github.mainstringargs.alpaca.websocket.client.AlpacaWebsocketClient; -import io.github.mainstringargs.alpaca.websocket.listener.AlpacaStreamListener; +import io.github.mainstringargs.alpaca.websocket.broker.client.AlpacaWebsocketClient; +import io.github.mainstringargs.alpaca.websocket.broker.listener.AlpacaStreamListener; import io.github.mainstringargs.domain.alpaca.account.Account; import io.github.mainstringargs.domain.alpaca.accountactivities.AccountActivity; import io.github.mainstringargs.domain.alpaca.accountactivities.NonTradeActivity; diff --git a/src/main/java/io/github/mainstringargs/alpaca/AlpacaExample.java b/src/main/java/io/github/mainstringargs/alpaca/AlpacaExample.java index e6bcfa52..67da7b46 100644 --- a/src/main/java/io/github/mainstringargs/alpaca/AlpacaExample.java +++ b/src/main/java/io/github/mainstringargs/alpaca/AlpacaExample.java @@ -4,8 +4,8 @@ import io.github.mainstringargs.alpaca.enums.OrderSide; import io.github.mainstringargs.alpaca.enums.OrderTimeInForce; import io.github.mainstringargs.alpaca.rest.exception.AlpacaAPIRequestException; -import io.github.mainstringargs.alpaca.websocket.listener.AlpacaStreamListenerAdapter; -import io.github.mainstringargs.alpaca.websocket.message.AlpacaStreamMessageType; +import io.github.mainstringargs.alpaca.websocket.broker.listener.AlpacaStreamListenerAdapter; +import io.github.mainstringargs.alpaca.websocket.broker.message.AlpacaStreamMessageType; import io.github.mainstringargs.domain.alpaca.account.Account; import io.github.mainstringargs.domain.alpaca.bar.Bar; import io.github.mainstringargs.domain.alpaca.order.Order; diff --git a/src/main/java/io/github/mainstringargs/alpaca/websocket/client/AlpacaWebsocketClient.java b/src/main/java/io/github/mainstringargs/alpaca/websocket/broker/client/AlpacaWebsocketClient.java similarity index 90% rename from src/main/java/io/github/mainstringargs/alpaca/websocket/client/AlpacaWebsocketClient.java rename to src/main/java/io/github/mainstringargs/alpaca/websocket/broker/client/AlpacaWebsocketClient.java index 6e79c789..96cb1c34 100644 --- a/src/main/java/io/github/mainstringargs/alpaca/websocket/client/AlpacaWebsocketClient.java +++ b/src/main/java/io/github/mainstringargs/alpaca/websocket/broker/client/AlpacaWebsocketClient.java @@ -1,4 +1,4 @@ -package io.github.mainstringargs.alpaca.websocket.client; +package io.github.mainstringargs.alpaca.websocket.broker.client; import com.google.common.base.Preconditions; import com.google.gson.JsonArray; @@ -10,13 +10,13 @@ import io.github.mainstringargs.abstracts.websocket.listener.StreamListener; import io.github.mainstringargs.abstracts.websocket.message.StreamMessage; import io.github.mainstringargs.abstracts.websocket.message.StreamMessageType; -import io.github.mainstringargs.alpaca.websocket.listener.AlpacaStreamListener; -import io.github.mainstringargs.alpaca.websocket.message.AlpacaStreamMessageType; -import io.github.mainstringargs.domain.alpaca.websocket.AlpacaStreamMessage; -import io.github.mainstringargs.domain.alpaca.websocket.account.AccountUpdateMessage; -import io.github.mainstringargs.domain.alpaca.websocket.authorization.AuthorizationMessage; -import io.github.mainstringargs.domain.alpaca.websocket.listening.ListeningMessage; -import io.github.mainstringargs.domain.alpaca.websocket.trade.TradeUpdateMessage; +import io.github.mainstringargs.alpaca.websocket.broker.listener.AlpacaStreamListener; +import io.github.mainstringargs.alpaca.websocket.broker.message.AlpacaStreamMessageType; +import io.github.mainstringargs.domain.alpaca.streaming.AlpacaStreamMessage; +import io.github.mainstringargs.domain.alpaca.streaming.account.AccountUpdateMessage; +import io.github.mainstringargs.domain.alpaca.streaming.authorization.AuthorizationMessage; +import io.github.mainstringargs.domain.alpaca.streaming.listening.ListeningMessage; +import io.github.mainstringargs.domain.alpaca.streaming.trade.TradeUpdateMessage; import io.github.mainstringargs.util.gson.GsonUtil; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; @@ -43,16 +43,16 @@ public class AlpacaWebsocketClient implements WebsocketClient { private static final String STREAM_KEY = "stream"; /** The key id. */ - private String keyId; + private final String keyId; /** The secret. */ - private String secret; + private final String secret; /** The Base api url. */ - private String baseAPIURL; + private final String streamAPIURL; /** The observers. */ - private List listeners; + private final List listeners; /** The client end point. */ private AlpacaWebsocketClientEndpoint alpacaWebsocketClientEndpoint; @@ -65,12 +65,12 @@ public class AlpacaWebsocketClient implements WebsocketClient { * * @param keyId the key id * @param secret the secret - * @param baseAPIURL the base apiurl + * @param baseAPIURL the base API URL */ public AlpacaWebsocketClient(String keyId, String secret, String baseAPIURL) { this.keyId = keyId; this.secret = secret; - this.baseAPIURL = baseAPIURL.replace("https", "wss") + "/stream"; + this.streamAPIURL = baseAPIURL.replace("https", "wss") + "/stream"; this.listeners = new ArrayList<>(); } @@ -106,7 +106,7 @@ public void connect() { LOGGER.info("Connecting..."); try { - alpacaWebsocketClientEndpoint = new AlpacaWebsocketClientEndpoint(this, new URI(baseAPIURL)); + alpacaWebsocketClientEndpoint = new AlpacaWebsocketClientEndpoint(this, new URI(streamAPIURL)); alpacaWebsocketClientEndpoint.connect(); LOGGER.info("Connected."); @@ -167,13 +167,6 @@ public void handleWebsocketMessage(String message) { AlpacaStreamMessageType.class); switch (alpacaStreamMessageType) { - case LISTENING: - ListeningMessage listeningMessage = GsonUtil.GSON.fromJson(messageJsonObject, - ListeningMessage.class); - sendStreamMessageToListeners(alpacaStreamMessageType, listeningMessage); - - LOGGER.debug(listeningMessage); - break; case AUTHORIZATION: AuthorizationMessage authorizationMessage = GsonUtil.GSON.fromJson(messageJsonObject, AuthorizationMessage.class); @@ -183,6 +176,13 @@ public void handleWebsocketMessage(String message) { LOGGER.debug(authorizationMessage); break; + case LISTENING: + ListeningMessage listeningMessage = GsonUtil.GSON.fromJson(messageJsonObject, + ListeningMessage.class); + sendStreamMessageToListeners(alpacaStreamMessageType, listeningMessage); + + LOGGER.debug(listeningMessage); + break; case TRADE_UPDATES: sendStreamMessageToListeners(alpacaStreamMessageType, GsonUtil.GSON.fromJson(messageJsonObject, TradeUpdateMessage.class)); @@ -194,8 +194,8 @@ public void handleWebsocketMessage(String message) { default: LOGGER.error("Unhandled stream type: " + alpacaStreamMessageType); } - } catch (JsonSyntaxException e) { - LOGGER.throwing(e); + } catch (JsonSyntaxException exception) { + LOGGER.error("Could not parse message: " + messageJsonObject, exception); } } else { LOGGER.error("Unknown stream message: " + messageJsonObject); diff --git a/src/main/java/io/github/mainstringargs/alpaca/websocket/client/AlpacaWebsocketClientEndpoint.java b/src/main/java/io/github/mainstringargs/alpaca/websocket/broker/client/AlpacaWebsocketClientEndpoint.java similarity index 95% rename from src/main/java/io/github/mainstringargs/alpaca/websocket/client/AlpacaWebsocketClientEndpoint.java rename to src/main/java/io/github/mainstringargs/alpaca/websocket/broker/client/AlpacaWebsocketClientEndpoint.java index 371e9aa8..3fe3a769 100644 --- a/src/main/java/io/github/mainstringargs/alpaca/websocket/client/AlpacaWebsocketClientEndpoint.java +++ b/src/main/java/io/github/mainstringargs/alpaca/websocket/broker/client/AlpacaWebsocketClientEndpoint.java @@ -1,4 +1,4 @@ -package io.github.mainstringargs.alpaca.websocket.client; +package io.github.mainstringargs.alpaca.websocket.broker.client; import io.github.mainstringargs.abstracts.websocket.client.AbstractWebsocketClientEndpoint; import io.github.mainstringargs.abstracts.websocket.client.WebsocketClient; diff --git a/src/main/java/io/github/mainstringargs/alpaca/websocket/listener/AlpacaStreamListener.java b/src/main/java/io/github/mainstringargs/alpaca/websocket/broker/listener/AlpacaStreamListener.java similarity index 72% rename from src/main/java/io/github/mainstringargs/alpaca/websocket/listener/AlpacaStreamListener.java rename to src/main/java/io/github/mainstringargs/alpaca/websocket/broker/listener/AlpacaStreamListener.java index 1cc9cd1c..949add03 100644 --- a/src/main/java/io/github/mainstringargs/alpaca/websocket/listener/AlpacaStreamListener.java +++ b/src/main/java/io/github/mainstringargs/alpaca/websocket/broker/listener/AlpacaStreamListener.java @@ -1,8 +1,8 @@ -package io.github.mainstringargs.alpaca.websocket.listener; +package io.github.mainstringargs.alpaca.websocket.broker.listener; import io.github.mainstringargs.abstracts.websocket.listener.StreamListener; -import io.github.mainstringargs.alpaca.websocket.message.AlpacaStreamMessageType; -import io.github.mainstringargs.domain.alpaca.websocket.AlpacaStreamMessage; +import io.github.mainstringargs.alpaca.websocket.broker.message.AlpacaStreamMessageType; +import io.github.mainstringargs.domain.alpaca.streaming.AlpacaStreamMessage; import java.util.Set; diff --git a/src/main/java/io/github/mainstringargs/alpaca/websocket/listener/AlpacaStreamListenerAdapter.java b/src/main/java/io/github/mainstringargs/alpaca/websocket/broker/listener/AlpacaStreamListenerAdapter.java similarity index 74% rename from src/main/java/io/github/mainstringargs/alpaca/websocket/listener/AlpacaStreamListenerAdapter.java rename to src/main/java/io/github/mainstringargs/alpaca/websocket/broker/listener/AlpacaStreamListenerAdapter.java index 3a256907..acade998 100644 --- a/src/main/java/io/github/mainstringargs/alpaca/websocket/listener/AlpacaStreamListenerAdapter.java +++ b/src/main/java/io/github/mainstringargs/alpaca/websocket/broker/listener/AlpacaStreamListenerAdapter.java @@ -1,7 +1,7 @@ -package io.github.mainstringargs.alpaca.websocket.listener; +package io.github.mainstringargs.alpaca.websocket.broker.listener; -import io.github.mainstringargs.alpaca.websocket.message.AlpacaStreamMessageType; -import io.github.mainstringargs.domain.alpaca.websocket.AlpacaStreamMessage; +import io.github.mainstringargs.alpaca.websocket.broker.message.AlpacaStreamMessageType; +import io.github.mainstringargs.domain.alpaca.streaming.AlpacaStreamMessage; import java.util.Arrays; import java.util.HashSet; @@ -13,7 +13,7 @@ public class AlpacaStreamListenerAdapter implements AlpacaStreamListener { /** The message types. */ - private HashSet streamUpdateTypes = new HashSet<>(); + private final HashSet streamUpdateTypes = new HashSet<>(); /** * Instantiates a new Alpaca stream listener adapter. diff --git a/src/main/java/io/github/mainstringargs/alpaca/websocket/message/AlpacaStreamMessageType.java b/src/main/java/io/github/mainstringargs/alpaca/websocket/broker/message/AlpacaStreamMessageType.java similarity index 90% rename from src/main/java/io/github/mainstringargs/alpaca/websocket/message/AlpacaStreamMessageType.java rename to src/main/java/io/github/mainstringargs/alpaca/websocket/broker/message/AlpacaStreamMessageType.java index 47b0f8c7..a4345def 100644 --- a/src/main/java/io/github/mainstringargs/alpaca/websocket/message/AlpacaStreamMessageType.java +++ b/src/main/java/io/github/mainstringargs/alpaca/websocket/broker/message/AlpacaStreamMessageType.java @@ -1,4 +1,4 @@ -package io.github.mainstringargs.alpaca.websocket.message; +package io.github.mainstringargs.alpaca.websocket.broker.message; import com.google.gson.annotations.SerializedName; import io.github.mainstringargs.abstracts.enums.APIName; @@ -27,7 +27,7 @@ public enum AlpacaStreamMessageType implements StreamMessageType, APIName { ACCOUNT_UPDATES(true); /** The is api subscribable. */ - private boolean isAPISubscribable; + private final boolean isAPISubscribable; /** * Instantiates a new Alpaca stream message type. @@ -44,7 +44,7 @@ public String getAPIName() { } /** - * Is api subscribable boolean. + * Is the API subscribable. * * @return the boolean */ diff --git a/src/main/java/io/github/mainstringargs/alpaca/websocket/marketdata/client/MarketDataWebsocketClient.java b/src/main/java/io/github/mainstringargs/alpaca/websocket/marketdata/client/MarketDataWebsocketClient.java new file mode 100644 index 00000000..bc02ffd4 --- /dev/null +++ b/src/main/java/io/github/mainstringargs/alpaca/websocket/marketdata/client/MarketDataWebsocketClient.java @@ -0,0 +1,340 @@ +package io.github.mainstringargs.alpaca.websocket.marketdata.client; + +import com.google.common.base.Preconditions; +import com.google.gson.JsonArray; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import com.google.gson.JsonParseException; +import com.google.gson.JsonPrimitive; +import io.github.mainstringargs.abstracts.websocket.client.WebsocketClient; +import io.github.mainstringargs.abstracts.websocket.listener.StreamListener; +import io.github.mainstringargs.abstracts.websocket.message.StreamMessage; +import io.github.mainstringargs.abstracts.websocket.message.StreamMessageType; +import io.github.mainstringargs.alpaca.websocket.broker.listener.AlpacaStreamListener; +import io.github.mainstringargs.alpaca.websocket.marketdata.listener.MarketDataStreamListener; +import io.github.mainstringargs.alpaca.websocket.marketdata.message.MarketDataStreamMessageType; +import io.github.mainstringargs.domain.alpaca.marketdata.streaming.MarketDataStreamDataMessage; +import io.github.mainstringargs.domain.alpaca.marketdata.streaming.MarketDataStreamMessage; +import io.github.mainstringargs.domain.alpaca.marketdata.streaming.MarketDataStreamStatusMessage; +import io.github.mainstringargs.domain.alpaca.marketdata.streaming.aggregate.AggregateMinuteMessage; +import io.github.mainstringargs.domain.alpaca.marketdata.streaming.authorization.AuthorizationMessage; +import io.github.mainstringargs.domain.alpaca.marketdata.streaming.listening.ListeningMessage; +import io.github.mainstringargs.domain.alpaca.marketdata.streaming.quote.QuoteMessage; +import io.github.mainstringargs.domain.alpaca.marketdata.streaming.trade.TradeMessage; +import io.github.mainstringargs.util.gson.GsonUtil; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +import javax.websocket.DeploymentException; +import java.io.IOException; +import java.net.URI; +import java.net.URISyntaxException; +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import java.util.stream.Collectors; + +/** + * The type Alpaca websocket client. + */ +public class MarketDataWebsocketClient implements WebsocketClient { + + /** The logger. */ + private static final Logger LOGGER = LogManager.getLogger(MarketDataWebsocketClient.class); + + /** The constant STREAM_KEY. */ + private static final String STREAM_KEY = "stream"; + + /** The constant EVENT_TYPE_KEY. */ + private static final String EVENT_TYPE_KEY = "ev"; + + /** The key id. */ + private final String keyId; + + /** The secret. */ + private final String secret; + + /** The Stream API URL. */ + private final String streamAPIURL; + + /** The observers. */ + private final List listeners; + + /** The client end point. */ + private MarketDataWebsocketClientEndpoint marketDataWebsocketClientEndpoint; + + /** The Authenticated. */ + private boolean authenticated; + + /** + * Instantiates a new Market data websocket client. + * + * @param keyId the key id + * @param secret the secret + * @param baseDataUrl the base data url + */ + public MarketDataWebsocketClient(String keyId, String secret, String baseDataUrl) { + this.keyId = keyId; + this.secret = secret; + this.streamAPIURL = baseDataUrl.replace("https", "wss") + "/stream"; + + this.listeners = new ArrayList<>(); + } + + @Override + public void addListener(StreamListener streamListener) { + Preconditions.checkState(streamListener instanceof MarketDataStreamListener); + + if (listeners.isEmpty()) { + connect(); + } + + listeners.add((MarketDataStreamListener) streamListener); + + submitStreamRequest(); + } + + @Override + public void removeListener(StreamListener streamListener) { + Preconditions.checkState(streamListener instanceof MarketDataStreamListener); + + listeners.remove(streamListener); + + submitStreamRequest(); + + if (listeners.isEmpty()) { + disconnect(); + } + } + + @Override + public void connect() { + LOGGER.info("Connecting..."); + + try { + marketDataWebsocketClientEndpoint = new MarketDataWebsocketClientEndpoint(this, new URI(streamAPIURL)); + marketDataWebsocketClientEndpoint.connect(); + + LOGGER.info("Connected."); + } catch (URISyntaxException | DeploymentException | IOException e) { + LOGGER.throwing(e); + } + } + + @Override + public void disconnect() { + LOGGER.info("Disconnecting..."); + + try { + marketDataWebsocketClientEndpoint.getUserSession().close(); + + LOGGER.info("Disconnected."); + } catch (IOException e) { + LOGGER.throwing(e); + } + } + + @Override + public void sendAuthenticationMessage() { + /* Format of message is: + * { + * "action": "authenticate", + * "data": { + * "key_id": "{YOUR_API_KEY_ID}", + * "secret_key": "{YOUR_API_SECRET_KEY}" + * } + * } + */ + + JsonObject authRequest = new JsonObject(); + authRequest.addProperty("action", "authenticate"); + + JsonObject dataJson = new JsonObject(); + dataJson.addProperty("key_id", keyId); + dataJson.addProperty("secret_key", secret); + + authRequest.add("data", dataJson); + + marketDataWebsocketClientEndpoint.sendMessage(authRequest.toString()); + } + + @Override + public void handleWebsocketMessage(String message) { + JsonElement messageJsonElement = GsonUtil.JSON_PARSER.parse(message); + + Preconditions.checkState(messageJsonElement instanceof JsonObject); + JsonObject messageJsonObject = messageJsonElement.getAsJsonObject(); + + if (messageJsonObject.has(STREAM_KEY)) { + JsonElement streamJsonElement = messageJsonObject.get(STREAM_KEY); + + if (streamJsonElement instanceof JsonPrimitive) { + try { + MarketDataStreamMessageType marketDataStreamMessageType = GsonUtil.GSON.fromJson(streamJsonElement, + MarketDataStreamMessageType.class); + + switch (marketDataStreamMessageType) { + case AUTHORIZATION: + AuthorizationMessage authorizationMessage = GsonUtil.GSON.fromJson(messageJsonObject, + AuthorizationMessage.class); + sendStreamMessageToListeners(marketDataStreamMessageType, authorizationMessage); + + authenticated = isAuthorizationMessageSuccess(authorizationMessage); + + LOGGER.debug(authorizationMessage); + break; + case LISTENING: + ListeningMessage listeningMessage = GsonUtil.GSON.fromJson(messageJsonObject, + ListeningMessage.class); + sendStreamMessageToListeners(marketDataStreamMessageType, listeningMessage); + + LOGGER.debug(listeningMessage); + break; + default: + LOGGER.error("Unhandled stream type: " + marketDataStreamMessageType); + } + } catch (JsonParseException exception) { + LOGGER.error("Could not parse message: " + messageJsonObject, exception); + } + } else { + LOGGER.error("Unknown stream message: " + messageJsonObject); + } + } else { // Must be a data object with "ev" key + JsonElement eventTypeJsonElement = messageJsonObject.get(EVENT_TYPE_KEY); + + if (eventTypeJsonElement instanceof JsonPrimitive) { + try { + MarketDataStreamMessageType marketDataStreamMessageType = + GsonUtil.GSON.fromJson(eventTypeJsonElement, MarketDataStreamMessageType.class); + + switch (marketDataStreamMessageType) { + case TRADES: + TradeMessage tradeMessage = GsonUtil.GSON.fromJson(messageJsonObject, TradeMessage.class); + sendStreamMessageToListeners(marketDataStreamMessageType, tradeMessage); + break; + case QUOTES: + QuoteMessage quoteMessage = GsonUtil.GSON.fromJson(messageJsonObject, QuoteMessage.class); + quoteMessage.setSym(quoteMessage.getTicker()); + sendStreamMessageToListeners(marketDataStreamMessageType, quoteMessage); + break; + case AGGREGATE_MINUTE: + AggregateMinuteMessage aggregateMinuteMessage = GsonUtil.GSON.fromJson(messageJsonObject, + AggregateMinuteMessage.class); + sendStreamMessageToListeners(marketDataStreamMessageType, aggregateMinuteMessage); + break; + default: + LOGGER.error("Unhandled stream type: " + marketDataStreamMessageType); + } + } catch (JsonParseException exception) { + LOGGER.error("Could not parse message: " + messageJsonObject, exception); + } + } else { + LOGGER.error("Unknown stream message: " + messageJsonObject); + } + } + } + + @Override + public void sendStreamMessageToListeners(StreamMessageType streamMessageType, StreamMessage streamMessage) { + Preconditions.checkState(streamMessageType instanceof MarketDataStreamMessageType); + MarketDataStreamMessageType marketDataStreamMessageType = (MarketDataStreamMessageType) streamMessageType; + + if (streamMessage instanceof MarketDataStreamStatusMessage) { + for (MarketDataStreamListener marketDataStreamListener : listeners) { + if (marketDataStreamListener.getDataStreams() == null || + marketDataStreamListener.getDataStreams().isEmpty() || + marketDataStreamListener.getDataStreams().values().stream() + .anyMatch(types -> types.contains(MarketDataStreamMessageType.LISTENING) || + types.contains(MarketDataStreamMessageType.AUTHORIZATION))) { + marketDataStreamListener.onStreamUpdate(marketDataStreamMessageType, + (MarketDataStreamMessage) streamMessage); + } + } + } else if (streamMessage instanceof MarketDataStreamDataMessage) { + MarketDataStreamDataMessage marketDataStreamDataMessage = (MarketDataStreamDataMessage) streamMessage; + for (MarketDataStreamListener marketDataStreamListener : listeners) { + if (marketDataStreamListener.getDataStreams().containsKey(marketDataStreamDataMessage.getSym())) { + if (marketDataStreamListener.getDataStreams().get(marketDataStreamDataMessage.getSym()) + .contains(marketDataStreamMessageType)) { + marketDataStreamListener.onStreamUpdate(marketDataStreamMessageType, + marketDataStreamDataMessage); + } + } + } + } + } + + @Override + public boolean isConnected() { + return marketDataWebsocketClientEndpoint.getUserSession().isOpen(); + } + + @Override + public boolean isAuthenticated() { + return authenticated; + } + + /** + * Is authorization message success boolean. + * + * @param authorizationMessage the authorization message + * + * @return the boolean + */ + private boolean isAuthorizationMessageSuccess(AuthorizationMessage authorizationMessage) { + return authorizationMessage.getData().getStatus().equalsIgnoreCase("authorized") && + authorizationMessage.getData().getAction().equalsIgnoreCase("authenticate"); + } + + /** + * Submit stream request. + */ + private void submitStreamRequest() { + // Stream request example: + // { + // "action": "listen", + // "data": { + // "streams": ["T.SPY", "Q.SPY", "AM.SPY"] + // } + // } + + JsonObject streamRequestJsonObject = new JsonObject(); + streamRequestJsonObject.addProperty("action", "listen"); + + JsonArray streamsJsonArray = new JsonArray(); + getRegisteredMessageTypes().forEach(alpacaStreamMessageType -> + streamsJsonArray.add(alpacaStreamMessageType.getAPIName())); + + JsonObject dataJsonObject = new JsonObject(); + dataJsonObject.add("streams", streamsJsonArray); + + streamRequestJsonObject.add("data", dataJsonObject); + + marketDataWebsocketClientEndpoint.sendMessage(streamRequestJsonObject.toString()); + + LOGGER.info("Requested subscriptions to update to " + streamsJsonArray); + } + + /** + * Gets the registered message types. + * + * @return the registered message types + */ + public Set getRegisteredMessageTypes() { + Set registeredStreamMessageTypes = new HashSet<>(); + + for (AlpacaStreamListener alpacaStreamListener : listeners) { + Set alpacaStreamMessageTypes = alpacaStreamListener.getStreamMessageTypes(); + + // if its empty, assume they want everything + Set streamMessageTypesToAdd = alpacaStreamMessageTypes == null ? new HashSet<>() : + alpacaStreamMessageTypes.stream().filter(AlpacaStreamMessageType::isAPISubscribable) + .collect(Collectors.toSet()); + + registeredStreamMessageTypes.addAll(streamMessageTypesToAdd); + } + + return registeredStreamMessageTypes; + } +} diff --git a/src/main/java/io/github/mainstringargs/alpaca/websocket/marketdata/client/MarketDataWebsocketClientEndpoint.java b/src/main/java/io/github/mainstringargs/alpaca/websocket/marketdata/client/MarketDataWebsocketClientEndpoint.java new file mode 100644 index 00000000..1e1cce86 --- /dev/null +++ b/src/main/java/io/github/mainstringargs/alpaca/websocket/marketdata/client/MarketDataWebsocketClientEndpoint.java @@ -0,0 +1,45 @@ +package io.github.mainstringargs.alpaca.websocket.marketdata.client; + +import io.github.mainstringargs.abstracts.websocket.client.AbstractWebsocketClientEndpoint; +import io.github.mainstringargs.abstracts.websocket.client.WebsocketClient; + +import javax.websocket.ClientEndpoint; +import javax.websocket.CloseReason; +import javax.websocket.OnClose; +import javax.websocket.OnMessage; +import javax.websocket.OnOpen; +import javax.websocket.Session; +import java.net.URI; +import java.nio.charset.StandardCharsets; + +/** + * The type Market data websocket client endpoint. + */ +@ClientEndpoint(subprotocols = "BINARY") +public class MarketDataWebsocketClientEndpoint extends AbstractWebsocketClientEndpoint { + + /** + * Instantiates a new Market data websocket client endpoint. + * + * @param websocketClient the websocket client + * @param endpointURI the endpoint uri + */ + public MarketDataWebsocketClientEndpoint(WebsocketClient websocketClient, URI endpointURI) { + super(websocketClient, endpointURI, "MarketDataWebsocketThread"); + } + + @OnOpen + public void onOpenAnnotated(Session userSession) { + super.onOpen(userSession); + } + + @OnClose + public void onCloseAnnotated(Session userSession, CloseReason reason) { + super.onClose(userSession, reason); + } + + @OnMessage + public void onMessageAnnotated(byte[] message) { + super.onMessage(new String(message, StandardCharsets.UTF_8)); + } +} diff --git a/src/main/java/io/github/mainstringargs/alpaca/websocket/marketdata/listener/MarketDataStreamListener.java b/src/main/java/io/github/mainstringargs/alpaca/websocket/marketdata/listener/MarketDataStreamListener.java new file mode 100644 index 00000000..42869948 --- /dev/null +++ b/src/main/java/io/github/mainstringargs/alpaca/websocket/marketdata/listener/MarketDataStreamListener.java @@ -0,0 +1,22 @@ +package io.github.mainstringargs.alpaca.websocket.marketdata.listener; + +import io.github.mainstringargs.abstracts.websocket.listener.StreamListener; +import io.github.mainstringargs.alpaca.websocket.marketdata.message.MarketDataStreamMessageType; +import io.github.mainstringargs.domain.alpaca.marketdata.streaming.MarketDataStreamMessage; + +import java.util.Map; +import java.util.Set; + +/** + * An asynchronous update interface for receiving notifications about Websocket information as the Websocket is + * constructed. + */ +public interface MarketDataStreamListener extends StreamListener { + + /** + * Gets data streams. Null or empty to listen to all stream messages. + * + * @return the stream message types + */ + Map> getDataStreams(); +} diff --git a/src/main/java/io/github/mainstringargs/alpaca/websocket/marketdata/listener/MarketDataStreamListenerAdapter.java b/src/main/java/io/github/mainstringargs/alpaca/websocket/marketdata/listener/MarketDataStreamListenerAdapter.java new file mode 100644 index 00000000..a4970dc4 --- /dev/null +++ b/src/main/java/io/github/mainstringargs/alpaca/websocket/marketdata/listener/MarketDataStreamListenerAdapter.java @@ -0,0 +1,69 @@ +package io.github.mainstringargs.alpaca.websocket.marketdata.listener; + +import io.github.mainstringargs.alpaca.websocket.marketdata.message.MarketDataStreamMessageType; +import io.github.mainstringargs.domain.alpaca.marketdata.streaming.MarketDataStreamMessage; + +import java.util.Arrays; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; + +/** + * The type Market data stream listener adapter. + */ +public class MarketDataStreamListenerAdapter implements MarketDataStreamListener { + + /** The Data streams. */ + private final Map> dataStreams = new HashMap<>(); + + /** + * Instantiates a new Market data stream listener adapter. + * + * @param ticker the ticker + * @param dataMessageTypes the data message types + */ + public MarketDataStreamListenerAdapter(String ticker, MarketDataStreamMessageType... dataMessageTypes) { + if (dataMessageTypes != null && dataMessageTypes.length > 0) { + dataStreams.put(ticker, new HashSet<>(Arrays.asList(dataMessageTypes))); + } else { + dataStreams.put(ticker, new HashSet<>(Arrays.asList(MarketDataStreamMessageType.values()))); + } + } + + /** + * Instantiates a new Market data stream listener adapter. + * + * @param tickers the tickers + * @param dataMessageTypes the data message types + */ + public MarketDataStreamListenerAdapter(Set tickers, MarketDataStreamMessageType... dataMessageTypes) { + for (String ticker : tickers) { + if (dataMessageTypes != null && dataMessageTypes.length > 0) { + dataStreams.put(ticker, new HashSet<>(Arrays.asList(dataMessageTypes))); + } else { + dataStreams.put(ticker, new HashSet<>(Arrays.asList(MarketDataStreamMessageType.values()))); + } + } + } + + /** + * Instantiates a new Market data stream listener adapter. + * + * @param dataStreams the data streams + */ + public MarketDataStreamListenerAdapter(Map> dataStreams) { + for (Map.Entry> entry : dataStreams.entrySet()) { + dataStreams.put(entry.getKey(), new HashSet<>(entry.getValue())); + } + } + + @Override + public Map> getDataStreams() { + return dataStreams; + } + + @Override + public void onStreamUpdate(MarketDataStreamMessageType streamMessageType, MarketDataStreamMessage streamMessage) { + } +} diff --git a/src/main/java/io/github/mainstringargs/alpaca/websocket/marketdata/message/MarketDataStreamMessageType.java b/src/main/java/io/github/mainstringargs/alpaca/websocket/marketdata/message/MarketDataStreamMessageType.java new file mode 100644 index 00000000..2b4fb7e7 --- /dev/null +++ b/src/main/java/io/github/mainstringargs/alpaca/websocket/marketdata/message/MarketDataStreamMessageType.java @@ -0,0 +1,58 @@ +package io.github.mainstringargs.alpaca.websocket.marketdata.message; + +import com.google.gson.annotations.SerializedName; +import io.github.mainstringargs.abstracts.enums.APIName; +import io.github.mainstringargs.abstracts.websocket.message.StreamMessageType; +import io.github.mainstringargs.util.gson.GsonUtil; + +/** + * The enum Market data stream message type. + */ +public enum MarketDataStreamMessageType implements StreamMessageType, APIName { + + /** Listening market data stream message type. */ + @SerializedName("listening") + LISTENING(false), + + /** Authorization market data stream message type. */ + @SerializedName("authorization") + AUTHORIZATION(false), + + /** Trades market data stream message type. */ + @SerializedName("T") + TRADES(true), + + /** Quotes market data stream message type. */ + @SerializedName("Q") + QUOTES(true), + + /** Aggregate minute market data stream message type. */ + @SerializedName("AM") + AGGREGATE_MINUTE(true); + + /** The is api subscribable. */ + private final boolean isAPISubscribable; + + /** + * Instantiates a new Market data stream message type. + * + * @param isAPISubscribable the is api subscribable + */ + MarketDataStreamMessageType(boolean isAPISubscribable) { + this.isAPISubscribable = isAPISubscribable; + } + + @Override + public String getAPIName() { + return GsonUtil.GSON.toJsonTree(this).getAsString(); + } + + /** + * Is the API subscribable. + * + * @return the boolean + */ + public boolean isAPISubscribable() { + return isAPISubscribable; + } +} diff --git a/src/main/java/io/github/mainstringargs/polygon/websocket/client/PolygonWebsocketClient.java b/src/main/java/io/github/mainstringargs/polygon/websocket/client/PolygonWebsocketClient.java index a790fb9e..752ac2ab 100644 --- a/src/main/java/io/github/mainstringargs/polygon/websocket/client/PolygonWebsocketClient.java +++ b/src/main/java/io/github/mainstringargs/polygon/websocket/client/PolygonWebsocketClient.java @@ -50,13 +50,13 @@ public class PolygonWebsocketClient implements WebsocketClient { private static final String EVENT_TYPE_KEY = "ev"; /** The key id. */ - private String keyId; + private final String keyId; /** The Websocket url. */ - private String websocketURL; + private final String websocketURL; /** The listeners. */ - private List listeners; + private final List listeners; /** The client end point. */ private PolygonWebsocketClientEndpoint polygonWebsocketClientEndpoint; From 23006f48fb4520a925655114e58081c76d9fea09 Mon Sep 17 00:00:00 2001 From: foobar Date: Fri, 21 Aug 2020 23:37:27 -0400 Subject: [PATCH 003/271] Add trailing stop --- schema_json/alpaca/order/order.json | 14 ++++ schema_json/alpaca/position/position.json | 12 +++ .../mainstringargs/alpaca/AlpacaAPI.java | 75 +++++++++++++------ .../alpaca/AlpacaConstants.java | 9 +++ .../alpaca/enums/OrderType.java | 5 +- 5 files changed, 91 insertions(+), 24 deletions(-) diff --git a/schema_json/alpaca/order/order.json b/schema_json/alpaca/order/order.json index 16f377e0..36bf44b8 100644 --- a/schema_json/alpaca/order/order.json +++ b/schema_json/alpaca/order/order.json @@ -90,6 +90,20 @@ "existingJavaType": "java.lang.String", "title": "Stop price" }, + "trail_percent": { + "existingJavaType": "java.lang.String", + "title": "Stop trail percent" + }, + + "trail_price": { + "existingJavaType": "java.lang.String", + "title": "Stop trailing price" + }, + + "hwm": { + "existingJavaType": "java.lang.String", + "title": "hwm" + }, "filled_avg_price": { "existingJavaType": "java.lang.String", "title": "Stop price" diff --git a/schema_json/alpaca/position/position.json b/schema_json/alpaca/position/position.json index 254678d3..cf0684b1 100644 --- a/schema_json/alpaca/position/position.json +++ b/schema_json/alpaca/position/position.json @@ -65,6 +65,18 @@ "change_today": { "existingJavaType": "java.lang.String", "title": "Percent change from last day price (by a factor of 1)" + }, + "trail_percent": { + "existingJavaType": "java.lang.String", + "title": "Stop trail percent" + }, + "trail_price": { + "existingJavaType": "java.lang.String", + "title": "Stop trail price" + }, + "hwm": { + "existingJavaType": "java.lang.String", + "title": "the actual high water mark value tracked in Alpaca" } } } diff --git a/src/main/java/io/github/mainstringargs/alpaca/AlpacaAPI.java b/src/main/java/io/github/mainstringargs/alpaca/AlpacaAPI.java index 1acf9e44..f018b031 100644 --- a/src/main/java/io/github/mainstringargs/alpaca/AlpacaAPI.java +++ b/src/main/java/io/github/mainstringargs/alpaca/AlpacaAPI.java @@ -395,6 +395,12 @@ public ArrayList getOrders(OrderStatus status, Integer limit, ZonedDateTi * @param stopLossLimitPrice Additional parameters for stop-loss leg of advanced orders. The stop-loss order * becomes a stop-limit order if specified. * + * @param trailPercent required if type is trailing_limit + * + * @param trailPrice required if type is trailing_limit + * + * @param hwm optional if type is trailing_limit + * * @return the order * * @throws AlpacaAPIRequestException the alpaca API exception @@ -403,7 +409,7 @@ public ArrayList getOrders(OrderStatus status, Integer limit, ZonedDateTi public Order requestNewOrder(String symbol, Integer quantity, OrderSide side, OrderType type, OrderTimeInForce timeInForce, Double limitPrice, Double stopPrice, Boolean extendedHours, String clientOrderId, OrderClass orderClass, Double takeProfitLimitPrice, Double stopLossStopPrice, - Double stopLossLimitPrice) + Double stopLossLimitPrice, Double trailPrice, Double trailPercent, Double hwm) throws AlpacaAPIRequestException { Preconditions.checkNotNull(symbol); Preconditions.checkNotNull(quantity); @@ -463,6 +469,21 @@ public Order requestNewOrder(String symbol, Integer quantity, OrderSide side, Or urlBuilder.appendJSONBodyJSONProperty(AlpacaConstants.STOP_LOSS_PARAMETER, stopLoss); } + if(trailPrice != null) { + urlBuilder.appendJSONBodyProperty(AlpacaConstants.TRAIL_PRICE_PARAMETER, + TimeUtil.toDecimalFormat(trailPrice)); + } + + if(trailPercent != null) { + urlBuilder.appendJSONBodyProperty(AlpacaConstants.TRAIL_PERCENT_PARAMETER, + TimeUtil.toDecimalFormat(trailPercent)); + } + + if(hwm != null) { + urlBuilder.appendJSONBodyProperty(AlpacaConstants.HWM_PARAMETER, + TimeUtil.toDecimalFormat(hwm)); + } + HttpResponse response = alpacaRequest.invokePost(urlBuilder); if (response.getStatus() != 200) { @@ -476,7 +497,7 @@ public Order requestNewOrder(String symbol, Integer quantity, OrderSide side, Or * A market order is a request to buy or sell a security at the currently available market price. It provides the * most likely method of filling an order. Market orders fill nearly instantaneously. This method calls {@link * #requestNewOrder(String, Integer, OrderSide, OrderType, OrderTimeInForce, Double, Double, Boolean, String, - * OrderClass, Double, Double, Double)} with {@link OrderType#MARKET}. + * OrderClass, Double, Double, Double, Double, Double, Double)} with {@link OrderType#MARKET}. * * @param symbol symbol or asset ID to identify the asset to trade * @param quantity number of shares to trade @@ -493,7 +514,7 @@ public Order requestNewOrder(String symbol, Integer quantity, OrderSide side, Or public Order requestNewMarketOrder(String symbol, Integer quantity, OrderSide side, OrderTimeInForce timeInForce, Boolean extendedHours) throws AlpacaAPIRequestException { return requestNewOrder(symbol, quantity, side, OrderType.MARKET, timeInForce, null, null, extendedHours, - null, OrderClass.SIMPLE, null, null, null); + null, OrderClass.SIMPLE, null, null, null, null, null, null); } /** @@ -501,7 +522,8 @@ public Order requestNewMarketOrder(String symbol, Integer quantity, OrderSide si * is executed at the specified limit price or lower (i.e., better). Conversely, a sell limit order (a limit order * to sell) is executed at the specified limit price or higher (better). Unlike a market order, you have to specify * the limit price parameter when submitting your order. This method calls {@link #requestNewOrder(String, Integer, - * OrderSide, OrderType, OrderTimeInForce, Double, Double, Boolean, String, OrderClass, Double, Double, Double)} + * OrderSide, OrderType, OrderTimeInForce, Double, Double, Boolean, String, OrderClass, Double, Double, Double, + * Double, Double, Double)} * with {@link OrderType#LIMIT}. * * @param symbol symbol or asset ID to identify the asset to trade @@ -520,7 +542,7 @@ public Order requestNewMarketOrder(String symbol, Integer quantity, OrderSide si public Order requestNewLimitOrder(String symbol, Integer quantity, OrderSide side, OrderTimeInForce timeInForce, Double limitPrice, Boolean extendedHours) throws AlpacaAPIRequestException { return requestNewOrder(symbol, quantity, side, OrderType.LIMIT, timeInForce, limitPrice, null, extendedHours, - null, OrderClass.SIMPLE, null, null, null); + null, OrderClass.SIMPLE, null, null, null, null, null, null); } /** @@ -530,7 +552,7 @@ public Order requestNewLimitOrder(String symbol, Integer quantity, OrderSide sid * orders with a limit price that is 4% higher than a stop price < $50 (or 2.5% higher than a stop price >= * $50). Sell stop orders are not converted into stop limit orders. This method calls {@link * #requestNewOrder(String, Integer, OrderSide, OrderType, OrderTimeInForce, Double, Double, Boolean, String, - * OrderClass, Double, Double, Double)} with {@link OrderType#STOP}. + * OrderClass, Double, Double, Double, Double, Double, Double)} with {@link OrderType#STOP}. * * @param symbol symbol or asset ID to identify the asset to trade * @param quantity number of shares to trade @@ -548,7 +570,7 @@ public Order requestNewLimitOrder(String symbol, Integer quantity, OrderSide sid public Order requestNewStopOrder(String symbol, Integer quantity, OrderSide side, OrderTimeInForce timeInForce, Double stopPrice, Boolean extendedHours) throws AlpacaAPIRequestException { return requestNewOrder(symbol, quantity, side, OrderType.STOP, timeInForce, null, stopPrice, extendedHours, - null, OrderClass.SIMPLE, null, null, null); + null, OrderClass.SIMPLE, null, null, null, null, null, null); } /** @@ -557,7 +579,7 @@ public Order requestNewStopOrder(String symbol, Integer quantity, OrderSide side * price, or better, after a given stop price has been reached. Once the stop price is reached, the stop-limit order * becomes a limit order to buy or sell at the limit price or better. This method calls {@link * #requestNewOrder(String, Integer, OrderSide, OrderType, OrderTimeInForce, Double, Double, Boolean, String, - * OrderClass, Double, Double, Double)} with {@link OrderType#STOP_LIMIT}. + * OrderClass, Double, Double, Double, Double, Double, Double)} with {@link OrderType#STOP_LIMIT}. * * @param symbol symbol or asset ID to identify the asset to trade * @param quantity number of shares to trade @@ -576,14 +598,15 @@ public Order requestNewStopOrder(String symbol, Integer quantity, OrderSide side public Order requestNewStopLimitOrder(String symbol, Integer quantity, OrderSide side, OrderTimeInForce timeInForce, Double limitPrice, Double stopPrice, Boolean extendedHours) throws AlpacaAPIRequestException { return requestNewOrder(symbol, quantity, side, OrderType.STOP_LIMIT, timeInForce, limitPrice, stopPrice, - extendedHours, null, OrderClass.SIMPLE, null, null, null); + extendedHours, null, OrderClass.SIMPLE, null, null, null, null, null, null); } /** * A bracket order is a chain of three orders that can be used to manage your position entry and exit. It is a * common use case of an OTOCO (One Triggers OCO {One Cancels Other}) order. This method calls {@link * #requestNewOrder(String, Integer, OrderSide, OrderType, OrderTimeInForce, Double, Double, Boolean, String, - * OrderClass, Double, Double, Double)} with {@link OrderType#MARKET} and with parameters for a bracket order. + * OrderClass, Double, Double, Double, Double, Double, Double)} with {@link OrderType#MARKET} and with + * parameters for a bracket order. * * @param symbol symbol or asset ID to identify the asset to trade * @param quantity number of shares to trade @@ -607,14 +630,15 @@ public Order requestNewMarketBracketOrder(String symbol, Integer quantity, Order OrderTimeInForce timeInForce, Boolean extendedHours, Double takeProfitLimitPrice, Double stopLossStopPrice, Double stopLossLimitPrice) throws AlpacaAPIRequestException { return requestNewOrder(symbol, quantity, side, OrderType.MARKET, timeInForce, null, null, - extendedHours, null, OrderClass.BRACKET, takeProfitLimitPrice, stopLossStopPrice, stopLossLimitPrice); + extendedHours, null, OrderClass.BRACKET, takeProfitLimitPrice, stopLossStopPrice, + stopLossLimitPrice, null, null, null); } /** * A bracket order is a chain of three orders that can be used to manage your position entry and exit. It is a * common use case of an OTOCO (One Triggers OCO {One Cancels Other}) order. This method calls {@link * #requestNewOrder(String, Integer, OrderSide, OrderType, OrderTimeInForce, Double, Double, Boolean, String, - * OrderClass, Double, Double, Double)} with {@link OrderType#LIMIT} and with parameters for a bracket order. + * OrderClass, Double, Double, Double, Double, Double, Double)} with {@link OrderType#LIMIT} and with parameters for a bracket order. * * @param symbol symbol or asset ID to identify the asset to trade * @param quantity number of shares to trade @@ -639,7 +663,7 @@ public Order requestNewLimitBracketOrder(String symbol, Integer quantity, OrderS OrderTimeInForce timeInForce, Double limitPrice, Boolean extendedHours, Double takeProfitLimitPrice, Double stopLossStopPrice, Double stopLossLimitPrice) throws AlpacaAPIRequestException { return requestNewOrder(symbol, quantity, side, OrderType.LIMIT, timeInForce, limitPrice, null, - extendedHours, null, OrderClass.BRACKET, takeProfitLimitPrice, stopLossStopPrice, stopLossLimitPrice); + extendedHours, null, OrderClass.BRACKET, takeProfitLimitPrice, stopLossStopPrice, stopLossLimitPrice, null, null, null); } /** @@ -647,7 +671,8 @@ public Order requestNewLimitBracketOrder(String symbol, Integer quantity, OrderS * (buy/buy or sell/sell) and currently only exit order is supported. In other words, this is the second part of the * bracket orders where the entry order is already filled, and you can submit the take-profit and stop-loss in one * order submission. This method calls {@link #requestNewOrder(String, Integer, OrderSide, OrderType, - * OrderTimeInForce, Double, Double, Boolean, String, OrderClass, Double, Double, Double)} with parameters for an + * OrderTimeInForce, Double, Double, Boolean, String, OrderClass, Double, Double, Double, Double, Double, Double)} + * with parameters for an * OCO order type. * * @param symbol symbol or asset ID to identify the asset to trade @@ -670,13 +695,14 @@ public Order requestNewOCOOrder(String symbol, Integer quantity, OrderSide side, OrderTimeInForce timeInForce, Boolean extendedHours, Double takeProfitLimitPrice, Double stopLossStopPrice, Double stopLossLimitPrice) throws AlpacaAPIRequestException { return requestNewOrder(symbol, quantity, side, OrderType.LIMIT, timeInForce, null, null, extendedHours, null, - OrderClass.OCO, takeProfitLimitPrice, stopLossStopPrice, stopLossLimitPrice); + OrderClass.OCO, takeProfitLimitPrice, stopLossStopPrice, stopLossLimitPrice, null, null, null); } /** * OTO (One-Triggers-Other) is a variant of bracket order. It takes one of the take-profit or stop-loss order in * addition to the entry order. This method calls {@link #requestNewOrder(String, Integer, OrderSide, OrderType, - * OrderTimeInForce, Double, Double, Boolean, String, OrderClass, Double, Double, Double)} with {@link + * OrderTimeInForce, Double, Double, Boolean, String, OrderClass, Double, Double, Double, Double, Double, Double)} + * with {@link * OrderType#LIMIT} and with parameters for an OTO order type. * * @param symbol symbol or asset ID to identify the asset to trade @@ -699,13 +725,14 @@ public Order requestNewOTOMarketOrder(String symbol, Integer quantity, OrderSide OrderTimeInForce timeInForce, Boolean extendedHours, Double takeProfitLimitPrice, Double stopLossStopPrice, Double stopLossLimitPrice) throws AlpacaAPIRequestException { return requestNewOrder(symbol, quantity, side, OrderType.MARKET, timeInForce, null, null, extendedHours, null, - OrderClass.OTO, takeProfitLimitPrice, stopLossStopPrice, stopLossLimitPrice); + OrderClass.OTO, takeProfitLimitPrice, stopLossStopPrice, stopLossLimitPrice, null, null, null); } /** * OTO (One-Triggers-Other) is a variant of bracket order. It takes one of the take-profit or stop-loss order in * addition to the entry order. This method calls {@link #requestNewOrder(String, Integer, OrderSide, OrderType, - * OrderTimeInForce, Double, Double, Boolean, String, OrderClass, Double, Double, Double)} with {@link + * OrderTimeInForce, Double, Double, Boolean, String, OrderClass, Double, Double, Double, Double, Double, Double)} + * with {@link * OrderType#LIMIT} and with parameters for an OTO order type. * * @param symbol symbol or asset ID to identify the asset to trade @@ -729,13 +756,14 @@ public Order requestNewOTOLimitOrder(String symbol, Integer quantity, OrderSide OrderTimeInForce timeInForce, Double limitPrice, Boolean extendedHours, Double takeProfitLimitPrice, Double stopLossStopPrice, Double stopLossLimitPrice) throws AlpacaAPIRequestException { return requestNewOrder(symbol, quantity, side, OrderType.LIMIT, timeInForce, limitPrice, null, extendedHours, - null, OrderClass.OTO, takeProfitLimitPrice, stopLossStopPrice, stopLossLimitPrice); + null, OrderClass.OTO, takeProfitLimitPrice, stopLossStopPrice, stopLossLimitPrice, null, null, null); } /** * OTO (One-Triggers-Other) is a variant of bracket order. It takes one of the take-profit or stop-loss order in * addition to the entry order. This method calls {@link #requestNewOrder(String, Integer, OrderSide, OrderType, - * OrderTimeInForce, Double, Double, Boolean, String, OrderClass, Double, Double, Double)} with {@link + * OrderTimeInForce, Double, Double, Boolean, String, OrderClass, Double, Double, Double, Double, Double, Double)} + * with {@link * OrderType#STOP} and with parameters for an OTO order type. * * @param symbol symbol or asset ID to identify the asset to trade @@ -759,13 +787,14 @@ public Order requestNewOTOStopOrder(String symbol, Integer quantity, OrderSide s OrderTimeInForce timeInForce, Double stopPrice, Boolean extendedHours, Double takeProfitLimitPrice, Double stopLossStopPrice, Double stopLossLimitPrice) throws AlpacaAPIRequestException { return requestNewOrder(symbol, quantity, side, OrderType.STOP, timeInForce, null, stopPrice, extendedHours, - null, OrderClass.OTO, takeProfitLimitPrice, stopLossStopPrice, stopLossLimitPrice); + null, OrderClass.OTO, takeProfitLimitPrice, stopLossStopPrice, stopLossLimitPrice, null, null, null); } /** * OTO (One-Triggers-Other) is a variant of bracket order. It takes one of the take-profit or stop-loss order in * addition to the entry order. This method calls {@link #requestNewOrder(String, Integer, OrderSide, OrderType, - * OrderTimeInForce, Double, Double, Boolean, String, OrderClass, Double, Double, Double)} with {@link + * OrderTimeInForce, Double, Double, Boolean, String, OrderClass, Double, Double, Double, Double, Double, Double)} + * with {@link * OrderType#STOP_LIMIT} and with parameters for an OTO order type. * * @param symbol symbol or asset ID to identify the asset to trade @@ -791,7 +820,7 @@ public Order requestNewOTOStopLimitOrder(String symbol, Integer quantity, OrderS Double takeProfitLimitPrice, Double stopLossStopPrice, Double stopLossLimitPrice) throws AlpacaAPIRequestException { return requestNewOrder(symbol, quantity, side, OrderType.STOP_LIMIT, timeInForce, limitPrice, stopPrice, - extendedHours, null, OrderClass.OTO, takeProfitLimitPrice, stopLossStopPrice, stopLossLimitPrice); + extendedHours, null, OrderClass.OTO, takeProfitLimitPrice, stopLossStopPrice, stopLossLimitPrice, null, null, null); } /** diff --git a/src/main/java/io/github/mainstringargs/alpaca/AlpacaConstants.java b/src/main/java/io/github/mainstringargs/alpaca/AlpacaConstants.java index c1b406cb..c8eb8610 100644 --- a/src/main/java/io/github/mainstringargs/alpaca/AlpacaConstants.java +++ b/src/main/java/io/github/mainstringargs/alpaca/AlpacaConstants.java @@ -41,6 +41,15 @@ public class AlpacaConstants { /** The Constant STOP_PRICE_PARAMETER. */ public static final String STOP_PRICE_PARAMETER = "stop_price"; + /** The Constant TRAIL_PRICE_PARAMETER. */ + public static final String TRAIL_PRICE_PARAMETER = "trail_price"; + + /** The Constant TRAIL_PERCENT_PARAMETER. */ + public static final String TRAIL_PERCENT_PARAMETER = "trail_percent"; + + /** The Constant HWM_PARAMETER. */ + public static final String HWM_PARAMETER = "hwm"; + /** The Constant LIMIT_PRICE_PARAMETER. */ public static final String LIMIT_PRICE_PARAMETER = "limit_price"; diff --git a/src/main/java/io/github/mainstringargs/alpaca/enums/OrderType.java b/src/main/java/io/github/mainstringargs/alpaca/enums/OrderType.java index 0f2cc3ba..553a45c2 100644 --- a/src/main/java/io/github/mainstringargs/alpaca/enums/OrderType.java +++ b/src/main/java/io/github/mainstringargs/alpaca/enums/OrderType.java @@ -17,7 +17,10 @@ public enum OrderType implements APIName { STOP("stop"), /** The stop limit. */ - STOP_LIMIT("stop_limit"); + STOP_LIMIT("stop_limit"), + + /** The trailing stop. */ + TRAILING_STOP("trailing_stop"); /** The api name. */ String apiName; From 34192fe069a4927477350a87caf9b36e1c612b36 Mon Sep 17 00:00:00 2001 From: foobar Date: Thu, 27 Aug 2020 14:50:43 -0400 Subject: [PATCH 004/271] Reconnect when a 1006 occurs --- .../AbstractWebsocketClientEndpoint.java | 44 +++++++++++++------ .../client/AlpacaWebsocketClient.java | 12 ++--- .../client/PolygonWebsocketClient.java | 36 +++++++-------- 3 files changed, 53 insertions(+), 39 deletions(-) diff --git a/src/main/java/io/github/mainstringargs/abstracts/websocket/client/AbstractWebsocketClientEndpoint.java b/src/main/java/io/github/mainstringargs/abstracts/websocket/client/AbstractWebsocketClientEndpoint.java index 4a099656..4914506d 100644 --- a/src/main/java/io/github/mainstringargs/abstracts/websocket/client/AbstractWebsocketClientEndpoint.java +++ b/src/main/java/io/github/mainstringargs/abstracts/websocket/client/AbstractWebsocketClientEndpoint.java @@ -25,7 +25,7 @@ public abstract class AbstractWebsocketClientEndpoint { private static final Logger LOGGER = LogManager.getLogger(AbstractWebsocketClientEndpoint.class); /** The Websocket client. */ - private WebsocketClient websocketClient; + private final WebsocketClient websocketClient; /** The Endpoint uri. */ private final URI endpointURI; @@ -39,6 +39,8 @@ public abstract class AbstractWebsocketClientEndpoint { /** The Retry attempts. */ private int retryAttempts = 0; + private String subscription; + /** * Instantiates a new Abstract websocket client endpoint. * @@ -62,7 +64,7 @@ public AbstractWebsocketClientEndpoint(WebsocketClient websocketClient, URI endp public void connect() throws DeploymentException, IOException { WebSocketContainer container = ContainerProvider.getWebSocketContainer(); - LOGGER.info("Connecting to " + endpointURI); + LOGGER.info("Connecting to {}", endpointURI); container.connectToServer(this, endpointURI); } @@ -86,10 +88,8 @@ public void disconnect() throws IOException { protected void onOpen(Session userSession) { this.userSession = userSession; - LOGGER.debug("onOpen " + userSession); - LOGGER.info("Websocket opened"); - - LOGGER.info("Authenticating..."); + LOGGER.debug("onOpen {}", userSession); + LOGGER.info("Websocket opened... Authenticating..."); websocketClient.sendAuthenticationMessage(); } @@ -100,13 +100,12 @@ protected void onOpen(Session userSession) { * @param reason the reason */ protected void onClose(Session userSession, CloseReason reason) { - this.userSession = null; - LOGGER.debug("onClose " + userSession); + LOGGER.debug("onClose {}", userSession); if (!reason.getCloseCode().equals(CloseReason.CloseCodes.NORMAL_CLOSURE)) { if (retryAttempts > 5) { - LOGGER.error("More than 5 attempts to reconnect were made."); + LOGGER.error("More than 5 attempts to reconnect were made. Bailing out."); return; } @@ -119,19 +118,28 @@ protected void onClose(Session userSession, CloseReason reason) { e1.printStackTrace(); } - LOGGER.info("Reconnecting due to closure " + + LOGGER.info("Reconnecting due to closure: {}", CloseReason.CloseCodes.getCloseCode(reason.getCloseCode().getCode())); try { - connect(); + reconnectAndResubscribe(); + retryAttempts = 0; // hopefully we are connected by this time, so reset the retry counter } catch (Exception e) { LOGGER.catching(e); } } else { + this.userSession = null; LOGGER.info("Websocket closed"); } } + private void reconnectAndResubscribe() throws IOException, DeploymentException { + LOGGER.info("Attempting to re-connect"); + connect(); + LOGGER.info("Resending subscriptions: {}", subscription); + sendSubscription(subscription); + } + /** * On message. * @@ -146,12 +154,20 @@ protected void onMessage(String message) { * * @param message the message */ - public void sendMessage(String message) { - LOGGER.debug("sendMessage " + message); - + private void sendMessage(String message) { + LOGGER.debug("sendMessage {}", message); userSession.getAsyncRemote().sendText(message); } + public void sendAuthentication(String message) { + sendMessage(message); + } + + public void sendSubscription(String message) { + this.subscription = message; + sendMessage(message); + } + /** * Gets user session. * diff --git a/src/main/java/io/github/mainstringargs/alpaca/websocket/client/AlpacaWebsocketClient.java b/src/main/java/io/github/mainstringargs/alpaca/websocket/client/AlpacaWebsocketClient.java index 6e79c789..def1d4da 100644 --- a/src/main/java/io/github/mainstringargs/alpaca/websocket/client/AlpacaWebsocketClient.java +++ b/src/main/java/io/github/mainstringargs/alpaca/websocket/client/AlpacaWebsocketClient.java @@ -43,16 +43,16 @@ public class AlpacaWebsocketClient implements WebsocketClient { private static final String STREAM_KEY = "stream"; /** The key id. */ - private String keyId; + private final String keyId; /** The secret. */ - private String secret; + private final String secret; /** The Base api url. */ - private String baseAPIURL; + private final String baseAPIURL; /** The observers. */ - private List listeners; + private final List listeners; /** The client end point. */ private AlpacaWebsocketClientEndpoint alpacaWebsocketClientEndpoint; @@ -149,7 +149,7 @@ public void sendAuthenticationMessage() { authRequest.add("data", payload); - alpacaWebsocketClientEndpoint.sendMessage(authRequest.toString()); + alpacaWebsocketClientEndpoint.sendAuthentication(authRequest.toString()); } @Override @@ -265,7 +265,7 @@ private void submitStreamRequestUpdate() { streamRequestJsonObject.add("data", dataJsonObject); - alpacaWebsocketClientEndpoint.sendMessage(streamRequestJsonObject.toString()); + alpacaWebsocketClientEndpoint.sendSubscription(streamRequestJsonObject.toString()); LOGGER.info("Requested subscriptions to update to " + streamsJsonArray); } diff --git a/src/main/java/io/github/mainstringargs/polygon/websocket/client/PolygonWebsocketClient.java b/src/main/java/io/github/mainstringargs/polygon/websocket/client/PolygonWebsocketClient.java index a790fb9e..7d96ded9 100644 --- a/src/main/java/io/github/mainstringargs/polygon/websocket/client/PolygonWebsocketClient.java +++ b/src/main/java/io/github/mainstringargs/polygon/websocket/client/PolygonWebsocketClient.java @@ -50,13 +50,13 @@ public class PolygonWebsocketClient implements WebsocketClient { private static final String EVENT_TYPE_KEY = "ev"; /** The key id. */ - private String keyId; + private final String keyId; /** The Websocket url. */ - private String websocketURL; + private final String websocketURL; /** The listeners. */ - private List listeners; + private final List listeners; /** The client end point. */ private PolygonWebsocketClientEndpoint polygonWebsocketClientEndpoint; @@ -122,8 +122,7 @@ public void disconnect() { LOGGER.info("Disconnecting..."); try { - polygonWebsocketClientEndpoint.getUserSession().close(); - + polygonWebsocketClientEndpoint.disconnect(); LOGGER.info("Disconnected."); } catch (IOException e) { LOGGER.throwing(e); @@ -138,7 +137,7 @@ public void sendAuthenticationMessage() { authRequest.addProperty("action", "auth"); authRequest.addProperty("params", keyId); - polygonWebsocketClientEndpoint.sendMessage(authRequest.toString()); + polygonWebsocketClientEndpoint.sendAuthentication(authRequest.toString()); } @Override @@ -183,13 +182,13 @@ public void handleWebsocketMessage(String message) { GsonUtil.GSON.fromJson(messageJsonObject, AggregatePerMinuteMessage.class)); break; default: - LOGGER.error("Unknown stream object: " + messageJsonObject); + LOGGER.error("Unknown stream object: {}", messageJsonObject); } } catch (JsonSyntaxException e) { LOGGER.throwing(e); } } else { - LOGGER.error("Unknown stream message: " + messageJsonObject); + LOGGER.error("Unknown stream message: {}", messageJsonObject); } } } @@ -274,20 +273,19 @@ private void submitStreamRequest(PolygonStreamAction polygonStreamAction, Polygo if (isTickerChannelRegistered) { if (polygonStreamAction == PolygonStreamAction.UNSUBSCRIBE) { - LOGGER.warn("Cannot unsubscribe from " + ticker + " for channel " + - listenerChannelType.name() + - " because it is being used by another stream listener!"); + LOGGER.warn( + "Cannot unsubscribe from {} for channel {} because it is being used by another stream listener!", + ticker, listenerChannelType.name()); } else if (polygonStreamAction == PolygonStreamAction.SUBSCRIBE) { - LOGGER.warn("Already subscribed to " + ticker + " for channel " + - listenerChannelType.name() + "!"); + LOGGER.warn("Already subscribed to {} for channel {}", ticker, listenerChannelType.name()); } } else { // Not a registered channel by other stream listeners actionTickerList.add(formattedWebsocketTicker); if (polygonStreamAction == PolygonStreamAction.UNSUBSCRIBE) { - LOGGER.info(("Unsubscribing from " + formattedWebsocketTicker)); + LOGGER.info("Unsubscribing from {}", formattedWebsocketTicker); } else if (polygonStreamAction == PolygonStreamAction.SUBSCRIBE) { - LOGGER.info(("Subscribing to " + formattedWebsocketTicker)); + LOGGER.info("Subscribing to {}", formattedWebsocketTicker); } } } @@ -302,9 +300,9 @@ private void submitStreamRequest(PolygonStreamAction polygonStreamAction, Polygo actionTickerList.add(formattedWebsocketTicker); if (polygonStreamAction == PolygonStreamAction.UNSUBSCRIBE) { - LOGGER.info(("Unsubscribing from " + formattedWebsocketTicker)); + LOGGER.info("Unsubscribing from: {}", formattedWebsocketTicker); } else if (polygonStreamAction == PolygonStreamAction.SUBSCRIBE) { - LOGGER.info(("Subscribing to " + formattedWebsocketTicker)); + LOGGER.info("Subscribing to: {}", formattedWebsocketTicker); } } } @@ -322,9 +320,9 @@ private void submitStreamRequest(PolygonStreamAction polygonStreamAction, Polygo actionJsonObject.addProperty("action", polygonStreamAction.getAPIName()); actionJsonObject.addProperty("params", commaActionTickers.toString()); - polygonWebsocketClientEndpoint.sendMessage(actionJsonObject.toString()); + polygonWebsocketClientEndpoint.sendSubscription(actionJsonObject.toString()); - LOGGER.info(("Requested subscriptions to update to " + getRegisteredTickerChannels(null))); + LOGGER.info("Requested subscriptions to update for: {}", getRegisteredTickerChannels(null)); } } From 922e849b1455f60ecb1f555736c61dfaba667a67 Mon Sep 17 00:00:00 2001 From: foobar Date: Thu, 27 Aug 2020 15:54:33 -0400 Subject: [PATCH 005/271] Remove `hwm` from the order request - it is not a field that can be set --- .../mainstringargs/alpaca/AlpacaAPI.java | 53 ++++++++----------- 1 file changed, 23 insertions(+), 30 deletions(-) diff --git a/src/main/java/io/github/mainstringargs/alpaca/AlpacaAPI.java b/src/main/java/io/github/mainstringargs/alpaca/AlpacaAPI.java index f018b031..bd20e473 100644 --- a/src/main/java/io/github/mainstringargs/alpaca/AlpacaAPI.java +++ b/src/main/java/io/github/mainstringargs/alpaca/AlpacaAPI.java @@ -399,8 +399,6 @@ public ArrayList getOrders(OrderStatus status, Integer limit, ZonedDateTi * * @param trailPrice required if type is trailing_limit * - * @param hwm optional if type is trailing_limit - * * @return the order * * @throws AlpacaAPIRequestException the alpaca API exception @@ -409,7 +407,7 @@ public ArrayList getOrders(OrderStatus status, Integer limit, ZonedDateTi public Order requestNewOrder(String symbol, Integer quantity, OrderSide side, OrderType type, OrderTimeInForce timeInForce, Double limitPrice, Double stopPrice, Boolean extendedHours, String clientOrderId, OrderClass orderClass, Double takeProfitLimitPrice, Double stopLossStopPrice, - Double stopLossLimitPrice, Double trailPrice, Double trailPercent, Double hwm) + Double stopLossLimitPrice, Double trailPrice, Double trailPercent) throws AlpacaAPIRequestException { Preconditions.checkNotNull(symbol); Preconditions.checkNotNull(quantity); @@ -479,11 +477,6 @@ public Order requestNewOrder(String symbol, Integer quantity, OrderSide side, Or TimeUtil.toDecimalFormat(trailPercent)); } - if(hwm != null) { - urlBuilder.appendJSONBodyProperty(AlpacaConstants.HWM_PARAMETER, - TimeUtil.toDecimalFormat(hwm)); - } - HttpResponse response = alpacaRequest.invokePost(urlBuilder); if (response.getStatus() != 200) { @@ -497,7 +490,7 @@ public Order requestNewOrder(String symbol, Integer quantity, OrderSide side, Or * A market order is a request to buy or sell a security at the currently available market price. It provides the * most likely method of filling an order. Market orders fill nearly instantaneously. This method calls {@link * #requestNewOrder(String, Integer, OrderSide, OrderType, OrderTimeInForce, Double, Double, Boolean, String, - * OrderClass, Double, Double, Double, Double, Double, Double)} with {@link OrderType#MARKET}. + * OrderClass, Double, Double, Double, Double, Double)} with {@link OrderType#MARKET}. * * @param symbol symbol or asset ID to identify the asset to trade * @param quantity number of shares to trade @@ -514,7 +507,7 @@ public Order requestNewOrder(String symbol, Integer quantity, OrderSide side, Or public Order requestNewMarketOrder(String symbol, Integer quantity, OrderSide side, OrderTimeInForce timeInForce, Boolean extendedHours) throws AlpacaAPIRequestException { return requestNewOrder(symbol, quantity, side, OrderType.MARKET, timeInForce, null, null, extendedHours, - null, OrderClass.SIMPLE, null, null, null, null, null, null); + null, OrderClass.SIMPLE, null, null, null, null, null); } /** @@ -522,7 +515,7 @@ public Order requestNewMarketOrder(String symbol, Integer quantity, OrderSide si * is executed at the specified limit price or lower (i.e., better). Conversely, a sell limit order (a limit order * to sell) is executed at the specified limit price or higher (better). Unlike a market order, you have to specify * the limit price parameter when submitting your order. This method calls {@link #requestNewOrder(String, Integer, - * OrderSide, OrderType, OrderTimeInForce, Double, Double, Boolean, String, OrderClass, Double, Double, Double, + * OrderSide, OrderType, OrderTimeInForce, Double, Double, Boolean, String, OrderClass, Double, Double, * Double, Double, Double)} * with {@link OrderType#LIMIT}. * @@ -542,7 +535,7 @@ public Order requestNewMarketOrder(String symbol, Integer quantity, OrderSide si public Order requestNewLimitOrder(String symbol, Integer quantity, OrderSide side, OrderTimeInForce timeInForce, Double limitPrice, Boolean extendedHours) throws AlpacaAPIRequestException { return requestNewOrder(symbol, quantity, side, OrderType.LIMIT, timeInForce, limitPrice, null, extendedHours, - null, OrderClass.SIMPLE, null, null, null, null, null, null); + null, OrderClass.SIMPLE, null, null, null, null, null); } /** @@ -552,7 +545,7 @@ public Order requestNewLimitOrder(String symbol, Integer quantity, OrderSide sid * orders with a limit price that is 4% higher than a stop price < $50 (or 2.5% higher than a stop price >= * $50). Sell stop orders are not converted into stop limit orders. This method calls {@link * #requestNewOrder(String, Integer, OrderSide, OrderType, OrderTimeInForce, Double, Double, Boolean, String, - * OrderClass, Double, Double, Double, Double, Double, Double)} with {@link OrderType#STOP}. + * OrderClass, Double, Double, Double, Double, Double)} with {@link OrderType#STOP}. * * @param symbol symbol or asset ID to identify the asset to trade * @param quantity number of shares to trade @@ -570,7 +563,7 @@ public Order requestNewLimitOrder(String symbol, Integer quantity, OrderSide sid public Order requestNewStopOrder(String symbol, Integer quantity, OrderSide side, OrderTimeInForce timeInForce, Double stopPrice, Boolean extendedHours) throws AlpacaAPIRequestException { return requestNewOrder(symbol, quantity, side, OrderType.STOP, timeInForce, null, stopPrice, extendedHours, - null, OrderClass.SIMPLE, null, null, null, null, null, null); + null, OrderClass.SIMPLE, null, null, null, null, null); } /** @@ -579,7 +572,7 @@ public Order requestNewStopOrder(String symbol, Integer quantity, OrderSide side * price, or better, after a given stop price has been reached. Once the stop price is reached, the stop-limit order * becomes a limit order to buy or sell at the limit price or better. This method calls {@link * #requestNewOrder(String, Integer, OrderSide, OrderType, OrderTimeInForce, Double, Double, Boolean, String, - * OrderClass, Double, Double, Double, Double, Double, Double)} with {@link OrderType#STOP_LIMIT}. + * OrderClass, Double, Double, Double, Double, Double)} with {@link OrderType#STOP_LIMIT}. * * @param symbol symbol or asset ID to identify the asset to trade * @param quantity number of shares to trade @@ -598,14 +591,14 @@ public Order requestNewStopOrder(String symbol, Integer quantity, OrderSide side public Order requestNewStopLimitOrder(String symbol, Integer quantity, OrderSide side, OrderTimeInForce timeInForce, Double limitPrice, Double stopPrice, Boolean extendedHours) throws AlpacaAPIRequestException { return requestNewOrder(symbol, quantity, side, OrderType.STOP_LIMIT, timeInForce, limitPrice, stopPrice, - extendedHours, null, OrderClass.SIMPLE, null, null, null, null, null, null); + extendedHours, null, OrderClass.SIMPLE, null, null, null, null, null); } /** * A bracket order is a chain of three orders that can be used to manage your position entry and exit. It is a * common use case of an OTOCO (One Triggers OCO {One Cancels Other}) order. This method calls {@link * #requestNewOrder(String, Integer, OrderSide, OrderType, OrderTimeInForce, Double, Double, Boolean, String, - * OrderClass, Double, Double, Double, Double, Double, Double)} with {@link OrderType#MARKET} and with + * OrderClass, Double, Double, Double, Double, Double)} with {@link OrderType#MARKET} and with * parameters for a bracket order. * * @param symbol symbol or asset ID to identify the asset to trade @@ -631,14 +624,14 @@ public Order requestNewMarketBracketOrder(String symbol, Integer quantity, Order Double stopLossLimitPrice) throws AlpacaAPIRequestException { return requestNewOrder(symbol, quantity, side, OrderType.MARKET, timeInForce, null, null, extendedHours, null, OrderClass.BRACKET, takeProfitLimitPrice, stopLossStopPrice, - stopLossLimitPrice, null, null, null); + stopLossLimitPrice, null, null); } /** * A bracket order is a chain of three orders that can be used to manage your position entry and exit. It is a * common use case of an OTOCO (One Triggers OCO {One Cancels Other}) order. This method calls {@link * #requestNewOrder(String, Integer, OrderSide, OrderType, OrderTimeInForce, Double, Double, Boolean, String, - * OrderClass, Double, Double, Double, Double, Double, Double)} with {@link OrderType#LIMIT} and with parameters for a bracket order. + * OrderClass, Double, Double, Double, Double, Double)} with {@link OrderType#LIMIT} and with parameters for a bracket order. * * @param symbol symbol or asset ID to identify the asset to trade * @param quantity number of shares to trade @@ -663,7 +656,7 @@ public Order requestNewLimitBracketOrder(String symbol, Integer quantity, OrderS OrderTimeInForce timeInForce, Double limitPrice, Boolean extendedHours, Double takeProfitLimitPrice, Double stopLossStopPrice, Double stopLossLimitPrice) throws AlpacaAPIRequestException { return requestNewOrder(symbol, quantity, side, OrderType.LIMIT, timeInForce, limitPrice, null, - extendedHours, null, OrderClass.BRACKET, takeProfitLimitPrice, stopLossStopPrice, stopLossLimitPrice, null, null, null); + extendedHours, null, OrderClass.BRACKET, takeProfitLimitPrice, stopLossStopPrice, stopLossLimitPrice, null, null); } /** @@ -671,7 +664,7 @@ public Order requestNewLimitBracketOrder(String symbol, Integer quantity, OrderS * (buy/buy or sell/sell) and currently only exit order is supported. In other words, this is the second part of the * bracket orders where the entry order is already filled, and you can submit the take-profit and stop-loss in one * order submission. This method calls {@link #requestNewOrder(String, Integer, OrderSide, OrderType, - * OrderTimeInForce, Double, Double, Boolean, String, OrderClass, Double, Double, Double, Double, Double, Double)} + * OrderTimeInForce, Double, Double, Boolean, String, OrderClass, Double, Double, Double, Double, Double)} * with parameters for an * OCO order type. * @@ -695,13 +688,13 @@ public Order requestNewOCOOrder(String symbol, Integer quantity, OrderSide side, OrderTimeInForce timeInForce, Boolean extendedHours, Double takeProfitLimitPrice, Double stopLossStopPrice, Double stopLossLimitPrice) throws AlpacaAPIRequestException { return requestNewOrder(symbol, quantity, side, OrderType.LIMIT, timeInForce, null, null, extendedHours, null, - OrderClass.OCO, takeProfitLimitPrice, stopLossStopPrice, stopLossLimitPrice, null, null, null); + OrderClass.OCO, takeProfitLimitPrice, stopLossStopPrice, stopLossLimitPrice, null, null); } /** * OTO (One-Triggers-Other) is a variant of bracket order. It takes one of the take-profit or stop-loss order in * addition to the entry order. This method calls {@link #requestNewOrder(String, Integer, OrderSide, OrderType, - * OrderTimeInForce, Double, Double, Boolean, String, OrderClass, Double, Double, Double, Double, Double, Double)} + * OrderTimeInForce, Double, Double, Boolean, String, OrderClass, Double, Double, Double, Double, Double)} * with {@link * OrderType#LIMIT} and with parameters for an OTO order type. * @@ -725,13 +718,13 @@ public Order requestNewOTOMarketOrder(String symbol, Integer quantity, OrderSide OrderTimeInForce timeInForce, Boolean extendedHours, Double takeProfitLimitPrice, Double stopLossStopPrice, Double stopLossLimitPrice) throws AlpacaAPIRequestException { return requestNewOrder(symbol, quantity, side, OrderType.MARKET, timeInForce, null, null, extendedHours, null, - OrderClass.OTO, takeProfitLimitPrice, stopLossStopPrice, stopLossLimitPrice, null, null, null); + OrderClass.OTO, takeProfitLimitPrice, stopLossStopPrice, stopLossLimitPrice, null, null); } /** * OTO (One-Triggers-Other) is a variant of bracket order. It takes one of the take-profit or stop-loss order in * addition to the entry order. This method calls {@link #requestNewOrder(String, Integer, OrderSide, OrderType, - * OrderTimeInForce, Double, Double, Boolean, String, OrderClass, Double, Double, Double, Double, Double, Double)} + * OrderTimeInForce, Double, Double, Boolean, String, OrderClass, Double, Double, Double, Double, Double)} * with {@link * OrderType#LIMIT} and with parameters for an OTO order type. * @@ -756,13 +749,13 @@ public Order requestNewOTOLimitOrder(String symbol, Integer quantity, OrderSide OrderTimeInForce timeInForce, Double limitPrice, Boolean extendedHours, Double takeProfitLimitPrice, Double stopLossStopPrice, Double stopLossLimitPrice) throws AlpacaAPIRequestException { return requestNewOrder(symbol, quantity, side, OrderType.LIMIT, timeInForce, limitPrice, null, extendedHours, - null, OrderClass.OTO, takeProfitLimitPrice, stopLossStopPrice, stopLossLimitPrice, null, null, null); + null, OrderClass.OTO, takeProfitLimitPrice, stopLossStopPrice, stopLossLimitPrice, null, null); } /** * OTO (One-Triggers-Other) is a variant of bracket order. It takes one of the take-profit or stop-loss order in * addition to the entry order. This method calls {@link #requestNewOrder(String, Integer, OrderSide, OrderType, - * OrderTimeInForce, Double, Double, Boolean, String, OrderClass, Double, Double, Double, Double, Double, Double)} + * OrderTimeInForce, Double, Double, Boolean, String, OrderClass, Double, Double, Double, Double, Double)} * with {@link * OrderType#STOP} and with parameters for an OTO order type. * @@ -787,13 +780,13 @@ public Order requestNewOTOStopOrder(String symbol, Integer quantity, OrderSide s OrderTimeInForce timeInForce, Double stopPrice, Boolean extendedHours, Double takeProfitLimitPrice, Double stopLossStopPrice, Double stopLossLimitPrice) throws AlpacaAPIRequestException { return requestNewOrder(symbol, quantity, side, OrderType.STOP, timeInForce, null, stopPrice, extendedHours, - null, OrderClass.OTO, takeProfitLimitPrice, stopLossStopPrice, stopLossLimitPrice, null, null, null); + null, OrderClass.OTO, takeProfitLimitPrice, stopLossStopPrice, stopLossLimitPrice, null, null); } /** * OTO (One-Triggers-Other) is a variant of bracket order. It takes one of the take-profit or stop-loss order in * addition to the entry order. This method calls {@link #requestNewOrder(String, Integer, OrderSide, OrderType, - * OrderTimeInForce, Double, Double, Boolean, String, OrderClass, Double, Double, Double, Double, Double, Double)} + * OrderTimeInForce, Double, Double, Boolean, String, OrderClass, Double, Double, Double, Double, Double)} * with {@link * OrderType#STOP_LIMIT} and with parameters for an OTO order type. * @@ -820,7 +813,7 @@ public Order requestNewOTOStopLimitOrder(String symbol, Integer quantity, OrderS Double takeProfitLimitPrice, Double stopLossStopPrice, Double stopLossLimitPrice) throws AlpacaAPIRequestException { return requestNewOrder(symbol, quantity, side, OrderType.STOP_LIMIT, timeInForce, limitPrice, stopPrice, - extendedHours, null, OrderClass.OTO, takeProfitLimitPrice, stopLossStopPrice, stopLossLimitPrice, null, null, null); + extendedHours, null, OrderClass.OTO, takeProfitLimitPrice, stopLossStopPrice, stopLossLimitPrice, null, null); } /** From b2093e5a1fc7e4f2a2bbd0883b6a4a6f2a05b436 Mon Sep 17 00:00:00 2001 From: Jacob Peterson Date: Sat, 29 Aug 2020 12:42:13 -0600 Subject: [PATCH 006/271] Add handleResubscribe --- .../AbstractWebsocketClientEndpoint.java | 41 ++++--------------- .../websocket/client/WebsocketClient.java | 5 +++ .../client/AlpacaWebsocketClient.java | 9 +++- .../client/PolygonWebsocketClient.java | 15 ++++--- 4 files changed, 30 insertions(+), 40 deletions(-) diff --git a/src/main/java/io/github/mainstringargs/abstracts/websocket/client/AbstractWebsocketClientEndpoint.java b/src/main/java/io/github/mainstringargs/abstracts/websocket/client/AbstractWebsocketClientEndpoint.java index 4914506d..7a978976 100644 --- a/src/main/java/io/github/mainstringargs/abstracts/websocket/client/AbstractWebsocketClientEndpoint.java +++ b/src/main/java/io/github/mainstringargs/abstracts/websocket/client/AbstractWebsocketClientEndpoint.java @@ -30,17 +30,15 @@ public abstract class AbstractWebsocketClientEndpoint { /** The Endpoint uri. */ private final URI endpointURI; - /** The Executor service. */ + /** + * The Executor service, which passes message handlers to a different thread, will prevent overflow of server + * buffers (causing a disconnect) from not consuming data fast enough on the client end. + */ private final ExecutorService executorService; /** The User session. */ private Session userSession; - /** The Retry attempts. */ - private int retryAttempts = 0; - - private String subscription; - /** * Instantiates a new Abstract websocket client endpoint. * @@ -100,17 +98,10 @@ protected void onOpen(Session userSession) { * @param reason the reason */ protected void onClose(Session userSession, CloseReason reason) { - LOGGER.debug("onClose {}", userSession); if (!reason.getCloseCode().equals(CloseReason.CloseCodes.NORMAL_CLOSURE)) { - if (retryAttempts > 5) { - LOGGER.error("More than 5 attempts to reconnect were made. Bailing out."); - return; - } - - LOGGER.info("Attempting a reconnect in 3 seconds."); - retryAttempts++; + LOGGER.info("Websocket closed abnormally. Attempting a reconnect in 3 seconds."); try { Thread.sleep(3000); @@ -122,8 +113,8 @@ protected void onClose(Session userSession, CloseReason reason) { CloseReason.CloseCodes.getCloseCode(reason.getCloseCode().getCode())); try { - reconnectAndResubscribe(); - retryAttempts = 0; // hopefully we are connected by this time, so reset the retry counter + connect(); + websocketClient.handleResubscribing(); } catch (Exception e) { LOGGER.catching(e); } @@ -133,13 +124,6 @@ protected void onClose(Session userSession, CloseReason reason) { } } - private void reconnectAndResubscribe() throws IOException, DeploymentException { - LOGGER.info("Attempting to re-connect"); - connect(); - LOGGER.info("Resending subscriptions: {}", subscription); - sendSubscription(subscription); - } - /** * On message. * @@ -154,20 +138,11 @@ protected void onMessage(String message) { * * @param message the message */ - private void sendMessage(String message) { + public void sendMessage(String message) { LOGGER.debug("sendMessage {}", message); userSession.getAsyncRemote().sendText(message); } - public void sendAuthentication(String message) { - sendMessage(message); - } - - public void sendSubscription(String message) { - this.subscription = message; - sendMessage(message); - } - /** * Gets user session. * diff --git a/src/main/java/io/github/mainstringargs/abstracts/websocket/client/WebsocketClient.java b/src/main/java/io/github/mainstringargs/abstracts/websocket/client/WebsocketClient.java index 78d2644c..77672173 100644 --- a/src/main/java/io/github/mainstringargs/abstracts/websocket/client/WebsocketClient.java +++ b/src/main/java/io/github/mainstringargs/abstracts/websocket/client/WebsocketClient.java @@ -38,6 +38,11 @@ public interface WebsocketClient { */ void sendAuthenticationMessage(); + /** + * Handle resubscribing. + */ + void handleResubscribing(); + /** * Handle websocket message. * diff --git a/src/main/java/io/github/mainstringargs/alpaca/websocket/client/AlpacaWebsocketClient.java b/src/main/java/io/github/mainstringargs/alpaca/websocket/client/AlpacaWebsocketClient.java index def1d4da..5b4435cb 100644 --- a/src/main/java/io/github/mainstringargs/alpaca/websocket/client/AlpacaWebsocketClient.java +++ b/src/main/java/io/github/mainstringargs/alpaca/websocket/client/AlpacaWebsocketClient.java @@ -149,7 +149,12 @@ public void sendAuthenticationMessage() { authRequest.add("data", payload); - alpacaWebsocketClientEndpoint.sendAuthentication(authRequest.toString()); + alpacaWebsocketClientEndpoint.sendMessage(authRequest.toString()); + } + + @Override + public void handleResubscribing() { + submitStreamRequestUpdate(); } @Override @@ -265,7 +270,7 @@ private void submitStreamRequestUpdate() { streamRequestJsonObject.add("data", dataJsonObject); - alpacaWebsocketClientEndpoint.sendSubscription(streamRequestJsonObject.toString()); + alpacaWebsocketClientEndpoint.sendMessage(streamRequestJsonObject.toString()); LOGGER.info("Requested subscriptions to update to " + streamsJsonArray); } diff --git a/src/main/java/io/github/mainstringargs/polygon/websocket/client/PolygonWebsocketClient.java b/src/main/java/io/github/mainstringargs/polygon/websocket/client/PolygonWebsocketClient.java index 7d96ded9..87c42178 100644 --- a/src/main/java/io/github/mainstringargs/polygon/websocket/client/PolygonWebsocketClient.java +++ b/src/main/java/io/github/mainstringargs/polygon/websocket/client/PolygonWebsocketClient.java @@ -137,7 +137,13 @@ public void sendAuthenticationMessage() { authRequest.addProperty("action", "auth"); authRequest.addProperty("params", keyId); - polygonWebsocketClientEndpoint.sendAuthentication(authRequest.toString()); + polygonWebsocketClientEndpoint.sendMessage(authRequest.toString()); + } + + @Override + public void handleResubscribing() { + listeners.forEach(polygonStreamListener -> + submitStreamRequest(PolygonStreamAction.SUBSCRIBE, polygonStreamListener)); } @Override @@ -273,9 +279,8 @@ private void submitStreamRequest(PolygonStreamAction polygonStreamAction, Polygo if (isTickerChannelRegistered) { if (polygonStreamAction == PolygonStreamAction.UNSUBSCRIBE) { - LOGGER.warn( - "Cannot unsubscribe from {} for channel {} because it is being used by another stream listener!", - ticker, listenerChannelType.name()); + LOGGER.warn("Cannot unsubscribe from {} for channel {} because it is being used by " + + "another stream listener!", ticker, listenerChannelType.name()); } else if (polygonStreamAction == PolygonStreamAction.SUBSCRIBE) { LOGGER.warn("Already subscribed to {} for channel {}", ticker, listenerChannelType.name()); } @@ -320,7 +325,7 @@ private void submitStreamRequest(PolygonStreamAction polygonStreamAction, Polygo actionJsonObject.addProperty("action", polygonStreamAction.getAPIName()); actionJsonObject.addProperty("params", commaActionTickers.toString()); - polygonWebsocketClientEndpoint.sendSubscription(actionJsonObject.toString()); + polygonWebsocketClientEndpoint.sendMessage(actionJsonObject.toString()); LOGGER.info("Requested subscriptions to update for: {}", getRegisteredTickerChannels(null)); } From 70d610ec74abe1704ea052007125fb18b13b29b6 Mon Sep 17 00:00:00 2001 From: Jacob Peterson Date: Sat, 29 Aug 2020 13:15:56 -0600 Subject: [PATCH 007/271] Refactor schemas --- schema_json/alpaca/market_data/last_quote.json | 2 +- schema_json/alpaca/market_data/last_trade.json | 2 +- .../{ => abstracts}/market_data_stream_data_message.json | 0 .../{ => abstracts}/market_data_stream_status_message.json | 0 .../streaming/aggregate/aggregate_minute_message.json | 2 +- .../streaming/authorization/authorization_message.json | 2 +- .../market_data/streaming/listening/listening_message.json | 2 +- .../alpaca/market_data/streaming/quote/quote_message.json | 2 +- .../alpaca/market_data/streaming/trade/trade_message.json | 2 +- 9 files changed, 7 insertions(+), 7 deletions(-) rename schema_json/alpaca/market_data/streaming/{ => abstracts}/market_data_stream_data_message.json (100%) rename schema_json/alpaca/market_data/streaming/{ => abstracts}/market_data_stream_status_message.json (100%) diff --git a/schema_json/alpaca/market_data/last_quote.json b/schema_json/alpaca/market_data/last_quote.json index 9d8f183a..4276182c 100644 --- a/schema_json/alpaca/market_data/last_quote.json +++ b/schema_json/alpaca/market_data/last_quote.json @@ -28,7 +28,7 @@ }, "timestamp": { "existingJavaType": "java.lang.Long", - "title": "Epoch timestamp in milliseconds" + "title": "Epoch timestamp in nanoseconds" } } } diff --git a/schema_json/alpaca/market_data/last_trade.json b/schema_json/alpaca/market_data/last_trade.json index af6051e8..6f98da79 100644 --- a/schema_json/alpaca/market_data/last_trade.json +++ b/schema_json/alpaca/market_data/last_trade.json @@ -32,7 +32,7 @@ }, "timestamp": { "existingJavaType": "java.lang.Long", - "title": "Epoch timestamp in milliseconds" + "title": "Epoch timestamp in nanoseconds" } } } diff --git a/schema_json/alpaca/market_data/streaming/market_data_stream_data_message.json b/schema_json/alpaca/market_data/streaming/abstracts/market_data_stream_data_message.json similarity index 100% rename from schema_json/alpaca/market_data/streaming/market_data_stream_data_message.json rename to schema_json/alpaca/market_data/streaming/abstracts/market_data_stream_data_message.json diff --git a/schema_json/alpaca/market_data/streaming/market_data_stream_status_message.json b/schema_json/alpaca/market_data/streaming/abstracts/market_data_stream_status_message.json similarity index 100% rename from schema_json/alpaca/market_data/streaming/market_data_stream_status_message.json rename to schema_json/alpaca/market_data/streaming/abstracts/market_data_stream_status_message.json diff --git a/schema_json/alpaca/market_data/streaming/aggregate/aggregate_minute_message.json b/schema_json/alpaca/market_data/streaming/aggregate/aggregate_minute_message.json index 391edef8..c1ff9cd2 100644 --- a/schema_json/alpaca/market_data/streaming/aggregate/aggregate_minute_message.json +++ b/schema_json/alpaca/market_data/streaming/aggregate/aggregate_minute_message.json @@ -1,7 +1,7 @@ { "type": "object", "extends": { - "existingJavaType": "io.github.mainstringargs.domain.alpaca.marketdata.streaming.MarketDataStreamDataMessage" + "existingJavaType": "io.github.mainstringargs.domain.alpaca.marketdata.streaming.abstracts.MarketDataStreamDataMessage" }, "title": "See https://alpaca.markets/docs/api-documentation/api-v2/market-data/streaming/", "properties": { diff --git a/schema_json/alpaca/market_data/streaming/authorization/authorization_message.json b/schema_json/alpaca/market_data/streaming/authorization/authorization_message.json index 77c3818b..7c08f2ea 100644 --- a/schema_json/alpaca/market_data/streaming/authorization/authorization_message.json +++ b/schema_json/alpaca/market_data/streaming/authorization/authorization_message.json @@ -2,7 +2,7 @@ "type": "object", "title": "See https://alpaca.markets/docs/api-documentation/api-v2/market-data/streaming/", "extends": { - "existingJavaType": "io.github.mainstringargs.domain.alpaca.marketdata.streaming.MarketDataStreamStatusMessage" + "existingJavaType": "io.github.mainstringargs.domain.alpaca.marketdata.streaming.abstracts.MarketDataStreamStatusMessage" }, "properties": { "data": { diff --git a/schema_json/alpaca/market_data/streaming/listening/listening_message.json b/schema_json/alpaca/market_data/streaming/listening/listening_message.json index c751c71a..31b840d1 100644 --- a/schema_json/alpaca/market_data/streaming/listening/listening_message.json +++ b/schema_json/alpaca/market_data/streaming/listening/listening_message.json @@ -2,7 +2,7 @@ "type": "object", "title": "See https://docs.alpaca.markets/api-documentation/api-v2/streaming/", "extends": { - "existingJavaType": "io.github.mainstringargs.domain.alpaca.marketdata.streaming.MarketDataStreamStatusMessage" + "existingJavaType": "io.github.mainstringargs.domain.alpaca.marketdata.streaming.abstracts.MarketDataStreamStatusMessage" }, "properties": { "data": { diff --git a/schema_json/alpaca/market_data/streaming/quote/quote_message.json b/schema_json/alpaca/market_data/streaming/quote/quote_message.json index 038e8152..b387a528 100644 --- a/schema_json/alpaca/market_data/streaming/quote/quote_message.json +++ b/schema_json/alpaca/market_data/streaming/quote/quote_message.json @@ -1,7 +1,7 @@ { "type": "object", "extends": { - "existingJavaType": "io.github.mainstringargs.domain.alpaca.marketdata.streaming.MarketDataStreamDataMessage" + "existingJavaType": "io.github.mainstringargs.domain.alpaca.marketdata.streaming.abstracts.MarketDataStreamDataMessage" }, "title": "See https://alpaca.markets/docs/api-documentation/api-v2/market-data/streaming/", "properties": { diff --git a/schema_json/alpaca/market_data/streaming/trade/trade_message.json b/schema_json/alpaca/market_data/streaming/trade/trade_message.json index 55d5a15d..60d60b01 100644 --- a/schema_json/alpaca/market_data/streaming/trade/trade_message.json +++ b/schema_json/alpaca/market_data/streaming/trade/trade_message.json @@ -1,7 +1,7 @@ { "type": "object", "extends": { - "existingJavaType": "io.github.mainstringargs.domain.alpaca.marketdata.streaming.MarketDataStreamDataMessage" + "existingJavaType": "io.github.mainstringargs.domain.alpaca.marketdata.streaming.abstracts.MarketDataStreamDataMessage" }, "title": "See https://alpaca.markets/docs/api-documentation/api-v2/market-data/streaming/", "properties": { From 07d3cc4dc65291d8e27960f6a177b82d6d2879e3 Mon Sep 17 00:00:00 2001 From: Jacob Peterson Date: Sat, 29 Aug 2020 13:55:49 -0600 Subject: [PATCH 008/271] Set theme jekyll-theme-tactile --- _config.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/_config.yml b/_config.yml index c7418817..259a24e4 100644 --- a/_config.yml +++ b/_config.yml @@ -1 +1 @@ -theme: jekyll-theme-slate \ No newline at end of file +theme: jekyll-theme-tactile \ No newline at end of file From 8802bad706155e910c666bcfcfe30f8a7a5cd408 Mon Sep 17 00:00:00 2001 From: Jacob Peterson Date: Sat, 29 Aug 2020 19:32:12 -0600 Subject: [PATCH 009/271] Cleanup and update gradlew --- .gitattributes | 4 + .travis.yml | 2 +- Jenkinsfile | 26 --- LICENSE | 2 +- build.gradle | 194 ++++++++++++---------- gradle/wrapper/gradle-wrapper.jar | Bin 55436 -> 59203 bytes gradle/wrapper/gradle-wrapper.properties | 3 +- gradlew | 59 ++++--- gradlew.bat | 43 ++--- schemaJson2Pojo.gradle | 200 ----------------------- settings.gradle | 17 -- 11 files changed, 171 insertions(+), 379 deletions(-) delete mode 100644 Jenkinsfile delete mode 100644 schemaJson2Pojo.gradle diff --git a/.gitattributes b/.gitattributes index 536966dd..1730d965 100644 --- a/.gitattributes +++ b/.gitattributes @@ -27,6 +27,10 @@ *.xml text *.yml text +# Declare files that will always have CRLF line endings on checkout. +*.sln text eol=crlf +*.bat text eol=crlf + # These files are binary and should be left untouched # (binary is a macro for -text -diff) *.class binary diff --git a/.travis.yml b/.travis.yml index 45d725d0..24c200d4 100644 --- a/.travis.yml +++ b/.travis.yml @@ -15,4 +15,4 @@ before_cache: cache: directories: - $HOME/.gradle/caches/ - - $HOME/.gradle/wrapper/ \ No newline at end of file + - $HOME/.gradle/wrapper/ diff --git a/Jenkinsfile b/Jenkinsfile deleted file mode 100644 index 415ddac0..00000000 --- a/Jenkinsfile +++ /dev/null @@ -1,26 +0,0 @@ -#!/usr/bin/env groovy - -node { - stage('checkout') { - checkout scm - } - - stage('check java') { - sh "java -version" - } - - stage('clean') { - sh "chmod +x gradlew" - sh "./gradlew clean" - } - - - stage('install') { - try { - sh "./gradlew build publishToMavenLocal" - } catch (err) { - throw err - } - } -} - diff --git a/LICENSE b/LICENSE index 2e9f22a3..81f990d6 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2018 mainstringargs +Copyright (c) 2020 Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/build.gradle b/build.gradle index 007107ec..bc9d0394 100644 --- a/build.gradle +++ b/build.gradle @@ -1,65 +1,49 @@ -/* - * This build file was generated by the Gradle 'init' task. - * - * This generated file contains a sample Java project to get you started. - * For more details take a look at the Java Quickstart chapter in the Gradle - * user guide available at https://docs.gradle.org/3.4.1/userguide/tutorial_java_projects.html - */ +import org.jsonschema2pojo.gradle.GenerateJsonSchemaJavaTask buildscript { repositories { - mavenLocal() mavenCentral() jcenter() } dependencies { classpath 'com.bmuschko:gradle-nexus-plugin:2.3.1' - // add additional dependencies here if you wish to reference instead of generate them (see example directory) + classpath 'org.jsonschema2pojo:jsonschema2pojo-gradle-plugin:1.0.2' } } plugins { + id 'java' + id 'application' + id 'eclipse' id 'maven-publish' + id 'com.bmuschko.nexus' version '2.3.1' id 'io.codearte.nexus-staging' version '0.11.0' } -// Apply the java plugin to add support for Java -apply plugin: 'java' - -// Apply the application plugin to add support for building an application -apply plugin: 'application' - -apply plugin: 'eclipse' - -apply plugin: 'com.bmuschko.nexus' +// Calling 'apply' here because jsonschema2pojo doesn't exist in Gradle Central Plugin Repository +apply plugin: 'jsonschema2pojo' +tasks.getByName("generateJsonSchema2Pojo").enabled(false) // Disable default jsonSchema2Pojo task archivesBaseName = 'alpaca-java' group = 'io.github.mainstringargs' version = '5.0.13-SNAPSHOT' - -// In this section you declare where to find the dependencies of your project repositories { - // Use jcenter for resolving your dependencies. - // You can declare any Maven/Ivy/file repository here. - maven { url "https://repo.maven.apache.org/maven2" } + mavenLocal() + mavenCentral() jcenter() - //maven { url "http://mvnrepository.com/artifact" } } dependencies { - // This dependency is found on compile classpath of this component and consumers. compile 'com.google.guava:guava:20.0' compile "com.mashape.unirest:unirest-java:1.4.9" compile "com.google.code.gson:gson:2.8.5" compile "commons-lang:commons-lang:2.6" compile 'org.apache.logging.log4j:log4j-api:2.11.1' compile 'org.apache.logging.log4j:log4j-core:2.11.1' - compile "org.eclipse.jetty.websocket:javax-websocket-client-impl:9.4.28.v20200408" - // Use JUnit test framework testCompile 'junit:junit:4.12' } @@ -67,45 +51,58 @@ sourceCompatibility = 1.8 targetCompatibility = 1.8 [compileJava, compileTestJava, javadoc]*.options*.encoding = 'UTF-8' -modifyPom { - project { - name 'alpaca-java' - description 'Java API for Alpaca trading' - url 'https://github.com/mainstringargs/alpaca-java' - inceptionYear '2018' - scm { - url 'https://github.com/mainstringargs/alpaca-java' - connection 'https://github.com/mainstringargs/alpaca-java.git' - developerConnection 'https://github.com/mainstringargs/alpaca-java.git' - } - licenses { - license { - name 'MIT License' - url 'https://opensource.org/licenses/MIT' - distribution 'repo' - } - } - developers { - developer { - id 'mainstringargs' - name 'main(String[] args)' - email 'mainstringargs@github.io' - } - } - } -} - -// https://stackoverflow.com/questions/52205209/configure-gradle-build-to-suppress-javadoc-console-warnings +// Used to suppress java doc warnings javadoc.options.addStringOption('Xdoclint:none', '-quiet') javadoc.options.addStringOption("charset", "UTF-8") +// Publish to local maven repo for local testing +build { + finalizedBy publishToMavenLocal +} + +//mainClassName = 'io.github.mainstringargs.polygon.PolygonExample' +mainClassName = 'io.github.mainstringargs.alpaca.AlpacaExample' +// +// START publishing +// publishing { publications { - maven(MavenPublication) { + mainPublication(MavenPublication) { from components.java - artifact sourcesJar - artifact javadocJar + + pom { + name = 'alpaca-java' + description = 'Java API for Alpaca and Polygon' + url = 'https://github.com/Petersoj/alpaca-java' + inceptionYear = '2018' + + licenses { + license { + name = 'MIT License' + url = 'https://opensource.org/licenses/MIT' + } + } + + developers { + developer { + id = 'mainstringargs' + name = 'main(String[] args)' + email = 'mainstringargs@github.io' + } + + developer { + id = 'Petersoj' + name = 'Jacob Peterson' + } + } + + scm { + url = "https://github.com/Petersoj/alpaca-java.git" + connection = "scm:git:https://github.com/Petersoj/alpaca-java.git" + developerConnection = "scm:git:git@github.com/Petersoj/alpaca-java.git" + } + } } } } @@ -125,19 +122,17 @@ nexusStaging { packageGroup = "io.github.mainstringargs" //optional if packageGroup == project.getGroup() } +// Used to prevent accidental publication of Alpaca keys processResources { exclude('alpaca.properties', 'polygon.properties') } +// +// END publishing +// -sourceSets { - main { - java { - srcDir 'src/main/java' - srcDir 'build/generated-sources/schemajson' - } - } -} - +// +// START POJO generation +// // The generated POJOs will be in a package structure analogous to the path in the 'schema_json/' directory // See: https://github.com/joelittlejohn/jsonschema2pojo/wiki/Reference task generatePOJOs() { @@ -145,24 +140,22 @@ task generatePOJOs() { final def jsonSourceDirectory = "${project.projectDir}" + File.separator + "schema_json" + File.separator final def jsonPackageNameStart = "io.github.mainstringargs.domain." - // Clear all generated sources - delete targetDirectory - // Loop through all files in schema JSON file tree - file(jsonSourceDirectory).eachFileRecurse { file -> - if (file.getName().endsWith(".json")) { - def startPackageIndex = file.getAbsolutePath().indexOf(jsonSourceDirectory) + jsonSourceDirectory.length() - def targetPackage = jsonPackageNameStart + file.getParentFile().getAbsolutePath() + file(jsonSourceDirectory).eachFileRecurse { jsonFile -> + if (jsonFile.getName().endsWith(".json")) { + def startPackageIndex = jsonFile.getAbsolutePath().indexOf(jsonSourceDirectory) + jsonSourceDirectory.length() + def targetPackage = jsonPackageNameStart + jsonFile.getParentFile().getAbsolutePath() .substring(startPackageIndex) .replace(File.separator, '.').replace('-', '').replace('_', '') .toLowerCase() - def jsonToPOJOTask = tasks.create('json2POJOTask-' + targetPackage + "." + file.getName(), GradleBuild) { - startParameter.projectProperties = ['source' : file.getAbsolutePath(), - 'targetPackage' : targetPackage, - 'targetDirectory': targetDirectory] - buildFile = 'schemaJson2Pojo.gradle' - tasks = ['generateJsonSchema2Pojo'] + def jsonToPOJOTask = tasks.create('json2POJOTask-' + targetPackage + "." + jsonFile.getName(), + GenerateJsonSchemaJavaTask) + + jsonToPOJOTask.doFirst { + jsonToPOJOTask.configuration.source = files(jsonFile.getAbsolutePath()) + jsonToPOJOTask.configuration.targetDirectory = file(targetDirectory) + jsonToPOJOTask.configuration.targetPackage = targetPackage } dependsOn jsonToPOJOTask @@ -174,15 +167,36 @@ compileJava { dependsOn generatePOJOs } -build.finalizedBy(publishToMavenLocal) - -if (hasProperty('buildScan')) { - buildScan { licenseAgreementUrl = 'https://gradle.com/terms-of-service'; licenseAgree = 'yes' } +sourceSets { + main { + java { + srcDir 'src/main/java' + srcDir 'build/generated-sources/schemajson' + } + } } -startScripts { - classpath += files('resources') -} +jsonSchema2Pojo { + includeAdditionalProperties = true + + targetDirectory = file('build/generated-sources/schemajson') + + propertyWordDelimiters = ['-', '_'] as char[] + + annotationStyle = 'gson' + + sourceType = 'jsonschema' -//mainClassName = 'io.github.mainstringargs.alpaca.AlpacaExample' -mainClassName = 'io.github.mainstringargs.polygon.PolygonExample' + customDateTimePattern = "yyyy-MM-ddTHH:mm:ssZ" + + includeConstructors = true + + serializable = true + + includeGetters = true + + includeSetters = true +} +// +// END POJO generation +// diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index 7855f0a61dd86c6263ae9630dcfc313c17d20a78..e708b1c023ec8b20f512888fe07c5bd3ff77bb8f 100644 GIT binary patch delta 40250 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 Ap#T5? delta 36780 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 Np2XT1wiUD(_8%X=^1c87 diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 7acf1540..33682bbb 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,5 @@ -#Mon Jun 18 11:04:32 CDT 2018 distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-6.6.1-all.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-4.3-bin.zip diff --git a/gradlew b/gradlew index 4453ccea..4f906e0c 100755 --- a/gradlew +++ b/gradlew @@ -1,5 +1,21 @@ #!/usr/bin/env sh +# +# Copyright 2015 the original author or authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + ############################################################################## ## ## Gradle start up script for UN*X @@ -28,16 +44,16 @@ APP_NAME="Gradle" APP_BASE_NAME=`basename "$0"` # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. -DEFAULT_JVM_OPTS="" +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' # Use the maximum available, or set MAX_FD != -1 to use that value. MAX_FD="maximum" -warn ( ) { +warn () { echo "$*" } -die ( ) { +die () { echo echo "$*" echo @@ -66,6 +82,7 @@ esac CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + # Determine the Java command to use to start the JVM. if [ -n "$JAVA_HOME" ] ; then if [ -x "$JAVA_HOME/jre/sh/java" ] ; then @@ -109,10 +126,11 @@ if $darwin; then GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" fi -# For Cygwin, switch paths to Windows format before running java -if $cygwin ; then +# For Cygwin or MSYS, switch paths to Windows format before running java +if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then APP_HOME=`cygpath --path --mixed "$APP_HOME"` CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` + JAVACMD=`cygpath --unix "$JAVACMD"` # We build the pattern for arguments to be converted via cygpath @@ -138,35 +156,30 @@ if $cygwin ; then else eval `echo args$i`="\"$arg\"" fi - i=$((i+1)) + i=`expr $i + 1` done case $i in - (0) set -- ;; - (1) set -- "$args0" ;; - (2) set -- "$args0" "$args1" ;; - (3) set -- "$args0" "$args1" "$args2" ;; - (4) set -- "$args0" "$args1" "$args2" "$args3" ;; - (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; - (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; - (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; - (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; - (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; + 0) set -- ;; + 1) set -- "$args0" ;; + 2) set -- "$args0" "$args1" ;; + 3) set -- "$args0" "$args1" "$args2" ;; + 4) set -- "$args0" "$args1" "$args2" "$args3" ;; + 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; + 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; + 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; + 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; + 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; esac fi # Escape application args -save ( ) { +save () { for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done echo " " } -APP_ARGS=$(save "$@") +APP_ARGS=`save "$@"` # Collect all arguments for the java command, following the shell quoting and substitution rules eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" -# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong -if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then - cd "$(dirname "$0")" -fi - exec "$JAVACMD" "$@" diff --git a/gradlew.bat b/gradlew.bat index f9553162..107acd32 100644 --- a/gradlew.bat +++ b/gradlew.bat @@ -1,3 +1,19 @@ +@rem +@rem Copyright 2015 the original author or authors. +@rem +@rem Licensed under the Apache License, Version 2.0 (the "License"); +@rem you may not use this file except in compliance with the License. +@rem You may obtain a copy of the License at +@rem +@rem https://www.apache.org/licenses/LICENSE-2.0 +@rem +@rem Unless required by applicable law or agreed to in writing, software +@rem distributed under the License is distributed on an "AS IS" BASIS, +@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@rem See the License for the specific language governing permissions and +@rem limitations under the License. +@rem + @if "%DEBUG%" == "" @echo off @rem ########################################################################## @rem @@ -13,15 +29,18 @@ if "%DIRNAME%" == "" set DIRNAME=. set APP_BASE_NAME=%~n0 set APP_HOME=%DIRNAME% +@rem Resolve any "." and ".." in APP_HOME to make it shorter. +for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi + @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. -set DEFAULT_JVM_OPTS= +set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" @rem Find java.exe if defined JAVA_HOME goto findJavaFromJavaHome set JAVA_EXE=java.exe %JAVA_EXE% -version >NUL 2>&1 -if "%ERRORLEVEL%" == "0" goto init +if "%ERRORLEVEL%" == "0" goto execute echo. echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. @@ -35,7 +54,7 @@ goto fail set JAVA_HOME=%JAVA_HOME:"=% set JAVA_EXE=%JAVA_HOME%/bin/java.exe -if exist "%JAVA_EXE%" goto init +if exist "%JAVA_EXE%" goto execute echo. echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% @@ -45,28 +64,14 @@ echo location of your Java installation. goto fail -:init -@rem Get command-line arguments, handling Windows variants - -if not "%OS%" == "Windows_NT" goto win9xME_args - -:win9xME_args -@rem Slurp the command line arguments. -set CMD_LINE_ARGS= -set _SKIP=2 - -:win9xME_args_slurp -if "x%~1" == "x" goto execute - -set CMD_LINE_ARGS=%* - :execute @rem Setup the command line set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + @rem Execute Gradle -"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* :end @rem End local scope for the variables with windows NT shell diff --git a/schemaJson2Pojo.gradle b/schemaJson2Pojo.gradle deleted file mode 100644 index d238941a..00000000 --- a/schemaJson2Pojo.gradle +++ /dev/null @@ -1,200 +0,0 @@ -/* - * This build file was generated by the Gradle 'init' task. - * - * This generated file contains a sample Java project to get you started. - * For more details take a look at the Java Quickstart chapter in the Gradle - * user guide available at https://docs.gradle.org/3.4.1/userguide/tutorial_java_projects.html - */ - -buildscript { - repositories { - mavenCentral() - jcenter() - } - - dependencies { - // this plugin - classpath 'org.jsonschema2pojo:jsonschema2pojo-gradle-plugin:1.0.0' - // add additional dependencies here if you wish to reference instead of generate them (see example directory) - } -} - - -// Apply the java plugin to add support for Java -apply plugin: 'java' - -apply plugin: 'jsonschema2pojo' - - -// In this section you declare where to find the dependencies of your project -repositories { - // Use jcenter for resolving your dependencies. - // You can declare any Maven/Ivy/file repository here. - maven { url "https://repo.maven.apache.org/maven2" } - jcenter() - //maven { url "http://mvnrepository.com/artifact" } -} - -sourceCompatibility = 1.8 -targetCompatibility = 1.8 -[compileJava, compileTestJava]*.options*.encoding = 'UTF-8' - - -// Each configuration is set to the default value -jsonSchema2Pojo { - - // Whether to allow 'additional' properties to be supported in classes by adding a map to - // hold these. This is true by default, meaning that the schema rule 'additionalProperties' - // controls whether the map is added. Set this to false to globabally disable additional properties. - includeAdditionalProperties = true - - // Whether to generate builder-style methods of the form withXxx(value) (that return this), - // alongside the standard, void-return setters. - generateBuilders = false - - // Whether to use primitives (long, double, boolean) instead of wrapper types where possible - // when generating bean properties (has the side-effect of making those properties non-null). - usePrimitives = true - - // Location of the JSON Schema file(s). This may refer to a single file or a directory of files. - source = files(project.properties.get("source")) - - // Target directory for generated Java source files. The plugin will add this directory to the - // java source set so the compiler will find and compile the newly generated source files. - targetDirectory = file(project.properties.get("targetDirectory")) - - // Package name used for generated Java classes (for types where a fully qualified name has not - // been supplied in the schema using the 'javaType' property). - targetPackage = project.properties.get("targetPackage", "io.github.mainstringargs.domain") - - // The characters that should be considered as word delimiters when creating Java Bean property - // names from JSON property names. If blank or not set, JSON properties will be considered to - // contain a single word when creating Java Bean property names. - propertyWordDelimiters = ['-', '_'] - - // Whether to use the java type long (or Long) instead of int (or Integer) when representing the - // JSON Schema type 'integer'. - useLongIntegers = true - - // Whether to use the java type BigInteger when representing the JSON Schema type 'integer'. Note - // that this configuration overrides useLongIntegers - useBigIntegers = false - - // Whether to use the java type double (or Double) instead of float (or Float) when representing - // the JSON Schema type 'number'. - useDoubleNumbers = false - - // Whether to use the java type BigDecimal when representing the JSON Schema type 'number'. Note - // that this configuration overrides useDoubleNumbers - useBigDecimals = false - - // Whether to include hashCode and equals methods in generated Java types. - includeHashcodeAndEquals = true - - // Whether to include a toString method in generated Java types. - includeToString = true - - // The style of annotations to use in the generated Java types. Supported values: - // - jackson (alias of jackson2) - // - jackson2 (apply annotations from the Jackson 2.x library) - // - jackson1 (apply annotations from the Jackson 1.x library) - // - gson (apply annotations from the Gson library) - // - moshi1 (apply annotations from the Moshi 1.x library) - // - none (apply no annotations at all) - annotationStyle = 'gson' - - // A fully qualified class name, referring to a custom annotator class that implements - // org.jsonschema2pojo.Annotator and will be used in addition to the one chosen - // by annotationStyle. If you want to use the custom annotator alone, set annotationStyle to none. - customAnnotator = 'org.jsonschema2pojo.NoopAnnotator' - - // Whether to include JSR-303/349 annotations (for schema rules like minimum, maximum, etc) in - // generated Java types. Schema rules and the annotation they produce: - // - maximum = @DecimalMax - // - minimum = @DecimalMin - // - minItems,maxItems = @Size - // - minLength,maxLength = @Size - // - pattern = @Pattern - // - required = @NotNull - // Any Java fields which are an object or array of objects will be annotated with @Valid to - // support validation of an entire document tree. - includeJsr303Annotations = false - - // The type of input documents that will be read. Supported values: - // - jsonschema (schema documents, containing formal rules that describe the structure of JSON data) - // - json (documents that represent an example of the kind of JSON data that the generated Java types - // will be mapped to) - // - yamlschema (JSON schema documents, represented as YAML) - // - yaml (documents that represent an example of the kind of YAML (or JSON) data that the generated Java types - // will be mapped to) - sourceType = 'jsonschema' - - // Whether to empty the target directory before generation occurs, to clear out all source files - // that have been generated previously. Be warned, when activated this option - // will cause jsonschema2pojo to indiscriminately delete the entire contents of the target - // directory (all files and folders) before it begins generating sources. - removeOldOutput = false - - // The character encoding that should be used when writing the generated Java source files - outputEncoding = 'UTF-8' - - // Whether to use {@link org.joda.time.DateTime} instead of {@link java.util.Date} when adding - // date type fields to generated Java types. - useJodaDates = false - - // Whether to add JsonFormat annotations when using Jackson 2 that cause format "date", "time", and "date-time" - // fields to be formatted as yyyy-MM-dd, HH:mm:ss.SSS and yyyy-MM-dd'T'HH:mm:ss.SSSZ respectively. To customize these - // patterns, use customDatePattern, customTimePattern, and customDateTimePattern config options or add these inside a - // schema to affect an individual field - formatDateTimes = true - formatDates = true - formatTimes = true - - //2018-10-05T05:48:59Z - customDateTimePattern = "yyyy-MM-ddTHH:mm:ssZ" - - // Whether to initialize Set and List fields as empty collections, or leave them as null. - initializeCollections = true - - // Whether to add a prefix to generated classes. - classNamePrefix = "" - - // Whether to add a suffix to generated classes. - classNameSuffix = "" - - // An array of strings that should be considered as file extensions and therefore not included in class names. - fileExtensions = [] as String[] - - // Whether to generate constructors or not. - includeConstructors = true - - // **EXPERIMENTAL** Whether to make the generated types Parcelable for Android - parcelable = false - - // Whether to make the generated types Serializable - serializable = true - - // Whether to include getters or to omit these accessor methods and create public fields instead. - includeGetters = true - - // Whether to include setters or to omit these accessor methods and create public fields instead. - includeSetters = true - - // Whether to include dynamic getters, setters, and builders or to omit these methods. - includeDynamicAccessors = false - - // Whether to include dynamic getters or to omit these methods. - includeDynamicGetters = false - - // Whether to include dynamic setters or to omit these methods. - includeDynamicSetters = false - - // Whether to include dynamic builders or to omit these methods. - includeDynamicBuilders = false - - // What type to use instead of string when adding string properties of format "date" to Java types - dateType = "java.time.LocalDate" - - // What type to use instead of string when adding string properties of format "date-time" to Java types - dateTimeType = "java.time.LocalDateTime" -} diff --git a/settings.gradle b/settings.gradle index 3443d148..6088a942 100644 --- a/settings.gradle +++ b/settings.gradle @@ -1,18 +1 @@ -/* - * This settings file was generated by the Gradle 'init' task. - * - * The settings file is used to specify which projects to include in your build. - * In a single project build this file can be empty or even removed. - * - * Detailed information about configuring a multi-project build in Gradle can be found - * in the user guide at https://docs.gradle.org/3.4.1/userguide/multi_project_builds.html - */ - -/* -// To declare projects as part of a multi-project build use the 'include' method -include 'shared' -include 'api' -include 'services:webservice' -*/ - rootProject.name = 'alpaca-java' From 8732f7fcdf1c945cb7ce7b657f3519665950c6a1 Mon Sep 17 00:00:00 2001 From: foobar Date: Tue, 1 Sep 2020 22:37:21 -0400 Subject: [PATCH 010/271] Added the functionality in replaceOrder for updating the trailingPercent, trailingPrice --- .../io/github/mainstringargs/alpaca/AlpacaAPI.java | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/src/main/java/io/github/mainstringargs/alpaca/AlpacaAPI.java b/src/main/java/io/github/mainstringargs/alpaca/AlpacaAPI.java index bd20e473..b7a947a7 100644 --- a/src/main/java/io/github/mainstringargs/alpaca/AlpacaAPI.java +++ b/src/main/java/io/github/mainstringargs/alpaca/AlpacaAPI.java @@ -890,7 +890,7 @@ public Order getOrderByClientID(String clientOrderId) throws AlpacaAPIRequestExc * @see Orders */ public Order replaceOrder(String orderID, Integer quantity, OrderTimeInForce timeInForce, Double limitPrice, - Double stopPrice, String clientOrderId) throws AlpacaAPIRequestException { + Double stopPrice, Double trailingPercent, Double trailingPrice, String clientOrderId) throws AlpacaAPIRequestException { Preconditions.checkNotNull(orderID); AlpacaRequestBuilder urlBuilder = new AlpacaRequestBuilder(baseAPIURL, apiVersion, @@ -915,6 +915,16 @@ public Order replaceOrder(String orderID, Integer quantity, OrderTimeInForce tim TimeUtil.toDecimalFormat(stopPrice)); } + if(trailingPercent != null) { + urlBuilder.appendJSONBodyProperty(AlpacaConstants.TRAIL_PERCENT_PARAMETER, + TimeUtil.toDecimalFormat(trailingPercent)); + } + + if(trailingPrice != null) { + urlBuilder.appendJSONBodyProperty(AlpacaConstants.TRAIL_PRICE_PARAMETER, + TimeUtil.toDecimalFormat(trailingPrice)); + } + if (clientOrderId != null) { urlBuilder.appendJSONBodyProperty(AlpacaConstants.CLIENT_ORDER_ID_PARAMETER, clientOrderId); } From d9a3f5be8bff8489fb3ad8f26017a4039e5b280c Mon Sep 17 00:00:00 2001 From: Jacob Peterson Date: Tue, 1 Sep 2020 21:37:47 -0600 Subject: [PATCH 011/271] Cleanup root files --- .travis.yml | 2 +- build.gradle | 23 +-- eclipse-java-google-style.xml | 337 ---------------------------------- upload.sh | 4 - 4 files changed, 13 insertions(+), 353 deletions(-) delete mode 100644 eclipse-java-google-style.xml delete mode 100644 upload.sh diff --git a/.travis.yml b/.travis.yml index 24c200d4..8168baab 100644 --- a/.travis.yml +++ b/.travis.yml @@ -6,7 +6,7 @@ jdk: - oraclejdk8 script: - - ./gradlew build --scan -s + - ./gradlew build -s before_cache: - rm -f $HOME/.gradle/caches/modules-2/modules-2.lock diff --git a/build.gradle b/build.gradle index bc9d0394..c823ecf3 100644 --- a/build.gradle +++ b/build.gradle @@ -47,6 +47,9 @@ dependencies { testCompile 'junit:junit:4.12' } +// +// START Build options +// sourceCompatibility = 1.8 targetCompatibility = 1.8 [compileJava, compileTestJava, javadoc]*.options*.encoding = 'UTF-8' @@ -55,13 +58,11 @@ targetCompatibility = 1.8 javadoc.options.addStringOption('Xdoclint:none', '-quiet') javadoc.options.addStringOption("charset", "UTF-8") -// Publish to local maven repo for local testing -build { - finalizedBy publishToMavenLocal -} - //mainClassName = 'io.github.mainstringargs.polygon.PolygonExample' mainClassName = 'io.github.mainstringargs.alpaca.AlpacaExample' +// +// END Build options +// // // START publishing @@ -86,14 +87,13 @@ publishing { developers { developer { - id = 'mainstringargs' - name = 'main(String[] args)' - email = 'mainstringargs@github.io' + id = 'Petersoj' + name = 'Jacob Peterson' } developer { - id = 'Petersoj' - name = 'Jacob Peterson' + id = 'mainstringargs' + name = 'main(String[] args)' } } @@ -112,6 +112,7 @@ extraArchive { tests = true javadoc = true } + nexus { sign = true repositoryUrl = 'https://oss.sonatype.org/service/local/staging/deploy/maven2/' @@ -119,7 +120,7 @@ nexus { } nexusStaging { - packageGroup = "io.github.mainstringargs" //optional if packageGroup == project.getGroup() + packageGroup = "io.github.mainstringargs" } // Used to prevent accidental publication of Alpaca keys diff --git a/eclipse-java-google-style.xml b/eclipse-java-google-style.xml deleted file mode 100644 index 2a9faacc..00000000 --- a/eclipse-java-google-style.xml +++ /dev/null @@ -1,337 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/upload.sh b/upload.sh deleted file mode 100644 index ae6499d4..00000000 --- a/upload.sh +++ /dev/null @@ -1,4 +0,0 @@ -#!/bin/bash - -./gradlew uploadArchives -./gradlew closeAndReleaseRepository From de54036c46942e0114c084596e86234c486cceeb Mon Sep 17 00:00:00 2001 From: Jacob Peterson Date: Tue, 1 Sep 2020 23:04:49 -0600 Subject: [PATCH 012/271] Refactor for repo transfer --- README.md | 22 ++--- build.gradle | 77 ++++++++-------- schema_json/alpaca/account/account.json | 2 +- .../non_trade_activity.json | 2 +- .../account_activities/trade_activity.json | 2 +- schema_json/alpaca/order/cancelled_order.json | 2 +- schema_json/alpaca/order/order.json | 2 +- .../portfolio_history/portfolio_history.json | 2 +- .../alpaca/position/close_position_order.json | 2 +- schema_json/alpaca/watchlist/watchlist.json | 2 +- .../websocket/account/account_update.json | 2 +- .../account/account_update_message.json | 4 +- .../websocket/alpaca_stream_message.json | 4 +- .../authorization/authorization_message.json | 4 +- .../listening/listening_message.json | 4 +- .../alpaca/websocket/trade/trade_update.json | 2 +- .../websocket/trade/trade_update_message.json | 4 +- .../aggregates/aggregates_response.json | 2 +- .../daily_open_close_response.json | 6 +- .../grouped_daily/grouped_daily_response.json | 2 +- .../historic_quotes_response.json | 2 +- .../historic_trades_response.json | 2 +- .../last_quote/last_quote_response.json | 2 +- .../last_trade/last_trade_response.json | 2 +- .../polygon/locales/locales_response.json | 2 +- .../polygon/markets/markets_response.json | 2 +- .../previous_close_response.json | 2 +- .../snapshot_all_tickers_response.json | 2 +- .../snapshot_gainers_losers_response.json | 2 +- .../snapshot_single_ticker_response.json | 2 +- .../snapshot/snapshot_ticker_book.json | 2 +- .../snapshot/ticker/book/book_data.json | 4 +- .../snapshot/ticker/snapshot_ticker.json | 10 +-- .../stock_dividends_response.json | 2 +- .../stock_financials_response.json | 2 +- .../stock_splits/stock_splits_response.json | 2 +- .../ticker_types/ticket_types_response.json | 2 +- .../polygon/tickers/ticker/forex_ticker.json | 2 +- .../polygon/tickers/ticker/stock_ticker.json | 2 +- .../polygon/tickers/tickers_response.json | 2 +- .../aggregate/aggregate_message.json | 2 +- .../aggregate_per_minute_message.json | 2 +- .../aggregate_per_second_message.json | 2 +- .../websocket/polygon_stream_message.json | 4 +- .../websocket/quote/quote_message.json | 2 +- .../websocket/status/status_message.json | 2 +- .../websocket/trade/trade_message.json | 2 +- .../abstracts/enums/APIName.java | 2 +- .../abstracts/rest/AbstractRequest.java | 4 +- .../rest/AbstractRequestBuilder.java | 2 +- .../AbstractAPIRequestException.java | 4 +- .../AbstractWebsocketClientEndpoint.java | 4 +- .../websocket/client/WebsocketClient.java | 8 +- .../websocket/listener/StreamListener.java | 6 +- .../websocket/message/StreamMessage.java | 2 +- .../websocket/message/StreamMessageType.java | 2 +- .../jacobpeterson}/alpaca/AlpacaAPI.java | 84 +++++++++--------- .../alpaca/AlpacaConstants.java | 2 +- .../jacobpeterson}/alpaca/AlpacaExample.java | 28 +++--- .../alpaca/enums/AccountStatus.java | 4 +- .../alpaca/enums/ActivityType.java | 4 +- .../alpaca/enums/AssetStatus.java | 4 +- .../alpaca/enums/BarsTimeFrame.java | 4 +- .../alpaca/enums/Direction.java | 4 +- .../alpaca/enums/OrderClass.java | 4 +- .../alpaca/enums/OrderSide.java | 4 +- .../alpaca/enums/OrderStatus.java | 4 +- .../alpaca/enums/OrderTimeInForce.java | 4 +- .../alpaca/enums/OrderType.java | 4 +- .../alpaca/enums/PortfolioPeriodUnit.java | 4 +- .../alpaca/enums/PortfolioTimeFrame.java | 6 +- .../alpaca/properties/AlpacaProperties.java | 4 +- .../alpaca/rest/AlpacaRequest.java | 6 +- .../alpaca/rest/AlpacaRequestBuilder.java | 4 +- .../exception/AlpacaAPIRequestException.java | 4 +- .../client/AlpacaWebsocketClient.java | 26 +++--- .../client/AlpacaWebsocketClientEndpoint.java | 6 +- .../listener/AlpacaStreamListener.java | 8 +- .../listener/AlpacaStreamListenerAdapter.java | 6 +- .../message/AlpacaStreamMessageType.java | 8 +- .../jacobpeterson}/polygon/PolygonAPI.java | 88 +++++++++---------- .../polygon/PolygonConstants.java | 2 +- .../polygon/PolygonExample.java | 20 ++--- .../polygon/enums/ConditionMappingsType.java | 4 +- .../polygon/enums/FinancialReportType.java | 4 +- .../polygon/enums/FinancialSort.java | 4 +- .../polygon/enums/GainersLosersDirection.java | 4 +- .../jacobpeterson}/polygon/enums/Market.java | 4 +- .../polygon/enums/StockType.java | 4 +- .../polygon/enums/TickerSort.java | 4 +- .../polygon/enums/Timespan.java | 4 +- .../polygon/properties/PolygonProperties.java | 6 +- .../polygon/rest/PolygonRequest.java | 8 +- .../polygon/rest/PolygonRequestBuilder.java | 4 +- .../exception/PolygonAPIRequestException.java | 4 +- .../client/PolygonWebsocketClient.java | 28 +++--- .../PolygonWebsocketClientEndpoint.java | 6 +- .../listener/PolygonStreamListener.java | 8 +- .../PolygonStreamListenerAdapter.java | 6 +- .../message/PolygonStreamMessageType.java | 8 +- .../util/concurrency/ExecutorTracer.java | 2 +- .../ScheduledThreadPoolExecutorTracer.java | 2 +- .../concurrency/ThreadPoolExecutorTracer.java | 2 +- .../util/doc/Documentation2JSONSchema.java | 2 +- .../jacobpeterson}/util/gson/GsonUtil.java | 2 +- .../util/gson/ZonedDateTimeAdapter.java | 2 +- .../util/properties/PropertyUtil.java | 2 +- .../jacobpeterson}/util/time/TimeUtil.java | 2 +- 108 files changed, 359 insertions(+), 362 deletions(-) rename src/main/java/{io/github/mainstringargs => net/jacobpeterson}/abstracts/enums/APIName.java (72%) rename src/main/java/{io/github/mainstringargs => net/jacobpeterson}/abstracts/rest/AbstractRequest.java (98%) rename src/main/java/{io/github/mainstringargs => net/jacobpeterson}/abstracts/rest/AbstractRequestBuilder.java (98%) rename src/main/java/{io/github/mainstringargs => net/jacobpeterson}/abstracts/rest/exception/AbstractAPIRequestException.java (97%) rename src/main/java/{io/github/mainstringargs => net/jacobpeterson}/abstracts/websocket/client/AbstractWebsocketClientEndpoint.java (97%) rename src/main/java/{io/github/mainstringargs => net/jacobpeterson}/abstracts/websocket/client/WebsocketClient.java (80%) rename src/main/java/{io/github/mainstringargs => net/jacobpeterson}/abstracts/websocket/listener/StreamListener.java (67%) rename src/main/java/{io/github/mainstringargs => net/jacobpeterson}/abstracts/websocket/message/StreamMessage.java (53%) rename src/main/java/{io/github/mainstringargs => net/jacobpeterson}/abstracts/websocket/message/StreamMessageType.java (56%) rename src/main/java/{io/github/mainstringargs => net/jacobpeterson}/alpaca/AlpacaAPI.java (96%) rename src/main/java/{io/github/mainstringargs => net/jacobpeterson}/alpaca/AlpacaConstants.java (99%) rename src/main/java/{io/github/mainstringargs => net/jacobpeterson}/alpaca/AlpacaExample.java (81%) rename src/main/java/{io/github/mainstringargs => net/jacobpeterson}/alpaca/enums/AccountStatus.java (90%) rename src/main/java/{io/github/mainstringargs => net/jacobpeterson}/alpaca/enums/ActivityType.java (94%) rename src/main/java/{io/github/mainstringargs => net/jacobpeterson}/alpaca/enums/AssetStatus.java (82%) rename src/main/java/{io/github/mainstringargs => net/jacobpeterson}/alpaca/enums/BarsTimeFrame.java (86%) rename src/main/java/{io/github/mainstringargs => net/jacobpeterson}/alpaca/enums/Direction.java (81%) rename src/main/java/{io/github/mainstringargs => net/jacobpeterson}/alpaca/enums/OrderClass.java (87%) rename src/main/java/{io/github/mainstringargs => net/jacobpeterson}/alpaca/enums/OrderSide.java (81%) rename src/main/java/{io/github/mainstringargs => net/jacobpeterson}/alpaca/enums/OrderStatus.java (82%) rename src/main/java/{io/github/mainstringargs => net/jacobpeterson}/alpaca/enums/OrderTimeInForce.java (86%) rename src/main/java/{io/github/mainstringargs => net/jacobpeterson}/alpaca/enums/OrderType.java (84%) rename src/main/java/{io/github/mainstringargs => net/jacobpeterson}/alpaca/enums/PortfolioPeriodUnit.java (85%) rename src/main/java/{io/github/mainstringargs => net/jacobpeterson}/alpaca/enums/PortfolioTimeFrame.java (80%) rename src/main/java/{io/github/mainstringargs => net/jacobpeterson}/alpaca/properties/AlpacaProperties.java (95%) rename src/main/java/{io/github/mainstringargs => net/jacobpeterson}/alpaca/rest/AlpacaRequest.java (81%) rename src/main/java/{io/github/mainstringargs => net/jacobpeterson}/alpaca/rest/AlpacaRequestBuilder.java (84%) rename src/main/java/{io/github/mainstringargs => net/jacobpeterson}/alpaca/rest/exception/AlpacaAPIRequestException.java (79%) rename src/main/java/{io/github/mainstringargs => net/jacobpeterson}/alpaca/websocket/client/AlpacaWebsocketClient.java (90%) rename src/main/java/{io/github/mainstringargs => net/jacobpeterson}/alpaca/websocket/client/AlpacaWebsocketClientEndpoint.java (84%) rename src/main/java/{io/github/mainstringargs => net/jacobpeterson}/alpaca/websocket/listener/AlpacaStreamListener.java (61%) rename src/main/java/{io/github/mainstringargs => net/jacobpeterson}/alpaca/websocket/listener/AlpacaStreamListenerAdapter.java (81%) rename src/main/java/{io/github/mainstringargs => net/jacobpeterson}/alpaca/websocket/message/AlpacaStreamMessageType.java (83%) rename src/main/java/{io/github/mainstringargs => net/jacobpeterson}/polygon/PolygonAPI.java (91%) rename src/main/java/{io/github/mainstringargs => net/jacobpeterson}/polygon/PolygonConstants.java (99%) rename src/main/java/{io/github/mainstringargs => net/jacobpeterson}/polygon/PolygonExample.java (77%) rename src/main/java/{io/github/mainstringargs => net/jacobpeterson}/polygon/enums/ConditionMappingsType.java (83%) rename src/main/java/{io/github/mainstringargs => net/jacobpeterson}/polygon/enums/FinancialReportType.java (87%) rename src/main/java/{io/github/mainstringargs => net/jacobpeterson}/polygon/enums/FinancialSort.java (88%) rename src/main/java/{io/github/mainstringargs => net/jacobpeterson}/polygon/enums/GainersLosersDirection.java (80%) rename src/main/java/{io/github/mainstringargs => net/jacobpeterson}/polygon/enums/Market.java (86%) rename src/main/java/{io/github/mainstringargs => net/jacobpeterson}/polygon/enums/StockType.java (81%) rename src/main/java/{io/github/mainstringargs => net/jacobpeterson}/polygon/enums/TickerSort.java (85%) rename src/main/java/{io/github/mainstringargs => net/jacobpeterson}/polygon/enums/Timespan.java (86%) rename src/main/java/{io/github/mainstringargs => net/jacobpeterson}/polygon/properties/PolygonProperties.java (91%) rename src/main/java/{io/github/mainstringargs => net/jacobpeterson}/polygon/rest/PolygonRequest.java (90%) rename src/main/java/{io/github/mainstringargs => net/jacobpeterson}/polygon/rest/PolygonRequestBuilder.java (84%) rename src/main/java/{io/github/mainstringargs => net/jacobpeterson}/polygon/rest/exception/PolygonAPIRequestException.java (79%) rename src/main/java/{io/github/mainstringargs => net/jacobpeterson}/polygon/websocket/client/PolygonWebsocketClient.java (93%) rename src/main/java/{io/github/mainstringargs => net/jacobpeterson}/polygon/websocket/client/PolygonWebsocketClientEndpoint.java (83%) rename src/main/java/{io/github/mainstringargs => net/jacobpeterson}/polygon/websocket/listener/PolygonStreamListener.java (71%) rename src/main/java/{io/github/mainstringargs => net/jacobpeterson}/polygon/websocket/listener/PolygonStreamListenerAdapter.java (91%) rename src/main/java/{io/github/mainstringargs => net/jacobpeterson}/polygon/websocket/message/PolygonStreamMessageType.java (84%) rename src/main/java/{io/github/mainstringargs => net/jacobpeterson}/util/concurrency/ExecutorTracer.java (99%) rename src/main/java/{io/github/mainstringargs => net/jacobpeterson}/util/concurrency/ScheduledThreadPoolExecutorTracer.java (97%) rename src/main/java/{io/github/mainstringargs => net/jacobpeterson}/util/concurrency/ThreadPoolExecutorTracer.java (97%) rename src/main/java/{io/github/mainstringargs => net/jacobpeterson}/util/doc/Documentation2JSONSchema.java (99%) rename src/main/java/{io/github/mainstringargs => net/jacobpeterson}/util/gson/GsonUtil.java (98%) rename src/main/java/{io/github/mainstringargs => net/jacobpeterson}/util/gson/ZonedDateTimeAdapter.java (96%) rename src/main/java/{io/github/mainstringargs => net/jacobpeterson}/util/properties/PropertyUtil.java (98%) rename src/main/java/{io/github/mainstringargs => net/jacobpeterson}/util/time/TimeUtil.java (98%) diff --git a/README.md b/README.md index 58656ece..b39021bf 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,9 @@ -

+# UPDATING FROM 5.0.13 TO 5.1 +This repository has been transferred and as a result, lots of refactoring has occurred. This is because artifacts cannot be transferred in Maven Central. So, the new groupID for the project is `net.jacobpeterson` and all packages have been renamed from `io.github.mainstringargs` to `net.jacobpeterson`. Please refactor accordingly when you update from 5.0.13 to 5.1. Thanks! +

-Maven Central Javadocs Build Status - GitHub +Maven Central Javadocs Build Status + GitHub

# Overview @@ -31,7 +33,7 @@ Add the following dependency to your build.gradle file: ``` dependencies { - compile "io.github.mainstringargs:alpaca-java:5.0.13" + compile "net.jacobpeterson:alpaca-java:5.1" } ``` @@ -41,9 +43,9 @@ Add the following dependency to your pom.xml file: ``` - io.github.mainstringargs + net.jacobpeterson alpaca-java - 5.0.13 + 5.1 compile ``` @@ -58,7 +60,7 @@ base_api_url = https://paper-api.alpaca.markets base_data_url = https://data.alpaca.markets user_agent=a_user_agent ``` -The default values for `alpaca.properties` can be found [here](https://github.com/mainstringargs/alpaca-java/tree/master/src/main/resources). +The default values for `alpaca.properties` can be found [here](https://github.com/Petersoj/alpaca-java/tree/master/src/main/resources). Similarly, set the following properties in a polygon.properties file on the classpath for using the PolygonAPI: ``` @@ -68,11 +70,11 @@ base_api_url = https://api.polygon.io web_socket_server_url = wss://alpaca.socket.polygon.io/stocks user_agent=a_user_agent ``` -The default values for `polygon.properties` can be found [here](https://github.com/mainstringargs/alpaca-java/tree/master/src/main/resources). +The default values for `polygon.properties` can be found [here](https://github.com/Petersoj/alpaca-java/tree/master/src/main/resources). ## AlpacaAPI Example -This example uses the `AlpacaAPI` class to subscribe to the Account and Trade Updates stream, print out the account information, submit a limit order, create a watchlist, and print out bars data. Click [here](https://docs.alpaca.markets/api-documentation/api-v2/) for the general Alpaca API documentation and click [here](https://javadoc.io/doc/io.github.mainstringargs/alpaca-java/latest/io/github/mainstringargs/alpaca/AlpacaAPI.html) for the `AlpacaAPI` javadoc. +This example uses the `AlpacaAPI` class to subscribe to the Account and Trade Updates stream, print out the account information, submit a limit order, create a watchlist, and print out bars data. Click [here](https://docs.alpaca.markets/api-documentation/api-v2/) for the general Alpaca API documentation and click [here](https://javadoc.io/doc/net.jacobpeterson/alpaca-java/latest/net/jacobpeterson/alpaca/AlpacaAPI.html) for the `AlpacaAPI` javadoc. ```java // This logs into Alpaca using the alpaca.properties file on the classpath. @@ -300,7 +302,7 @@ Bars response: # PolygonAPI Example -This example uses the `PolygonAPI` class to subscribe to the Polygon websocket stream, get stocks splits, and get aggregates. Click [here](https://polygon.io/docs/) for the general Polygon API documentation and click [here](https://javadoc.io/doc/io.github.mainstringargs/alpaca-java/latest/io/github/mainstringargs/polygon/PolygonAPI.html) for the `PolygonAPI` javadoc. +This example uses the `PolygonAPI` class to subscribe to the Polygon websocket stream, get stocks splits, and get aggregates. Click [here](https://polygon.io/docs/) for the general Polygon API documentation and click [here](https://javadoc.io/doc/net.jacobpeterson/alpaca-java/latest/net/jacobpeterson/polygon/PolygonAPI.html) for the `PolygonAPI` javadoc. ```java // This will use the key_id in the alpaca.properties file by default diff --git a/build.gradle b/build.gradle index c823ecf3..7366210e 100644 --- a/build.gradle +++ b/build.gradle @@ -26,8 +26,8 @@ apply plugin: 'jsonschema2pojo' tasks.getByName("generateJsonSchema2Pojo").enabled(false) // Disable default jsonSchema2Pojo task archivesBaseName = 'alpaca-java' -group = 'io.github.mainstringargs' -version = '5.0.13-SNAPSHOT' +group = 'net.jacobpeterson' +version = '5.1' repositories { mavenLocal() @@ -58,8 +58,8 @@ targetCompatibility = 1.8 javadoc.options.addStringOption('Xdoclint:none', '-quiet') javadoc.options.addStringOption("charset", "UTF-8") -//mainClassName = 'io.github.mainstringargs.polygon.PolygonExample' -mainClassName = 'io.github.mainstringargs.alpaca.AlpacaExample' +//mainClassName = 'net.jacobpeterson.polygon.PolygonExample' +mainClassName = 'net.jacobpeterson.alpaca.AlpacaExample' // // END Build options // @@ -67,42 +67,37 @@ mainClassName = 'io.github.mainstringargs.alpaca.AlpacaExample' // // START publishing // -publishing { - publications { - mainPublication(MavenPublication) { - from components.java - - pom { - name = 'alpaca-java' - description = 'Java API for Alpaca and Polygon' - url = 'https://github.com/Petersoj/alpaca-java' - inceptionYear = '2018' - - licenses { - license { - name = 'MIT License' - url = 'https://opensource.org/licenses/MIT' - } - } - - developers { - developer { - id = 'Petersoj' - name = 'Jacob Peterson' - } - - developer { - id = 'mainstringargs' - name = 'main(String[] args)' - } - } - - scm { - url = "https://github.com/Petersoj/alpaca-java.git" - connection = "scm:git:https://github.com/Petersoj/alpaca-java.git" - developerConnection = "scm:git:git@github.com/Petersoj/alpaca-java.git" - } + +modifyPom { + project { + name = 'alpaca-java' + description = 'Java API for Alpaca and Polygon' + url = 'https://github.com/Petersoj/alpaca-java' + inceptionYear = '2018' + + licenses { + license { + name = 'MIT License' + url = 'https://opensource.org/licenses/MIT' + } + } + + developers { + developer { + id = 'Petersoj' + name = 'Jacob Peterson' } + + developer { + id = 'mainstringargs' + name = 'main(String[] args)' + } + } + + scm { + url = "https://github.com/Petersoj/alpaca-java.git" + connection = "scm:git:https://github.com/Petersoj/alpaca-java.git" + developerConnection = "scm:git:git@github.com/Petersoj/alpaca-java.git" } } } @@ -120,7 +115,7 @@ nexus { } nexusStaging { - packageGroup = "io.github.mainstringargs" + packageGroup = "net.jacobpeterson" } // Used to prevent accidental publication of Alpaca keys @@ -139,7 +134,7 @@ processResources { task generatePOJOs() { final def targetDirectory = "${project.buildDir}" + File.separator + "generated-sources" + File.separator + "schemajson" final def jsonSourceDirectory = "${project.projectDir}" + File.separator + "schema_json" + File.separator - final def jsonPackageNameStart = "io.github.mainstringargs.domain." + final def jsonPackageNameStart = "net.jacobpeterson.domain." // Loop through all files in schema JSON file tree file(jsonSourceDirectory).eachFileRecurse { jsonFile -> diff --git a/schema_json/alpaca/account/account.json b/schema_json/alpaca/account/account.json index b4423179..fb93106b 100644 --- a/schema_json/alpaca/account/account.json +++ b/schema_json/alpaca/account/account.json @@ -11,7 +11,7 @@ "title": "Account number." }, "status": { - "existingJavaType": "io.github.mainstringargs.alpaca.enums.AccountStatus", + "existingJavaType": "net.jacobpeterson.alpaca.enums.AccountStatus", "title": "See Account Status" }, "currency": { diff --git a/schema_json/alpaca/account_activities/non_trade_activity.json b/schema_json/alpaca/account_activities/non_trade_activity.json index 17b43c6b..d5f18724 100644 --- a/schema_json/alpaca/account_activities/non_trade_activity.json +++ b/schema_json/alpaca/account_activities/non_trade_activity.json @@ -2,7 +2,7 @@ "type": "object", "title": "See https://docs.alpaca.markets/api-documentation/api-v2/account-activities/", "extends": { - "existingJavaType": "io.github.mainstringargs.domain.alpaca.accountactivities.AccountActivity" + "existingJavaType": "net.jacobpeterson.domain.alpaca.accountactivities.AccountActivity" }, "properties": { "activity_type": { diff --git a/schema_json/alpaca/account_activities/trade_activity.json b/schema_json/alpaca/account_activities/trade_activity.json index 99b52cee..4bc34b68 100644 --- a/schema_json/alpaca/account_activities/trade_activity.json +++ b/schema_json/alpaca/account_activities/trade_activity.json @@ -2,7 +2,7 @@ "type": "object", "title": "See https://docs.alpaca.markets/api-documentation/api-v2/account-activities/", "extends": { - "existingJavaType": "io.github.mainstringargs.domain.alpaca.accountactivities.AccountActivity" + "existingJavaType": "net.jacobpeterson.domain.alpaca.accountactivities.AccountActivity" }, "properties": { "activity_type": { diff --git a/schema_json/alpaca/order/cancelled_order.json b/schema_json/alpaca/order/cancelled_order.json index 531be7c9..1cd5c244 100644 --- a/schema_json/alpaca/order/cancelled_order.json +++ b/schema_json/alpaca/order/cancelled_order.json @@ -11,7 +11,7 @@ "title": "The HTTP status code of the cancel status" }, "body": { - "existingJavaType": "io.github.mainstringargs.domain.alpaca.order.Order", + "existingJavaType": "net.jacobpeterson.domain.alpaca.order.Order", "title": "The cancelled order" } } diff --git a/schema_json/alpaca/order/order.json b/schema_json/alpaca/order/order.json index 16f377e0..36f0be60 100644 --- a/schema_json/alpaca/order/order.json +++ b/schema_json/alpaca/order/order.json @@ -103,7 +103,7 @@ "title": "If true, eligible for execution outside regular trading hours." }, "legs": { - "existingJavaType": "java.util.ArrayList", + "existingJavaType": "java.util.ArrayList", "title": "When querying non-simple order_class orders in a nested style, an array of Order entities associated with this order is returned. Otherwise, null." } } diff --git a/schema_json/alpaca/portfolio_history/portfolio_history.json b/schema_json/alpaca/portfolio_history/portfolio_history.json index b39982c3..254eacdf 100644 --- a/schema_json/alpaca/portfolio_history/portfolio_history.json +++ b/schema_json/alpaca/portfolio_history/portfolio_history.json @@ -23,7 +23,7 @@ "title": "Basis in dollar of the profit loss calculation" }, "timeframe": { - "existingJavaType": "io.github.mainstringargs.alpaca.enums.PortfolioTimeFrame", + "existingJavaType": "net.jacobpeterson.alpaca.enums.PortfolioTimeFrame", "title": "Time window size of each data element" } } diff --git a/schema_json/alpaca/position/close_position_order.json b/schema_json/alpaca/position/close_position_order.json index 0731f66d..31f14ae3 100644 --- a/schema_json/alpaca/position/close_position_order.json +++ b/schema_json/alpaca/position/close_position_order.json @@ -11,7 +11,7 @@ "title": "The HTTP status code of the position-closing order status" }, "body": { - "existingJavaType": "io.github.mainstringargs.domain.alpaca.order.Order", + "existingJavaType": "net.jacobpeterson.domain.alpaca.order.Order", "title": "The position-closing order" } } diff --git a/schema_json/alpaca/watchlist/watchlist.json b/schema_json/alpaca/watchlist/watchlist.json index 9976aac0..6f924558 100644 --- a/schema_json/alpaca/watchlist/watchlist.json +++ b/schema_json/alpaca/watchlist/watchlist.json @@ -23,7 +23,7 @@ "title": "Account ID" }, "assets": { - "existingJavaType": "java.util.ArrayList", + "existingJavaType": "java.util.ArrayList", "title": "The content of this watchlist, in the order as registered by the client" } } diff --git a/schema_json/alpaca/websocket/account/account_update.json b/schema_json/alpaca/websocket/account/account_update.json index 83fb89d7..90c1b561 100644 --- a/schema_json/alpaca/websocket/account/account_update.json +++ b/schema_json/alpaca/websocket/account/account_update.json @@ -1,7 +1,7 @@ { "type": "object", "extends": { - "existingJavaType": "io.github.mainstringargs.domain.alpaca.account.Account" + "existingJavaType": "net.jacobpeterson.domain.alpaca.account.Account" }, "title": "See https://docs.alpaca.markets/api-documentation/api-v2/streaming/" } diff --git a/schema_json/alpaca/websocket/account/account_update_message.json b/schema_json/alpaca/websocket/account/account_update_message.json index 0a68a12c..abd838e5 100644 --- a/schema_json/alpaca/websocket/account/account_update_message.json +++ b/schema_json/alpaca/websocket/account/account_update_message.json @@ -1,12 +1,12 @@ { "type": "object", "extends": { - "existingJavaType": "io.github.mainstringargs.domain.alpaca.websocket.AlpacaStreamMessage" + "existingJavaType": "net.jacobpeterson.domain.alpaca.websocket.AlpacaStreamMessage" }, "title": "See https://docs.alpaca.markets/api-documentation/api-v2/streaming/", "properties": { "data": { - "existingJavaType": "io.github.mainstringargs.domain.alpaca.websocket.account.AccountUpdate" + "existingJavaType": "net.jacobpeterson.domain.alpaca.websocket.account.AccountUpdate" } } } diff --git a/schema_json/alpaca/websocket/alpaca_stream_message.json b/schema_json/alpaca/websocket/alpaca_stream_message.json index d1d0cca4..fc3c44bd 100644 --- a/schema_json/alpaca/websocket/alpaca_stream_message.json +++ b/schema_json/alpaca/websocket/alpaca_stream_message.json @@ -1,12 +1,12 @@ { "type": "object", "javaInterfaces": [ - "io.github.mainstringargs.abstracts.websocket.message.StreamMessage" + "net.jacobpeterson.abstracts.websocket.message.StreamMessage" ], "title": "See https://docs.alpaca.markets/api-documentation/api-v2/streaming/", "properties": { "stream": { - "existingJavaType": "io.github.mainstringargs.alpaca.websocket.message.AlpacaStreamMessageType", + "existingJavaType": "net.jacobpeterson.alpaca.websocket.message.AlpacaStreamMessageType", "title": "The stream type" } } diff --git a/schema_json/alpaca/websocket/authorization/authorization_message.json b/schema_json/alpaca/websocket/authorization/authorization_message.json index 04e7e88a..0bf7ec3e 100644 --- a/schema_json/alpaca/websocket/authorization/authorization_message.json +++ b/schema_json/alpaca/websocket/authorization/authorization_message.json @@ -2,11 +2,11 @@ "type": "object", "title": "See https://docs.alpaca.markets/api-documentation/api-v2/streaming/", "extends": { - "existingJavaType": "io.github.mainstringargs.domain.alpaca.websocket.AlpacaStreamMessage" + "existingJavaType": "net.jacobpeterson.domain.alpaca.websocket.AlpacaStreamMessage" }, "properties": { "data": { - "existingJavaType": "io.github.mainstringargs.domain.alpaca.websocket.authorization.AuthorizationData", + "existingJavaType": "net.jacobpeterson.domain.alpaca.websocket.authorization.AuthorizationData", "title": "The data" } } diff --git a/schema_json/alpaca/websocket/listening/listening_message.json b/schema_json/alpaca/websocket/listening/listening_message.json index 013a80fb..8f732137 100644 --- a/schema_json/alpaca/websocket/listening/listening_message.json +++ b/schema_json/alpaca/websocket/listening/listening_message.json @@ -2,11 +2,11 @@ "type": "object", "title": "See https://docs.alpaca.markets/api-documentation/api-v2/streaming/", "extends": { - "existingJavaType": "io.github.mainstringargs.domain.alpaca.websocket.AlpacaStreamMessage" + "existingJavaType": "net.jacobpeterson.domain.alpaca.websocket.AlpacaStreamMessage" }, "properties": { "data": { - "existingJavaType": "io.github.mainstringargs.domain.alpaca.websocket.listening.ListeningData", + "existingJavaType": "net.jacobpeterson.domain.alpaca.websocket.listening.ListeningData", "title": "The data" } } diff --git a/schema_json/alpaca/websocket/trade/trade_update.json b/schema_json/alpaca/websocket/trade/trade_update.json index 694a4bcd..a94b47f6 100644 --- a/schema_json/alpaca/websocket/trade/trade_update.json +++ b/schema_json/alpaca/websocket/trade/trade_update.json @@ -19,7 +19,7 @@ "title": "The position quantity" }, "order": { - "existingJavaType": "io.github.mainstringargs.domain.alpaca.order.Order", + "existingJavaType": "net.jacobpeterson.domain.alpaca.order.Order", "title": "The Order" } } diff --git a/schema_json/alpaca/websocket/trade/trade_update_message.json b/schema_json/alpaca/websocket/trade/trade_update_message.json index 7a7498ce..f461ec0b 100644 --- a/schema_json/alpaca/websocket/trade/trade_update_message.json +++ b/schema_json/alpaca/websocket/trade/trade_update_message.json @@ -1,12 +1,12 @@ { "type": "object", "extends": { - "existingJavaType": "io.github.mainstringargs.domain.alpaca.websocket.AlpacaStreamMessage" + "existingJavaType": "net.jacobpeterson.domain.alpaca.websocket.AlpacaStreamMessage" }, "title": "See https://docs.alpaca.markets/api-documentation/api-v2/streaming/", "properties": { "data": { - "existingJavaType": "io.github.mainstringargs.domain.alpaca.websocket.trade.TradeUpdate" + "existingJavaType": "net.jacobpeterson.domain.alpaca.websocket.trade.TradeUpdate" } } } diff --git a/schema_json/polygon/aggregates/aggregates_response.json b/schema_json/polygon/aggregates/aggregates_response.json index f8924c96..dde56292 100644 --- a/schema_json/polygon/aggregates/aggregates_response.json +++ b/schema_json/polygon/aggregates/aggregates_response.json @@ -23,7 +23,7 @@ "title": "Total number of results generated" }, "results": { - "existingJavaType": "java.util.ArrayList", + "existingJavaType": "java.util.ArrayList", "title": "results" } } diff --git a/schema_json/polygon/daily_open_close/daily_open_close_response.json b/schema_json/polygon/daily_open_close/daily_open_close_response.json index 972e9e32..8b2a3dea 100644 --- a/schema_json/polygon/daily_open_close/daily_open_close_response.json +++ b/schema_json/polygon/daily_open_close/daily_open_close_response.json @@ -7,15 +7,15 @@ "title": "Symbol that was evaluated from the request" }, "open": { - "existingJavaType": "io.github.mainstringargs.domain.polygon.dailyopenclose.DailyOpenCloseTrade", + "existingJavaType": "net.jacobpeterson.domain.polygon.dailyopenclose.DailyOpenCloseTrade", "title": "open" }, "close": { - "existingJavaType": "io.github.mainstringargs.domain.polygon.dailyopenclose.DailyOpenCloseTrade", + "existingJavaType": "net.jacobpeterson.domain.polygon.dailyopenclose.DailyOpenCloseTrade", "title": "close" }, "afterHours": { - "existingJavaType": "io.github.mainstringargs.domain.polygon.dailyopenclose.DailyOpenCloseTrade", + "existingJavaType": "net.jacobpeterson.domain.polygon.dailyopenclose.DailyOpenCloseTrade", "title": "afterHours" } } diff --git a/schema_json/polygon/grouped_daily/grouped_daily_response.json b/schema_json/polygon/grouped_daily/grouped_daily_response.json index 6a39aa9d..dbe3fbd0 100644 --- a/schema_json/polygon/grouped_daily/grouped_daily_response.json +++ b/schema_json/polygon/grouped_daily/grouped_daily_response.json @@ -23,7 +23,7 @@ "title": "Total number of results generated" }, "results": { - "existingJavaType": "java.util.ArrayList", + "existingJavaType": "java.util.ArrayList", "title": "Results" } } diff --git a/schema_json/polygon/historic_quotes/historic_quotes_response.json b/schema_json/polygon/historic_quotes/historic_quotes_response.json index 62a6026a..9319ef32 100644 --- a/schema_json/polygon/historic_quotes/historic_quotes_response.json +++ b/schema_json/polygon/historic_quotes/historic_quotes_response.json @@ -19,7 +19,7 @@ "title": "Ticker symbol that was evaluated from the request" }, "results": { - "existingJavaType": "java.util.ArrayList", + "existingJavaType": "java.util.ArrayList", "title": "results" } } diff --git a/schema_json/polygon/historic_trades/historic_trades_response.json b/schema_json/polygon/historic_trades/historic_trades_response.json index d37da34b..12dee80a 100644 --- a/schema_json/polygon/historic_trades/historic_trades_response.json +++ b/schema_json/polygon/historic_trades/historic_trades_response.json @@ -19,7 +19,7 @@ "title": "Ticker symbol that was evaluated from the request" }, "results": { - "existingJavaType": "java.util.ArrayList", + "existingJavaType": "java.util.ArrayList", "title": "results" } } diff --git a/schema_json/polygon/last_quote/last_quote_response.json b/schema_json/polygon/last_quote/last_quote_response.json index 20ce7727..9528b29d 100644 --- a/schema_json/polygon/last_quote/last_quote_response.json +++ b/schema_json/polygon/last_quote/last_quote_response.json @@ -11,7 +11,7 @@ "title": "Symbol that was evaluated from the request" }, "last": { - "existingJavaType": "io.github.mainstringargs.domain.polygon.lastquote.LastQuote", + "existingJavaType": "net.jacobpeterson.domain.polygon.lastquote.LastQuote", "title": "last" } } diff --git a/schema_json/polygon/last_trade/last_trade_response.json b/schema_json/polygon/last_trade/last_trade_response.json index c86bb30d..96ac11b2 100644 --- a/schema_json/polygon/last_trade/last_trade_response.json +++ b/schema_json/polygon/last_trade/last_trade_response.json @@ -11,7 +11,7 @@ "title": "Symbol that was evaluated from the request" }, "last": { - "existingJavaType": "io.github.mainstringargs.domain.polygon.lasttrade.LastTrade", + "existingJavaType": "net.jacobpeterson.domain.polygon.lasttrade.LastTrade", "title": "last" } } diff --git a/schema_json/polygon/locales/locales_response.json b/schema_json/polygon/locales/locales_response.json index 6be63576..6006bb64 100644 --- a/schema_json/polygon/locales/locales_response.json +++ b/schema_json/polygon/locales/locales_response.json @@ -7,7 +7,7 @@ "title": "Status" }, "results": { - "existingJavaType": "java.util.ArrayList", + "existingJavaType": "java.util.ArrayList", "title": "Results" } } diff --git a/schema_json/polygon/markets/markets_response.json b/schema_json/polygon/markets/markets_response.json index 619a5031..6c45fee6 100644 --- a/schema_json/polygon/markets/markets_response.json +++ b/schema_json/polygon/markets/markets_response.json @@ -7,7 +7,7 @@ "title": "Status" }, "results": { - "existingJavaType": "java.util.ArrayList", + "existingJavaType": "java.util.ArrayList", "title": "Results" } } diff --git a/schema_json/polygon/previous_close/previous_close_response.json b/schema_json/polygon/previous_close/previous_close_response.json index da57b1a1..f31fc57a 100644 --- a/schema_json/polygon/previous_close/previous_close_response.json +++ b/schema_json/polygon/previous_close/previous_close_response.json @@ -23,7 +23,7 @@ "title": "Total number of results generated" }, "results": { - "existingJavaType": "java.util.ArrayList", + "existingJavaType": "java.util.ArrayList", "title": "Results" } } diff --git a/schema_json/polygon/snapshot/snapshot_all_tickers_response.json b/schema_json/polygon/snapshot/snapshot_all_tickers_response.json index 42b1c494..7d1a502c 100644 --- a/schema_json/polygon/snapshot/snapshot_all_tickers_response.json +++ b/schema_json/polygon/snapshot/snapshot_all_tickers_response.json @@ -7,7 +7,7 @@ "title": "Status of this requests response" }, "tickers": { - "existingJavaType": "java.util.ArrayList", + "existingJavaType": "java.util.ArrayList", "title": "tickers" } } diff --git a/schema_json/polygon/snapshot/snapshot_gainers_losers_response.json b/schema_json/polygon/snapshot/snapshot_gainers_losers_response.json index dae0184a..e5bf71a1 100644 --- a/schema_json/polygon/snapshot/snapshot_gainers_losers_response.json +++ b/schema_json/polygon/snapshot/snapshot_gainers_losers_response.json @@ -7,7 +7,7 @@ "title": "Status of this requests response" }, "tickers": { - "existingJavaType": "java.util.ArrayList", + "existingJavaType": "java.util.ArrayList", "title": "tickers" } } diff --git a/schema_json/polygon/snapshot/snapshot_single_ticker_response.json b/schema_json/polygon/snapshot/snapshot_single_ticker_response.json index 389c9f15..8861077b 100644 --- a/schema_json/polygon/snapshot/snapshot_single_ticker_response.json +++ b/schema_json/polygon/snapshot/snapshot_single_ticker_response.json @@ -7,7 +7,7 @@ "title": "Status of this requests response" }, "ticker": { - "existingJavaType": "io.github.mainstringargs.domain.polygon.snapshot.ticker.SnapshotTicker", + "existingJavaType": "net.jacobpeterson.domain.polygon.snapshot.ticker.SnapshotTicker", "title": "tickers" } } diff --git a/schema_json/polygon/snapshot/snapshot_ticker_book.json b/schema_json/polygon/snapshot/snapshot_ticker_book.json index d9f795ce..3d14c5e5 100644 --- a/schema_json/polygon/snapshot/snapshot_ticker_book.json +++ b/schema_json/polygon/snapshot/snapshot_ticker_book.json @@ -7,7 +7,7 @@ "title": "Status of this requests response" }, "data": { - "existingJavaType": "io.github.mainstringargs.domain.polygon.snapshot.ticker.book.BookData", + "existingJavaType": "net.jacobpeterson.domain.polygon.snapshot.ticker.book.BookData", "title": "The book data" } } diff --git a/schema_json/polygon/snapshot/ticker/book/book_data.json b/schema_json/polygon/snapshot/ticker/book/book_data.json index 0fbc7d1c..3d5becdf 100644 --- a/schema_json/polygon/snapshot/ticker/book/book_data.json +++ b/schema_json/polygon/snapshot/ticker/book/book_data.json @@ -7,11 +7,11 @@ "title": "The ticker" }, "bids": { - "existingJavaType": "java.util.ArrayList", + "existingJavaType": "java.util.ArrayList", "title": "The bids on the book" }, "asks": { - "existingJavaType": "java.util.ArrayList", + "existingJavaType": "java.util.ArrayList", "title": "The asks on the book" }, "bidCount": { diff --git a/schema_json/polygon/snapshot/ticker/snapshot_ticker.json b/schema_json/polygon/snapshot/ticker/snapshot_ticker.json index 0b9f7d68..50bfd180 100644 --- a/schema_json/polygon/snapshot/ticker/snapshot_ticker.json +++ b/schema_json/polygon/snapshot/ticker/snapshot_ticker.json @@ -7,23 +7,23 @@ "title": "Ticker of the object" }, "day": { - "existingJavaType": "io.github.mainstringargs.domain.polygon.snapshot.ticker.TickerCandle", + "existingJavaType": "net.jacobpeterson.domain.polygon.snapshot.ticker.TickerCandle", "title": "day" }, "lastTrade": { - "existingJavaType": "io.github.mainstringargs.domain.polygon.snapshot.ticker.LastTrade", + "existingJavaType": "net.jacobpeterson.domain.polygon.snapshot.ticker.LastTrade", "title": "lastTrade" }, "lastQuote": { - "existingJavaType": "io.github.mainstringargs.domain.polygon.snapshot.ticker.LastQuote", + "existingJavaType": "net.jacobpeterson.domain.polygon.snapshot.ticker.LastQuote", "title": "lastQuote" }, "min": { - "existingJavaType": "io.github.mainstringargs.domain.polygon.snapshot.ticker.TickerCandle", + "existingJavaType": "net.jacobpeterson.domain.polygon.snapshot.ticker.TickerCandle", "title": "min" }, "prevDay": { - "existingJavaType": "io.github.mainstringargs.domain.polygon.snapshot.ticker.TickerCandle", + "existingJavaType": "net.jacobpeterson.domain.polygon.snapshot.ticker.TickerCandle", "title": "prevDay" }, "todaysChange": { diff --git a/schema_json/polygon/stock_dividends/stock_dividends_response.json b/schema_json/polygon/stock_dividends/stock_dividends_response.json index b9f7fe26..b35c9456 100644 --- a/schema_json/polygon/stock_dividends/stock_dividends_response.json +++ b/schema_json/polygon/stock_dividends/stock_dividends_response.json @@ -11,7 +11,7 @@ "title": "Count" }, "results": { - "existingJavaType": "java.util.ArrayList", + "existingJavaType": "java.util.ArrayList", "title": "Results" } } diff --git a/schema_json/polygon/stock_financials/stock_financials_response.json b/schema_json/polygon/stock_financials/stock_financials_response.json index 725cfffd..e8c86207 100644 --- a/schema_json/polygon/stock_financials/stock_financials_response.json +++ b/schema_json/polygon/stock_financials/stock_financials_response.json @@ -11,7 +11,7 @@ "title": "Count" }, "results": { - "existingJavaType": "java.util.ArrayList", + "existingJavaType": "java.util.ArrayList", "title": "Results" } } diff --git a/schema_json/polygon/stock_splits/stock_splits_response.json b/schema_json/polygon/stock_splits/stock_splits_response.json index ffdf5926..5437ad2a 100644 --- a/schema_json/polygon/stock_splits/stock_splits_response.json +++ b/schema_json/polygon/stock_splits/stock_splits_response.json @@ -11,7 +11,7 @@ "title": "Count" }, "results": { - "existingJavaType": "java.util.ArrayList", + "existingJavaType": "java.util.ArrayList", "title": "Results" } } diff --git a/schema_json/polygon/ticker_types/ticket_types_response.json b/schema_json/polygon/ticker_types/ticket_types_response.json index 4a9c0694..4f22bb9c 100644 --- a/schema_json/polygon/ticker_types/ticket_types_response.json +++ b/schema_json/polygon/ticker_types/ticket_types_response.json @@ -7,7 +7,7 @@ "title": "Status" }, "results": { - "existingJavaType": "io.github.mainstringargs.domain.polygon.tickertypes.TickerTypes", + "existingJavaType": "net.jacobpeterson.domain.polygon.tickertypes.TickerTypes", "title": "Results" } } diff --git a/schema_json/polygon/tickers/ticker/forex_ticker.json b/schema_json/polygon/tickers/ticker/forex_ticker.json index 07fc2871..b640a665 100644 --- a/schema_json/polygon/tickers/ticker/forex_ticker.json +++ b/schema_json/polygon/tickers/ticker/forex_ticker.json @@ -2,7 +2,7 @@ "type": "object", "title": "See https://polygon.io/docs/#!/Reference/get_v2_reference_tickers", "extends": { - "existingJavaType": "io.github.mainstringargs.domain.polygon.tickers.ticker.Ticker" + "existingJavaType": "net.jacobpeterson.domain.polygon.tickers.ticker.Ticker" }, "properties": { "attrs": { diff --git a/schema_json/polygon/tickers/ticker/stock_ticker.json b/schema_json/polygon/tickers/ticker/stock_ticker.json index 5e940bbe..7735d79c 100644 --- a/schema_json/polygon/tickers/ticker/stock_ticker.json +++ b/schema_json/polygon/tickers/ticker/stock_ticker.json @@ -2,7 +2,7 @@ "type": "object", "title": "See https://polygon.io/docs/#!/Reference/get_v2_reference_tickers", "extends": { - "existingJavaType": "io.github.mainstringargs.domain.polygon.tickers.ticker.Ticker" + "existingJavaType": "net.jacobpeterson.domain.polygon.tickers.ticker.Ticker" }, "properties": { "type": { diff --git a/schema_json/polygon/tickers/tickers_response.json b/schema_json/polygon/tickers/tickers_response.json index a887d3e4..3dab0aa3 100644 --- a/schema_json/polygon/tickers/tickers_response.json +++ b/schema_json/polygon/tickers/tickers_response.json @@ -19,7 +19,7 @@ "title": "Count" }, "results": { - "existingJavaType": "java.util.ArrayList", + "existingJavaType": "java.util.ArrayList", "title": "Results" } } diff --git a/schema_json/polygon/websocket/aggregate/aggregate_message.json b/schema_json/polygon/websocket/aggregate/aggregate_message.json index cf055d6b..4b2e1aa7 100644 --- a/schema_json/polygon/websocket/aggregate/aggregate_message.json +++ b/schema_json/polygon/websocket/aggregate/aggregate_message.json @@ -1,7 +1,7 @@ { "type": "object", "extends": { - "existingJavaType": "io.github.mainstringargs.domain.polygon.websocket.PolygonStreamMessage" + "existingJavaType": "net.jacobpeterson.domain.polygon.websocket.PolygonStreamMessage" }, "title": "See https://polygon.io/sockets", "properties": { diff --git a/schema_json/polygon/websocket/aggregate/aggregate_per_minute_message.json b/schema_json/polygon/websocket/aggregate/aggregate_per_minute_message.json index 41bdde93..0d4997b6 100644 --- a/schema_json/polygon/websocket/aggregate/aggregate_per_minute_message.json +++ b/schema_json/polygon/websocket/aggregate/aggregate_per_minute_message.json @@ -1,7 +1,7 @@ { "type": "object", "extends": { - "existingJavaType": "io.github.mainstringargs.domain.polygon.websocket.aggregate.AggregateMessage" + "existingJavaType": "net.jacobpeterson.domain.polygon.websocket.aggregate.AggregateMessage" }, "title": "See https://polygon.io/sockets" } diff --git a/schema_json/polygon/websocket/aggregate/aggregate_per_second_message.json b/schema_json/polygon/websocket/aggregate/aggregate_per_second_message.json index 41bdde93..0d4997b6 100644 --- a/schema_json/polygon/websocket/aggregate/aggregate_per_second_message.json +++ b/schema_json/polygon/websocket/aggregate/aggregate_per_second_message.json @@ -1,7 +1,7 @@ { "type": "object", "extends": { - "existingJavaType": "io.github.mainstringargs.domain.polygon.websocket.aggregate.AggregateMessage" + "existingJavaType": "net.jacobpeterson.domain.polygon.websocket.aggregate.AggregateMessage" }, "title": "See https://polygon.io/sockets" } diff --git a/schema_json/polygon/websocket/polygon_stream_message.json b/schema_json/polygon/websocket/polygon_stream_message.json index ee06b470..5f57f96b 100644 --- a/schema_json/polygon/websocket/polygon_stream_message.json +++ b/schema_json/polygon/websocket/polygon_stream_message.json @@ -1,12 +1,12 @@ { "type": "object", "javaInterfaces": [ - "io.github.mainstringargs.abstracts.websocket.message.StreamMessage" + "net.jacobpeterson.abstracts.websocket.message.StreamMessage" ], "title": "See https://polygon.io/sockets", "properties": { "ev": { - "existingJavaType": "io.github.mainstringargs.polygon.websocket.message.PolygonStreamMessageType", + "existingJavaType": "net.jacobpeterson.polygon.websocket.message.PolygonStreamMessageType", "title": "The event type" }, "sym": { diff --git a/schema_json/polygon/websocket/quote/quote_message.json b/schema_json/polygon/websocket/quote/quote_message.json index 487c79cb..e5492076 100644 --- a/schema_json/polygon/websocket/quote/quote_message.json +++ b/schema_json/polygon/websocket/quote/quote_message.json @@ -1,7 +1,7 @@ { "type": "object", "extends": { - "existingJavaType": "io.github.mainstringargs.domain.polygon.websocket.PolygonStreamMessage" + "existingJavaType": "net.jacobpeterson.domain.polygon.websocket.PolygonStreamMessage" }, "title": "See https://polygon.io/sockets", "properties": { diff --git a/schema_json/polygon/websocket/status/status_message.json b/schema_json/polygon/websocket/status/status_message.json index 1311a5df..6d06364c 100644 --- a/schema_json/polygon/websocket/status/status_message.json +++ b/schema_json/polygon/websocket/status/status_message.json @@ -1,7 +1,7 @@ { "type": "object", "extends": { - "existingJavaType": "io.github.mainstringargs.domain.polygon.websocket.PolygonStreamMessage" + "existingJavaType": "net.jacobpeterson.domain.polygon.websocket.PolygonStreamMessage" }, "title": "See https://polygon.io/sockets", "properties": { diff --git a/schema_json/polygon/websocket/trade/trade_message.json b/schema_json/polygon/websocket/trade/trade_message.json index bede5ac3..1e904582 100644 --- a/schema_json/polygon/websocket/trade/trade_message.json +++ b/schema_json/polygon/websocket/trade/trade_message.json @@ -1,7 +1,7 @@ { "type": "object", "extends": { - "existingJavaType": "io.github.mainstringargs.domain.polygon.websocket.PolygonStreamMessage" + "existingJavaType": "net.jacobpeterson.domain.polygon.websocket.PolygonStreamMessage" }, "title": "See https://polygon.io/sockets", "properties": { diff --git a/src/main/java/io/github/mainstringargs/abstracts/enums/APIName.java b/src/main/java/net/jacobpeterson/abstracts/enums/APIName.java similarity index 72% rename from src/main/java/io/github/mainstringargs/abstracts/enums/APIName.java rename to src/main/java/net/jacobpeterson/abstracts/enums/APIName.java index 89f2ac69..bfabe725 100644 --- a/src/main/java/io/github/mainstringargs/abstracts/enums/APIName.java +++ b/src/main/java/net/jacobpeterson/abstracts/enums/APIName.java @@ -1,4 +1,4 @@ -package io.github.mainstringargs.abstracts.enums; +package net.jacobpeterson.abstracts.enums; public interface APIName { diff --git a/src/main/java/io/github/mainstringargs/abstracts/rest/AbstractRequest.java b/src/main/java/net/jacobpeterson/abstracts/rest/AbstractRequest.java similarity index 98% rename from src/main/java/io/github/mainstringargs/abstracts/rest/AbstractRequest.java rename to src/main/java/net/jacobpeterson/abstracts/rest/AbstractRequest.java index 9c8fb844..eb4d87e5 100644 --- a/src/main/java/io/github/mainstringargs/abstracts/rest/AbstractRequest.java +++ b/src/main/java/net/jacobpeterson/abstracts/rest/AbstractRequest.java @@ -1,4 +1,4 @@ -package io.github.mainstringargs.abstracts.rest; +package net.jacobpeterson.abstracts.rest; import com.google.gson.JsonElement; import com.google.gson.stream.JsonReader; @@ -7,7 +7,7 @@ import com.mashape.unirest.http.exceptions.UnirestException; import com.mashape.unirest.request.GetRequest; import com.mashape.unirest.request.HttpRequestWithBody; -import io.github.mainstringargs.util.gson.GsonUtil; +import net.jacobpeterson.util.gson.GsonUtil; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; diff --git a/src/main/java/io/github/mainstringargs/abstracts/rest/AbstractRequestBuilder.java b/src/main/java/net/jacobpeterson/abstracts/rest/AbstractRequestBuilder.java similarity index 98% rename from src/main/java/io/github/mainstringargs/abstracts/rest/AbstractRequestBuilder.java rename to src/main/java/net/jacobpeterson/abstracts/rest/AbstractRequestBuilder.java index 489fa253..81e8f407 100644 --- a/src/main/java/io/github/mainstringargs/abstracts/rest/AbstractRequestBuilder.java +++ b/src/main/java/net/jacobpeterson/abstracts/rest/AbstractRequestBuilder.java @@ -1,4 +1,4 @@ -package io.github.mainstringargs.abstracts.rest; +package net.jacobpeterson.abstracts.rest; import com.google.gson.JsonElement; import com.google.gson.JsonObject; diff --git a/src/main/java/io/github/mainstringargs/abstracts/rest/exception/AbstractAPIRequestException.java b/src/main/java/net/jacobpeterson/abstracts/rest/exception/AbstractAPIRequestException.java similarity index 97% rename from src/main/java/io/github/mainstringargs/abstracts/rest/exception/AbstractAPIRequestException.java rename to src/main/java/net/jacobpeterson/abstracts/rest/exception/AbstractAPIRequestException.java index 4dfda189..11a373d5 100644 --- a/src/main/java/io/github/mainstringargs/abstracts/rest/exception/AbstractAPIRequestException.java +++ b/src/main/java/net/jacobpeterson/abstracts/rest/exception/AbstractAPIRequestException.java @@ -1,9 +1,9 @@ -package io.github.mainstringargs.abstracts.rest.exception; +package net.jacobpeterson.abstracts.rest.exception; import com.google.gson.JsonElement; import com.google.gson.JsonObject; import com.mashape.unirest.http.HttpResponse; -import io.github.mainstringargs.util.gson.GsonUtil; +import net.jacobpeterson.util.gson.GsonUtil; import java.io.InputStream; import java.io.InputStreamReader; diff --git a/src/main/java/io/github/mainstringargs/abstracts/websocket/client/AbstractWebsocketClientEndpoint.java b/src/main/java/net/jacobpeterson/abstracts/websocket/client/AbstractWebsocketClientEndpoint.java similarity index 97% rename from src/main/java/io/github/mainstringargs/abstracts/websocket/client/AbstractWebsocketClientEndpoint.java rename to src/main/java/net/jacobpeterson/abstracts/websocket/client/AbstractWebsocketClientEndpoint.java index 4a099656..81a13fd2 100644 --- a/src/main/java/io/github/mainstringargs/abstracts/websocket/client/AbstractWebsocketClientEndpoint.java +++ b/src/main/java/net/jacobpeterson/abstracts/websocket/client/AbstractWebsocketClientEndpoint.java @@ -1,6 +1,6 @@ -package io.github.mainstringargs.abstracts.websocket.client; +package net.jacobpeterson.abstracts.websocket.client; -import io.github.mainstringargs.util.concurrency.ExecutorTracer; +import net.jacobpeterson.util.concurrency.ExecutorTracer; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; diff --git a/src/main/java/io/github/mainstringargs/abstracts/websocket/client/WebsocketClient.java b/src/main/java/net/jacobpeterson/abstracts/websocket/client/WebsocketClient.java similarity index 80% rename from src/main/java/io/github/mainstringargs/abstracts/websocket/client/WebsocketClient.java rename to src/main/java/net/jacobpeterson/abstracts/websocket/client/WebsocketClient.java index 78d2644c..63b62d51 100644 --- a/src/main/java/io/github/mainstringargs/abstracts/websocket/client/WebsocketClient.java +++ b/src/main/java/net/jacobpeterson/abstracts/websocket/client/WebsocketClient.java @@ -1,8 +1,8 @@ -package io.github.mainstringargs.abstracts.websocket.client; +package net.jacobpeterson.abstracts.websocket.client; -import io.github.mainstringargs.abstracts.websocket.listener.StreamListener; -import io.github.mainstringargs.abstracts.websocket.message.StreamMessage; -import io.github.mainstringargs.abstracts.websocket.message.StreamMessageType; +import net.jacobpeterson.abstracts.websocket.listener.StreamListener; +import net.jacobpeterson.abstracts.websocket.message.StreamMessage; +import net.jacobpeterson.abstracts.websocket.message.StreamMessageType; /** * The type Websocket client. diff --git a/src/main/java/io/github/mainstringargs/abstracts/websocket/listener/StreamListener.java b/src/main/java/net/jacobpeterson/abstracts/websocket/listener/StreamListener.java similarity index 67% rename from src/main/java/io/github/mainstringargs/abstracts/websocket/listener/StreamListener.java rename to src/main/java/net/jacobpeterson/abstracts/websocket/listener/StreamListener.java index 0bd0b6ba..62f728da 100644 --- a/src/main/java/io/github/mainstringargs/abstracts/websocket/listener/StreamListener.java +++ b/src/main/java/net/jacobpeterson/abstracts/websocket/listener/StreamListener.java @@ -1,7 +1,7 @@ -package io.github.mainstringargs.abstracts.websocket.listener; +package net.jacobpeterson.abstracts.websocket.listener; -import io.github.mainstringargs.abstracts.websocket.message.StreamMessage; -import io.github.mainstringargs.abstracts.websocket.message.StreamMessageType; +import net.jacobpeterson.abstracts.websocket.message.StreamMessage; +import net.jacobpeterson.abstracts.websocket.message.StreamMessageType; /** * The type Stream listener. diff --git a/src/main/java/io/github/mainstringargs/abstracts/websocket/message/StreamMessage.java b/src/main/java/net/jacobpeterson/abstracts/websocket/message/StreamMessage.java similarity index 53% rename from src/main/java/io/github/mainstringargs/abstracts/websocket/message/StreamMessage.java rename to src/main/java/net/jacobpeterson/abstracts/websocket/message/StreamMessage.java index c57c48a1..f0721826 100644 --- a/src/main/java/io/github/mainstringargs/abstracts/websocket/message/StreamMessage.java +++ b/src/main/java/net/jacobpeterson/abstracts/websocket/message/StreamMessage.java @@ -1,4 +1,4 @@ -package io.github.mainstringargs.abstracts.websocket.message; +package net.jacobpeterson.abstracts.websocket.message; /** * The type stream message. diff --git a/src/main/java/io/github/mainstringargs/abstracts/websocket/message/StreamMessageType.java b/src/main/java/net/jacobpeterson/abstracts/websocket/message/StreamMessageType.java similarity index 56% rename from src/main/java/io/github/mainstringargs/abstracts/websocket/message/StreamMessageType.java rename to src/main/java/net/jacobpeterson/abstracts/websocket/message/StreamMessageType.java index 55c5cd8e..0f808b72 100644 --- a/src/main/java/io/github/mainstringargs/abstracts/websocket/message/StreamMessageType.java +++ b/src/main/java/net/jacobpeterson/abstracts/websocket/message/StreamMessageType.java @@ -1,4 +1,4 @@ -package io.github.mainstringargs.abstracts.websocket.message; +package net.jacobpeterson.abstracts.websocket.message; /** * The type stream message type. diff --git a/src/main/java/io/github/mainstringargs/alpaca/AlpacaAPI.java b/src/main/java/net/jacobpeterson/alpaca/AlpacaAPI.java similarity index 96% rename from src/main/java/io/github/mainstringargs/alpaca/AlpacaAPI.java rename to src/main/java/net/jacobpeterson/alpaca/AlpacaAPI.java index 1acf9e44..e76d0a1c 100644 --- a/src/main/java/io/github/mainstringargs/alpaca/AlpacaAPI.java +++ b/src/main/java/net/jacobpeterson/alpaca/AlpacaAPI.java @@ -1,4 +1,4 @@ -package io.github.mainstringargs.alpaca; +package net.jacobpeterson.alpaca; import com.google.common.base.Preconditions; import com.google.gson.JsonArray; @@ -7,40 +7,40 @@ import com.google.gson.JsonPrimitive; import com.google.gson.reflect.TypeToken; import com.mashape.unirest.http.HttpResponse; -import io.github.mainstringargs.alpaca.enums.ActivityType; -import io.github.mainstringargs.alpaca.enums.AssetStatus; -import io.github.mainstringargs.alpaca.enums.BarsTimeFrame; -import io.github.mainstringargs.alpaca.enums.Direction; -import io.github.mainstringargs.alpaca.enums.OrderClass; -import io.github.mainstringargs.alpaca.enums.OrderSide; -import io.github.mainstringargs.alpaca.enums.OrderStatus; -import io.github.mainstringargs.alpaca.enums.OrderTimeInForce; -import io.github.mainstringargs.alpaca.enums.OrderType; -import io.github.mainstringargs.alpaca.enums.PortfolioPeriodUnit; -import io.github.mainstringargs.alpaca.enums.PortfolioTimeFrame; -import io.github.mainstringargs.alpaca.properties.AlpacaProperties; -import io.github.mainstringargs.alpaca.rest.AlpacaRequest; -import io.github.mainstringargs.alpaca.rest.AlpacaRequestBuilder; -import io.github.mainstringargs.alpaca.rest.exception.AlpacaAPIRequestException; -import io.github.mainstringargs.alpaca.websocket.client.AlpacaWebsocketClient; -import io.github.mainstringargs.alpaca.websocket.listener.AlpacaStreamListener; -import io.github.mainstringargs.domain.alpaca.account.Account; -import io.github.mainstringargs.domain.alpaca.accountactivities.AccountActivity; -import io.github.mainstringargs.domain.alpaca.accountactivities.NonTradeActivity; -import io.github.mainstringargs.domain.alpaca.accountactivities.TradeActivity; -import io.github.mainstringargs.domain.alpaca.accountconfiguration.AccountConfiguration; -import io.github.mainstringargs.domain.alpaca.asset.Asset; -import io.github.mainstringargs.domain.alpaca.bar.Bar; -import io.github.mainstringargs.domain.alpaca.calendar.Calendar; -import io.github.mainstringargs.domain.alpaca.clock.Clock; -import io.github.mainstringargs.domain.alpaca.order.CancelledOrder; -import io.github.mainstringargs.domain.alpaca.order.Order; -import io.github.mainstringargs.domain.alpaca.portfoliohistory.PortfolioHistory; -import io.github.mainstringargs.domain.alpaca.position.ClosePositionOrder; -import io.github.mainstringargs.domain.alpaca.position.Position; -import io.github.mainstringargs.domain.alpaca.watchlist.Watchlist; -import io.github.mainstringargs.util.gson.GsonUtil; -import io.github.mainstringargs.util.time.TimeUtil; +import net.jacobpeterson.alpaca.enums.ActivityType; +import net.jacobpeterson.alpaca.enums.AssetStatus; +import net.jacobpeterson.alpaca.enums.BarsTimeFrame; +import net.jacobpeterson.alpaca.enums.Direction; +import net.jacobpeterson.alpaca.enums.OrderClass; +import net.jacobpeterson.alpaca.enums.OrderSide; +import net.jacobpeterson.alpaca.enums.OrderStatus; +import net.jacobpeterson.alpaca.enums.OrderTimeInForce; +import net.jacobpeterson.alpaca.enums.OrderType; +import net.jacobpeterson.alpaca.enums.PortfolioPeriodUnit; +import net.jacobpeterson.alpaca.enums.PortfolioTimeFrame; +import net.jacobpeterson.alpaca.properties.AlpacaProperties; +import net.jacobpeterson.alpaca.rest.AlpacaRequest; +import net.jacobpeterson.alpaca.rest.AlpacaRequestBuilder; +import net.jacobpeterson.alpaca.rest.exception.AlpacaAPIRequestException; +import net.jacobpeterson.alpaca.websocket.client.AlpacaWebsocketClient; +import net.jacobpeterson.alpaca.websocket.listener.AlpacaStreamListener; +import net.jacobpeterson.domain.alpaca.account.Account; +import net.jacobpeterson.domain.alpaca.accountactivities.AccountActivity; +import net.jacobpeterson.domain.alpaca.accountactivities.NonTradeActivity; +import net.jacobpeterson.domain.alpaca.accountactivities.TradeActivity; +import net.jacobpeterson.domain.alpaca.accountconfiguration.AccountConfiguration; +import net.jacobpeterson.domain.alpaca.asset.Asset; +import net.jacobpeterson.domain.alpaca.bar.Bar; +import net.jacobpeterson.domain.alpaca.calendar.Calendar; +import net.jacobpeterson.domain.alpaca.clock.Clock; +import net.jacobpeterson.domain.alpaca.order.CancelledOrder; +import net.jacobpeterson.domain.alpaca.order.Order; +import net.jacobpeterson.domain.alpaca.portfoliohistory.PortfolioHistory; +import net.jacobpeterson.domain.alpaca.position.ClosePositionOrder; +import net.jacobpeterson.domain.alpaca.position.Position; +import net.jacobpeterson.domain.alpaca.watchlist.Watchlist; +import net.jacobpeterson.util.gson.GsonUtil; +import net.jacobpeterson.util.time.TimeUtil; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; @@ -187,7 +187,7 @@ public Account getAccount() throws AlpacaAPIRequestException { * Activities */ public ArrayList getAccountActivities(ZonedDateTime date, ZonedDateTime until, ZonedDateTime after, - Direction direction, Integer pageSize, String pageToken, ActivityType... activityTypes) + Direction direction, Integer pageSize, String pageToken, ActivityType... activityTypes) throws AlpacaAPIRequestException { AlpacaRequestBuilder urlBuilder = new AlpacaRequestBuilder(baseAPIURL, AlpacaConstants.VERSION_2_ENDPOINT, AlpacaConstants.ACCOUNT_ENDPOINT, @@ -330,7 +330,7 @@ public AccountConfiguration setAccountConfiguration(AccountConfiguration account * @see Orders */ public ArrayList getOrders(OrderStatus status, Integer limit, ZonedDateTime after, ZonedDateTime until, - Direction direction, Boolean nested) throws AlpacaAPIRequestException { + Direction direction, Boolean nested) throws AlpacaAPIRequestException { AlpacaRequestBuilder urlBuilder = new AlpacaRequestBuilder(baseAPIURL, apiVersion, AlpacaConstants.ORDERS_ENDPOINT); @@ -401,9 +401,9 @@ public ArrayList getOrders(OrderStatus status, Integer limit, ZonedDateTi * @see Orders */ public Order requestNewOrder(String symbol, Integer quantity, OrderSide side, OrderType type, - OrderTimeInForce timeInForce, Double limitPrice, Double stopPrice, Boolean extendedHours, - String clientOrderId, OrderClass orderClass, Double takeProfitLimitPrice, Double stopLossStopPrice, - Double stopLossLimitPrice) + OrderTimeInForce timeInForce, Double limitPrice, Double stopPrice, Boolean extendedHours, + String clientOrderId, OrderClass orderClass, Double takeProfitLimitPrice, Double stopLossStopPrice, + Double stopLossLimitPrice) throws AlpacaAPIRequestException { Preconditions.checkNotNull(symbol); Preconditions.checkNotNull(quantity); @@ -1345,7 +1345,7 @@ public Watchlist removeSymbolFromWatchlist(String watchlistID, String symbol) th * @throws AlpacaAPIRequestException the alpaca api request exception */ public PortfolioHistory getPortfolioHistory(Integer periodLength, PortfolioPeriodUnit periodUnit, - PortfolioTimeFrame timeFrame, LocalDate dateEnd, Boolean extendedHours) throws AlpacaAPIRequestException { + PortfolioTimeFrame timeFrame, LocalDate dateEnd, Boolean extendedHours) throws AlpacaAPIRequestException { AlpacaRequestBuilder urlBuilder = new AlpacaRequestBuilder(baseAPIURL, apiVersion, AlpacaConstants.ACCOUNT_ENDPOINT, AlpacaConstants.PORTFOLIO_ENDPOINT, @@ -1476,7 +1476,7 @@ public Clock getClock() throws AlpacaAPIRequestException { * @see Bars */ public Map> getBars(BarsTimeFrame timeframe, String symbol, Integer limit, - ZonedDateTime start, ZonedDateTime end, ZonedDateTime after, ZonedDateTime until) + ZonedDateTime start, ZonedDateTime end, ZonedDateTime after, ZonedDateTime until) throws AlpacaAPIRequestException { return this.getBars(timeframe, new String[]{symbol}, limit, start, end, after, until); } diff --git a/src/main/java/io/github/mainstringargs/alpaca/AlpacaConstants.java b/src/main/java/net/jacobpeterson/alpaca/AlpacaConstants.java similarity index 99% rename from src/main/java/io/github/mainstringargs/alpaca/AlpacaConstants.java rename to src/main/java/net/jacobpeterson/alpaca/AlpacaConstants.java index c1b406cb..977548f6 100644 --- a/src/main/java/io/github/mainstringargs/alpaca/AlpacaConstants.java +++ b/src/main/java/net/jacobpeterson/alpaca/AlpacaConstants.java @@ -1,4 +1,4 @@ -package io.github.mainstringargs.alpaca; +package net.jacobpeterson.alpaca; /** * The Class AlpacaConstants. diff --git a/src/main/java/io/github/mainstringargs/alpaca/AlpacaExample.java b/src/main/java/net/jacobpeterson/alpaca/AlpacaExample.java similarity index 81% rename from src/main/java/io/github/mainstringargs/alpaca/AlpacaExample.java rename to src/main/java/net/jacobpeterson/alpaca/AlpacaExample.java index e6bcfa52..a3b37acf 100644 --- a/src/main/java/io/github/mainstringargs/alpaca/AlpacaExample.java +++ b/src/main/java/net/jacobpeterson/alpaca/AlpacaExample.java @@ -1,18 +1,18 @@ -package io.github.mainstringargs.alpaca; +package net.jacobpeterson.alpaca; -import io.github.mainstringargs.alpaca.enums.BarsTimeFrame; -import io.github.mainstringargs.alpaca.enums.OrderSide; -import io.github.mainstringargs.alpaca.enums.OrderTimeInForce; -import io.github.mainstringargs.alpaca.rest.exception.AlpacaAPIRequestException; -import io.github.mainstringargs.alpaca.websocket.listener.AlpacaStreamListenerAdapter; -import io.github.mainstringargs.alpaca.websocket.message.AlpacaStreamMessageType; -import io.github.mainstringargs.domain.alpaca.account.Account; -import io.github.mainstringargs.domain.alpaca.bar.Bar; -import io.github.mainstringargs.domain.alpaca.order.Order; -import io.github.mainstringargs.domain.alpaca.watchlist.Watchlist; -import io.github.mainstringargs.domain.alpaca.websocket.AlpacaStreamMessage; -import io.github.mainstringargs.domain.alpaca.websocket.account.AccountUpdateMessage; -import io.github.mainstringargs.domain.alpaca.websocket.trade.TradeUpdateMessage; +import net.jacobpeterson.alpaca.enums.BarsTimeFrame; +import net.jacobpeterson.alpaca.enums.OrderSide; +import net.jacobpeterson.alpaca.enums.OrderTimeInForce; +import net.jacobpeterson.alpaca.rest.exception.AlpacaAPIRequestException; +import net.jacobpeterson.alpaca.websocket.listener.AlpacaStreamListenerAdapter; +import net.jacobpeterson.alpaca.websocket.message.AlpacaStreamMessageType; +import net.jacobpeterson.domain.alpaca.account.Account; +import net.jacobpeterson.domain.alpaca.bar.Bar; +import net.jacobpeterson.domain.alpaca.order.Order; +import net.jacobpeterson.domain.alpaca.watchlist.Watchlist; +import net.jacobpeterson.domain.alpaca.websocket.AlpacaStreamMessage; +import net.jacobpeterson.domain.alpaca.websocket.account.AccountUpdateMessage; +import net.jacobpeterson.domain.alpaca.websocket.trade.TradeUpdateMessage; import java.time.Instant; import java.time.ZoneId; diff --git a/src/main/java/io/github/mainstringargs/alpaca/enums/AccountStatus.java b/src/main/java/net/jacobpeterson/alpaca/enums/AccountStatus.java similarity index 90% rename from src/main/java/io/github/mainstringargs/alpaca/enums/AccountStatus.java rename to src/main/java/net/jacobpeterson/alpaca/enums/AccountStatus.java index 6e1e6a29..da7d2a75 100644 --- a/src/main/java/io/github/mainstringargs/alpaca/enums/AccountStatus.java +++ b/src/main/java/net/jacobpeterson/alpaca/enums/AccountStatus.java @@ -1,6 +1,6 @@ -package io.github.mainstringargs.alpaca.enums; +package net.jacobpeterson.alpaca.enums; -import io.github.mainstringargs.abstracts.enums.APIName; +import net.jacobpeterson.abstracts.enums.APIName; /** * The enum Account status. diff --git a/src/main/java/io/github/mainstringargs/alpaca/enums/ActivityType.java b/src/main/java/net/jacobpeterson/alpaca/enums/ActivityType.java similarity index 94% rename from src/main/java/io/github/mainstringargs/alpaca/enums/ActivityType.java rename to src/main/java/net/jacobpeterson/alpaca/enums/ActivityType.java index ff927694..1cff6ae6 100644 --- a/src/main/java/io/github/mainstringargs/alpaca/enums/ActivityType.java +++ b/src/main/java/net/jacobpeterson/alpaca/enums/ActivityType.java @@ -1,6 +1,6 @@ -package io.github.mainstringargs.alpaca.enums; +package net.jacobpeterson.alpaca.enums; -import io.github.mainstringargs.abstracts.enums.APIName; +import net.jacobpeterson.abstracts.enums.APIName; /** * The enum Activity type. diff --git a/src/main/java/io/github/mainstringargs/alpaca/enums/AssetStatus.java b/src/main/java/net/jacobpeterson/alpaca/enums/AssetStatus.java similarity index 82% rename from src/main/java/io/github/mainstringargs/alpaca/enums/AssetStatus.java rename to src/main/java/net/jacobpeterson/alpaca/enums/AssetStatus.java index 888989e8..b043cc65 100644 --- a/src/main/java/io/github/mainstringargs/alpaca/enums/AssetStatus.java +++ b/src/main/java/net/jacobpeterson/alpaca/enums/AssetStatus.java @@ -1,6 +1,6 @@ -package io.github.mainstringargs.alpaca.enums; +package net.jacobpeterson.alpaca.enums; -import io.github.mainstringargs.abstracts.enums.APIName; +import net.jacobpeterson.abstracts.enums.APIName; /** * The Enum AssetStatus. diff --git a/src/main/java/io/github/mainstringargs/alpaca/enums/BarsTimeFrame.java b/src/main/java/net/jacobpeterson/alpaca/enums/BarsTimeFrame.java similarity index 86% rename from src/main/java/io/github/mainstringargs/alpaca/enums/BarsTimeFrame.java rename to src/main/java/net/jacobpeterson/alpaca/enums/BarsTimeFrame.java index a7295eeb..d73eb3bd 100644 --- a/src/main/java/io/github/mainstringargs/alpaca/enums/BarsTimeFrame.java +++ b/src/main/java/net/jacobpeterson/alpaca/enums/BarsTimeFrame.java @@ -1,6 +1,6 @@ -package io.github.mainstringargs.alpaca.enums; +package net.jacobpeterson.alpaca.enums; -import io.github.mainstringargs.abstracts.enums.APIName; +import net.jacobpeterson.abstracts.enums.APIName; /** * The Enum BarsTimeFrame. diff --git a/src/main/java/io/github/mainstringargs/alpaca/enums/Direction.java b/src/main/java/net/jacobpeterson/alpaca/enums/Direction.java similarity index 81% rename from src/main/java/io/github/mainstringargs/alpaca/enums/Direction.java rename to src/main/java/net/jacobpeterson/alpaca/enums/Direction.java index adca7f5f..74f0fa7b 100644 --- a/src/main/java/io/github/mainstringargs/alpaca/enums/Direction.java +++ b/src/main/java/net/jacobpeterson/alpaca/enums/Direction.java @@ -1,6 +1,6 @@ -package io.github.mainstringargs.alpaca.enums; +package net.jacobpeterson.alpaca.enums; -import io.github.mainstringargs.abstracts.enums.APIName; +import net.jacobpeterson.abstracts.enums.APIName; /** * The Enum Direction. diff --git a/src/main/java/io/github/mainstringargs/alpaca/enums/OrderClass.java b/src/main/java/net/jacobpeterson/alpaca/enums/OrderClass.java similarity index 87% rename from src/main/java/io/github/mainstringargs/alpaca/enums/OrderClass.java rename to src/main/java/net/jacobpeterson/alpaca/enums/OrderClass.java index a0613c77..9db37313 100644 --- a/src/main/java/io/github/mainstringargs/alpaca/enums/OrderClass.java +++ b/src/main/java/net/jacobpeterson/alpaca/enums/OrderClass.java @@ -1,6 +1,6 @@ -package io.github.mainstringargs.alpaca.enums; +package net.jacobpeterson.alpaca.enums; -import io.github.mainstringargs.abstracts.enums.APIName; +import net.jacobpeterson.abstracts.enums.APIName; /** * The enum Account status. diff --git a/src/main/java/io/github/mainstringargs/alpaca/enums/OrderSide.java b/src/main/java/net/jacobpeterson/alpaca/enums/OrderSide.java similarity index 81% rename from src/main/java/io/github/mainstringargs/alpaca/enums/OrderSide.java rename to src/main/java/net/jacobpeterson/alpaca/enums/OrderSide.java index cbc7fc6f..6e01a342 100644 --- a/src/main/java/io/github/mainstringargs/alpaca/enums/OrderSide.java +++ b/src/main/java/net/jacobpeterson/alpaca/enums/OrderSide.java @@ -1,6 +1,6 @@ -package io.github.mainstringargs.alpaca.enums; +package net.jacobpeterson.alpaca.enums; -import io.github.mainstringargs.abstracts.enums.APIName; +import net.jacobpeterson.abstracts.enums.APIName; /** * The Enum OrderSide. diff --git a/src/main/java/io/github/mainstringargs/alpaca/enums/OrderStatus.java b/src/main/java/net/jacobpeterson/alpaca/enums/OrderStatus.java similarity index 82% rename from src/main/java/io/github/mainstringargs/alpaca/enums/OrderStatus.java rename to src/main/java/net/jacobpeterson/alpaca/enums/OrderStatus.java index cefbbe83..4661eb10 100644 --- a/src/main/java/io/github/mainstringargs/alpaca/enums/OrderStatus.java +++ b/src/main/java/net/jacobpeterson/alpaca/enums/OrderStatus.java @@ -1,6 +1,6 @@ -package io.github.mainstringargs.alpaca.enums; +package net.jacobpeterson.alpaca.enums; -import io.github.mainstringargs.abstracts.enums.APIName; +import net.jacobpeterson.abstracts.enums.APIName; /** * The Enum OrderStatus. diff --git a/src/main/java/io/github/mainstringargs/alpaca/enums/OrderTimeInForce.java b/src/main/java/net/jacobpeterson/alpaca/enums/OrderTimeInForce.java similarity index 86% rename from src/main/java/io/github/mainstringargs/alpaca/enums/OrderTimeInForce.java rename to src/main/java/net/jacobpeterson/alpaca/enums/OrderTimeInForce.java index 8fbf48fe..a476eaca 100644 --- a/src/main/java/io/github/mainstringargs/alpaca/enums/OrderTimeInForce.java +++ b/src/main/java/net/jacobpeterson/alpaca/enums/OrderTimeInForce.java @@ -1,6 +1,6 @@ -package io.github.mainstringargs.alpaca.enums; +package net.jacobpeterson.alpaca.enums; -import io.github.mainstringargs.abstracts.enums.APIName; +import net.jacobpeterson.abstracts.enums.APIName; /** * The Enum OrderTimeInForce. diff --git a/src/main/java/io/github/mainstringargs/alpaca/enums/OrderType.java b/src/main/java/net/jacobpeterson/alpaca/enums/OrderType.java similarity index 84% rename from src/main/java/io/github/mainstringargs/alpaca/enums/OrderType.java rename to src/main/java/net/jacobpeterson/alpaca/enums/OrderType.java index 0f2cc3ba..fc198fad 100644 --- a/src/main/java/io/github/mainstringargs/alpaca/enums/OrderType.java +++ b/src/main/java/net/jacobpeterson/alpaca/enums/OrderType.java @@ -1,6 +1,6 @@ -package io.github.mainstringargs.alpaca.enums; +package net.jacobpeterson.alpaca.enums; -import io.github.mainstringargs.abstracts.enums.APIName; +import net.jacobpeterson.abstracts.enums.APIName; /** * The Enum OrderType. diff --git a/src/main/java/io/github/mainstringargs/alpaca/enums/PortfolioPeriodUnit.java b/src/main/java/net/jacobpeterson/alpaca/enums/PortfolioPeriodUnit.java similarity index 85% rename from src/main/java/io/github/mainstringargs/alpaca/enums/PortfolioPeriodUnit.java rename to src/main/java/net/jacobpeterson/alpaca/enums/PortfolioPeriodUnit.java index 7ed92f09..35e640ae 100644 --- a/src/main/java/io/github/mainstringargs/alpaca/enums/PortfolioPeriodUnit.java +++ b/src/main/java/net/jacobpeterson/alpaca/enums/PortfolioPeriodUnit.java @@ -1,6 +1,6 @@ -package io.github.mainstringargs.alpaca.enums; +package net.jacobpeterson.alpaca.enums; -import io.github.mainstringargs.abstracts.enums.APIName; +import net.jacobpeterson.abstracts.enums.APIName; /** * The enum Portfolio period unit. diff --git a/src/main/java/io/github/mainstringargs/alpaca/enums/PortfolioTimeFrame.java b/src/main/java/net/jacobpeterson/alpaca/enums/PortfolioTimeFrame.java similarity index 80% rename from src/main/java/io/github/mainstringargs/alpaca/enums/PortfolioTimeFrame.java rename to src/main/java/net/jacobpeterson/alpaca/enums/PortfolioTimeFrame.java index 5b408f96..236d819f 100644 --- a/src/main/java/io/github/mainstringargs/alpaca/enums/PortfolioTimeFrame.java +++ b/src/main/java/net/jacobpeterson/alpaca/enums/PortfolioTimeFrame.java @@ -1,8 +1,8 @@ -package io.github.mainstringargs.alpaca.enums; +package net.jacobpeterson.alpaca.enums; import com.google.gson.annotations.SerializedName; -import io.github.mainstringargs.abstracts.enums.APIName; -import io.github.mainstringargs.util.gson.GsonUtil; +import net.jacobpeterson.abstracts.enums.APIName; +import net.jacobpeterson.util.gson.GsonUtil; public enum PortfolioTimeFrame implements APIName { diff --git a/src/main/java/io/github/mainstringargs/alpaca/properties/AlpacaProperties.java b/src/main/java/net/jacobpeterson/alpaca/properties/AlpacaProperties.java similarity index 95% rename from src/main/java/io/github/mainstringargs/alpaca/properties/AlpacaProperties.java rename to src/main/java/net/jacobpeterson/alpaca/properties/AlpacaProperties.java index c9ae9dbd..ce95c2d3 100644 --- a/src/main/java/io/github/mainstringargs/alpaca/properties/AlpacaProperties.java +++ b/src/main/java/net/jacobpeterson/alpaca/properties/AlpacaProperties.java @@ -1,8 +1,8 @@ -package io.github.mainstringargs.alpaca.properties; +package net.jacobpeterson.alpaca.properties; import java.util.StringJoiner; -import static io.github.mainstringargs.util.properties.PropertyUtil.getProperty; +import static net.jacobpeterson.util.properties.PropertyUtil.getProperty; /** * The Class AlpacaProperties. diff --git a/src/main/java/io/github/mainstringargs/alpaca/rest/AlpacaRequest.java b/src/main/java/net/jacobpeterson/alpaca/rest/AlpacaRequest.java similarity index 81% rename from src/main/java/io/github/mainstringargs/alpaca/rest/AlpacaRequest.java rename to src/main/java/net/jacobpeterson/alpaca/rest/AlpacaRequest.java index ca026cb6..883bb00d 100644 --- a/src/main/java/io/github/mainstringargs/alpaca/rest/AlpacaRequest.java +++ b/src/main/java/net/jacobpeterson/alpaca/rest/AlpacaRequest.java @@ -1,7 +1,7 @@ -package io.github.mainstringargs.alpaca.rest; +package net.jacobpeterson.alpaca.rest; -import io.github.mainstringargs.abstracts.rest.AbstractRequest; -import io.github.mainstringargs.alpaca.properties.AlpacaProperties; +import net.jacobpeterson.abstracts.rest.AbstractRequest; +import net.jacobpeterson.alpaca.properties.AlpacaProperties; /** * The Class AlpacaRequest. diff --git a/src/main/java/io/github/mainstringargs/alpaca/rest/AlpacaRequestBuilder.java b/src/main/java/net/jacobpeterson/alpaca/rest/AlpacaRequestBuilder.java similarity index 84% rename from src/main/java/io/github/mainstringargs/alpaca/rest/AlpacaRequestBuilder.java rename to src/main/java/net/jacobpeterson/alpaca/rest/AlpacaRequestBuilder.java index 927955d0..d6a0eabe 100644 --- a/src/main/java/io/github/mainstringargs/alpaca/rest/AlpacaRequestBuilder.java +++ b/src/main/java/net/jacobpeterson/alpaca/rest/AlpacaRequestBuilder.java @@ -1,6 +1,6 @@ -package io.github.mainstringargs.alpaca.rest; +package net.jacobpeterson.alpaca.rest; -import io.github.mainstringargs.abstracts.rest.AbstractRequestBuilder; +import net.jacobpeterson.abstracts.rest.AbstractRequestBuilder; import java.util.Arrays; diff --git a/src/main/java/io/github/mainstringargs/alpaca/rest/exception/AlpacaAPIRequestException.java b/src/main/java/net/jacobpeterson/alpaca/rest/exception/AlpacaAPIRequestException.java similarity index 79% rename from src/main/java/io/github/mainstringargs/alpaca/rest/exception/AlpacaAPIRequestException.java rename to src/main/java/net/jacobpeterson/alpaca/rest/exception/AlpacaAPIRequestException.java index 2dfc600a..42414399 100644 --- a/src/main/java/io/github/mainstringargs/alpaca/rest/exception/AlpacaAPIRequestException.java +++ b/src/main/java/net/jacobpeterson/alpaca/rest/exception/AlpacaAPIRequestException.java @@ -1,7 +1,7 @@ -package io.github.mainstringargs.alpaca.rest.exception; +package net.jacobpeterson.alpaca.rest.exception; import com.mashape.unirest.http.HttpResponse; -import io.github.mainstringargs.abstracts.rest.exception.AbstractAPIRequestException; +import net.jacobpeterson.abstracts.rest.exception.AbstractAPIRequestException; import java.io.InputStream; diff --git a/src/main/java/io/github/mainstringargs/alpaca/websocket/client/AlpacaWebsocketClient.java b/src/main/java/net/jacobpeterson/alpaca/websocket/client/AlpacaWebsocketClient.java similarity index 90% rename from src/main/java/io/github/mainstringargs/alpaca/websocket/client/AlpacaWebsocketClient.java rename to src/main/java/net/jacobpeterson/alpaca/websocket/client/AlpacaWebsocketClient.java index 6e79c789..a7fb00ba 100644 --- a/src/main/java/io/github/mainstringargs/alpaca/websocket/client/AlpacaWebsocketClient.java +++ b/src/main/java/net/jacobpeterson/alpaca/websocket/client/AlpacaWebsocketClient.java @@ -1,4 +1,4 @@ -package io.github.mainstringargs.alpaca.websocket.client; +package net.jacobpeterson.alpaca.websocket.client; import com.google.common.base.Preconditions; import com.google.gson.JsonArray; @@ -6,18 +6,18 @@ import com.google.gson.JsonObject; import com.google.gson.JsonPrimitive; import com.google.gson.JsonSyntaxException; -import io.github.mainstringargs.abstracts.websocket.client.WebsocketClient; -import io.github.mainstringargs.abstracts.websocket.listener.StreamListener; -import io.github.mainstringargs.abstracts.websocket.message.StreamMessage; -import io.github.mainstringargs.abstracts.websocket.message.StreamMessageType; -import io.github.mainstringargs.alpaca.websocket.listener.AlpacaStreamListener; -import io.github.mainstringargs.alpaca.websocket.message.AlpacaStreamMessageType; -import io.github.mainstringargs.domain.alpaca.websocket.AlpacaStreamMessage; -import io.github.mainstringargs.domain.alpaca.websocket.account.AccountUpdateMessage; -import io.github.mainstringargs.domain.alpaca.websocket.authorization.AuthorizationMessage; -import io.github.mainstringargs.domain.alpaca.websocket.listening.ListeningMessage; -import io.github.mainstringargs.domain.alpaca.websocket.trade.TradeUpdateMessage; -import io.github.mainstringargs.util.gson.GsonUtil; +import net.jacobpeterson.abstracts.websocket.client.WebsocketClient; +import net.jacobpeterson.abstracts.websocket.listener.StreamListener; +import net.jacobpeterson.abstracts.websocket.message.StreamMessage; +import net.jacobpeterson.abstracts.websocket.message.StreamMessageType; +import net.jacobpeterson.alpaca.websocket.listener.AlpacaStreamListener; +import net.jacobpeterson.alpaca.websocket.message.AlpacaStreamMessageType; +import net.jacobpeterson.domain.alpaca.websocket.AlpacaStreamMessage; +import net.jacobpeterson.domain.alpaca.websocket.account.AccountUpdateMessage; +import net.jacobpeterson.domain.alpaca.websocket.authorization.AuthorizationMessage; +import net.jacobpeterson.domain.alpaca.websocket.listening.ListeningMessage; +import net.jacobpeterson.domain.alpaca.websocket.trade.TradeUpdateMessage; +import net.jacobpeterson.util.gson.GsonUtil; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; diff --git a/src/main/java/io/github/mainstringargs/alpaca/websocket/client/AlpacaWebsocketClientEndpoint.java b/src/main/java/net/jacobpeterson/alpaca/websocket/client/AlpacaWebsocketClientEndpoint.java similarity index 84% rename from src/main/java/io/github/mainstringargs/alpaca/websocket/client/AlpacaWebsocketClientEndpoint.java rename to src/main/java/net/jacobpeterson/alpaca/websocket/client/AlpacaWebsocketClientEndpoint.java index 371e9aa8..93b81137 100644 --- a/src/main/java/io/github/mainstringargs/alpaca/websocket/client/AlpacaWebsocketClientEndpoint.java +++ b/src/main/java/net/jacobpeterson/alpaca/websocket/client/AlpacaWebsocketClientEndpoint.java @@ -1,7 +1,7 @@ -package io.github.mainstringargs.alpaca.websocket.client; +package net.jacobpeterson.alpaca.websocket.client; -import io.github.mainstringargs.abstracts.websocket.client.AbstractWebsocketClientEndpoint; -import io.github.mainstringargs.abstracts.websocket.client.WebsocketClient; +import net.jacobpeterson.abstracts.websocket.client.AbstractWebsocketClientEndpoint; +import net.jacobpeterson.abstracts.websocket.client.WebsocketClient; import javax.websocket.ClientEndpoint; import javax.websocket.CloseReason; diff --git a/src/main/java/io/github/mainstringargs/alpaca/websocket/listener/AlpacaStreamListener.java b/src/main/java/net/jacobpeterson/alpaca/websocket/listener/AlpacaStreamListener.java similarity index 61% rename from src/main/java/io/github/mainstringargs/alpaca/websocket/listener/AlpacaStreamListener.java rename to src/main/java/net/jacobpeterson/alpaca/websocket/listener/AlpacaStreamListener.java index 1cc9cd1c..dfcece31 100644 --- a/src/main/java/io/github/mainstringargs/alpaca/websocket/listener/AlpacaStreamListener.java +++ b/src/main/java/net/jacobpeterson/alpaca/websocket/listener/AlpacaStreamListener.java @@ -1,8 +1,8 @@ -package io.github.mainstringargs.alpaca.websocket.listener; +package net.jacobpeterson.alpaca.websocket.listener; -import io.github.mainstringargs.abstracts.websocket.listener.StreamListener; -import io.github.mainstringargs.alpaca.websocket.message.AlpacaStreamMessageType; -import io.github.mainstringargs.domain.alpaca.websocket.AlpacaStreamMessage; +import net.jacobpeterson.abstracts.websocket.listener.StreamListener; +import net.jacobpeterson.alpaca.websocket.message.AlpacaStreamMessageType; +import net.jacobpeterson.domain.alpaca.websocket.AlpacaStreamMessage; import java.util.Set; diff --git a/src/main/java/io/github/mainstringargs/alpaca/websocket/listener/AlpacaStreamListenerAdapter.java b/src/main/java/net/jacobpeterson/alpaca/websocket/listener/AlpacaStreamListenerAdapter.java similarity index 81% rename from src/main/java/io/github/mainstringargs/alpaca/websocket/listener/AlpacaStreamListenerAdapter.java rename to src/main/java/net/jacobpeterson/alpaca/websocket/listener/AlpacaStreamListenerAdapter.java index 3a256907..44c0b2ee 100644 --- a/src/main/java/io/github/mainstringargs/alpaca/websocket/listener/AlpacaStreamListenerAdapter.java +++ b/src/main/java/net/jacobpeterson/alpaca/websocket/listener/AlpacaStreamListenerAdapter.java @@ -1,7 +1,7 @@ -package io.github.mainstringargs.alpaca.websocket.listener; +package net.jacobpeterson.alpaca.websocket.listener; -import io.github.mainstringargs.alpaca.websocket.message.AlpacaStreamMessageType; -import io.github.mainstringargs.domain.alpaca.websocket.AlpacaStreamMessage; +import net.jacobpeterson.alpaca.websocket.message.AlpacaStreamMessageType; +import net.jacobpeterson.domain.alpaca.websocket.AlpacaStreamMessage; import java.util.Arrays; import java.util.HashSet; diff --git a/src/main/java/io/github/mainstringargs/alpaca/websocket/message/AlpacaStreamMessageType.java b/src/main/java/net/jacobpeterson/alpaca/websocket/message/AlpacaStreamMessageType.java similarity index 83% rename from src/main/java/io/github/mainstringargs/alpaca/websocket/message/AlpacaStreamMessageType.java rename to src/main/java/net/jacobpeterson/alpaca/websocket/message/AlpacaStreamMessageType.java index 47b0f8c7..6e4c7b40 100644 --- a/src/main/java/io/github/mainstringargs/alpaca/websocket/message/AlpacaStreamMessageType.java +++ b/src/main/java/net/jacobpeterson/alpaca/websocket/message/AlpacaStreamMessageType.java @@ -1,9 +1,9 @@ -package io.github.mainstringargs.alpaca.websocket.message; +package net.jacobpeterson.alpaca.websocket.message; import com.google.gson.annotations.SerializedName; -import io.github.mainstringargs.abstracts.enums.APIName; -import io.github.mainstringargs.abstracts.websocket.message.StreamMessageType; -import io.github.mainstringargs.util.gson.GsonUtil; +import net.jacobpeterson.abstracts.enums.APIName; +import net.jacobpeterson.abstracts.websocket.message.StreamMessageType; +import net.jacobpeterson.util.gson.GsonUtil; /** * The enum Alpaca stream message type. diff --git a/src/main/java/io/github/mainstringargs/polygon/PolygonAPI.java b/src/main/java/net/jacobpeterson/polygon/PolygonAPI.java similarity index 91% rename from src/main/java/io/github/mainstringargs/polygon/PolygonAPI.java rename to src/main/java/net/jacobpeterson/polygon/PolygonAPI.java index 30b1a9ab..581af412 100644 --- a/src/main/java/io/github/mainstringargs/polygon/PolygonAPI.java +++ b/src/main/java/net/jacobpeterson/polygon/PolygonAPI.java @@ -1,48 +1,48 @@ -package io.github.mainstringargs.polygon; +package net.jacobpeterson.polygon; import com.google.common.base.Preconditions; import com.google.gson.reflect.TypeToken; import com.mashape.unirest.http.HttpResponse; -import io.github.mainstringargs.domain.polygon.aggregates.AggregatesResponse; -import io.github.mainstringargs.domain.polygon.conditionsmapping.ConditionsMapping; -import io.github.mainstringargs.domain.polygon.dailyopenclose.DailyOpenCloseResponse; -import io.github.mainstringargs.domain.polygon.exchanges.Exchange; -import io.github.mainstringargs.domain.polygon.groupeddaily.GroupedDailyResponse; -import io.github.mainstringargs.domain.polygon.historicquotes.HistoricQuotesResponse; -import io.github.mainstringargs.domain.polygon.historictrades.HistoricTradesResponse; -import io.github.mainstringargs.domain.polygon.lastquote.LastQuoteResponse; -import io.github.mainstringargs.domain.polygon.lasttrade.LastTradeResponse; -import io.github.mainstringargs.domain.polygon.locales.LocalesResponse; -import io.github.mainstringargs.domain.polygon.marketholidays.MarketHoliday; -import io.github.mainstringargs.domain.polygon.markets.MarketsResponse; -import io.github.mainstringargs.domain.polygon.marketstatus.MarketStatus; -import io.github.mainstringargs.domain.polygon.previousclose.PreviousCloseResponse; -import io.github.mainstringargs.domain.polygon.snapshot.SnapshotAllTickersResponse; -import io.github.mainstringargs.domain.polygon.snapshot.SnapshotGainersLosersResponse; -import io.github.mainstringargs.domain.polygon.snapshot.SnapshotSingleTickerResponse; -import io.github.mainstringargs.domain.polygon.snapshot.SnapshotTickerBook; -import io.github.mainstringargs.domain.polygon.stockdividends.StockDividendsResponse; -import io.github.mainstringargs.domain.polygon.stockfinancials.StockFinancialsResponse; -import io.github.mainstringargs.domain.polygon.stocksplits.StockSplitsResponse; -import io.github.mainstringargs.domain.polygon.tickerdetails.TickerDetails; -import io.github.mainstringargs.domain.polygon.tickernews.TickerNews; -import io.github.mainstringargs.domain.polygon.tickers.TickersResponse; -import io.github.mainstringargs.domain.polygon.tickertypes.TickerTypes; -import io.github.mainstringargs.polygon.enums.ConditionMappingsType; -import io.github.mainstringargs.polygon.enums.FinancialReportType; -import io.github.mainstringargs.polygon.enums.FinancialSort; -import io.github.mainstringargs.polygon.enums.GainersLosersDirection; -import io.github.mainstringargs.polygon.enums.Market; -import io.github.mainstringargs.polygon.enums.StockType; -import io.github.mainstringargs.polygon.enums.TickerSort; -import io.github.mainstringargs.polygon.enums.Timespan; -import io.github.mainstringargs.polygon.properties.PolygonProperties; -import io.github.mainstringargs.polygon.rest.PolygonRequest; -import io.github.mainstringargs.polygon.rest.PolygonRequestBuilder; -import io.github.mainstringargs.polygon.rest.exception.PolygonAPIRequestException; -import io.github.mainstringargs.polygon.websocket.client.PolygonWebsocketClient; -import io.github.mainstringargs.polygon.websocket.listener.PolygonStreamListener; -import io.github.mainstringargs.util.time.TimeUtil; +import net.jacobpeterson.domain.polygon.aggregates.AggregatesResponse; +import net.jacobpeterson.domain.polygon.conditionsmapping.ConditionsMapping; +import net.jacobpeterson.domain.polygon.dailyopenclose.DailyOpenCloseResponse; +import net.jacobpeterson.domain.polygon.exchanges.Exchange; +import net.jacobpeterson.domain.polygon.groupeddaily.GroupedDailyResponse; +import net.jacobpeterson.domain.polygon.historicquotes.HistoricQuotesResponse; +import net.jacobpeterson.domain.polygon.historictrades.HistoricTradesResponse; +import net.jacobpeterson.domain.polygon.lastquote.LastQuoteResponse; +import net.jacobpeterson.domain.polygon.lasttrade.LastTradeResponse; +import net.jacobpeterson.domain.polygon.locales.LocalesResponse; +import net.jacobpeterson.domain.polygon.marketholidays.MarketHoliday; +import net.jacobpeterson.domain.polygon.markets.MarketsResponse; +import net.jacobpeterson.domain.polygon.marketstatus.MarketStatus; +import net.jacobpeterson.domain.polygon.previousclose.PreviousCloseResponse; +import net.jacobpeterson.domain.polygon.snapshot.SnapshotAllTickersResponse; +import net.jacobpeterson.domain.polygon.snapshot.SnapshotGainersLosersResponse; +import net.jacobpeterson.domain.polygon.snapshot.SnapshotSingleTickerResponse; +import net.jacobpeterson.domain.polygon.snapshot.SnapshotTickerBook; +import net.jacobpeterson.domain.polygon.stockdividends.StockDividendsResponse; +import net.jacobpeterson.domain.polygon.stockfinancials.StockFinancialsResponse; +import net.jacobpeterson.domain.polygon.stocksplits.StockSplitsResponse; +import net.jacobpeterson.domain.polygon.tickerdetails.TickerDetails; +import net.jacobpeterson.domain.polygon.tickernews.TickerNews; +import net.jacobpeterson.domain.polygon.tickers.TickersResponse; +import net.jacobpeterson.domain.polygon.tickertypes.TickerTypes; +import net.jacobpeterson.polygon.enums.ConditionMappingsType; +import net.jacobpeterson.polygon.enums.FinancialReportType; +import net.jacobpeterson.polygon.enums.FinancialSort; +import net.jacobpeterson.polygon.enums.GainersLosersDirection; +import net.jacobpeterson.polygon.enums.Market; +import net.jacobpeterson.polygon.enums.StockType; +import net.jacobpeterson.polygon.enums.TickerSort; +import net.jacobpeterson.polygon.enums.Timespan; +import net.jacobpeterson.polygon.properties.PolygonProperties; +import net.jacobpeterson.polygon.rest.PolygonRequest; +import net.jacobpeterson.polygon.rest.PolygonRequestBuilder; +import net.jacobpeterson.polygon.rest.exception.PolygonAPIRequestException; +import net.jacobpeterson.polygon.websocket.client.PolygonWebsocketClient; +import net.jacobpeterson.polygon.websocket.listener.PolygonStreamListener; +import net.jacobpeterson.util.time.TimeUtil; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; @@ -129,7 +129,7 @@ public PolygonAPI(String baseAPIURL, String websocketURL, String keyID) { * @see Tickers */ public TickersResponse getTickers(TickerSort tickerSort, StockType stockType, Market market, String locale, - String search, Integer perpage, Integer page, Boolean active) throws PolygonAPIRequestException { + String search, Integer perpage, Integer page, Boolean active) throws PolygonAPIRequestException { PolygonRequestBuilder builder = new PolygonRequestBuilder(baseAPIURL, PolygonConstants.VERSION_2_ENDPOINT, PolygonConstants.REFERENCE_ENDPOINT, PolygonConstants.TICKERS_ENDPOINT); @@ -377,7 +377,7 @@ public StockDividendsResponse getStockDividends(String symbol) throws PolygonAPI * @see Stock Financials */ public StockFinancialsResponse getStockFinancials(String symbol, Integer limit, - FinancialReportType financialReportType, FinancialSort financialSort) throws PolygonAPIRequestException { + FinancialReportType financialReportType, FinancialSort financialSort) throws PolygonAPIRequestException { Preconditions.checkNotNull(symbol); PolygonRequestBuilder builder = new PolygonRequestBuilder(baseAPIURL, PolygonConstants.VERSION_2_ENDPOINT, @@ -879,7 +879,7 @@ public PreviousCloseResponse getPreviousClose(String ticker, Boolean unadjusted) * Aggregates */ public AggregatesResponse getAggregates(String ticker, Integer multiplier, Timespan timeSpan, LocalDate fromDate, - LocalDate toDate, Boolean unadjusted) throws PolygonAPIRequestException { + LocalDate toDate, Boolean unadjusted) throws PolygonAPIRequestException { Preconditions.checkNotNull(ticker); Preconditions.checkNotNull(timeSpan); Preconditions.checkNotNull(fromDate); diff --git a/src/main/java/io/github/mainstringargs/polygon/PolygonConstants.java b/src/main/java/net/jacobpeterson/polygon/PolygonConstants.java similarity index 99% rename from src/main/java/io/github/mainstringargs/polygon/PolygonConstants.java rename to src/main/java/net/jacobpeterson/polygon/PolygonConstants.java index 949c8d4e..ac772fb3 100644 --- a/src/main/java/io/github/mainstringargs/polygon/PolygonConstants.java +++ b/src/main/java/net/jacobpeterson/polygon/PolygonConstants.java @@ -1,4 +1,4 @@ -package io.github.mainstringargs.polygon; +package net.jacobpeterson.polygon; /** * The type Polygon constants. diff --git a/src/main/java/io/github/mainstringargs/polygon/PolygonExample.java b/src/main/java/net/jacobpeterson/polygon/PolygonExample.java similarity index 77% rename from src/main/java/io/github/mainstringargs/polygon/PolygonExample.java rename to src/main/java/net/jacobpeterson/polygon/PolygonExample.java index fdc4afdc..8b3f1543 100644 --- a/src/main/java/io/github/mainstringargs/polygon/PolygonExample.java +++ b/src/main/java/net/jacobpeterson/polygon/PolygonExample.java @@ -1,14 +1,14 @@ -package io.github.mainstringargs.polygon; +package net.jacobpeterson.polygon; -import io.github.mainstringargs.domain.polygon.aggregates.Aggregate; -import io.github.mainstringargs.domain.polygon.aggregates.AggregatesResponse; -import io.github.mainstringargs.domain.polygon.stocksplits.StockSplit; -import io.github.mainstringargs.domain.polygon.stocksplits.StockSplitsResponse; -import io.github.mainstringargs.domain.polygon.websocket.PolygonStreamMessage; -import io.github.mainstringargs.polygon.enums.Timespan; -import io.github.mainstringargs.polygon.rest.exception.PolygonAPIRequestException; -import io.github.mainstringargs.polygon.websocket.listener.PolygonStreamListenerAdapter; -import io.github.mainstringargs.polygon.websocket.message.PolygonStreamMessageType; +import net.jacobpeterson.domain.polygon.aggregates.Aggregate; +import net.jacobpeterson.domain.polygon.aggregates.AggregatesResponse; +import net.jacobpeterson.domain.polygon.stocksplits.StockSplit; +import net.jacobpeterson.domain.polygon.stocksplits.StockSplitsResponse; +import net.jacobpeterson.domain.polygon.websocket.PolygonStreamMessage; +import net.jacobpeterson.polygon.enums.Timespan; +import net.jacobpeterson.polygon.rest.exception.PolygonAPIRequestException; +import net.jacobpeterson.polygon.websocket.listener.PolygonStreamListenerAdapter; +import net.jacobpeterson.polygon.websocket.message.PolygonStreamMessageType; import java.time.LocalDate; diff --git a/src/main/java/io/github/mainstringargs/polygon/enums/ConditionMappingsType.java b/src/main/java/net/jacobpeterson/polygon/enums/ConditionMappingsType.java similarity index 83% rename from src/main/java/io/github/mainstringargs/polygon/enums/ConditionMappingsType.java rename to src/main/java/net/jacobpeterson/polygon/enums/ConditionMappingsType.java index 32241deb..aff8606a 100644 --- a/src/main/java/io/github/mainstringargs/polygon/enums/ConditionMappingsType.java +++ b/src/main/java/net/jacobpeterson/polygon/enums/ConditionMappingsType.java @@ -1,6 +1,6 @@ -package io.github.mainstringargs.polygon.enums; +package net.jacobpeterson.polygon.enums; -import io.github.mainstringargs.abstracts.enums.APIName; +import net.jacobpeterson.abstracts.enums.APIName; /** * The enum Condition mappings type. diff --git a/src/main/java/io/github/mainstringargs/polygon/enums/FinancialReportType.java b/src/main/java/net/jacobpeterson/polygon/enums/FinancialReportType.java similarity index 87% rename from src/main/java/io/github/mainstringargs/polygon/enums/FinancialReportType.java rename to src/main/java/net/jacobpeterson/polygon/enums/FinancialReportType.java index f27d386f..03a145ed 100644 --- a/src/main/java/io/github/mainstringargs/polygon/enums/FinancialReportType.java +++ b/src/main/java/net/jacobpeterson/polygon/enums/FinancialReportType.java @@ -1,6 +1,6 @@ -package io.github.mainstringargs.polygon.enums; +package net.jacobpeterson.polygon.enums; -import io.github.mainstringargs.abstracts.enums.APIName; +import net.jacobpeterson.abstracts.enums.APIName; /** * The enum Financial report type. diff --git a/src/main/java/io/github/mainstringargs/polygon/enums/FinancialSort.java b/src/main/java/net/jacobpeterson/polygon/enums/FinancialSort.java similarity index 88% rename from src/main/java/io/github/mainstringargs/polygon/enums/FinancialSort.java rename to src/main/java/net/jacobpeterson/polygon/enums/FinancialSort.java index dbb79f0a..0e933f85 100644 --- a/src/main/java/io/github/mainstringargs/polygon/enums/FinancialSort.java +++ b/src/main/java/net/jacobpeterson/polygon/enums/FinancialSort.java @@ -1,6 +1,6 @@ -package io.github.mainstringargs.polygon.enums; +package net.jacobpeterson.polygon.enums; -import io.github.mainstringargs.abstracts.enums.APIName; +import net.jacobpeterson.abstracts.enums.APIName; /** * The enum Financial sort. diff --git a/src/main/java/io/github/mainstringargs/polygon/enums/GainersLosersDirection.java b/src/main/java/net/jacobpeterson/polygon/enums/GainersLosersDirection.java similarity index 80% rename from src/main/java/io/github/mainstringargs/polygon/enums/GainersLosersDirection.java rename to src/main/java/net/jacobpeterson/polygon/enums/GainersLosersDirection.java index 50a71dff..16ce1131 100644 --- a/src/main/java/io/github/mainstringargs/polygon/enums/GainersLosersDirection.java +++ b/src/main/java/net/jacobpeterson/polygon/enums/GainersLosersDirection.java @@ -1,6 +1,6 @@ -package io.github.mainstringargs.polygon.enums; +package net.jacobpeterson.polygon.enums; -import io.github.mainstringargs.abstracts.enums.APIName; +import net.jacobpeterson.abstracts.enums.APIName; public enum GainersLosersDirection implements APIName { diff --git a/src/main/java/io/github/mainstringargs/polygon/enums/Market.java b/src/main/java/net/jacobpeterson/polygon/enums/Market.java similarity index 86% rename from src/main/java/io/github/mainstringargs/polygon/enums/Market.java rename to src/main/java/net/jacobpeterson/polygon/enums/Market.java index a05bcbe8..96324552 100644 --- a/src/main/java/io/github/mainstringargs/polygon/enums/Market.java +++ b/src/main/java/net/jacobpeterson/polygon/enums/Market.java @@ -1,6 +1,6 @@ -package io.github.mainstringargs.polygon.enums; +package net.jacobpeterson.polygon.enums; -import io.github.mainstringargs.abstracts.enums.APIName; +import net.jacobpeterson.abstracts.enums.APIName; /** * The Enum SymbolsMarket. diff --git a/src/main/java/io/github/mainstringargs/polygon/enums/StockType.java b/src/main/java/net/jacobpeterson/polygon/enums/StockType.java similarity index 81% rename from src/main/java/io/github/mainstringargs/polygon/enums/StockType.java rename to src/main/java/net/jacobpeterson/polygon/enums/StockType.java index 5a417579..d12e3d16 100644 --- a/src/main/java/io/github/mainstringargs/polygon/enums/StockType.java +++ b/src/main/java/net/jacobpeterson/polygon/enums/StockType.java @@ -1,6 +1,6 @@ -package io.github.mainstringargs.polygon.enums; +package net.jacobpeterson.polygon.enums; -import io.github.mainstringargs.abstracts.enums.APIName; +import net.jacobpeterson.abstracts.enums.APIName; /** * The Enum StockType. diff --git a/src/main/java/io/github/mainstringargs/polygon/enums/TickerSort.java b/src/main/java/net/jacobpeterson/polygon/enums/TickerSort.java similarity index 85% rename from src/main/java/io/github/mainstringargs/polygon/enums/TickerSort.java rename to src/main/java/net/jacobpeterson/polygon/enums/TickerSort.java index a6b6a694..8a884dea 100644 --- a/src/main/java/io/github/mainstringargs/polygon/enums/TickerSort.java +++ b/src/main/java/net/jacobpeterson/polygon/enums/TickerSort.java @@ -1,6 +1,6 @@ -package io.github.mainstringargs.polygon.enums; +package net.jacobpeterson.polygon.enums; -import io.github.mainstringargs.abstracts.enums.APIName; +import net.jacobpeterson.abstracts.enums.APIName; /** * The enum Ticker sort. diff --git a/src/main/java/io/github/mainstringargs/polygon/enums/Timespan.java b/src/main/java/net/jacobpeterson/polygon/enums/Timespan.java similarity index 86% rename from src/main/java/io/github/mainstringargs/polygon/enums/Timespan.java rename to src/main/java/net/jacobpeterson/polygon/enums/Timespan.java index ef9474b8..83d0f3c9 100644 --- a/src/main/java/io/github/mainstringargs/polygon/enums/Timespan.java +++ b/src/main/java/net/jacobpeterson/polygon/enums/Timespan.java @@ -1,6 +1,6 @@ -package io.github.mainstringargs.polygon.enums; +package net.jacobpeterson.polygon.enums; -import io.github.mainstringargs.abstracts.enums.APIName; +import net.jacobpeterson.abstracts.enums.APIName; /** * The Enum Timespan. diff --git a/src/main/java/io/github/mainstringargs/polygon/properties/PolygonProperties.java b/src/main/java/net/jacobpeterson/polygon/properties/PolygonProperties.java similarity index 91% rename from src/main/java/io/github/mainstringargs/polygon/properties/PolygonProperties.java rename to src/main/java/net/jacobpeterson/polygon/properties/PolygonProperties.java index aabd1779..3d165e22 100644 --- a/src/main/java/io/github/mainstringargs/polygon/properties/PolygonProperties.java +++ b/src/main/java/net/jacobpeterson/polygon/properties/PolygonProperties.java @@ -1,10 +1,10 @@ -package io.github.mainstringargs.polygon.properties; +package net.jacobpeterson.polygon.properties; -import io.github.mainstringargs.alpaca.properties.AlpacaProperties; +import net.jacobpeterson.alpaca.properties.AlpacaProperties; import java.util.StringJoiner; -import static io.github.mainstringargs.util.properties.PropertyUtil.getProperty; +import static net.jacobpeterson.util.properties.PropertyUtil.getProperty; /** * The Class AlpacaProperties. diff --git a/src/main/java/io/github/mainstringargs/polygon/rest/PolygonRequest.java b/src/main/java/net/jacobpeterson/polygon/rest/PolygonRequest.java similarity index 90% rename from src/main/java/io/github/mainstringargs/polygon/rest/PolygonRequest.java rename to src/main/java/net/jacobpeterson/polygon/rest/PolygonRequest.java index 2acc844c..d595c649 100644 --- a/src/main/java/io/github/mainstringargs/polygon/rest/PolygonRequest.java +++ b/src/main/java/net/jacobpeterson/polygon/rest/PolygonRequest.java @@ -1,9 +1,9 @@ -package io.github.mainstringargs.polygon.rest; +package net.jacobpeterson.polygon.rest; import com.mashape.unirest.http.HttpResponse; -import io.github.mainstringargs.abstracts.rest.AbstractRequest; -import io.github.mainstringargs.abstracts.rest.AbstractRequestBuilder; -import io.github.mainstringargs.polygon.properties.PolygonProperties; +import net.jacobpeterson.abstracts.rest.AbstractRequest; +import net.jacobpeterson.abstracts.rest.AbstractRequestBuilder; +import net.jacobpeterson.polygon.properties.PolygonProperties; import java.io.InputStream; diff --git a/src/main/java/io/github/mainstringargs/polygon/rest/PolygonRequestBuilder.java b/src/main/java/net/jacobpeterson/polygon/rest/PolygonRequestBuilder.java similarity index 84% rename from src/main/java/io/github/mainstringargs/polygon/rest/PolygonRequestBuilder.java rename to src/main/java/net/jacobpeterson/polygon/rest/PolygonRequestBuilder.java index fc907c85..2d379643 100644 --- a/src/main/java/io/github/mainstringargs/polygon/rest/PolygonRequestBuilder.java +++ b/src/main/java/net/jacobpeterson/polygon/rest/PolygonRequestBuilder.java @@ -1,6 +1,6 @@ -package io.github.mainstringargs.polygon.rest; +package net.jacobpeterson.polygon.rest; -import io.github.mainstringargs.abstracts.rest.AbstractRequestBuilder; +import net.jacobpeterson.abstracts.rest.AbstractRequestBuilder; import java.util.Arrays; diff --git a/src/main/java/io/github/mainstringargs/polygon/rest/exception/PolygonAPIRequestException.java b/src/main/java/net/jacobpeterson/polygon/rest/exception/PolygonAPIRequestException.java similarity index 79% rename from src/main/java/io/github/mainstringargs/polygon/rest/exception/PolygonAPIRequestException.java rename to src/main/java/net/jacobpeterson/polygon/rest/exception/PolygonAPIRequestException.java index 6c6fd219..d2e7328a 100644 --- a/src/main/java/io/github/mainstringargs/polygon/rest/exception/PolygonAPIRequestException.java +++ b/src/main/java/net/jacobpeterson/polygon/rest/exception/PolygonAPIRequestException.java @@ -1,7 +1,7 @@ -package io.github.mainstringargs.polygon.rest.exception; +package net.jacobpeterson.polygon.rest.exception; import com.mashape.unirest.http.HttpResponse; -import io.github.mainstringargs.abstracts.rest.exception.AbstractAPIRequestException; +import net.jacobpeterson.abstracts.rest.exception.AbstractAPIRequestException; import java.io.InputStream; diff --git a/src/main/java/io/github/mainstringargs/polygon/websocket/client/PolygonWebsocketClient.java b/src/main/java/net/jacobpeterson/polygon/websocket/client/PolygonWebsocketClient.java similarity index 93% rename from src/main/java/io/github/mainstringargs/polygon/websocket/client/PolygonWebsocketClient.java rename to src/main/java/net/jacobpeterson/polygon/websocket/client/PolygonWebsocketClient.java index a790fb9e..31ffe3f9 100644 --- a/src/main/java/io/github/mainstringargs/polygon/websocket/client/PolygonWebsocketClient.java +++ b/src/main/java/net/jacobpeterson/polygon/websocket/client/PolygonWebsocketClient.java @@ -1,4 +1,4 @@ -package io.github.mainstringargs.polygon.websocket.client; +package net.jacobpeterson.polygon.websocket.client; import com.google.common.base.Preconditions; import com.google.gson.JsonArray; @@ -6,19 +6,19 @@ import com.google.gson.JsonObject; import com.google.gson.JsonPrimitive; import com.google.gson.JsonSyntaxException; -import io.github.mainstringargs.abstracts.websocket.client.WebsocketClient; -import io.github.mainstringargs.abstracts.websocket.listener.StreamListener; -import io.github.mainstringargs.abstracts.websocket.message.StreamMessage; -import io.github.mainstringargs.abstracts.websocket.message.StreamMessageType; -import io.github.mainstringargs.domain.polygon.websocket.PolygonStreamMessage; -import io.github.mainstringargs.domain.polygon.websocket.aggregate.AggregatePerMinuteMessage; -import io.github.mainstringargs.domain.polygon.websocket.aggregate.AggregatePerSecondMessage; -import io.github.mainstringargs.domain.polygon.websocket.quote.QuoteMessage; -import io.github.mainstringargs.domain.polygon.websocket.status.StatusMessage; -import io.github.mainstringargs.domain.polygon.websocket.trade.TradeMessage; -import io.github.mainstringargs.polygon.websocket.listener.PolygonStreamListener; -import io.github.mainstringargs.polygon.websocket.message.PolygonStreamMessageType; -import io.github.mainstringargs.util.gson.GsonUtil; +import net.jacobpeterson.abstracts.websocket.client.WebsocketClient; +import net.jacobpeterson.abstracts.websocket.listener.StreamListener; +import net.jacobpeterson.abstracts.websocket.message.StreamMessage; +import net.jacobpeterson.abstracts.websocket.message.StreamMessageType; +import net.jacobpeterson.domain.polygon.websocket.PolygonStreamMessage; +import net.jacobpeterson.domain.polygon.websocket.aggregate.AggregatePerMinuteMessage; +import net.jacobpeterson.domain.polygon.websocket.aggregate.AggregatePerSecondMessage; +import net.jacobpeterson.domain.polygon.websocket.quote.QuoteMessage; +import net.jacobpeterson.domain.polygon.websocket.status.StatusMessage; +import net.jacobpeterson.domain.polygon.websocket.trade.TradeMessage; +import net.jacobpeterson.polygon.websocket.listener.PolygonStreamListener; +import net.jacobpeterson.polygon.websocket.message.PolygonStreamMessageType; +import net.jacobpeterson.util.gson.GsonUtil; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; diff --git a/src/main/java/io/github/mainstringargs/polygon/websocket/client/PolygonWebsocketClientEndpoint.java b/src/main/java/net/jacobpeterson/polygon/websocket/client/PolygonWebsocketClientEndpoint.java similarity index 83% rename from src/main/java/io/github/mainstringargs/polygon/websocket/client/PolygonWebsocketClientEndpoint.java rename to src/main/java/net/jacobpeterson/polygon/websocket/client/PolygonWebsocketClientEndpoint.java index 0787f6b0..5b26e7dc 100644 --- a/src/main/java/io/github/mainstringargs/polygon/websocket/client/PolygonWebsocketClientEndpoint.java +++ b/src/main/java/net/jacobpeterson/polygon/websocket/client/PolygonWebsocketClientEndpoint.java @@ -1,7 +1,7 @@ -package io.github.mainstringargs.polygon.websocket.client; +package net.jacobpeterson.polygon.websocket.client; -import io.github.mainstringargs.abstracts.websocket.client.AbstractWebsocketClientEndpoint; -import io.github.mainstringargs.abstracts.websocket.client.WebsocketClient; +import net.jacobpeterson.abstracts.websocket.client.AbstractWebsocketClientEndpoint; +import net.jacobpeterson.abstracts.websocket.client.WebsocketClient; import javax.websocket.ClientEndpoint; import javax.websocket.CloseReason; diff --git a/src/main/java/io/github/mainstringargs/polygon/websocket/listener/PolygonStreamListener.java b/src/main/java/net/jacobpeterson/polygon/websocket/listener/PolygonStreamListener.java similarity index 71% rename from src/main/java/io/github/mainstringargs/polygon/websocket/listener/PolygonStreamListener.java rename to src/main/java/net/jacobpeterson/polygon/websocket/listener/PolygonStreamListener.java index cf2c2a73..9547316e 100644 --- a/src/main/java/io/github/mainstringargs/polygon/websocket/listener/PolygonStreamListener.java +++ b/src/main/java/net/jacobpeterson/polygon/websocket/listener/PolygonStreamListener.java @@ -1,8 +1,8 @@ -package io.github.mainstringargs.polygon.websocket.listener; +package net.jacobpeterson.polygon.websocket.listener; -import io.github.mainstringargs.abstracts.websocket.listener.StreamListener; -import io.github.mainstringargs.domain.polygon.websocket.PolygonStreamMessage; -import io.github.mainstringargs.polygon.websocket.message.PolygonStreamMessageType; +import net.jacobpeterson.abstracts.websocket.listener.StreamListener; +import net.jacobpeterson.domain.polygon.websocket.PolygonStreamMessage; +import net.jacobpeterson.polygon.websocket.message.PolygonStreamMessageType; import java.util.Map; import java.util.Set; diff --git a/src/main/java/io/github/mainstringargs/polygon/websocket/listener/PolygonStreamListenerAdapter.java b/src/main/java/net/jacobpeterson/polygon/websocket/listener/PolygonStreamListenerAdapter.java similarity index 91% rename from src/main/java/io/github/mainstringargs/polygon/websocket/listener/PolygonStreamListenerAdapter.java rename to src/main/java/net/jacobpeterson/polygon/websocket/listener/PolygonStreamListenerAdapter.java index e23360c2..4372b9e3 100644 --- a/src/main/java/io/github/mainstringargs/polygon/websocket/listener/PolygonStreamListenerAdapter.java +++ b/src/main/java/net/jacobpeterson/polygon/websocket/listener/PolygonStreamListenerAdapter.java @@ -1,7 +1,7 @@ -package io.github.mainstringargs.polygon.websocket.listener; +package net.jacobpeterson.polygon.websocket.listener; -import io.github.mainstringargs.domain.polygon.websocket.PolygonStreamMessage; -import io.github.mainstringargs.polygon.websocket.message.PolygonStreamMessageType; +import net.jacobpeterson.domain.polygon.websocket.PolygonStreamMessage; +import net.jacobpeterson.polygon.websocket.message.PolygonStreamMessageType; import java.util.Arrays; import java.util.HashMap; diff --git a/src/main/java/io/github/mainstringargs/polygon/websocket/message/PolygonStreamMessageType.java b/src/main/java/net/jacobpeterson/polygon/websocket/message/PolygonStreamMessageType.java similarity index 84% rename from src/main/java/io/github/mainstringargs/polygon/websocket/message/PolygonStreamMessageType.java rename to src/main/java/net/jacobpeterson/polygon/websocket/message/PolygonStreamMessageType.java index c18f8083..d82b6d3a 100644 --- a/src/main/java/io/github/mainstringargs/polygon/websocket/message/PolygonStreamMessageType.java +++ b/src/main/java/net/jacobpeterson/polygon/websocket/message/PolygonStreamMessageType.java @@ -1,9 +1,9 @@ -package io.github.mainstringargs.polygon.websocket.message; +package net.jacobpeterson.polygon.websocket.message; import com.google.gson.annotations.SerializedName; -import io.github.mainstringargs.abstracts.enums.APIName; -import io.github.mainstringargs.abstracts.websocket.message.StreamMessageType; -import io.github.mainstringargs.util.gson.GsonUtil; +import net.jacobpeterson.abstracts.enums.APIName; +import net.jacobpeterson.abstracts.websocket.message.StreamMessageType; +import net.jacobpeterson.util.gson.GsonUtil; /** * The enum Polygon stream message type. diff --git a/src/main/java/io/github/mainstringargs/util/concurrency/ExecutorTracer.java b/src/main/java/net/jacobpeterson/util/concurrency/ExecutorTracer.java similarity index 99% rename from src/main/java/io/github/mainstringargs/util/concurrency/ExecutorTracer.java rename to src/main/java/net/jacobpeterson/util/concurrency/ExecutorTracer.java index 4e32deb7..5d5dad80 100644 --- a/src/main/java/io/github/mainstringargs/util/concurrency/ExecutorTracer.java +++ b/src/main/java/net/jacobpeterson/util/concurrency/ExecutorTracer.java @@ -1,4 +1,4 @@ -package io.github.mainstringargs.util.concurrency; +package net.jacobpeterson.util.concurrency; import java.util.Collection; import java.util.List; diff --git a/src/main/java/io/github/mainstringargs/util/concurrency/ScheduledThreadPoolExecutorTracer.java b/src/main/java/net/jacobpeterson/util/concurrency/ScheduledThreadPoolExecutorTracer.java similarity index 97% rename from src/main/java/io/github/mainstringargs/util/concurrency/ScheduledThreadPoolExecutorTracer.java rename to src/main/java/net/jacobpeterson/util/concurrency/ScheduledThreadPoolExecutorTracer.java index 61bc9acd..1b9de9e5 100644 --- a/src/main/java/io/github/mainstringargs/util/concurrency/ScheduledThreadPoolExecutorTracer.java +++ b/src/main/java/net/jacobpeterson/util/concurrency/ScheduledThreadPoolExecutorTracer.java @@ -1,4 +1,4 @@ -package io.github.mainstringargs.util.concurrency; +package net.jacobpeterson.util.concurrency; import java.util.concurrent.CancellationException; import java.util.concurrent.ExecutionException; diff --git a/src/main/java/io/github/mainstringargs/util/concurrency/ThreadPoolExecutorTracer.java b/src/main/java/net/jacobpeterson/util/concurrency/ThreadPoolExecutorTracer.java similarity index 97% rename from src/main/java/io/github/mainstringargs/util/concurrency/ThreadPoolExecutorTracer.java rename to src/main/java/net/jacobpeterson/util/concurrency/ThreadPoolExecutorTracer.java index fa950de6..3109e7bf 100644 --- a/src/main/java/io/github/mainstringargs/util/concurrency/ThreadPoolExecutorTracer.java +++ b/src/main/java/net/jacobpeterson/util/concurrency/ThreadPoolExecutorTracer.java @@ -1,4 +1,4 @@ -package io.github.mainstringargs.util.concurrency; +package net.jacobpeterson.util.concurrency; import java.util.concurrent.BlockingQueue; import java.util.concurrent.CancellationException; diff --git a/src/main/java/io/github/mainstringargs/util/doc/Documentation2JSONSchema.java b/src/main/java/net/jacobpeterson/util/doc/Documentation2JSONSchema.java similarity index 99% rename from src/main/java/io/github/mainstringargs/util/doc/Documentation2JSONSchema.java rename to src/main/java/net/jacobpeterson/util/doc/Documentation2JSONSchema.java index 91d057a8..5deab579 100644 --- a/src/main/java/io/github/mainstringargs/util/doc/Documentation2JSONSchema.java +++ b/src/main/java/net/jacobpeterson/util/doc/Documentation2JSONSchema.java @@ -1,4 +1,4 @@ -package io.github.mainstringargs.util.doc; +package net.jacobpeterson.util.doc; import org.w3c.dom.Document; import org.w3c.dom.NodeList; diff --git a/src/main/java/io/github/mainstringargs/util/gson/GsonUtil.java b/src/main/java/net/jacobpeterson/util/gson/GsonUtil.java similarity index 98% rename from src/main/java/io/github/mainstringargs/util/gson/GsonUtil.java rename to src/main/java/net/jacobpeterson/util/gson/GsonUtil.java index dc89c727..6d3c03cc 100644 --- a/src/main/java/io/github/mainstringargs/util/gson/GsonUtil.java +++ b/src/main/java/net/jacobpeterson/util/gson/GsonUtil.java @@ -1,4 +1,4 @@ -package io.github.mainstringargs.util.gson; +package net.jacobpeterson.util.gson; import com.google.gson.Gson; import com.google.gson.GsonBuilder; diff --git a/src/main/java/io/github/mainstringargs/util/gson/ZonedDateTimeAdapter.java b/src/main/java/net/jacobpeterson/util/gson/ZonedDateTimeAdapter.java similarity index 96% rename from src/main/java/io/github/mainstringargs/util/gson/ZonedDateTimeAdapter.java rename to src/main/java/net/jacobpeterson/util/gson/ZonedDateTimeAdapter.java index 47927096..c7794564 100644 --- a/src/main/java/io/github/mainstringargs/util/gson/ZonedDateTimeAdapter.java +++ b/src/main/java/net/jacobpeterson/util/gson/ZonedDateTimeAdapter.java @@ -1,4 +1,4 @@ -package io.github.mainstringargs.util.gson; +package net.jacobpeterson.util.gson; import com.google.gson.JsonDeserializationContext; import com.google.gson.JsonDeserializer; diff --git a/src/main/java/io/github/mainstringargs/util/properties/PropertyUtil.java b/src/main/java/net/jacobpeterson/util/properties/PropertyUtil.java similarity index 98% rename from src/main/java/io/github/mainstringargs/util/properties/PropertyUtil.java rename to src/main/java/net/jacobpeterson/util/properties/PropertyUtil.java index 29d345e2..3ff46855 100644 --- a/src/main/java/io/github/mainstringargs/util/properties/PropertyUtil.java +++ b/src/main/java/net/jacobpeterson/util/properties/PropertyUtil.java @@ -1,4 +1,4 @@ -package io.github.mainstringargs.util.properties; +package net.jacobpeterson.util.properties; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; diff --git a/src/main/java/io/github/mainstringargs/util/time/TimeUtil.java b/src/main/java/net/jacobpeterson/util/time/TimeUtil.java similarity index 98% rename from src/main/java/io/github/mainstringargs/util/time/TimeUtil.java rename to src/main/java/net/jacobpeterson/util/time/TimeUtil.java index 2d8383ef..0ccaa36a 100644 --- a/src/main/java/io/github/mainstringargs/util/time/TimeUtil.java +++ b/src/main/java/net/jacobpeterson/util/time/TimeUtil.java @@ -1,4 +1,4 @@ -package io.github.mainstringargs.util.time; +package net.jacobpeterson.util.time; import java.text.DecimalFormat; import java.text.NumberFormat; From 2a7237e5d330739afa7040c91c9f1428cf7090f6 Mon Sep 17 00:00:00 2001 From: Jacob Peterson Date: Wed, 2 Sep 2020 12:06:12 -0600 Subject: [PATCH 013/271] Add snapshot version --- README.md | 4 ++-- build.gradle | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index b39021bf..8f121c95 100644 --- a/README.md +++ b/README.md @@ -33,7 +33,7 @@ Add the following dependency to your build.gradle file: ``` dependencies { - compile "net.jacobpeterson:alpaca-java:5.1" + compile "net.jacobpeterson:alpaca-java:5.1-SNAPSHOT" } ``` @@ -45,7 +45,7 @@ Add the following dependency to your pom.xml file: net.jacobpeterson alpaca-java - 5.1 + 5.1-SNAPSHOT compile ``` diff --git a/build.gradle b/build.gradle index 7366210e..fb1c2271 100644 --- a/build.gradle +++ b/build.gradle @@ -27,7 +27,7 @@ tasks.getByName("generateJsonSchema2Pojo").enabled(false) // Disable default jso archivesBaseName = 'alpaca-java' group = 'net.jacobpeterson' -version = '5.1' +version = '5.1-SNAPSHOT' repositories { mavenLocal() From 545cc31cfc42c9196fa2deaed248a7366954dbb2 Mon Sep 17 00:00:00 2001 From: foobar Date: Fri, 4 Sep 2020 23:45:36 -0400 Subject: [PATCH 014/271] Fix bug where replacing order for trailing stops was using incorrect field for body payload --- schema_json/alpaca/order/order.json | 2 +- .../java/net/jacobpeterson/alpaca/AlpacaAPI.java | 15 +++++---------- .../net/jacobpeterson/alpaca/AlpacaConstants.java | 4 ++-- 3 files changed, 8 insertions(+), 13 deletions(-) diff --git a/schema_json/alpaca/order/order.json b/schema_json/alpaca/order/order.json index 96ba0973..c647f52c 100644 --- a/schema_json/alpaca/order/order.json +++ b/schema_json/alpaca/order/order.json @@ -102,7 +102,7 @@ "hwm": { "existingJavaType": "java.lang.String", - "title": "hwm" + "title": "High water mark - the highest (or lowest, if shorting) price seen. Set by Alpaca" }, "filled_avg_price": { "existingJavaType": "java.lang.String", diff --git a/src/main/java/net/jacobpeterson/alpaca/AlpacaAPI.java b/src/main/java/net/jacobpeterson/alpaca/AlpacaAPI.java index 50a56ce6..cc952cc4 100644 --- a/src/main/java/net/jacobpeterson/alpaca/AlpacaAPI.java +++ b/src/main/java/net/jacobpeterson/alpaca/AlpacaAPI.java @@ -877,6 +877,7 @@ public Order getOrderByClientID(String clientOrderId) throws AlpacaAPIRequestExc * @param timeInForce day, gtc, opg, cls, ioc, fok. Please see Understand Orders for more info. * @param limitPrice required if type is limit or stop_limit * @param stopPrice required if type is stop or stop_limit + * @param trail required if type is trail_stop * @param clientOrderId A unique identifier for the order. Automatically generated if not sent. * * @return the order @@ -885,8 +886,7 @@ public Order getOrderByClientID(String clientOrderId) throws AlpacaAPIRequestExc * @see Orders */ public Order replaceOrder(String orderID, Integer quantity, OrderTimeInForce timeInForce, Double limitPrice, - Double stopPrice, Double trailingPercent, Double trailingPrice, String clientOrderId) - throws AlpacaAPIRequestException { + Double stopPrice, Double trail, String clientOrderId) throws AlpacaAPIRequestException { Preconditions.checkNotNull(orderID); AlpacaRequestBuilder urlBuilder = new AlpacaRequestBuilder(baseAPIURL, apiVersion, @@ -911,14 +911,9 @@ public Order replaceOrder(String orderID, Integer quantity, OrderTimeInForce tim TimeUtil.toDecimalFormat(stopPrice)); } - if (trailingPercent != null) { - urlBuilder.appendJSONBodyProperty(AlpacaConstants.TRAIL_PERCENT_PARAMETER, - TimeUtil.toDecimalFormat(trailingPercent)); - } - - if (trailingPrice != null) { - urlBuilder.appendJSONBodyProperty(AlpacaConstants.TRAIL_PRICE_PARAMETER, - TimeUtil.toDecimalFormat(trailingPrice)); + if(trail != null) { + urlBuilder.appendJSONBodyProperty(AlpacaConstants.TRAIL_PARAMETER, + TimeUtil.toDecimalFormat(trail)); } if (clientOrderId != null) { diff --git a/src/main/java/net/jacobpeterson/alpaca/AlpacaConstants.java b/src/main/java/net/jacobpeterson/alpaca/AlpacaConstants.java index b88f6727..64324b7b 100644 --- a/src/main/java/net/jacobpeterson/alpaca/AlpacaConstants.java +++ b/src/main/java/net/jacobpeterson/alpaca/AlpacaConstants.java @@ -47,8 +47,8 @@ public class AlpacaConstants { /** The Constant TRAIL_PERCENT_PARAMETER. */ public static final String TRAIL_PERCENT_PARAMETER = "trail_percent"; - /** The Constant HWM_PARAMETER. */ - public static final String HWM_PARAMETER = "hwm"; + /** The Constant TRAIL_PARAMETER. */ + public static final String TRAIL_PARAMETER = "trail"; /** The Constant LIMIT_PRICE_PARAMETER. */ public static final String LIMIT_PRICE_PARAMETER = "limit_price"; From 17ea3d4b4785f79e657c3ddc6b10d820fae08f83 Mon Sep 17 00:00:00 2001 From: Jacob Peterson Date: Tue, 15 Sep 2020 13:29:32 -0600 Subject: [PATCH 015/271] Trailing stop refactoring --- schema_json/alpaca/order/order.json | 26 ++--- schema_json/alpaca/position/position.json | 12 -- .../net/jacobpeterson/alpaca/AlpacaAPI.java | 109 +++++++++--------- .../net/jacobpeterson/util/time/TimeUtil.java | 12 +- 4 files changed, 72 insertions(+), 87 deletions(-) diff --git a/schema_json/alpaca/order/order.json b/schema_json/alpaca/order/order.json index c647f52c..ac9db9c4 100644 --- a/schema_json/alpaca/order/order.json +++ b/schema_json/alpaca/order/order.json @@ -90,20 +90,6 @@ "existingJavaType": "java.lang.String", "title": "Stop price" }, - "trail_percent": { - "existingJavaType": "java.lang.String", - "title": "Stop trail percent" - }, - - "trail_price": { - "existingJavaType": "java.lang.String", - "title": "Stop trailing price" - }, - - "hwm": { - "existingJavaType": "java.lang.String", - "title": "High water mark - the highest (or lowest, if shorting) price seen. Set by Alpaca" - }, "filled_avg_price": { "existingJavaType": "java.lang.String", "title": "Stop price" @@ -119,6 +105,18 @@ "legs": { "existingJavaType": "java.util.ArrayList", "title": "When querying non-simple order_class orders in a nested style, an array of Order entities associated with this order is returned. Otherwise, null." + }, + "trail_price": { + "existingJavaType": "java.lang.String", + "title": "The dollar value away from the high water mark for trailing stop orders." + }, + "trail_percent": { + "existingJavaType": "java.lang.String", + "title": "The percent value away from the high water mark for trailing stop orders." + }, + "hwm": { + "existingJavaType": "java.lang.String", + "title": "High Water Mark - The highest (lowest) market price seen since the trailing stop order was submitted." } } } diff --git a/schema_json/alpaca/position/position.json b/schema_json/alpaca/position/position.json index cf0684b1..254678d3 100644 --- a/schema_json/alpaca/position/position.json +++ b/schema_json/alpaca/position/position.json @@ -65,18 +65,6 @@ "change_today": { "existingJavaType": "java.lang.String", "title": "Percent change from last day price (by a factor of 1)" - }, - "trail_percent": { - "existingJavaType": "java.lang.String", - "title": "Stop trail percent" - }, - "trail_price": { - "existingJavaType": "java.lang.String", - "title": "Stop trail price" - }, - "hwm": { - "existingJavaType": "java.lang.String", - "title": "the actual high water mark value tracked in Alpaca" } } } diff --git a/src/main/java/net/jacobpeterson/alpaca/AlpacaAPI.java b/src/main/java/net/jacobpeterson/alpaca/AlpacaAPI.java index cc952cc4..cbae655a 100644 --- a/src/main/java/net/jacobpeterson/alpaca/AlpacaAPI.java +++ b/src/main/java/net/jacobpeterson/alpaca/AlpacaAPI.java @@ -383,6 +383,8 @@ public ArrayList getOrders(OrderStatus status, Integer limit, ZonedDateTi * @param timeInForce day, gtc, opg, cls, ioc, fok. Please see Understand Orders for more info. * @param limitPrice required if type is limit or stop_limit * @param stopPrice required if type is stop or stop_limit + * @param trailPrice this or trail_percent is required if type is trailing_stop + * @param trailPercent this or trail_price is required if type is trailing_stop * @param extendedHours (default) false. If true, order will be eligible to execute in premarket/afterhours. * Only works with type limit and time_in_force day. * @param clientOrderId A unique identifier for the order. Automatically generated if not sent. @@ -394,8 +396,6 @@ public ArrayList getOrders(OrderStatus status, Integer limit, ZonedDateTi * orders. * @param stopLossLimitPrice Additional parameters for stop-loss leg of advanced orders. The stop-loss order * becomes a stop-limit order if specified. - * @param trailPercent required if type is trailing_limit - * @param trailPrice required if type is trailing_limit * * @return the order * @@ -403,9 +403,9 @@ public ArrayList getOrders(OrderStatus status, Integer limit, ZonedDateTi * @see Orders */ public Order requestNewOrder(String symbol, Integer quantity, OrderSide side, OrderType type, - OrderTimeInForce timeInForce, Double limitPrice, Double stopPrice, Boolean extendedHours, - String clientOrderId, OrderClass orderClass, Double takeProfitLimitPrice, Double stopLossStopPrice, - Double stopLossLimitPrice, Double trailPrice, Double trailPercent) + OrderTimeInForce timeInForce, Double limitPrice, Double stopPrice, Double trailPrice, Double trailPercent, + Boolean extendedHours, String clientOrderId, OrderClass orderClass, Double takeProfitLimitPrice, + Double stopLossStopPrice, Double stopLossLimitPrice) throws AlpacaAPIRequestException { Preconditions.checkNotNull(symbol); Preconditions.checkNotNull(quantity); @@ -432,6 +432,16 @@ public Order requestNewOrder(String symbol, Integer quantity, OrderSide side, Or TimeUtil.toDecimalFormat(stopPrice)); } + if (trailPrice != null) { + urlBuilder.appendJSONBodyProperty(AlpacaConstants.TRAIL_PRICE_PARAMETER, + TimeUtil.toDecimalFormat(trailPrice)); + } + + if (trailPercent != null) { + urlBuilder.appendJSONBodyProperty(AlpacaConstants.TRAIL_PERCENT_PARAMETER, + TimeUtil.toDecimalFormat(trailPercent)); + } + if (extendedHours != null) { urlBuilder.appendJSONBodyJSONProperty(AlpacaConstants.EXTENDED_HOURS_PARAMETER, new JsonPrimitive(extendedHours)); @@ -465,16 +475,6 @@ public Order requestNewOrder(String symbol, Integer quantity, OrderSide side, Or urlBuilder.appendJSONBodyJSONProperty(AlpacaConstants.STOP_LOSS_PARAMETER, stopLoss); } - if (trailPrice != null) { - urlBuilder.appendJSONBodyProperty(AlpacaConstants.TRAIL_PRICE_PARAMETER, - TimeUtil.toDecimalFormat(trailPrice)); - } - - if (trailPercent != null) { - urlBuilder.appendJSONBodyProperty(AlpacaConstants.TRAIL_PERCENT_PARAMETER, - TimeUtil.toDecimalFormat(trailPercent)); - } - HttpResponse response = alpacaRequest.invokePost(urlBuilder); if (response.getStatus() != 200) { @@ -487,8 +487,8 @@ public Order requestNewOrder(String symbol, Integer quantity, OrderSide side, Or /** * A market order is a request to buy or sell a security at the currently available market price. It provides the * most likely method of filling an order. Market orders fill nearly instantaneously. This method calls {@link - * #requestNewOrder(String, Integer, OrderSide, OrderType, OrderTimeInForce, Double, Double, Boolean, String, - * OrderClass, Double, Double, Double, Double, Double)} with {@link OrderType#MARKET}. + * #requestNewOrder(String, Integer, OrderSide, OrderType, OrderTimeInForce, Double, Double, Double, Double, + * Boolean, String, OrderClass, Double, Double, Double)} with {@link OrderType#MARKET}. * * @param symbol symbol or asset ID to identify the asset to trade * @param quantity number of shares to trade @@ -504,8 +504,8 @@ public Order requestNewOrder(String symbol, Integer quantity, OrderSide side, Or */ public Order requestNewMarketOrder(String symbol, Integer quantity, OrderSide side, OrderTimeInForce timeInForce, Boolean extendedHours) throws AlpacaAPIRequestException { - return requestNewOrder(symbol, quantity, side, OrderType.MARKET, timeInForce, null, null, extendedHours, - null, OrderClass.SIMPLE, null, null, null, null, null); + return requestNewOrder(symbol, quantity, side, OrderType.MARKET, timeInForce, null, null, null, null, + extendedHours, null, OrderClass.SIMPLE, null, null, null); } /** @@ -513,7 +513,7 @@ public Order requestNewMarketOrder(String symbol, Integer quantity, OrderSide si * is executed at the specified limit price or lower (i.e., better). Conversely, a sell limit order (a limit order * to sell) is executed at the specified limit price or higher (better). Unlike a market order, you have to specify * the limit price parameter when submitting your order. This method calls {@link #requestNewOrder(String, Integer, - * OrderSide, OrderType, OrderTimeInForce, Double, Double, Boolean, String, OrderClass, Double, Double, Double, + * OrderSide, OrderType, OrderTimeInForce, Double, Double, Double, Double, Boolean, String, OrderClass, Double, * Double, Double)} with {@link OrderType#LIMIT}. * * @param symbol symbol or asset ID to identify the asset to trade @@ -531,8 +531,8 @@ public Order requestNewMarketOrder(String symbol, Integer quantity, OrderSide si */ public Order requestNewLimitOrder(String symbol, Integer quantity, OrderSide side, OrderTimeInForce timeInForce, Double limitPrice, Boolean extendedHours) throws AlpacaAPIRequestException { - return requestNewOrder(symbol, quantity, side, OrderType.LIMIT, timeInForce, limitPrice, null, extendedHours, - null, OrderClass.SIMPLE, null, null, null, null, null); + return requestNewOrder(symbol, quantity, side, OrderType.LIMIT, timeInForce, limitPrice, null, null, null, + extendedHours, null, OrderClass.SIMPLE, null, null, null); } /** @@ -541,8 +541,8 @@ public Order requestNewLimitOrder(String symbol, Integer quantity, OrderSide sid * specified stop price, the stop order becomes a market order. Alpaca converts buy stop orders into stop limit * orders with a limit price that is 4% higher than a stop price < $50 (or 2.5% higher than a stop price >= * $50). Sell stop orders are not converted into stop limit orders. This method calls {@link - * #requestNewOrder(String, Integer, OrderSide, OrderType, OrderTimeInForce, Double, Double, Boolean, String, - * OrderClass, Double, Double, Double, Double, Double)} with {@link OrderType#STOP}. + * #requestNewOrder(String, Integer, OrderSide, OrderType, OrderTimeInForce, Double, Double, Double, Double, + * Boolean, String, OrderClass, Double, Double, Double)} with {@link OrderType#STOP}. * * @param symbol symbol or asset ID to identify the asset to trade * @param quantity number of shares to trade @@ -559,8 +559,8 @@ public Order requestNewLimitOrder(String symbol, Integer quantity, OrderSide sid */ public Order requestNewStopOrder(String symbol, Integer quantity, OrderSide side, OrderTimeInForce timeInForce, Double stopPrice, Boolean extendedHours) throws AlpacaAPIRequestException { - return requestNewOrder(symbol, quantity, side, OrderType.STOP, timeInForce, null, stopPrice, extendedHours, - null, OrderClass.SIMPLE, null, null, null, null, null); + return requestNewOrder(symbol, quantity, side, OrderType.STOP, timeInForce, null, stopPrice, null, null, + extendedHours, null, OrderClass.SIMPLE, null, null, null); } /** @@ -568,8 +568,8 @@ public Order requestNewStopOrder(String symbol, Integer quantity, OrderSide side * those of a limit order and is used to mitigate risk. The stop-limit order will be executed at a specified limit * price, or better, after a given stop price has been reached. Once the stop price is reached, the stop-limit order * becomes a limit order to buy or sell at the limit price or better. This method calls {@link - * #requestNewOrder(String, Integer, OrderSide, OrderType, OrderTimeInForce, Double, Double, Boolean, String, - * OrderClass, Double, Double, Double, Double, Double)} with {@link OrderType#STOP_LIMIT}. + * #requestNewOrder(String, Integer, OrderSide, OrderType, OrderTimeInForce, Double, Double, Double, Double, + * Boolean, String, OrderClass, Double, Double, Double)} with {@link OrderType#STOP_LIMIT}. * * @param symbol symbol or asset ID to identify the asset to trade * @param quantity number of shares to trade @@ -588,14 +588,14 @@ public Order requestNewStopOrder(String symbol, Integer quantity, OrderSide side public Order requestNewStopLimitOrder(String symbol, Integer quantity, OrderSide side, OrderTimeInForce timeInForce, Double limitPrice, Double stopPrice, Boolean extendedHours) throws AlpacaAPIRequestException { return requestNewOrder(symbol, quantity, side, OrderType.STOP_LIMIT, timeInForce, limitPrice, stopPrice, - extendedHours, null, OrderClass.SIMPLE, null, null, null, null, null); + null, null, extendedHours, null, OrderClass.SIMPLE, null, null, null); } /** * A bracket order is a chain of three orders that can be used to manage your position entry and exit. It is a * common use case of an OTOCO (One Triggers OCO {One Cancels Other}) order. This method calls {@link - * #requestNewOrder(String, Integer, OrderSide, OrderType, OrderTimeInForce, Double, Double, Boolean, String, - * OrderClass, Double, Double, Double, Double, Double)} with {@link OrderType#MARKET} and with parameters for a + * #requestNewOrder(String, Integer, OrderSide, OrderType, OrderTimeInForce, Double, Double, Double, Double, + * Boolean, String, OrderClass, Double, Double, Double)} with {@link OrderType#MARKET} and with parameters for a * bracket order. * * @param symbol symbol or asset ID to identify the asset to trade @@ -619,16 +619,16 @@ public Order requestNewStopLimitOrder(String symbol, Integer quantity, OrderSide public Order requestNewMarketBracketOrder(String symbol, Integer quantity, OrderSide side, OrderTimeInForce timeInForce, Boolean extendedHours, Double takeProfitLimitPrice, Double stopLossStopPrice, Double stopLossLimitPrice) throws AlpacaAPIRequestException { - return requestNewOrder(symbol, quantity, side, OrderType.MARKET, timeInForce, null, null, + return requestNewOrder(symbol, quantity, side, OrderType.MARKET, timeInForce, null, null, null, null, extendedHours, null, OrderClass.BRACKET, takeProfitLimitPrice, stopLossStopPrice, - stopLossLimitPrice, null, null); + stopLossLimitPrice); } /** * A bracket order is a chain of three orders that can be used to manage your position entry and exit. It is a * common use case of an OTOCO (One Triggers OCO {One Cancels Other}) order. This method calls {@link - * #requestNewOrder(String, Integer, OrderSide, OrderType, OrderTimeInForce, Double, Double, Boolean, String, - * OrderClass, Double, Double, Double, Double, Double)} with {@link OrderType#LIMIT} and with parameters for a + * #requestNewOrder(String, Integer, OrderSide, OrderType, OrderTimeInForce, Double, Double, Double, Double, + * Boolean, String, OrderClass, Double, Double, Double)} with {@link OrderType#LIMIT} and with parameters for a * bracket order. * * @param symbol symbol or asset ID to identify the asset to trade @@ -653,9 +653,8 @@ public Order requestNewMarketBracketOrder(String symbol, Integer quantity, Order public Order requestNewLimitBracketOrder(String symbol, Integer quantity, OrderSide side, OrderTimeInForce timeInForce, Double limitPrice, Boolean extendedHours, Double takeProfitLimitPrice, Double stopLossStopPrice, Double stopLossLimitPrice) throws AlpacaAPIRequestException { - return requestNewOrder(symbol, quantity, side, OrderType.LIMIT, timeInForce, limitPrice, null, - extendedHours, null, OrderClass.BRACKET, takeProfitLimitPrice, stopLossStopPrice, stopLossLimitPrice, - null, null); + return requestNewOrder(symbol, quantity, side, OrderType.LIMIT, timeInForce, limitPrice, null, null, null, + extendedHours, null, OrderClass.BRACKET, takeProfitLimitPrice, stopLossStopPrice, stopLossLimitPrice); } /** @@ -663,7 +662,7 @@ public Order requestNewLimitBracketOrder(String symbol, Integer quantity, OrderS * (buy/buy or sell/sell) and currently only exit order is supported. In other words, this is the second part of the * bracket orders where the entry order is already filled, and you can submit the take-profit and stop-loss in one * order submission. This method calls {@link #requestNewOrder(String, Integer, OrderSide, OrderType, - * OrderTimeInForce, Double, Double, Boolean, String, OrderClass, Double, Double, Double, Double, Double)} with + * OrderTimeInForce, Double, Double, Double, Double, Boolean, String, OrderClass, Double, Double, Double)} with * parameters for an OCO order type. * * @param symbol symbol or asset ID to identify the asset to trade @@ -685,14 +684,14 @@ public Order requestNewLimitBracketOrder(String symbol, Integer quantity, OrderS public Order requestNewOCOOrder(String symbol, Integer quantity, OrderSide side, OrderTimeInForce timeInForce, Boolean extendedHours, Double takeProfitLimitPrice, Double stopLossStopPrice, Double stopLossLimitPrice) throws AlpacaAPIRequestException { - return requestNewOrder(symbol, quantity, side, OrderType.LIMIT, timeInForce, null, null, extendedHours, null, - OrderClass.OCO, takeProfitLimitPrice, stopLossStopPrice, stopLossLimitPrice, null, null); + return requestNewOrder(symbol, quantity, side, OrderType.LIMIT, timeInForce, null, null, null, null, + extendedHours, null, OrderClass.OCO, takeProfitLimitPrice, stopLossStopPrice, stopLossLimitPrice); } /** * OTO (One-Triggers-Other) is a variant of bracket order. It takes one of the take-profit or stop-loss order in * addition to the entry order. This method calls {@link #requestNewOrder(String, Integer, OrderSide, OrderType, - * OrderTimeInForce, Double, Double, Boolean, String, OrderClass, Double, Double, Double, Double, Double)} with + * OrderTimeInForce, Double, Double, Double, Double, Boolean, String, OrderClass, Double, Double, Double)} with * {@link OrderType#LIMIT} and with parameters for an OTO order type. * * @param symbol symbol or asset ID to identify the asset to trade @@ -714,14 +713,14 @@ public Order requestNewOCOOrder(String symbol, Integer quantity, OrderSide side, public Order requestNewOTOMarketOrder(String symbol, Integer quantity, OrderSide side, OrderTimeInForce timeInForce, Boolean extendedHours, Double takeProfitLimitPrice, Double stopLossStopPrice, Double stopLossLimitPrice) throws AlpacaAPIRequestException { - return requestNewOrder(symbol, quantity, side, OrderType.MARKET, timeInForce, null, null, extendedHours, null, - OrderClass.OTO, takeProfitLimitPrice, stopLossStopPrice, stopLossLimitPrice, null, null); + return requestNewOrder(symbol, quantity, side, OrderType.MARKET, timeInForce, null, null, null, null, + extendedHours, null, OrderClass.OTO, takeProfitLimitPrice, stopLossStopPrice, stopLossLimitPrice); } /** * OTO (One-Triggers-Other) is a variant of bracket order. It takes one of the take-profit or stop-loss order in * addition to the entry order. This method calls {@link #requestNewOrder(String, Integer, OrderSide, OrderType, - * OrderTimeInForce, Double, Double, Boolean, String, OrderClass, Double, Double, Double, Double, Double)} with + * OrderTimeInForce, Double, Double, Double, Double, Boolean, String, OrderClass, Double, Double, Double)} with * {@link OrderType#LIMIT} and with parameters for an OTO order type. * * @param symbol symbol or asset ID to identify the asset to trade @@ -744,14 +743,14 @@ public Order requestNewOTOMarketOrder(String symbol, Integer quantity, OrderSide public Order requestNewOTOLimitOrder(String symbol, Integer quantity, OrderSide side, OrderTimeInForce timeInForce, Double limitPrice, Boolean extendedHours, Double takeProfitLimitPrice, Double stopLossStopPrice, Double stopLossLimitPrice) throws AlpacaAPIRequestException { - return requestNewOrder(symbol, quantity, side, OrderType.LIMIT, timeInForce, limitPrice, null, extendedHours, - null, OrderClass.OTO, takeProfitLimitPrice, stopLossStopPrice, stopLossLimitPrice, null, null); + return requestNewOrder(symbol, quantity, side, OrderType.LIMIT, timeInForce, limitPrice, null, null, null, + extendedHours, null, OrderClass.OTO, takeProfitLimitPrice, stopLossStopPrice, stopLossLimitPrice); } /** * OTO (One-Triggers-Other) is a variant of bracket order. It takes one of the take-profit or stop-loss order in * addition to the entry order. This method calls {@link #requestNewOrder(String, Integer, OrderSide, OrderType, - * OrderTimeInForce, Double, Double, Boolean, String, OrderClass, Double, Double, Double, Double, Double)} with + * OrderTimeInForce, Double, Double, Double, Double, Boolean, String, OrderClass, Double, Double, Double)} with * {@link OrderType#STOP} and with parameters for an OTO order type. * * @param symbol symbol or asset ID to identify the asset to trade @@ -774,14 +773,14 @@ public Order requestNewOTOLimitOrder(String symbol, Integer quantity, OrderSide public Order requestNewOTOStopOrder(String symbol, Integer quantity, OrderSide side, OrderTimeInForce timeInForce, Double stopPrice, Boolean extendedHours, Double takeProfitLimitPrice, Double stopLossStopPrice, Double stopLossLimitPrice) throws AlpacaAPIRequestException { - return requestNewOrder(symbol, quantity, side, OrderType.STOP, timeInForce, null, stopPrice, extendedHours, - null, OrderClass.OTO, takeProfitLimitPrice, stopLossStopPrice, stopLossLimitPrice, null, null); + return requestNewOrder(symbol, quantity, side, OrderType.STOP, timeInForce, null, stopPrice, null, null, + extendedHours, null, OrderClass.OTO, takeProfitLimitPrice, stopLossStopPrice, stopLossLimitPrice); } /** * OTO (One-Triggers-Other) is a variant of bracket order. It takes one of the take-profit or stop-loss order in * addition to the entry order. This method calls {@link #requestNewOrder(String, Integer, OrderSide, OrderType, - * OrderTimeInForce, Double, Double, Boolean, String, OrderClass, Double, Double, Double, Double, Double)} with + * OrderTimeInForce, Double, Double, Double, Double, Boolean, String, OrderClass, Double, Double, Double)} with * {@link OrderType#STOP_LIMIT} and with parameters for an OTO order type. * * @param symbol symbol or asset ID to identify the asset to trade @@ -807,8 +806,8 @@ public Order requestNewOTOStopLimitOrder(String symbol, Integer quantity, OrderS Double takeProfitLimitPrice, Double stopLossStopPrice, Double stopLossLimitPrice) throws AlpacaAPIRequestException { return requestNewOrder(symbol, quantity, side, OrderType.STOP_LIMIT, timeInForce, limitPrice, stopPrice, - extendedHours, null, OrderClass.OTO, takeProfitLimitPrice, stopLossStopPrice, stopLossLimitPrice, null, - null); + null, null, extendedHours, null, OrderClass.OTO, takeProfitLimitPrice, + stopLossStopPrice, stopLossLimitPrice); } /** @@ -877,7 +876,7 @@ public Order getOrderByClientID(String clientOrderId) throws AlpacaAPIRequestExc * @param timeInForce day, gtc, opg, cls, ioc, fok. Please see Understand Orders for more info. * @param limitPrice required if type is limit or stop_limit * @param stopPrice required if type is stop or stop_limit - * @param trail required if type is trail_stop + * @param trail the new value of the trail_price or trail_percent value (works only for type=trailing_stop) * @param clientOrderId A unique identifier for the order. Automatically generated if not sent. * * @return the order @@ -911,7 +910,7 @@ public Order replaceOrder(String orderID, Integer quantity, OrderTimeInForce tim TimeUtil.toDecimalFormat(stopPrice)); } - if(trail != null) { + if (trail != null) { urlBuilder.appendJSONBodyProperty(AlpacaConstants.TRAIL_PARAMETER, TimeUtil.toDecimalFormat(trail)); } diff --git a/src/main/java/net/jacobpeterson/util/time/TimeUtil.java b/src/main/java/net/jacobpeterson/util/time/TimeUtil.java index 0ccaa36a..9dc3e4a9 100644 --- a/src/main/java/net/jacobpeterson/util/time/TimeUtil.java +++ b/src/main/java/net/jacobpeterson/util/time/TimeUtil.java @@ -18,14 +18,14 @@ public class TimeUtil { /** The constant PREV_UNIX_EPOCH_NANO_TIME. */ private static final long PREV_UNIX_EPOCH_MICRO_TIME = PREV_UNIX_EPOCH_NANO_TIME / 1000L; /** The date time formatter. */ - private static DateTimeFormatter inputDateTimeFormatter = DateTimeFormatter.ofPattern( + private static final DateTimeFormatter INPUT_DATE_TIME_FORMATTER = DateTimeFormatter.ofPattern( "[yyyyMMdd][yyyy-MM-dd][yyyy-DDD]['T'[HHmmss][HHmm][HH:mm:ss][HH:mm][.SSSSSSSSS][.SSSSSSSS][.SSSSSSS][" + ".SSSSSS][.SSSSS][.SSS][.SS][.S]][OOOO][O][z][XXXXX][XXXX]['['VV']']"); /** The output date time formatter. */ - private static DateTimeFormatter outputDateTimeFormatter = + private static final DateTimeFormatter OUTPUT_DATE_TIME_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss'Z'").withZone(ZoneId.of("UTC")); /** The number formatter. */ - private static NumberFormat numberFormatter = new DecimalFormat("#0.00"); + private static final NumberFormat NUMBER_FORMATTER = new DecimalFormat("#0.00"); /** * To date time string. @@ -39,7 +39,7 @@ public static String toDateTimeString(LocalDateTime ldt) { ZonedDateTime localTimeZoned = ldtZoned.withZoneSameInstant(ZoneId.of("UTC")); - return outputDateTimeFormatter.format(localTimeZoned); + return OUTPUT_DATE_TIME_FORMATTER.format(localTimeZoned); } /** @@ -50,7 +50,7 @@ public static String toDateTimeString(LocalDateTime ldt) { * @return the string */ public static String toDecimalFormat(Number numberToFormat) { - return numberFormatter.format(numberToFormat); + return NUMBER_FORMATTER.format(numberToFormat); } /** @@ -72,7 +72,7 @@ public static String toDateString(LocalDate ld) { * @return the local date time using the system time zone */ public static LocalDateTime fromDateTimeString(String dateTimeString) { - LocalDateTime ldt = LocalDateTime.parse(dateTimeString, inputDateTimeFormatter); + LocalDateTime ldt = LocalDateTime.parse(dateTimeString, INPUT_DATE_TIME_FORMATTER); ZonedDateTime ldtZoned; From 7a03e56fb7ca93da9c9bb7cbc02158c37af001dd Mon Sep 17 00:00:00 2001 From: Jacob Peterson Date: Tue, 15 Sep 2020 13:30:17 -0600 Subject: [PATCH 016/271] Rename TimeUtil to FormatUtil --- .../net/jacobpeterson/alpaca/AlpacaAPI.java | 22 +++++++++---------- .../net/jacobpeterson/polygon/PolygonAPI.java | 14 ++++++------ .../TimeUtil.java => format/FormatUtil.java} | 6 ++--- 3 files changed, 21 insertions(+), 21 deletions(-) rename src/main/java/net/jacobpeterson/util/{time/TimeUtil.java => format/FormatUtil.java} (97%) diff --git a/src/main/java/net/jacobpeterson/alpaca/AlpacaAPI.java b/src/main/java/net/jacobpeterson/alpaca/AlpacaAPI.java index cbae655a..1ac42a87 100644 --- a/src/main/java/net/jacobpeterson/alpaca/AlpacaAPI.java +++ b/src/main/java/net/jacobpeterson/alpaca/AlpacaAPI.java @@ -40,7 +40,7 @@ import net.jacobpeterson.domain.alpaca.position.Position; import net.jacobpeterson.domain.alpaca.watchlist.Watchlist; import net.jacobpeterson.util.gson.GsonUtil; -import net.jacobpeterson.util.time.TimeUtil; +import net.jacobpeterson.util.format.FormatUtil; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; @@ -424,22 +424,22 @@ public Order requestNewOrder(String symbol, Integer quantity, OrderSide side, Or if (limitPrice != null) { urlBuilder.appendJSONBodyProperty(AlpacaConstants.LIMIT_PRICE_PARAMETER, - TimeUtil.toDecimalFormat(limitPrice)); + FormatUtil.toDecimalFormat(limitPrice)); } if (stopPrice != null) { urlBuilder.appendJSONBodyProperty(AlpacaConstants.STOP_PRICE_PARAMETER, - TimeUtil.toDecimalFormat(stopPrice)); + FormatUtil.toDecimalFormat(stopPrice)); } if (trailPrice != null) { urlBuilder.appendJSONBodyProperty(AlpacaConstants.TRAIL_PRICE_PARAMETER, - TimeUtil.toDecimalFormat(trailPrice)); + FormatUtil.toDecimalFormat(trailPrice)); } if (trailPercent != null) { urlBuilder.appendJSONBodyProperty(AlpacaConstants.TRAIL_PERCENT_PARAMETER, - TimeUtil.toDecimalFormat(trailPercent)); + FormatUtil.toDecimalFormat(trailPercent)); } if (extendedHours != null) { @@ -902,17 +902,17 @@ public Order replaceOrder(String orderID, Integer quantity, OrderTimeInForce tim if (limitPrice != null) { urlBuilder.appendJSONBodyProperty(AlpacaConstants.LIMIT_PRICE_PARAMETER, - TimeUtil.toDecimalFormat(limitPrice)); + FormatUtil.toDecimalFormat(limitPrice)); } if (stopPrice != null) { urlBuilder.appendJSONBodyProperty(AlpacaConstants.STOP_PRICE_PARAMETER, - TimeUtil.toDecimalFormat(stopPrice)); + FormatUtil.toDecimalFormat(stopPrice)); } if (trail != null) { urlBuilder.appendJSONBodyProperty(AlpacaConstants.TRAIL_PARAMETER, - TimeUtil.toDecimalFormat(trail)); + FormatUtil.toDecimalFormat(trail)); } if (clientOrderId != null) { @@ -1382,7 +1382,7 @@ public PortfolioHistory getPortfolioHistory(Integer periodLength, PortfolioPerio } if (dateEnd != null) { - urlBuilder.appendURLParameter(AlpacaConstants.DATE_END_PARAMETER, TimeUtil.toDateString(dateEnd)); + urlBuilder.appendURLParameter(AlpacaConstants.DATE_END_PARAMETER, FormatUtil.toDateString(dateEnd)); } if (extendedHours != null) { @@ -1437,11 +1437,11 @@ public ArrayList getCalendar(LocalDate start, LocalDate end) throws Al AlpacaConstants.CALENDAR_ENDPOINT); if (start != null) { - urlBuilder.appendURLParameter(AlpacaConstants.START_PARAMETER, TimeUtil.toDateString(start)); + urlBuilder.appendURLParameter(AlpacaConstants.START_PARAMETER, FormatUtil.toDateString(start)); } if (end != null) { - urlBuilder.appendURLParameter(AlpacaConstants.END_PARAMETER, TimeUtil.toDateString(end)); + urlBuilder.appendURLParameter(AlpacaConstants.END_PARAMETER, FormatUtil.toDateString(end)); } HttpResponse response = alpacaRequest.invokeGet(urlBuilder); diff --git a/src/main/java/net/jacobpeterson/polygon/PolygonAPI.java b/src/main/java/net/jacobpeterson/polygon/PolygonAPI.java index 581af412..e9095917 100644 --- a/src/main/java/net/jacobpeterson/polygon/PolygonAPI.java +++ b/src/main/java/net/jacobpeterson/polygon/PolygonAPI.java @@ -42,7 +42,7 @@ import net.jacobpeterson.polygon.rest.exception.PolygonAPIRequestException; import net.jacobpeterson.polygon.websocket.client.PolygonWebsocketClient; import net.jacobpeterson.polygon.websocket.listener.PolygonStreamListener; -import net.jacobpeterson.util.time.TimeUtil; +import net.jacobpeterson.util.format.FormatUtil; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; @@ -504,7 +504,7 @@ public HistoricTradesResponse getHistoricTrades(String ticker, LocalDate date, L PolygonConstants.STOCKS_ENDPOINT, PolygonConstants.TRADES_ENDPOINT, ticker, - TimeUtil.toDateString(date)); + FormatUtil.toDateString(date)); if (timestamp != null) { builder.appendURLParameter(PolygonConstants.TIMESTAMP_PARAMETER, String.valueOf(timestamp)); @@ -559,7 +559,7 @@ public HistoricQuotesResponse getHistoricQuotes(String ticker, LocalDate date, L PolygonConstants.STOCKS_ENDPOINT, PolygonConstants.NBBO_ENDPOINT, ticker, - TimeUtil.toDateString(date)); + FormatUtil.toDateString(date)); if (timestamp != null) { builder.appendURLParameter(PolygonConstants.TIMESTAMP_PARAMETER, String.valueOf(timestamp)); @@ -658,7 +658,7 @@ public DailyOpenCloseResponse getDailyOpenClose(String symbol, LocalDate date) t PolygonRequestBuilder builder = new PolygonRequestBuilder(baseAPIURL, PolygonConstants.VERSION_1_ENDPOINT, PolygonConstants.OPEN_CLOSE_ENDPOINT, symbol, - TimeUtil.toDateString(date)); + FormatUtil.toDateString(date)); HttpResponse response = polygonRequest.invokeGet(builder); @@ -892,8 +892,8 @@ public AggregatesResponse getAggregates(String ticker, Integer multiplier, Times PolygonConstants.RANGE_ENDPOINT, Integer.toString((multiplier == null) ? 1 : multiplier), timeSpan.getAPIName(), - TimeUtil.toDateString(fromDate), - TimeUtil.toDateString(toDate)); + FormatUtil.toDateString(fromDate), + FormatUtil.toDateString(toDate)); if (unadjusted != null) { builder.appendURLParameter(PolygonConstants.UNADJUSTED_PARAMETER, unadjusted.toString()); @@ -936,7 +936,7 @@ public GroupedDailyResponse getGroupedDaily(String locale, Market market, LocalD locale, PolygonConstants.MARKET_ENDPOINT, market.getAPIName(), - TimeUtil.toDateString(date)); + FormatUtil.toDateString(date)); if (unadjusted != null) { builder.appendURLParameter(PolygonConstants.UNADJUSTED_PARAMETER, unadjusted.toString()); diff --git a/src/main/java/net/jacobpeterson/util/time/TimeUtil.java b/src/main/java/net/jacobpeterson/util/format/FormatUtil.java similarity index 97% rename from src/main/java/net/jacobpeterson/util/time/TimeUtil.java rename to src/main/java/net/jacobpeterson/util/format/FormatUtil.java index 9dc3e4a9..0af76a5b 100644 --- a/src/main/java/net/jacobpeterson/util/time/TimeUtil.java +++ b/src/main/java/net/jacobpeterson/util/format/FormatUtil.java @@ -1,4 +1,4 @@ -package net.jacobpeterson.util.time; +package net.jacobpeterson.util.format; import java.text.DecimalFormat; import java.text.NumberFormat; @@ -9,9 +9,9 @@ import java.time.format.DateTimeFormatter; /** - * The Class Utilities. + * The Class FormatUtil. */ -public class TimeUtil { +public class FormatUtil { /** The constant PREV_UNIX_EPOCH_NANO_TIME. */ private static final long PREV_UNIX_EPOCH_NANO_TIME = System.currentTimeMillis() * 1000000L; From 9ede1e3efbfcab59fab67e6dce73630c0f81db47 Mon Sep 17 00:00:00 2001 From: Jacob Peterson Date: Tue, 15 Sep 2020 14:00:12 -0600 Subject: [PATCH 017/271] Add Trailing Stop Order methods --- .../net/jacobpeterson/alpaca/AlpacaAPI.java | 52 ++++++++++++++++++- 1 file changed, 51 insertions(+), 1 deletion(-) diff --git a/src/main/java/net/jacobpeterson/alpaca/AlpacaAPI.java b/src/main/java/net/jacobpeterson/alpaca/AlpacaAPI.java index 1ac42a87..a0ede6d5 100644 --- a/src/main/java/net/jacobpeterson/alpaca/AlpacaAPI.java +++ b/src/main/java/net/jacobpeterson/alpaca/AlpacaAPI.java @@ -39,8 +39,8 @@ import net.jacobpeterson.domain.alpaca.position.ClosePositionOrder; import net.jacobpeterson.domain.alpaca.position.Position; import net.jacobpeterson.domain.alpaca.watchlist.Watchlist; -import net.jacobpeterson.util.gson.GsonUtil; import net.jacobpeterson.util.format.FormatUtil; +import net.jacobpeterson.util.gson.GsonUtil; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; @@ -810,6 +810,56 @@ public Order requestNewOTOStopLimitOrder(String symbol, Integer quantity, OrderS stopLossStopPrice, stopLossLimitPrice); } + /** + * Trailing stop orders allow you to continuously and automatically keep updating the stop price threshold based on + * the stock price movement. This method calls {@link #requestNewOrder(String, Integer, OrderSide, OrderType, + * OrderTimeInForce, Double, Double, Double, Double, Boolean, String, OrderClass, Double, Double, Double)} with + * {@link OrderType#TRAILING_STOP} and with parameters for a Trailing Stop Price order type. + * + * @param symbol symbol or asset ID to identify the asset to trade + * @param quantity number of shares to trade + * @param side buy or sell + * @param timeInForce day, gtc, opg, cls, ioc, fok. Please see Understand Orders for more info. + * @param trailPrice this or trail_percent is required if type is trailing_stop + * @param extendedHours (default) false. If true, order will be eligible to execute in premarket/afterhours. Only + * works with type limit and time_in_force day. + * + * @return the order + * + * @throws AlpacaAPIRequestException the alpaca api request exception + * @see Order Types + */ + public Order requestNewTrailingStopPriceOrder(String symbol, Integer quantity, OrderSide side, + OrderTimeInForce timeInForce, Double trailPrice, Boolean extendedHours) throws AlpacaAPIRequestException { + return requestNewOrder(symbol, quantity, side, OrderType.TRAILING_STOP, timeInForce, null, null, + trailPrice, null, extendedHours, null, OrderClass.OTO, null, null, null); + } + + /** + * Trailing stop orders allow you to continuously and automatically keep updating the stop price threshold based on + * the stock price movement. This method calls {@link #requestNewOrder(String, Integer, OrderSide, OrderType, + * OrderTimeInForce, Double, Double, Double, Double, Boolean, String, OrderClass, Double, Double, Double)} with + * {@link OrderType#TRAILING_STOP} and with parameters for a Trailing Stop Percent order type. + * + * @param symbol symbol or asset ID to identify the asset to trade + * @param quantity number of shares to trade + * @param side buy or sell + * @param timeInForce day, gtc, opg, cls, ioc, fok. Please see Understand Orders for more info. + * @param trailPercent this or trail_price is required if type is trailing_stop + * @param extendedHours (default) false. If true, order will be eligible to execute in premarket/afterhours. Only + * works with type limit and time_in_force day. + * + * @return the order + * + * @throws AlpacaAPIRequestException the alpaca api request exception + * @see Order Types + */ + public Order requestNewTrailingStopPercentOrder(String symbol, Integer quantity, OrderSide side, + OrderTimeInForce timeInForce, Double trailPercent, Boolean extendedHours) throws AlpacaAPIRequestException { + return requestNewOrder(symbol, quantity, side, OrderType.TRAILING_STOP, timeInForce, null, null, + null, trailPercent, extendedHours, null, OrderClass.OTO, null, null, null); + } + /** * Retrieves a single order for the given order_id. * From c37a6528a211a4ac01909ae92d43b6105dbbf83a Mon Sep 17 00:00:00 2001 From: Jacob Peterson Date: Tue, 15 Sep 2020 14:02:19 -0600 Subject: [PATCH 018/271] Update to 5.2 --- README.md | 8 ++++---- build.gradle | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 8f121c95..b6591af3 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,5 @@ -# UPDATING FROM 5.0.13 TO 5.1 -This repository has been transferred and as a result, lots of refactoring has occurred. This is because artifacts cannot be transferred in Maven Central. So, the new groupID for the project is `net.jacobpeterson` and all packages have been renamed from `io.github.mainstringargs` to `net.jacobpeterson`. Please refactor accordingly when you update from 5.0.13 to 5.1. Thanks! +# UPDATING FROM 5.0.13 TO 5.2 +This repository has been transferred and as a result, lots of refactoring has occurred. This is because artifacts cannot be transferred in Maven Central. So, the new groupID for the project is `net.jacobpeterson` and all packages have been renamed from `io.github.mainstringargs` to `net.jacobpeterson`. Please refactor accordingly when you update from 5.0.13 to 5.2. Thanks!

Maven Central Javadocs Build Status @@ -33,7 +33,7 @@ Add the following dependency to your build.gradle file: ``` dependencies { - compile "net.jacobpeterson:alpaca-java:5.1-SNAPSHOT" + compile "net.jacobpeterson:alpaca-java:5.2" } ``` @@ -45,7 +45,7 @@ Add the following dependency to your pom.xml file: net.jacobpeterson alpaca-java - 5.1-SNAPSHOT + 5.2 compile ``` diff --git a/build.gradle b/build.gradle index fb1c2271..7254014d 100644 --- a/build.gradle +++ b/build.gradle @@ -27,7 +27,7 @@ tasks.getByName("generateJsonSchema2Pojo").enabled(false) // Disable default jso archivesBaseName = 'alpaca-java' group = 'net.jacobpeterson' -version = '5.1-SNAPSHOT' +version = '5.2' repositories { mavenLocal() From 7c58e9048b29d59cf96ac108193bdee112529e19 Mon Sep 17 00:00:00 2001 From: foobar Date: Thu, 27 Aug 2020 14:50:43 -0400 Subject: [PATCH 019/271] Reconnect when a 1006 occurs --- .../AbstractWebsocketClientEndpoint.java | 44 +++++++++++++------ .../client/AlpacaWebsocketClient.java | 12 ++--- .../client/PolygonWebsocketClient.java | 36 +++++++-------- 3 files changed, 53 insertions(+), 39 deletions(-) diff --git a/src/main/java/net/jacobpeterson/abstracts/websocket/client/AbstractWebsocketClientEndpoint.java b/src/main/java/net/jacobpeterson/abstracts/websocket/client/AbstractWebsocketClientEndpoint.java index 81a13fd2..5cfa287a 100644 --- a/src/main/java/net/jacobpeterson/abstracts/websocket/client/AbstractWebsocketClientEndpoint.java +++ b/src/main/java/net/jacobpeterson/abstracts/websocket/client/AbstractWebsocketClientEndpoint.java @@ -25,7 +25,7 @@ public abstract class AbstractWebsocketClientEndpoint { private static final Logger LOGGER = LogManager.getLogger(AbstractWebsocketClientEndpoint.class); /** The Websocket client. */ - private WebsocketClient websocketClient; + private final WebsocketClient websocketClient; /** The Endpoint uri. */ private final URI endpointURI; @@ -39,6 +39,8 @@ public abstract class AbstractWebsocketClientEndpoint { /** The Retry attempts. */ private int retryAttempts = 0; + private String subscription; + /** * Instantiates a new Abstract websocket client endpoint. * @@ -62,7 +64,7 @@ public AbstractWebsocketClientEndpoint(WebsocketClient websocketClient, URI endp public void connect() throws DeploymentException, IOException { WebSocketContainer container = ContainerProvider.getWebSocketContainer(); - LOGGER.info("Connecting to " + endpointURI); + LOGGER.info("Connecting to {}", endpointURI); container.connectToServer(this, endpointURI); } @@ -86,10 +88,8 @@ public void disconnect() throws IOException { protected void onOpen(Session userSession) { this.userSession = userSession; - LOGGER.debug("onOpen " + userSession); - LOGGER.info("Websocket opened"); - - LOGGER.info("Authenticating..."); + LOGGER.debug("onOpen {}", userSession); + LOGGER.info("Websocket opened... Authenticating..."); websocketClient.sendAuthenticationMessage(); } @@ -100,13 +100,12 @@ protected void onOpen(Session userSession) { * @param reason the reason */ protected void onClose(Session userSession, CloseReason reason) { - this.userSession = null; - LOGGER.debug("onClose " + userSession); + LOGGER.debug("onClose {}", userSession); if (!reason.getCloseCode().equals(CloseReason.CloseCodes.NORMAL_CLOSURE)) { if (retryAttempts > 5) { - LOGGER.error("More than 5 attempts to reconnect were made."); + LOGGER.error("More than 5 attempts to reconnect were made. Bailing out."); return; } @@ -119,19 +118,28 @@ protected void onClose(Session userSession, CloseReason reason) { e1.printStackTrace(); } - LOGGER.info("Reconnecting due to closure " + + LOGGER.info("Reconnecting due to closure: {}", CloseReason.CloseCodes.getCloseCode(reason.getCloseCode().getCode())); try { - connect(); + reconnectAndResubscribe(); + retryAttempts = 0; // hopefully we are connected by this time, so reset the retry counter } catch (Exception e) { LOGGER.catching(e); } } else { + this.userSession = null; LOGGER.info("Websocket closed"); } } + private void reconnectAndResubscribe() throws IOException, DeploymentException { + LOGGER.info("Attempting to re-connect"); + connect(); + LOGGER.info("Resending subscriptions: {}", subscription); + sendSubscription(subscription); + } + /** * On message. * @@ -146,12 +154,20 @@ protected void onMessage(String message) { * * @param message the message */ - public void sendMessage(String message) { - LOGGER.debug("sendMessage " + message); - + private void sendMessage(String message) { + LOGGER.debug("sendMessage {}", message); userSession.getAsyncRemote().sendText(message); } + public void sendAuthentication(String message) { + sendMessage(message); + } + + public void sendSubscription(String message) { + this.subscription = message; + sendMessage(message); + } + /** * Gets user session. * diff --git a/src/main/java/net/jacobpeterson/alpaca/websocket/client/AlpacaWebsocketClient.java b/src/main/java/net/jacobpeterson/alpaca/websocket/client/AlpacaWebsocketClient.java index a7fb00ba..d4f0a8a6 100644 --- a/src/main/java/net/jacobpeterson/alpaca/websocket/client/AlpacaWebsocketClient.java +++ b/src/main/java/net/jacobpeterson/alpaca/websocket/client/AlpacaWebsocketClient.java @@ -43,16 +43,16 @@ public class AlpacaWebsocketClient implements WebsocketClient { private static final String STREAM_KEY = "stream"; /** The key id. */ - private String keyId; + private final String keyId; /** The secret. */ - private String secret; + private final String secret; /** The Base api url. */ - private String baseAPIURL; + private final String baseAPIURL; /** The observers. */ - private List listeners; + private final List listeners; /** The client end point. */ private AlpacaWebsocketClientEndpoint alpacaWebsocketClientEndpoint; @@ -149,7 +149,7 @@ public void sendAuthenticationMessage() { authRequest.add("data", payload); - alpacaWebsocketClientEndpoint.sendMessage(authRequest.toString()); + alpacaWebsocketClientEndpoint.sendAuthentication(authRequest.toString()); } @Override @@ -265,7 +265,7 @@ private void submitStreamRequestUpdate() { streamRequestJsonObject.add("data", dataJsonObject); - alpacaWebsocketClientEndpoint.sendMessage(streamRequestJsonObject.toString()); + alpacaWebsocketClientEndpoint.sendSubscription(streamRequestJsonObject.toString()); LOGGER.info("Requested subscriptions to update to " + streamsJsonArray); } diff --git a/src/main/java/net/jacobpeterson/polygon/websocket/client/PolygonWebsocketClient.java b/src/main/java/net/jacobpeterson/polygon/websocket/client/PolygonWebsocketClient.java index 31ffe3f9..3e240b25 100644 --- a/src/main/java/net/jacobpeterson/polygon/websocket/client/PolygonWebsocketClient.java +++ b/src/main/java/net/jacobpeterson/polygon/websocket/client/PolygonWebsocketClient.java @@ -50,13 +50,13 @@ public class PolygonWebsocketClient implements WebsocketClient { private static final String EVENT_TYPE_KEY = "ev"; /** The key id. */ - private String keyId; + private final String keyId; /** The Websocket url. */ - private String websocketURL; + private final String websocketURL; /** The listeners. */ - private List listeners; + private final List listeners; /** The client end point. */ private PolygonWebsocketClientEndpoint polygonWebsocketClientEndpoint; @@ -122,8 +122,7 @@ public void disconnect() { LOGGER.info("Disconnecting..."); try { - polygonWebsocketClientEndpoint.getUserSession().close(); - + polygonWebsocketClientEndpoint.disconnect(); LOGGER.info("Disconnected."); } catch (IOException e) { LOGGER.throwing(e); @@ -138,7 +137,7 @@ public void sendAuthenticationMessage() { authRequest.addProperty("action", "auth"); authRequest.addProperty("params", keyId); - polygonWebsocketClientEndpoint.sendMessage(authRequest.toString()); + polygonWebsocketClientEndpoint.sendAuthentication(authRequest.toString()); } @Override @@ -183,13 +182,13 @@ public void handleWebsocketMessage(String message) { GsonUtil.GSON.fromJson(messageJsonObject, AggregatePerMinuteMessage.class)); break; default: - LOGGER.error("Unknown stream object: " + messageJsonObject); + LOGGER.error("Unknown stream object: {}", messageJsonObject); } } catch (JsonSyntaxException e) { LOGGER.throwing(e); } } else { - LOGGER.error("Unknown stream message: " + messageJsonObject); + LOGGER.error("Unknown stream message: {}", messageJsonObject); } } } @@ -274,20 +273,19 @@ private void submitStreamRequest(PolygonStreamAction polygonStreamAction, Polygo if (isTickerChannelRegistered) { if (polygonStreamAction == PolygonStreamAction.UNSUBSCRIBE) { - LOGGER.warn("Cannot unsubscribe from " + ticker + " for channel " + - listenerChannelType.name() + - " because it is being used by another stream listener!"); + LOGGER.warn( + "Cannot unsubscribe from {} for channel {} because it is being used by another stream listener!", + ticker, listenerChannelType.name()); } else if (polygonStreamAction == PolygonStreamAction.SUBSCRIBE) { - LOGGER.warn("Already subscribed to " + ticker + " for channel " + - listenerChannelType.name() + "!"); + LOGGER.warn("Already subscribed to {} for channel {}", ticker, listenerChannelType.name()); } } else { // Not a registered channel by other stream listeners actionTickerList.add(formattedWebsocketTicker); if (polygonStreamAction == PolygonStreamAction.UNSUBSCRIBE) { - LOGGER.info(("Unsubscribing from " + formattedWebsocketTicker)); + LOGGER.info("Unsubscribing from {}", formattedWebsocketTicker); } else if (polygonStreamAction == PolygonStreamAction.SUBSCRIBE) { - LOGGER.info(("Subscribing to " + formattedWebsocketTicker)); + LOGGER.info("Subscribing to {}", formattedWebsocketTicker); } } } @@ -302,9 +300,9 @@ private void submitStreamRequest(PolygonStreamAction polygonStreamAction, Polygo actionTickerList.add(formattedWebsocketTicker); if (polygonStreamAction == PolygonStreamAction.UNSUBSCRIBE) { - LOGGER.info(("Unsubscribing from " + formattedWebsocketTicker)); + LOGGER.info("Unsubscribing from: {}", formattedWebsocketTicker); } else if (polygonStreamAction == PolygonStreamAction.SUBSCRIBE) { - LOGGER.info(("Subscribing to " + formattedWebsocketTicker)); + LOGGER.info("Subscribing to: {}", formattedWebsocketTicker); } } } @@ -322,9 +320,9 @@ private void submitStreamRequest(PolygonStreamAction polygonStreamAction, Polygo actionJsonObject.addProperty("action", polygonStreamAction.getAPIName()); actionJsonObject.addProperty("params", commaActionTickers.toString()); - polygonWebsocketClientEndpoint.sendMessage(actionJsonObject.toString()); + polygonWebsocketClientEndpoint.sendSubscription(actionJsonObject.toString()); - LOGGER.info(("Requested subscriptions to update to " + getRegisteredTickerChannels(null))); + LOGGER.info("Requested subscriptions to update for: {}", getRegisteredTickerChannels(null)); } } From 3c287b85a85eadc462f549a4f5ecd4b38c0bb0d7 Mon Sep 17 00:00:00 2001 From: Jesse Thouin Date: Tue, 29 Sep 2020 13:01:28 -0400 Subject: [PATCH 020/271] Updated package names; Fixed MarketDataWebsocketClient.getRegisteredMessageTypes; --- README.md | 46 +++++----- build.gradle | 10 +-- schema_json/alpaca/account/account.json | 2 +- .../non_trade_activity.json | 2 +- .../account_activities/trade_activity.json | 2 +- .../market_data/last_quote_response.json | 2 +- .../market_data/last_trade_response.json | 2 +- .../market_data_stream_data_message.json | 4 +- .../market_data_stream_status_message.json | 4 +- .../aggregate/aggregate_minute_message.json | 2 +- .../authorization/authorization_message.json | 4 +- .../listening/listening_message.json | 4 +- .../streaming/market_data_stream_message.json | 2 +- .../streaming/quote/quote_message.json | 2 +- .../streaming/trade/trade_message.json | 2 +- schema_json/alpaca/order/cancelled_order.json | 2 +- schema_json/alpaca/order/order.json | 2 +- .../portfolio_history/portfolio_history.json | 2 +- .../alpaca/position/close_position_order.json | 2 +- .../streaming/account/account_update.json | 2 +- .../account/account_update_message.json | 4 +- .../streaming/alpaca_stream_message.json | 4 +- .../authorization/authorization_message.json | 4 +- .../listening/listening_message.json | 4 +- .../alpaca/streaming/trade/trade_update.json | 2 +- .../streaming/trade/trade_update_message.json | 4 +- schema_json/alpaca/watchlist/watchlist.json | 2 +- .../aggregates/aggregates_response.json | 2 +- .../daily_open_close_response.json | 6 +- .../grouped_daily/grouped_daily_response.json | 2 +- .../historic_quotes_response.json | 2 +- .../historic_trades_response.json | 2 +- .../last_quote/last_quote_response.json | 2 +- .../last_trade/last_trade_response.json | 2 +- .../polygon/locales/locales_response.json | 2 +- .../polygon/markets/markets_response.json | 2 +- .../previous_close_response.json | 2 +- .../snapshot_all_tickers_response.json | 2 +- .../snapshot_gainers_losers_response.json | 2 +- .../snapshot_single_ticker_response.json | 2 +- .../snapshot/snapshot_ticker_book.json | 2 +- .../snapshot/ticker/book/book_data.json | 4 +- .../snapshot/ticker/snapshot_ticker.json | 10 +-- .../stock_dividends_response.json | 2 +- .../stock_financials_response.json | 2 +- .../stock_splits/stock_splits_response.json | 2 +- .../ticker_types/ticket_types_response.json | 2 +- .../polygon/tickers/ticker/forex_ticker.json | 2 +- .../polygon/tickers/ticker/stock_ticker.json | 2 +- .../polygon/tickers/tickers_response.json | 2 +- .../aggregate/aggregate_message.json | 2 +- .../aggregate_per_minute_message.json | 2 +- .../aggregate_per_second_message.json | 2 +- .../websocket/polygon_stream_message.json | 4 +- .../websocket/quote/quote_message.json | 2 +- .../websocket/status/status_message.json | 2 +- .../websocket/trade/trade_message.json | 2 +- .../abstracts/enums/APIName.java | 2 +- .../abstracts/rest/AbstractRequest.java | 4 +- .../rest/AbstractRequestBuilder.java | 2 +- .../AbstractAPIRequestException.java | 4 +- .../AbstractWebsocketClientEndpoint.java | 4 +- .../websocket/client/WebsocketClient.java | 8 +- .../websocket/listener/StreamListener.java | 6 +- .../websocket/message/StreamMessage.java | 2 +- .../websocket/message/StreamMessageType.java | 2 +- .../jacobpeterson}/alpaca/AlpacaAPI.java | 88 +++++++++---------- .../alpaca/AlpacaConstants.java | 2 +- .../jacobpeterson}/alpaca/AlpacaExample.java | 28 +++--- .../alpaca/enums/AccountStatus.java | 4 +- .../alpaca/enums/ActivityType.java | 4 +- .../alpaca/enums/AssetStatus.java | 4 +- .../alpaca/enums/BarsTimeFrame.java | 4 +- .../alpaca/enums/Direction.java | 4 +- .../alpaca/enums/OrderClass.java | 4 +- .../alpaca/enums/OrderSide.java | 4 +- .../alpaca/enums/OrderStatus.java | 4 +- .../alpaca/enums/OrderTimeInForce.java | 4 +- .../alpaca/enums/OrderType.java | 4 +- .../alpaca/enums/PortfolioPeriodUnit.java | 4 +- .../alpaca/enums/PortfolioTimeFrame.java | 6 +- .../alpaca/properties/AlpacaProperties.java | 4 +- .../alpaca/rest/AlpacaRequest.java | 6 +- .../alpaca/rest/AlpacaRequestBuilder.java | 4 +- .../exception/AlpacaAPIRequestException.java | 4 +- .../broker/client/AlpacaWebsocketClient.java | 26 +++--- .../client/AlpacaWebsocketClientEndpoint.java | 6 +- .../broker/listener/AlpacaStreamListener.java | 8 +- .../listener/AlpacaStreamListenerAdapter.java | 6 +- .../message/AlpacaStreamMessageType.java | 8 +- .../client/MarketDataWebsocketClient.java | 46 +++++----- .../MarketDataWebsocketClientEndpoint.java | 6 +- .../listener/MarketDataStreamListener.java | 8 +- .../MarketDataStreamListenerAdapter.java | 6 +- .../message/MarketDataStreamMessageType.java | 8 +- .../jacobpeterson}/polygon/PolygonAPI.java | 86 +++++++++--------- .../polygon/PolygonConstants.java | 2 +- .../polygon/PolygonExample.java | 20 ++--- .../polygon/enums/ConditionMappingsType.java | 4 +- .../polygon/enums/FinancialReportType.java | 4 +- .../polygon/enums/FinancialSort.java | 4 +- .../polygon/enums/GainersLosersDirection.java | 4 +- .../jacobpeterson}/polygon/enums/Market.java | 4 +- .../polygon/enums/StockType.java | 4 +- .../polygon/enums/TickerSort.java | 4 +- .../polygon/enums/Timespan.java | 4 +- .../polygon/properties/PolygonProperties.java | 6 +- .../polygon/rest/PolygonRequest.java | 8 +- .../polygon/rest/PolygonRequestBuilder.java | 4 +- .../exception/PolygonAPIRequestException.java | 4 +- .../client/PolygonWebsocketClient.java | 28 +++--- .../PolygonWebsocketClientEndpoint.java | 6 +- .../listener/PolygonStreamListener.java | 8 +- .../PolygonStreamListenerAdapter.java | 6 +- .../message/PolygonStreamMessageType.java | 8 +- .../util/concurrency/ExecutorTracer.java | 2 +- .../ScheduledThreadPoolExecutorTracer.java | 2 +- .../concurrency/ThreadPoolExecutorTracer.java | 2 +- .../util/doc/Documentation2JSONSchema.java | 2 +- .../jacobpeterson}/util/gson/GsonUtil.java | 2 +- .../util/gson/ZonedDateTimeAdapter.java | 2 +- .../util/properties/PropertyUtil.java | 2 +- .../jacobpeterson}/util/time/TimeUtil.java | 2 +- 123 files changed, 391 insertions(+), 391 deletions(-) rename src/main/java/{io/github/mainstringargs => net/jacobpeterson}/abstracts/enums/APIName.java (72%) rename src/main/java/{io/github/mainstringargs => net/jacobpeterson}/abstracts/rest/AbstractRequest.java (98%) rename src/main/java/{io/github/mainstringargs => net/jacobpeterson}/abstracts/rest/AbstractRequestBuilder.java (98%) rename src/main/java/{io/github/mainstringargs => net/jacobpeterson}/abstracts/rest/exception/AbstractAPIRequestException.java (97%) rename src/main/java/{io/github/mainstringargs => net/jacobpeterson}/abstracts/websocket/client/AbstractWebsocketClientEndpoint.java (97%) rename src/main/java/{io/github/mainstringargs => net/jacobpeterson}/abstracts/websocket/client/WebsocketClient.java (80%) rename src/main/java/{io/github/mainstringargs => net/jacobpeterson}/abstracts/websocket/listener/StreamListener.java (67%) rename src/main/java/{io/github/mainstringargs => net/jacobpeterson}/abstracts/websocket/message/StreamMessage.java (53%) rename src/main/java/{io/github/mainstringargs => net/jacobpeterson}/abstracts/websocket/message/StreamMessageType.java (56%) rename src/main/java/{io/github/mainstringargs => net/jacobpeterson}/alpaca/AlpacaAPI.java (96%) rename src/main/java/{io/github/mainstringargs => net/jacobpeterson}/alpaca/AlpacaConstants.java (99%) rename src/main/java/{io/github/mainstringargs => net/jacobpeterson}/alpaca/AlpacaExample.java (80%) rename src/main/java/{io/github/mainstringargs => net/jacobpeterson}/alpaca/enums/AccountStatus.java (90%) rename src/main/java/{io/github/mainstringargs => net/jacobpeterson}/alpaca/enums/ActivityType.java (94%) rename src/main/java/{io/github/mainstringargs => net/jacobpeterson}/alpaca/enums/AssetStatus.java (82%) rename src/main/java/{io/github/mainstringargs => net/jacobpeterson}/alpaca/enums/BarsTimeFrame.java (86%) rename src/main/java/{io/github/mainstringargs => net/jacobpeterson}/alpaca/enums/Direction.java (81%) rename src/main/java/{io/github/mainstringargs => net/jacobpeterson}/alpaca/enums/OrderClass.java (87%) rename src/main/java/{io/github/mainstringargs => net/jacobpeterson}/alpaca/enums/OrderSide.java (81%) rename src/main/java/{io/github/mainstringargs => net/jacobpeterson}/alpaca/enums/OrderStatus.java (82%) rename src/main/java/{io/github/mainstringargs => net/jacobpeterson}/alpaca/enums/OrderTimeInForce.java (86%) rename src/main/java/{io/github/mainstringargs => net/jacobpeterson}/alpaca/enums/OrderType.java (84%) rename src/main/java/{io/github/mainstringargs => net/jacobpeterson}/alpaca/enums/PortfolioPeriodUnit.java (85%) rename src/main/java/{io/github/mainstringargs => net/jacobpeterson}/alpaca/enums/PortfolioTimeFrame.java (80%) rename src/main/java/{io/github/mainstringargs => net/jacobpeterson}/alpaca/properties/AlpacaProperties.java (95%) rename src/main/java/{io/github/mainstringargs => net/jacobpeterson}/alpaca/rest/AlpacaRequest.java (81%) rename src/main/java/{io/github/mainstringargs => net/jacobpeterson}/alpaca/rest/AlpacaRequestBuilder.java (84%) rename src/main/java/{io/github/mainstringargs => net/jacobpeterson}/alpaca/rest/exception/AlpacaAPIRequestException.java (79%) rename src/main/java/{io/github/mainstringargs => net/jacobpeterson}/alpaca/websocket/broker/client/AlpacaWebsocketClient.java (90%) rename src/main/java/{io/github/mainstringargs => net/jacobpeterson}/alpaca/websocket/broker/client/AlpacaWebsocketClientEndpoint.java (83%) rename src/main/java/{io/github/mainstringargs => net/jacobpeterson}/alpaca/websocket/broker/listener/AlpacaStreamListener.java (60%) rename src/main/java/{io/github/mainstringargs => net/jacobpeterson}/alpaca/websocket/broker/listener/AlpacaStreamListenerAdapter.java (80%) rename src/main/java/{io/github/mainstringargs => net/jacobpeterson}/alpaca/websocket/broker/message/AlpacaStreamMessageType.java (83%) rename src/main/java/{io/github/mainstringargs => net/jacobpeterson}/alpaca/websocket/marketdata/client/MarketDataWebsocketClient.java (85%) rename src/main/java/{io/github/mainstringargs => net/jacobpeterson}/alpaca/websocket/marketdata/client/MarketDataWebsocketClientEndpoint.java (84%) rename src/main/java/{io/github/mainstringargs => net/jacobpeterson}/alpaca/websocket/marketdata/listener/MarketDataStreamListener.java (60%) rename src/main/java/{io/github/mainstringargs => net/jacobpeterson}/alpaca/websocket/marketdata/listener/MarketDataStreamListenerAdapter.java (89%) rename src/main/java/{io/github/mainstringargs => net/jacobpeterson}/alpaca/websocket/marketdata/message/MarketDataStreamMessageType.java (83%) rename src/main/java/{io/github/mainstringargs => net/jacobpeterson}/polygon/PolygonAPI.java (91%) rename src/main/java/{io/github/mainstringargs => net/jacobpeterson}/polygon/PolygonConstants.java (99%) rename src/main/java/{io/github/mainstringargs => net/jacobpeterson}/polygon/PolygonExample.java (77%) rename src/main/java/{io/github/mainstringargs => net/jacobpeterson}/polygon/enums/ConditionMappingsType.java (83%) rename src/main/java/{io/github/mainstringargs => net/jacobpeterson}/polygon/enums/FinancialReportType.java (87%) rename src/main/java/{io/github/mainstringargs => net/jacobpeterson}/polygon/enums/FinancialSort.java (88%) rename src/main/java/{io/github/mainstringargs => net/jacobpeterson}/polygon/enums/GainersLosersDirection.java (80%) rename src/main/java/{io/github/mainstringargs => net/jacobpeterson}/polygon/enums/Market.java (86%) rename src/main/java/{io/github/mainstringargs => net/jacobpeterson}/polygon/enums/StockType.java (81%) rename src/main/java/{io/github/mainstringargs => net/jacobpeterson}/polygon/enums/TickerSort.java (85%) rename src/main/java/{io/github/mainstringargs => net/jacobpeterson}/polygon/enums/Timespan.java (86%) rename src/main/java/{io/github/mainstringargs => net/jacobpeterson}/polygon/properties/PolygonProperties.java (91%) rename src/main/java/{io/github/mainstringargs => net/jacobpeterson}/polygon/rest/PolygonRequest.java (90%) rename src/main/java/{io/github/mainstringargs => net/jacobpeterson}/polygon/rest/PolygonRequestBuilder.java (84%) rename src/main/java/{io/github/mainstringargs => net/jacobpeterson}/polygon/rest/exception/PolygonAPIRequestException.java (79%) rename src/main/java/{io/github/mainstringargs => net/jacobpeterson}/polygon/websocket/client/PolygonWebsocketClient.java (93%) rename src/main/java/{io/github/mainstringargs => net/jacobpeterson}/polygon/websocket/client/PolygonWebsocketClientEndpoint.java (83%) rename src/main/java/{io/github/mainstringargs => net/jacobpeterson}/polygon/websocket/listener/PolygonStreamListener.java (71%) rename src/main/java/{io/github/mainstringargs => net/jacobpeterson}/polygon/websocket/listener/PolygonStreamListenerAdapter.java (91%) rename src/main/java/{io/github/mainstringargs => net/jacobpeterson}/polygon/websocket/message/PolygonStreamMessageType.java (84%) rename src/main/java/{io/github/mainstringargs => net/jacobpeterson}/util/concurrency/ExecutorTracer.java (99%) rename src/main/java/{io/github/mainstringargs => net/jacobpeterson}/util/concurrency/ScheduledThreadPoolExecutorTracer.java (97%) rename src/main/java/{io/github/mainstringargs => net/jacobpeterson}/util/concurrency/ThreadPoolExecutorTracer.java (97%) rename src/main/java/{io/github/mainstringargs => net/jacobpeterson}/util/doc/Documentation2JSONSchema.java (99%) rename src/main/java/{io/github/mainstringargs => net/jacobpeterson}/util/gson/GsonUtil.java (98%) rename src/main/java/{io/github/mainstringargs => net/jacobpeterson}/util/gson/ZonedDateTimeAdapter.java (96%) rename src/main/java/{io/github/mainstringargs => net/jacobpeterson}/util/properties/PropertyUtil.java (98%) rename src/main/java/{io/github/mainstringargs => net/jacobpeterson}/util/time/TimeUtil.java (98%) diff --git a/README.md b/README.md index 58656ece..10512289 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@

-Maven Central Javadocs Build Status +Maven Central Javadocs Build Status GitHub

@@ -31,7 +31,7 @@ Add the following dependency to your build.gradle file: ``` dependencies { - compile "io.github.mainstringargs:alpaca-java:5.0.13" + compile "net.jacobpeterson:alpaca-java:5.0.13" } ``` @@ -41,7 +41,7 @@ Add the following dependency to your pom.xml file: ``` - io.github.mainstringargs + net.jacobpeterson alpaca-java 5.0.13 compile @@ -72,7 +72,7 @@ The default values for `polygon.properties` can be found [here](https://github.c ## AlpacaAPI Example -This example uses the `AlpacaAPI` class to subscribe to the Account and Trade Updates stream, print out the account information, submit a limit order, create a watchlist, and print out bars data. Click [here](https://docs.alpaca.markets/api-documentation/api-v2/) for the general Alpaca API documentation and click [here](https://javadoc.io/doc/io.github.mainstringargs/alpaca-java/latest/io/github/mainstringargs/alpaca/AlpacaAPI.html) for the `AlpacaAPI` javadoc. +This example uses the `AlpacaAPI` class to subscribe to the Account and Trade Updates stream, print out the account information, submit a limit order, create a watchlist, and print out bars data. Click [here](https://docs.alpaca.markets/api-documentation/api-v2/) for the general Alpaca API documentation and click [here](https://javadoc.io/doc/net.jacobpeterson/alpaca-java/latest/io/github/mainstringargs/alpaca/AlpacaAPI.html) for the `AlpacaAPI` javadoc. ```java // This logs into Alpaca using the alpaca.properties file on the classpath. @@ -165,7 +165,7 @@ This code will output the following: ``` Account Information: - io.github.mainstringargs.domain.alpaca.account.Account@626abbd0[id=, + Account@626abbd0[id=, accountNumber=, status=ACTIVE, currency=USD, @@ -193,7 +193,7 @@ Account Information: regtBuyingPower=92734.08] New AAPL Order: - io.github.mainstringargs.domain.alpaca.order.Order@37313c65[id=, + Order@37313c65[id=, clientOrderId=929e412f-32b6-4ddf-9729-d77a59c19ec8, createdAt=2019-11-26T03:41:10.475959154Z, updatedAt=2019-11-26T03:41:10.481465924Z, @@ -220,7 +220,7 @@ Received Order Update: OrderUpdateMessage [event=NEW, price=null, timestamp=null, - order=io.github.mainstringargs.alpaca.domain.Order@5c728415[id=, + order=net.jacobpeterson.alpaca.domain.Order@5c728415[id=, clientOrderId=e23ca503-ebec-4754-b65a-ee42806fc67d, createdAt=2019-11-23T14:41:05.503352Z, updatedAt=2019-11-23T14:41:05.599835871Z, @@ -245,12 +245,12 @@ Received Order Update: Day Trade Watchlist: - io.github.mainstringargs.domain.alpaca.watchlist.Watchlist@34bde49d[id=7c414350-79d8-4527-8892-f1667faa712a, + Watchlist@34bde49d[id=7c414350-79d8-4527-8892-f1667faa712a, createdAt=2019-11-26T03:20:31.953679Z, updatedAt=2019-11-26T03:20:31.953679Z, name=Day Trade, accountId=, - assets=[io.github.mainstringargs.domain.alpaca.asset.Asset@1b1cfb87[id=b0b6dd9d-8b9b-48a9-ba46-b9d54906e415, + assets=[Asset@1b1cfb87[id=b0b6dd9d-8b9b-48a9-ba46-b9d54906e415, _class=us_equity, exchange=NASDAQ, symbol=AAPL, @@ -300,7 +300,7 @@ Bars response: # PolygonAPI Example -This example uses the `PolygonAPI` class to subscribe to the Polygon websocket stream, get stocks splits, and get aggregates. Click [here](https://polygon.io/docs/) for the general Polygon API documentation and click [here](https://javadoc.io/doc/io.github.mainstringargs/alpaca-java/latest/io/github/mainstringargs/polygon/PolygonAPI.html) for the `PolygonAPI` javadoc. +This example uses the `PolygonAPI` class to subscribe to the Polygon websocket stream, get stocks splits, and get aggregates. Click [here](https://polygon.io/docs/) for the general Polygon API documentation and click [here](https://javadoc.io/doc/net.jacobpeterson/alpaca-java/latest/io/github/mainstringargs/polygon/PolygonAPI.html) for the `PolygonAPI` javadoc. ```java // This will use the key_id in the alpaca.properties file by default @@ -362,21 +362,21 @@ This code will output the following: [INFO ] 2019-11-25 21:33:20.489 [main] PolygonWebsocketClient - Subscribing to Q.AAPL [DEBUG] 2019-11-25 21:33:20.491 [main] PolygonWebsocketClientEndpoint - sendMessage {"action":"subscribe","params":"A.AAPL,T.AAPL,AM.AAPL,Q.AAPL"} [INFO ] 2019-11-25 21:33:20.492 [main] PolygonWebsocketClient - Subscriptions updated to {AAPL=[AGGREGATE_PER_SECOND, TRADES, AGGREGATE_PER_MINUTE, QUOTES]} -[INFO] 2019-11-25 21:33:20.508 [PolygonWebsocketThread] PolygonWebsocketClient - Channel status: io.github.mainstringargs.domain.polygon.websocket.ChannelStatus@78428bcb[ev=status,status=connected,message=Connected Successfully] -[INFO] 2019-11-25 21:33:20.667 [PolygonWebsocketThread] PolygonWebsocketClient - Channel status: io.github.mainstringargs.domain.polygon.websocket.ChannelStatus@629dc521[ev=status,status=auth_success,message=authenticated] -[INFO] 2019-11-25 21:33:20.735 [PolygonWebsocketThread] PolygonWebsocketClient - Channel status: io.github.mainstringargs.domain.polygon.websocket.ChannelStatus@7f4d9c50[ev=status,status=success,message=subscribed to: A.AAPL] -[INFO] 2019-11-25 21:33:20.737 [PolygonWebsocketThread] PolygonWebsocketClient - Channel status: io.github.mainstringargs.domain.polygon.websocket.ChannelStatus@2aa9728c[ev=status,status=success,message=subscribed to: T.AAPL] -[INFO] 2019-11-25 21:33:20.738 [PolygonWebsocketThread] PolygonWebsocketClient - Channel status: io.github.mainstringargs.domain.polygon.websocket.ChannelStatus@4659b8c1[ev=status,status=success,message=subscribed to: AM.AAPL] -[INFO] 2019-11-25 21:33:20.738 [PolygonWebsocketThread] PolygonWebsocketClient - Channel status: io.github.mainstringargs.domain.polygon.websocket.ChannelStatus@4a61d9e6[ev=status,status=success,message=subscribed to: Q.AAPL] -===> streamUpdate QUOTES QuotesMessage [ticker=AAPL, channelType=QUOTES, stockQuote=io.github.mainstringargs.polygon.domain.StockQuote@4d5b710e[ev=,sym=AAPL,bx=12,bp=124.86,bs=6,ax=12,ap=124.87,as=5,c=1,t=1559143439610], timestamp=2019-05-29T10:23:59.610] -===> streamUpdate QUOTES QuotesMessage [ticker=AAPL, channelType=QUOTES, stockQuote=io.github.mainstringargs.polygon.domain.StockQuote@16b79976[ev=,sym=AAPL,bx=12,bp=124.86,bs=4,ax=12,ap=124.87,as=5,c=1,t=1559143439712], timestamp=2019-05-29T10:23:59.712] -===> streamUpdate TRADES TradesMessage [ticker=AAPL, channelType=TRADES, stockTrade=io.github.mainstringargs.polygon.domain.StockTrade@32b4aa49[ev=,sym=AAPL,x=4,p=124.86,s=100,c=[],t=1559143439772], timestamp=2019-05-29T10:23:59.772] -===> streamUpdate AGGREGATE_PER_SECOND AggregateMessage [ticker=AAPL, channelType=AGGREGATE_PER_SECOND, stockAggregate=io.github.mainstringargs.polygon.domain.StockAggregate@5ace47f7[ev=,sym=AAPL,v=1131,av=6602041,op=125.5,vw=124.9778,o=124.8665,c=124.86,h=124.8665,l=124.86,a=124.8636,s=1559143439000,e=1559143440000], start=2019-05-29T10:23:59, end=2019-05-29T10:24] +[INFO] 2019-11-25 21:33:20.508 [PolygonWebsocketThread] PolygonWebsocketClient - Channel status: net.jacobpeterson.domain.polygon.websocket.ChannelStatus@78428bcb[ev=status,status=connected,message=Connected Successfully] +[INFO] 2019-11-25 21:33:20.667 [PolygonWebsocketThread] PolygonWebsocketClient - Channel status: net.jacobpeterson.domain.polygon.websocket.ChannelStatus@629dc521[ev=status,status=auth_success,message=authenticated] +[INFO] 2019-11-25 21:33:20.735 [PolygonWebsocketThread] PolygonWebsocketClient - Channel status: net.jacobpeterson.domain.polygon.websocket.ChannelStatus@7f4d9c50[ev=status,status=success,message=subscribed to: A.AAPL] +[INFO] 2019-11-25 21:33:20.737 [PolygonWebsocketThread] PolygonWebsocketClient - Channel status: net.jacobpeterson.domain.polygon.websocket.ChannelStatus@2aa9728c[ev=status,status=success,message=subscribed to: T.AAPL] +[INFO] 2019-11-25 21:33:20.738 [PolygonWebsocketThread] PolygonWebsocketClient - Channel status: net.jacobpeterson.domain.polygon.websocket.ChannelStatus@4659b8c1[ev=status,status=success,message=subscribed to: AM.AAPL] +[INFO] 2019-11-25 21:33:20.738 [PolygonWebsocketThread] PolygonWebsocketClient - Channel status: net.jacobpeterson.domain.polygon.websocket.ChannelStatus@4a61d9e6[ev=status,status=success,message=subscribed to: Q.AAPL] +===> streamUpdate QUOTES QuotesMessage [ticker=AAPL, channelType=QUOTES, stockQuote=net.jacobpeterson.polygon.domain.StockQuote@4d5b710e[ev=,sym=AAPL,bx=12,bp=124.86,bs=6,ax=12,ap=124.87,as=5,c=1,t=1559143439610], timestamp=2019-05-29T10:23:59.610] +===> streamUpdate QUOTES QuotesMessage [ticker=AAPL, channelType=QUOTES, stockQuote=net.jacobpeterson.polygon.domain.StockQuote@16b79976[ev=,sym=AAPL,bx=12,bp=124.86,bs=4,ax=12,ap=124.87,as=5,c=1,t=1559143439712], timestamp=2019-05-29T10:23:59.712] +===> streamUpdate TRADES TradesMessage [ticker=AAPL, channelType=TRADES, stockTrade=net.jacobpeterson.polygon.domain.StockTrade@32b4aa49[ev=,sym=AAPL,x=4,p=124.86,s=100,c=[],t=1559143439772], timestamp=2019-05-29T10:23:59.772] +===> streamUpdate AGGREGATE_PER_SECOND AggregateMessage [ticker=AAPL, channelType=AGGREGATE_PER_SECOND, stockAggregate=net.jacobpeterson.polygon.domain.StockAggregate@5ace47f7[ev=,sym=AAPL,v=1131,av=6602041,op=125.5,vw=124.9778,o=124.8665,c=124.86,h=124.8665,l=124.86,a=124.8636,s=1559143439000,e=1559143440000], start=2019-05-29T10:23:59, end=2019-05-29T10:24] AAPL Stock Split Response: Status: OK Count: 3 - io.github.mainstringargs.domain.polygon.stocksplits.StockSplit@6b09fb41[ticker=AAPL, + StockSplit@6b09fb41[ticker=AAPL, exDate=2014-06-09, paymentDate=2014-06-09, recordDate=, @@ -389,7 +389,7 @@ AAPL Stock Split Response: Aggregate Response: Status: OK Count: 5.0 - io.github.mainstringargs.domain.polygon.aggregates.Aggregate@6cd24612[ticker=, + Aggregate@6cd24612[ticker=, v=23984783, o=265.8, c=267.1, @@ -397,7 +397,7 @@ Aggregate Response: l=264.23, t=1574053200000, n=1] - io.github.mainstringargs.domain.polygon.aggregates.Aggregate@5dafbe45[ticker=, + Aggregate@5dafbe45[ticker=, v=21208039, o=267.9, c=266.29, diff --git a/build.gradle b/build.gradle index bc9d0394..5f1f4119 100644 --- a/build.gradle +++ b/build.gradle @@ -26,7 +26,7 @@ apply plugin: 'jsonschema2pojo' tasks.getByName("generateJsonSchema2Pojo").enabled(false) // Disable default jsonSchema2Pojo task archivesBaseName = 'alpaca-java' -group = 'io.github.mainstringargs' +group = 'net.jacobpeterson' version = '5.0.13-SNAPSHOT' repositories { @@ -60,8 +60,8 @@ build { finalizedBy publishToMavenLocal } -//mainClassName = 'io.github.mainstringargs.polygon.PolygonExample' -mainClassName = 'io.github.mainstringargs.alpaca.AlpacaExample' +//mainClassName = 'net.jacobpeterson.polygon.PolygonExample' +mainClassName = 'net.jacobpeterson.alpaca.AlpacaExample' // // START publishing @@ -119,7 +119,7 @@ nexus { } nexusStaging { - packageGroup = "io.github.mainstringargs" //optional if packageGroup == project.getGroup() + packageGroup = "net.jacobpeterson" //optional if packageGroup == project.getGroup() } // Used to prevent accidental publication of Alpaca keys @@ -138,7 +138,7 @@ processResources { task generatePOJOs() { final def targetDirectory = "${project.buildDir}" + File.separator + "generated-sources" + File.separator + "schemajson" final def jsonSourceDirectory = "${project.projectDir}" + File.separator + "schema_json" + File.separator - final def jsonPackageNameStart = "io.github.mainstringargs.domain." + final def jsonPackageNameStart = "net.jacobpeterson.domain." // Loop through all files in schema JSON file tree file(jsonSourceDirectory).eachFileRecurse { jsonFile -> diff --git a/schema_json/alpaca/account/account.json b/schema_json/alpaca/account/account.json index b4423179..fb93106b 100644 --- a/schema_json/alpaca/account/account.json +++ b/schema_json/alpaca/account/account.json @@ -11,7 +11,7 @@ "title": "Account number." }, "status": { - "existingJavaType": "io.github.mainstringargs.alpaca.enums.AccountStatus", + "existingJavaType": "net.jacobpeterson.alpaca.enums.AccountStatus", "title": "See Account Status" }, "currency": { diff --git a/schema_json/alpaca/account_activities/non_trade_activity.json b/schema_json/alpaca/account_activities/non_trade_activity.json index 17b43c6b..d5f18724 100644 --- a/schema_json/alpaca/account_activities/non_trade_activity.json +++ b/schema_json/alpaca/account_activities/non_trade_activity.json @@ -2,7 +2,7 @@ "type": "object", "title": "See https://docs.alpaca.markets/api-documentation/api-v2/account-activities/", "extends": { - "existingJavaType": "io.github.mainstringargs.domain.alpaca.accountactivities.AccountActivity" + "existingJavaType": "net.jacobpeterson.domain.alpaca.accountactivities.AccountActivity" }, "properties": { "activity_type": { diff --git a/schema_json/alpaca/account_activities/trade_activity.json b/schema_json/alpaca/account_activities/trade_activity.json index 99b52cee..4bc34b68 100644 --- a/schema_json/alpaca/account_activities/trade_activity.json +++ b/schema_json/alpaca/account_activities/trade_activity.json @@ -2,7 +2,7 @@ "type": "object", "title": "See https://docs.alpaca.markets/api-documentation/api-v2/account-activities/", "extends": { - "existingJavaType": "io.github.mainstringargs.domain.alpaca.accountactivities.AccountActivity" + "existingJavaType": "net.jacobpeterson.domain.alpaca.accountactivities.AccountActivity" }, "properties": { "activity_type": { diff --git a/schema_json/alpaca/market_data/last_quote_response.json b/schema_json/alpaca/market_data/last_quote_response.json index fefa8c5d..68de463e 100644 --- a/schema_json/alpaca/market_data/last_quote_response.json +++ b/schema_json/alpaca/market_data/last_quote_response.json @@ -11,7 +11,7 @@ "title": "Symbol that was evaluated from the request" }, "last": { - "existingJavaType": "io.github.mainstringargs.domain.alpaca.marketdata.LastQuote", + "existingJavaType": "net.jacobpeterson.domain.alpaca.marketdata.LastQuote", "title": "The Last Quote object" } } diff --git a/schema_json/alpaca/market_data/last_trade_response.json b/schema_json/alpaca/market_data/last_trade_response.json index e745db21..e11f6547 100644 --- a/schema_json/alpaca/market_data/last_trade_response.json +++ b/schema_json/alpaca/market_data/last_trade_response.json @@ -11,7 +11,7 @@ "title": "Symbol that was evaluated from the request" }, "last": { - "existingJavaType": "io.github.mainstringargs.domain.alpaca.marketdata.LastTrade", + "existingJavaType": "net.jacobpeterson.domain.alpaca.marketdata.LastTrade", "title": "The Last Trade object" } } diff --git a/schema_json/alpaca/market_data/streaming/abstracts/market_data_stream_data_message.json b/schema_json/alpaca/market_data/streaming/abstracts/market_data_stream_data_message.json index 2ac43fb5..ca467235 100644 --- a/schema_json/alpaca/market_data/streaming/abstracts/market_data_stream_data_message.json +++ b/schema_json/alpaca/market_data/streaming/abstracts/market_data_stream_data_message.json @@ -1,12 +1,12 @@ { "type": "object", "extends": { - "existingJavaType": "io.github.mainstringargs.domain.alpaca.marketdata.streaming.MarketDataStreamMessage" + "existingJavaType": "net.jacobpeterson.domain.alpaca.marketdata.streaming.MarketDataStreamMessage" }, "title": "See https://alpaca.markets/docs/api-documentation/api-v2/market-data/streaming/", "properties": { "ev": { - "existingJavaType": "io.github.mainstringargs.alpaca.websocket.marketdata.message.MarketDataStreamMessageType", + "existingJavaType": "net.jacobpeterson.alpaca.websocket.marketdata.message.MarketDataStreamMessageType", "title": "The event type" }, "sym": { diff --git a/schema_json/alpaca/market_data/streaming/abstracts/market_data_stream_status_message.json b/schema_json/alpaca/market_data/streaming/abstracts/market_data_stream_status_message.json index 56f4a237..3be488cd 100644 --- a/schema_json/alpaca/market_data/streaming/abstracts/market_data_stream_status_message.json +++ b/schema_json/alpaca/market_data/streaming/abstracts/market_data_stream_status_message.json @@ -1,12 +1,12 @@ { "type": "object", "extends": { - "existingJavaType": "io.github.mainstringargs.domain.alpaca.marketdata.streaming.MarketDataStreamMessage" + "existingJavaType": "net.jacobpeterson.domain.alpaca.marketdata.streaming.MarketDataStreamMessage" }, "title": "See https://alpaca.markets/docs/api-documentation/api-v2/market-data/streaming/", "properties": { "stream": { - "existingJavaType": "io.github.mainstringargs.alpaca.websocket.marketdata.message.MarketDataStreamMessageType", + "existingJavaType": "net.jacobpeterson.alpaca.websocket.marketdata.message.MarketDataStreamMessageType", "title": "The stream type" } } diff --git a/schema_json/alpaca/market_data/streaming/aggregate/aggregate_minute_message.json b/schema_json/alpaca/market_data/streaming/aggregate/aggregate_minute_message.json index c1ff9cd2..7180cfd6 100644 --- a/schema_json/alpaca/market_data/streaming/aggregate/aggregate_minute_message.json +++ b/schema_json/alpaca/market_data/streaming/aggregate/aggregate_minute_message.json @@ -1,7 +1,7 @@ { "type": "object", "extends": { - "existingJavaType": "io.github.mainstringargs.domain.alpaca.marketdata.streaming.abstracts.MarketDataStreamDataMessage" + "existingJavaType": "net.jacobpeterson.domain.alpaca.marketdata.streaming.abstracts.MarketDataStreamDataMessage" }, "title": "See https://alpaca.markets/docs/api-documentation/api-v2/market-data/streaming/", "properties": { diff --git a/schema_json/alpaca/market_data/streaming/authorization/authorization_message.json b/schema_json/alpaca/market_data/streaming/authorization/authorization_message.json index 7c08f2ea..024be680 100644 --- a/schema_json/alpaca/market_data/streaming/authorization/authorization_message.json +++ b/schema_json/alpaca/market_data/streaming/authorization/authorization_message.json @@ -2,11 +2,11 @@ "type": "object", "title": "See https://alpaca.markets/docs/api-documentation/api-v2/market-data/streaming/", "extends": { - "existingJavaType": "io.github.mainstringargs.domain.alpaca.marketdata.streaming.abstracts.MarketDataStreamStatusMessage" + "existingJavaType": "net.jacobpeterson.domain.alpaca.marketdata.streaming.abstracts.MarketDataStreamStatusMessage" }, "properties": { "data": { - "existingJavaType": "io.github.mainstringargs.domain.alpaca.marketdata.streaming.authorization.AuthorizationData", + "existingJavaType": "net.jacobpeterson.domain.alpaca.marketdata.streaming.authorization.AuthorizationData", "title": "The data" } } diff --git a/schema_json/alpaca/market_data/streaming/listening/listening_message.json b/schema_json/alpaca/market_data/streaming/listening/listening_message.json index 31b840d1..40ca046c 100644 --- a/schema_json/alpaca/market_data/streaming/listening/listening_message.json +++ b/schema_json/alpaca/market_data/streaming/listening/listening_message.json @@ -2,11 +2,11 @@ "type": "object", "title": "See https://docs.alpaca.markets/api-documentation/api-v2/streaming/", "extends": { - "existingJavaType": "io.github.mainstringargs.domain.alpaca.marketdata.streaming.abstracts.MarketDataStreamStatusMessage" + "existingJavaType": "net.jacobpeterson.domain.alpaca.marketdata.streaming.abstracts.MarketDataStreamStatusMessage" }, "properties": { "data": { - "existingJavaType": "io.github.mainstringargs.domain.alpaca.marketdata.streaming.listening.ListeningData", + "existingJavaType": "net.jacobpeterson.domain.alpaca.marketdata.streaming.listening.ListeningData", "title": "The data" } } diff --git a/schema_json/alpaca/market_data/streaming/market_data_stream_message.json b/schema_json/alpaca/market_data/streaming/market_data_stream_message.json index ca623f9a..b4461616 100644 --- a/schema_json/alpaca/market_data/streaming/market_data_stream_message.json +++ b/schema_json/alpaca/market_data/streaming/market_data_stream_message.json @@ -1,7 +1,7 @@ { "type": "object", "javaInterfaces": [ - "io.github.mainstringargs.abstracts.websocket.message.StreamMessage" + "net.jacobpeterson.abstracts.websocket.message.StreamMessage" ], "title": "See https://alpaca.markets/docs/api-documentation/api-v2/market-data/streaming/" } diff --git a/schema_json/alpaca/market_data/streaming/quote/quote_message.json b/schema_json/alpaca/market_data/streaming/quote/quote_message.json index b387a528..109c93d8 100644 --- a/schema_json/alpaca/market_data/streaming/quote/quote_message.json +++ b/schema_json/alpaca/market_data/streaming/quote/quote_message.json @@ -1,7 +1,7 @@ { "type": "object", "extends": { - "existingJavaType": "io.github.mainstringargs.domain.alpaca.marketdata.streaming.abstracts.MarketDataStreamDataMessage" + "existingJavaType": "net.jacobpeterson.domain.alpaca.marketdata.streaming.abstracts.MarketDataStreamDataMessage" }, "title": "See https://alpaca.markets/docs/api-documentation/api-v2/market-data/streaming/", "properties": { diff --git a/schema_json/alpaca/market_data/streaming/trade/trade_message.json b/schema_json/alpaca/market_data/streaming/trade/trade_message.json index 60d60b01..9cb9742c 100644 --- a/schema_json/alpaca/market_data/streaming/trade/trade_message.json +++ b/schema_json/alpaca/market_data/streaming/trade/trade_message.json @@ -1,7 +1,7 @@ { "type": "object", "extends": { - "existingJavaType": "io.github.mainstringargs.domain.alpaca.marketdata.streaming.abstracts.MarketDataStreamDataMessage" + "existingJavaType": "net.jacobpeterson.domain.alpaca.marketdata.streaming.abstracts.MarketDataStreamDataMessage" }, "title": "See https://alpaca.markets/docs/api-documentation/api-v2/market-data/streaming/", "properties": { diff --git a/schema_json/alpaca/order/cancelled_order.json b/schema_json/alpaca/order/cancelled_order.json index 531be7c9..1cd5c244 100644 --- a/schema_json/alpaca/order/cancelled_order.json +++ b/schema_json/alpaca/order/cancelled_order.json @@ -11,7 +11,7 @@ "title": "The HTTP status code of the cancel status" }, "body": { - "existingJavaType": "io.github.mainstringargs.domain.alpaca.order.Order", + "existingJavaType": "net.jacobpeterson.domain.alpaca.order.Order", "title": "The cancelled order" } } diff --git a/schema_json/alpaca/order/order.json b/schema_json/alpaca/order/order.json index 16f377e0..36f0be60 100644 --- a/schema_json/alpaca/order/order.json +++ b/schema_json/alpaca/order/order.json @@ -103,7 +103,7 @@ "title": "If true, eligible for execution outside regular trading hours." }, "legs": { - "existingJavaType": "java.util.ArrayList", + "existingJavaType": "java.util.ArrayList", "title": "When querying non-simple order_class orders in a nested style, an array of Order entities associated with this order is returned. Otherwise, null." } } diff --git a/schema_json/alpaca/portfolio_history/portfolio_history.json b/schema_json/alpaca/portfolio_history/portfolio_history.json index b39982c3..254eacdf 100644 --- a/schema_json/alpaca/portfolio_history/portfolio_history.json +++ b/schema_json/alpaca/portfolio_history/portfolio_history.json @@ -23,7 +23,7 @@ "title": "Basis in dollar of the profit loss calculation" }, "timeframe": { - "existingJavaType": "io.github.mainstringargs.alpaca.enums.PortfolioTimeFrame", + "existingJavaType": "net.jacobpeterson.alpaca.enums.PortfolioTimeFrame", "title": "Time window size of each data element" } } diff --git a/schema_json/alpaca/position/close_position_order.json b/schema_json/alpaca/position/close_position_order.json index 0731f66d..31f14ae3 100644 --- a/schema_json/alpaca/position/close_position_order.json +++ b/schema_json/alpaca/position/close_position_order.json @@ -11,7 +11,7 @@ "title": "The HTTP status code of the position-closing order status" }, "body": { - "existingJavaType": "io.github.mainstringargs.domain.alpaca.order.Order", + "existingJavaType": "net.jacobpeterson.domain.alpaca.order.Order", "title": "The position-closing order" } } diff --git a/schema_json/alpaca/streaming/account/account_update.json b/schema_json/alpaca/streaming/account/account_update.json index 83fb89d7..90c1b561 100644 --- a/schema_json/alpaca/streaming/account/account_update.json +++ b/schema_json/alpaca/streaming/account/account_update.json @@ -1,7 +1,7 @@ { "type": "object", "extends": { - "existingJavaType": "io.github.mainstringargs.domain.alpaca.account.Account" + "existingJavaType": "net.jacobpeterson.domain.alpaca.account.Account" }, "title": "See https://docs.alpaca.markets/api-documentation/api-v2/streaming/" } diff --git a/schema_json/alpaca/streaming/account/account_update_message.json b/schema_json/alpaca/streaming/account/account_update_message.json index 61c70909..93028221 100644 --- a/schema_json/alpaca/streaming/account/account_update_message.json +++ b/schema_json/alpaca/streaming/account/account_update_message.json @@ -1,12 +1,12 @@ { "type": "object", "extends": { - "existingJavaType": "io.github.mainstringargs.domain.alpaca.streaming.AlpacaStreamMessage" + "existingJavaType": "net.jacobpeterson.domain.alpaca.streaming.AlpacaStreamMessage" }, "title": "See https://docs.alpaca.markets/api-documentation/api-v2/streaming/", "properties": { "data": { - "existingJavaType": "io.github.mainstringargs.domain.alpaca.streaming.account.AccountUpdate" + "existingJavaType": "net.jacobpeterson.domain.alpaca.streaming.account.AccountUpdate" } } } diff --git a/schema_json/alpaca/streaming/alpaca_stream_message.json b/schema_json/alpaca/streaming/alpaca_stream_message.json index 1853f5ee..50a446fa 100644 --- a/schema_json/alpaca/streaming/alpaca_stream_message.json +++ b/schema_json/alpaca/streaming/alpaca_stream_message.json @@ -1,12 +1,12 @@ { "type": "object", "javaInterfaces": [ - "io.github.mainstringargs.abstracts.websocket.message.StreamMessage" + "net.jacobpeterson.abstracts.websocket.message.StreamMessage" ], "title": "See https://docs.alpaca.markets/api-documentation/api-v2/streaming/", "properties": { "stream": { - "existingJavaType": "io.github.mainstringargs.alpaca.websocket.broker.message.AlpacaStreamMessageType", + "existingJavaType": "net.jacobpeterson.alpaca.websocket.broker.message.AlpacaStreamMessageType", "title": "The stream type" } } diff --git a/schema_json/alpaca/streaming/authorization/authorization_message.json b/schema_json/alpaca/streaming/authorization/authorization_message.json index 78724c97..e067a5d7 100644 --- a/schema_json/alpaca/streaming/authorization/authorization_message.json +++ b/schema_json/alpaca/streaming/authorization/authorization_message.json @@ -2,11 +2,11 @@ "type": "object", "title": "See https://docs.alpaca.markets/api-documentation/api-v2/streaming/", "extends": { - "existingJavaType": "io.github.mainstringargs.domain.alpaca.streaming.AlpacaStreamMessage" + "existingJavaType": "net.jacobpeterson.domain.alpaca.streaming.AlpacaStreamMessage" }, "properties": { "data": { - "existingJavaType": "io.github.mainstringargs.domain.alpaca.streaming.authorization.AuthorizationData", + "existingJavaType": "net.jacobpeterson.domain.alpaca.streaming.authorization.AuthorizationData", "title": "The data" } } diff --git a/schema_json/alpaca/streaming/listening/listening_message.json b/schema_json/alpaca/streaming/listening/listening_message.json index 4aa3eb19..6c99cfcf 100644 --- a/schema_json/alpaca/streaming/listening/listening_message.json +++ b/schema_json/alpaca/streaming/listening/listening_message.json @@ -2,11 +2,11 @@ "type": "object", "title": "See https://docs.alpaca.markets/api-documentation/api-v2/streaming/", "extends": { - "existingJavaType": "io.github.mainstringargs.domain.alpaca.streaming.AlpacaStreamMessage" + "existingJavaType": "net.jacobpeterson.domain.alpaca.streaming.AlpacaStreamMessage" }, "properties": { "data": { - "existingJavaType": "io.github.mainstringargs.domain.alpaca.streaming.listening.ListeningData", + "existingJavaType": "net.jacobpeterson.domain.alpaca.streaming.listening.ListeningData", "title": "The data" } } diff --git a/schema_json/alpaca/streaming/trade/trade_update.json b/schema_json/alpaca/streaming/trade/trade_update.json index 694a4bcd..a94b47f6 100644 --- a/schema_json/alpaca/streaming/trade/trade_update.json +++ b/schema_json/alpaca/streaming/trade/trade_update.json @@ -19,7 +19,7 @@ "title": "The position quantity" }, "order": { - "existingJavaType": "io.github.mainstringargs.domain.alpaca.order.Order", + "existingJavaType": "net.jacobpeterson.domain.alpaca.order.Order", "title": "The Order" } } diff --git a/schema_json/alpaca/streaming/trade/trade_update_message.json b/schema_json/alpaca/streaming/trade/trade_update_message.json index 8c07eec5..4077af01 100644 --- a/schema_json/alpaca/streaming/trade/trade_update_message.json +++ b/schema_json/alpaca/streaming/trade/trade_update_message.json @@ -1,12 +1,12 @@ { "type": "object", "extends": { - "existingJavaType": "io.github.mainstringargs.domain.alpaca.streaming.AlpacaStreamMessage" + "existingJavaType": "net.jacobpeterson.domain.alpaca.streaming.AlpacaStreamMessage" }, "title": "See https://docs.alpaca.markets/api-documentation/api-v2/streaming/", "properties": { "data": { - "existingJavaType": "io.github.mainstringargs.domain.alpaca.streaming.trade.TradeUpdate" + "existingJavaType": "net.jacobpeterson.domain.alpaca.streaming.trade.TradeUpdate" } } } diff --git a/schema_json/alpaca/watchlist/watchlist.json b/schema_json/alpaca/watchlist/watchlist.json index 9976aac0..6f924558 100644 --- a/schema_json/alpaca/watchlist/watchlist.json +++ b/schema_json/alpaca/watchlist/watchlist.json @@ -23,7 +23,7 @@ "title": "Account ID" }, "assets": { - "existingJavaType": "java.util.ArrayList", + "existingJavaType": "java.util.ArrayList", "title": "The content of this watchlist, in the order as registered by the client" } } diff --git a/schema_json/polygon/aggregates/aggregates_response.json b/schema_json/polygon/aggregates/aggregates_response.json index f8924c96..dde56292 100644 --- a/schema_json/polygon/aggregates/aggregates_response.json +++ b/schema_json/polygon/aggregates/aggregates_response.json @@ -23,7 +23,7 @@ "title": "Total number of results generated" }, "results": { - "existingJavaType": "java.util.ArrayList", + "existingJavaType": "java.util.ArrayList", "title": "results" } } diff --git a/schema_json/polygon/daily_open_close/daily_open_close_response.json b/schema_json/polygon/daily_open_close/daily_open_close_response.json index 972e9e32..8b2a3dea 100644 --- a/schema_json/polygon/daily_open_close/daily_open_close_response.json +++ b/schema_json/polygon/daily_open_close/daily_open_close_response.json @@ -7,15 +7,15 @@ "title": "Symbol that was evaluated from the request" }, "open": { - "existingJavaType": "io.github.mainstringargs.domain.polygon.dailyopenclose.DailyOpenCloseTrade", + "existingJavaType": "net.jacobpeterson.domain.polygon.dailyopenclose.DailyOpenCloseTrade", "title": "open" }, "close": { - "existingJavaType": "io.github.mainstringargs.domain.polygon.dailyopenclose.DailyOpenCloseTrade", + "existingJavaType": "net.jacobpeterson.domain.polygon.dailyopenclose.DailyOpenCloseTrade", "title": "close" }, "afterHours": { - "existingJavaType": "io.github.mainstringargs.domain.polygon.dailyopenclose.DailyOpenCloseTrade", + "existingJavaType": "net.jacobpeterson.domain.polygon.dailyopenclose.DailyOpenCloseTrade", "title": "afterHours" } } diff --git a/schema_json/polygon/grouped_daily/grouped_daily_response.json b/schema_json/polygon/grouped_daily/grouped_daily_response.json index 6a39aa9d..dbe3fbd0 100644 --- a/schema_json/polygon/grouped_daily/grouped_daily_response.json +++ b/schema_json/polygon/grouped_daily/grouped_daily_response.json @@ -23,7 +23,7 @@ "title": "Total number of results generated" }, "results": { - "existingJavaType": "java.util.ArrayList", + "existingJavaType": "java.util.ArrayList", "title": "Results" } } diff --git a/schema_json/polygon/historic_quotes/historic_quotes_response.json b/schema_json/polygon/historic_quotes/historic_quotes_response.json index 62a6026a..9319ef32 100644 --- a/schema_json/polygon/historic_quotes/historic_quotes_response.json +++ b/schema_json/polygon/historic_quotes/historic_quotes_response.json @@ -19,7 +19,7 @@ "title": "Ticker symbol that was evaluated from the request" }, "results": { - "existingJavaType": "java.util.ArrayList", + "existingJavaType": "java.util.ArrayList", "title": "results" } } diff --git a/schema_json/polygon/historic_trades/historic_trades_response.json b/schema_json/polygon/historic_trades/historic_trades_response.json index d37da34b..12dee80a 100644 --- a/schema_json/polygon/historic_trades/historic_trades_response.json +++ b/schema_json/polygon/historic_trades/historic_trades_response.json @@ -19,7 +19,7 @@ "title": "Ticker symbol that was evaluated from the request" }, "results": { - "existingJavaType": "java.util.ArrayList", + "existingJavaType": "java.util.ArrayList", "title": "results" } } diff --git a/schema_json/polygon/last_quote/last_quote_response.json b/schema_json/polygon/last_quote/last_quote_response.json index 20ce7727..9528b29d 100644 --- a/schema_json/polygon/last_quote/last_quote_response.json +++ b/schema_json/polygon/last_quote/last_quote_response.json @@ -11,7 +11,7 @@ "title": "Symbol that was evaluated from the request" }, "last": { - "existingJavaType": "io.github.mainstringargs.domain.polygon.lastquote.LastQuote", + "existingJavaType": "net.jacobpeterson.domain.polygon.lastquote.LastQuote", "title": "last" } } diff --git a/schema_json/polygon/last_trade/last_trade_response.json b/schema_json/polygon/last_trade/last_trade_response.json index c86bb30d..96ac11b2 100644 --- a/schema_json/polygon/last_trade/last_trade_response.json +++ b/schema_json/polygon/last_trade/last_trade_response.json @@ -11,7 +11,7 @@ "title": "Symbol that was evaluated from the request" }, "last": { - "existingJavaType": "io.github.mainstringargs.domain.polygon.lasttrade.LastTrade", + "existingJavaType": "net.jacobpeterson.domain.polygon.lasttrade.LastTrade", "title": "last" } } diff --git a/schema_json/polygon/locales/locales_response.json b/schema_json/polygon/locales/locales_response.json index 6be63576..6006bb64 100644 --- a/schema_json/polygon/locales/locales_response.json +++ b/schema_json/polygon/locales/locales_response.json @@ -7,7 +7,7 @@ "title": "Status" }, "results": { - "existingJavaType": "java.util.ArrayList", + "existingJavaType": "java.util.ArrayList", "title": "Results" } } diff --git a/schema_json/polygon/markets/markets_response.json b/schema_json/polygon/markets/markets_response.json index 619a5031..6c45fee6 100644 --- a/schema_json/polygon/markets/markets_response.json +++ b/schema_json/polygon/markets/markets_response.json @@ -7,7 +7,7 @@ "title": "Status" }, "results": { - "existingJavaType": "java.util.ArrayList", + "existingJavaType": "java.util.ArrayList", "title": "Results" } } diff --git a/schema_json/polygon/previous_close/previous_close_response.json b/schema_json/polygon/previous_close/previous_close_response.json index da57b1a1..f31fc57a 100644 --- a/schema_json/polygon/previous_close/previous_close_response.json +++ b/schema_json/polygon/previous_close/previous_close_response.json @@ -23,7 +23,7 @@ "title": "Total number of results generated" }, "results": { - "existingJavaType": "java.util.ArrayList", + "existingJavaType": "java.util.ArrayList", "title": "Results" } } diff --git a/schema_json/polygon/snapshot/snapshot_all_tickers_response.json b/schema_json/polygon/snapshot/snapshot_all_tickers_response.json index 42b1c494..7d1a502c 100644 --- a/schema_json/polygon/snapshot/snapshot_all_tickers_response.json +++ b/schema_json/polygon/snapshot/snapshot_all_tickers_response.json @@ -7,7 +7,7 @@ "title": "Status of this requests response" }, "tickers": { - "existingJavaType": "java.util.ArrayList", + "existingJavaType": "java.util.ArrayList", "title": "tickers" } } diff --git a/schema_json/polygon/snapshot/snapshot_gainers_losers_response.json b/schema_json/polygon/snapshot/snapshot_gainers_losers_response.json index dae0184a..e5bf71a1 100644 --- a/schema_json/polygon/snapshot/snapshot_gainers_losers_response.json +++ b/schema_json/polygon/snapshot/snapshot_gainers_losers_response.json @@ -7,7 +7,7 @@ "title": "Status of this requests response" }, "tickers": { - "existingJavaType": "java.util.ArrayList", + "existingJavaType": "java.util.ArrayList", "title": "tickers" } } diff --git a/schema_json/polygon/snapshot/snapshot_single_ticker_response.json b/schema_json/polygon/snapshot/snapshot_single_ticker_response.json index 389c9f15..8861077b 100644 --- a/schema_json/polygon/snapshot/snapshot_single_ticker_response.json +++ b/schema_json/polygon/snapshot/snapshot_single_ticker_response.json @@ -7,7 +7,7 @@ "title": "Status of this requests response" }, "ticker": { - "existingJavaType": "io.github.mainstringargs.domain.polygon.snapshot.ticker.SnapshotTicker", + "existingJavaType": "net.jacobpeterson.domain.polygon.snapshot.ticker.SnapshotTicker", "title": "tickers" } } diff --git a/schema_json/polygon/snapshot/snapshot_ticker_book.json b/schema_json/polygon/snapshot/snapshot_ticker_book.json index d9f795ce..3d14c5e5 100644 --- a/schema_json/polygon/snapshot/snapshot_ticker_book.json +++ b/schema_json/polygon/snapshot/snapshot_ticker_book.json @@ -7,7 +7,7 @@ "title": "Status of this requests response" }, "data": { - "existingJavaType": "io.github.mainstringargs.domain.polygon.snapshot.ticker.book.BookData", + "existingJavaType": "net.jacobpeterson.domain.polygon.snapshot.ticker.book.BookData", "title": "The book data" } } diff --git a/schema_json/polygon/snapshot/ticker/book/book_data.json b/schema_json/polygon/snapshot/ticker/book/book_data.json index 0fbc7d1c..3d5becdf 100644 --- a/schema_json/polygon/snapshot/ticker/book/book_data.json +++ b/schema_json/polygon/snapshot/ticker/book/book_data.json @@ -7,11 +7,11 @@ "title": "The ticker" }, "bids": { - "existingJavaType": "java.util.ArrayList", + "existingJavaType": "java.util.ArrayList", "title": "The bids on the book" }, "asks": { - "existingJavaType": "java.util.ArrayList", + "existingJavaType": "java.util.ArrayList", "title": "The asks on the book" }, "bidCount": { diff --git a/schema_json/polygon/snapshot/ticker/snapshot_ticker.json b/schema_json/polygon/snapshot/ticker/snapshot_ticker.json index 0b9f7d68..50bfd180 100644 --- a/schema_json/polygon/snapshot/ticker/snapshot_ticker.json +++ b/schema_json/polygon/snapshot/ticker/snapshot_ticker.json @@ -7,23 +7,23 @@ "title": "Ticker of the object" }, "day": { - "existingJavaType": "io.github.mainstringargs.domain.polygon.snapshot.ticker.TickerCandle", + "existingJavaType": "net.jacobpeterson.domain.polygon.snapshot.ticker.TickerCandle", "title": "day" }, "lastTrade": { - "existingJavaType": "io.github.mainstringargs.domain.polygon.snapshot.ticker.LastTrade", + "existingJavaType": "net.jacobpeterson.domain.polygon.snapshot.ticker.LastTrade", "title": "lastTrade" }, "lastQuote": { - "existingJavaType": "io.github.mainstringargs.domain.polygon.snapshot.ticker.LastQuote", + "existingJavaType": "net.jacobpeterson.domain.polygon.snapshot.ticker.LastQuote", "title": "lastQuote" }, "min": { - "existingJavaType": "io.github.mainstringargs.domain.polygon.snapshot.ticker.TickerCandle", + "existingJavaType": "net.jacobpeterson.domain.polygon.snapshot.ticker.TickerCandle", "title": "min" }, "prevDay": { - "existingJavaType": "io.github.mainstringargs.domain.polygon.snapshot.ticker.TickerCandle", + "existingJavaType": "net.jacobpeterson.domain.polygon.snapshot.ticker.TickerCandle", "title": "prevDay" }, "todaysChange": { diff --git a/schema_json/polygon/stock_dividends/stock_dividends_response.json b/schema_json/polygon/stock_dividends/stock_dividends_response.json index b9f7fe26..b35c9456 100644 --- a/schema_json/polygon/stock_dividends/stock_dividends_response.json +++ b/schema_json/polygon/stock_dividends/stock_dividends_response.json @@ -11,7 +11,7 @@ "title": "Count" }, "results": { - "existingJavaType": "java.util.ArrayList", + "existingJavaType": "java.util.ArrayList", "title": "Results" } } diff --git a/schema_json/polygon/stock_financials/stock_financials_response.json b/schema_json/polygon/stock_financials/stock_financials_response.json index 725cfffd..e8c86207 100644 --- a/schema_json/polygon/stock_financials/stock_financials_response.json +++ b/schema_json/polygon/stock_financials/stock_financials_response.json @@ -11,7 +11,7 @@ "title": "Count" }, "results": { - "existingJavaType": "java.util.ArrayList", + "existingJavaType": "java.util.ArrayList", "title": "Results" } } diff --git a/schema_json/polygon/stock_splits/stock_splits_response.json b/schema_json/polygon/stock_splits/stock_splits_response.json index ffdf5926..5437ad2a 100644 --- a/schema_json/polygon/stock_splits/stock_splits_response.json +++ b/schema_json/polygon/stock_splits/stock_splits_response.json @@ -11,7 +11,7 @@ "title": "Count" }, "results": { - "existingJavaType": "java.util.ArrayList", + "existingJavaType": "java.util.ArrayList", "title": "Results" } } diff --git a/schema_json/polygon/ticker_types/ticket_types_response.json b/schema_json/polygon/ticker_types/ticket_types_response.json index 4a9c0694..4f22bb9c 100644 --- a/schema_json/polygon/ticker_types/ticket_types_response.json +++ b/schema_json/polygon/ticker_types/ticket_types_response.json @@ -7,7 +7,7 @@ "title": "Status" }, "results": { - "existingJavaType": "io.github.mainstringargs.domain.polygon.tickertypes.TickerTypes", + "existingJavaType": "net.jacobpeterson.domain.polygon.tickertypes.TickerTypes", "title": "Results" } } diff --git a/schema_json/polygon/tickers/ticker/forex_ticker.json b/schema_json/polygon/tickers/ticker/forex_ticker.json index 07fc2871..b640a665 100644 --- a/schema_json/polygon/tickers/ticker/forex_ticker.json +++ b/schema_json/polygon/tickers/ticker/forex_ticker.json @@ -2,7 +2,7 @@ "type": "object", "title": "See https://polygon.io/docs/#!/Reference/get_v2_reference_tickers", "extends": { - "existingJavaType": "io.github.mainstringargs.domain.polygon.tickers.ticker.Ticker" + "existingJavaType": "net.jacobpeterson.domain.polygon.tickers.ticker.Ticker" }, "properties": { "attrs": { diff --git a/schema_json/polygon/tickers/ticker/stock_ticker.json b/schema_json/polygon/tickers/ticker/stock_ticker.json index 5e940bbe..7735d79c 100644 --- a/schema_json/polygon/tickers/ticker/stock_ticker.json +++ b/schema_json/polygon/tickers/ticker/stock_ticker.json @@ -2,7 +2,7 @@ "type": "object", "title": "See https://polygon.io/docs/#!/Reference/get_v2_reference_tickers", "extends": { - "existingJavaType": "io.github.mainstringargs.domain.polygon.tickers.ticker.Ticker" + "existingJavaType": "net.jacobpeterson.domain.polygon.tickers.ticker.Ticker" }, "properties": { "type": { diff --git a/schema_json/polygon/tickers/tickers_response.json b/schema_json/polygon/tickers/tickers_response.json index a887d3e4..3dab0aa3 100644 --- a/schema_json/polygon/tickers/tickers_response.json +++ b/schema_json/polygon/tickers/tickers_response.json @@ -19,7 +19,7 @@ "title": "Count" }, "results": { - "existingJavaType": "java.util.ArrayList", + "existingJavaType": "java.util.ArrayList", "title": "Results" } } diff --git a/schema_json/polygon/websocket/aggregate/aggregate_message.json b/schema_json/polygon/websocket/aggregate/aggregate_message.json index cf055d6b..4b2e1aa7 100644 --- a/schema_json/polygon/websocket/aggregate/aggregate_message.json +++ b/schema_json/polygon/websocket/aggregate/aggregate_message.json @@ -1,7 +1,7 @@ { "type": "object", "extends": { - "existingJavaType": "io.github.mainstringargs.domain.polygon.websocket.PolygonStreamMessage" + "existingJavaType": "net.jacobpeterson.domain.polygon.websocket.PolygonStreamMessage" }, "title": "See https://polygon.io/sockets", "properties": { diff --git a/schema_json/polygon/websocket/aggregate/aggregate_per_minute_message.json b/schema_json/polygon/websocket/aggregate/aggregate_per_minute_message.json index 41bdde93..0d4997b6 100644 --- a/schema_json/polygon/websocket/aggregate/aggregate_per_minute_message.json +++ b/schema_json/polygon/websocket/aggregate/aggregate_per_minute_message.json @@ -1,7 +1,7 @@ { "type": "object", "extends": { - "existingJavaType": "io.github.mainstringargs.domain.polygon.websocket.aggregate.AggregateMessage" + "existingJavaType": "net.jacobpeterson.domain.polygon.websocket.aggregate.AggregateMessage" }, "title": "See https://polygon.io/sockets" } diff --git a/schema_json/polygon/websocket/aggregate/aggregate_per_second_message.json b/schema_json/polygon/websocket/aggregate/aggregate_per_second_message.json index 41bdde93..0d4997b6 100644 --- a/schema_json/polygon/websocket/aggregate/aggregate_per_second_message.json +++ b/schema_json/polygon/websocket/aggregate/aggregate_per_second_message.json @@ -1,7 +1,7 @@ { "type": "object", "extends": { - "existingJavaType": "io.github.mainstringargs.domain.polygon.websocket.aggregate.AggregateMessage" + "existingJavaType": "net.jacobpeterson.domain.polygon.websocket.aggregate.AggregateMessage" }, "title": "See https://polygon.io/sockets" } diff --git a/schema_json/polygon/websocket/polygon_stream_message.json b/schema_json/polygon/websocket/polygon_stream_message.json index ee06b470..5f57f96b 100644 --- a/schema_json/polygon/websocket/polygon_stream_message.json +++ b/schema_json/polygon/websocket/polygon_stream_message.json @@ -1,12 +1,12 @@ { "type": "object", "javaInterfaces": [ - "io.github.mainstringargs.abstracts.websocket.message.StreamMessage" + "net.jacobpeterson.abstracts.websocket.message.StreamMessage" ], "title": "See https://polygon.io/sockets", "properties": { "ev": { - "existingJavaType": "io.github.mainstringargs.polygon.websocket.message.PolygonStreamMessageType", + "existingJavaType": "net.jacobpeterson.polygon.websocket.message.PolygonStreamMessageType", "title": "The event type" }, "sym": { diff --git a/schema_json/polygon/websocket/quote/quote_message.json b/schema_json/polygon/websocket/quote/quote_message.json index 487c79cb..e5492076 100644 --- a/schema_json/polygon/websocket/quote/quote_message.json +++ b/schema_json/polygon/websocket/quote/quote_message.json @@ -1,7 +1,7 @@ { "type": "object", "extends": { - "existingJavaType": "io.github.mainstringargs.domain.polygon.websocket.PolygonStreamMessage" + "existingJavaType": "net.jacobpeterson.domain.polygon.websocket.PolygonStreamMessage" }, "title": "See https://polygon.io/sockets", "properties": { diff --git a/schema_json/polygon/websocket/status/status_message.json b/schema_json/polygon/websocket/status/status_message.json index 1311a5df..6d06364c 100644 --- a/schema_json/polygon/websocket/status/status_message.json +++ b/schema_json/polygon/websocket/status/status_message.json @@ -1,7 +1,7 @@ { "type": "object", "extends": { - "existingJavaType": "io.github.mainstringargs.domain.polygon.websocket.PolygonStreamMessage" + "existingJavaType": "net.jacobpeterson.domain.polygon.websocket.PolygonStreamMessage" }, "title": "See https://polygon.io/sockets", "properties": { diff --git a/schema_json/polygon/websocket/trade/trade_message.json b/schema_json/polygon/websocket/trade/trade_message.json index bede5ac3..1e904582 100644 --- a/schema_json/polygon/websocket/trade/trade_message.json +++ b/schema_json/polygon/websocket/trade/trade_message.json @@ -1,7 +1,7 @@ { "type": "object", "extends": { - "existingJavaType": "io.github.mainstringargs.domain.polygon.websocket.PolygonStreamMessage" + "existingJavaType": "net.jacobpeterson.domain.polygon.websocket.PolygonStreamMessage" }, "title": "See https://polygon.io/sockets", "properties": { diff --git a/src/main/java/io/github/mainstringargs/abstracts/enums/APIName.java b/src/main/java/net/jacobpeterson/abstracts/enums/APIName.java similarity index 72% rename from src/main/java/io/github/mainstringargs/abstracts/enums/APIName.java rename to src/main/java/net/jacobpeterson/abstracts/enums/APIName.java index 89f2ac69..bfabe725 100644 --- a/src/main/java/io/github/mainstringargs/abstracts/enums/APIName.java +++ b/src/main/java/net/jacobpeterson/abstracts/enums/APIName.java @@ -1,4 +1,4 @@ -package io.github.mainstringargs.abstracts.enums; +package net.jacobpeterson.abstracts.enums; public interface APIName { diff --git a/src/main/java/io/github/mainstringargs/abstracts/rest/AbstractRequest.java b/src/main/java/net/jacobpeterson/abstracts/rest/AbstractRequest.java similarity index 98% rename from src/main/java/io/github/mainstringargs/abstracts/rest/AbstractRequest.java rename to src/main/java/net/jacobpeterson/abstracts/rest/AbstractRequest.java index 9c8fb844..eb4d87e5 100644 --- a/src/main/java/io/github/mainstringargs/abstracts/rest/AbstractRequest.java +++ b/src/main/java/net/jacobpeterson/abstracts/rest/AbstractRequest.java @@ -1,4 +1,4 @@ -package io.github.mainstringargs.abstracts.rest; +package net.jacobpeterson.abstracts.rest; import com.google.gson.JsonElement; import com.google.gson.stream.JsonReader; @@ -7,7 +7,7 @@ import com.mashape.unirest.http.exceptions.UnirestException; import com.mashape.unirest.request.GetRequest; import com.mashape.unirest.request.HttpRequestWithBody; -import io.github.mainstringargs.util.gson.GsonUtil; +import net.jacobpeterson.util.gson.GsonUtil; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; diff --git a/src/main/java/io/github/mainstringargs/abstracts/rest/AbstractRequestBuilder.java b/src/main/java/net/jacobpeterson/abstracts/rest/AbstractRequestBuilder.java similarity index 98% rename from src/main/java/io/github/mainstringargs/abstracts/rest/AbstractRequestBuilder.java rename to src/main/java/net/jacobpeterson/abstracts/rest/AbstractRequestBuilder.java index 489fa253..81e8f407 100644 --- a/src/main/java/io/github/mainstringargs/abstracts/rest/AbstractRequestBuilder.java +++ b/src/main/java/net/jacobpeterson/abstracts/rest/AbstractRequestBuilder.java @@ -1,4 +1,4 @@ -package io.github.mainstringargs.abstracts.rest; +package net.jacobpeterson.abstracts.rest; import com.google.gson.JsonElement; import com.google.gson.JsonObject; diff --git a/src/main/java/io/github/mainstringargs/abstracts/rest/exception/AbstractAPIRequestException.java b/src/main/java/net/jacobpeterson/abstracts/rest/exception/AbstractAPIRequestException.java similarity index 97% rename from src/main/java/io/github/mainstringargs/abstracts/rest/exception/AbstractAPIRequestException.java rename to src/main/java/net/jacobpeterson/abstracts/rest/exception/AbstractAPIRequestException.java index 4dfda189..11a373d5 100644 --- a/src/main/java/io/github/mainstringargs/abstracts/rest/exception/AbstractAPIRequestException.java +++ b/src/main/java/net/jacobpeterson/abstracts/rest/exception/AbstractAPIRequestException.java @@ -1,9 +1,9 @@ -package io.github.mainstringargs.abstracts.rest.exception; +package net.jacobpeterson.abstracts.rest.exception; import com.google.gson.JsonElement; import com.google.gson.JsonObject; import com.mashape.unirest.http.HttpResponse; -import io.github.mainstringargs.util.gson.GsonUtil; +import net.jacobpeterson.util.gson.GsonUtil; import java.io.InputStream; import java.io.InputStreamReader; diff --git a/src/main/java/io/github/mainstringargs/abstracts/websocket/client/AbstractWebsocketClientEndpoint.java b/src/main/java/net/jacobpeterson/abstracts/websocket/client/AbstractWebsocketClientEndpoint.java similarity index 97% rename from src/main/java/io/github/mainstringargs/abstracts/websocket/client/AbstractWebsocketClientEndpoint.java rename to src/main/java/net/jacobpeterson/abstracts/websocket/client/AbstractWebsocketClientEndpoint.java index 6fac5910..3c6f9e16 100644 --- a/src/main/java/io/github/mainstringargs/abstracts/websocket/client/AbstractWebsocketClientEndpoint.java +++ b/src/main/java/net/jacobpeterson/abstracts/websocket/client/AbstractWebsocketClientEndpoint.java @@ -1,6 +1,6 @@ -package io.github.mainstringargs.abstracts.websocket.client; +package net.jacobpeterson.abstracts.websocket.client; -import io.github.mainstringargs.util.concurrency.ExecutorTracer; +import net.jacobpeterson.util.concurrency.ExecutorTracer; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; diff --git a/src/main/java/io/github/mainstringargs/abstracts/websocket/client/WebsocketClient.java b/src/main/java/net/jacobpeterson/abstracts/websocket/client/WebsocketClient.java similarity index 80% rename from src/main/java/io/github/mainstringargs/abstracts/websocket/client/WebsocketClient.java rename to src/main/java/net/jacobpeterson/abstracts/websocket/client/WebsocketClient.java index 78d2644c..63b62d51 100644 --- a/src/main/java/io/github/mainstringargs/abstracts/websocket/client/WebsocketClient.java +++ b/src/main/java/net/jacobpeterson/abstracts/websocket/client/WebsocketClient.java @@ -1,8 +1,8 @@ -package io.github.mainstringargs.abstracts.websocket.client; +package net.jacobpeterson.abstracts.websocket.client; -import io.github.mainstringargs.abstracts.websocket.listener.StreamListener; -import io.github.mainstringargs.abstracts.websocket.message.StreamMessage; -import io.github.mainstringargs.abstracts.websocket.message.StreamMessageType; +import net.jacobpeterson.abstracts.websocket.listener.StreamListener; +import net.jacobpeterson.abstracts.websocket.message.StreamMessage; +import net.jacobpeterson.abstracts.websocket.message.StreamMessageType; /** * The type Websocket client. diff --git a/src/main/java/io/github/mainstringargs/abstracts/websocket/listener/StreamListener.java b/src/main/java/net/jacobpeterson/abstracts/websocket/listener/StreamListener.java similarity index 67% rename from src/main/java/io/github/mainstringargs/abstracts/websocket/listener/StreamListener.java rename to src/main/java/net/jacobpeterson/abstracts/websocket/listener/StreamListener.java index 0bd0b6ba..62f728da 100644 --- a/src/main/java/io/github/mainstringargs/abstracts/websocket/listener/StreamListener.java +++ b/src/main/java/net/jacobpeterson/abstracts/websocket/listener/StreamListener.java @@ -1,7 +1,7 @@ -package io.github.mainstringargs.abstracts.websocket.listener; +package net.jacobpeterson.abstracts.websocket.listener; -import io.github.mainstringargs.abstracts.websocket.message.StreamMessage; -import io.github.mainstringargs.abstracts.websocket.message.StreamMessageType; +import net.jacobpeterson.abstracts.websocket.message.StreamMessage; +import net.jacobpeterson.abstracts.websocket.message.StreamMessageType; /** * The type Stream listener. diff --git a/src/main/java/io/github/mainstringargs/abstracts/websocket/message/StreamMessage.java b/src/main/java/net/jacobpeterson/abstracts/websocket/message/StreamMessage.java similarity index 53% rename from src/main/java/io/github/mainstringargs/abstracts/websocket/message/StreamMessage.java rename to src/main/java/net/jacobpeterson/abstracts/websocket/message/StreamMessage.java index c57c48a1..f0721826 100644 --- a/src/main/java/io/github/mainstringargs/abstracts/websocket/message/StreamMessage.java +++ b/src/main/java/net/jacobpeterson/abstracts/websocket/message/StreamMessage.java @@ -1,4 +1,4 @@ -package io.github.mainstringargs.abstracts.websocket.message; +package net.jacobpeterson.abstracts.websocket.message; /** * The type stream message. diff --git a/src/main/java/io/github/mainstringargs/abstracts/websocket/message/StreamMessageType.java b/src/main/java/net/jacobpeterson/abstracts/websocket/message/StreamMessageType.java similarity index 56% rename from src/main/java/io/github/mainstringargs/abstracts/websocket/message/StreamMessageType.java rename to src/main/java/net/jacobpeterson/abstracts/websocket/message/StreamMessageType.java index 55c5cd8e..0f808b72 100644 --- a/src/main/java/io/github/mainstringargs/abstracts/websocket/message/StreamMessageType.java +++ b/src/main/java/net/jacobpeterson/abstracts/websocket/message/StreamMessageType.java @@ -1,4 +1,4 @@ -package io.github.mainstringargs.abstracts.websocket.message; +package net.jacobpeterson.abstracts.websocket.message; /** * The type stream message type. diff --git a/src/main/java/io/github/mainstringargs/alpaca/AlpacaAPI.java b/src/main/java/net/jacobpeterson/alpaca/AlpacaAPI.java similarity index 96% rename from src/main/java/io/github/mainstringargs/alpaca/AlpacaAPI.java rename to src/main/java/net/jacobpeterson/alpaca/AlpacaAPI.java index 5e6a627d..30f5da81 100644 --- a/src/main/java/io/github/mainstringargs/alpaca/AlpacaAPI.java +++ b/src/main/java/net/jacobpeterson/alpaca/AlpacaAPI.java @@ -1,4 +1,4 @@ -package io.github.mainstringargs.alpaca; +package net.jacobpeterson.alpaca; import com.google.common.base.Preconditions; import com.google.gson.JsonArray; @@ -7,42 +7,42 @@ import com.google.gson.JsonPrimitive; import com.google.gson.reflect.TypeToken; import com.mashape.unirest.http.HttpResponse; -import io.github.mainstringargs.alpaca.enums.ActivityType; -import io.github.mainstringargs.alpaca.enums.AssetStatus; -import io.github.mainstringargs.alpaca.enums.BarsTimeFrame; -import io.github.mainstringargs.alpaca.enums.Direction; -import io.github.mainstringargs.alpaca.enums.OrderClass; -import io.github.mainstringargs.alpaca.enums.OrderSide; -import io.github.mainstringargs.alpaca.enums.OrderStatus; -import io.github.mainstringargs.alpaca.enums.OrderTimeInForce; -import io.github.mainstringargs.alpaca.enums.OrderType; -import io.github.mainstringargs.alpaca.enums.PortfolioPeriodUnit; -import io.github.mainstringargs.alpaca.enums.PortfolioTimeFrame; -import io.github.mainstringargs.alpaca.properties.AlpacaProperties; -import io.github.mainstringargs.alpaca.rest.AlpacaRequest; -import io.github.mainstringargs.alpaca.rest.AlpacaRequestBuilder; -import io.github.mainstringargs.alpaca.rest.exception.AlpacaAPIRequestException; -import io.github.mainstringargs.alpaca.websocket.broker.client.AlpacaWebsocketClient; -import io.github.mainstringargs.alpaca.websocket.broker.listener.AlpacaStreamListener; -import io.github.mainstringargs.domain.alpaca.account.Account; -import io.github.mainstringargs.domain.alpaca.accountactivities.AccountActivity; -import io.github.mainstringargs.domain.alpaca.accountactivities.NonTradeActivity; -import io.github.mainstringargs.domain.alpaca.accountactivities.TradeActivity; -import io.github.mainstringargs.domain.alpaca.accountconfiguration.AccountConfiguration; -import io.github.mainstringargs.domain.alpaca.asset.Asset; -import io.github.mainstringargs.domain.alpaca.calendar.Calendar; -import io.github.mainstringargs.domain.alpaca.clock.Clock; -import io.github.mainstringargs.domain.alpaca.marketdata.Bar; -import io.github.mainstringargs.domain.alpaca.marketdata.LastQuoteResponse; -import io.github.mainstringargs.domain.alpaca.marketdata.LastTradeResponse; -import io.github.mainstringargs.domain.alpaca.order.CancelledOrder; -import io.github.mainstringargs.domain.alpaca.order.Order; -import io.github.mainstringargs.domain.alpaca.portfoliohistory.PortfolioHistory; -import io.github.mainstringargs.domain.alpaca.position.ClosePositionOrder; -import io.github.mainstringargs.domain.alpaca.position.Position; -import io.github.mainstringargs.domain.alpaca.watchlist.Watchlist; -import io.github.mainstringargs.util.gson.GsonUtil; -import io.github.mainstringargs.util.time.TimeUtil; +import net.jacobpeterson.alpaca.enums.ActivityType; +import net.jacobpeterson.alpaca.enums.AssetStatus; +import net.jacobpeterson.alpaca.enums.BarsTimeFrame; +import net.jacobpeterson.alpaca.enums.Direction; +import net.jacobpeterson.alpaca.enums.OrderClass; +import net.jacobpeterson.alpaca.enums.OrderSide; +import net.jacobpeterson.alpaca.enums.OrderStatus; +import net.jacobpeterson.alpaca.enums.OrderTimeInForce; +import net.jacobpeterson.alpaca.enums.OrderType; +import net.jacobpeterson.alpaca.enums.PortfolioPeriodUnit; +import net.jacobpeterson.alpaca.enums.PortfolioTimeFrame; +import net.jacobpeterson.alpaca.properties.AlpacaProperties; +import net.jacobpeterson.alpaca.rest.AlpacaRequest; +import net.jacobpeterson.alpaca.rest.AlpacaRequestBuilder; +import net.jacobpeterson.alpaca.rest.exception.AlpacaAPIRequestException; +import net.jacobpeterson.alpaca.websocket.broker.client.AlpacaWebsocketClient; +import net.jacobpeterson.alpaca.websocket.broker.listener.AlpacaStreamListener; +import net.jacobpeterson.domain.alpaca.account.Account; +import net.jacobpeterson.domain.alpaca.accountactivities.AccountActivity; +import net.jacobpeterson.domain.alpaca.accountactivities.NonTradeActivity; +import net.jacobpeterson.domain.alpaca.accountactivities.TradeActivity; +import net.jacobpeterson.domain.alpaca.accountconfiguration.AccountConfiguration; +import net.jacobpeterson.domain.alpaca.asset.Asset; +import net.jacobpeterson.domain.alpaca.calendar.Calendar; +import net.jacobpeterson.domain.alpaca.clock.Clock; +import net.jacobpeterson.domain.alpaca.marketdata.Bar; +import net.jacobpeterson.domain.alpaca.marketdata.LastQuoteResponse; +import net.jacobpeterson.domain.alpaca.marketdata.LastTradeResponse; +import net.jacobpeterson.domain.alpaca.order.CancelledOrder; +import net.jacobpeterson.domain.alpaca.order.Order; +import net.jacobpeterson.domain.alpaca.portfoliohistory.PortfolioHistory; +import net.jacobpeterson.domain.alpaca.position.ClosePositionOrder; +import net.jacobpeterson.domain.alpaca.position.Position; +import net.jacobpeterson.domain.alpaca.watchlist.Watchlist; +import net.jacobpeterson.util.gson.GsonUtil; +import net.jacobpeterson.util.time.TimeUtil; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; @@ -189,7 +189,7 @@ public Account getAccount() throws AlpacaAPIRequestException { * Activities */ public ArrayList getAccountActivities(ZonedDateTime date, ZonedDateTime until, ZonedDateTime after, - Direction direction, Integer pageSize, String pageToken, ActivityType... activityTypes) + Direction direction, Integer pageSize, String pageToken, ActivityType... activityTypes) throws AlpacaAPIRequestException { AlpacaRequestBuilder urlBuilder = new AlpacaRequestBuilder(baseAPIURL, AlpacaConstants.VERSION_2_ENDPOINT, AlpacaConstants.ACCOUNT_ENDPOINT, @@ -332,7 +332,7 @@ public AccountConfiguration setAccountConfiguration(AccountConfiguration account * @see Orders */ public ArrayList getOrders(OrderStatus status, Integer limit, ZonedDateTime after, ZonedDateTime until, - Direction direction, Boolean nested) throws AlpacaAPIRequestException { + Direction direction, Boolean nested) throws AlpacaAPIRequestException { AlpacaRequestBuilder urlBuilder = new AlpacaRequestBuilder(baseAPIURL, apiVersion, AlpacaConstants.ORDERS_ENDPOINT); @@ -403,9 +403,9 @@ public ArrayList getOrders(OrderStatus status, Integer limit, ZonedDateTi * @see Orders */ public Order requestNewOrder(String symbol, Integer quantity, OrderSide side, OrderType type, - OrderTimeInForce timeInForce, Double limitPrice, Double stopPrice, Boolean extendedHours, - String clientOrderId, OrderClass orderClass, Double takeProfitLimitPrice, Double stopLossStopPrice, - Double stopLossLimitPrice) + OrderTimeInForce timeInForce, Double limitPrice, Double stopPrice, Boolean extendedHours, + String clientOrderId, OrderClass orderClass, Double takeProfitLimitPrice, Double stopLossStopPrice, + Double stopLossLimitPrice) throws AlpacaAPIRequestException { Preconditions.checkNotNull(symbol); Preconditions.checkNotNull(quantity); @@ -1347,7 +1347,7 @@ public Watchlist removeSymbolFromWatchlist(String watchlistID, String symbol) th * @throws AlpacaAPIRequestException the alpaca api request exception */ public PortfolioHistory getPortfolioHistory(Integer periodLength, PortfolioPeriodUnit periodUnit, - PortfolioTimeFrame timeFrame, LocalDate dateEnd, Boolean extendedHours) throws AlpacaAPIRequestException { + PortfolioTimeFrame timeFrame, LocalDate dateEnd, Boolean extendedHours) throws AlpacaAPIRequestException { AlpacaRequestBuilder urlBuilder = new AlpacaRequestBuilder(baseAPIURL, apiVersion, AlpacaConstants.ACCOUNT_ENDPOINT, AlpacaConstants.PORTFOLIO_ENDPOINT, @@ -1479,7 +1479,7 @@ public Clock getClock() throws AlpacaAPIRequestException { * @see Bars */ public Map> getBars(BarsTimeFrame timeframe, String symbol, Integer limit, - ZonedDateTime start, ZonedDateTime end, ZonedDateTime after, ZonedDateTime until) + ZonedDateTime start, ZonedDateTime end, ZonedDateTime after, ZonedDateTime until) throws AlpacaAPIRequestException { return this.getBars(timeframe, new String[]{symbol}, limit, start, end, after, until); } diff --git a/src/main/java/io/github/mainstringargs/alpaca/AlpacaConstants.java b/src/main/java/net/jacobpeterson/alpaca/AlpacaConstants.java similarity index 99% rename from src/main/java/io/github/mainstringargs/alpaca/AlpacaConstants.java rename to src/main/java/net/jacobpeterson/alpaca/AlpacaConstants.java index 9c74f1f0..fc9d318f 100644 --- a/src/main/java/io/github/mainstringargs/alpaca/AlpacaConstants.java +++ b/src/main/java/net/jacobpeterson/alpaca/AlpacaConstants.java @@ -1,4 +1,4 @@ -package io.github.mainstringargs.alpaca; +package net.jacobpeterson.alpaca; /** * The Class AlpacaConstants. diff --git a/src/main/java/io/github/mainstringargs/alpaca/AlpacaExample.java b/src/main/java/net/jacobpeterson/alpaca/AlpacaExample.java similarity index 80% rename from src/main/java/io/github/mainstringargs/alpaca/AlpacaExample.java rename to src/main/java/net/jacobpeterson/alpaca/AlpacaExample.java index 67da7b46..e428e896 100644 --- a/src/main/java/io/github/mainstringargs/alpaca/AlpacaExample.java +++ b/src/main/java/net/jacobpeterson/alpaca/AlpacaExample.java @@ -1,18 +1,18 @@ -package io.github.mainstringargs.alpaca; +package net.jacobpeterson.alpaca; -import io.github.mainstringargs.alpaca.enums.BarsTimeFrame; -import io.github.mainstringargs.alpaca.enums.OrderSide; -import io.github.mainstringargs.alpaca.enums.OrderTimeInForce; -import io.github.mainstringargs.alpaca.rest.exception.AlpacaAPIRequestException; -import io.github.mainstringargs.alpaca.websocket.broker.listener.AlpacaStreamListenerAdapter; -import io.github.mainstringargs.alpaca.websocket.broker.message.AlpacaStreamMessageType; -import io.github.mainstringargs.domain.alpaca.account.Account; -import io.github.mainstringargs.domain.alpaca.bar.Bar; -import io.github.mainstringargs.domain.alpaca.order.Order; -import io.github.mainstringargs.domain.alpaca.watchlist.Watchlist; -import io.github.mainstringargs.domain.alpaca.websocket.AlpacaStreamMessage; -import io.github.mainstringargs.domain.alpaca.websocket.account.AccountUpdateMessage; -import io.github.mainstringargs.domain.alpaca.websocket.trade.TradeUpdateMessage; +import net.jacobpeterson.alpaca.enums.BarsTimeFrame; +import net.jacobpeterson.alpaca.enums.OrderSide; +import net.jacobpeterson.alpaca.enums.OrderTimeInForce; +import net.jacobpeterson.alpaca.rest.exception.AlpacaAPIRequestException; +import net.jacobpeterson.alpaca.websocket.broker.listener.AlpacaStreamListenerAdapter; +import net.jacobpeterson.alpaca.websocket.broker.message.AlpacaStreamMessageType; +import net.jacobpeterson.domain.alpaca.account.Account; +import net.jacobpeterson.domain.alpaca.marketdata.Bar; +import net.jacobpeterson.domain.alpaca.order.Order; +import net.jacobpeterson.domain.alpaca.watchlist.Watchlist; +import net.jacobpeterson.domain.alpaca.streaming.AlpacaStreamMessage; +import net.jacobpeterson.domain.alpaca.streaming.account.AccountUpdateMessage; +import net.jacobpeterson.domain.alpaca.streaming.trade.TradeUpdateMessage; import java.time.Instant; import java.time.ZoneId; diff --git a/src/main/java/io/github/mainstringargs/alpaca/enums/AccountStatus.java b/src/main/java/net/jacobpeterson/alpaca/enums/AccountStatus.java similarity index 90% rename from src/main/java/io/github/mainstringargs/alpaca/enums/AccountStatus.java rename to src/main/java/net/jacobpeterson/alpaca/enums/AccountStatus.java index 6e1e6a29..da7d2a75 100644 --- a/src/main/java/io/github/mainstringargs/alpaca/enums/AccountStatus.java +++ b/src/main/java/net/jacobpeterson/alpaca/enums/AccountStatus.java @@ -1,6 +1,6 @@ -package io.github.mainstringargs.alpaca.enums; +package net.jacobpeterson.alpaca.enums; -import io.github.mainstringargs.abstracts.enums.APIName; +import net.jacobpeterson.abstracts.enums.APIName; /** * The enum Account status. diff --git a/src/main/java/io/github/mainstringargs/alpaca/enums/ActivityType.java b/src/main/java/net/jacobpeterson/alpaca/enums/ActivityType.java similarity index 94% rename from src/main/java/io/github/mainstringargs/alpaca/enums/ActivityType.java rename to src/main/java/net/jacobpeterson/alpaca/enums/ActivityType.java index ff927694..1cff6ae6 100644 --- a/src/main/java/io/github/mainstringargs/alpaca/enums/ActivityType.java +++ b/src/main/java/net/jacobpeterson/alpaca/enums/ActivityType.java @@ -1,6 +1,6 @@ -package io.github.mainstringargs.alpaca.enums; +package net.jacobpeterson.alpaca.enums; -import io.github.mainstringargs.abstracts.enums.APIName; +import net.jacobpeterson.abstracts.enums.APIName; /** * The enum Activity type. diff --git a/src/main/java/io/github/mainstringargs/alpaca/enums/AssetStatus.java b/src/main/java/net/jacobpeterson/alpaca/enums/AssetStatus.java similarity index 82% rename from src/main/java/io/github/mainstringargs/alpaca/enums/AssetStatus.java rename to src/main/java/net/jacobpeterson/alpaca/enums/AssetStatus.java index 888989e8..b043cc65 100644 --- a/src/main/java/io/github/mainstringargs/alpaca/enums/AssetStatus.java +++ b/src/main/java/net/jacobpeterson/alpaca/enums/AssetStatus.java @@ -1,6 +1,6 @@ -package io.github.mainstringargs.alpaca.enums; +package net.jacobpeterson.alpaca.enums; -import io.github.mainstringargs.abstracts.enums.APIName; +import net.jacobpeterson.abstracts.enums.APIName; /** * The Enum AssetStatus. diff --git a/src/main/java/io/github/mainstringargs/alpaca/enums/BarsTimeFrame.java b/src/main/java/net/jacobpeterson/alpaca/enums/BarsTimeFrame.java similarity index 86% rename from src/main/java/io/github/mainstringargs/alpaca/enums/BarsTimeFrame.java rename to src/main/java/net/jacobpeterson/alpaca/enums/BarsTimeFrame.java index a7295eeb..d73eb3bd 100644 --- a/src/main/java/io/github/mainstringargs/alpaca/enums/BarsTimeFrame.java +++ b/src/main/java/net/jacobpeterson/alpaca/enums/BarsTimeFrame.java @@ -1,6 +1,6 @@ -package io.github.mainstringargs.alpaca.enums; +package net.jacobpeterson.alpaca.enums; -import io.github.mainstringargs.abstracts.enums.APIName; +import net.jacobpeterson.abstracts.enums.APIName; /** * The Enum BarsTimeFrame. diff --git a/src/main/java/io/github/mainstringargs/alpaca/enums/Direction.java b/src/main/java/net/jacobpeterson/alpaca/enums/Direction.java similarity index 81% rename from src/main/java/io/github/mainstringargs/alpaca/enums/Direction.java rename to src/main/java/net/jacobpeterson/alpaca/enums/Direction.java index adca7f5f..74f0fa7b 100644 --- a/src/main/java/io/github/mainstringargs/alpaca/enums/Direction.java +++ b/src/main/java/net/jacobpeterson/alpaca/enums/Direction.java @@ -1,6 +1,6 @@ -package io.github.mainstringargs.alpaca.enums; +package net.jacobpeterson.alpaca.enums; -import io.github.mainstringargs.abstracts.enums.APIName; +import net.jacobpeterson.abstracts.enums.APIName; /** * The Enum Direction. diff --git a/src/main/java/io/github/mainstringargs/alpaca/enums/OrderClass.java b/src/main/java/net/jacobpeterson/alpaca/enums/OrderClass.java similarity index 87% rename from src/main/java/io/github/mainstringargs/alpaca/enums/OrderClass.java rename to src/main/java/net/jacobpeterson/alpaca/enums/OrderClass.java index a0613c77..9db37313 100644 --- a/src/main/java/io/github/mainstringargs/alpaca/enums/OrderClass.java +++ b/src/main/java/net/jacobpeterson/alpaca/enums/OrderClass.java @@ -1,6 +1,6 @@ -package io.github.mainstringargs.alpaca.enums; +package net.jacobpeterson.alpaca.enums; -import io.github.mainstringargs.abstracts.enums.APIName; +import net.jacobpeterson.abstracts.enums.APIName; /** * The enum Account status. diff --git a/src/main/java/io/github/mainstringargs/alpaca/enums/OrderSide.java b/src/main/java/net/jacobpeterson/alpaca/enums/OrderSide.java similarity index 81% rename from src/main/java/io/github/mainstringargs/alpaca/enums/OrderSide.java rename to src/main/java/net/jacobpeterson/alpaca/enums/OrderSide.java index cbc7fc6f..6e01a342 100644 --- a/src/main/java/io/github/mainstringargs/alpaca/enums/OrderSide.java +++ b/src/main/java/net/jacobpeterson/alpaca/enums/OrderSide.java @@ -1,6 +1,6 @@ -package io.github.mainstringargs.alpaca.enums; +package net.jacobpeterson.alpaca.enums; -import io.github.mainstringargs.abstracts.enums.APIName; +import net.jacobpeterson.abstracts.enums.APIName; /** * The Enum OrderSide. diff --git a/src/main/java/io/github/mainstringargs/alpaca/enums/OrderStatus.java b/src/main/java/net/jacobpeterson/alpaca/enums/OrderStatus.java similarity index 82% rename from src/main/java/io/github/mainstringargs/alpaca/enums/OrderStatus.java rename to src/main/java/net/jacobpeterson/alpaca/enums/OrderStatus.java index cefbbe83..4661eb10 100644 --- a/src/main/java/io/github/mainstringargs/alpaca/enums/OrderStatus.java +++ b/src/main/java/net/jacobpeterson/alpaca/enums/OrderStatus.java @@ -1,6 +1,6 @@ -package io.github.mainstringargs.alpaca.enums; +package net.jacobpeterson.alpaca.enums; -import io.github.mainstringargs.abstracts.enums.APIName; +import net.jacobpeterson.abstracts.enums.APIName; /** * The Enum OrderStatus. diff --git a/src/main/java/io/github/mainstringargs/alpaca/enums/OrderTimeInForce.java b/src/main/java/net/jacobpeterson/alpaca/enums/OrderTimeInForce.java similarity index 86% rename from src/main/java/io/github/mainstringargs/alpaca/enums/OrderTimeInForce.java rename to src/main/java/net/jacobpeterson/alpaca/enums/OrderTimeInForce.java index 8fbf48fe..a476eaca 100644 --- a/src/main/java/io/github/mainstringargs/alpaca/enums/OrderTimeInForce.java +++ b/src/main/java/net/jacobpeterson/alpaca/enums/OrderTimeInForce.java @@ -1,6 +1,6 @@ -package io.github.mainstringargs.alpaca.enums; +package net.jacobpeterson.alpaca.enums; -import io.github.mainstringargs.abstracts.enums.APIName; +import net.jacobpeterson.abstracts.enums.APIName; /** * The Enum OrderTimeInForce. diff --git a/src/main/java/io/github/mainstringargs/alpaca/enums/OrderType.java b/src/main/java/net/jacobpeterson/alpaca/enums/OrderType.java similarity index 84% rename from src/main/java/io/github/mainstringargs/alpaca/enums/OrderType.java rename to src/main/java/net/jacobpeterson/alpaca/enums/OrderType.java index 0f2cc3ba..fc198fad 100644 --- a/src/main/java/io/github/mainstringargs/alpaca/enums/OrderType.java +++ b/src/main/java/net/jacobpeterson/alpaca/enums/OrderType.java @@ -1,6 +1,6 @@ -package io.github.mainstringargs.alpaca.enums; +package net.jacobpeterson.alpaca.enums; -import io.github.mainstringargs.abstracts.enums.APIName; +import net.jacobpeterson.abstracts.enums.APIName; /** * The Enum OrderType. diff --git a/src/main/java/io/github/mainstringargs/alpaca/enums/PortfolioPeriodUnit.java b/src/main/java/net/jacobpeterson/alpaca/enums/PortfolioPeriodUnit.java similarity index 85% rename from src/main/java/io/github/mainstringargs/alpaca/enums/PortfolioPeriodUnit.java rename to src/main/java/net/jacobpeterson/alpaca/enums/PortfolioPeriodUnit.java index 7ed92f09..35e640ae 100644 --- a/src/main/java/io/github/mainstringargs/alpaca/enums/PortfolioPeriodUnit.java +++ b/src/main/java/net/jacobpeterson/alpaca/enums/PortfolioPeriodUnit.java @@ -1,6 +1,6 @@ -package io.github.mainstringargs.alpaca.enums; +package net.jacobpeterson.alpaca.enums; -import io.github.mainstringargs.abstracts.enums.APIName; +import net.jacobpeterson.abstracts.enums.APIName; /** * The enum Portfolio period unit. diff --git a/src/main/java/io/github/mainstringargs/alpaca/enums/PortfolioTimeFrame.java b/src/main/java/net/jacobpeterson/alpaca/enums/PortfolioTimeFrame.java similarity index 80% rename from src/main/java/io/github/mainstringargs/alpaca/enums/PortfolioTimeFrame.java rename to src/main/java/net/jacobpeterson/alpaca/enums/PortfolioTimeFrame.java index 5b408f96..236d819f 100644 --- a/src/main/java/io/github/mainstringargs/alpaca/enums/PortfolioTimeFrame.java +++ b/src/main/java/net/jacobpeterson/alpaca/enums/PortfolioTimeFrame.java @@ -1,8 +1,8 @@ -package io.github.mainstringargs.alpaca.enums; +package net.jacobpeterson.alpaca.enums; import com.google.gson.annotations.SerializedName; -import io.github.mainstringargs.abstracts.enums.APIName; -import io.github.mainstringargs.util.gson.GsonUtil; +import net.jacobpeterson.abstracts.enums.APIName; +import net.jacobpeterson.util.gson.GsonUtil; public enum PortfolioTimeFrame implements APIName { diff --git a/src/main/java/io/github/mainstringargs/alpaca/properties/AlpacaProperties.java b/src/main/java/net/jacobpeterson/alpaca/properties/AlpacaProperties.java similarity index 95% rename from src/main/java/io/github/mainstringargs/alpaca/properties/AlpacaProperties.java rename to src/main/java/net/jacobpeterson/alpaca/properties/AlpacaProperties.java index c9ae9dbd..ce95c2d3 100644 --- a/src/main/java/io/github/mainstringargs/alpaca/properties/AlpacaProperties.java +++ b/src/main/java/net/jacobpeterson/alpaca/properties/AlpacaProperties.java @@ -1,8 +1,8 @@ -package io.github.mainstringargs.alpaca.properties; +package net.jacobpeterson.alpaca.properties; import java.util.StringJoiner; -import static io.github.mainstringargs.util.properties.PropertyUtil.getProperty; +import static net.jacobpeterson.util.properties.PropertyUtil.getProperty; /** * The Class AlpacaProperties. diff --git a/src/main/java/io/github/mainstringargs/alpaca/rest/AlpacaRequest.java b/src/main/java/net/jacobpeterson/alpaca/rest/AlpacaRequest.java similarity index 81% rename from src/main/java/io/github/mainstringargs/alpaca/rest/AlpacaRequest.java rename to src/main/java/net/jacobpeterson/alpaca/rest/AlpacaRequest.java index ca026cb6..883bb00d 100644 --- a/src/main/java/io/github/mainstringargs/alpaca/rest/AlpacaRequest.java +++ b/src/main/java/net/jacobpeterson/alpaca/rest/AlpacaRequest.java @@ -1,7 +1,7 @@ -package io.github.mainstringargs.alpaca.rest; +package net.jacobpeterson.alpaca.rest; -import io.github.mainstringargs.abstracts.rest.AbstractRequest; -import io.github.mainstringargs.alpaca.properties.AlpacaProperties; +import net.jacobpeterson.abstracts.rest.AbstractRequest; +import net.jacobpeterson.alpaca.properties.AlpacaProperties; /** * The Class AlpacaRequest. diff --git a/src/main/java/io/github/mainstringargs/alpaca/rest/AlpacaRequestBuilder.java b/src/main/java/net/jacobpeterson/alpaca/rest/AlpacaRequestBuilder.java similarity index 84% rename from src/main/java/io/github/mainstringargs/alpaca/rest/AlpacaRequestBuilder.java rename to src/main/java/net/jacobpeterson/alpaca/rest/AlpacaRequestBuilder.java index 927955d0..d6a0eabe 100644 --- a/src/main/java/io/github/mainstringargs/alpaca/rest/AlpacaRequestBuilder.java +++ b/src/main/java/net/jacobpeterson/alpaca/rest/AlpacaRequestBuilder.java @@ -1,6 +1,6 @@ -package io.github.mainstringargs.alpaca.rest; +package net.jacobpeterson.alpaca.rest; -import io.github.mainstringargs.abstracts.rest.AbstractRequestBuilder; +import net.jacobpeterson.abstracts.rest.AbstractRequestBuilder; import java.util.Arrays; diff --git a/src/main/java/io/github/mainstringargs/alpaca/rest/exception/AlpacaAPIRequestException.java b/src/main/java/net/jacobpeterson/alpaca/rest/exception/AlpacaAPIRequestException.java similarity index 79% rename from src/main/java/io/github/mainstringargs/alpaca/rest/exception/AlpacaAPIRequestException.java rename to src/main/java/net/jacobpeterson/alpaca/rest/exception/AlpacaAPIRequestException.java index 2dfc600a..42414399 100644 --- a/src/main/java/io/github/mainstringargs/alpaca/rest/exception/AlpacaAPIRequestException.java +++ b/src/main/java/net/jacobpeterson/alpaca/rest/exception/AlpacaAPIRequestException.java @@ -1,7 +1,7 @@ -package io.github.mainstringargs.alpaca.rest.exception; +package net.jacobpeterson.alpaca.rest.exception; import com.mashape.unirest.http.HttpResponse; -import io.github.mainstringargs.abstracts.rest.exception.AbstractAPIRequestException; +import net.jacobpeterson.abstracts.rest.exception.AbstractAPIRequestException; import java.io.InputStream; diff --git a/src/main/java/io/github/mainstringargs/alpaca/websocket/broker/client/AlpacaWebsocketClient.java b/src/main/java/net/jacobpeterson/alpaca/websocket/broker/client/AlpacaWebsocketClient.java similarity index 90% rename from src/main/java/io/github/mainstringargs/alpaca/websocket/broker/client/AlpacaWebsocketClient.java rename to src/main/java/net/jacobpeterson/alpaca/websocket/broker/client/AlpacaWebsocketClient.java index 96cb1c34..ba4d9405 100644 --- a/src/main/java/io/github/mainstringargs/alpaca/websocket/broker/client/AlpacaWebsocketClient.java +++ b/src/main/java/net/jacobpeterson/alpaca/websocket/broker/client/AlpacaWebsocketClient.java @@ -1,4 +1,4 @@ -package io.github.mainstringargs.alpaca.websocket.broker.client; +package net.jacobpeterson.alpaca.websocket.broker.client; import com.google.common.base.Preconditions; import com.google.gson.JsonArray; @@ -6,18 +6,18 @@ import com.google.gson.JsonObject; import com.google.gson.JsonPrimitive; import com.google.gson.JsonSyntaxException; -import io.github.mainstringargs.abstracts.websocket.client.WebsocketClient; -import io.github.mainstringargs.abstracts.websocket.listener.StreamListener; -import io.github.mainstringargs.abstracts.websocket.message.StreamMessage; -import io.github.mainstringargs.abstracts.websocket.message.StreamMessageType; -import io.github.mainstringargs.alpaca.websocket.broker.listener.AlpacaStreamListener; -import io.github.mainstringargs.alpaca.websocket.broker.message.AlpacaStreamMessageType; -import io.github.mainstringargs.domain.alpaca.streaming.AlpacaStreamMessage; -import io.github.mainstringargs.domain.alpaca.streaming.account.AccountUpdateMessage; -import io.github.mainstringargs.domain.alpaca.streaming.authorization.AuthorizationMessage; -import io.github.mainstringargs.domain.alpaca.streaming.listening.ListeningMessage; -import io.github.mainstringargs.domain.alpaca.streaming.trade.TradeUpdateMessage; -import io.github.mainstringargs.util.gson.GsonUtil; +import net.jacobpeterson.abstracts.websocket.client.WebsocketClient; +import net.jacobpeterson.abstracts.websocket.listener.StreamListener; +import net.jacobpeterson.abstracts.websocket.message.StreamMessage; +import net.jacobpeterson.abstracts.websocket.message.StreamMessageType; +import net.jacobpeterson.alpaca.websocket.broker.listener.AlpacaStreamListener; +import net.jacobpeterson.alpaca.websocket.broker.message.AlpacaStreamMessageType; +import net.jacobpeterson.domain.alpaca.streaming.AlpacaStreamMessage; +import net.jacobpeterson.domain.alpaca.streaming.account.AccountUpdateMessage; +import net.jacobpeterson.domain.alpaca.streaming.authorization.AuthorizationMessage; +import net.jacobpeterson.domain.alpaca.streaming.listening.ListeningMessage; +import net.jacobpeterson.domain.alpaca.streaming.trade.TradeUpdateMessage; +import net.jacobpeterson.util.gson.GsonUtil; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; diff --git a/src/main/java/io/github/mainstringargs/alpaca/websocket/broker/client/AlpacaWebsocketClientEndpoint.java b/src/main/java/net/jacobpeterson/alpaca/websocket/broker/client/AlpacaWebsocketClientEndpoint.java similarity index 83% rename from src/main/java/io/github/mainstringargs/alpaca/websocket/broker/client/AlpacaWebsocketClientEndpoint.java rename to src/main/java/net/jacobpeterson/alpaca/websocket/broker/client/AlpacaWebsocketClientEndpoint.java index 3fe3a769..a9175f3d 100644 --- a/src/main/java/io/github/mainstringargs/alpaca/websocket/broker/client/AlpacaWebsocketClientEndpoint.java +++ b/src/main/java/net/jacobpeterson/alpaca/websocket/broker/client/AlpacaWebsocketClientEndpoint.java @@ -1,7 +1,7 @@ -package io.github.mainstringargs.alpaca.websocket.broker.client; +package net.jacobpeterson.alpaca.websocket.broker.client; -import io.github.mainstringargs.abstracts.websocket.client.AbstractWebsocketClientEndpoint; -import io.github.mainstringargs.abstracts.websocket.client.WebsocketClient; +import net.jacobpeterson.abstracts.websocket.client.AbstractWebsocketClientEndpoint; +import net.jacobpeterson.abstracts.websocket.client.WebsocketClient; import javax.websocket.ClientEndpoint; import javax.websocket.CloseReason; diff --git a/src/main/java/io/github/mainstringargs/alpaca/websocket/broker/listener/AlpacaStreamListener.java b/src/main/java/net/jacobpeterson/alpaca/websocket/broker/listener/AlpacaStreamListener.java similarity index 60% rename from src/main/java/io/github/mainstringargs/alpaca/websocket/broker/listener/AlpacaStreamListener.java rename to src/main/java/net/jacobpeterson/alpaca/websocket/broker/listener/AlpacaStreamListener.java index 949add03..0c009096 100644 --- a/src/main/java/io/github/mainstringargs/alpaca/websocket/broker/listener/AlpacaStreamListener.java +++ b/src/main/java/net/jacobpeterson/alpaca/websocket/broker/listener/AlpacaStreamListener.java @@ -1,8 +1,8 @@ -package io.github.mainstringargs.alpaca.websocket.broker.listener; +package net.jacobpeterson.alpaca.websocket.broker.listener; -import io.github.mainstringargs.abstracts.websocket.listener.StreamListener; -import io.github.mainstringargs.alpaca.websocket.broker.message.AlpacaStreamMessageType; -import io.github.mainstringargs.domain.alpaca.streaming.AlpacaStreamMessage; +import net.jacobpeterson.abstracts.websocket.listener.StreamListener; +import net.jacobpeterson.alpaca.websocket.broker.message.AlpacaStreamMessageType; +import net.jacobpeterson.domain.alpaca.streaming.AlpacaStreamMessage; import java.util.Set; diff --git a/src/main/java/io/github/mainstringargs/alpaca/websocket/broker/listener/AlpacaStreamListenerAdapter.java b/src/main/java/net/jacobpeterson/alpaca/websocket/broker/listener/AlpacaStreamListenerAdapter.java similarity index 80% rename from src/main/java/io/github/mainstringargs/alpaca/websocket/broker/listener/AlpacaStreamListenerAdapter.java rename to src/main/java/net/jacobpeterson/alpaca/websocket/broker/listener/AlpacaStreamListenerAdapter.java index acade998..6b84cc99 100644 --- a/src/main/java/io/github/mainstringargs/alpaca/websocket/broker/listener/AlpacaStreamListenerAdapter.java +++ b/src/main/java/net/jacobpeterson/alpaca/websocket/broker/listener/AlpacaStreamListenerAdapter.java @@ -1,7 +1,7 @@ -package io.github.mainstringargs.alpaca.websocket.broker.listener; +package net.jacobpeterson.alpaca.websocket.broker.listener; -import io.github.mainstringargs.alpaca.websocket.broker.message.AlpacaStreamMessageType; -import io.github.mainstringargs.domain.alpaca.streaming.AlpacaStreamMessage; +import net.jacobpeterson.alpaca.websocket.broker.message.AlpacaStreamMessageType; +import net.jacobpeterson.domain.alpaca.streaming.AlpacaStreamMessage; import java.util.Arrays; import java.util.HashSet; diff --git a/src/main/java/io/github/mainstringargs/alpaca/websocket/broker/message/AlpacaStreamMessageType.java b/src/main/java/net/jacobpeterson/alpaca/websocket/broker/message/AlpacaStreamMessageType.java similarity index 83% rename from src/main/java/io/github/mainstringargs/alpaca/websocket/broker/message/AlpacaStreamMessageType.java rename to src/main/java/net/jacobpeterson/alpaca/websocket/broker/message/AlpacaStreamMessageType.java index a4345def..6d1cbb4e 100644 --- a/src/main/java/io/github/mainstringargs/alpaca/websocket/broker/message/AlpacaStreamMessageType.java +++ b/src/main/java/net/jacobpeterson/alpaca/websocket/broker/message/AlpacaStreamMessageType.java @@ -1,9 +1,9 @@ -package io.github.mainstringargs.alpaca.websocket.broker.message; +package net.jacobpeterson.alpaca.websocket.broker.message; import com.google.gson.annotations.SerializedName; -import io.github.mainstringargs.abstracts.enums.APIName; -import io.github.mainstringargs.abstracts.websocket.message.StreamMessageType; -import io.github.mainstringargs.util.gson.GsonUtil; +import net.jacobpeterson.abstracts.enums.APIName; +import net.jacobpeterson.abstracts.websocket.message.StreamMessageType; +import net.jacobpeterson.util.gson.GsonUtil; /** * The enum Alpaca stream message type. diff --git a/src/main/java/io/github/mainstringargs/alpaca/websocket/marketdata/client/MarketDataWebsocketClient.java b/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/client/MarketDataWebsocketClient.java similarity index 85% rename from src/main/java/io/github/mainstringargs/alpaca/websocket/marketdata/client/MarketDataWebsocketClient.java rename to src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/client/MarketDataWebsocketClient.java index bc02ffd4..9cd80184 100644 --- a/src/main/java/io/github/mainstringargs/alpaca/websocket/marketdata/client/MarketDataWebsocketClient.java +++ b/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/client/MarketDataWebsocketClient.java @@ -1,4 +1,4 @@ -package io.github.mainstringargs.alpaca.websocket.marketdata.client; +package net.jacobpeterson.alpaca.websocket.marketdata.client; import com.google.common.base.Preconditions; import com.google.gson.JsonArray; @@ -6,22 +6,21 @@ import com.google.gson.JsonObject; import com.google.gson.JsonParseException; import com.google.gson.JsonPrimitive; -import io.github.mainstringargs.abstracts.websocket.client.WebsocketClient; -import io.github.mainstringargs.abstracts.websocket.listener.StreamListener; -import io.github.mainstringargs.abstracts.websocket.message.StreamMessage; -import io.github.mainstringargs.abstracts.websocket.message.StreamMessageType; -import io.github.mainstringargs.alpaca.websocket.broker.listener.AlpacaStreamListener; -import io.github.mainstringargs.alpaca.websocket.marketdata.listener.MarketDataStreamListener; -import io.github.mainstringargs.alpaca.websocket.marketdata.message.MarketDataStreamMessageType; -import io.github.mainstringargs.domain.alpaca.marketdata.streaming.MarketDataStreamDataMessage; -import io.github.mainstringargs.domain.alpaca.marketdata.streaming.MarketDataStreamMessage; -import io.github.mainstringargs.domain.alpaca.marketdata.streaming.MarketDataStreamStatusMessage; -import io.github.mainstringargs.domain.alpaca.marketdata.streaming.aggregate.AggregateMinuteMessage; -import io.github.mainstringargs.domain.alpaca.marketdata.streaming.authorization.AuthorizationMessage; -import io.github.mainstringargs.domain.alpaca.marketdata.streaming.listening.ListeningMessage; -import io.github.mainstringargs.domain.alpaca.marketdata.streaming.quote.QuoteMessage; -import io.github.mainstringargs.domain.alpaca.marketdata.streaming.trade.TradeMessage; -import io.github.mainstringargs.util.gson.GsonUtil; +import net.jacobpeterson.abstracts.websocket.client.WebsocketClient; +import net.jacobpeterson.abstracts.websocket.listener.StreamListener; +import net.jacobpeterson.abstracts.websocket.message.StreamMessage; +import net.jacobpeterson.abstracts.websocket.message.StreamMessageType; +import net.jacobpeterson.alpaca.websocket.marketdata.listener.MarketDataStreamListener; +import net.jacobpeterson.alpaca.websocket.marketdata.message.MarketDataStreamMessageType; +import net.jacobpeterson.domain.alpaca.marketdata.streaming.abstracts.MarketDataStreamDataMessage; +import net.jacobpeterson.domain.alpaca.marketdata.streaming.MarketDataStreamMessage; +import net.jacobpeterson.domain.alpaca.marketdata.streaming.abstracts.MarketDataStreamStatusMessage; +import net.jacobpeterson.domain.alpaca.marketdata.streaming.aggregate.AggregateMinuteMessage; +import net.jacobpeterson.domain.alpaca.marketdata.streaming.authorization.AuthorizationMessage; +import net.jacobpeterson.domain.alpaca.marketdata.streaming.listening.ListeningMessage; +import net.jacobpeterson.domain.alpaca.marketdata.streaming.quote.QuoteMessage; +import net.jacobpeterson.domain.alpaca.marketdata.streaming.trade.TradeMessage; +import net.jacobpeterson.util.gson.GsonUtil; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; @@ -321,15 +320,16 @@ private void submitStreamRequest() { * * @return the registered message types */ - public Set getRegisteredMessageTypes() { - Set registeredStreamMessageTypes = new HashSet<>(); + public Set getRegisteredMessageTypes() { + Set registeredStreamMessageTypes = new HashSet<>(); - for (AlpacaStreamListener alpacaStreamListener : listeners) { - Set alpacaStreamMessageTypes = alpacaStreamListener.getStreamMessageTypes(); + for (MarketDataStreamListener marketDataStreamListener : listeners) { + Set marketDataStreamMessageTypes = new HashSet<>(); + marketDataStreamListener.getDataStreams().forEach((s, m) -> marketDataStreamMessageTypes.addAll(m)); // if its empty, assume they want everything - Set streamMessageTypesToAdd = alpacaStreamMessageTypes == null ? new HashSet<>() : - alpacaStreamMessageTypes.stream().filter(AlpacaStreamMessageType::isAPISubscribable) + Set streamMessageTypesToAdd = marketDataStreamMessageTypes.isEmpty() ? new HashSet<>() : + marketDataStreamMessageTypes.stream().filter(MarketDataStreamMessageType::isAPISubscribable) .collect(Collectors.toSet()); registeredStreamMessageTypes.addAll(streamMessageTypesToAdd); diff --git a/src/main/java/io/github/mainstringargs/alpaca/websocket/marketdata/client/MarketDataWebsocketClientEndpoint.java b/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/client/MarketDataWebsocketClientEndpoint.java similarity index 84% rename from src/main/java/io/github/mainstringargs/alpaca/websocket/marketdata/client/MarketDataWebsocketClientEndpoint.java rename to src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/client/MarketDataWebsocketClientEndpoint.java index 1e1cce86..90446613 100644 --- a/src/main/java/io/github/mainstringargs/alpaca/websocket/marketdata/client/MarketDataWebsocketClientEndpoint.java +++ b/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/client/MarketDataWebsocketClientEndpoint.java @@ -1,7 +1,7 @@ -package io.github.mainstringargs.alpaca.websocket.marketdata.client; +package net.jacobpeterson.alpaca.websocket.marketdata.client; -import io.github.mainstringargs.abstracts.websocket.client.AbstractWebsocketClientEndpoint; -import io.github.mainstringargs.abstracts.websocket.client.WebsocketClient; +import net.jacobpeterson.abstracts.websocket.client.AbstractWebsocketClientEndpoint; +import net.jacobpeterson.abstracts.websocket.client.WebsocketClient; import javax.websocket.ClientEndpoint; import javax.websocket.CloseReason; diff --git a/src/main/java/io/github/mainstringargs/alpaca/websocket/marketdata/listener/MarketDataStreamListener.java b/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/listener/MarketDataStreamListener.java similarity index 60% rename from src/main/java/io/github/mainstringargs/alpaca/websocket/marketdata/listener/MarketDataStreamListener.java rename to src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/listener/MarketDataStreamListener.java index 42869948..4616521c 100644 --- a/src/main/java/io/github/mainstringargs/alpaca/websocket/marketdata/listener/MarketDataStreamListener.java +++ b/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/listener/MarketDataStreamListener.java @@ -1,8 +1,8 @@ -package io.github.mainstringargs.alpaca.websocket.marketdata.listener; +package net.jacobpeterson.alpaca.websocket.marketdata.listener; -import io.github.mainstringargs.abstracts.websocket.listener.StreamListener; -import io.github.mainstringargs.alpaca.websocket.marketdata.message.MarketDataStreamMessageType; -import io.github.mainstringargs.domain.alpaca.marketdata.streaming.MarketDataStreamMessage; +import net.jacobpeterson.abstracts.websocket.listener.StreamListener; +import net.jacobpeterson.alpaca.websocket.marketdata.message.MarketDataStreamMessageType; +import net.jacobpeterson.domain.alpaca.marketdata.streaming.MarketDataStreamMessage; import java.util.Map; import java.util.Set; diff --git a/src/main/java/io/github/mainstringargs/alpaca/websocket/marketdata/listener/MarketDataStreamListenerAdapter.java b/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/listener/MarketDataStreamListenerAdapter.java similarity index 89% rename from src/main/java/io/github/mainstringargs/alpaca/websocket/marketdata/listener/MarketDataStreamListenerAdapter.java rename to src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/listener/MarketDataStreamListenerAdapter.java index a4970dc4..aee3bac8 100644 --- a/src/main/java/io/github/mainstringargs/alpaca/websocket/marketdata/listener/MarketDataStreamListenerAdapter.java +++ b/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/listener/MarketDataStreamListenerAdapter.java @@ -1,7 +1,7 @@ -package io.github.mainstringargs.alpaca.websocket.marketdata.listener; +package net.jacobpeterson.alpaca.websocket.marketdata.listener; -import io.github.mainstringargs.alpaca.websocket.marketdata.message.MarketDataStreamMessageType; -import io.github.mainstringargs.domain.alpaca.marketdata.streaming.MarketDataStreamMessage; +import net.jacobpeterson.alpaca.websocket.marketdata.message.MarketDataStreamMessageType; +import net.jacobpeterson.domain.alpaca.marketdata.streaming.MarketDataStreamMessage; import java.util.Arrays; import java.util.HashMap; diff --git a/src/main/java/io/github/mainstringargs/alpaca/websocket/marketdata/message/MarketDataStreamMessageType.java b/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/message/MarketDataStreamMessageType.java similarity index 83% rename from src/main/java/io/github/mainstringargs/alpaca/websocket/marketdata/message/MarketDataStreamMessageType.java rename to src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/message/MarketDataStreamMessageType.java index 2b4fb7e7..5fd4b215 100644 --- a/src/main/java/io/github/mainstringargs/alpaca/websocket/marketdata/message/MarketDataStreamMessageType.java +++ b/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/message/MarketDataStreamMessageType.java @@ -1,9 +1,9 @@ -package io.github.mainstringargs.alpaca.websocket.marketdata.message; +package net.jacobpeterson.alpaca.websocket.marketdata.message; import com.google.gson.annotations.SerializedName; -import io.github.mainstringargs.abstracts.enums.APIName; -import io.github.mainstringargs.abstracts.websocket.message.StreamMessageType; -import io.github.mainstringargs.util.gson.GsonUtil; +import net.jacobpeterson.abstracts.enums.APIName; +import net.jacobpeterson.abstracts.websocket.message.StreamMessageType; +import net.jacobpeterson.util.gson.GsonUtil; /** * The enum Market data stream message type. diff --git a/src/main/java/io/github/mainstringargs/polygon/PolygonAPI.java b/src/main/java/net/jacobpeterson/polygon/PolygonAPI.java similarity index 91% rename from src/main/java/io/github/mainstringargs/polygon/PolygonAPI.java rename to src/main/java/net/jacobpeterson/polygon/PolygonAPI.java index 30b1a9ab..8f9ee005 100644 --- a/src/main/java/io/github/mainstringargs/polygon/PolygonAPI.java +++ b/src/main/java/net/jacobpeterson/polygon/PolygonAPI.java @@ -1,48 +1,48 @@ -package io.github.mainstringargs.polygon; +package net.jacobpeterson.polygon; import com.google.common.base.Preconditions; import com.google.gson.reflect.TypeToken; import com.mashape.unirest.http.HttpResponse; -import io.github.mainstringargs.domain.polygon.aggregates.AggregatesResponse; -import io.github.mainstringargs.domain.polygon.conditionsmapping.ConditionsMapping; -import io.github.mainstringargs.domain.polygon.dailyopenclose.DailyOpenCloseResponse; -import io.github.mainstringargs.domain.polygon.exchanges.Exchange; -import io.github.mainstringargs.domain.polygon.groupeddaily.GroupedDailyResponse; -import io.github.mainstringargs.domain.polygon.historicquotes.HistoricQuotesResponse; -import io.github.mainstringargs.domain.polygon.historictrades.HistoricTradesResponse; -import io.github.mainstringargs.domain.polygon.lastquote.LastQuoteResponse; -import io.github.mainstringargs.domain.polygon.lasttrade.LastTradeResponse; -import io.github.mainstringargs.domain.polygon.locales.LocalesResponse; -import io.github.mainstringargs.domain.polygon.marketholidays.MarketHoliday; -import io.github.mainstringargs.domain.polygon.markets.MarketsResponse; -import io.github.mainstringargs.domain.polygon.marketstatus.MarketStatus; -import io.github.mainstringargs.domain.polygon.previousclose.PreviousCloseResponse; -import io.github.mainstringargs.domain.polygon.snapshot.SnapshotAllTickersResponse; -import io.github.mainstringargs.domain.polygon.snapshot.SnapshotGainersLosersResponse; -import io.github.mainstringargs.domain.polygon.snapshot.SnapshotSingleTickerResponse; -import io.github.mainstringargs.domain.polygon.snapshot.SnapshotTickerBook; -import io.github.mainstringargs.domain.polygon.stockdividends.StockDividendsResponse; -import io.github.mainstringargs.domain.polygon.stockfinancials.StockFinancialsResponse; -import io.github.mainstringargs.domain.polygon.stocksplits.StockSplitsResponse; -import io.github.mainstringargs.domain.polygon.tickerdetails.TickerDetails; -import io.github.mainstringargs.domain.polygon.tickernews.TickerNews; -import io.github.mainstringargs.domain.polygon.tickers.TickersResponse; -import io.github.mainstringargs.domain.polygon.tickertypes.TickerTypes; -import io.github.mainstringargs.polygon.enums.ConditionMappingsType; -import io.github.mainstringargs.polygon.enums.FinancialReportType; -import io.github.mainstringargs.polygon.enums.FinancialSort; -import io.github.mainstringargs.polygon.enums.GainersLosersDirection; -import io.github.mainstringargs.polygon.enums.Market; -import io.github.mainstringargs.polygon.enums.StockType; -import io.github.mainstringargs.polygon.enums.TickerSort; -import io.github.mainstringargs.polygon.enums.Timespan; -import io.github.mainstringargs.polygon.properties.PolygonProperties; -import io.github.mainstringargs.polygon.rest.PolygonRequest; -import io.github.mainstringargs.polygon.rest.PolygonRequestBuilder; -import io.github.mainstringargs.polygon.rest.exception.PolygonAPIRequestException; -import io.github.mainstringargs.polygon.websocket.client.PolygonWebsocketClient; -import io.github.mainstringargs.polygon.websocket.listener.PolygonStreamListener; -import io.github.mainstringargs.util.time.TimeUtil; +import net.jacobpeterson.domain.polygon.aggregates.AggregatesResponse; +import net.jacobpeterson.domain.polygon.conditionsmapping.ConditionsMapping; +import net.jacobpeterson.domain.polygon.dailyopenclose.DailyOpenCloseResponse; +import net.jacobpeterson.domain.polygon.exchanges.Exchange; +import net.jacobpeterson.domain.polygon.groupeddaily.GroupedDailyResponse; +import net.jacobpeterson.domain.polygon.historicquotes.HistoricQuotesResponse; +import net.jacobpeterson.domain.polygon.historictrades.HistoricTradesResponse; +import net.jacobpeterson.domain.polygon.lastquote.LastQuoteResponse; +import net.jacobpeterson.domain.polygon.lasttrade.LastTradeResponse; +import net.jacobpeterson.domain.polygon.locales.LocalesResponse; +import net.jacobpeterson.domain.polygon.marketholidays.MarketHoliday; +import net.jacobpeterson.domain.polygon.markets.MarketsResponse; +import net.jacobpeterson.domain.polygon.marketstatus.MarketStatus; +import net.jacobpeterson.domain.polygon.previousclose.PreviousCloseResponse; +import net.jacobpeterson.domain.polygon.snapshot.SnapshotAllTickersResponse; +import net.jacobpeterson.domain.polygon.snapshot.SnapshotGainersLosersResponse; +import net.jacobpeterson.domain.polygon.snapshot.SnapshotSingleTickerResponse; +import net.jacobpeterson.domain.polygon.snapshot.SnapshotTickerBook; +import net.jacobpeterson.domain.polygon.stockdividends.StockDividendsResponse; +import net.jacobpeterson.domain.polygon.stockfinancials.StockFinancialsResponse; +import net.jacobpeterson.domain.polygon.stocksplits.StockSplitsResponse; +import net.jacobpeterson.domain.polygon.tickerdetails.TickerDetails; +import net.jacobpeterson.domain.polygon.tickernews.TickerNews; +import net.jacobpeterson.domain.polygon.tickers.TickersResponse; +import net.jacobpeterson.domain.polygon.tickertypes.TickerTypes; +import net.jacobpeterson.polygon.enums.ConditionMappingsType; +import net.jacobpeterson.polygon.enums.FinancialReportType; +import net.jacobpeterson.polygon.enums.FinancialSort; +import net.jacobpeterson.polygon.enums.GainersLosersDirection; +import net.jacobpeterson.polygon.enums.Market; +import net.jacobpeterson.polygon.enums.StockType; +import net.jacobpeterson.polygon.enums.TickerSort; +import net.jacobpeterson.polygon.enums.Timespan; +import net.jacobpeterson.polygon.properties.PolygonProperties; +import net.jacobpeterson.polygon.rest.PolygonRequest; +import net.jacobpeterson.polygon.rest.PolygonRequestBuilder; +import net.jacobpeterson.polygon.rest.exception.PolygonAPIRequestException; +import net.jacobpeterson.polygon.websocket.client.PolygonWebsocketClient; +import net.jacobpeterson.polygon.websocket.listener.PolygonStreamListener; +import net.jacobpeterson.util.time.TimeUtil; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; @@ -129,7 +129,7 @@ public PolygonAPI(String baseAPIURL, String websocketURL, String keyID) { * @see Tickers */ public TickersResponse getTickers(TickerSort tickerSort, StockType stockType, Market market, String locale, - String search, Integer perpage, Integer page, Boolean active) throws PolygonAPIRequestException { + String search, Integer perpage, Integer page, Boolean active) throws PolygonAPIRequestException { PolygonRequestBuilder builder = new PolygonRequestBuilder(baseAPIURL, PolygonConstants.VERSION_2_ENDPOINT, PolygonConstants.REFERENCE_ENDPOINT, PolygonConstants.TICKERS_ENDPOINT); @@ -377,7 +377,7 @@ public StockDividendsResponse getStockDividends(String symbol) throws PolygonAPI * @see Stock Financials */ public StockFinancialsResponse getStockFinancials(String symbol, Integer limit, - FinancialReportType financialReportType, FinancialSort financialSort) throws PolygonAPIRequestException { + FinancialReportType financialReportType, FinancialSort financialSort) throws PolygonAPIRequestException { Preconditions.checkNotNull(symbol); PolygonRequestBuilder builder = new PolygonRequestBuilder(baseAPIURL, PolygonConstants.VERSION_2_ENDPOINT, diff --git a/src/main/java/io/github/mainstringargs/polygon/PolygonConstants.java b/src/main/java/net/jacobpeterson/polygon/PolygonConstants.java similarity index 99% rename from src/main/java/io/github/mainstringargs/polygon/PolygonConstants.java rename to src/main/java/net/jacobpeterson/polygon/PolygonConstants.java index 949c8d4e..ac772fb3 100644 --- a/src/main/java/io/github/mainstringargs/polygon/PolygonConstants.java +++ b/src/main/java/net/jacobpeterson/polygon/PolygonConstants.java @@ -1,4 +1,4 @@ -package io.github.mainstringargs.polygon; +package net.jacobpeterson.polygon; /** * The type Polygon constants. diff --git a/src/main/java/io/github/mainstringargs/polygon/PolygonExample.java b/src/main/java/net/jacobpeterson/polygon/PolygonExample.java similarity index 77% rename from src/main/java/io/github/mainstringargs/polygon/PolygonExample.java rename to src/main/java/net/jacobpeterson/polygon/PolygonExample.java index fdc4afdc..8b3f1543 100644 --- a/src/main/java/io/github/mainstringargs/polygon/PolygonExample.java +++ b/src/main/java/net/jacobpeterson/polygon/PolygonExample.java @@ -1,14 +1,14 @@ -package io.github.mainstringargs.polygon; +package net.jacobpeterson.polygon; -import io.github.mainstringargs.domain.polygon.aggregates.Aggregate; -import io.github.mainstringargs.domain.polygon.aggregates.AggregatesResponse; -import io.github.mainstringargs.domain.polygon.stocksplits.StockSplit; -import io.github.mainstringargs.domain.polygon.stocksplits.StockSplitsResponse; -import io.github.mainstringargs.domain.polygon.websocket.PolygonStreamMessage; -import io.github.mainstringargs.polygon.enums.Timespan; -import io.github.mainstringargs.polygon.rest.exception.PolygonAPIRequestException; -import io.github.mainstringargs.polygon.websocket.listener.PolygonStreamListenerAdapter; -import io.github.mainstringargs.polygon.websocket.message.PolygonStreamMessageType; +import net.jacobpeterson.domain.polygon.aggregates.Aggregate; +import net.jacobpeterson.domain.polygon.aggregates.AggregatesResponse; +import net.jacobpeterson.domain.polygon.stocksplits.StockSplit; +import net.jacobpeterson.domain.polygon.stocksplits.StockSplitsResponse; +import net.jacobpeterson.domain.polygon.websocket.PolygonStreamMessage; +import net.jacobpeterson.polygon.enums.Timespan; +import net.jacobpeterson.polygon.rest.exception.PolygonAPIRequestException; +import net.jacobpeterson.polygon.websocket.listener.PolygonStreamListenerAdapter; +import net.jacobpeterson.polygon.websocket.message.PolygonStreamMessageType; import java.time.LocalDate; diff --git a/src/main/java/io/github/mainstringargs/polygon/enums/ConditionMappingsType.java b/src/main/java/net/jacobpeterson/polygon/enums/ConditionMappingsType.java similarity index 83% rename from src/main/java/io/github/mainstringargs/polygon/enums/ConditionMappingsType.java rename to src/main/java/net/jacobpeterson/polygon/enums/ConditionMappingsType.java index 32241deb..aff8606a 100644 --- a/src/main/java/io/github/mainstringargs/polygon/enums/ConditionMappingsType.java +++ b/src/main/java/net/jacobpeterson/polygon/enums/ConditionMappingsType.java @@ -1,6 +1,6 @@ -package io.github.mainstringargs.polygon.enums; +package net.jacobpeterson.polygon.enums; -import io.github.mainstringargs.abstracts.enums.APIName; +import net.jacobpeterson.abstracts.enums.APIName; /** * The enum Condition mappings type. diff --git a/src/main/java/io/github/mainstringargs/polygon/enums/FinancialReportType.java b/src/main/java/net/jacobpeterson/polygon/enums/FinancialReportType.java similarity index 87% rename from src/main/java/io/github/mainstringargs/polygon/enums/FinancialReportType.java rename to src/main/java/net/jacobpeterson/polygon/enums/FinancialReportType.java index f27d386f..03a145ed 100644 --- a/src/main/java/io/github/mainstringargs/polygon/enums/FinancialReportType.java +++ b/src/main/java/net/jacobpeterson/polygon/enums/FinancialReportType.java @@ -1,6 +1,6 @@ -package io.github.mainstringargs.polygon.enums; +package net.jacobpeterson.polygon.enums; -import io.github.mainstringargs.abstracts.enums.APIName; +import net.jacobpeterson.abstracts.enums.APIName; /** * The enum Financial report type. diff --git a/src/main/java/io/github/mainstringargs/polygon/enums/FinancialSort.java b/src/main/java/net/jacobpeterson/polygon/enums/FinancialSort.java similarity index 88% rename from src/main/java/io/github/mainstringargs/polygon/enums/FinancialSort.java rename to src/main/java/net/jacobpeterson/polygon/enums/FinancialSort.java index dbb79f0a..0e933f85 100644 --- a/src/main/java/io/github/mainstringargs/polygon/enums/FinancialSort.java +++ b/src/main/java/net/jacobpeterson/polygon/enums/FinancialSort.java @@ -1,6 +1,6 @@ -package io.github.mainstringargs.polygon.enums; +package net.jacobpeterson.polygon.enums; -import io.github.mainstringargs.abstracts.enums.APIName; +import net.jacobpeterson.abstracts.enums.APIName; /** * The enum Financial sort. diff --git a/src/main/java/io/github/mainstringargs/polygon/enums/GainersLosersDirection.java b/src/main/java/net/jacobpeterson/polygon/enums/GainersLosersDirection.java similarity index 80% rename from src/main/java/io/github/mainstringargs/polygon/enums/GainersLosersDirection.java rename to src/main/java/net/jacobpeterson/polygon/enums/GainersLosersDirection.java index 50a71dff..16ce1131 100644 --- a/src/main/java/io/github/mainstringargs/polygon/enums/GainersLosersDirection.java +++ b/src/main/java/net/jacobpeterson/polygon/enums/GainersLosersDirection.java @@ -1,6 +1,6 @@ -package io.github.mainstringargs.polygon.enums; +package net.jacobpeterson.polygon.enums; -import io.github.mainstringargs.abstracts.enums.APIName; +import net.jacobpeterson.abstracts.enums.APIName; public enum GainersLosersDirection implements APIName { diff --git a/src/main/java/io/github/mainstringargs/polygon/enums/Market.java b/src/main/java/net/jacobpeterson/polygon/enums/Market.java similarity index 86% rename from src/main/java/io/github/mainstringargs/polygon/enums/Market.java rename to src/main/java/net/jacobpeterson/polygon/enums/Market.java index a05bcbe8..96324552 100644 --- a/src/main/java/io/github/mainstringargs/polygon/enums/Market.java +++ b/src/main/java/net/jacobpeterson/polygon/enums/Market.java @@ -1,6 +1,6 @@ -package io.github.mainstringargs.polygon.enums; +package net.jacobpeterson.polygon.enums; -import io.github.mainstringargs.abstracts.enums.APIName; +import net.jacobpeterson.abstracts.enums.APIName; /** * The Enum SymbolsMarket. diff --git a/src/main/java/io/github/mainstringargs/polygon/enums/StockType.java b/src/main/java/net/jacobpeterson/polygon/enums/StockType.java similarity index 81% rename from src/main/java/io/github/mainstringargs/polygon/enums/StockType.java rename to src/main/java/net/jacobpeterson/polygon/enums/StockType.java index 5a417579..d12e3d16 100644 --- a/src/main/java/io/github/mainstringargs/polygon/enums/StockType.java +++ b/src/main/java/net/jacobpeterson/polygon/enums/StockType.java @@ -1,6 +1,6 @@ -package io.github.mainstringargs.polygon.enums; +package net.jacobpeterson.polygon.enums; -import io.github.mainstringargs.abstracts.enums.APIName; +import net.jacobpeterson.abstracts.enums.APIName; /** * The Enum StockType. diff --git a/src/main/java/io/github/mainstringargs/polygon/enums/TickerSort.java b/src/main/java/net/jacobpeterson/polygon/enums/TickerSort.java similarity index 85% rename from src/main/java/io/github/mainstringargs/polygon/enums/TickerSort.java rename to src/main/java/net/jacobpeterson/polygon/enums/TickerSort.java index a6b6a694..8a884dea 100644 --- a/src/main/java/io/github/mainstringargs/polygon/enums/TickerSort.java +++ b/src/main/java/net/jacobpeterson/polygon/enums/TickerSort.java @@ -1,6 +1,6 @@ -package io.github.mainstringargs.polygon.enums; +package net.jacobpeterson.polygon.enums; -import io.github.mainstringargs.abstracts.enums.APIName; +import net.jacobpeterson.abstracts.enums.APIName; /** * The enum Ticker sort. diff --git a/src/main/java/io/github/mainstringargs/polygon/enums/Timespan.java b/src/main/java/net/jacobpeterson/polygon/enums/Timespan.java similarity index 86% rename from src/main/java/io/github/mainstringargs/polygon/enums/Timespan.java rename to src/main/java/net/jacobpeterson/polygon/enums/Timespan.java index ef9474b8..83d0f3c9 100644 --- a/src/main/java/io/github/mainstringargs/polygon/enums/Timespan.java +++ b/src/main/java/net/jacobpeterson/polygon/enums/Timespan.java @@ -1,6 +1,6 @@ -package io.github.mainstringargs.polygon.enums; +package net.jacobpeterson.polygon.enums; -import io.github.mainstringargs.abstracts.enums.APIName; +import net.jacobpeterson.abstracts.enums.APIName; /** * The Enum Timespan. diff --git a/src/main/java/io/github/mainstringargs/polygon/properties/PolygonProperties.java b/src/main/java/net/jacobpeterson/polygon/properties/PolygonProperties.java similarity index 91% rename from src/main/java/io/github/mainstringargs/polygon/properties/PolygonProperties.java rename to src/main/java/net/jacobpeterson/polygon/properties/PolygonProperties.java index aabd1779..3d165e22 100644 --- a/src/main/java/io/github/mainstringargs/polygon/properties/PolygonProperties.java +++ b/src/main/java/net/jacobpeterson/polygon/properties/PolygonProperties.java @@ -1,10 +1,10 @@ -package io.github.mainstringargs.polygon.properties; +package net.jacobpeterson.polygon.properties; -import io.github.mainstringargs.alpaca.properties.AlpacaProperties; +import net.jacobpeterson.alpaca.properties.AlpacaProperties; import java.util.StringJoiner; -import static io.github.mainstringargs.util.properties.PropertyUtil.getProperty; +import static net.jacobpeterson.util.properties.PropertyUtil.getProperty; /** * The Class AlpacaProperties. diff --git a/src/main/java/io/github/mainstringargs/polygon/rest/PolygonRequest.java b/src/main/java/net/jacobpeterson/polygon/rest/PolygonRequest.java similarity index 90% rename from src/main/java/io/github/mainstringargs/polygon/rest/PolygonRequest.java rename to src/main/java/net/jacobpeterson/polygon/rest/PolygonRequest.java index 2acc844c..d595c649 100644 --- a/src/main/java/io/github/mainstringargs/polygon/rest/PolygonRequest.java +++ b/src/main/java/net/jacobpeterson/polygon/rest/PolygonRequest.java @@ -1,9 +1,9 @@ -package io.github.mainstringargs.polygon.rest; +package net.jacobpeterson.polygon.rest; import com.mashape.unirest.http.HttpResponse; -import io.github.mainstringargs.abstracts.rest.AbstractRequest; -import io.github.mainstringargs.abstracts.rest.AbstractRequestBuilder; -import io.github.mainstringargs.polygon.properties.PolygonProperties; +import net.jacobpeterson.abstracts.rest.AbstractRequest; +import net.jacobpeterson.abstracts.rest.AbstractRequestBuilder; +import net.jacobpeterson.polygon.properties.PolygonProperties; import java.io.InputStream; diff --git a/src/main/java/io/github/mainstringargs/polygon/rest/PolygonRequestBuilder.java b/src/main/java/net/jacobpeterson/polygon/rest/PolygonRequestBuilder.java similarity index 84% rename from src/main/java/io/github/mainstringargs/polygon/rest/PolygonRequestBuilder.java rename to src/main/java/net/jacobpeterson/polygon/rest/PolygonRequestBuilder.java index fc907c85..2d379643 100644 --- a/src/main/java/io/github/mainstringargs/polygon/rest/PolygonRequestBuilder.java +++ b/src/main/java/net/jacobpeterson/polygon/rest/PolygonRequestBuilder.java @@ -1,6 +1,6 @@ -package io.github.mainstringargs.polygon.rest; +package net.jacobpeterson.polygon.rest; -import io.github.mainstringargs.abstracts.rest.AbstractRequestBuilder; +import net.jacobpeterson.abstracts.rest.AbstractRequestBuilder; import java.util.Arrays; diff --git a/src/main/java/io/github/mainstringargs/polygon/rest/exception/PolygonAPIRequestException.java b/src/main/java/net/jacobpeterson/polygon/rest/exception/PolygonAPIRequestException.java similarity index 79% rename from src/main/java/io/github/mainstringargs/polygon/rest/exception/PolygonAPIRequestException.java rename to src/main/java/net/jacobpeterson/polygon/rest/exception/PolygonAPIRequestException.java index 6c6fd219..d2e7328a 100644 --- a/src/main/java/io/github/mainstringargs/polygon/rest/exception/PolygonAPIRequestException.java +++ b/src/main/java/net/jacobpeterson/polygon/rest/exception/PolygonAPIRequestException.java @@ -1,7 +1,7 @@ -package io.github.mainstringargs.polygon.rest.exception; +package net.jacobpeterson.polygon.rest.exception; import com.mashape.unirest.http.HttpResponse; -import io.github.mainstringargs.abstracts.rest.exception.AbstractAPIRequestException; +import net.jacobpeterson.abstracts.rest.exception.AbstractAPIRequestException; import java.io.InputStream; diff --git a/src/main/java/io/github/mainstringargs/polygon/websocket/client/PolygonWebsocketClient.java b/src/main/java/net/jacobpeterson/polygon/websocket/client/PolygonWebsocketClient.java similarity index 93% rename from src/main/java/io/github/mainstringargs/polygon/websocket/client/PolygonWebsocketClient.java rename to src/main/java/net/jacobpeterson/polygon/websocket/client/PolygonWebsocketClient.java index 752ac2ab..062e2e88 100644 --- a/src/main/java/io/github/mainstringargs/polygon/websocket/client/PolygonWebsocketClient.java +++ b/src/main/java/net/jacobpeterson/polygon/websocket/client/PolygonWebsocketClient.java @@ -1,4 +1,4 @@ -package io.github.mainstringargs.polygon.websocket.client; +package net.jacobpeterson.polygon.websocket.client; import com.google.common.base.Preconditions; import com.google.gson.JsonArray; @@ -6,19 +6,19 @@ import com.google.gson.JsonObject; import com.google.gson.JsonPrimitive; import com.google.gson.JsonSyntaxException; -import io.github.mainstringargs.abstracts.websocket.client.WebsocketClient; -import io.github.mainstringargs.abstracts.websocket.listener.StreamListener; -import io.github.mainstringargs.abstracts.websocket.message.StreamMessage; -import io.github.mainstringargs.abstracts.websocket.message.StreamMessageType; -import io.github.mainstringargs.domain.polygon.websocket.PolygonStreamMessage; -import io.github.mainstringargs.domain.polygon.websocket.aggregate.AggregatePerMinuteMessage; -import io.github.mainstringargs.domain.polygon.websocket.aggregate.AggregatePerSecondMessage; -import io.github.mainstringargs.domain.polygon.websocket.quote.QuoteMessage; -import io.github.mainstringargs.domain.polygon.websocket.status.StatusMessage; -import io.github.mainstringargs.domain.polygon.websocket.trade.TradeMessage; -import io.github.mainstringargs.polygon.websocket.listener.PolygonStreamListener; -import io.github.mainstringargs.polygon.websocket.message.PolygonStreamMessageType; -import io.github.mainstringargs.util.gson.GsonUtil; +import net.jacobpeterson.abstracts.websocket.client.WebsocketClient; +import net.jacobpeterson.abstracts.websocket.listener.StreamListener; +import net.jacobpeterson.abstracts.websocket.message.StreamMessage; +import net.jacobpeterson.abstracts.websocket.message.StreamMessageType; +import net.jacobpeterson.domain.polygon.websocket.PolygonStreamMessage; +import net.jacobpeterson.domain.polygon.websocket.aggregate.AggregatePerMinuteMessage; +import net.jacobpeterson.domain.polygon.websocket.aggregate.AggregatePerSecondMessage; +import net.jacobpeterson.domain.polygon.websocket.quote.QuoteMessage; +import net.jacobpeterson.domain.polygon.websocket.status.StatusMessage; +import net.jacobpeterson.domain.polygon.websocket.trade.TradeMessage; +import net.jacobpeterson.polygon.websocket.listener.PolygonStreamListener; +import net.jacobpeterson.polygon.websocket.message.PolygonStreamMessageType; +import net.jacobpeterson.util.gson.GsonUtil; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; diff --git a/src/main/java/io/github/mainstringargs/polygon/websocket/client/PolygonWebsocketClientEndpoint.java b/src/main/java/net/jacobpeterson/polygon/websocket/client/PolygonWebsocketClientEndpoint.java similarity index 83% rename from src/main/java/io/github/mainstringargs/polygon/websocket/client/PolygonWebsocketClientEndpoint.java rename to src/main/java/net/jacobpeterson/polygon/websocket/client/PolygonWebsocketClientEndpoint.java index 0787f6b0..5b26e7dc 100644 --- a/src/main/java/io/github/mainstringargs/polygon/websocket/client/PolygonWebsocketClientEndpoint.java +++ b/src/main/java/net/jacobpeterson/polygon/websocket/client/PolygonWebsocketClientEndpoint.java @@ -1,7 +1,7 @@ -package io.github.mainstringargs.polygon.websocket.client; +package net.jacobpeterson.polygon.websocket.client; -import io.github.mainstringargs.abstracts.websocket.client.AbstractWebsocketClientEndpoint; -import io.github.mainstringargs.abstracts.websocket.client.WebsocketClient; +import net.jacobpeterson.abstracts.websocket.client.AbstractWebsocketClientEndpoint; +import net.jacobpeterson.abstracts.websocket.client.WebsocketClient; import javax.websocket.ClientEndpoint; import javax.websocket.CloseReason; diff --git a/src/main/java/io/github/mainstringargs/polygon/websocket/listener/PolygonStreamListener.java b/src/main/java/net/jacobpeterson/polygon/websocket/listener/PolygonStreamListener.java similarity index 71% rename from src/main/java/io/github/mainstringargs/polygon/websocket/listener/PolygonStreamListener.java rename to src/main/java/net/jacobpeterson/polygon/websocket/listener/PolygonStreamListener.java index cf2c2a73..9547316e 100644 --- a/src/main/java/io/github/mainstringargs/polygon/websocket/listener/PolygonStreamListener.java +++ b/src/main/java/net/jacobpeterson/polygon/websocket/listener/PolygonStreamListener.java @@ -1,8 +1,8 @@ -package io.github.mainstringargs.polygon.websocket.listener; +package net.jacobpeterson.polygon.websocket.listener; -import io.github.mainstringargs.abstracts.websocket.listener.StreamListener; -import io.github.mainstringargs.domain.polygon.websocket.PolygonStreamMessage; -import io.github.mainstringargs.polygon.websocket.message.PolygonStreamMessageType; +import net.jacobpeterson.abstracts.websocket.listener.StreamListener; +import net.jacobpeterson.domain.polygon.websocket.PolygonStreamMessage; +import net.jacobpeterson.polygon.websocket.message.PolygonStreamMessageType; import java.util.Map; import java.util.Set; diff --git a/src/main/java/io/github/mainstringargs/polygon/websocket/listener/PolygonStreamListenerAdapter.java b/src/main/java/net/jacobpeterson/polygon/websocket/listener/PolygonStreamListenerAdapter.java similarity index 91% rename from src/main/java/io/github/mainstringargs/polygon/websocket/listener/PolygonStreamListenerAdapter.java rename to src/main/java/net/jacobpeterson/polygon/websocket/listener/PolygonStreamListenerAdapter.java index e23360c2..4372b9e3 100644 --- a/src/main/java/io/github/mainstringargs/polygon/websocket/listener/PolygonStreamListenerAdapter.java +++ b/src/main/java/net/jacobpeterson/polygon/websocket/listener/PolygonStreamListenerAdapter.java @@ -1,7 +1,7 @@ -package io.github.mainstringargs.polygon.websocket.listener; +package net.jacobpeterson.polygon.websocket.listener; -import io.github.mainstringargs.domain.polygon.websocket.PolygonStreamMessage; -import io.github.mainstringargs.polygon.websocket.message.PolygonStreamMessageType; +import net.jacobpeterson.domain.polygon.websocket.PolygonStreamMessage; +import net.jacobpeterson.polygon.websocket.message.PolygonStreamMessageType; import java.util.Arrays; import java.util.HashMap; diff --git a/src/main/java/io/github/mainstringargs/polygon/websocket/message/PolygonStreamMessageType.java b/src/main/java/net/jacobpeterson/polygon/websocket/message/PolygonStreamMessageType.java similarity index 84% rename from src/main/java/io/github/mainstringargs/polygon/websocket/message/PolygonStreamMessageType.java rename to src/main/java/net/jacobpeterson/polygon/websocket/message/PolygonStreamMessageType.java index c18f8083..d82b6d3a 100644 --- a/src/main/java/io/github/mainstringargs/polygon/websocket/message/PolygonStreamMessageType.java +++ b/src/main/java/net/jacobpeterson/polygon/websocket/message/PolygonStreamMessageType.java @@ -1,9 +1,9 @@ -package io.github.mainstringargs.polygon.websocket.message; +package net.jacobpeterson.polygon.websocket.message; import com.google.gson.annotations.SerializedName; -import io.github.mainstringargs.abstracts.enums.APIName; -import io.github.mainstringargs.abstracts.websocket.message.StreamMessageType; -import io.github.mainstringargs.util.gson.GsonUtil; +import net.jacobpeterson.abstracts.enums.APIName; +import net.jacobpeterson.abstracts.websocket.message.StreamMessageType; +import net.jacobpeterson.util.gson.GsonUtil; /** * The enum Polygon stream message type. diff --git a/src/main/java/io/github/mainstringargs/util/concurrency/ExecutorTracer.java b/src/main/java/net/jacobpeterson/util/concurrency/ExecutorTracer.java similarity index 99% rename from src/main/java/io/github/mainstringargs/util/concurrency/ExecutorTracer.java rename to src/main/java/net/jacobpeterson/util/concurrency/ExecutorTracer.java index 4e32deb7..5d5dad80 100644 --- a/src/main/java/io/github/mainstringargs/util/concurrency/ExecutorTracer.java +++ b/src/main/java/net/jacobpeterson/util/concurrency/ExecutorTracer.java @@ -1,4 +1,4 @@ -package io.github.mainstringargs.util.concurrency; +package net.jacobpeterson.util.concurrency; import java.util.Collection; import java.util.List; diff --git a/src/main/java/io/github/mainstringargs/util/concurrency/ScheduledThreadPoolExecutorTracer.java b/src/main/java/net/jacobpeterson/util/concurrency/ScheduledThreadPoolExecutorTracer.java similarity index 97% rename from src/main/java/io/github/mainstringargs/util/concurrency/ScheduledThreadPoolExecutorTracer.java rename to src/main/java/net/jacobpeterson/util/concurrency/ScheduledThreadPoolExecutorTracer.java index 61bc9acd..1b9de9e5 100644 --- a/src/main/java/io/github/mainstringargs/util/concurrency/ScheduledThreadPoolExecutorTracer.java +++ b/src/main/java/net/jacobpeterson/util/concurrency/ScheduledThreadPoolExecutorTracer.java @@ -1,4 +1,4 @@ -package io.github.mainstringargs.util.concurrency; +package net.jacobpeterson.util.concurrency; import java.util.concurrent.CancellationException; import java.util.concurrent.ExecutionException; diff --git a/src/main/java/io/github/mainstringargs/util/concurrency/ThreadPoolExecutorTracer.java b/src/main/java/net/jacobpeterson/util/concurrency/ThreadPoolExecutorTracer.java similarity index 97% rename from src/main/java/io/github/mainstringargs/util/concurrency/ThreadPoolExecutorTracer.java rename to src/main/java/net/jacobpeterson/util/concurrency/ThreadPoolExecutorTracer.java index fa950de6..3109e7bf 100644 --- a/src/main/java/io/github/mainstringargs/util/concurrency/ThreadPoolExecutorTracer.java +++ b/src/main/java/net/jacobpeterson/util/concurrency/ThreadPoolExecutorTracer.java @@ -1,4 +1,4 @@ -package io.github.mainstringargs.util.concurrency; +package net.jacobpeterson.util.concurrency; import java.util.concurrent.BlockingQueue; import java.util.concurrent.CancellationException; diff --git a/src/main/java/io/github/mainstringargs/util/doc/Documentation2JSONSchema.java b/src/main/java/net/jacobpeterson/util/doc/Documentation2JSONSchema.java similarity index 99% rename from src/main/java/io/github/mainstringargs/util/doc/Documentation2JSONSchema.java rename to src/main/java/net/jacobpeterson/util/doc/Documentation2JSONSchema.java index 91d057a8..5deab579 100644 --- a/src/main/java/io/github/mainstringargs/util/doc/Documentation2JSONSchema.java +++ b/src/main/java/net/jacobpeterson/util/doc/Documentation2JSONSchema.java @@ -1,4 +1,4 @@ -package io.github.mainstringargs.util.doc; +package net.jacobpeterson.util.doc; import org.w3c.dom.Document; import org.w3c.dom.NodeList; diff --git a/src/main/java/io/github/mainstringargs/util/gson/GsonUtil.java b/src/main/java/net/jacobpeterson/util/gson/GsonUtil.java similarity index 98% rename from src/main/java/io/github/mainstringargs/util/gson/GsonUtil.java rename to src/main/java/net/jacobpeterson/util/gson/GsonUtil.java index dc89c727..6d3c03cc 100644 --- a/src/main/java/io/github/mainstringargs/util/gson/GsonUtil.java +++ b/src/main/java/net/jacobpeterson/util/gson/GsonUtil.java @@ -1,4 +1,4 @@ -package io.github.mainstringargs.util.gson; +package net.jacobpeterson.util.gson; import com.google.gson.Gson; import com.google.gson.GsonBuilder; diff --git a/src/main/java/io/github/mainstringargs/util/gson/ZonedDateTimeAdapter.java b/src/main/java/net/jacobpeterson/util/gson/ZonedDateTimeAdapter.java similarity index 96% rename from src/main/java/io/github/mainstringargs/util/gson/ZonedDateTimeAdapter.java rename to src/main/java/net/jacobpeterson/util/gson/ZonedDateTimeAdapter.java index 47927096..c7794564 100644 --- a/src/main/java/io/github/mainstringargs/util/gson/ZonedDateTimeAdapter.java +++ b/src/main/java/net/jacobpeterson/util/gson/ZonedDateTimeAdapter.java @@ -1,4 +1,4 @@ -package io.github.mainstringargs.util.gson; +package net.jacobpeterson.util.gson; import com.google.gson.JsonDeserializationContext; import com.google.gson.JsonDeserializer; diff --git a/src/main/java/io/github/mainstringargs/util/properties/PropertyUtil.java b/src/main/java/net/jacobpeterson/util/properties/PropertyUtil.java similarity index 98% rename from src/main/java/io/github/mainstringargs/util/properties/PropertyUtil.java rename to src/main/java/net/jacobpeterson/util/properties/PropertyUtil.java index 29d345e2..3ff46855 100644 --- a/src/main/java/io/github/mainstringargs/util/properties/PropertyUtil.java +++ b/src/main/java/net/jacobpeterson/util/properties/PropertyUtil.java @@ -1,4 +1,4 @@ -package io.github.mainstringargs.util.properties; +package net.jacobpeterson.util.properties; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; diff --git a/src/main/java/io/github/mainstringargs/util/time/TimeUtil.java b/src/main/java/net/jacobpeterson/util/time/TimeUtil.java similarity index 98% rename from src/main/java/io/github/mainstringargs/util/time/TimeUtil.java rename to src/main/java/net/jacobpeterson/util/time/TimeUtil.java index 2d8383ef..0ccaa36a 100644 --- a/src/main/java/io/github/mainstringargs/util/time/TimeUtil.java +++ b/src/main/java/net/jacobpeterson/util/time/TimeUtil.java @@ -1,4 +1,4 @@ -package io.github.mainstringargs.util.time; +package net.jacobpeterson.util.time; import java.text.DecimalFormat; import java.text.NumberFormat; From 4e91716d1ee39e364c81902a162f1c2276cd331a Mon Sep 17 00:00:00 2001 From: Jesse Thouin Date: Tue, 29 Sep 2020 14:09:56 -0400 Subject: [PATCH 021/271] Merged master back into branch new_market_data_api --- .travis.yml | 2 +- README.md | 20 +- build.gradle | 78 ++-- eclipse-java-google-style.xml | 337 ------------------ schema_json/alpaca/order/order.json | 12 + .../net/jacobpeterson/alpaca/AlpacaAPI.java | 178 ++++++--- .../jacobpeterson/alpaca/AlpacaConstants.java | 9 + .../jacobpeterson/alpaca/enums/OrderType.java | 5 +- .../net/jacobpeterson/polygon/PolygonAPI.java | 16 +- .../TimeUtil.java => format/FormatUtil.java} | 18 +- upload.sh | 4 - 11 files changed, 219 insertions(+), 460 deletions(-) delete mode 100644 eclipse-java-google-style.xml rename src/main/java/net/jacobpeterson/util/{time/TimeUtil.java => format/FormatUtil.java} (86%) delete mode 100644 upload.sh diff --git a/.travis.yml b/.travis.yml index 24c200d4..8168baab 100644 --- a/.travis.yml +++ b/.travis.yml @@ -6,7 +6,7 @@ jdk: - oraclejdk8 script: - - ./gradlew build --scan -s + - ./gradlew build -s before_cache: - rm -f $HOME/.gradle/caches/modules-2/modules-2.lock diff --git a/README.md b/README.md index 10512289..f73f9d0c 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,9 @@ -

+# UPDATING FROM 5.0.13 TO 5.2 +This repository has been transferred and as a result, lots of refactoring has occurred. This is because artifacts cannot be transferred in Maven Central. So, the new groupID for the project is `net.jacobpeterson` and all packages have been renamed from `io.github.mainstringargs` to `net.jacobpeterson`. Please refactor accordingly when you update from 5.0.13 to 5.2. Thanks! +

-Maven Central Javadocs Build Status - GitHub +Maven Central Javadocs Build Status + GitHub

# Overview @@ -31,7 +33,7 @@ Add the following dependency to your build.gradle file: ``` dependencies { - compile "net.jacobpeterson:alpaca-java:5.0.13" + compile "net.jacobpeterson:alpaca-java:5.2" } ``` @@ -43,7 +45,7 @@ Add the following dependency to your pom.xml file: net.jacobpeterson alpaca-java - 5.0.13 + 5.2 compile ``` @@ -58,7 +60,7 @@ base_api_url = https://paper-api.alpaca.markets base_data_url = https://data.alpaca.markets user_agent=a_user_agent ``` -The default values for `alpaca.properties` can be found [here](https://github.com/mainstringargs/alpaca-java/tree/master/src/main/resources). +The default values for `alpaca.properties` can be found [here](https://github.com/Petersoj/alpaca-java/tree/master/src/main/resources). Similarly, set the following properties in a polygon.properties file on the classpath for using the PolygonAPI: ``` @@ -68,11 +70,11 @@ base_api_url = https://api.polygon.io web_socket_server_url = wss://alpaca.socket.polygon.io/stocks user_agent=a_user_agent ``` -The default values for `polygon.properties` can be found [here](https://github.com/mainstringargs/alpaca-java/tree/master/src/main/resources). +The default values for `polygon.properties` can be found [here](https://github.com/Petersoj/alpaca-java/tree/master/src/main/resources). ## AlpacaAPI Example -This example uses the `AlpacaAPI` class to subscribe to the Account and Trade Updates stream, print out the account information, submit a limit order, create a watchlist, and print out bars data. Click [here](https://docs.alpaca.markets/api-documentation/api-v2/) for the general Alpaca API documentation and click [here](https://javadoc.io/doc/net.jacobpeterson/alpaca-java/latest/io/github/mainstringargs/alpaca/AlpacaAPI.html) for the `AlpacaAPI` javadoc. +This example uses the `AlpacaAPI` class to subscribe to the Account and Trade Updates stream, print out the account information, submit a limit order, create a watchlist, and print out bars data. Click [here](https://docs.alpaca.markets/api-documentation/api-v2/) for the general Alpaca API documentation and click [here](https://javadoc.io/doc/net.jacobpeterson/alpaca-java/latest/net/jacobpeterson/alpaca/AlpacaAPI.html) for the `AlpacaAPI` javadoc. ```java // This logs into Alpaca using the alpaca.properties file on the classpath. @@ -300,7 +302,7 @@ Bars response: # PolygonAPI Example -This example uses the `PolygonAPI` class to subscribe to the Polygon websocket stream, get stocks splits, and get aggregates. Click [here](https://polygon.io/docs/) for the general Polygon API documentation and click [here](https://javadoc.io/doc/net.jacobpeterson/alpaca-java/latest/io/github/mainstringargs/polygon/PolygonAPI.html) for the `PolygonAPI` javadoc. +This example uses the `PolygonAPI` class to subscribe to the Polygon websocket stream, get stocks splits, and get aggregates. Click [here](https://polygon.io/docs/) for the general Polygon API documentation and click [here](https://javadoc.io/doc/net.jacobpeterson/alpaca-java/latest/net/jacobpeterson/polygon/PolygonAPI.html) for the `PolygonAPI` javadoc. ```java // This will use the key_id in the alpaca.properties file by default diff --git a/build.gradle b/build.gradle index 5f1f4119..94e23a0c 100644 --- a/build.gradle +++ b/build.gradle @@ -19,6 +19,7 @@ plugins { id 'maven-publish' id 'com.bmuschko.nexus' version '2.3.1' id 'io.codearte.nexus-staging' version '0.11.0' + id 'com.github.johnrengelman.shadow' version '6.0.0' } // Calling 'apply' here because jsonschema2pojo doesn't exist in Gradle Central Plugin Repository @@ -27,7 +28,7 @@ tasks.getByName("generateJsonSchema2Pojo").enabled(false) // Disable default jso archivesBaseName = 'alpaca-java' group = 'net.jacobpeterson' -version = '5.0.13-SNAPSHOT' +version = '5.2' repositories { mavenLocal() @@ -47,6 +48,9 @@ dependencies { testCompile 'junit:junit:4.12' } +// +// START Build options +// sourceCompatibility = 1.8 targetCompatibility = 1.8 [compileJava, compileTestJava, javadoc]*.options*.encoding = 'UTF-8' @@ -62,48 +66,45 @@ build { //mainClassName = 'net.jacobpeterson.polygon.PolygonExample' mainClassName = 'net.jacobpeterson.alpaca.AlpacaExample' +// +// END Build options +// // // START publishing // -publishing { - publications { - mainPublication(MavenPublication) { - from components.java - - pom { - name = 'alpaca-java' - description = 'Java API for Alpaca and Polygon' - url = 'https://github.com/Petersoj/alpaca-java' - inceptionYear = '2018' - - licenses { - license { - name = 'MIT License' - url = 'https://opensource.org/licenses/MIT' - } - } - - developers { - developer { - id = 'mainstringargs' - name = 'main(String[] args)' - email = 'mainstringargs@github.io' - } - - developer { - id = 'Petersoj' - name = 'Jacob Peterson' - } - } - - scm { - url = "https://github.com/Petersoj/alpaca-java.git" - connection = "scm:git:https://github.com/Petersoj/alpaca-java.git" - developerConnection = "scm:git:git@github.com/Petersoj/alpaca-java.git" - } + +modifyPom { + project { + name = 'alpaca-java' + description = 'Java API for Alpaca and Polygon' + url = 'https://github.com/Petersoj/alpaca-java' + inceptionYear = '2018' + + licenses { + license { + name = 'MIT License' + url = 'https://opensource.org/licenses/MIT' } } + + developers { + developer { + id = 'Petersoj' + name = 'Jacob Peterson' + } + + developer { + id = 'mainstringargs' + name = 'main(String[] args)' + } + } + + scm { + url = "https://github.com/Petersoj/alpaca-java.git" + connection = "scm:git:https://github.com/Petersoj/alpaca-java.git" + developerConnection = "scm:git:git@github.com/Petersoj/alpaca-java.git" + } } } @@ -112,6 +113,7 @@ extraArchive { tests = true javadoc = true } + nexus { sign = true repositoryUrl = 'https://oss.sonatype.org/service/local/staging/deploy/maven2/' @@ -119,7 +121,7 @@ nexus { } nexusStaging { - packageGroup = "net.jacobpeterson" //optional if packageGroup == project.getGroup() + packageGroup = "net.jacobpeterson" } // Used to prevent accidental publication of Alpaca keys diff --git a/eclipse-java-google-style.xml b/eclipse-java-google-style.xml deleted file mode 100644 index 2a9faacc..00000000 --- a/eclipse-java-google-style.xml +++ /dev/null @@ -1,337 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/schema_json/alpaca/order/order.json b/schema_json/alpaca/order/order.json index 36f0be60..ac9db9c4 100644 --- a/schema_json/alpaca/order/order.json +++ b/schema_json/alpaca/order/order.json @@ -105,6 +105,18 @@ "legs": { "existingJavaType": "java.util.ArrayList", "title": "When querying non-simple order_class orders in a nested style, an array of Order entities associated with this order is returned. Otherwise, null." + }, + "trail_price": { + "existingJavaType": "java.lang.String", + "title": "The dollar value away from the high water mark for trailing stop orders." + }, + "trail_percent": { + "existingJavaType": "java.lang.String", + "title": "The percent value away from the high water mark for trailing stop orders." + }, + "hwm": { + "existingJavaType": "java.lang.String", + "title": "High Water Mark - The highest (lowest) market price seen since the trailing stop order was submitted." } } } diff --git a/src/main/java/net/jacobpeterson/alpaca/AlpacaAPI.java b/src/main/java/net/jacobpeterson/alpaca/AlpacaAPI.java index 30f5da81..b74e04a7 100644 --- a/src/main/java/net/jacobpeterson/alpaca/AlpacaAPI.java +++ b/src/main/java/net/jacobpeterson/alpaca/AlpacaAPI.java @@ -42,7 +42,7 @@ import net.jacobpeterson.domain.alpaca.position.Position; import net.jacobpeterson.domain.alpaca.watchlist.Watchlist; import net.jacobpeterson.util.gson.GsonUtil; -import net.jacobpeterson.util.time.TimeUtil; +import net.jacobpeterson.util.format.FormatUtil; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; @@ -385,6 +385,8 @@ public ArrayList getOrders(OrderStatus status, Integer limit, ZonedDateTi * @param timeInForce day, gtc, opg, cls, ioc, fok. Please see Understand Orders for more info. * @param limitPrice required if type is limit or stop_limit * @param stopPrice required if type is stop or stop_limit + * @param trailPrice this or trail_percent is required if type is trailing_stop + * @param trailPercent this or trail_price is required if type is trailing_stop * @param extendedHours (default) false. If true, order will be eligible to execute in premarket/afterhours. * Only works with type limit and time_in_force day. * @param clientOrderId A unique identifier for the order. Automatically generated if not sent. @@ -403,9 +405,9 @@ public ArrayList getOrders(OrderStatus status, Integer limit, ZonedDateTi * @see Orders */ public Order requestNewOrder(String symbol, Integer quantity, OrderSide side, OrderType type, - OrderTimeInForce timeInForce, Double limitPrice, Double stopPrice, Boolean extendedHours, - String clientOrderId, OrderClass orderClass, Double takeProfitLimitPrice, Double stopLossStopPrice, - Double stopLossLimitPrice) + OrderTimeInForce timeInForce, Double limitPrice, Double stopPrice, Double trailPrice, Double trailPercent, + Boolean extendedHours, String clientOrderId, OrderClass orderClass, Double takeProfitLimitPrice, + Double stopLossStopPrice, Double stopLossLimitPrice) throws AlpacaAPIRequestException { Preconditions.checkNotNull(symbol); Preconditions.checkNotNull(quantity); @@ -424,12 +426,22 @@ public Order requestNewOrder(String symbol, Integer quantity, OrderSide side, Or if (limitPrice != null) { urlBuilder.appendJSONBodyProperty(AlpacaConstants.LIMIT_PRICE_PARAMETER, - TimeUtil.toDecimalFormat(limitPrice)); + FormatUtil.toDecimalFormat(limitPrice)); } if (stopPrice != null) { urlBuilder.appendJSONBodyProperty(AlpacaConstants.STOP_PRICE_PARAMETER, - TimeUtil.toDecimalFormat(stopPrice)); + FormatUtil.toDecimalFormat(stopPrice)); + } + + if (trailPrice != null) { + urlBuilder.appendJSONBodyProperty(AlpacaConstants.TRAIL_PRICE_PARAMETER, + FormatUtil.toDecimalFormat(trailPrice)); + } + + if (trailPercent != null) { + urlBuilder.appendJSONBodyProperty(AlpacaConstants.TRAIL_PERCENT_PARAMETER, + FormatUtil.toDecimalFormat(trailPercent)); } if (extendedHours != null) { @@ -477,8 +489,8 @@ public Order requestNewOrder(String symbol, Integer quantity, OrderSide side, Or /** * A market order is a request to buy or sell a security at the currently available market price. It provides the * most likely method of filling an order. Market orders fill nearly instantaneously. This method calls {@link - * #requestNewOrder(String, Integer, OrderSide, OrderType, OrderTimeInForce, Double, Double, Boolean, String, - * OrderClass, Double, Double, Double)} with {@link OrderType#MARKET}. + * #requestNewOrder(String, Integer, OrderSide, OrderType, OrderTimeInForce, Double, Double, Double, Double, + * Boolean, String, OrderClass, Double, Double, Double)} with {@link OrderType#MARKET}. * * @param symbol symbol or asset ID to identify the asset to trade * @param quantity number of shares to trade @@ -494,8 +506,8 @@ public Order requestNewOrder(String symbol, Integer quantity, OrderSide side, Or */ public Order requestNewMarketOrder(String symbol, Integer quantity, OrderSide side, OrderTimeInForce timeInForce, Boolean extendedHours) throws AlpacaAPIRequestException { - return requestNewOrder(symbol, quantity, side, OrderType.MARKET, timeInForce, null, null, extendedHours, - null, OrderClass.SIMPLE, null, null, null); + return requestNewOrder(symbol, quantity, side, OrderType.MARKET, timeInForce, null, null, null, null, + extendedHours, null, OrderClass.SIMPLE, null, null, null); } /** @@ -503,8 +515,8 @@ public Order requestNewMarketOrder(String symbol, Integer quantity, OrderSide si * is executed at the specified limit price or lower (i.e., better). Conversely, a sell limit order (a limit order * to sell) is executed at the specified limit price or higher (better). Unlike a market order, you have to specify * the limit price parameter when submitting your order. This method calls {@link #requestNewOrder(String, Integer, - * OrderSide, OrderType, OrderTimeInForce, Double, Double, Boolean, String, OrderClass, Double, Double, Double)} - * with {@link OrderType#LIMIT}. + * OrderSide, OrderType, OrderTimeInForce, Double, Double, Double, Double, Boolean, String, OrderClass, Double, + * Double, Double)} with {@link OrderType#LIMIT}. * * @param symbol symbol or asset ID to identify the asset to trade * @param quantity number of shares to trade @@ -521,8 +533,8 @@ public Order requestNewMarketOrder(String symbol, Integer quantity, OrderSide si */ public Order requestNewLimitOrder(String symbol, Integer quantity, OrderSide side, OrderTimeInForce timeInForce, Double limitPrice, Boolean extendedHours) throws AlpacaAPIRequestException { - return requestNewOrder(symbol, quantity, side, OrderType.LIMIT, timeInForce, limitPrice, null, extendedHours, - null, OrderClass.SIMPLE, null, null, null); + return requestNewOrder(symbol, quantity, side, OrderType.LIMIT, timeInForce, limitPrice, null, null, null, + extendedHours, null, OrderClass.SIMPLE, null, null, null); } /** @@ -531,8 +543,8 @@ public Order requestNewLimitOrder(String symbol, Integer quantity, OrderSide sid * specified stop price, the stop order becomes a market order. Alpaca converts buy stop orders into stop limit * orders with a limit price that is 4% higher than a stop price < $50 (or 2.5% higher than a stop price >= * $50). Sell stop orders are not converted into stop limit orders. This method calls {@link - * #requestNewOrder(String, Integer, OrderSide, OrderType, OrderTimeInForce, Double, Double, Boolean, String, - * OrderClass, Double, Double, Double)} with {@link OrderType#STOP}. + * #requestNewOrder(String, Integer, OrderSide, OrderType, OrderTimeInForce, Double, Double, Double, Double, + * Boolean, String, OrderClass, Double, Double, Double)} with {@link OrderType#STOP}. * * @param symbol symbol or asset ID to identify the asset to trade * @param quantity number of shares to trade @@ -549,8 +561,8 @@ public Order requestNewLimitOrder(String symbol, Integer quantity, OrderSide sid */ public Order requestNewStopOrder(String symbol, Integer quantity, OrderSide side, OrderTimeInForce timeInForce, Double stopPrice, Boolean extendedHours) throws AlpacaAPIRequestException { - return requestNewOrder(symbol, quantity, side, OrderType.STOP, timeInForce, null, stopPrice, extendedHours, - null, OrderClass.SIMPLE, null, null, null); + return requestNewOrder(symbol, quantity, side, OrderType.STOP, timeInForce, null, stopPrice, null, null, + extendedHours, null, OrderClass.SIMPLE, null, null, null); } /** @@ -558,8 +570,8 @@ public Order requestNewStopOrder(String symbol, Integer quantity, OrderSide side * those of a limit order and is used to mitigate risk. The stop-limit order will be executed at a specified limit * price, or better, after a given stop price has been reached. Once the stop price is reached, the stop-limit order * becomes a limit order to buy or sell at the limit price or better. This method calls {@link - * #requestNewOrder(String, Integer, OrderSide, OrderType, OrderTimeInForce, Double, Double, Boolean, String, - * OrderClass, Double, Double, Double)} with {@link OrderType#STOP_LIMIT}. + * #requestNewOrder(String, Integer, OrderSide, OrderType, OrderTimeInForce, Double, Double, Double, Double, + * Boolean, String, OrderClass, Double, Double, Double)} with {@link OrderType#STOP_LIMIT}. * * @param symbol symbol or asset ID to identify the asset to trade * @param quantity number of shares to trade @@ -578,14 +590,15 @@ public Order requestNewStopOrder(String symbol, Integer quantity, OrderSide side public Order requestNewStopLimitOrder(String symbol, Integer quantity, OrderSide side, OrderTimeInForce timeInForce, Double limitPrice, Double stopPrice, Boolean extendedHours) throws AlpacaAPIRequestException { return requestNewOrder(symbol, quantity, side, OrderType.STOP_LIMIT, timeInForce, limitPrice, stopPrice, - extendedHours, null, OrderClass.SIMPLE, null, null, null); + null, null, extendedHours, null, OrderClass.SIMPLE, null, null, null); } /** * A bracket order is a chain of three orders that can be used to manage your position entry and exit. It is a * common use case of an OTOCO (One Triggers OCO {One Cancels Other}) order. This method calls {@link - * #requestNewOrder(String, Integer, OrderSide, OrderType, OrderTimeInForce, Double, Double, Boolean, String, - * OrderClass, Double, Double, Double)} with {@link OrderType#MARKET} and with parameters for a bracket order. + * #requestNewOrder(String, Integer, OrderSide, OrderType, OrderTimeInForce, Double, Double, Double, Double, + * Boolean, String, OrderClass, Double, Double, Double)} with {@link OrderType#MARKET} and with parameters for a + * bracket order. * * @param symbol symbol or asset ID to identify the asset to trade * @param quantity number of shares to trade @@ -608,15 +621,17 @@ public Order requestNewStopLimitOrder(String symbol, Integer quantity, OrderSide public Order requestNewMarketBracketOrder(String symbol, Integer quantity, OrderSide side, OrderTimeInForce timeInForce, Boolean extendedHours, Double takeProfitLimitPrice, Double stopLossStopPrice, Double stopLossLimitPrice) throws AlpacaAPIRequestException { - return requestNewOrder(symbol, quantity, side, OrderType.MARKET, timeInForce, null, null, - extendedHours, null, OrderClass.BRACKET, takeProfitLimitPrice, stopLossStopPrice, stopLossLimitPrice); + return requestNewOrder(symbol, quantity, side, OrderType.MARKET, timeInForce, null, null, null, null, + extendedHours, null, OrderClass.BRACKET, takeProfitLimitPrice, stopLossStopPrice, + stopLossLimitPrice); } /** * A bracket order is a chain of three orders that can be used to manage your position entry and exit. It is a * common use case of an OTOCO (One Triggers OCO {One Cancels Other}) order. This method calls {@link - * #requestNewOrder(String, Integer, OrderSide, OrderType, OrderTimeInForce, Double, Double, Boolean, String, - * OrderClass, Double, Double, Double)} with {@link OrderType#LIMIT} and with parameters for a bracket order. + * #requestNewOrder(String, Integer, OrderSide, OrderType, OrderTimeInForce, Double, Double, Double, Double, + * Boolean, String, OrderClass, Double, Double, Double)} with {@link OrderType#LIMIT} and with parameters for a + * bracket order. * * @param symbol symbol or asset ID to identify the asset to trade * @param quantity number of shares to trade @@ -640,7 +655,7 @@ public Order requestNewMarketBracketOrder(String symbol, Integer quantity, Order public Order requestNewLimitBracketOrder(String symbol, Integer quantity, OrderSide side, OrderTimeInForce timeInForce, Double limitPrice, Boolean extendedHours, Double takeProfitLimitPrice, Double stopLossStopPrice, Double stopLossLimitPrice) throws AlpacaAPIRequestException { - return requestNewOrder(symbol, quantity, side, OrderType.LIMIT, timeInForce, limitPrice, null, + return requestNewOrder(symbol, quantity, side, OrderType.LIMIT, timeInForce, limitPrice, null, null, null, extendedHours, null, OrderClass.BRACKET, takeProfitLimitPrice, stopLossStopPrice, stopLossLimitPrice); } @@ -649,8 +664,8 @@ public Order requestNewLimitBracketOrder(String symbol, Integer quantity, OrderS * (buy/buy or sell/sell) and currently only exit order is supported. In other words, this is the second part of the * bracket orders where the entry order is already filled, and you can submit the take-profit and stop-loss in one * order submission. This method calls {@link #requestNewOrder(String, Integer, OrderSide, OrderType, - * OrderTimeInForce, Double, Double, Boolean, String, OrderClass, Double, Double, Double)} with parameters for an - * OCO order type. + * OrderTimeInForce, Double, Double, Double, Double, Boolean, String, OrderClass, Double, Double, Double)} with + * parameters for an OCO order type. * * @param symbol symbol or asset ID to identify the asset to trade * @param quantity number of shares to trade @@ -671,15 +686,15 @@ public Order requestNewLimitBracketOrder(String symbol, Integer quantity, OrderS public Order requestNewOCOOrder(String symbol, Integer quantity, OrderSide side, OrderTimeInForce timeInForce, Boolean extendedHours, Double takeProfitLimitPrice, Double stopLossStopPrice, Double stopLossLimitPrice) throws AlpacaAPIRequestException { - return requestNewOrder(symbol, quantity, side, OrderType.LIMIT, timeInForce, null, null, extendedHours, null, - OrderClass.OCO, takeProfitLimitPrice, stopLossStopPrice, stopLossLimitPrice); + return requestNewOrder(symbol, quantity, side, OrderType.LIMIT, timeInForce, null, null, null, null, + extendedHours, null, OrderClass.OCO, takeProfitLimitPrice, stopLossStopPrice, stopLossLimitPrice); } /** * OTO (One-Triggers-Other) is a variant of bracket order. It takes one of the take-profit or stop-loss order in * addition to the entry order. This method calls {@link #requestNewOrder(String, Integer, OrderSide, OrderType, - * OrderTimeInForce, Double, Double, Boolean, String, OrderClass, Double, Double, Double)} with {@link - * OrderType#LIMIT} and with parameters for an OTO order type. + * OrderTimeInForce, Double, Double, Double, Double, Boolean, String, OrderClass, Double, Double, Double)} with + * {@link OrderType#LIMIT} and with parameters for an OTO order type. * * @param symbol symbol or asset ID to identify the asset to trade * @param quantity number of shares to trade @@ -700,15 +715,15 @@ public Order requestNewOCOOrder(String symbol, Integer quantity, OrderSide side, public Order requestNewOTOMarketOrder(String symbol, Integer quantity, OrderSide side, OrderTimeInForce timeInForce, Boolean extendedHours, Double takeProfitLimitPrice, Double stopLossStopPrice, Double stopLossLimitPrice) throws AlpacaAPIRequestException { - return requestNewOrder(symbol, quantity, side, OrderType.MARKET, timeInForce, null, null, extendedHours, null, - OrderClass.OTO, takeProfitLimitPrice, stopLossStopPrice, stopLossLimitPrice); + return requestNewOrder(symbol, quantity, side, OrderType.MARKET, timeInForce, null, null, null, null, + extendedHours, null, OrderClass.OTO, takeProfitLimitPrice, stopLossStopPrice, stopLossLimitPrice); } /** * OTO (One-Triggers-Other) is a variant of bracket order. It takes one of the take-profit or stop-loss order in * addition to the entry order. This method calls {@link #requestNewOrder(String, Integer, OrderSide, OrderType, - * OrderTimeInForce, Double, Double, Boolean, String, OrderClass, Double, Double, Double)} with {@link - * OrderType#LIMIT} and with parameters for an OTO order type. + * OrderTimeInForce, Double, Double, Double, Double, Boolean, String, OrderClass, Double, Double, Double)} with + * {@link OrderType#LIMIT} and with parameters for an OTO order type. * * @param symbol symbol or asset ID to identify the asset to trade * @param quantity number of shares to trade @@ -730,15 +745,15 @@ public Order requestNewOTOMarketOrder(String symbol, Integer quantity, OrderSide public Order requestNewOTOLimitOrder(String symbol, Integer quantity, OrderSide side, OrderTimeInForce timeInForce, Double limitPrice, Boolean extendedHours, Double takeProfitLimitPrice, Double stopLossStopPrice, Double stopLossLimitPrice) throws AlpacaAPIRequestException { - return requestNewOrder(symbol, quantity, side, OrderType.LIMIT, timeInForce, limitPrice, null, extendedHours, - null, OrderClass.OTO, takeProfitLimitPrice, stopLossStopPrice, stopLossLimitPrice); + return requestNewOrder(symbol, quantity, side, OrderType.LIMIT, timeInForce, limitPrice, null, null, null, + extendedHours, null, OrderClass.OTO, takeProfitLimitPrice, stopLossStopPrice, stopLossLimitPrice); } /** * OTO (One-Triggers-Other) is a variant of bracket order. It takes one of the take-profit or stop-loss order in * addition to the entry order. This method calls {@link #requestNewOrder(String, Integer, OrderSide, OrderType, - * OrderTimeInForce, Double, Double, Boolean, String, OrderClass, Double, Double, Double)} with {@link - * OrderType#STOP} and with parameters for an OTO order type. + * OrderTimeInForce, Double, Double, Double, Double, Boolean, String, OrderClass, Double, Double, Double)} with + * {@link OrderType#STOP} and with parameters for an OTO order type. * * @param symbol symbol or asset ID to identify the asset to trade * @param quantity number of shares to trade @@ -760,15 +775,15 @@ public Order requestNewOTOLimitOrder(String symbol, Integer quantity, OrderSide public Order requestNewOTOStopOrder(String symbol, Integer quantity, OrderSide side, OrderTimeInForce timeInForce, Double stopPrice, Boolean extendedHours, Double takeProfitLimitPrice, Double stopLossStopPrice, Double stopLossLimitPrice) throws AlpacaAPIRequestException { - return requestNewOrder(symbol, quantity, side, OrderType.STOP, timeInForce, null, stopPrice, extendedHours, - null, OrderClass.OTO, takeProfitLimitPrice, stopLossStopPrice, stopLossLimitPrice); + return requestNewOrder(symbol, quantity, side, OrderType.STOP, timeInForce, null, stopPrice, null, null, + extendedHours, null, OrderClass.OTO, takeProfitLimitPrice, stopLossStopPrice, stopLossLimitPrice); } /** * OTO (One-Triggers-Other) is a variant of bracket order. It takes one of the take-profit or stop-loss order in * addition to the entry order. This method calls {@link #requestNewOrder(String, Integer, OrderSide, OrderType, - * OrderTimeInForce, Double, Double, Boolean, String, OrderClass, Double, Double, Double)} with {@link - * OrderType#STOP_LIMIT} and with parameters for an OTO order type. + * OrderTimeInForce, Double, Double, Double, Double, Boolean, String, OrderClass, Double, Double, Double)} with + * {@link OrderType#STOP_LIMIT} and with parameters for an OTO order type. * * @param symbol symbol or asset ID to identify the asset to trade * @param quantity number of shares to trade @@ -793,7 +808,58 @@ public Order requestNewOTOStopLimitOrder(String symbol, Integer quantity, OrderS Double takeProfitLimitPrice, Double stopLossStopPrice, Double stopLossLimitPrice) throws AlpacaAPIRequestException { return requestNewOrder(symbol, quantity, side, OrderType.STOP_LIMIT, timeInForce, limitPrice, stopPrice, - extendedHours, null, OrderClass.OTO, takeProfitLimitPrice, stopLossStopPrice, stopLossLimitPrice); + null, null, extendedHours, null, OrderClass.OTO, takeProfitLimitPrice, + stopLossStopPrice, stopLossLimitPrice); + } + + /** + * Trailing stop orders allow you to continuously and automatically keep updating the stop price threshold based on + * the stock price movement. This method calls {@link #requestNewOrder(String, Integer, OrderSide, OrderType, + * OrderTimeInForce, Double, Double, Double, Double, Boolean, String, OrderClass, Double, Double, Double)} with + * {@link OrderType#TRAILING_STOP} and with parameters for a Trailing Stop Price order type. + * + * @param symbol symbol or asset ID to identify the asset to trade + * @param quantity number of shares to trade + * @param side buy or sell + * @param timeInForce day, gtc, opg, cls, ioc, fok. Please see Understand Orders for more info. + * @param trailPrice this or trail_percent is required if type is trailing_stop + * @param extendedHours (default) false. If true, order will be eligible to execute in premarket/afterhours. Only + * works with type limit and time_in_force day. + * + * @return the order + * + * @throws AlpacaAPIRequestException the alpaca api request exception + * @see Order Types + */ + public Order requestNewTrailingStopPriceOrder(String symbol, Integer quantity, OrderSide side, + OrderTimeInForce timeInForce, Double trailPrice, Boolean extendedHours) throws AlpacaAPIRequestException { + return requestNewOrder(symbol, quantity, side, OrderType.TRAILING_STOP, timeInForce, null, null, + trailPrice, null, extendedHours, null, OrderClass.OTO, null, null, null); + } + + /** + * Trailing stop orders allow you to continuously and automatically keep updating the stop price threshold based on + * the stock price movement. This method calls {@link #requestNewOrder(String, Integer, OrderSide, OrderType, + * OrderTimeInForce, Double, Double, Double, Double, Boolean, String, OrderClass, Double, Double, Double)} with + * {@link OrderType#TRAILING_STOP} and with parameters for a Trailing Stop Percent order type. + * + * @param symbol symbol or asset ID to identify the asset to trade + * @param quantity number of shares to trade + * @param side buy or sell + * @param timeInForce day, gtc, opg, cls, ioc, fok. Please see Understand Orders for more info. + * @param trailPercent this or trail_price is required if type is trailing_stop + * @param extendedHours (default) false. If true, order will be eligible to execute in premarket/afterhours. Only + * works with type limit and time_in_force day. + * + * @return the order + * + * @throws AlpacaAPIRequestException the alpaca api request exception + * @see Order Types + */ + public Order requestNewTrailingStopPercentOrder(String symbol, Integer quantity, OrderSide side, + OrderTimeInForce timeInForce, Double trailPercent, Boolean extendedHours) throws AlpacaAPIRequestException { + return requestNewOrder(symbol, quantity, side, OrderType.TRAILING_STOP, timeInForce, null, null, + null, trailPercent, extendedHours, null, OrderClass.OTO, null, null, null); } /** @@ -862,6 +928,7 @@ public Order getOrderByClientID(String clientOrderId) throws AlpacaAPIRequestExc * @param timeInForce day, gtc, opg, cls, ioc, fok. Please see Understand Orders for more info. * @param limitPrice required if type is limit or stop_limit * @param stopPrice required if type is stop or stop_limit + * @param trail the new value of the trail_price or trail_percent value (works only for type=trailing_stop) * @param clientOrderId A unique identifier for the order. Automatically generated if not sent. * * @return the order @@ -870,7 +937,7 @@ public Order getOrderByClientID(String clientOrderId) throws AlpacaAPIRequestExc * @see Orders */ public Order replaceOrder(String orderID, Integer quantity, OrderTimeInForce timeInForce, Double limitPrice, - Double stopPrice, String clientOrderId) throws AlpacaAPIRequestException { + Double stopPrice, Double trail, String clientOrderId) throws AlpacaAPIRequestException { Preconditions.checkNotNull(orderID); AlpacaRequestBuilder urlBuilder = new AlpacaRequestBuilder(baseAPIURL, apiVersion, @@ -887,12 +954,17 @@ public Order replaceOrder(String orderID, Integer quantity, OrderTimeInForce tim if (limitPrice != null) { urlBuilder.appendJSONBodyProperty(AlpacaConstants.LIMIT_PRICE_PARAMETER, - TimeUtil.toDecimalFormat(limitPrice)); + FormatUtil.toDecimalFormat(limitPrice)); } if (stopPrice != null) { urlBuilder.appendJSONBodyProperty(AlpacaConstants.STOP_PRICE_PARAMETER, - TimeUtil.toDecimalFormat(stopPrice)); + FormatUtil.toDecimalFormat(stopPrice)); + } + + if (trail != null) { + urlBuilder.appendJSONBodyProperty(AlpacaConstants.TRAIL_PARAMETER, + FormatUtil.toDecimalFormat(trail)); } if (clientOrderId != null) { @@ -1362,7 +1434,7 @@ public PortfolioHistory getPortfolioHistory(Integer periodLength, PortfolioPerio } if (dateEnd != null) { - urlBuilder.appendURLParameter(AlpacaConstants.DATE_END_PARAMETER, TimeUtil.toDateString(dateEnd)); + urlBuilder.appendURLParameter(AlpacaConstants.DATE_END_PARAMETER, FormatUtil.toDateString(dateEnd)); } if (extendedHours != null) { @@ -1417,11 +1489,11 @@ public ArrayList getCalendar(LocalDate start, LocalDate end) throws Al AlpacaConstants.CALENDAR_ENDPOINT); if (start != null) { - urlBuilder.appendURLParameter(AlpacaConstants.START_PARAMETER, TimeUtil.toDateString(start)); + urlBuilder.appendURLParameter(AlpacaConstants.START_PARAMETER, FormatUtil.toDateString(start)); } if (end != null) { - urlBuilder.appendURLParameter(AlpacaConstants.END_PARAMETER, TimeUtil.toDateString(end)); + urlBuilder.appendURLParameter(AlpacaConstants.END_PARAMETER, FormatUtil.toDateString(end)); } HttpResponse response = alpacaRequest.invokeGet(urlBuilder); diff --git a/src/main/java/net/jacobpeterson/alpaca/AlpacaConstants.java b/src/main/java/net/jacobpeterson/alpaca/AlpacaConstants.java index fc9d318f..4e231c6c 100644 --- a/src/main/java/net/jacobpeterson/alpaca/AlpacaConstants.java +++ b/src/main/java/net/jacobpeterson/alpaca/AlpacaConstants.java @@ -41,6 +41,15 @@ public class AlpacaConstants { /** The Constant STOP_PRICE_PARAMETER. */ public static final String STOP_PRICE_PARAMETER = "stop_price"; + /** The Constant TRAIL_PRICE_PARAMETER. */ + public static final String TRAIL_PRICE_PARAMETER = "trail_price"; + + /** The Constant TRAIL_PERCENT_PARAMETER. */ + public static final String TRAIL_PERCENT_PARAMETER = "trail_percent"; + + /** The Constant TRAIL_PARAMETER. */ + public static final String TRAIL_PARAMETER = "trail"; + /** The Constant LIMIT_PRICE_PARAMETER. */ public static final String LIMIT_PRICE_PARAMETER = "limit_price"; diff --git a/src/main/java/net/jacobpeterson/alpaca/enums/OrderType.java b/src/main/java/net/jacobpeterson/alpaca/enums/OrderType.java index fc198fad..b83a3c24 100644 --- a/src/main/java/net/jacobpeterson/alpaca/enums/OrderType.java +++ b/src/main/java/net/jacobpeterson/alpaca/enums/OrderType.java @@ -17,7 +17,10 @@ public enum OrderType implements APIName { STOP("stop"), /** The stop limit. */ - STOP_LIMIT("stop_limit"); + STOP_LIMIT("stop_limit"), + + /** The trailing stop. */ + TRAILING_STOP("trailing_stop"); /** The api name. */ String apiName; diff --git a/src/main/java/net/jacobpeterson/polygon/PolygonAPI.java b/src/main/java/net/jacobpeterson/polygon/PolygonAPI.java index 8f9ee005..e9095917 100644 --- a/src/main/java/net/jacobpeterson/polygon/PolygonAPI.java +++ b/src/main/java/net/jacobpeterson/polygon/PolygonAPI.java @@ -42,7 +42,7 @@ import net.jacobpeterson.polygon.rest.exception.PolygonAPIRequestException; import net.jacobpeterson.polygon.websocket.client.PolygonWebsocketClient; import net.jacobpeterson.polygon.websocket.listener.PolygonStreamListener; -import net.jacobpeterson.util.time.TimeUtil; +import net.jacobpeterson.util.format.FormatUtil; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; @@ -504,7 +504,7 @@ public HistoricTradesResponse getHistoricTrades(String ticker, LocalDate date, L PolygonConstants.STOCKS_ENDPOINT, PolygonConstants.TRADES_ENDPOINT, ticker, - TimeUtil.toDateString(date)); + FormatUtil.toDateString(date)); if (timestamp != null) { builder.appendURLParameter(PolygonConstants.TIMESTAMP_PARAMETER, String.valueOf(timestamp)); @@ -559,7 +559,7 @@ public HistoricQuotesResponse getHistoricQuotes(String ticker, LocalDate date, L PolygonConstants.STOCKS_ENDPOINT, PolygonConstants.NBBO_ENDPOINT, ticker, - TimeUtil.toDateString(date)); + FormatUtil.toDateString(date)); if (timestamp != null) { builder.appendURLParameter(PolygonConstants.TIMESTAMP_PARAMETER, String.valueOf(timestamp)); @@ -658,7 +658,7 @@ public DailyOpenCloseResponse getDailyOpenClose(String symbol, LocalDate date) t PolygonRequestBuilder builder = new PolygonRequestBuilder(baseAPIURL, PolygonConstants.VERSION_1_ENDPOINT, PolygonConstants.OPEN_CLOSE_ENDPOINT, symbol, - TimeUtil.toDateString(date)); + FormatUtil.toDateString(date)); HttpResponse response = polygonRequest.invokeGet(builder); @@ -879,7 +879,7 @@ public PreviousCloseResponse getPreviousClose(String ticker, Boolean unadjusted) * Aggregates */ public AggregatesResponse getAggregates(String ticker, Integer multiplier, Timespan timeSpan, LocalDate fromDate, - LocalDate toDate, Boolean unadjusted) throws PolygonAPIRequestException { + LocalDate toDate, Boolean unadjusted) throws PolygonAPIRequestException { Preconditions.checkNotNull(ticker); Preconditions.checkNotNull(timeSpan); Preconditions.checkNotNull(fromDate); @@ -892,8 +892,8 @@ public AggregatesResponse getAggregates(String ticker, Integer multiplier, Times PolygonConstants.RANGE_ENDPOINT, Integer.toString((multiplier == null) ? 1 : multiplier), timeSpan.getAPIName(), - TimeUtil.toDateString(fromDate), - TimeUtil.toDateString(toDate)); + FormatUtil.toDateString(fromDate), + FormatUtil.toDateString(toDate)); if (unadjusted != null) { builder.appendURLParameter(PolygonConstants.UNADJUSTED_PARAMETER, unadjusted.toString()); @@ -936,7 +936,7 @@ public GroupedDailyResponse getGroupedDaily(String locale, Market market, LocalD locale, PolygonConstants.MARKET_ENDPOINT, market.getAPIName(), - TimeUtil.toDateString(date)); + FormatUtil.toDateString(date)); if (unadjusted != null) { builder.appendURLParameter(PolygonConstants.UNADJUSTED_PARAMETER, unadjusted.toString()); diff --git a/src/main/java/net/jacobpeterson/util/time/TimeUtil.java b/src/main/java/net/jacobpeterson/util/format/FormatUtil.java similarity index 86% rename from src/main/java/net/jacobpeterson/util/time/TimeUtil.java rename to src/main/java/net/jacobpeterson/util/format/FormatUtil.java index 0ccaa36a..0af76a5b 100644 --- a/src/main/java/net/jacobpeterson/util/time/TimeUtil.java +++ b/src/main/java/net/jacobpeterson/util/format/FormatUtil.java @@ -1,4 +1,4 @@ -package net.jacobpeterson.util.time; +package net.jacobpeterson.util.format; import java.text.DecimalFormat; import java.text.NumberFormat; @@ -9,23 +9,23 @@ import java.time.format.DateTimeFormatter; /** - * The Class Utilities. + * The Class FormatUtil. */ -public class TimeUtil { +public class FormatUtil { /** The constant PREV_UNIX_EPOCH_NANO_TIME. */ private static final long PREV_UNIX_EPOCH_NANO_TIME = System.currentTimeMillis() * 1000000L; /** The constant PREV_UNIX_EPOCH_NANO_TIME. */ private static final long PREV_UNIX_EPOCH_MICRO_TIME = PREV_UNIX_EPOCH_NANO_TIME / 1000L; /** The date time formatter. */ - private static DateTimeFormatter inputDateTimeFormatter = DateTimeFormatter.ofPattern( + private static final DateTimeFormatter INPUT_DATE_TIME_FORMATTER = DateTimeFormatter.ofPattern( "[yyyyMMdd][yyyy-MM-dd][yyyy-DDD]['T'[HHmmss][HHmm][HH:mm:ss][HH:mm][.SSSSSSSSS][.SSSSSSSS][.SSSSSSS][" + ".SSSSSS][.SSSSS][.SSS][.SS][.S]][OOOO][O][z][XXXXX][XXXX]['['VV']']"); /** The output date time formatter. */ - private static DateTimeFormatter outputDateTimeFormatter = + private static final DateTimeFormatter OUTPUT_DATE_TIME_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss'Z'").withZone(ZoneId.of("UTC")); /** The number formatter. */ - private static NumberFormat numberFormatter = new DecimalFormat("#0.00"); + private static final NumberFormat NUMBER_FORMATTER = new DecimalFormat("#0.00"); /** * To date time string. @@ -39,7 +39,7 @@ public static String toDateTimeString(LocalDateTime ldt) { ZonedDateTime localTimeZoned = ldtZoned.withZoneSameInstant(ZoneId.of("UTC")); - return outputDateTimeFormatter.format(localTimeZoned); + return OUTPUT_DATE_TIME_FORMATTER.format(localTimeZoned); } /** @@ -50,7 +50,7 @@ public static String toDateTimeString(LocalDateTime ldt) { * @return the string */ public static String toDecimalFormat(Number numberToFormat) { - return numberFormatter.format(numberToFormat); + return NUMBER_FORMATTER.format(numberToFormat); } /** @@ -72,7 +72,7 @@ public static String toDateString(LocalDate ld) { * @return the local date time using the system time zone */ public static LocalDateTime fromDateTimeString(String dateTimeString) { - LocalDateTime ldt = LocalDateTime.parse(dateTimeString, inputDateTimeFormatter); + LocalDateTime ldt = LocalDateTime.parse(dateTimeString, INPUT_DATE_TIME_FORMATTER); ZonedDateTime ldtZoned; diff --git a/upload.sh b/upload.sh deleted file mode 100644 index ae6499d4..00000000 --- a/upload.sh +++ /dev/null @@ -1,4 +0,0 @@ -#!/bin/bash - -./gradlew uploadArchives -./gradlew closeAndReleaseRepository From c250d61164edaf2895e68124189db2130404cbaa Mon Sep 17 00:00:00 2001 From: Jesse Thouin Date: Tue, 29 Sep 2020 20:24:35 -0400 Subject: [PATCH 022/271] Added functionality for MarketDataStreamListener; added example code for MarketDataStreamListenerAdapter; fixed bugs in MarketDataWebsocketClient.getRegisteredMessageTypes --- .../net/jacobpeterson/alpaca/AlpacaAPI.java | 24 ++++++++++++++++++ .../jacobpeterson/alpaca/AlpacaExample.java | 25 +++++++++++++++++++ .../client/MarketDataWebsocketClient.java | 24 +++++++++--------- 3 files changed, 61 insertions(+), 12 deletions(-) diff --git a/src/main/java/net/jacobpeterson/alpaca/AlpacaAPI.java b/src/main/java/net/jacobpeterson/alpaca/AlpacaAPI.java index b74e04a7..311f1fe6 100644 --- a/src/main/java/net/jacobpeterson/alpaca/AlpacaAPI.java +++ b/src/main/java/net/jacobpeterson/alpaca/AlpacaAPI.java @@ -24,6 +24,8 @@ import net.jacobpeterson.alpaca.rest.exception.AlpacaAPIRequestException; import net.jacobpeterson.alpaca.websocket.broker.client.AlpacaWebsocketClient; import net.jacobpeterson.alpaca.websocket.broker.listener.AlpacaStreamListener; +import net.jacobpeterson.alpaca.websocket.marketdata.client.MarketDataWebsocketClient; +import net.jacobpeterson.alpaca.websocket.marketdata.listener.MarketDataStreamListener; import net.jacobpeterson.domain.alpaca.account.Account; import net.jacobpeterson.domain.alpaca.accountactivities.AccountActivity; import net.jacobpeterson.domain.alpaca.accountactivities.NonTradeActivity; @@ -83,6 +85,9 @@ public class AlpacaAPI { /** The alpaca web socket client. */ private final AlpacaWebsocketClient alpacaWebSocketClient; + /** The alpaca web socket client. */ + private final MarketDataWebsocketClient marketDataWebSocketClient; + /** * Instantiates a new Alpaca API using properties specified in alpaca.properties file (or relevant defaults) */ @@ -144,6 +149,7 @@ public AlpacaAPI(String apiVersion, String keyId, String secret, String baseAPIU alpacaRequest = new AlpacaRequest(keyId, secret); alpacaWebSocketClient = new AlpacaWebsocketClient(keyId, secret, baseAPIURL); + marketDataWebSocketClient = new MarketDataWebsocketClient(keyId, secret, baseDataUrl); LOGGER.debug(this.toString()); } @@ -1699,6 +1705,24 @@ public void removeAlpacaStreamListener(AlpacaStreamListener streamListener) { alpacaWebSocketClient.removeListener(streamListener); } + /** + * Adds the alpaca stream listener. + * + * @param streamListener the stream listener + */ + public void addMarketDataStreamListener(MarketDataStreamListener streamListener) { + marketDataWebSocketClient.addListener(streamListener); + } + + /** + * Removes the alpaca stream listener. + * + * @param streamListener the stream listener + */ + public void removeMarketDataStreamListener(MarketDataStreamListener streamListener) { + marketDataWebSocketClient.removeListener(streamListener); + } + @Override public String toString() { return new StringJoiner(", ", this.getClass().getSimpleName() + "[", "]") diff --git a/src/main/java/net/jacobpeterson/alpaca/AlpacaExample.java b/src/main/java/net/jacobpeterson/alpaca/AlpacaExample.java index e428e896..9b57c54e 100644 --- a/src/main/java/net/jacobpeterson/alpaca/AlpacaExample.java +++ b/src/main/java/net/jacobpeterson/alpaca/AlpacaExample.java @@ -6,8 +6,13 @@ import net.jacobpeterson.alpaca.rest.exception.AlpacaAPIRequestException; import net.jacobpeterson.alpaca.websocket.broker.listener.AlpacaStreamListenerAdapter; import net.jacobpeterson.alpaca.websocket.broker.message.AlpacaStreamMessageType; +import net.jacobpeterson.alpaca.websocket.marketdata.listener.MarketDataStreamListenerAdapter; +import net.jacobpeterson.alpaca.websocket.marketdata.message.MarketDataStreamMessageType; import net.jacobpeterson.domain.alpaca.account.Account; import net.jacobpeterson.domain.alpaca.marketdata.Bar; +import net.jacobpeterson.domain.alpaca.marketdata.streaming.MarketDataStreamMessage; +import net.jacobpeterson.domain.alpaca.marketdata.streaming.quote.QuoteMessage; +import net.jacobpeterson.domain.alpaca.marketdata.streaming.trade.TradeMessage; import net.jacobpeterson.domain.alpaca.order.Order; import net.jacobpeterson.domain.alpaca.watchlist.Watchlist; import net.jacobpeterson.domain.alpaca.streaming.AlpacaStreamMessage; @@ -21,6 +26,8 @@ import java.util.ArrayList; import java.util.Map; +import static net.jacobpeterson.alpaca.websocket.marketdata.message.MarketDataStreamMessageType.QUOTES; + /** * The type Alpaca example. */ @@ -56,6 +63,24 @@ public void onStreamUpdate(AlpacaStreamMessageType streamMessageType, AlpacaStre } }); + alpacaAPI.addMarketDataStreamListener(new MarketDataStreamListenerAdapter("AAPL", QUOTES) { + @Override + public void onStreamUpdate(MarketDataStreamMessageType streamMessageType, MarketDataStreamMessage streamMessage) { + switch (streamMessageType) { + case QUOTES: + QuoteMessage quoteMessage = (QuoteMessage) streamMessage; + System.out.println("\nQuote Update: \n\t" + + quoteMessage.toString().replace(",", ",\n\t")); + break; + case TRADES: + TradeMessage tradeMessage = (TradeMessage) streamMessage; + System.out.println("\nTrade Update: \n\t" + + tradeMessage.toString().replace(",", ",\n\t")); + break; + } + } + }); + // Get Account Information try { Account alpacaAccount = alpacaAPI.getAccount(); diff --git a/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/client/MarketDataWebsocketClient.java b/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/client/MarketDataWebsocketClient.java index 9cd80184..279fcfa8 100644 --- a/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/client/MarketDataWebsocketClient.java +++ b/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/client/MarketDataWebsocketClient.java @@ -29,8 +29,10 @@ import java.net.URI; import java.net.URISyntaxException; import java.util.ArrayList; +import java.util.HashMap; import java.util.HashSet; import java.util.List; +import java.util.Map; import java.util.Set; import java.util.stream.Collectors; @@ -302,8 +304,7 @@ private void submitStreamRequest() { streamRequestJsonObject.addProperty("action", "listen"); JsonArray streamsJsonArray = new JsonArray(); - getRegisteredMessageTypes().forEach(alpacaStreamMessageType -> - streamsJsonArray.add(alpacaStreamMessageType.getAPIName())); + getRegisteredMessageTypes().forEach((s, m) -> m.forEach(i -> streamsJsonArray.add(i.getAPIName() + "." + s))); JsonObject dataJsonObject = new JsonObject(); dataJsonObject.add("streams", streamsJsonArray); @@ -320,21 +321,20 @@ private void submitStreamRequest() { * * @return the registered message types */ - public Set getRegisteredMessageTypes() { - Set registeredStreamMessageTypes = new HashSet<>(); + public Map> getRegisteredMessageTypes() { + Map> marketDataStreamMessageTypes = new HashMap<>(); for (MarketDataStreamListener marketDataStreamListener : listeners) { - Set marketDataStreamMessageTypes = new HashSet<>(); - marketDataStreamListener.getDataStreams().forEach((s, m) -> marketDataStreamMessageTypes.addAll(m)); + // Set marketDataStreamMessageTypes = new HashSet<>(); - // if its empty, assume they want everything - Set streamMessageTypesToAdd = marketDataStreamMessageTypes.isEmpty() ? new HashSet<>() : - marketDataStreamMessageTypes.stream().filter(MarketDataStreamMessageType::isAPISubscribable) - .collect(Collectors.toSet()); + marketDataStreamListener.getDataStreams().forEach((s, m) -> { + Set marketDataStreamMessageTypeSet = + m.stream().filter(MarketDataStreamMessageType::isAPISubscribable).collect(Collectors.toSet()); + marketDataStreamMessageTypes.put(s, m.stream().filter(MarketDataStreamMessageType::isAPISubscribable).collect(Collectors.toSet())); + }); - registeredStreamMessageTypes.addAll(streamMessageTypesToAdd); } - return registeredStreamMessageTypes; + return marketDataStreamMessageTypes; } } From ce49d9a9b6571507a6ea883cb78663943177e4f3 Mon Sep 17 00:00:00 2001 From: Jesse Thouin Date: Tue, 29 Sep 2020 20:55:55 -0400 Subject: [PATCH 023/271] Removed unnecessary code. --- .../websocket/marketdata/client/MarketDataWebsocketClient.java | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/client/MarketDataWebsocketClient.java b/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/client/MarketDataWebsocketClient.java index 279fcfa8..4b4bc766 100644 --- a/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/client/MarketDataWebsocketClient.java +++ b/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/client/MarketDataWebsocketClient.java @@ -325,11 +325,8 @@ public Map> getRegisteredMessageTypes() Map> marketDataStreamMessageTypes = new HashMap<>(); for (MarketDataStreamListener marketDataStreamListener : listeners) { - // Set marketDataStreamMessageTypes = new HashSet<>(); marketDataStreamListener.getDataStreams().forEach((s, m) -> { - Set marketDataStreamMessageTypeSet = - m.stream().filter(MarketDataStreamMessageType::isAPISubscribable).collect(Collectors.toSet()); marketDataStreamMessageTypes.put(s, m.stream().filter(MarketDataStreamMessageType::isAPISubscribable).collect(Collectors.toSet())); }); From a9bba5750bba449548ec0e0bd2a437c85821858f Mon Sep 17 00:00:00 2001 From: Jesse Thouin Date: Tue, 29 Sep 2020 21:02:00 -0400 Subject: [PATCH 024/271] Updated copy/paste error in comment. --- src/main/java/net/jacobpeterson/alpaca/AlpacaAPI.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/net/jacobpeterson/alpaca/AlpacaAPI.java b/src/main/java/net/jacobpeterson/alpaca/AlpacaAPI.java index 311f1fe6..30a7c3be 100644 --- a/src/main/java/net/jacobpeterson/alpaca/AlpacaAPI.java +++ b/src/main/java/net/jacobpeterson/alpaca/AlpacaAPI.java @@ -85,7 +85,7 @@ public class AlpacaAPI { /** The alpaca web socket client. */ private final AlpacaWebsocketClient alpacaWebSocketClient; - /** The alpaca web socket client. */ + /** The data web socket client. */ private final MarketDataWebsocketClient marketDataWebSocketClient; /** From c2708ed97100f2443ed3abb0d32d62fd776a8241 Mon Sep 17 00:00:00 2001 From: Jesse Thouin Date: Thu, 1 Oct 2020 21:24:03 -0400 Subject: [PATCH 025/271] The MarketData websocket server at alpaca returns String, not binary. --- .../client/MarketDataWebsocketClientEndpoint.java | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/client/MarketDataWebsocketClientEndpoint.java b/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/client/MarketDataWebsocketClientEndpoint.java index 90446613..e1da350e 100644 --- a/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/client/MarketDataWebsocketClientEndpoint.java +++ b/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/client/MarketDataWebsocketClientEndpoint.java @@ -6,6 +6,7 @@ import javax.websocket.ClientEndpoint; import javax.websocket.CloseReason; import javax.websocket.OnClose; +import javax.websocket.OnError; import javax.websocket.OnMessage; import javax.websocket.OnOpen; import javax.websocket.Session; @@ -15,7 +16,7 @@ /** * The type Market data websocket client endpoint. */ -@ClientEndpoint(subprotocols = "BINARY") +@ClientEndpoint(subprotocols = "STRING") public class MarketDataWebsocketClientEndpoint extends AbstractWebsocketClientEndpoint { /** @@ -39,7 +40,12 @@ public void onCloseAnnotated(Session userSession, CloseReason reason) { } @OnMessage - public void onMessageAnnotated(byte[] message) { - super.onMessage(new String(message, StandardCharsets.UTF_8)); + public void onMessageAnnotated(String message) { + super.onMessage(message); + } + + @OnError + public void onError(Throwable throwable) { + System.out.println(throwable.getLocalizedMessage()); } } From ca9257d05fffebc4649de19997fdbf6d3a97f130 Mon Sep 17 00:00:00 2001 From: Jesse Thouin Date: Fri, 2 Oct 2020 12:16:34 -0400 Subject: [PATCH 026/271] All data streams come in as a stream element, despite what you might expect from https://alpaca.markets/docs/api-documentation/api-v2/market-data/streaming/ --- .../client/MarketDataWebsocketClient.java | 60 +++++++++++-------- 1 file changed, 34 insertions(+), 26 deletions(-) diff --git a/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/client/MarketDataWebsocketClient.java b/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/client/MarketDataWebsocketClient.java index 4b4bc766..ac5c5710 100644 --- a/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/client/MarketDataWebsocketClient.java +++ b/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/client/MarketDataWebsocketClient.java @@ -3,6 +3,7 @@ import com.google.common.base.Preconditions; import com.google.gson.JsonArray; import com.google.gson.JsonElement; +import com.google.gson.JsonNull; import com.google.gson.JsonObject; import com.google.gson.JsonParseException; import com.google.gson.JsonPrimitive; @@ -21,6 +22,7 @@ import net.jacobpeterson.domain.alpaca.marketdata.streaming.quote.QuoteMessage; import net.jacobpeterson.domain.alpaca.marketdata.streaming.trade.TradeMessage; import net.jacobpeterson.util.gson.GsonUtil; +import org.apache.commons.lang.StringUtils; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; @@ -36,6 +38,9 @@ import java.util.Set; import java.util.stream.Collectors; +import static net.jacobpeterson.alpaca.websocket.marketdata.message.MarketDataStreamMessageType.AUTHORIZATION; +import static net.jacobpeterson.alpaca.websocket.marketdata.message.MarketDataStreamMessageType.LISTENING; + /** * The type Alpaca websocket client. */ @@ -50,6 +55,9 @@ public class MarketDataWebsocketClient implements WebsocketClient { /** The constant EVENT_TYPE_KEY. */ private static final String EVENT_TYPE_KEY = "ev"; + /** The constant EVENT_TYPE_KEY. */ + private static final String DATA_KEY = "data"; + /** The key id. */ private final String keyId; @@ -162,6 +170,9 @@ public void sendAuthenticationMessage() { @Override public void handleWebsocketMessage(String message) { + //RECEIVED: {"stream":"authorization","data":{"action":"authenticate","status":"authorized"}} + //RECEIVED: {"stream":"listening","data":{"streams":["Q.AAPL"]}} + //RECEIVED: {"stream":"Q.AAPL","data":{"ev":"Q","T":"AAPL","x":17,"p":112.12,"s":1,"X":3,"P":112.98,"S":1,"c":[0],"t":1601645053026000000}} JsonElement messageJsonElement = GsonUtil.JSON_PARSER.parse(message); Preconditions.checkState(messageJsonElement instanceof JsonObject); @@ -172,8 +183,16 @@ public void handleWebsocketMessage(String message) { if (streamJsonElement instanceof JsonPrimitive) { try { - MarketDataStreamMessageType marketDataStreamMessageType = GsonUtil.GSON.fromJson(streamJsonElement, - MarketDataStreamMessageType.class); + MarketDataStreamMessageType marketDataStreamMessageType; + JsonElement eventMessageJsonElement = JsonNull.INSTANCE; + + if(streamJsonElement.getAsString().contains(".")) { + eventMessageJsonElement = messageJsonObject.get(DATA_KEY); + JsonElement eventTypeJsonElement = eventMessageJsonElement.getAsJsonObject().get(EVENT_TYPE_KEY); + marketDataStreamMessageType = GsonUtil.GSON.fromJson(eventTypeJsonElement, MarketDataStreamMessageType.class); + } else { + marketDataStreamMessageType = GsonUtil.GSON.fromJson(streamJsonElement, MarketDataStreamMessageType.class); + } switch (marketDataStreamMessageType) { case AUTHORIZATION: @@ -192,37 +211,25 @@ public void handleWebsocketMessage(String message) { LOGGER.debug(listeningMessage); break; - default: - LOGGER.error("Unhandled stream type: " + marketDataStreamMessageType); - } - } catch (JsonParseException exception) { - LOGGER.error("Could not parse message: " + messageJsonObject, exception); - } - } else { - LOGGER.error("Unknown stream message: " + messageJsonObject); - } - } else { // Must be a data object with "ev" key - JsonElement eventTypeJsonElement = messageJsonObject.get(EVENT_TYPE_KEY); - - if (eventTypeJsonElement instanceof JsonPrimitive) { - try { - MarketDataStreamMessageType marketDataStreamMessageType = - GsonUtil.GSON.fromJson(eventTypeJsonElement, MarketDataStreamMessageType.class); - - switch (marketDataStreamMessageType) { case TRADES: - TradeMessage tradeMessage = GsonUtil.GSON.fromJson(messageJsonObject, TradeMessage.class); + TradeMessage tradeMessage = GsonUtil.GSON.fromJson(eventMessageJsonElement, TradeMessage.class); sendStreamMessageToListeners(marketDataStreamMessageType, tradeMessage); + + LOGGER.debug(tradeMessage); break; case QUOTES: - QuoteMessage quoteMessage = GsonUtil.GSON.fromJson(messageJsonObject, QuoteMessage.class); + QuoteMessage quoteMessage = GsonUtil.GSON.fromJson(eventMessageJsonElement, QuoteMessage.class); quoteMessage.setSym(quoteMessage.getTicker()); sendStreamMessageToListeners(marketDataStreamMessageType, quoteMessage); + + LOGGER.debug(quoteMessage); break; case AGGREGATE_MINUTE: - AggregateMinuteMessage aggregateMinuteMessage = GsonUtil.GSON.fromJson(messageJsonObject, + AggregateMinuteMessage aggregateMinuteMessage = GsonUtil.GSON.fromJson(eventMessageJsonElement, AggregateMinuteMessage.class); sendStreamMessageToListeners(marketDataStreamMessageType, aggregateMinuteMessage); + + LOGGER.debug(aggregateMinuteMessage); break; default: LOGGER.error("Unhandled stream type: " + marketDataStreamMessageType); @@ -246,8 +253,8 @@ public void sendStreamMessageToListeners(StreamMessageType streamMessageType, St if (marketDataStreamListener.getDataStreams() == null || marketDataStreamListener.getDataStreams().isEmpty() || marketDataStreamListener.getDataStreams().values().stream() - .anyMatch(types -> types.contains(MarketDataStreamMessageType.LISTENING) || - types.contains(MarketDataStreamMessageType.AUTHORIZATION))) { + .anyMatch(types -> types.contains(LISTENING) || + types.contains(AUTHORIZATION))) { marketDataStreamListener.onStreamUpdate(marketDataStreamMessageType, (MarketDataStreamMessage) streamMessage); } @@ -327,7 +334,8 @@ public Map> getRegisteredMessageTypes() for (MarketDataStreamListener marketDataStreamListener : listeners) { marketDataStreamListener.getDataStreams().forEach((s, m) -> { - marketDataStreamMessageTypes.put(s, m.stream().filter(MarketDataStreamMessageType::isAPISubscribable).collect(Collectors.toSet())); + marketDataStreamMessageTypes.put(s, + m.stream().filter(MarketDataStreamMessageType::isAPISubscribable).collect(Collectors.toSet())); }); } From 04ad4805c5cc32b090092a890e201786aa85ecc7 Mon Sep 17 00:00:00 2001 From: Jacob Peterson Date: Sat, 3 Oct 2020 15:07:01 -0600 Subject: [PATCH 027/271] Fix formatting --- .../net/jacobpeterson/alpaca/AlpacaAPI.java | 12 ++++---- .../jacobpeterson/alpaca/AlpacaExample.java | 5 ++-- .../client/MarketDataWebsocketClient.java | 28 +++++++++++-------- .../MarketDataWebsocketClientEndpoint.java | 1 - .../net/jacobpeterson/polygon/PolygonAPI.java | 14 +++++----- .../polygon/rest/PolygonRequest.java | 2 +- .../PolygonStreamListenerAdapter.java | 2 +- .../message/PolygonStreamMessageType.java | 2 +- 8 files changed, 35 insertions(+), 31 deletions(-) diff --git a/src/main/java/net/jacobpeterson/alpaca/AlpacaAPI.java b/src/main/java/net/jacobpeterson/alpaca/AlpacaAPI.java index 30a7c3be..45c3c64c 100644 --- a/src/main/java/net/jacobpeterson/alpaca/AlpacaAPI.java +++ b/src/main/java/net/jacobpeterson/alpaca/AlpacaAPI.java @@ -43,8 +43,8 @@ import net.jacobpeterson.domain.alpaca.position.ClosePositionOrder; import net.jacobpeterson.domain.alpaca.position.Position; import net.jacobpeterson.domain.alpaca.watchlist.Watchlist; -import net.jacobpeterson.util.gson.GsonUtil; import net.jacobpeterson.util.format.FormatUtil; +import net.jacobpeterson.util.gson.GsonUtil; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; @@ -65,7 +65,7 @@ public class AlpacaAPI { /** The logger. */ - private static Logger LOGGER = LogManager.getLogger(AlpacaAPI.class); + private static final Logger LOGGER = LogManager.getLogger(AlpacaAPI.class); /** The version. */ private final String apiVersion; @@ -195,7 +195,7 @@ public Account getAccount() throws AlpacaAPIRequestException { * Activities */ public ArrayList getAccountActivities(ZonedDateTime date, ZonedDateTime until, ZonedDateTime after, - Direction direction, Integer pageSize, String pageToken, ActivityType... activityTypes) + Direction direction, Integer pageSize, String pageToken, ActivityType... activityTypes) throws AlpacaAPIRequestException { AlpacaRequestBuilder urlBuilder = new AlpacaRequestBuilder(baseAPIURL, AlpacaConstants.VERSION_2_ENDPOINT, AlpacaConstants.ACCOUNT_ENDPOINT, @@ -338,7 +338,7 @@ public AccountConfiguration setAccountConfiguration(AccountConfiguration account * @see Orders */ public ArrayList getOrders(OrderStatus status, Integer limit, ZonedDateTime after, ZonedDateTime until, - Direction direction, Boolean nested) throws AlpacaAPIRequestException { + Direction direction, Boolean nested) throws AlpacaAPIRequestException { AlpacaRequestBuilder urlBuilder = new AlpacaRequestBuilder(baseAPIURL, apiVersion, AlpacaConstants.ORDERS_ENDPOINT); @@ -1425,7 +1425,7 @@ public Watchlist removeSymbolFromWatchlist(String watchlistID, String symbol) th * @throws AlpacaAPIRequestException the alpaca api request exception */ public PortfolioHistory getPortfolioHistory(Integer periodLength, PortfolioPeriodUnit periodUnit, - PortfolioTimeFrame timeFrame, LocalDate dateEnd, Boolean extendedHours) throws AlpacaAPIRequestException { + PortfolioTimeFrame timeFrame, LocalDate dateEnd, Boolean extendedHours) throws AlpacaAPIRequestException { AlpacaRequestBuilder urlBuilder = new AlpacaRequestBuilder(baseAPIURL, apiVersion, AlpacaConstants.ACCOUNT_ENDPOINT, AlpacaConstants.PORTFOLIO_ENDPOINT, @@ -1557,7 +1557,7 @@ public Clock getClock() throws AlpacaAPIRequestException { * @see Bars */ public Map> getBars(BarsTimeFrame timeframe, String symbol, Integer limit, - ZonedDateTime start, ZonedDateTime end, ZonedDateTime after, ZonedDateTime until) + ZonedDateTime start, ZonedDateTime end, ZonedDateTime after, ZonedDateTime until) throws AlpacaAPIRequestException { return this.getBars(timeframe, new String[]{symbol}, limit, start, end, after, until); } diff --git a/src/main/java/net/jacobpeterson/alpaca/AlpacaExample.java b/src/main/java/net/jacobpeterson/alpaca/AlpacaExample.java index 9b57c54e..82e637ec 100644 --- a/src/main/java/net/jacobpeterson/alpaca/AlpacaExample.java +++ b/src/main/java/net/jacobpeterson/alpaca/AlpacaExample.java @@ -14,10 +14,10 @@ import net.jacobpeterson.domain.alpaca.marketdata.streaming.quote.QuoteMessage; import net.jacobpeterson.domain.alpaca.marketdata.streaming.trade.TradeMessage; import net.jacobpeterson.domain.alpaca.order.Order; -import net.jacobpeterson.domain.alpaca.watchlist.Watchlist; import net.jacobpeterson.domain.alpaca.streaming.AlpacaStreamMessage; import net.jacobpeterson.domain.alpaca.streaming.account.AccountUpdateMessage; import net.jacobpeterson.domain.alpaca.streaming.trade.TradeUpdateMessage; +import net.jacobpeterson.domain.alpaca.watchlist.Watchlist; import java.time.Instant; import java.time.ZoneId; @@ -65,7 +65,8 @@ public void onStreamUpdate(AlpacaStreamMessageType streamMessageType, AlpacaStre alpacaAPI.addMarketDataStreamListener(new MarketDataStreamListenerAdapter("AAPL", QUOTES) { @Override - public void onStreamUpdate(MarketDataStreamMessageType streamMessageType, MarketDataStreamMessage streamMessage) { + public void onStreamUpdate(MarketDataStreamMessageType streamMessageType, + MarketDataStreamMessage streamMessage) { switch (streamMessageType) { case QUOTES: QuoteMessage quoteMessage = (QuoteMessage) streamMessage; diff --git a/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/client/MarketDataWebsocketClient.java b/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/client/MarketDataWebsocketClient.java index ac5c5710..1f793e92 100644 --- a/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/client/MarketDataWebsocketClient.java +++ b/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/client/MarketDataWebsocketClient.java @@ -13,8 +13,8 @@ import net.jacobpeterson.abstracts.websocket.message.StreamMessageType; import net.jacobpeterson.alpaca.websocket.marketdata.listener.MarketDataStreamListener; import net.jacobpeterson.alpaca.websocket.marketdata.message.MarketDataStreamMessageType; -import net.jacobpeterson.domain.alpaca.marketdata.streaming.abstracts.MarketDataStreamDataMessage; import net.jacobpeterson.domain.alpaca.marketdata.streaming.MarketDataStreamMessage; +import net.jacobpeterson.domain.alpaca.marketdata.streaming.abstracts.MarketDataStreamDataMessage; import net.jacobpeterson.domain.alpaca.marketdata.streaming.abstracts.MarketDataStreamStatusMessage; import net.jacobpeterson.domain.alpaca.marketdata.streaming.aggregate.AggregateMinuteMessage; import net.jacobpeterson.domain.alpaca.marketdata.streaming.authorization.AuthorizationMessage; @@ -22,7 +22,6 @@ import net.jacobpeterson.domain.alpaca.marketdata.streaming.quote.QuoteMessage; import net.jacobpeterson.domain.alpaca.marketdata.streaming.trade.TradeMessage; import net.jacobpeterson.util.gson.GsonUtil; -import org.apache.commons.lang.StringUtils; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; @@ -32,7 +31,6 @@ import java.net.URISyntaxException; import java.util.ArrayList; import java.util.HashMap; -import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; @@ -172,7 +170,8 @@ public void sendAuthenticationMessage() { public void handleWebsocketMessage(String message) { //RECEIVED: {"stream":"authorization","data":{"action":"authenticate","status":"authorized"}} //RECEIVED: {"stream":"listening","data":{"streams":["Q.AAPL"]}} - //RECEIVED: {"stream":"Q.AAPL","data":{"ev":"Q","T":"AAPL","x":17,"p":112.12,"s":1,"X":3,"P":112.98,"S":1,"c":[0],"t":1601645053026000000}} + //RECEIVED: {"stream":"Q.AAPL","data":{"ev":"Q","T":"AAPL","x":17,"p":112.12,"s":1,"X":3,"P":112.98,"S":1, + // "c":[0],"t":1601645053026000000}} JsonElement messageJsonElement = GsonUtil.JSON_PARSER.parse(message); Preconditions.checkState(messageJsonElement instanceof JsonObject); @@ -186,12 +185,15 @@ public void handleWebsocketMessage(String message) { MarketDataStreamMessageType marketDataStreamMessageType; JsonElement eventMessageJsonElement = JsonNull.INSTANCE; - if(streamJsonElement.getAsString().contains(".")) { + if (streamJsonElement.getAsString().contains(".")) { eventMessageJsonElement = messageJsonObject.get(DATA_KEY); - JsonElement eventTypeJsonElement = eventMessageJsonElement.getAsJsonObject().get(EVENT_TYPE_KEY); - marketDataStreamMessageType = GsonUtil.GSON.fromJson(eventTypeJsonElement, MarketDataStreamMessageType.class); + JsonElement eventTypeJsonElement = eventMessageJsonElement.getAsJsonObject() + .get(EVENT_TYPE_KEY); + marketDataStreamMessageType = GsonUtil.GSON.fromJson(eventTypeJsonElement, + MarketDataStreamMessageType.class); } else { - marketDataStreamMessageType = GsonUtil.GSON.fromJson(streamJsonElement, MarketDataStreamMessageType.class); + marketDataStreamMessageType = GsonUtil.GSON.fromJson(streamJsonElement, + MarketDataStreamMessageType.class); } switch (marketDataStreamMessageType) { @@ -212,21 +214,23 @@ public void handleWebsocketMessage(String message) { LOGGER.debug(listeningMessage); break; case TRADES: - TradeMessage tradeMessage = GsonUtil.GSON.fromJson(eventMessageJsonElement, TradeMessage.class); + TradeMessage tradeMessage = GsonUtil.GSON.fromJson(eventMessageJsonElement, + TradeMessage.class); sendStreamMessageToListeners(marketDataStreamMessageType, tradeMessage); LOGGER.debug(tradeMessage); break; case QUOTES: - QuoteMessage quoteMessage = GsonUtil.GSON.fromJson(eventMessageJsonElement, QuoteMessage.class); + QuoteMessage quoteMessage = GsonUtil.GSON.fromJson(eventMessageJsonElement, + QuoteMessage.class); quoteMessage.setSym(quoteMessage.getTicker()); sendStreamMessageToListeners(marketDataStreamMessageType, quoteMessage); LOGGER.debug(quoteMessage); break; case AGGREGATE_MINUTE: - AggregateMinuteMessage aggregateMinuteMessage = GsonUtil.GSON.fromJson(eventMessageJsonElement, - AggregateMinuteMessage.class); + AggregateMinuteMessage aggregateMinuteMessage = GsonUtil.GSON.fromJson( + eventMessageJsonElement, AggregateMinuteMessage.class); sendStreamMessageToListeners(marketDataStreamMessageType, aggregateMinuteMessage); LOGGER.debug(aggregateMinuteMessage); diff --git a/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/client/MarketDataWebsocketClientEndpoint.java b/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/client/MarketDataWebsocketClientEndpoint.java index e1da350e..80e8eedd 100644 --- a/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/client/MarketDataWebsocketClientEndpoint.java +++ b/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/client/MarketDataWebsocketClientEndpoint.java @@ -11,7 +11,6 @@ import javax.websocket.OnOpen; import javax.websocket.Session; import java.net.URI; -import java.nio.charset.StandardCharsets; /** * The type Market data websocket client endpoint. diff --git a/src/main/java/net/jacobpeterson/polygon/PolygonAPI.java b/src/main/java/net/jacobpeterson/polygon/PolygonAPI.java index e9095917..2e2a792f 100644 --- a/src/main/java/net/jacobpeterson/polygon/PolygonAPI.java +++ b/src/main/java/net/jacobpeterson/polygon/PolygonAPI.java @@ -58,7 +58,7 @@ public class PolygonAPI { /** The logger. */ - private static Logger LOGGER = LogManager.getLogger(PolygonAPI.class); + private static final Logger LOGGER = LogManager.getLogger(PolygonAPI.class); /** The Polygon websocket client. */ private final PolygonWebsocketClient polygonWebsocketClient; @@ -67,13 +67,13 @@ public class PolygonAPI { private final PolygonRequest polygonRequest; /** The base api url. */ - private String baseAPIURL; + private final String baseAPIURL; /** The Websocket url. */ - private String websocketURL; + private final String websocketURL; /** The Key id. */ - private String keyID; + private final String keyID; /** * Instantiates a new polygon API. @@ -129,7 +129,7 @@ public PolygonAPI(String baseAPIURL, String websocketURL, String keyID) { * @see Tickers */ public TickersResponse getTickers(TickerSort tickerSort, StockType stockType, Market market, String locale, - String search, Integer perpage, Integer page, Boolean active) throws PolygonAPIRequestException { + String search, Integer perpage, Integer page, Boolean active) throws PolygonAPIRequestException { PolygonRequestBuilder builder = new PolygonRequestBuilder(baseAPIURL, PolygonConstants.VERSION_2_ENDPOINT, PolygonConstants.REFERENCE_ENDPOINT, PolygonConstants.TICKERS_ENDPOINT); @@ -377,7 +377,7 @@ public StockDividendsResponse getStockDividends(String symbol) throws PolygonAPI * @see Stock Financials */ public StockFinancialsResponse getStockFinancials(String symbol, Integer limit, - FinancialReportType financialReportType, FinancialSort financialSort) throws PolygonAPIRequestException { + FinancialReportType financialReportType, FinancialSort financialSort) throws PolygonAPIRequestException { Preconditions.checkNotNull(symbol); PolygonRequestBuilder builder = new PolygonRequestBuilder(baseAPIURL, PolygonConstants.VERSION_2_ENDPOINT, @@ -879,7 +879,7 @@ public PreviousCloseResponse getPreviousClose(String ticker, Boolean unadjusted) * Aggregates */ public AggregatesResponse getAggregates(String ticker, Integer multiplier, Timespan timeSpan, LocalDate fromDate, - LocalDate toDate, Boolean unadjusted) throws PolygonAPIRequestException { + LocalDate toDate, Boolean unadjusted) throws PolygonAPIRequestException { Preconditions.checkNotNull(ticker); Preconditions.checkNotNull(timeSpan); Preconditions.checkNotNull(fromDate); diff --git a/src/main/java/net/jacobpeterson/polygon/rest/PolygonRequest.java b/src/main/java/net/jacobpeterson/polygon/rest/PolygonRequest.java index d595c649..5ecb3feb 100644 --- a/src/main/java/net/jacobpeterson/polygon/rest/PolygonRequest.java +++ b/src/main/java/net/jacobpeterson/polygon/rest/PolygonRequest.java @@ -19,7 +19,7 @@ public class PolygonRequest extends AbstractRequest { private static final String API_KEY_PARAMETER = "apiKey"; /** The key id. */ - private String keyId; + private final String keyId; /** * Instantiates a new polygon request. diff --git a/src/main/java/net/jacobpeterson/polygon/websocket/listener/PolygonStreamListenerAdapter.java b/src/main/java/net/jacobpeterson/polygon/websocket/listener/PolygonStreamListenerAdapter.java index 4372b9e3..abb8f1d4 100644 --- a/src/main/java/net/jacobpeterson/polygon/websocket/listener/PolygonStreamListenerAdapter.java +++ b/src/main/java/net/jacobpeterson/polygon/websocket/listener/PolygonStreamListenerAdapter.java @@ -16,7 +16,7 @@ public class PolygonStreamListenerAdapter implements PolygonStreamListener { /** The stock channel types. */ - private Map> stockChannels = new HashMap<>(); + private final Map> stockChannels = new HashMap<>(); /** * Instantiates a new polygon stream listener adapter. diff --git a/src/main/java/net/jacobpeterson/polygon/websocket/message/PolygonStreamMessageType.java b/src/main/java/net/jacobpeterson/polygon/websocket/message/PolygonStreamMessageType.java index d82b6d3a..18cce2e6 100644 --- a/src/main/java/net/jacobpeterson/polygon/websocket/message/PolygonStreamMessageType.java +++ b/src/main/java/net/jacobpeterson/polygon/websocket/message/PolygonStreamMessageType.java @@ -31,7 +31,7 @@ public enum PolygonStreamMessageType implements StreamMessageType, APIName { AGGREGATE_PER_MINUTE(true); /** The is api subscribable. */ - private boolean isAPISubscribable; + private final boolean isAPISubscribable; /** * Instantiates a new Polygon stream message type. From a1e4027e9f501fe1e82e1f01f5c27087702f9e46 Mon Sep 17 00:00:00 2001 From: Jesse Thouin Date: Mon, 5 Oct 2020 13:45:49 -0400 Subject: [PATCH 028/271] Trade and Aggregate Minute were not assigned sym in client. --- .../streaming/aggregate/aggregate_minute_message.json | 5 +++++ .../market_data/streaming/trade/trade_message.json | 5 +++++ .../java/net/jacobpeterson/alpaca/AlpacaExample.java | 10 ++++++++-- .../marketdata/client/MarketDataWebsocketClient.java | 2 ++ 4 files changed, 20 insertions(+), 2 deletions(-) diff --git a/schema_json/alpaca/market_data/streaming/aggregate/aggregate_minute_message.json b/schema_json/alpaca/market_data/streaming/aggregate/aggregate_minute_message.json index 7180cfd6..13aa62da 100644 --- a/schema_json/alpaca/market_data/streaming/aggregate/aggregate_minute_message.json +++ b/schema_json/alpaca/market_data/streaming/aggregate/aggregate_minute_message.json @@ -5,6 +5,11 @@ }, "title": "See https://alpaca.markets/docs/api-documentation/api-v2/market-data/streaming/", "properties": { + "T": { + "existingJavaType": "java.lang.String", + "javaName": "ticker", + "title": "The ticker" + }, "v": { "existingJavaType": "java.lang.Long", "title": "Tick volume (shares)" diff --git a/schema_json/alpaca/market_data/streaming/trade/trade_message.json b/schema_json/alpaca/market_data/streaming/trade/trade_message.json index 9cb9742c..6f18f611 100644 --- a/schema_json/alpaca/market_data/streaming/trade/trade_message.json +++ b/schema_json/alpaca/market_data/streaming/trade/trade_message.json @@ -5,6 +5,11 @@ }, "title": "See https://alpaca.markets/docs/api-documentation/api-v2/market-data/streaming/", "properties": { + "T": { + "existingJavaType": "java.lang.String", + "javaName": "ticker", + "title": "The ticker" + }, "i": { "existingJavaType": "java.lang.Integer", "title": "Trade ID" diff --git a/src/main/java/net/jacobpeterson/alpaca/AlpacaExample.java b/src/main/java/net/jacobpeterson/alpaca/AlpacaExample.java index 9b57c54e..9923a888 100644 --- a/src/main/java/net/jacobpeterson/alpaca/AlpacaExample.java +++ b/src/main/java/net/jacobpeterson/alpaca/AlpacaExample.java @@ -11,6 +11,7 @@ import net.jacobpeterson.domain.alpaca.account.Account; import net.jacobpeterson.domain.alpaca.marketdata.Bar; import net.jacobpeterson.domain.alpaca.marketdata.streaming.MarketDataStreamMessage; +import net.jacobpeterson.domain.alpaca.marketdata.streaming.aggregate.AggregateMinuteMessage; import net.jacobpeterson.domain.alpaca.marketdata.streaming.quote.QuoteMessage; import net.jacobpeterson.domain.alpaca.marketdata.streaming.trade.TradeMessage; import net.jacobpeterson.domain.alpaca.order.Order; @@ -26,7 +27,7 @@ import java.util.ArrayList; import java.util.Map; -import static net.jacobpeterson.alpaca.websocket.marketdata.message.MarketDataStreamMessageType.QUOTES; +import static net.jacobpeterson.alpaca.websocket.marketdata.message.MarketDataStreamMessageType.*; /** * The type Alpaca example. @@ -63,7 +64,7 @@ public void onStreamUpdate(AlpacaStreamMessageType streamMessageType, AlpacaStre } }); - alpacaAPI.addMarketDataStreamListener(new MarketDataStreamListenerAdapter("AAPL", QUOTES) { + alpacaAPI.addMarketDataStreamListener(new MarketDataStreamListenerAdapter("AAPL", QUOTES, TRADES, AGGREGATE_MINUTE) { @Override public void onStreamUpdate(MarketDataStreamMessageType streamMessageType, MarketDataStreamMessage streamMessage) { switch (streamMessageType) { @@ -77,6 +78,11 @@ public void onStreamUpdate(MarketDataStreamMessageType streamMessageType, Market System.out.println("\nTrade Update: \n\t" + tradeMessage.toString().replace(",", ",\n\t")); break; + case AGGREGATE_MINUTE: + AggregateMinuteMessage aggregateMinuteMessage = (AggregateMinuteMessage) streamMessage; + System.out.println("\nAggregate Minute Update: \n\t" + + aggregateMinuteMessage.toString().replace(",", ",\n\t")); + break; } } }); diff --git a/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/client/MarketDataWebsocketClient.java b/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/client/MarketDataWebsocketClient.java index ac5c5710..41024225 100644 --- a/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/client/MarketDataWebsocketClient.java +++ b/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/client/MarketDataWebsocketClient.java @@ -213,6 +213,7 @@ public void handleWebsocketMessage(String message) { break; case TRADES: TradeMessage tradeMessage = GsonUtil.GSON.fromJson(eventMessageJsonElement, TradeMessage.class); + tradeMessage.setSym(tradeMessage.getTicker()); sendStreamMessageToListeners(marketDataStreamMessageType, tradeMessage); LOGGER.debug(tradeMessage); @@ -227,6 +228,7 @@ public void handleWebsocketMessage(String message) { case AGGREGATE_MINUTE: AggregateMinuteMessage aggregateMinuteMessage = GsonUtil.GSON.fromJson(eventMessageJsonElement, AggregateMinuteMessage.class); + aggregateMinuteMessage.setSym(aggregateMinuteMessage.getTicker()); sendStreamMessageToListeners(marketDataStreamMessageType, aggregateMinuteMessage); LOGGER.debug(aggregateMinuteMessage); From 359a725ad185d294738c1b1c6a0c94332b119109 Mon Sep 17 00:00:00 2001 From: foobar Date: Tue, 6 Oct 2020 12:04:38 -0400 Subject: [PATCH 029/271] Merge changes that didn't make it --- .../websocket/client/AbstractWebsocketClientEndpoint.java | 7 ------- .../alpaca/websocket/client/AlpacaWebsocketClient.java | 4 ++-- 2 files changed, 2 insertions(+), 9 deletions(-) diff --git a/src/main/java/net/jacobpeterson/abstracts/websocket/client/AbstractWebsocketClientEndpoint.java b/src/main/java/net/jacobpeterson/abstracts/websocket/client/AbstractWebsocketClientEndpoint.java index 0eb49d2b..9608c535 100644 --- a/src/main/java/net/jacobpeterson/abstracts/websocket/client/AbstractWebsocketClientEndpoint.java +++ b/src/main/java/net/jacobpeterson/abstracts/websocket/client/AbstractWebsocketClientEndpoint.java @@ -106,13 +106,6 @@ protected void onClose(Session userSession, CloseReason reason) { LOGGER.debug("onClose {}", userSession); if (!reason.getCloseCode().equals(CloseReason.CloseCodes.NORMAL_CLOSURE)) { - LOGGER.info("Websocket closed abnormally. Attempting a reconnect in 3 seconds."); - - try { - Thread.sleep(3000); - } catch (InterruptedException e1) { - e1.printStackTrace(); - } LOGGER.info("Reconnecting due to closure: {}", CloseReason.CloseCodes.getCloseCode(reason.getCloseCode().getCode())); diff --git a/src/main/java/net/jacobpeterson/alpaca/websocket/client/AlpacaWebsocketClient.java b/src/main/java/net/jacobpeterson/alpaca/websocket/client/AlpacaWebsocketClient.java index 3dcb1a6f..f3fa5978 100644 --- a/src/main/java/net/jacobpeterson/alpaca/websocket/client/AlpacaWebsocketClient.java +++ b/src/main/java/net/jacobpeterson/alpaca/websocket/client/AlpacaWebsocketClient.java @@ -149,7 +149,7 @@ public void sendAuthenticationMessage() { authRequest.add("data", payload); - alpacaWebsocketClientEndpoint.sendAuthentication(authRequest.toString()); + alpacaWebsocketClientEndpoint.sendMessage(authRequest.toString()); } @Override @@ -270,7 +270,7 @@ private void submitStreamRequestUpdate() { streamRequestJsonObject.add("data", dataJsonObject); - alpacaWebsocketClientEndpoint.sendSubscription(streamRequestJsonObject.toString()); + alpacaWebsocketClientEndpoint.sendMessage(streamRequestJsonObject.toString()); LOGGER.info("Requested subscriptions to update to " + streamsJsonArray); } From 3e419798751bd1d832a9409d3a6ba5855df768f1 Mon Sep 17 00:00:00 2001 From: Jacob Peterson Date: Sat, 10 Oct 2020 10:46:14 -0600 Subject: [PATCH 030/271] Remove unused variables --- .../websocket/client/AbstractWebsocketClientEndpoint.java | 5 ----- 1 file changed, 5 deletions(-) diff --git a/src/main/java/net/jacobpeterson/abstracts/websocket/client/AbstractWebsocketClientEndpoint.java b/src/main/java/net/jacobpeterson/abstracts/websocket/client/AbstractWebsocketClientEndpoint.java index 9608c535..855809e7 100644 --- a/src/main/java/net/jacobpeterson/abstracts/websocket/client/AbstractWebsocketClientEndpoint.java +++ b/src/main/java/net/jacobpeterson/abstracts/websocket/client/AbstractWebsocketClientEndpoint.java @@ -39,11 +39,6 @@ public abstract class AbstractWebsocketClientEndpoint { /** The User session. */ private Session userSession; - /** The Retry attempts. */ - private int retryAttempts = 0; - - private String subscription; - /** * Instantiates a new Abstract websocket client endpoint. * From 48f292dff51f9485172544ec5e86d1ed5113d20c Mon Sep 17 00:00:00 2001 From: Jacob Peterson Date: Sat, 10 Oct 2020 10:49:47 -0600 Subject: [PATCH 031/271] Format code and add final modifiers --- .../java/net/jacobpeterson/alpaca/AlpacaAPI.java | 2 +- .../listener/AlpacaStreamListenerAdapter.java | 2 +- .../websocket/message/AlpacaStreamMessageType.java | 2 +- .../java/net/jacobpeterson/polygon/PolygonAPI.java | 14 +++++++------- .../jacobpeterson/polygon/rest/PolygonRequest.java | 2 +- .../listener/PolygonStreamListenerAdapter.java | 2 +- .../message/PolygonStreamMessageType.java | 2 +- 7 files changed, 13 insertions(+), 13 deletions(-) diff --git a/src/main/java/net/jacobpeterson/alpaca/AlpacaAPI.java b/src/main/java/net/jacobpeterson/alpaca/AlpacaAPI.java index a0ede6d5..1e437966 100644 --- a/src/main/java/net/jacobpeterson/alpaca/AlpacaAPI.java +++ b/src/main/java/net/jacobpeterson/alpaca/AlpacaAPI.java @@ -61,7 +61,7 @@ public class AlpacaAPI { /** The logger. */ - private static Logger LOGGER = LogManager.getLogger(AlpacaAPI.class); + private static final Logger LOGGER = LogManager.getLogger(AlpacaAPI.class); /** The version. */ private final String apiVersion; diff --git a/src/main/java/net/jacobpeterson/alpaca/websocket/listener/AlpacaStreamListenerAdapter.java b/src/main/java/net/jacobpeterson/alpaca/websocket/listener/AlpacaStreamListenerAdapter.java index 44c0b2ee..1ce19b92 100644 --- a/src/main/java/net/jacobpeterson/alpaca/websocket/listener/AlpacaStreamListenerAdapter.java +++ b/src/main/java/net/jacobpeterson/alpaca/websocket/listener/AlpacaStreamListenerAdapter.java @@ -13,7 +13,7 @@ public class AlpacaStreamListenerAdapter implements AlpacaStreamListener { /** The message types. */ - private HashSet streamUpdateTypes = new HashSet<>(); + private final HashSet streamUpdateTypes = new HashSet<>(); /** * Instantiates a new Alpaca stream listener adapter. diff --git a/src/main/java/net/jacobpeterson/alpaca/websocket/message/AlpacaStreamMessageType.java b/src/main/java/net/jacobpeterson/alpaca/websocket/message/AlpacaStreamMessageType.java index 6e4c7b40..8db5c04e 100644 --- a/src/main/java/net/jacobpeterson/alpaca/websocket/message/AlpacaStreamMessageType.java +++ b/src/main/java/net/jacobpeterson/alpaca/websocket/message/AlpacaStreamMessageType.java @@ -27,7 +27,7 @@ public enum AlpacaStreamMessageType implements StreamMessageType, APIName { ACCOUNT_UPDATES(true); /** The is api subscribable. */ - private boolean isAPISubscribable; + private final boolean isAPISubscribable; /** * Instantiates a new Alpaca stream message type. diff --git a/src/main/java/net/jacobpeterson/polygon/PolygonAPI.java b/src/main/java/net/jacobpeterson/polygon/PolygonAPI.java index e9095917..2e2a792f 100644 --- a/src/main/java/net/jacobpeterson/polygon/PolygonAPI.java +++ b/src/main/java/net/jacobpeterson/polygon/PolygonAPI.java @@ -58,7 +58,7 @@ public class PolygonAPI { /** The logger. */ - private static Logger LOGGER = LogManager.getLogger(PolygonAPI.class); + private static final Logger LOGGER = LogManager.getLogger(PolygonAPI.class); /** The Polygon websocket client. */ private final PolygonWebsocketClient polygonWebsocketClient; @@ -67,13 +67,13 @@ public class PolygonAPI { private final PolygonRequest polygonRequest; /** The base api url. */ - private String baseAPIURL; + private final String baseAPIURL; /** The Websocket url. */ - private String websocketURL; + private final String websocketURL; /** The Key id. */ - private String keyID; + private final String keyID; /** * Instantiates a new polygon API. @@ -129,7 +129,7 @@ public PolygonAPI(String baseAPIURL, String websocketURL, String keyID) { * @see Tickers */ public TickersResponse getTickers(TickerSort tickerSort, StockType stockType, Market market, String locale, - String search, Integer perpage, Integer page, Boolean active) throws PolygonAPIRequestException { + String search, Integer perpage, Integer page, Boolean active) throws PolygonAPIRequestException { PolygonRequestBuilder builder = new PolygonRequestBuilder(baseAPIURL, PolygonConstants.VERSION_2_ENDPOINT, PolygonConstants.REFERENCE_ENDPOINT, PolygonConstants.TICKERS_ENDPOINT); @@ -377,7 +377,7 @@ public StockDividendsResponse getStockDividends(String symbol) throws PolygonAPI * @see Stock Financials */ public StockFinancialsResponse getStockFinancials(String symbol, Integer limit, - FinancialReportType financialReportType, FinancialSort financialSort) throws PolygonAPIRequestException { + FinancialReportType financialReportType, FinancialSort financialSort) throws PolygonAPIRequestException { Preconditions.checkNotNull(symbol); PolygonRequestBuilder builder = new PolygonRequestBuilder(baseAPIURL, PolygonConstants.VERSION_2_ENDPOINT, @@ -879,7 +879,7 @@ public PreviousCloseResponse getPreviousClose(String ticker, Boolean unadjusted) * Aggregates */ public AggregatesResponse getAggregates(String ticker, Integer multiplier, Timespan timeSpan, LocalDate fromDate, - LocalDate toDate, Boolean unadjusted) throws PolygonAPIRequestException { + LocalDate toDate, Boolean unadjusted) throws PolygonAPIRequestException { Preconditions.checkNotNull(ticker); Preconditions.checkNotNull(timeSpan); Preconditions.checkNotNull(fromDate); diff --git a/src/main/java/net/jacobpeterson/polygon/rest/PolygonRequest.java b/src/main/java/net/jacobpeterson/polygon/rest/PolygonRequest.java index d595c649..5ecb3feb 100644 --- a/src/main/java/net/jacobpeterson/polygon/rest/PolygonRequest.java +++ b/src/main/java/net/jacobpeterson/polygon/rest/PolygonRequest.java @@ -19,7 +19,7 @@ public class PolygonRequest extends AbstractRequest { private static final String API_KEY_PARAMETER = "apiKey"; /** The key id. */ - private String keyId; + private final String keyId; /** * Instantiates a new polygon request. diff --git a/src/main/java/net/jacobpeterson/polygon/websocket/listener/PolygonStreamListenerAdapter.java b/src/main/java/net/jacobpeterson/polygon/websocket/listener/PolygonStreamListenerAdapter.java index 4372b9e3..abb8f1d4 100644 --- a/src/main/java/net/jacobpeterson/polygon/websocket/listener/PolygonStreamListenerAdapter.java +++ b/src/main/java/net/jacobpeterson/polygon/websocket/listener/PolygonStreamListenerAdapter.java @@ -16,7 +16,7 @@ public class PolygonStreamListenerAdapter implements PolygonStreamListener { /** The stock channel types. */ - private Map> stockChannels = new HashMap<>(); + private final Map> stockChannels = new HashMap<>(); /** * Instantiates a new polygon stream listener adapter. diff --git a/src/main/java/net/jacobpeterson/polygon/websocket/message/PolygonStreamMessageType.java b/src/main/java/net/jacobpeterson/polygon/websocket/message/PolygonStreamMessageType.java index d82b6d3a..18cce2e6 100644 --- a/src/main/java/net/jacobpeterson/polygon/websocket/message/PolygonStreamMessageType.java +++ b/src/main/java/net/jacobpeterson/polygon/websocket/message/PolygonStreamMessageType.java @@ -31,7 +31,7 @@ public enum PolygonStreamMessageType implements StreamMessageType, APIName { AGGREGATE_PER_MINUTE(true); /** The is api subscribable. */ - private boolean isAPISubscribable; + private final boolean isAPISubscribable; /** * Instantiates a new Polygon stream message type. From bc6617cdb510b4857cedc472f7d0bf1e19837b53 Mon Sep 17 00:00:00 2001 From: Jacob Peterson Date: Sat, 10 Oct 2020 11:02:05 -0600 Subject: [PATCH 032/271] Remove unused dependency and format --- build.gradle | 1 - .../jacobpeterson/alpaca/AlpacaExample.java | 47 ++++++++++--------- 2 files changed, 24 insertions(+), 24 deletions(-) diff --git a/build.gradle b/build.gradle index 94e23a0c..70dcff12 100644 --- a/build.gradle +++ b/build.gradle @@ -19,7 +19,6 @@ plugins { id 'maven-publish' id 'com.bmuschko.nexus' version '2.3.1' id 'io.codearte.nexus-staging' version '0.11.0' - id 'com.github.johnrengelman.shadow' version '6.0.0' } // Calling 'apply' here because jsonschema2pojo doesn't exist in Gradle Central Plugin Repository diff --git a/src/main/java/net/jacobpeterson/alpaca/AlpacaExample.java b/src/main/java/net/jacobpeterson/alpaca/AlpacaExample.java index 9479d117..41a72ee9 100644 --- a/src/main/java/net/jacobpeterson/alpaca/AlpacaExample.java +++ b/src/main/java/net/jacobpeterson/alpaca/AlpacaExample.java @@ -64,29 +64,30 @@ public void onStreamUpdate(AlpacaStreamMessageType streamMessageType, AlpacaStre } }); - alpacaAPI.addMarketDataStreamListener(new MarketDataStreamListenerAdapter("AAPL", QUOTES, TRADES, AGGREGATE_MINUTE) { - @Override - public void onStreamUpdate(MarketDataStreamMessageType streamMessageType, - MarketDataStreamMessage streamMessage) { - switch (streamMessageType) { - case QUOTES: - QuoteMessage quoteMessage = (QuoteMessage) streamMessage; - System.out.println("\nQuote Update: \n\t" + - quoteMessage.toString().replace(",", ",\n\t")); - break; - case TRADES: - TradeMessage tradeMessage = (TradeMessage) streamMessage; - System.out.println("\nTrade Update: \n\t" + - tradeMessage.toString().replace(",", ",\n\t")); - break; - case AGGREGATE_MINUTE: - AggregateMinuteMessage aggregateMinuteMessage = (AggregateMinuteMessage) streamMessage; - System.out.println("\nAggregate Minute Update: \n\t" + - aggregateMinuteMessage.toString().replace(",", ",\n\t")); - break; - } - } - }); + alpacaAPI.addMarketDataStreamListener( + new MarketDataStreamListenerAdapter("AAPL", QUOTES, TRADES, AGGREGATE_MINUTE) { + @Override + public void onStreamUpdate(MarketDataStreamMessageType streamMessageType, + MarketDataStreamMessage streamMessage) { + switch (streamMessageType) { + case QUOTES: + QuoteMessage quoteMessage = (QuoteMessage) streamMessage; + System.out.println("\nQuote Update: \n\t" + + quoteMessage.toString().replace(",", ",\n\t")); + break; + case TRADES: + TradeMessage tradeMessage = (TradeMessage) streamMessage; + System.out.println("\nTrade Update: \n\t" + + tradeMessage.toString().replace(",", ",\n\t")); + break; + case AGGREGATE_MINUTE: + AggregateMinuteMessage aggregateMinuteMessage = (AggregateMinuteMessage) streamMessage; + System.out.println("\nAggregate Minute Update: \n\t" + + aggregateMinuteMessage.toString().replace(",", ",\n\t")); + break; + } + } + }); // Get Account Information try { From 8a41f96f7e87748d6ec0ddbe6bfc2b88a77dda11 Mon Sep 17 00:00:00 2001 From: Jacob Peterson Date: Sat, 10 Oct 2020 11:25:39 -0600 Subject: [PATCH 033/271] Transparent Logo and modify README --- README.md | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/README.md b/README.md index b6591af3..0555cd91 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,4 @@ -# UPDATING FROM 5.0.13 TO 5.2 -This repository has been transferred and as a result, lots of refactoring has occurred. This is because artifacts cannot be transferred in Maven Central. So, the new groupID for the project is `net.jacobpeterson` and all packages have been renamed from `io.github.mainstringargs` to `net.jacobpeterson`. Please refactor accordingly when you update from 5.0.13 to 5.2. Thanks! -

+

Maven Central Javadocs Build Status GitHub From eb9f384c624bd4924808a535e353b3d1b57f852b Mon Sep 17 00:00:00 2001 From: Jacob Peterson Date: Sat, 10 Oct 2020 11:58:48 -0600 Subject: [PATCH 034/271] Add handleResubscribe for MarketDataWebsocketClient --- .../marketdata/client/MarketDataWebsocketClient.java | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/client/MarketDataWebsocketClient.java b/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/client/MarketDataWebsocketClient.java index 8fabcf1a..e9663fef 100644 --- a/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/client/MarketDataWebsocketClient.java +++ b/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/client/MarketDataWebsocketClient.java @@ -166,6 +166,11 @@ public void sendAuthenticationMessage() { marketDataWebsocketClientEndpoint.sendMessage(authRequest.toString()); } + @Override + public void handleResubscribing() { + submitStreamRequest(); + } + @Override public void handleWebsocketMessage(String message) { //RECEIVED: {"stream":"authorization","data":{"action":"authenticate","status":"authorized"}} From 91c73c8ce74dc2a70fbe7c2c54205c78615d2324 Mon Sep 17 00:00:00 2001 From: Jacob Peterson Date: Sat, 10 Oct 2020 12:31:40 -0600 Subject: [PATCH 035/271] Migrate to travi-ci.com --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 0555cd91..4b8aaac8 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@

-Maven Central Javadocs Build Status +Maven Central Javadocs Build Status GitHub

From f3ee23e1d45424fe63d1134d5bccf2b6324310fc Mon Sep 17 00:00:00 2001 From: Jacob Peterson Date: Thu, 15 Oct 2020 12:47:20 -0600 Subject: [PATCH 036/271] Update to 5.3 --- README.md | 4 ++-- build.gradle | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 4b8aaac8..b0ebc368 100644 --- a/README.md +++ b/README.md @@ -31,7 +31,7 @@ Add the following dependency to your build.gradle file: ``` dependencies { - compile "net.jacobpeterson:alpaca-java:5.2" + compile "net.jacobpeterson:alpaca-java:5.3" } ``` @@ -43,7 +43,7 @@ Add the following dependency to your pom.xml file: net.jacobpeterson alpaca-java - 5.2 + 5.3 compile ``` diff --git a/build.gradle b/build.gradle index 7254014d..53638c54 100644 --- a/build.gradle +++ b/build.gradle @@ -27,7 +27,7 @@ tasks.getByName("generateJsonSchema2Pojo").enabled(false) // Disable default jso archivesBaseName = 'alpaca-java' group = 'net.jacobpeterson' -version = '5.2' +version = '5.3' repositories { mavenLocal() From 258d0b20cccfab211f3ac100eef4af44ea161b8d Mon Sep 17 00:00:00 2001 From: Jacob Peterson Date: Sat, 17 Oct 2020 12:18:50 -0600 Subject: [PATCH 037/271] Maven Central checksum integrity info --- README.md | 37 +++++++++++++++++++++++++++++++------ build.gradle | 20 ++++++++++++++++++++ upload_to_maven_central.sh | 2 ++ 3 files changed, 53 insertions(+), 6 deletions(-) create mode 100755 upload_to_maven_central.sh diff --git a/README.md b/README.md index b0ebc368..a81cc197 100644 --- a/README.md +++ b/README.md @@ -15,9 +15,7 @@ This is a Java implementation for Alpaca. 5. [AlpacaAPI Example](#alpacaapi-example) 7. [PolygonAPI Example](#polygonapi-example) -## Alpaca Java Building - -This project exposes that data as a Java project. +## Building To build this project yourself, clone the project and run: @@ -25,17 +23,23 @@ To build this project yourself, clone the project and run: ./gradlew build ``` -## Alpaca Java Gradle Integration +To build this project and install it to your local maven repo, run: + +``` +./gradlew build install +``` + +## Gradle Integration Add the following dependency to your build.gradle file: ``` dependencies { - compile "net.jacobpeterson:alpaca-java:5.3" + compile "net.jacobpeterson:alpaca-java:5.3" } ``` -## Alpaca Java Maven Integration +## Maven Integration Add the following dependency to your pom.xml file: @@ -47,6 +51,27 @@ Add the following dependency to your pom.xml file: compile
``` + +## Maven Central Integrity + +Since this community-developed library uses sensitive information (e.g. your Alpaca API keys), +it's important to verify that the jar in Maven Central that is downloaded via Gradle or Maven is indeed the jar that is built +from this source code that you see on this Github repository. This library has been configured to create reproducible builds +that provide the same file checksums when built on any machine. Maven Central has a way to verify that artifacts are uploaded +by the domain holder of the artifact group ID (e.g. `jacobpeterson.net`) via PGP artifact signing, but lacks the ability +to verify the legitimacy of those artifacts in the context of an open-source library. To verify that this dependency +that you use in your trading algorithm projects is indeed legitimate, we need to compare the Jar MD5 checksum in Maven Central +with the Jar MD5 checksum from this source code: +1. Clone the repo somewhere: `git clone https://github.com/Petersoj/alpaca-java` +2. `cd alpaca-java` +3. Build the project: `./gradlew build` + 1. Note: if you build the project twice without a `clean` task in between (e.g. `./gradlew build; ./gradlew build`), the MD5 checksum will change. +4. Generate the MD5 checksum: `md5 build/libs/alpaca-java-5.3.jar` +5. Confirm that the checksum value in Maven Central is the same as in step 4: +`curl https://repo1.maven.org/maven2/net/jacobpeterson/alpaca-java/5.3/alpaca-java-5.3.jar.md5` + +If the checksums are different, please [create a new issue](https://github.com/Petersoj/alpaca-java/issues/new)! + ## Configuration If you plan on using the alpaca.properties, set the following properties in an alpaca.properties file on the classpath: diff --git a/build.gradle b/build.gradle index 53638c54..933e92f2 100644 --- a/build.gradle +++ b/build.gradle @@ -60,10 +60,30 @@ javadoc.options.addStringOption("charset", "UTF-8") //mainClassName = 'net.jacobpeterson.polygon.PolygonExample' mainClassName = 'net.jacobpeterson.alpaca.AlpacaExample' + // // END Build options // +// +// START reproducible builds +// +// Reference: https://dzone.com/articles/reproducible-builds-in-java +// Prevent manifest from changing every build +project.tasks.withType(Jar) { + manifest.attributes Date: '' + preserveFileTimestamps = false +} + +// Prevent timestamps from appearing in JAR and use reproducible file order +tasks.withType(AbstractArchiveTask) { + preserveFileTimestamps = false + reproducibleFileOrder = true +} +// +// END reproducible builds +// + // // START publishing // diff --git a/upload_to_maven_central.sh b/upload_to_maven_central.sh new file mode 100755 index 00000000..32e339be --- /dev/null +++ b/upload_to_maven_central.sh @@ -0,0 +1,2 @@ +./gradlew clean build uploadArchives # uploadArchives MUST be done with a clean build otherwise the checksums of the files will differ +./gradlew closeAndReleaseRepository From da7a1a22087be8793cff1488b5f284680c7d15fa Mon Sep 17 00:00:00 2001 From: Jacob Peterson Date: Sat, 17 Oct 2020 12:24:34 -0600 Subject: [PATCH 038/271] Update to 5.4 --- README.md | 8 ++++---- build.gradle | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index a81cc197..7a796497 100644 --- a/README.md +++ b/README.md @@ -35,7 +35,7 @@ Add the following dependency to your build.gradle file: ``` dependencies { - compile "net.jacobpeterson:alpaca-java:5.3" + compile "net.jacobpeterson:alpaca-java:5.4" } ``` @@ -47,7 +47,7 @@ Add the following dependency to your pom.xml file: net.jacobpeterson alpaca-java - 5.3 + 5.4 compile ``` @@ -66,9 +66,9 @@ with the Jar MD5 checksum from this source code: 2. `cd alpaca-java` 3. Build the project: `./gradlew build` 1. Note: if you build the project twice without a `clean` task in between (e.g. `./gradlew build; ./gradlew build`), the MD5 checksum will change. -4. Generate the MD5 checksum: `md5 build/libs/alpaca-java-5.3.jar` +4. Generate the MD5 checksum: `md5 build/libs/alpaca-java-5.4.jar` 5. Confirm that the checksum value in Maven Central is the same as in step 4: -`curl https://repo1.maven.org/maven2/net/jacobpeterson/alpaca-java/5.3/alpaca-java-5.3.jar.md5` +`curl https://repo1.maven.org/maven2/net/jacobpeterson/alpaca-java/5.4/alpaca-java-5.4.jar.md5` If the checksums are different, please [create a new issue](https://github.com/Petersoj/alpaca-java/issues/new)! diff --git a/build.gradle b/build.gradle index 933e92f2..63f6e5a5 100644 --- a/build.gradle +++ b/build.gradle @@ -27,7 +27,7 @@ tasks.getByName("generateJsonSchema2Pojo").enabled(false) // Disable default jso archivesBaseName = 'alpaca-java' group = 'net.jacobpeterson' -version = '5.3' +version = '5.4' repositories { mavenLocal() From 70bb65ee1549cdae72fd7c2cad8fa88cd400aa8b Mon Sep 17 00:00:00 2001 From: Jacob Peterson <14127217+Petersoj@users.noreply.github.com> Date: Mon, 19 Oct 2020 00:24:33 -0600 Subject: [PATCH 039/271] Update README.md --- README.md | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index 7a796497..e7429141 100644 --- a/README.md +++ b/README.md @@ -4,15 +4,16 @@ GitHub

-# Overview +## Overview This is a Java implementation for Alpaca. Alpaca lets you build and trade with real-time market data for free. This library is community developed and if you have any questions, please ask them on the [Alpaca Slack #dev-alpaca-java channel](https://alpaca.markets/slack) or on the [Alpaca Forums](https://forum.alpaca.markets/). ## Table of Contents -1. [Alpaca Java Building](#alpaca-java-building) -2. [Alpaca Java Gradle Integration](#alpaca-java-gradle-integration) -3. [Alpaca Java Maven Integration](#alpaca-java-maven-integration) -4. [Configuration](#configuration) -5. [AlpacaAPI Example](#alpacaapi-example) +1. [Building](#building) +2. [Gradle Integration](#gradle-integration) +3. [Maven Integration](#maven-integration) +4. [Maven Central Integrity](#maven-central-integrity) +5. [Configuration](#configuration) +6. [AlpacaAPI Example](#alpacaapi-example) 7. [PolygonAPI Example](#polygonapi-example) ## Building @@ -323,7 +324,7 @@ Bars response: Volume: 1.4021695E7 ``` -# PolygonAPI Example +## PolygonAPI Example This example uses the `PolygonAPI` class to subscribe to the Polygon websocket stream, get stocks splits, and get aggregates. Click [here](https://polygon.io/docs/) for the general Polygon API documentation and click [here](https://javadoc.io/doc/net.jacobpeterson/alpaca-java/latest/net/jacobpeterson/polygon/PolygonAPI.html) for the `PolygonAPI` javadoc. From 3d063f6a6bf247d3dab1de5c1b56c00a4605e57e Mon Sep 17 00:00:00 2001 From: Justin Schoeff Date: Tue, 20 Oct 2020 13:23:13 -0500 Subject: [PATCH 040/271] Synchronize listeners array This makes the listeners array list interactions thread-safe. Currently, if a listener is added or removed while receiving a notification you will get a concurrent modification exception. This fixes that by simply wrapping the listeners in a synchronized list impl and synchronizing on the listeners array any time iteration is to be performed. --- .../client/AlpacaWebsocketClient.java | 31 +++++++++++-------- 1 file changed, 18 insertions(+), 13 deletions(-) diff --git a/src/main/java/net/jacobpeterson/alpaca/websocket/client/AlpacaWebsocketClient.java b/src/main/java/net/jacobpeterson/alpaca/websocket/client/AlpacaWebsocketClient.java index f3fa5978..1ee127fa 100644 --- a/src/main/java/net/jacobpeterson/alpaca/websocket/client/AlpacaWebsocketClient.java +++ b/src/main/java/net/jacobpeterson/alpaca/websocket/client/AlpacaWebsocketClient.java @@ -26,6 +26,7 @@ import java.net.URI; import java.net.URISyntaxException; import java.util.ArrayList; +import java.util.Collections; import java.util.HashSet; import java.util.List; import java.util.Set; @@ -72,7 +73,7 @@ public AlpacaWebsocketClient(String keyId, String secret, String baseAPIURL) { this.secret = secret; this.baseAPIURL = baseAPIURL.replace("https", "wss") + "/stream"; - this.listeners = new ArrayList<>(); + this.listeners = Collections.synchronizedList(new ArrayList<>()); } @Override @@ -215,11 +216,13 @@ public void sendStreamMessageToListeners(StreamMessageType streamMessageType, St AlpacaStreamMessageType alpacaStreamMessageType = (AlpacaStreamMessageType) streamMessageType; AlpacaStreamMessage alpacaStreamMessage = (AlpacaStreamMessage) streamMessage; - for (AlpacaStreamListener alpacaStreamListener : listeners) { - if (alpacaStreamListener.getStreamMessageTypes() == null || - alpacaStreamListener.getStreamMessageTypes().isEmpty() || - alpacaStreamListener.getStreamMessageTypes().contains(alpacaStreamMessageType)) { - alpacaStreamListener.onStreamUpdate(alpacaStreamMessageType, alpacaStreamMessage); + synchronized (listeners) { + for (AlpacaStreamListener alpacaStreamListener : listeners) { + if (alpacaStreamListener.getStreamMessageTypes() == null || + alpacaStreamListener.getStreamMessageTypes().isEmpty() || + alpacaStreamListener.getStreamMessageTypes().contains(alpacaStreamMessageType)) { + alpacaStreamListener.onStreamUpdate(alpacaStreamMessageType, alpacaStreamMessage); + } } } } @@ -283,15 +286,17 @@ private void submitStreamRequestUpdate() { public Set getRegisteredMessageTypes() { Set registeredStreamMessageTypes = new HashSet<>(); - for (AlpacaStreamListener alpacaStreamListener : listeners) { - Set alpacaStreamMessageTypes = alpacaStreamListener.getStreamMessageTypes(); + synchronized (listeners) { + for (AlpacaStreamListener alpacaStreamListener : listeners) { + Set alpacaStreamMessageTypes = alpacaStreamListener.getStreamMessageTypes(); - // if its empty, assume they want everything - Set streamMessageTypesToAdd = alpacaStreamMessageTypes == null ? new HashSet<>() : - alpacaStreamMessageTypes.stream().filter(AlpacaStreamMessageType::isAPISubscribable) - .collect(Collectors.toSet()); + // if its empty, assume they want everything + Set streamMessageTypesToAdd = alpacaStreamMessageTypes == null ? new HashSet<>() : + alpacaStreamMessageTypes.stream().filter(AlpacaStreamMessageType::isAPISubscribable) + .collect(Collectors.toSet()); - registeredStreamMessageTypes.addAll(streamMessageTypesToAdd); + registeredStreamMessageTypes.addAll(streamMessageTypesToAdd); + } } return registeredStreamMessageTypes; From 0c48c1f5f49f08e77068af7dea0060ce3995177f Mon Sep 17 00:00:00 2001 From: Jacob Peterson Date: Tue, 20 Oct 2020 13:31:42 -0600 Subject: [PATCH 041/271] Remove synchronized and use copy constructors --- .../client/AlpacaWebsocketClient.java | 29 +++++++++---------- .../client/PolygonWebsocketClient.java | 7 +++-- 2 files changed, 17 insertions(+), 19 deletions(-) diff --git a/src/main/java/net/jacobpeterson/alpaca/websocket/client/AlpacaWebsocketClient.java b/src/main/java/net/jacobpeterson/alpaca/websocket/client/AlpacaWebsocketClient.java index 1ee127fa..a3234260 100644 --- a/src/main/java/net/jacobpeterson/alpaca/websocket/client/AlpacaWebsocketClient.java +++ b/src/main/java/net/jacobpeterson/alpaca/websocket/client/AlpacaWebsocketClient.java @@ -216,13 +216,11 @@ public void sendStreamMessageToListeners(StreamMessageType streamMessageType, St AlpacaStreamMessageType alpacaStreamMessageType = (AlpacaStreamMessageType) streamMessageType; AlpacaStreamMessage alpacaStreamMessage = (AlpacaStreamMessage) streamMessage; - synchronized (listeners) { - for (AlpacaStreamListener alpacaStreamListener : listeners) { - if (alpacaStreamListener.getStreamMessageTypes() == null || - alpacaStreamListener.getStreamMessageTypes().isEmpty() || - alpacaStreamListener.getStreamMessageTypes().contains(alpacaStreamMessageType)) { - alpacaStreamListener.onStreamUpdate(alpacaStreamMessageType, alpacaStreamMessage); - } + for (AlpacaStreamListener alpacaStreamListener : new ArrayList<>(listeners)) { + if (alpacaStreamListener.getStreamMessageTypes() == null || + alpacaStreamListener.getStreamMessageTypes().isEmpty() || + alpacaStreamListener.getStreamMessageTypes().contains(alpacaStreamMessageType)) { + alpacaStreamListener.onStreamUpdate(alpacaStreamMessageType, alpacaStreamMessage); } } } @@ -286,17 +284,16 @@ private void submitStreamRequestUpdate() { public Set getRegisteredMessageTypes() { Set registeredStreamMessageTypes = new HashSet<>(); - synchronized (listeners) { - for (AlpacaStreamListener alpacaStreamListener : listeners) { - Set alpacaStreamMessageTypes = alpacaStreamListener.getStreamMessageTypes(); + for (AlpacaStreamListener alpacaStreamListener : new ArrayList<>(listeners)) { + Set alpacaStreamMessageTypes = alpacaStreamListener.getStreamMessageTypes(); - // if its empty, assume they want everything - Set streamMessageTypesToAdd = alpacaStreamMessageTypes == null ? new HashSet<>() : - alpacaStreamMessageTypes.stream().filter(AlpacaStreamMessageType::isAPISubscribable) - .collect(Collectors.toSet()); + // if its empty, assume they want everything + Set streamMessageTypesToAdd = + alpacaStreamMessageTypes == null ? new HashSet<>() : + alpacaStreamMessageTypes.stream().filter(AlpacaStreamMessageType::isAPISubscribable) + .collect(Collectors.toSet()); - registeredStreamMessageTypes.addAll(streamMessageTypesToAdd); - } + registeredStreamMessageTypes.addAll(streamMessageTypesToAdd); } return registeredStreamMessageTypes; diff --git a/src/main/java/net/jacobpeterson/polygon/websocket/client/PolygonWebsocketClient.java b/src/main/java/net/jacobpeterson/polygon/websocket/client/PolygonWebsocketClient.java index 93a0c68a..1a84e4cc 100644 --- a/src/main/java/net/jacobpeterson/polygon/websocket/client/PolygonWebsocketClient.java +++ b/src/main/java/net/jacobpeterson/polygon/websocket/client/PolygonWebsocketClient.java @@ -27,6 +27,7 @@ import java.net.URI; import java.net.URISyntaxException; import java.util.ArrayList; +import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.List; @@ -74,7 +75,7 @@ public PolygonWebsocketClient(String keyId, String websocketURL) { this.keyId = keyId; this.websocketURL = websocketURL; - this.listeners = new ArrayList<>(); + this.listeners = Collections.synchronizedList(new ArrayList<>()); } @Override @@ -207,7 +208,7 @@ public void sendStreamMessageToListeners(StreamMessageType streamMessageType, St PolygonStreamMessageType polygonStreamMessageType = (PolygonStreamMessageType) streamMessageType; PolygonStreamMessage polygonStreamMessage = (PolygonStreamMessage) streamMessage; - for (PolygonStreamListener streamListener : listeners) { + for (PolygonStreamListener streamListener : new ArrayList<>(listeners)) { boolean sendToStreamListener = false; if (streamListener.getStockChannels().containsKey(polygonStreamMessage.getSym())) { @@ -353,7 +354,7 @@ private String formatWebsocketTicker(PolygonStreamMessageType polygonStreamMessa private Map> getRegisteredTickerChannels(PolygonStreamListener exclude) { HashMap> registeredTickerChannels = new HashMap<>(); - for (PolygonStreamListener streamListener : listeners) { + for (PolygonStreamListener streamListener : new ArrayList<>(listeners)) { if (streamListener.equals(exclude)) { continue; } From 3de4e2be0b5a016bc8de3716ac5977cd1c358501 Mon Sep 17 00:00:00 2001 From: Jacob Peterson Date: Wed, 21 Oct 2020 15:59:01 -0600 Subject: [PATCH 042/271] Remove synchronizedList --- .../alpaca/websocket/client/AlpacaWebsocketClient.java | 3 +-- .../polygon/websocket/client/PolygonWebsocketClient.java | 3 +-- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/src/main/java/net/jacobpeterson/alpaca/websocket/client/AlpacaWebsocketClient.java b/src/main/java/net/jacobpeterson/alpaca/websocket/client/AlpacaWebsocketClient.java index a3234260..129fb632 100644 --- a/src/main/java/net/jacobpeterson/alpaca/websocket/client/AlpacaWebsocketClient.java +++ b/src/main/java/net/jacobpeterson/alpaca/websocket/client/AlpacaWebsocketClient.java @@ -26,7 +26,6 @@ import java.net.URI; import java.net.URISyntaxException; import java.util.ArrayList; -import java.util.Collections; import java.util.HashSet; import java.util.List; import java.util.Set; @@ -73,7 +72,7 @@ public AlpacaWebsocketClient(String keyId, String secret, String baseAPIURL) { this.secret = secret; this.baseAPIURL = baseAPIURL.replace("https", "wss") + "/stream"; - this.listeners = Collections.synchronizedList(new ArrayList<>()); + this.listeners = new ArrayList<>(); } @Override diff --git a/src/main/java/net/jacobpeterson/polygon/websocket/client/PolygonWebsocketClient.java b/src/main/java/net/jacobpeterson/polygon/websocket/client/PolygonWebsocketClient.java index 1a84e4cc..5c10338d 100644 --- a/src/main/java/net/jacobpeterson/polygon/websocket/client/PolygonWebsocketClient.java +++ b/src/main/java/net/jacobpeterson/polygon/websocket/client/PolygonWebsocketClient.java @@ -27,7 +27,6 @@ import java.net.URI; import java.net.URISyntaxException; import java.util.ArrayList; -import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.List; @@ -75,7 +74,7 @@ public PolygonWebsocketClient(String keyId, String websocketURL) { this.keyId = keyId; this.websocketURL = websocketURL; - this.listeners = Collections.synchronizedList(new ArrayList<>()); + this.listeners = new ArrayList<>(); } @Override From 52e5a4ae809849707906edd0c4de2e2eb8f5ed5a Mon Sep 17 00:00:00 2001 From: Jacob Peterson Date: Sun, 25 Oct 2020 11:37:24 -0600 Subject: [PATCH 043/271] Remove application plugin and add allJar task --- build.gradle | 23 ++- .../jacobpeterson/alpaca/AlpacaExample.java | 152 ------------------ .../jacobpeterson/polygon/PolygonExample.java | 74 --------- 3 files changed, 18 insertions(+), 231 deletions(-) delete mode 100644 src/main/java/net/jacobpeterson/alpaca/AlpacaExample.java delete mode 100644 src/main/java/net/jacobpeterson/polygon/PolygonExample.java diff --git a/build.gradle b/build.gradle index 63f6e5a5..c0acd348 100644 --- a/build.gradle +++ b/build.gradle @@ -14,7 +14,6 @@ buildscript { plugins { id 'java' - id 'application' id 'eclipse' id 'maven-publish' id 'com.bmuschko.nexus' version '2.3.1' @@ -57,10 +56,6 @@ targetCompatibility = 1.8 // Used to suppress java doc warnings javadoc.options.addStringOption('Xdoclint:none', '-quiet') javadoc.options.addStringOption("charset", "UTF-8") - -//mainClassName = 'net.jacobpeterson.polygon.PolygonExample' -mainClassName = 'net.jacobpeterson.alpaca.AlpacaExample' - // // END Build options // @@ -146,6 +141,24 @@ processResources { // END publishing // +// +// START allJar task +// +// Used for creating a far jar (a jar packaged with dependencies) +task allJar(type: Jar) { + from { + configurations.compile.collect { it.isDirectory() ? it : zipTree(it) } + configurations.runtimeClasspath.collect { it.isDirectory() ? it : zipTree(it) } + } + + archiveClassifier.set "all" + + with jar +} +// +// END allJar task +// + // // START POJO generation // diff --git a/src/main/java/net/jacobpeterson/alpaca/AlpacaExample.java b/src/main/java/net/jacobpeterson/alpaca/AlpacaExample.java deleted file mode 100644 index 41a72ee9..00000000 --- a/src/main/java/net/jacobpeterson/alpaca/AlpacaExample.java +++ /dev/null @@ -1,152 +0,0 @@ -package net.jacobpeterson.alpaca; - -import net.jacobpeterson.alpaca.enums.BarsTimeFrame; -import net.jacobpeterson.alpaca.enums.OrderSide; -import net.jacobpeterson.alpaca.enums.OrderTimeInForce; -import net.jacobpeterson.alpaca.rest.exception.AlpacaAPIRequestException; -import net.jacobpeterson.alpaca.websocket.broker.listener.AlpacaStreamListenerAdapter; -import net.jacobpeterson.alpaca.websocket.broker.message.AlpacaStreamMessageType; -import net.jacobpeterson.alpaca.websocket.marketdata.listener.MarketDataStreamListenerAdapter; -import net.jacobpeterson.alpaca.websocket.marketdata.message.MarketDataStreamMessageType; -import net.jacobpeterson.domain.alpaca.account.Account; -import net.jacobpeterson.domain.alpaca.marketdata.Bar; -import net.jacobpeterson.domain.alpaca.marketdata.streaming.MarketDataStreamMessage; -import net.jacobpeterson.domain.alpaca.marketdata.streaming.aggregate.AggregateMinuteMessage; -import net.jacobpeterson.domain.alpaca.marketdata.streaming.quote.QuoteMessage; -import net.jacobpeterson.domain.alpaca.marketdata.streaming.trade.TradeMessage; -import net.jacobpeterson.domain.alpaca.order.Order; -import net.jacobpeterson.domain.alpaca.streaming.AlpacaStreamMessage; -import net.jacobpeterson.domain.alpaca.streaming.account.AccountUpdateMessage; -import net.jacobpeterson.domain.alpaca.streaming.trade.TradeUpdateMessage; -import net.jacobpeterson.domain.alpaca.watchlist.Watchlist; - -import java.time.Instant; -import java.time.ZoneId; -import java.time.ZoneOffset; -import java.time.ZonedDateTime; -import java.util.ArrayList; -import java.util.Map; - -import static net.jacobpeterson.alpaca.websocket.marketdata.message.MarketDataStreamMessageType.*; - -/** - * The type Alpaca example. - */ -public class AlpacaExample { - - /** - * The entry point of application. - * - * @param args the input arguments - */ - public static void main(String[] args) { - // This logs into Alpaca using the alpaca.properties file on the classpath. - AlpacaAPI alpacaAPI = new AlpacaAPI(); - - // Register explicitly for ACCOUNT_UPDATES and ORDER_UPDATES Messages via stream listener - alpacaAPI.addAlpacaStreamListener(new AlpacaStreamListenerAdapter( - AlpacaStreamMessageType.ACCOUNT_UPDATES, - AlpacaStreamMessageType.TRADE_UPDATES) { - @Override - public void onStreamUpdate(AlpacaStreamMessageType streamMessageType, AlpacaStreamMessage streamMessage) { - switch (streamMessageType) { - case ACCOUNT_UPDATES: - AccountUpdateMessage accountUpdateMessage = (AccountUpdateMessage) streamMessage; - System.out.println("\nReceived Account Update: \n\t" + - accountUpdateMessage.toString().replace(",", ",\n\t")); - break; - case TRADE_UPDATES: - TradeUpdateMessage tradeUpdateMessage = (TradeUpdateMessage) streamMessage; - System.out.println("\nReceived Order Update: \n\t" + - tradeUpdateMessage.toString().replace(",", ",\n\t")); - break; - } - } - }); - - alpacaAPI.addMarketDataStreamListener( - new MarketDataStreamListenerAdapter("AAPL", QUOTES, TRADES, AGGREGATE_MINUTE) { - @Override - public void onStreamUpdate(MarketDataStreamMessageType streamMessageType, - MarketDataStreamMessage streamMessage) { - switch (streamMessageType) { - case QUOTES: - QuoteMessage quoteMessage = (QuoteMessage) streamMessage; - System.out.println("\nQuote Update: \n\t" + - quoteMessage.toString().replace(",", ",\n\t")); - break; - case TRADES: - TradeMessage tradeMessage = (TradeMessage) streamMessage; - System.out.println("\nTrade Update: \n\t" + - tradeMessage.toString().replace(",", ",\n\t")); - break; - case AGGREGATE_MINUTE: - AggregateMinuteMessage aggregateMinuteMessage = (AggregateMinuteMessage) streamMessage; - System.out.println("\nAggregate Minute Update: \n\t" + - aggregateMinuteMessage.toString().replace(",", ",\n\t")); - break; - } - } - }); - - // Get Account Information - try { - Account alpacaAccount = alpacaAPI.getAccount(); - - System.out.println("\n\nAccount Information:"); - System.out.println("\t" + alpacaAccount.toString().replace(",", ",\n\t")); - } catch (AlpacaAPIRequestException e) { - e.printStackTrace(); - } - - // Request an Order - try { - Order aaplLimitOrder = alpacaAPI.requestNewLimitOrder("AAPL", 1, OrderSide.BUY, OrderTimeInForce.DAY, - 201.30, true); - - System.out.println("\n\nNew AAPL Order:"); - System.out.println("\t" + aaplLimitOrder.toString().replace(",", ",\n\t")); - } catch (AlpacaAPIRequestException e) { - e.printStackTrace(); - } - - // Create watchlist - try { - Watchlist dayTradeWatchlist = alpacaAPI.createWatchlist("Day Trade", "AAPL"); - - System.out.println("\n\nDay Trade Watchlist:"); - System.out.println("\t" + dayTradeWatchlist.toString().replace(",", ",\n\t")); - } catch (AlpacaAPIRequestException e) { - e.printStackTrace(); - } - - // Get bars - try { - ZonedDateTime start = ZonedDateTime.of(2019, 11, 18, 0, 0, 0, 0, ZoneId.of("America/New_York")); - ZonedDateTime end = ZonedDateTime.of(2019, 11, 22, 23, 59, 0, 0, ZoneId.of("America/New_York")); - - Map> bars = alpacaAPI.getBars(BarsTimeFrame.ONE_DAY, "AAPL", null, start, end, - null, null); - - System.out.println("\n\nBars response:"); - for (Bar bar : bars.get("AAPL")) { - System.out.println("\t=========="); - System.out.println("\tUnix Time " + ZonedDateTime.ofInstant(Instant.ofEpochSecond(bar.getT()), - ZoneOffset.UTC)); - System.out.println("\tOpen: $" + bar.getO()); - System.out.println("\tHigh: $" + bar.getH()); - System.out.println("\tLow: $" + bar.getL()); - System.out.println("\tClose: $" + bar.getC()); - System.out.println("\tVolume: " + bar.getV()); - } - } catch (AlpacaAPIRequestException e) { - e.printStackTrace(); - } - - try { - Thread.sleep(5000); - } catch (InterruptedException e) { - e.printStackTrace(); - } - } -} diff --git a/src/main/java/net/jacobpeterson/polygon/PolygonExample.java b/src/main/java/net/jacobpeterson/polygon/PolygonExample.java deleted file mode 100644 index 8b3f1543..00000000 --- a/src/main/java/net/jacobpeterson/polygon/PolygonExample.java +++ /dev/null @@ -1,74 +0,0 @@ -package net.jacobpeterson.polygon; - -import net.jacobpeterson.domain.polygon.aggregates.Aggregate; -import net.jacobpeterson.domain.polygon.aggregates.AggregatesResponse; -import net.jacobpeterson.domain.polygon.stocksplits.StockSplit; -import net.jacobpeterson.domain.polygon.stocksplits.StockSplitsResponse; -import net.jacobpeterson.domain.polygon.websocket.PolygonStreamMessage; -import net.jacobpeterson.polygon.enums.Timespan; -import net.jacobpeterson.polygon.rest.exception.PolygonAPIRequestException; -import net.jacobpeterson.polygon.websocket.listener.PolygonStreamListenerAdapter; -import net.jacobpeterson.polygon.websocket.message.PolygonStreamMessageType; - -import java.time.LocalDate; - -/** - * The Class Example. - */ -public class PolygonExample { - - /** - * The main method. - * - * @param args the arguments - */ - public static void main(String[] args) { - // This will use the key_id in the alpaca.properties file by default - PolygonAPI polygonAPI = new PolygonAPI(); - - String aaplTicker = "AAPL"; - - // Add a Polygon stream listener to listen to "T.AAPL", "Q.AAPL", "A.AAPL", "AM.AAPL", and status messages - polygonAPI.addPolygonStreamListener(new PolygonStreamListenerAdapter(aaplTicker, - PolygonStreamMessageType.values()) { - @Override - public void onStreamUpdate(PolygonStreamMessageType streamMessageType, PolygonStreamMessage streamMessage) { - System.out.println("===> streamUpdate " + streamMessageType + " " + streamMessage); - } - }); - - // Sleep the current thread for 2 seconds so we can see some trade/quote/aggregates updates on the stream! - try { - Thread.sleep(2000); - } catch (InterruptedException e) { - e.printStackTrace(); - } - - try { - StockSplitsResponse stockSplitsResponse = polygonAPI.getStockSplits(aaplTicker); - - System.out.println(aaplTicker + " Stock Split Response:"); - System.out.println("\tStatus: " + stockSplitsResponse.getStatus()); - System.out.println("\tCount: " + stockSplitsResponse.getCount()); - for (StockSplit stockSplit : stockSplitsResponse.getResults()) { - System.out.println("\t" + stockSplit.toString().replace(",", ",\n\t")); - } - } catch (PolygonAPIRequestException e) { - e.printStackTrace(); - } - - try { - AggregatesResponse aggregatesResponse = polygonAPI.getAggregates(aaplTicker, 1, Timespan.DAY, - LocalDate.of(2019, 11, 18), LocalDate.of(2019, 11, 25), false); - - System.out.println("Aggregate Response:"); - System.out.println("\tStatus: " + aggregatesResponse.getStatus()); - System.out.println("\tCount: " + aggregatesResponse.getResultsCount()); - for (Aggregate aggregate : aggregatesResponse.getResults()) { - System.out.println("\t" + aggregate.toString().replace(",", ",\n\t")); - } - } catch (PolygonAPIRequestException e) { - e.printStackTrace(); - } - } -} From 0e3d2f07eff7b781e324c73001d5c78d9bc14848 Mon Sep 17 00:00:00 2001 From: Jacob Peterson Date: Sun, 25 Oct 2020 11:50:27 -0600 Subject: [PATCH 044/271] Add MarketDataWebsocket example to README --- README.md | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/README.md b/README.md index 167cf497..55c89358 100644 --- a/README.md +++ b/README.md @@ -125,6 +125,31 @@ alpacaAPI.addAlpacaStreamListener(new AlpacaStreamListenerAdapter( } }); +alpacaAPI.addMarketDataStreamListener( + new MarketDataStreamListenerAdapter("AAPL", MarketDataStreamMessageType.values()) { + @Override + public void onStreamUpdate(MarketDataStreamMessageType streamMessageType, + MarketDataStreamMessage streamMessage) { + switch (streamMessageType) { + case QUOTES: + QuoteMessage quoteMessage = (QuoteMessage) streamMessage; + System.out.println("\nQuote Update: \n\t" + + quoteMessage.toString().replace(",", ",\n\t")); + break; + case TRADES: + TradeMessage tradeMessage = (TradeMessage) streamMessage; + System.out.println("\nTrade Update: \n\t" + + tradeMessage.toString().replace(",", ",\n\t")); + break; + case AGGREGATE_MINUTE: + AggregateMinuteMessage aggregateMinuteMessage = (AggregateMinuteMessage) streamMessage; + System.out.println("\nAggregate Minute Update: \n\t" + + aggregateMinuteMessage.toString().replace(",", ",\n\t")); + break; + } + } + }); + // Get Account Information try { Account alpacaAccount = alpacaAPI.getAccount(); From edf2d828d299b49cb387274f8694783be1299f68 Mon Sep 17 00:00:00 2001 From: Jacob Peterson Date: Sun, 25 Oct 2020 11:55:06 -0600 Subject: [PATCH 045/271] Format and add ArrayList copy constructor --- .../broker/client/AlpacaWebsocketClient.java | 7 +++---- .../client/MarketDataWebsocketClient.java | 13 +++++-------- 2 files changed, 8 insertions(+), 12 deletions(-) diff --git a/src/main/java/net/jacobpeterson/alpaca/websocket/broker/client/AlpacaWebsocketClient.java b/src/main/java/net/jacobpeterson/alpaca/websocket/broker/client/AlpacaWebsocketClient.java index e8fcc8c9..5af9fa52 100644 --- a/src/main/java/net/jacobpeterson/alpaca/websocket/broker/client/AlpacaWebsocketClient.java +++ b/src/main/java/net/jacobpeterson/alpaca/websocket/broker/client/AlpacaWebsocketClient.java @@ -287,10 +287,9 @@ public Set getRegisteredMessageTypes() { Set alpacaStreamMessageTypes = alpacaStreamListener.getStreamMessageTypes(); // if its empty, assume they want everything - Set streamMessageTypesToAdd = - alpacaStreamMessageTypes == null ? new HashSet<>() : - alpacaStreamMessageTypes.stream().filter(AlpacaStreamMessageType::isAPISubscribable) - .collect(Collectors.toSet()); + Set streamMessageTypesToAdd = alpacaStreamMessageTypes == null ? new HashSet<>() : + alpacaStreamMessageTypes.stream().filter(AlpacaStreamMessageType::isAPISubscribable) + .collect(Collectors.toSet()); registeredStreamMessageTypes.addAll(streamMessageTypesToAdd); } diff --git a/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/client/MarketDataWebsocketClient.java b/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/client/MarketDataWebsocketClient.java index e9663fef..47bdb770 100644 --- a/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/client/MarketDataWebsocketClient.java +++ b/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/client/MarketDataWebsocketClient.java @@ -260,7 +260,7 @@ public void sendStreamMessageToListeners(StreamMessageType streamMessageType, St MarketDataStreamMessageType marketDataStreamMessageType = (MarketDataStreamMessageType) streamMessageType; if (streamMessage instanceof MarketDataStreamStatusMessage) { - for (MarketDataStreamListener marketDataStreamListener : listeners) { + for (MarketDataStreamListener marketDataStreamListener : new ArrayList<>(listeners)) { if (marketDataStreamListener.getDataStreams() == null || marketDataStreamListener.getDataStreams().isEmpty() || marketDataStreamListener.getDataStreams().values().stream() @@ -272,7 +272,7 @@ public void sendStreamMessageToListeners(StreamMessageType streamMessageType, St } } else if (streamMessage instanceof MarketDataStreamDataMessage) { MarketDataStreamDataMessage marketDataStreamDataMessage = (MarketDataStreamDataMessage) streamMessage; - for (MarketDataStreamListener marketDataStreamListener : listeners) { + for (MarketDataStreamListener marketDataStreamListener : new ArrayList<>(listeners)) { if (marketDataStreamListener.getDataStreams().containsKey(marketDataStreamDataMessage.getSym())) { if (marketDataStreamListener.getDataStreams().get(marketDataStreamDataMessage.getSym()) .contains(marketDataStreamMessageType)) { @@ -342,12 +342,9 @@ private void submitStreamRequest() { public Map> getRegisteredMessageTypes() { Map> marketDataStreamMessageTypes = new HashMap<>(); - for (MarketDataStreamListener marketDataStreamListener : listeners) { - - marketDataStreamListener.getDataStreams().forEach((s, m) -> { - marketDataStreamMessageTypes.put(s, - m.stream().filter(MarketDataStreamMessageType::isAPISubscribable).collect(Collectors.toSet())); - }); + for (MarketDataStreamListener marketDataStreamListener : new ArrayList<>(listeners)) { + marketDataStreamListener.getDataStreams().forEach((s, m) -> marketDataStreamMessageTypes.put(s, + m.stream().filter(MarketDataStreamMessageType::isAPISubscribable).collect(Collectors.toSet()))); } From 5d428ecb1a9a6ef48fe57404404d18a1cec98ce2 Mon Sep 17 00:00:00 2001 From: Jacob Peterson Date: Sun, 25 Oct 2020 11:59:36 -0600 Subject: [PATCH 046/271] Replace raw types with wildcards --- .../abstracts/websocket/client/WebsocketClient.java | 4 ++-- .../alpaca/websocket/broker/client/AlpacaWebsocketClient.java | 4 ++-- .../marketdata/client/MarketDataWebsocketClient.java | 4 ++-- .../polygon/websocket/client/PolygonWebsocketClient.java | 4 ++-- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/main/java/net/jacobpeterson/abstracts/websocket/client/WebsocketClient.java b/src/main/java/net/jacobpeterson/abstracts/websocket/client/WebsocketClient.java index b773b372..82aca3a1 100644 --- a/src/main/java/net/jacobpeterson/abstracts/websocket/client/WebsocketClient.java +++ b/src/main/java/net/jacobpeterson/abstracts/websocket/client/WebsocketClient.java @@ -14,14 +14,14 @@ public interface WebsocketClient { * * @param streamListener the stream listener */ - void addListener(StreamListener streamListener); + void addListener(StreamListener streamListener); /** * Remove listener. * * @param streamListener the stream listener */ - void removeListener(StreamListener streamListener); + void removeListener(StreamListener streamListener); /** * Connect. diff --git a/src/main/java/net/jacobpeterson/alpaca/websocket/broker/client/AlpacaWebsocketClient.java b/src/main/java/net/jacobpeterson/alpaca/websocket/broker/client/AlpacaWebsocketClient.java index 5af9fa52..38e6b83b 100644 --- a/src/main/java/net/jacobpeterson/alpaca/websocket/broker/client/AlpacaWebsocketClient.java +++ b/src/main/java/net/jacobpeterson/alpaca/websocket/broker/client/AlpacaWebsocketClient.java @@ -76,7 +76,7 @@ public AlpacaWebsocketClient(String keyId, String secret, String baseAPIURL) { } @Override - public void addListener(StreamListener streamListener) { + public void addListener(StreamListener streamListener) { Preconditions.checkState(streamListener instanceof AlpacaStreamListener); if (listeners.isEmpty()) { @@ -89,7 +89,7 @@ public void addListener(StreamListener streamListener) { } @Override - public void removeListener(StreamListener streamListener) { + public void removeListener(StreamListener streamListener) { Preconditions.checkState(streamListener instanceof AlpacaStreamListener); listeners.remove(streamListener); diff --git a/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/client/MarketDataWebsocketClient.java b/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/client/MarketDataWebsocketClient.java index 47bdb770..1d466e98 100644 --- a/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/client/MarketDataWebsocketClient.java +++ b/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/client/MarketDataWebsocketClient.java @@ -90,7 +90,7 @@ public MarketDataWebsocketClient(String keyId, String secret, String baseDataUrl } @Override - public void addListener(StreamListener streamListener) { + public void addListener(StreamListener streamListener) { Preconditions.checkState(streamListener instanceof MarketDataStreamListener); if (listeners.isEmpty()) { @@ -103,7 +103,7 @@ public void addListener(StreamListener streamListener) { } @Override - public void removeListener(StreamListener streamListener) { + public void removeListener(StreamListener streamListener) { Preconditions.checkState(streamListener instanceof MarketDataStreamListener); listeners.remove(streamListener); diff --git a/src/main/java/net/jacobpeterson/polygon/websocket/client/PolygonWebsocketClient.java b/src/main/java/net/jacobpeterson/polygon/websocket/client/PolygonWebsocketClient.java index 5c10338d..bcc4adfe 100644 --- a/src/main/java/net/jacobpeterson/polygon/websocket/client/PolygonWebsocketClient.java +++ b/src/main/java/net/jacobpeterson/polygon/websocket/client/PolygonWebsocketClient.java @@ -78,7 +78,7 @@ public PolygonWebsocketClient(String keyId, String websocketURL) { } @Override - public void addListener(StreamListener listener) { + public void addListener(StreamListener listener) { Preconditions.checkState(listener instanceof PolygonStreamListener); if (listeners.isEmpty()) { @@ -91,7 +91,7 @@ public void addListener(StreamListener listener) { } @Override - public void removeListener(StreamListener listener) { + public void removeListener(StreamListener listener) { Preconditions.checkState(listener instanceof PolygonStreamListener); listeners.remove(listener); From 8c1bc672bbcc24d43694144fc5edf0d5c21022ed Mon Sep 17 00:00:00 2001 From: Jacob Peterson Date: Sun, 25 Oct 2020 12:11:23 -0600 Subject: [PATCH 047/271] Add onError for WebsocketClientEndpoints --- .../AbstractWebsocketClientEndpoint.java | 19 +++++++++++++++---- .../client/AlpacaWebsocketClientEndpoint.java | 6 ++++++ .../MarketDataWebsocketClientEndpoint.java | 2 +- .../PolygonWebsocketClientEndpoint.java | 6 ++++++ 4 files changed, 28 insertions(+), 5 deletions(-) diff --git a/src/main/java/net/jacobpeterson/abstracts/websocket/client/AbstractWebsocketClientEndpoint.java b/src/main/java/net/jacobpeterson/abstracts/websocket/client/AbstractWebsocketClientEndpoint.java index 855809e7..3e6738e4 100644 --- a/src/main/java/net/jacobpeterson/abstracts/websocket/client/AbstractWebsocketClientEndpoint.java +++ b/src/main/java/net/jacobpeterson/abstracts/websocket/client/AbstractWebsocketClientEndpoint.java @@ -14,10 +14,12 @@ import java.util.concurrent.ExecutorService; /** - * The type Abstract websocket client endpoint. You must annotate a subclass with {@link javax.websocket.ClientEndpoint} - * and the appropriate websocket subprotocols because websocket annotations don't work with inheritance. The subclass - * must also contain separate methods with the following annotations: {@link javax.websocket.OnOpen}, {@link - * javax.websocket.OnClose}, and {@link javax.websocket.OnMessage}. + * The type Abstract websocket client endpoint. + *
+ * NOTES: You MUST annotate a subclass with {@link javax.websocket.ClientEndpoint} and the appropriate websocket + * subprotocols because websocket annotations don't work with inheritance. The subclass must also contain separate + * methods with the following annotations: {@link javax.websocket.OnOpen}, {@link javax.websocket.OnClose}, {@link + * javax.websocket.OnMessage}, and {@link javax.websocket.OnError}. */ public abstract class AbstractWebsocketClientEndpoint { @@ -126,6 +128,15 @@ protected void onMessage(String message) { executorService.execute(() -> websocketClient.handleWebsocketMessage(message)); } + /** + * On error. + * + * @param throwable the throwable + */ + protected void onError(Throwable throwable) { + LOGGER.error(throwable); + } + /** * Send a message. * diff --git a/src/main/java/net/jacobpeterson/alpaca/websocket/broker/client/AlpacaWebsocketClientEndpoint.java b/src/main/java/net/jacobpeterson/alpaca/websocket/broker/client/AlpacaWebsocketClientEndpoint.java index a9175f3d..1416503f 100644 --- a/src/main/java/net/jacobpeterson/alpaca/websocket/broker/client/AlpacaWebsocketClientEndpoint.java +++ b/src/main/java/net/jacobpeterson/alpaca/websocket/broker/client/AlpacaWebsocketClientEndpoint.java @@ -6,6 +6,7 @@ import javax.websocket.ClientEndpoint; import javax.websocket.CloseReason; import javax.websocket.OnClose; +import javax.websocket.OnError; import javax.websocket.OnMessage; import javax.websocket.OnOpen; import javax.websocket.Session; @@ -42,4 +43,9 @@ public void onCloseAnnotated(Session userSession, CloseReason reason) { public void onMessageAnnotated(byte[] message) { super.onMessage(new String(message, StandardCharsets.UTF_8)); } + + @OnError + public void onErrorAnnotated(Throwable throwable) { + super.onError(throwable); + } } diff --git a/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/client/MarketDataWebsocketClientEndpoint.java b/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/client/MarketDataWebsocketClientEndpoint.java index 80e8eedd..7ad69b49 100644 --- a/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/client/MarketDataWebsocketClientEndpoint.java +++ b/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/client/MarketDataWebsocketClientEndpoint.java @@ -45,6 +45,6 @@ public void onMessageAnnotated(String message) { @OnError public void onError(Throwable throwable) { - System.out.println(throwable.getLocalizedMessage()); + super.onError(throwable); } } diff --git a/src/main/java/net/jacobpeterson/polygon/websocket/client/PolygonWebsocketClientEndpoint.java b/src/main/java/net/jacobpeterson/polygon/websocket/client/PolygonWebsocketClientEndpoint.java index 5b26e7dc..ed214c10 100644 --- a/src/main/java/net/jacobpeterson/polygon/websocket/client/PolygonWebsocketClientEndpoint.java +++ b/src/main/java/net/jacobpeterson/polygon/websocket/client/PolygonWebsocketClientEndpoint.java @@ -6,6 +6,7 @@ import javax.websocket.ClientEndpoint; import javax.websocket.CloseReason; import javax.websocket.OnClose; +import javax.websocket.OnError; import javax.websocket.OnMessage; import javax.websocket.OnOpen; import javax.websocket.Session; @@ -41,4 +42,9 @@ public void onCloseAnnotated(Session userSession, CloseReason reason) { public void onMessageAnnotated(String message) { super.onMessage(message); } + + @OnError + public void onError(Throwable throwable) { + super.onError(throwable); + } } From d36bbf5b4cf6b1a7ae3af9c7a333c5f5e72ceab0 Mon Sep 17 00:00:00 2001 From: Jacob Peterson Date: Sun, 25 Oct 2020 17:05:33 -0600 Subject: [PATCH 048/271] Optimize MarketDataWebsocketClient --- .../market_data_stream_data_message.json | 5 +- .../aggregate/aggregate_minute_message.json | 5 - .../streaming/quote/quote_message.json | 5 - .../streaming/trade/trade_message.json | 5 - .../websocket/client/WebsocketClient.java | 17 +- .../net/jacobpeterson/alpaca/AlpacaAPI.java | 6 +- .../broker/listener/AlpacaStreamListener.java | 6 +- .../client/MarketDataWebsocketClient.java | 161 ++++++++++++------ .../listener/MarketDataStreamListener.java | 6 +- .../net/jacobpeterson/polygon/PolygonAPI.java | 2 +- .../listener/PolygonStreamListener.java | 2 +- 11 files changed, 128 insertions(+), 92 deletions(-) diff --git a/schema_json/alpaca/market_data/streaming/abstracts/market_data_stream_data_message.json b/schema_json/alpaca/market_data/streaming/abstracts/market_data_stream_data_message.json index ca467235..2e9632cd 100644 --- a/schema_json/alpaca/market_data/streaming/abstracts/market_data_stream_data_message.json +++ b/schema_json/alpaca/market_data/streaming/abstracts/market_data_stream_data_message.json @@ -9,9 +9,10 @@ "existingJavaType": "net.jacobpeterson.alpaca.websocket.marketdata.message.MarketDataStreamMessageType", "title": "The event type" }, - "sym": { + "T": { "existingJavaType": "java.lang.String", - "title": "The symbol" + "javaName": "ticker", + "title": "The ticker" } } } diff --git a/schema_json/alpaca/market_data/streaming/aggregate/aggregate_minute_message.json b/schema_json/alpaca/market_data/streaming/aggregate/aggregate_minute_message.json index 13aa62da..7180cfd6 100644 --- a/schema_json/alpaca/market_data/streaming/aggregate/aggregate_minute_message.json +++ b/schema_json/alpaca/market_data/streaming/aggregate/aggregate_minute_message.json @@ -5,11 +5,6 @@ }, "title": "See https://alpaca.markets/docs/api-documentation/api-v2/market-data/streaming/", "properties": { - "T": { - "existingJavaType": "java.lang.String", - "javaName": "ticker", - "title": "The ticker" - }, "v": { "existingJavaType": "java.lang.Long", "title": "Tick volume (shares)" diff --git a/schema_json/alpaca/market_data/streaming/quote/quote_message.json b/schema_json/alpaca/market_data/streaming/quote/quote_message.json index 109c93d8..a583ea19 100644 --- a/schema_json/alpaca/market_data/streaming/quote/quote_message.json +++ b/schema_json/alpaca/market_data/streaming/quote/quote_message.json @@ -5,11 +5,6 @@ }, "title": "See https://alpaca.markets/docs/api-documentation/api-v2/market-data/streaming/", "properties": { - "T": { - "existingJavaType": "java.lang.String", - "javaName": "ticker", - "title": "The ticker" - }, "x": { "existingJavaType": "java.lang.String", "javaName": "bx", diff --git a/schema_json/alpaca/market_data/streaming/trade/trade_message.json b/schema_json/alpaca/market_data/streaming/trade/trade_message.json index 6f18f611..9cb9742c 100644 --- a/schema_json/alpaca/market_data/streaming/trade/trade_message.json +++ b/schema_json/alpaca/market_data/streaming/trade/trade_message.json @@ -5,11 +5,6 @@ }, "title": "See https://alpaca.markets/docs/api-documentation/api-v2/market-data/streaming/", "properties": { - "T": { - "existingJavaType": "java.lang.String", - "javaName": "ticker", - "title": "The ticker" - }, "i": { "existingJavaType": "java.lang.Integer", "title": "Trade ID" diff --git a/src/main/java/net/jacobpeterson/abstracts/websocket/client/WebsocketClient.java b/src/main/java/net/jacobpeterson/abstracts/websocket/client/WebsocketClient.java index 82aca3a1..61974f29 100644 --- a/src/main/java/net/jacobpeterson/abstracts/websocket/client/WebsocketClient.java +++ b/src/main/java/net/jacobpeterson/abstracts/websocket/client/WebsocketClient.java @@ -10,36 +10,36 @@ public interface WebsocketClient { /** - * Add listener. + * Adds a listener. Note that this method is blocking. * * @param streamListener the stream listener */ void addListener(StreamListener streamListener); /** - * Remove listener. + * Remove listener. Note that this method is blocking. * * @param streamListener the stream listener */ void removeListener(StreamListener streamListener); /** - * Connect. + * Connects. Note that this method is blocking. */ void connect(); /** - * Disconnect. + * Disconnects. Note that this method is blocking. */ void disconnect(); /** - * Send authentication message. + * Send authentication message. Note that this method is blocking. */ void sendAuthenticationMessage(); /** - * Handle resubscribing. + * Handle resubscribing. Note that this method is blocking. */ void handleResubscribing(); @@ -59,17 +59,16 @@ public interface WebsocketClient { void sendStreamMessageToListeners(StreamMessageType streamMessageType, StreamMessage streamMessage); /** - * Is connected boolean. + * Returns true if the websocket is connected. * * @return the boolean */ boolean isConnected(); /** - * Is authenticated boolean. + * Returns true if the websocket is authenticated. * * @return the boolean */ boolean isAuthenticated(); - } diff --git a/src/main/java/net/jacobpeterson/alpaca/AlpacaAPI.java b/src/main/java/net/jacobpeterson/alpaca/AlpacaAPI.java index 45c3c64c..2ac02d3a 100644 --- a/src/main/java/net/jacobpeterson/alpaca/AlpacaAPI.java +++ b/src/main/java/net/jacobpeterson/alpaca/AlpacaAPI.java @@ -60,7 +60,7 @@ import java.util.stream.Collectors; /** - * The Class AlpacaAPI. + * The class AlpacaAPI. Note that most of these methods are blocking methods and this class in NOT thread-safe. */ public class AlpacaAPI { @@ -82,10 +82,10 @@ public class AlpacaAPI { /** The base data url. */ private final String baseDataUrl; - /** The alpaca web socket client. */ + /** The alpaca websocket client. */ private final AlpacaWebsocketClient alpacaWebSocketClient; - /** The data web socket client. */ + /** The market data websocket client. */ private final MarketDataWebsocketClient marketDataWebSocketClient; /** diff --git a/src/main/java/net/jacobpeterson/alpaca/websocket/broker/listener/AlpacaStreamListener.java b/src/main/java/net/jacobpeterson/alpaca/websocket/broker/listener/AlpacaStreamListener.java index 0c009096..752cbcaf 100644 --- a/src/main/java/net/jacobpeterson/alpaca/websocket/broker/listener/AlpacaStreamListener.java +++ b/src/main/java/net/jacobpeterson/alpaca/websocket/broker/listener/AlpacaStreamListener.java @@ -7,13 +7,13 @@ import java.util.Set; /** - * An asynchronous update interface for receiving notifications about Websocket information as the Websocket is - * constructed. + * An asynchronous update interface for receiving websocket messages for + * {@link net.jacobpeterson.alpaca.websocket.broker.client.AlpacaWebsocketClient}. */ public interface AlpacaStreamListener extends StreamListener { /** - * Gets stream message types. Null or empty to listen to all other stream messages. + * Gets the stream message types for this listener. Null or empty to listen to all other stream messages. * * @return the stream message types */ diff --git a/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/client/MarketDataWebsocketClient.java b/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/client/MarketDataWebsocketClient.java index 1d466e98..9636a889 100644 --- a/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/client/MarketDataWebsocketClient.java +++ b/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/client/MarketDataWebsocketClient.java @@ -3,7 +3,6 @@ import com.google.common.base.Preconditions; import com.google.gson.JsonArray; import com.google.gson.JsonElement; -import com.google.gson.JsonNull; import com.google.gson.JsonObject; import com.google.gson.JsonParseException; import com.google.gson.JsonPrimitive; @@ -31,14 +30,11 @@ import java.net.URISyntaxException; import java.util.ArrayList; import java.util.HashMap; +import java.util.HashSet; import java.util.List; -import java.util.Map; import java.util.Set; import java.util.stream.Collectors; -import static net.jacobpeterson.alpaca.websocket.marketdata.message.MarketDataStreamMessageType.AUTHORIZATION; -import static net.jacobpeterson.alpaca.websocket.marketdata.message.MarketDataStreamMessageType.LISTENING; - /** * The type Alpaca websocket client. */ @@ -56,6 +52,21 @@ public class MarketDataWebsocketClient implements WebsocketClient { /** The constant EVENT_TYPE_KEY. */ private static final String DATA_KEY = "data"; + /** The constant ALL_TICKERS. */ + private static final String ALL_TICKERS = "*"; + + /** The constant TRADES_STREAM_PREFIX. */ + private static final String TRADES_STREAM_PREFIX = + GsonUtil.GSON.toJson(MarketDataStreamMessageType.TRADES).replace("\"", "") + "."; + + /** The constant QUOTES_STREAM_PREFIX. */ + private static final String QUOTES_STREAM_PREFIX = + GsonUtil.GSON.toJson(MarketDataStreamMessageType.QUOTES).replace("\"", "") + "."; + + /** The constant AGGREGATE_MINUTE_STREAM_PREFIX. */ + private static final String AGGREGATE_MINUTE_STREAM_PREFIX = + GsonUtil.GSON.toJson(MarketDataStreamMessageType.AGGREGATE_MINUTE).replace("\"", "") + "."; + /** The key id. */ private final String keyId; @@ -173,13 +184,20 @@ public void handleResubscribing() { @Override public void handleWebsocketMessage(String message) { - //RECEIVED: {"stream":"authorization","data":{"action":"authenticate","status":"authorized"}} - //RECEIVED: {"stream":"listening","data":{"streams":["Q.AAPL"]}} - //RECEIVED: {"stream":"Q.AAPL","data":{"ev":"Q","T":"AAPL","x":17,"p":112.12,"s":1,"X":3,"P":112.98,"S":1, - // "c":[0],"t":1601645053026000000}} - JsonElement messageJsonElement = GsonUtil.JSON_PARSER.parse(message); + /* Format of authorization message: + * {"stream":"authorization","data":{"action":"authenticate","status":"authorized"}} + * + * Format of listened streams message: + * {"stream":"listening","data":{"streams":["Q.AAPL"]}} + * + * Format of stream data message: + * {"stream":"Q.AAPL","data":{"ev":"Q","T":"AAPL","x":17,"p":112.12,"s":1,"X":3,"P":112.98,"S":1, + * "c":[0],"t":1601645053026000000}} + */ + JsonElement messageJsonElement = GsonUtil.JSON_PARSER.parse(message); Preconditions.checkState(messageJsonElement instanceof JsonObject); + JsonObject messageJsonObject = messageJsonElement.getAsJsonObject(); if (messageJsonObject.has(STREAM_KEY)) { @@ -187,14 +205,18 @@ public void handleWebsocketMessage(String message) { if (streamJsonElement instanceof JsonPrimitive) { try { + String streamString = streamJsonElement.getAsString(); + MarketDataStreamMessageType marketDataStreamMessageType; - JsonElement eventMessageJsonElement = JsonNull.INSTANCE; - if (streamJsonElement.getAsString().contains(".")) { - eventMessageJsonElement = messageJsonObject.get(DATA_KEY); - JsonElement eventTypeJsonElement = eventMessageJsonElement.getAsJsonObject() - .get(EVENT_TYPE_KEY); - marketDataStreamMessageType = GsonUtil.GSON.fromJson(eventTypeJsonElement, + // Check if the "stream" is a data stream, that is, Q, T, or AM + if (streamString.startsWith(TRADES_STREAM_PREFIX) || + streamString.startsWith(QUOTES_STREAM_PREFIX) || + streamString.startsWith(AGGREGATE_MINUTE_STREAM_PREFIX)) { + + // Set the stream message type to the parsed EVENT_TYPE_KEY + marketDataStreamMessageType = GsonUtil.GSON.fromJson( + messageJsonObject.get(DATA_KEY).getAsJsonObject().get(EVENT_TYPE_KEY), MarketDataStreamMessageType.class); } else { marketDataStreamMessageType = GsonUtil.GSON.fromJson(streamJsonElement, @@ -219,25 +241,22 @@ public void handleWebsocketMessage(String message) { LOGGER.debug(listeningMessage); break; case TRADES: - TradeMessage tradeMessage = GsonUtil.GSON.fromJson(eventMessageJsonElement, + TradeMessage tradeMessage = GsonUtil.GSON.fromJson(messageJsonObject.get(DATA_KEY), TradeMessage.class); - tradeMessage.setSym(tradeMessage.getTicker()); sendStreamMessageToListeners(marketDataStreamMessageType, tradeMessage); LOGGER.debug(tradeMessage); break; case QUOTES: - QuoteMessage quoteMessage = GsonUtil.GSON.fromJson(eventMessageJsonElement, + QuoteMessage quoteMessage = GsonUtil.GSON.fromJson(messageJsonObject.get(DATA_KEY), QuoteMessage.class); - quoteMessage.setSym(quoteMessage.getTicker()); sendStreamMessageToListeners(marketDataStreamMessageType, quoteMessage); LOGGER.debug(quoteMessage); break; case AGGREGATE_MINUTE: AggregateMinuteMessage aggregateMinuteMessage = GsonUtil.GSON.fromJson( - eventMessageJsonElement, AggregateMinuteMessage.class); - aggregateMinuteMessage.setSym(aggregateMinuteMessage.getTicker()); + messageJsonObject.get(DATA_KEY), AggregateMinuteMessage.class); sendStreamMessageToListeners(marketDataStreamMessageType, aggregateMinuteMessage); LOGGER.debug(aggregateMinuteMessage); @@ -257,30 +276,47 @@ public void handleWebsocketMessage(String message) { @Override public void sendStreamMessageToListeners(StreamMessageType streamMessageType, StreamMessage streamMessage) { Preconditions.checkState(streamMessageType instanceof MarketDataStreamMessageType); - MarketDataStreamMessageType marketDataStreamMessageType = (MarketDataStreamMessageType) streamMessageType; + Preconditions.checkState(streamMessage instanceof MarketDataStreamMessage); - if (streamMessage instanceof MarketDataStreamStatusMessage) { - for (MarketDataStreamListener marketDataStreamListener : new ArrayList<>(listeners)) { - if (marketDataStreamListener.getDataStreams() == null || - marketDataStreamListener.getDataStreams().isEmpty() || - marketDataStreamListener.getDataStreams().values().stream() - .anyMatch(types -> types.contains(LISTENING) || - types.contains(AUTHORIZATION))) { - marketDataStreamListener.onStreamUpdate(marketDataStreamMessageType, - (MarketDataStreamMessage) streamMessage); + MarketDataStreamMessageType marketDataStreamMessageType = (MarketDataStreamMessageType) streamMessageType; + MarketDataStreamMessage marketDataStreamMessage = (MarketDataStreamMessage) streamMessage; + + for (MarketDataStreamListener streamListener : new ArrayList<>(listeners)) { + boolean sendToStreamListener = false; + + if (marketDataStreamMessage instanceof MarketDataStreamStatusMessage) { + if (streamListener.getDataStreams() != null && !streamListener.getDataStreams().isEmpty()) { + // Send the message if any message type registered in the listener for + // any ticker is a status message + sendToStreamListener = streamListener.getDataStreams().values().stream().anyMatch(types -> + types.contains(MarketDataStreamMessageType.LISTENING) || + types.contains(MarketDataStreamMessageType.AUTHORIZATION)); + } else { // If the data streams list is empty or null, send the message + sendToStreamListener = true; } - } - } else if (streamMessage instanceof MarketDataStreamDataMessage) { - MarketDataStreamDataMessage marketDataStreamDataMessage = (MarketDataStreamDataMessage) streamMessage; - for (MarketDataStreamListener marketDataStreamListener : new ArrayList<>(listeners)) { - if (marketDataStreamListener.getDataStreams().containsKey(marketDataStreamDataMessage.getSym())) { - if (marketDataStreamListener.getDataStreams().get(marketDataStreamDataMessage.getSym()) - .contains(marketDataStreamMessageType)) { - marketDataStreamListener.onStreamUpdate(marketDataStreamMessageType, - marketDataStreamDataMessage); + } else if (marketDataStreamMessage instanceof MarketDataStreamDataMessage) { + MarketDataStreamDataMessage marketDataStreamDataMessage = (MarketDataStreamDataMessage) + marketDataStreamMessage; + + if (streamListener.getDataStreams() != null && !streamListener.getDataStreams().isEmpty()) { + Set tickerMessageTypes = + streamListener.getDataStreams().getOrDefault(marketDataStreamDataMessage.getTicker(), null); + Set allTickerMessageTypes = + streamListener.getDataStreams().getOrDefault(ALL_TICKERS, null); + + if (tickerMessageTypes != null) { + sendToStreamListener = tickerMessageTypes.contains(marketDataStreamMessageType); + } else if (allTickerMessageTypes != null) { + sendToStreamListener = allTickerMessageTypes.contains(marketDataStreamMessageType); } + } else { // If the data streams list is empty or null, send the message + sendToStreamListener = true; } } + + if (sendToStreamListener) { + streamListener.onStreamUpdate(marketDataStreamMessageType, marketDataStreamMessage); + } } } @@ -310,13 +346,14 @@ private boolean isAuthorizationMessageSuccess(AuthorizationMessage authorization * Submit stream request. */ private void submitStreamRequest() { - // Stream request example: - // { - // "action": "listen", - // "data": { - // "streams": ["T.SPY", "Q.SPY", "AM.SPY"] - // } - // } + /* Stream request example: + * { + * "action": "listen", + * "data": { + * "streams": ["T.SPY", "Q.SPY", "AM.SPY"] + * } + * } + */ JsonObject streamRequestJsonObject = new JsonObject(); streamRequestJsonObject.addProperty("action", "listen"); @@ -335,19 +372,33 @@ private void submitStreamRequest() { } /** - * Gets the registered message types. + * Gets the registered message types. May contain duplicates. * * @return the registered message types */ - public Map> getRegisteredMessageTypes() { - Map> marketDataStreamMessageTypes = new HashMap<>(); + public HashMap> getRegisteredMessageTypes() { + HashMap> allMarketDataStreamMessageTypes = new HashMap<>(); - for (MarketDataStreamListener marketDataStreamListener : new ArrayList<>(listeners)) { - marketDataStreamListener.getDataStreams().forEach((s, m) -> marketDataStreamMessageTypes.put(s, - m.stream().filter(MarketDataStreamMessageType::isAPISubscribable).collect(Collectors.toSet()))); + for (MarketDataStreamListener streamListener : new ArrayList<>(listeners)) { + if (streamListener.getDataStreams() == null || streamListener.getDataStreams().isEmpty()) { + continue; // Skip over empty/null data stream listeners + } + streamListener.getDataStreams().forEach((dataStreamTicker, marketDataStreamMessageTypes) -> { + // Filter to only subscribable message types and use HashSet to prevent duplicates + HashSet subscribableMessageTypes = marketDataStreamMessageTypes.stream() + .filter(MarketDataStreamMessageType::isAPISubscribable) + .collect(Collectors.toCollection(HashSet::new)); + + // Add the subscribableMessageTypes if the ticker already exists, otherwise insert a new pair + if (allMarketDataStreamMessageTypes.containsKey(dataStreamTicker)) { + allMarketDataStreamMessageTypes.get(dataStreamTicker).addAll(subscribableMessageTypes); + } else { + allMarketDataStreamMessageTypes.put(dataStreamTicker, subscribableMessageTypes); + } + }); } - return marketDataStreamMessageTypes; + return allMarketDataStreamMessageTypes; } } diff --git a/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/listener/MarketDataStreamListener.java b/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/listener/MarketDataStreamListener.java index 4616521c..8bb0bc4c 100644 --- a/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/listener/MarketDataStreamListener.java +++ b/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/listener/MarketDataStreamListener.java @@ -8,13 +8,13 @@ import java.util.Set; /** - * An asynchronous update interface for receiving notifications about Websocket information as the Websocket is - * constructed. + * An asynchronous update interface for receiving websocket messages for + * {@link net.jacobpeterson.alpaca.websocket.marketdata.client.MarketDataWebsocketClient} */ public interface MarketDataStreamListener extends StreamListener { /** - * Gets data streams. Null or empty to listen to all stream messages. + * Gets the data streams for this listener. Null or empty to listen to all stream messages. * * @return the stream message types */ diff --git a/src/main/java/net/jacobpeterson/polygon/PolygonAPI.java b/src/main/java/net/jacobpeterson/polygon/PolygonAPI.java index 2e2a792f..4643326a 100644 --- a/src/main/java/net/jacobpeterson/polygon/PolygonAPI.java +++ b/src/main/java/net/jacobpeterson/polygon/PolygonAPI.java @@ -53,7 +53,7 @@ import java.util.StringJoiner; /** - * The Class PolygonAPI. + * The class PolygonAPI. Note that most of these methods are blocking methods and this class in NOT thread-safe. */ public class PolygonAPI { diff --git a/src/main/java/net/jacobpeterson/polygon/websocket/listener/PolygonStreamListener.java b/src/main/java/net/jacobpeterson/polygon/websocket/listener/PolygonStreamListener.java index 9547316e..1bca9c56 100644 --- a/src/main/java/net/jacobpeterson/polygon/websocket/listener/PolygonStreamListener.java +++ b/src/main/java/net/jacobpeterson/polygon/websocket/listener/PolygonStreamListener.java @@ -16,7 +16,7 @@ public interface PolygonStreamListener extends StreamListener { /** - * Gets the stock channels. Null or empty to listen to all other stream messages. + * Gets the stock channels for this listener. Null or empty to listen to all other stream messages. * * @return the stock channels */ From bbc8a6ea0e4621ca1ac1fb298aff22401ba6165e Mon Sep 17 00:00:00 2001 From: Jacob Peterson <14127217+Petersoj@users.noreply.github.com> Date: Sun, 25 Oct 2020 20:56:48 -0600 Subject: [PATCH 049/271] Modify README Maven Central Integrity section --- README.md | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index e7429141..86908a89 100644 --- a/README.md +++ b/README.md @@ -65,10 +65,11 @@ that you use in your trading algorithm projects is indeed legitimate, we need to with the Jar MD5 checksum from this source code: 1. Clone the repo somewhere: `git clone https://github.com/Petersoj/alpaca-java` 2. `cd alpaca-java` -3. Build the project: `./gradlew build` +3. Checkout/reset to the latest version: `git reset 5.4 --hard` +4. Build the project: `./gradlew build` 1. Note: if you build the project twice without a `clean` task in between (e.g. `./gradlew build; ./gradlew build`), the MD5 checksum will change. -4. Generate the MD5 checksum: `md5 build/libs/alpaca-java-5.4.jar` -5. Confirm that the checksum value in Maven Central is the same as in step 4: +5. Generate the MD5 checksum: `md5 build/libs/alpaca-java-5.4.jar` +6. Confirm that the checksum value in Maven Central is the same as in step 4: `curl https://repo1.maven.org/maven2/net/jacobpeterson/alpaca-java/5.4/alpaca-java-5.4.jar.md5` If the checksums are different, please [create a new issue](https://github.com/Petersoj/alpaca-java/issues/new)! From 852538619f89eac4c64ea74570ce83bd38894735 Mon Sep 17 00:00:00 2001 From: Jacob Peterson Date: Mon, 26 Oct 2020 12:44:02 -0600 Subject: [PATCH 050/271] Add exceptions to websocket signatures and fix websocket thread hanging --- .../AbstractAPIRequestException.java | 8 ++-- .../AbstractWebsocketClientEndpoint.java | 48 +++++++++++++++++-- .../websocket/client/WebsocketClient.java | 17 +++++-- .../exception/WebsocketException.java | 37 ++++++++++++++ .../net/jacobpeterson/alpaca/AlpacaAPI.java | 17 +++++-- .../broker/client/AlpacaWebsocketClient.java | 44 +++++++++-------- .../client/MarketDataWebsocketClient.java | 44 +++++++++-------- .../net/jacobpeterson/polygon/PolygonAPI.java | 9 +++- .../client/PolygonWebsocketClient.java | 45 +++++++++-------- 9 files changed, 187 insertions(+), 82 deletions(-) create mode 100644 src/main/java/net/jacobpeterson/abstracts/websocket/exception/WebsocketException.java diff --git a/src/main/java/net/jacobpeterson/abstracts/rest/exception/AbstractAPIRequestException.java b/src/main/java/net/jacobpeterson/abstracts/rest/exception/AbstractAPIRequestException.java index 11a373d5..34175106 100644 --- a/src/main/java/net/jacobpeterson/abstracts/rest/exception/AbstractAPIRequestException.java +++ b/src/main/java/net/jacobpeterson/abstracts/rest/exception/AbstractAPIRequestException.java @@ -34,10 +34,10 @@ public abstract class AbstractAPIRequestException extends Exception { /** The Request status text. */ protected String requestStatusText; - /** The Api response code. */ - protected int apiResponseCode = -1; + /** The API response code. */ + protected Integer apiResponseCode; - /** The Api response message. */ + /** The API response message. */ protected String apiResponseMessage; /** @@ -104,7 +104,7 @@ public String getMessage() { messageBuilder.append("Status Code = ").append(requestStatusCode); messageBuilder.append(", Status Text = \"").append(requestStatusText).append("\""); - if (apiResponseCode != -1) { + if (apiResponseCode != null) { messageBuilder.append(", API Response Code = ").append(apiResponseCode); } diff --git a/src/main/java/net/jacobpeterson/abstracts/websocket/client/AbstractWebsocketClientEndpoint.java b/src/main/java/net/jacobpeterson/abstracts/websocket/client/AbstractWebsocketClientEndpoint.java index 3e6738e4..f9ccdd5a 100644 --- a/src/main/java/net/jacobpeterson/abstracts/websocket/client/AbstractWebsocketClientEndpoint.java +++ b/src/main/java/net/jacobpeterson/abstracts/websocket/client/AbstractWebsocketClientEndpoint.java @@ -3,6 +3,7 @@ import net.jacobpeterson.util.concurrency.ExecutorTracer; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; +import org.eclipse.jetty.util.component.LifeCycle; import javax.websocket.CloseReason; import javax.websocket.ContainerProvider; @@ -32,11 +33,17 @@ public abstract class AbstractWebsocketClientEndpoint { /** The Endpoint uri. */ private final URI endpointURI; + /** The Message thread name. */ + private final String messageThreadName; + /** * The Executor service, which passes message handlers to a different thread, will prevent overflow of server * buffers (causing a disconnect) from not consuming data fast enough on the client end. */ - private final ExecutorService executorService; + private ExecutorService executorService; + + /** The automatically reconnect boolean. */ + private boolean automaticallyReconnect; /** The User session. */ private Session userSession; @@ -52,7 +59,8 @@ public AbstractWebsocketClientEndpoint(WebsocketClient websocketClient, URI endp String messageThreadName) { this.websocketClient = websocketClient; this.endpointURI = endpointURI; - this.executorService = ExecutorTracer.newSingleThreadExecutor(r -> new Thread(r, messageThreadName)); + this.messageThreadName = messageThreadName; + this.automaticallyReconnect = true; } /** @@ -62,10 +70,11 @@ public AbstractWebsocketClientEndpoint(WebsocketClient websocketClient, URI endp * @throws IOException Signals that an I/O exception has occurred. */ public void connect() throws DeploymentException, IOException { + executorService = ExecutorTracer.newSingleThreadExecutor(r -> new Thread(r, messageThreadName)); + WebSocketContainer container = ContainerProvider.getWebSocketContainer(); LOGGER.info("Connecting to {}", endpointURI); - container.connectToServer(this, endpointURI); } @@ -74,9 +83,20 @@ public void connect() throws DeploymentException, IOException { * * @throws IOException the io exception */ - public void disconnect() throws IOException { + public void disconnect() throws Exception { + automaticallyReconnect = false; + if (userSession != null) { userSession.close(); + + WebSocketContainer webSocketContainer = userSession.getContainer(); + if (webSocketContainer instanceof LifeCycle) { + ((LifeCycle) webSocketContainer).stop(); // Closes all websocket-related threads + } + } + + if (executorService != null) { + executorService.shutdown(); } } @@ -102,7 +122,7 @@ protected void onOpen(Session userSession) { protected void onClose(Session userSession, CloseReason reason) { LOGGER.debug("onClose {}", userSession); - if (!reason.getCloseCode().equals(CloseReason.CloseCodes.NORMAL_CLOSURE)) { + if (!reason.getCloseCode().equals(CloseReason.CloseCodes.NORMAL_CLOSURE) && automaticallyReconnect) { LOGGER.info("Reconnecting due to closure: {}", CloseReason.CloseCodes.getCloseCode(reason.getCloseCode().getCode())); @@ -155,4 +175,22 @@ public void sendMessage(String message) { public Session getUserSession() { return userSession; } + + /** + * Does automatically reconnect boolean. + * + * @return the boolean + */ + public boolean doesAutomaticallyReconnect() { + return automaticallyReconnect; + } + + /** + * Sets automatically reconnect. + * + * @param automaticallyReconnect the automatically reconnect + */ + public void setAutomaticallyReconnect(boolean automaticallyReconnect) { + this.automaticallyReconnect = automaticallyReconnect; + } } diff --git a/src/main/java/net/jacobpeterson/abstracts/websocket/client/WebsocketClient.java b/src/main/java/net/jacobpeterson/abstracts/websocket/client/WebsocketClient.java index 61974f29..a25ccb55 100644 --- a/src/main/java/net/jacobpeterson/abstracts/websocket/client/WebsocketClient.java +++ b/src/main/java/net/jacobpeterson/abstracts/websocket/client/WebsocketClient.java @@ -1,5 +1,6 @@ package net.jacobpeterson.abstracts.websocket.client; +import net.jacobpeterson.abstracts.websocket.exception.WebsocketException; import net.jacobpeterson.abstracts.websocket.listener.StreamListener; import net.jacobpeterson.abstracts.websocket.message.StreamMessage; import net.jacobpeterson.abstracts.websocket.message.StreamMessageType; @@ -13,25 +14,33 @@ public interface WebsocketClient { * Adds a listener. Note that this method is blocking. * * @param streamListener the stream listener + * + * @throws WebsocketException the WebsocketException */ - void addListener(StreamListener streamListener); + void addListener(StreamListener streamListener) throws WebsocketException; /** * Remove listener. Note that this method is blocking. * * @param streamListener the stream listener + * + * @throws WebsocketException the WebsocketException */ - void removeListener(StreamListener streamListener); + void removeListener(StreamListener streamListener) throws WebsocketException; /** * Connects. Note that this method is blocking. + * + * @throws Exception the Exception */ - void connect(); + void connect() throws Exception; /** * Disconnects. Note that this method is blocking. + * + * @throws Exception the Exception */ - void disconnect(); + void disconnect() throws Exception; /** * Send authentication message. Note that this method is blocking. diff --git a/src/main/java/net/jacobpeterson/abstracts/websocket/exception/WebsocketException.java b/src/main/java/net/jacobpeterson/abstracts/websocket/exception/WebsocketException.java new file mode 100644 index 00000000..e9581037 --- /dev/null +++ b/src/main/java/net/jacobpeterson/abstracts/websocket/exception/WebsocketException.java @@ -0,0 +1,37 @@ +package net.jacobpeterson.abstracts.websocket.exception; + +import java.io.IOException; + +/** + * The type WebsocketException. + */ +public class WebsocketException extends IOException { + + /** + * Instantiates a new WebsocketException. + * + * @param message the message + */ + public WebsocketException(String message) { + super(message); + } + + /** + * Instantiates a new WebsocketException. + * + * @param message the message + * @param cause the cause + */ + public WebsocketException(String message, Throwable cause) { + super(message, cause); + } + + /** + * Instantiates a new WebsocketException. + * + * @param exception the Exception + */ + public WebsocketException(Exception exception) { + super(exception.getMessage(), exception.getCause()); + } +} diff --git a/src/main/java/net/jacobpeterson/alpaca/AlpacaAPI.java b/src/main/java/net/jacobpeterson/alpaca/AlpacaAPI.java index 2ac02d3a..7f560784 100644 --- a/src/main/java/net/jacobpeterson/alpaca/AlpacaAPI.java +++ b/src/main/java/net/jacobpeterson/alpaca/AlpacaAPI.java @@ -7,6 +7,7 @@ import com.google.gson.JsonPrimitive; import com.google.gson.reflect.TypeToken; import com.mashape.unirest.http.HttpResponse; +import net.jacobpeterson.abstracts.websocket.exception.WebsocketException; import net.jacobpeterson.alpaca.enums.ActivityType; import net.jacobpeterson.alpaca.enums.AssetStatus; import net.jacobpeterson.alpaca.enums.BarsTimeFrame; @@ -1691,8 +1692,10 @@ public LastQuoteResponse getLastQuote(String symbol) throws AlpacaAPIRequestExce * Adds the alpaca stream listener. * * @param streamListener the stream listener + * + * @throws WebsocketException the WebsocketException */ - public void addAlpacaStreamListener(AlpacaStreamListener streamListener) { + public void addAlpacaStreamListener(AlpacaStreamListener streamListener) throws WebsocketException { alpacaWebSocketClient.addListener(streamListener); } @@ -1700,8 +1703,10 @@ public void addAlpacaStreamListener(AlpacaStreamListener streamListener) { * Removes the alpaca stream listener. * * @param streamListener the stream listener + * + * @throws WebsocketException the WebsocketException */ - public void removeAlpacaStreamListener(AlpacaStreamListener streamListener) { + public void removeAlpacaStreamListener(AlpacaStreamListener streamListener) throws WebsocketException { alpacaWebSocketClient.removeListener(streamListener); } @@ -1709,8 +1714,10 @@ public void removeAlpacaStreamListener(AlpacaStreamListener streamListener) { * Adds the alpaca stream listener. * * @param streamListener the stream listener + * + * @throws WebsocketException the WebsocketException */ - public void addMarketDataStreamListener(MarketDataStreamListener streamListener) { + public void addMarketDataStreamListener(MarketDataStreamListener streamListener) throws WebsocketException { marketDataWebSocketClient.addListener(streamListener); } @@ -1718,8 +1725,10 @@ public void addMarketDataStreamListener(MarketDataStreamListener streamListener) * Removes the alpaca stream listener. * * @param streamListener the stream listener + * + * @throws WebsocketException the WebsocketException */ - public void removeMarketDataStreamListener(MarketDataStreamListener streamListener) { + public void removeMarketDataStreamListener(MarketDataStreamListener streamListener) throws WebsocketException { marketDataWebSocketClient.removeListener(streamListener); } diff --git a/src/main/java/net/jacobpeterson/alpaca/websocket/broker/client/AlpacaWebsocketClient.java b/src/main/java/net/jacobpeterson/alpaca/websocket/broker/client/AlpacaWebsocketClient.java index 38e6b83b..61d5c555 100644 --- a/src/main/java/net/jacobpeterson/alpaca/websocket/broker/client/AlpacaWebsocketClient.java +++ b/src/main/java/net/jacobpeterson/alpaca/websocket/broker/client/AlpacaWebsocketClient.java @@ -7,6 +7,7 @@ import com.google.gson.JsonPrimitive; import com.google.gson.JsonSyntaxException; import net.jacobpeterson.abstracts.websocket.client.WebsocketClient; +import net.jacobpeterson.abstracts.websocket.exception.WebsocketException; import net.jacobpeterson.abstracts.websocket.listener.StreamListener; import net.jacobpeterson.abstracts.websocket.message.StreamMessage; import net.jacobpeterson.abstracts.websocket.message.StreamMessageType; @@ -76,11 +77,15 @@ public AlpacaWebsocketClient(String keyId, String secret, String baseAPIURL) { } @Override - public void addListener(StreamListener streamListener) { + public void addListener(StreamListener streamListener) throws WebsocketException { Preconditions.checkState(streamListener instanceof AlpacaStreamListener); if (listeners.isEmpty()) { - connect(); + try { + connect(); + } catch (IOException | URISyntaxException | DeploymentException exception) { + throw new WebsocketException(exception); + } } listeners.add((AlpacaStreamListener) streamListener); @@ -89,43 +94,40 @@ public void addListener(StreamListener streamListener) { } @Override - public void removeListener(StreamListener streamListener) { + public void removeListener(StreamListener streamListener) throws WebsocketException { Preconditions.checkState(streamListener instanceof AlpacaStreamListener); listeners.remove(streamListener); - submitStreamRequestUpdate(); - if (listeners.isEmpty()) { - disconnect(); + try { + disconnect(); + } catch (Exception exception) { + throw new WebsocketException(exception); + } + } else { + submitStreamRequestUpdate(); } } @Override - public void connect() { + public void connect() throws URISyntaxException, IOException, DeploymentException { LOGGER.info("Connecting..."); - try { - alpacaWebsocketClientEndpoint = new AlpacaWebsocketClientEndpoint(this, new URI(streamAPIURL)); - alpacaWebsocketClientEndpoint.connect(); + alpacaWebsocketClientEndpoint = new AlpacaWebsocketClientEndpoint(this, new URI(streamAPIURL)); + alpacaWebsocketClientEndpoint.setAutomaticallyReconnect(true); + alpacaWebsocketClientEndpoint.connect(); - LOGGER.info("Connected."); - } catch (URISyntaxException | DeploymentException | IOException e) { - LOGGER.throwing(e); - } + LOGGER.info("Connected."); } @Override - public void disconnect() { + public void disconnect() throws Exception { LOGGER.info("Disconnecting..."); - try { - alpacaWebsocketClientEndpoint.getUserSession().close(); + alpacaWebsocketClientEndpoint.disconnect(); - LOGGER.info("Disconnected."); - } catch (IOException e) { - LOGGER.throwing(e); - } + LOGGER.info("Disconnected."); } @Override diff --git a/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/client/MarketDataWebsocketClient.java b/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/client/MarketDataWebsocketClient.java index 9636a889..2ef1ed21 100644 --- a/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/client/MarketDataWebsocketClient.java +++ b/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/client/MarketDataWebsocketClient.java @@ -7,6 +7,7 @@ import com.google.gson.JsonParseException; import com.google.gson.JsonPrimitive; import net.jacobpeterson.abstracts.websocket.client.WebsocketClient; +import net.jacobpeterson.abstracts.websocket.exception.WebsocketException; import net.jacobpeterson.abstracts.websocket.listener.StreamListener; import net.jacobpeterson.abstracts.websocket.message.StreamMessage; import net.jacobpeterson.abstracts.websocket.message.StreamMessageType; @@ -101,11 +102,15 @@ public MarketDataWebsocketClient(String keyId, String secret, String baseDataUrl } @Override - public void addListener(StreamListener streamListener) { + public void addListener(StreamListener streamListener) throws WebsocketException { Preconditions.checkState(streamListener instanceof MarketDataStreamListener); if (listeners.isEmpty()) { - connect(); + try { + connect(); + } catch (IOException | URISyntaxException | DeploymentException exception) { + throw new WebsocketException(exception); + } } listeners.add((MarketDataStreamListener) streamListener); @@ -114,43 +119,40 @@ public void addListener(StreamListener streamListener) { } @Override - public void removeListener(StreamListener streamListener) { + public void removeListener(StreamListener streamListener) throws WebsocketException { Preconditions.checkState(streamListener instanceof MarketDataStreamListener); listeners.remove(streamListener); - submitStreamRequest(); - if (listeners.isEmpty()) { - disconnect(); + try { + disconnect(); + } catch (Exception exception) { + throw new WebsocketException(exception); + } + } else { + submitStreamRequest(); } } @Override - public void connect() { + public void connect() throws URISyntaxException, IOException, DeploymentException { LOGGER.info("Connecting..."); - try { - marketDataWebsocketClientEndpoint = new MarketDataWebsocketClientEndpoint(this, new URI(streamAPIURL)); - marketDataWebsocketClientEndpoint.connect(); + marketDataWebsocketClientEndpoint = new MarketDataWebsocketClientEndpoint(this, new URI(streamAPIURL)); + marketDataWebsocketClientEndpoint.setAutomaticallyReconnect(true); + marketDataWebsocketClientEndpoint.connect(); - LOGGER.info("Connected."); - } catch (URISyntaxException | DeploymentException | IOException e) { - LOGGER.throwing(e); - } + LOGGER.info("Connected."); } @Override - public void disconnect() { + public void disconnect() throws Exception { LOGGER.info("Disconnecting..."); - try { - marketDataWebsocketClientEndpoint.getUserSession().close(); + marketDataWebsocketClientEndpoint.disconnect(); - LOGGER.info("Disconnected."); - } catch (IOException e) { - LOGGER.throwing(e); - } + LOGGER.info("Disconnected."); } @Override diff --git a/src/main/java/net/jacobpeterson/polygon/PolygonAPI.java b/src/main/java/net/jacobpeterson/polygon/PolygonAPI.java index 4643326a..a06b8fad 100644 --- a/src/main/java/net/jacobpeterson/polygon/PolygonAPI.java +++ b/src/main/java/net/jacobpeterson/polygon/PolygonAPI.java @@ -3,6 +3,7 @@ import com.google.common.base.Preconditions; import com.google.gson.reflect.TypeToken; import com.mashape.unirest.http.HttpResponse; +import net.jacobpeterson.abstracts.websocket.exception.WebsocketException; import net.jacobpeterson.domain.polygon.aggregates.AggregatesResponse; import net.jacobpeterson.domain.polygon.conditionsmapping.ConditionsMapping; import net.jacobpeterson.domain.polygon.dailyopenclose.DailyOpenCloseResponse; @@ -955,8 +956,10 @@ public GroupedDailyResponse getGroupedDaily(String locale, Market market, LocalD * Adds the polygon stream listener. * * @param streamListener the stream listener + * + * @throws WebsocketException the WebsocketException */ - public void addPolygonStreamListener(PolygonStreamListener streamListener) { + public void addPolygonStreamListener(PolygonStreamListener streamListener) throws WebsocketException { polygonWebsocketClient.addListener(streamListener); } @@ -964,8 +967,10 @@ public void addPolygonStreamListener(PolygonStreamListener streamListener) { * Removes the polygon stream listener. * * @param streamListener the stream listener + * + * @throws WebsocketException the WebsocketException */ - public void removePolygonStreamListener(PolygonStreamListener streamListener) { + public void removePolygonStreamListener(PolygonStreamListener streamListener) throws WebsocketException { polygonWebsocketClient.removeListener(streamListener); } diff --git a/src/main/java/net/jacobpeterson/polygon/websocket/client/PolygonWebsocketClient.java b/src/main/java/net/jacobpeterson/polygon/websocket/client/PolygonWebsocketClient.java index bcc4adfe..92e6765b 100644 --- a/src/main/java/net/jacobpeterson/polygon/websocket/client/PolygonWebsocketClient.java +++ b/src/main/java/net/jacobpeterson/polygon/websocket/client/PolygonWebsocketClient.java @@ -7,6 +7,7 @@ import com.google.gson.JsonPrimitive; import com.google.gson.JsonSyntaxException; import net.jacobpeterson.abstracts.websocket.client.WebsocketClient; +import net.jacobpeterson.abstracts.websocket.exception.WebsocketException; import net.jacobpeterson.abstracts.websocket.listener.StreamListener; import net.jacobpeterson.abstracts.websocket.message.StreamMessage; import net.jacobpeterson.abstracts.websocket.message.StreamMessageType; @@ -78,11 +79,15 @@ public PolygonWebsocketClient(String keyId, String websocketURL) { } @Override - public void addListener(StreamListener listener) { + public void addListener(StreamListener listener) throws WebsocketException { Preconditions.checkState(listener instanceof PolygonStreamListener); if (listeners.isEmpty()) { - connect(); + try { + connect(); + } catch (IOException | URISyntaxException | DeploymentException exception) { + throw new WebsocketException(exception); + } } listeners.add((PolygonStreamListener) listener); @@ -91,42 +96,40 @@ public void addListener(StreamListener listener) { } @Override - public void removeListener(StreamListener listener) { + public void removeListener(StreamListener listener) throws WebsocketException { Preconditions.checkState(listener instanceof PolygonStreamListener); listeners.remove(listener); - submitStreamRequest(PolygonStreamAction.UNSUBSCRIBE, (PolygonStreamListener) listener); - if (listeners.isEmpty()) { - disconnect(); + try { + disconnect(); + } catch (Exception exception) { + throw new WebsocketException(exception); + } + } else { + submitStreamRequest(PolygonStreamAction.UNSUBSCRIBE, (PolygonStreamListener) listener); } } @Override - public void connect() { + public void connect() throws URISyntaxException, IOException, DeploymentException { LOGGER.info("Connecting..."); - try { - polygonWebsocketClientEndpoint = new PolygonWebsocketClientEndpoint(this, new URI(websocketURL)); - polygonWebsocketClientEndpoint.connect(); + polygonWebsocketClientEndpoint = new PolygonWebsocketClientEndpoint(this, new URI(websocketURL)); + polygonWebsocketClientEndpoint.setAutomaticallyReconnect(true); + polygonWebsocketClientEndpoint.connect(); - LOGGER.info("Connected."); - } catch (URISyntaxException | DeploymentException | IOException e) { - LOGGER.throwing(e); - } + LOGGER.info("Connected."); } @Override - public void disconnect() { + public void disconnect() throws Exception { LOGGER.info("Disconnecting..."); - try { - polygonWebsocketClientEndpoint.disconnect(); - LOGGER.info("Disconnected."); - } catch (IOException e) { - LOGGER.throwing(e); - } + polygonWebsocketClientEndpoint.disconnect(); + + LOGGER.info("Disconnected."); } @Override From 89cafaf5a6ab1d6f67db48d3baa5b599f92b8dee Mon Sep 17 00:00:00 2001 From: Jacob Peterson Date: Mon, 26 Oct 2020 12:48:42 -0600 Subject: [PATCH 051/271] Update README --- README.md | 109 +++++++++++++++++++++++++++--------------------------- 1 file changed, 54 insertions(+), 55 deletions(-) diff --git a/README.md b/README.md index 55c89358..a6a6e41b 100644 --- a/README.md +++ b/README.md @@ -105,50 +105,59 @@ This example uses the `AlpacaAPI` class to subscribe to the Account and Trade Up AlpacaAPI alpacaAPI = new AlpacaAPI(); // Register explicitly for ACCOUNT_UPDATES and ORDER_UPDATES Messages via stream listener -alpacaAPI.addAlpacaStreamListener(new AlpacaStreamListenerAdapter( - AlpacaStreamMessageType.ACCOUNT_UPDATES, - AlpacaStreamMessageType.TRADE_UPDATES) { - @Override - public void onStreamUpdate(AlpacaStreamMessageType streamMessageType, AlpacaStreamMessage streamMessage) { - switch (streamMessageType) { - case ACCOUNT_UPDATES: - AccountUpdateMessage accountUpdateMessage = (AccountUpdateMessage) streamMessage; - System.out.println("\nReceived Account Update: \n\t" + - accountUpdateMessage.toString().replace(",", ",\n\t")); - break; - case TRADE_UPDATES: - TradeUpdateMessage tradeUpdateMessage = (TradeUpdateMessage) streamMessage; - System.out.println("\nReceived Order Update: \n\t" + - tradeUpdateMessage.toString().replace(",", ",\n\t")); - break; - } - } -}); - -alpacaAPI.addMarketDataStreamListener( - new MarketDataStreamListenerAdapter("AAPL", MarketDataStreamMessageType.values()) { +try { + alpacaAPI.addAlpacaStreamListener(new AlpacaStreamListenerAdapter( + AlpacaStreamMessageType.ACCOUNT_UPDATES, + AlpacaStreamMessageType.TRADE_UPDATES) { @Override - public void onStreamUpdate(MarketDataStreamMessageType streamMessageType, - MarketDataStreamMessage streamMessage) { + public void onStreamUpdate(AlpacaStreamMessageType streamMessageType, AlpacaStreamMessage streamMessage) { switch (streamMessageType) { - case QUOTES: - QuoteMessage quoteMessage = (QuoteMessage) streamMessage; - System.out.println("\nQuote Update: \n\t" + - quoteMessage.toString().replace(",", ",\n\t")); - break; - case TRADES: - TradeMessage tradeMessage = (TradeMessage) streamMessage; - System.out.println("\nTrade Update: \n\t" + - tradeMessage.toString().replace(",", ",\n\t")); + case ACCOUNT_UPDATES: + AccountUpdateMessage accountUpdateMessage = (AccountUpdateMessage) streamMessage; + System.out.println("\nReceived Account Update: \n\t" + + accountUpdateMessage.toString().replace(",", ",\n\t")); break; - case AGGREGATE_MINUTE: - AggregateMinuteMessage aggregateMinuteMessage = (AggregateMinuteMessage) streamMessage; - System.out.println("\nAggregate Minute Update: \n\t" + - aggregateMinuteMessage.toString().replace(",", ",\n\t")); + case TRADE_UPDATES: + TradeUpdateMessage tradeUpdateMessage = (TradeUpdateMessage) streamMessage; + System.out.println("\nReceived Order Update: \n\t" + + tradeUpdateMessage.toString().replace(",", ",\n\t")); break; } } }); +} catch (WebsocketException e) { + e.printStackTrace(); +} + +// Add an Alpaca Market data stream listener to listen to "T.AAPL", "Q.AAPL", and "AM.AAPL" messages +try { + alpacaAPI.addMarketDataStreamListener( + new MarketDataStreamListenerAdapter("AAPL", MarketDataStreamMessageType.values()) { + @Override + public void onStreamUpdate(MarketDataStreamMessageType streamMessageType, + MarketDataStreamMessage streamMessage) { + switch (streamMessageType) { + case QUOTES: + QuoteMessage quoteMessage = (QuoteMessage) streamMessage; + System.out.println("\nQuote Update: \n\t" + + quoteMessage.toString().replace(",", ",\n\t")); + break; + case TRADES: + TradeMessage tradeMessage = (TradeMessage) streamMessage; + System.out.println("\nTrade Update: \n\t" + + tradeMessage.toString().replace(",", ",\n\t")); + break; + case AGGREGATE_MINUTE: + AggregateMinuteMessage aggregateMinuteMessage = (AggregateMinuteMessage) streamMessage; + System.out.println("\nAggregate Minute Update: \n\t" + + aggregateMinuteMessage.toString().replace(",", ",\n\t")); + break; + } + } + }); +} catch (WebsocketException e) { + e.printStackTrace(); +} // Get Account Information try { @@ -203,13 +212,6 @@ try { } catch (AlpacaAPIRequestException e) { e.printStackTrace(); } - -// Keep the Alpaca websocket stream open for 5 seconds -try { - Thread.sleep(5000); -} catch (InterruptedException e) { - e.printStackTrace(); -} ``` This code will output the following: @@ -360,18 +362,15 @@ PolygonAPI polygonAPI = new PolygonAPI(); String aaplTicker = "AAPL"; // Add a Polygon stream listener to listen to "T.AAPL", "Q.AAPL", "A.AAPL", "AM.AAPL", and status messages -polygonAPI.addPolygonStreamListener(new PolygonStreamListenerAdapter(aaplTicker, - PolygonStreamMessageType.values()) { - @Override - public void onStreamUpdate(PolygonStreamMessageType streamMessageType, PolygonStreamMessage streamMessage) { - System.out.println("===> streamUpdate " + streamMessageType + " " + streamMessage); - } -}); - -// Sleep the current thread for 2 seconds so we can see some trade/quote/aggregates updates on the stream! try { - Thread.sleep(2000); -} catch (InterruptedException e) { + polygonAPI.addPolygonStreamListener(new PolygonStreamListenerAdapter(aaplTicker, + PolygonStreamMessageType.values()) { + @Override + public void onStreamUpdate(PolygonStreamMessageType streamMessageType, PolygonStreamMessage streamMessage) { + System.out.println("===> streamUpdate " + streamMessageType + " " + streamMessage); + } + }); +} catch (WebsocketException e) { e.printStackTrace(); } From 02857580ec28bb6032af8e0ef818ac30778f1add Mon Sep 17 00:00:00 2001 From: Jacob Peterson Date: Mon, 26 Oct 2020 12:56:21 -0600 Subject: [PATCH 052/271] Update requestNewMarketOrder signature --- .../net/jacobpeterson/alpaca/AlpacaAPI.java | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) diff --git a/src/main/java/net/jacobpeterson/alpaca/AlpacaAPI.java b/src/main/java/net/jacobpeterson/alpaca/AlpacaAPI.java index 7f560784..627a3257 100644 --- a/src/main/java/net/jacobpeterson/alpaca/AlpacaAPI.java +++ b/src/main/java/net/jacobpeterson/alpaca/AlpacaAPI.java @@ -499,22 +499,20 @@ public Order requestNewOrder(String symbol, Integer quantity, OrderSide side, Or * #requestNewOrder(String, Integer, OrderSide, OrderType, OrderTimeInForce, Double, Double, Double, Double, * Boolean, String, OrderClass, Double, Double, Double)} with {@link OrderType#MARKET}. * - * @param symbol symbol or asset ID to identify the asset to trade - * @param quantity number of shares to trade - * @param side buy or sell - * @param timeInForce day, gtc, opg, cls, ioc, fok. Please see Understand Orders for more info. - * @param extendedHours (default) false. If true, order will be eligible to execute in premarket/afterhours. Only - * works with type limit and time_in_force day. + * @param symbol symbol or asset ID to identify the asset to trade + * @param quantity number of shares to trade + * @param side buy or sell + * @param timeInForce day, gtc, opg, cls, ioc, fok. Please see Understand Orders for more info. * * @return the order * * @throws AlpacaAPIRequestException the alpaca api request exception * @see Order Types */ - public Order requestNewMarketOrder(String symbol, Integer quantity, OrderSide side, OrderTimeInForce timeInForce, - Boolean extendedHours) throws AlpacaAPIRequestException { - return requestNewOrder(symbol, quantity, side, OrderType.MARKET, timeInForce, null, null, null, null, - extendedHours, null, OrderClass.SIMPLE, null, null, null); + public Order requestNewMarketOrder(String symbol, Integer quantity, OrderSide side, OrderTimeInForce timeInForce) + throws AlpacaAPIRequestException { + return requestNewOrder(symbol, quantity, side, OrderType.MARKET, timeInForce, null, null, null, null, null, + null, OrderClass.SIMPLE, null, null, null); } /** From dab4aeed1dee15644b6bf717426975f2a0e4692e Mon Sep 17 00:00:00 2001 From: Jacob Peterson Date: Mon, 26 Oct 2020 12:59:30 -0600 Subject: [PATCH 053/271] Update to 6.0 --- README.md | 10 +++++----- build.gradle | 2 +- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index ac0e30fa..16c339e3 100644 --- a/README.md +++ b/README.md @@ -36,7 +36,7 @@ Add the following dependency to your build.gradle file: ``` dependencies { - compile "net.jacobpeterson:alpaca-java:5.4" + compile "net.jacobpeterson:alpaca-java:6.0" } ``` @@ -48,7 +48,7 @@ Add the following dependency to your pom.xml file: net.jacobpeterson alpaca-java - 5.4 + 6.0 compile ``` @@ -65,12 +65,12 @@ that you use in your trading algorithm projects is indeed legitimate, we need to with the Jar MD5 checksum from this source code: 1. Clone the repo somewhere: `git clone https://github.com/Petersoj/alpaca-java` 2. `cd alpaca-java` -3. Checkout/reset to the latest version: `git reset 5.4 --hard` +3. Checkout/reset to the latest version: `git reset 6.0 --hard` 4. Build the project: `./gradlew build` 1. Note: if you build the project twice without a `clean` task in between (e.g. `./gradlew build; ./gradlew build`), the MD5 checksum will change. -5. Generate the MD5 checksum: `md5 build/libs/alpaca-java-5.4.jar` +5. Generate the MD5 checksum: `md5 build/libs/alpaca-java-6.0.jar` 6. Confirm that the checksum value in Maven Central is the same as in step 4: -`curl https://repo1.maven.org/maven2/net/jacobpeterson/alpaca-java/5.4/alpaca-java-5.4.jar.md5` +`curl https://repo1.maven.org/maven2/net/jacobpeterson/alpaca-java/6.0/alpaca-java-6.0.jar.md5` If the checksums are different, please [create a new issue](https://github.com/Petersoj/alpaca-java/issues/new)! diff --git a/build.gradle b/build.gradle index c0acd348..e8398c88 100644 --- a/build.gradle +++ b/build.gradle @@ -26,7 +26,7 @@ tasks.getByName("generateJsonSchema2Pojo").enabled(false) // Disable default jso archivesBaseName = 'alpaca-java' group = 'net.jacobpeterson' -version = '5.4' +version = '6.0' repositories { mavenLocal() From f13864fd086ada04ebc17569ffbfb5696f16c50e Mon Sep 17 00:00:00 2001 From: Sean Miller Date: Thu, 29 Oct 2020 10:52:07 -0500 Subject: [PATCH 054/271] Added OAuthAlpaca Class Instantiation Haven't tested, and won't work with normal properties yet,will continue to fix --- .../jacobpeterson/alpaca/AlpacaOAuthAPI.java | 1738 +++++++++++++++++ .../alpaca/properties/AlpacaProperties.java | 7 + .../alpaca/rest/AlpacaRequest.java | 15 + .../broker/client/AlpacaWebsocketClient.java | 28 +- .../client/MarketDataWebsocketClient.java | 28 +- 5 files changed, 1812 insertions(+), 4 deletions(-) create mode 100644 src/main/java/net/jacobpeterson/alpaca/AlpacaOAuthAPI.java diff --git a/src/main/java/net/jacobpeterson/alpaca/AlpacaOAuthAPI.java b/src/main/java/net/jacobpeterson/alpaca/AlpacaOAuthAPI.java new file mode 100644 index 00000000..89bbc834 --- /dev/null +++ b/src/main/java/net/jacobpeterson/alpaca/AlpacaOAuthAPI.java @@ -0,0 +1,1738 @@ +package net.jacobpeterson.alpaca; + +import com.google.common.base.Preconditions; +import com.google.gson.JsonArray; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import com.google.gson.JsonPrimitive; +import com.google.gson.reflect.TypeToken; +import com.mashape.unirest.http.HttpResponse; +import net.jacobpeterson.abstracts.websocket.exception.WebsocketException; +import net.jacobpeterson.alpaca.enums.ActivityType; +import net.jacobpeterson.alpaca.enums.AssetStatus; +import net.jacobpeterson.alpaca.enums.BarsTimeFrame; +import net.jacobpeterson.alpaca.enums.Direction; +import net.jacobpeterson.alpaca.enums.OrderClass; +import net.jacobpeterson.alpaca.enums.OrderSide; +import net.jacobpeterson.alpaca.enums.OrderStatus; +import net.jacobpeterson.alpaca.enums.OrderTimeInForce; +import net.jacobpeterson.alpaca.enums.OrderType; +import net.jacobpeterson.alpaca.enums.PortfolioPeriodUnit; +import net.jacobpeterson.alpaca.enums.PortfolioTimeFrame; +import net.jacobpeterson.alpaca.properties.AlpacaProperties; +import net.jacobpeterson.alpaca.rest.AlpacaRequest; +import net.jacobpeterson.alpaca.rest.AlpacaRequestBuilder; +import net.jacobpeterson.alpaca.rest.exception.AlpacaAPIRequestException; +import net.jacobpeterson.alpaca.websocket.broker.client.AlpacaWebsocketClient; +import net.jacobpeterson.alpaca.websocket.broker.listener.AlpacaStreamListener; +import net.jacobpeterson.alpaca.websocket.marketdata.client.MarketDataWebsocketClient; +import net.jacobpeterson.alpaca.websocket.marketdata.listener.MarketDataStreamListener; +import net.jacobpeterson.domain.alpaca.account.Account; +import net.jacobpeterson.domain.alpaca.accountactivities.AccountActivity; +import net.jacobpeterson.domain.alpaca.accountactivities.NonTradeActivity; +import net.jacobpeterson.domain.alpaca.accountactivities.TradeActivity; +import net.jacobpeterson.domain.alpaca.accountconfiguration.AccountConfiguration; +import net.jacobpeterson.domain.alpaca.asset.Asset; +import net.jacobpeterson.domain.alpaca.calendar.Calendar; +import net.jacobpeterson.domain.alpaca.clock.Clock; +import net.jacobpeterson.domain.alpaca.marketdata.Bar; +import net.jacobpeterson.domain.alpaca.marketdata.LastQuoteResponse; +import net.jacobpeterson.domain.alpaca.marketdata.LastTradeResponse; +import net.jacobpeterson.domain.alpaca.order.CancelledOrder; +import net.jacobpeterson.domain.alpaca.order.Order; +import net.jacobpeterson.domain.alpaca.portfoliohistory.PortfolioHistory; +import net.jacobpeterson.domain.alpaca.position.ClosePositionOrder; +import net.jacobpeterson.domain.alpaca.position.Position; +import net.jacobpeterson.domain.alpaca.watchlist.Watchlist; +import net.jacobpeterson.util.format.FormatUtil; +import net.jacobpeterson.util.gson.GsonUtil; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +import java.io.InputStream; +import java.lang.reflect.Type; +import java.time.LocalDate; +import java.time.ZonedDateTime; +import java.time.format.DateTimeFormatter; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Map; +import java.util.StringJoiner; +import java.util.stream.Collectors; + +/** + * The class AlpacaOAuthAPI. Note that most of these methods are blocking methods and this class in NOT thread-safe. + */ +public class AlpacaOAuthAPI { + + /** The logger. */ + private static final Logger LOGGER = LogManager.getLogger(AlpacaOAuthAPI.class); + + /** The version. */ + private final String apiVersion; + + /** The auth token. */ + private final String auth_token; + + /** The base API url. */ + private final String baseAPIURL; + + /** The alpaca request. */ + private final AlpacaRequest alpacaRequest; + + /** The base data url. */ + private final String baseDataUrl; + + /** The alpaca websocket client. */ + private final AlpacaWebsocketClient alpacaWebSocketClient; + + /** The market data websocket client. */ + private final MarketDataWebsocketClient marketDataWebSocketClient; + + /** + * Instantiates a new Alpaca API using properties specified in alpaca.properties file (or relevant defaults) + */ + public AlpacaOAuthAPI() { + this(AlpacaProperties.API_VERSION_VALUE, AlpacaProperties.AUTH_TOKEN_VALUE, AlpacaProperties.BASE_API_URL_VALUE, + AlpacaProperties.BASE_DATA_URL_VALUE); + + LOGGER.debug(AlpacaProperties.staticToString()); + } + + /** + * Instantiates a new Alpaca API using the specified apiVersion + * + * @param apiVersion the api version + */ + public AlpacaOAuthAPI(String apiVersion) { + this(apiVersion, AlpacaProperties.AUTH_TOKEN_VALUE, + AlpacaProperties.BASE_API_URL_VALUE, AlpacaProperties.BASE_DATA_URL_VALUE); + } + + /** + * Instantiates a new Alpaca API using the specified apiVersion, keyId, and secret. + * + * @param token the auth token + */ + public AlpacaOAuthAPI(String apiVersion, String token) { + this(apiVersion, token, AlpacaProperties.BASE_API_URL_VALUE, + AlpacaProperties.BASE_DATA_URL_VALUE); + } + + /** + * Instantiates a new Alpaca API using the specified apiVersion, keyId, secret, and baseAPIURL. + * + * @param apiVersion the api version + * @param token the auth token + * @param baseAPIURL the api account url + */ + public AlpacaOAuthAPI(String apiVersion, String token, String baseAPIURL) { + this(apiVersion, token, baseAPIURL, AlpacaProperties.BASE_DATA_URL_VALUE); + } + + /** + * Instantiates a new Alpaca API using the specified keyId, secret, baseAPIURL, and baseDataUrl. + * + * @param apiVersion the api version + * @param token the auth token + * @param baseAPIURL the base api url + * @param baseDataUrl the base data url + */ + public AlpacaOAuthAPI(String apiVersion, String token, String baseAPIURL, String baseDataUrl) { + this.apiVersion = apiVersion; + this.auth_token = token; + this.baseAPIURL = baseAPIURL; + this.baseDataUrl = baseDataUrl; + + alpacaRequest = new AlpacaRequest(token); + alpacaWebSocketClient = new AlpacaWebsocketClient(token, baseAPIURL); + marketDataWebSocketClient = new MarketDataWebsocketClient(token, baseDataUrl); + + LOGGER.debug(this.toString()); + } + + /** + * Returns the account associated with the API key. + * + * @return the account + * + * @throws AlpacaAPIRequestException the alpaca API exception + * @see Get the Account + */ + public Account getAccount() throws AlpacaAPIRequestException { + AlpacaRequestBuilder urlBuilder = new AlpacaRequestBuilder(baseAPIURL, apiVersion, + AlpacaConstants.ACCOUNT_ENDPOINT); + + HttpResponse response = alpacaRequest.invokeGet(urlBuilder); + + if (response.getStatus() != 200) { + throw new AlpacaAPIRequestException(response); + } + + return alpacaRequest.getResponseObject(response, Account.class); + } + + /** + * Returns account activity entries for many or for a specific type of activity. + * + * @param date The date for which you want to see activities. + * @param until The response will contain only activities submitted before this date. (Cannot be used with + * date.) + * @param after The response will contain only activities submitted after this date. (Cannot be used with + * date.) + * @param direction asc or desc (default desc if unspecified.) + * @param pageSize The maximum number of entries to return in the response. (See the section on paging above.) + * @param pageToken The ID of the end of your current page of results. (See the section on paging above.) + * @param activityTypes the activity types (null for all activities) + * + * @return the account activities + * + * @throws AlpacaAPIRequestException the alpaca api exception + * @see Gets the Account + * Activities + */ + public ArrayList getAccountActivities(ZonedDateTime date, ZonedDateTime until, ZonedDateTime after, + Direction direction, Integer pageSize, String pageToken, ActivityType... activityTypes) + throws AlpacaAPIRequestException { + AlpacaRequestBuilder urlBuilder = new AlpacaRequestBuilder(baseAPIURL, AlpacaConstants.VERSION_2_ENDPOINT, + AlpacaConstants.ACCOUNT_ENDPOINT, + AlpacaConstants.ACTIVITIES_ENDPOINT); + + if (activityTypes != null) { // Check if we don't want all activity types + if (activityTypes.length == 1) { // Get one activity + urlBuilder.appendEndpoint(activityTypes[0].getAPIName()); + } else { // Get list of activities + urlBuilder.appendURLParameter("activity_types", Arrays.stream(activityTypes) + .map(ActivityType::getAPIName).collect(Collectors.joining(","))); // Makes comma-separated list + } + } + + if (date != null) { + urlBuilder.appendURLParameter(AlpacaConstants.DATE_PARAMETER, + date.format(DateTimeFormatter.ISO_OFFSET_DATE_TIME)); + } + + if (until != null) { + urlBuilder.appendURLParameter(AlpacaConstants.UNTIL_PARAMETER, + until.format(DateTimeFormatter.ISO_OFFSET_DATE_TIME)); + } + + if (after != null) { + urlBuilder.appendURLParameter(AlpacaConstants.AFTER_PARAMETER, + after.format(DateTimeFormatter.ISO_OFFSET_DATE_TIME)); + } + + if (direction != null) { + urlBuilder.appendURLParameter(AlpacaConstants.DIRECTION_PARAMETER, direction.getAPIName()); + } + + if (pageSize != null) { + urlBuilder.appendURLParameter(AlpacaConstants.PAGE_SIZE_PARAMETER, pageSize.toString()); + } + + if (pageToken != null) { + urlBuilder.appendURLParameter(AlpacaConstants.PAGE_TOKEN_PARAMETER, pageToken); + } + + HttpResponse response = alpacaRequest.invokeGet(urlBuilder); + + if (response.getStatus() != 200) { + throw new AlpacaAPIRequestException(response); + } + JsonElement responseJsonElement = alpacaRequest.getResponseJSON(response); + ArrayList accountActivities = new ArrayList<>(); + + if (responseJsonElement instanceof JsonArray) { + JsonArray responseJsonArray = (JsonArray) responseJsonElement; + + for (JsonElement arrayJsonElement : responseJsonArray) { // Loop through response array + if (arrayJsonElement instanceof JsonObject) { + JsonObject arrayJsonObject = (JsonObject) arrayJsonElement; + + if (GsonUtil.doesGsonPOJOMatch(TradeActivity.class, arrayJsonObject)) { + accountActivities.add(GsonUtil.GSON.fromJson(arrayJsonObject, TradeActivity.class)); + } else if (GsonUtil.doesGsonPOJOMatch(NonTradeActivity.class, arrayJsonObject)) { + accountActivities.add(GsonUtil.GSON.fromJson(arrayJsonObject, NonTradeActivity.class)); + } else { + LOGGER.warn("Received unknown JSON Object in response!"); + } + } else { + throw new IllegalStateException("All array elements must be objects!"); + } + } + return accountActivities; + } else { + throw new IllegalStateException("The response must be an array!"); + } + } + + /** + * Returns the current account configuration values. + * + * @return the account configurations + * + * @throws AlpacaAPIRequestException the alpaca api exception + * @see Account + * Configuration + */ + public AccountConfiguration getAccountConfiguration() throws AlpacaAPIRequestException { + AlpacaRequestBuilder urlBuilder = new AlpacaRequestBuilder(baseAPIURL, apiVersion, + AlpacaConstants.ACCOUNT_ENDPOINT, + AlpacaConstants.CONFIGURATIONS_ENDPOINT); + + HttpResponse response = alpacaRequest.invokeGet(urlBuilder); + + if (response.getStatus() != 200) { + throw new AlpacaAPIRequestException(response); + } + + return alpacaRequest.getResponseObject(response, AccountConfiguration.class); + } + + /** + * Sets account configuration. + * + * @param accountConfiguration the account configuration + * + * @return the updated account configuration + * + * @throws AlpacaAPIRequestException the alpaca api exception + * @see Account + * Configuration + */ + public AccountConfiguration setAccountConfiguration(AccountConfiguration accountConfiguration) + throws AlpacaAPIRequestException { + Preconditions.checkNotNull(accountConfiguration); + + AlpacaRequestBuilder urlBuilder = new AlpacaRequestBuilder(baseAPIURL, apiVersion, + AlpacaConstants.ACCOUNT_ENDPOINT, + AlpacaConstants.CONFIGURATIONS_ENDPOINT); + urlBuilder.setCustomBody(GsonUtil.GSON.toJson(accountConfiguration)); + + HttpResponse response = alpacaRequest.invokePatch(urlBuilder); + + if (response.getStatus() != 200) { + throw new AlpacaAPIRequestException(response); + } + + return alpacaRequest.getResponseObject(response, AccountConfiguration.class); + } + + /** + * Retrieves a list of orders for the account, filtered by the supplied query parameters. + * + * @param status Order status to be queried. open, closed or all. Defaults to open. + * @param limit The maximum number of orders in response. Defaults to 50 and max is 500. + * @param after The response will include only ones submitted after this timestamp (exclusive.) + * @param until The response will include only ones submitted until this timestamp (exclusive.) + * @param direction The chronological order of response based on the submission time. asc or desc. Defaults to + * desc. + * @param nested If true, the result will roll up multi-leg orders under the legs field of primary order. + * + * @return the orders + * + * @throws AlpacaAPIRequestException the alpaca API exception + * @see Orders + */ + public ArrayList getOrders(OrderStatus status, Integer limit, ZonedDateTime after, ZonedDateTime until, + Direction direction, Boolean nested) throws AlpacaAPIRequestException { + AlpacaRequestBuilder urlBuilder = new AlpacaRequestBuilder(baseAPIURL, apiVersion, + AlpacaConstants.ORDERS_ENDPOINT); + + if (status != null) { + urlBuilder.appendURLParameter(AlpacaConstants.STATUS_PARAMETER, status.getAPIName()); + } + + if (limit != null) { + urlBuilder.appendURLParameter(AlpacaConstants.LIMIT_PARAMETER, limit.toString()); + } + + if (after != null) { + urlBuilder.appendURLParameter(AlpacaConstants.AFTER_PARAMETER, + after.format(DateTimeFormatter.ISO_OFFSET_DATE_TIME)); + } + + if (until != null) { + urlBuilder.appendURLParameter(AlpacaConstants.UNTIL_PARAMETER, + until.format(DateTimeFormatter.ISO_OFFSET_DATE_TIME)); + } + + if (direction != null) { + urlBuilder.appendURLParameter(AlpacaConstants.DIRECTION_PARAMETER, direction.getAPIName()); + } + + if (nested != null) { + urlBuilder.appendURLParameter(AlpacaConstants.NESTED_PARAMETER, nested.toString()); + } + + HttpResponse response = alpacaRequest.invokeGet(urlBuilder); + + if (response.getStatus() != 200) { + throw new AlpacaAPIRequestException(response); + } + + Type arrayListType = new TypeToken>() {}.getType(); + + return alpacaRequest.getResponseObject(response, arrayListType); + } + + /** + * Places a new order for the given account. An order request may be rejected if the account is not authorized for + * trading, or if the tradable balance is insufficient to fill the order. Note: many of the parameters for this + * method can be set to null if they aren't required for your order type. + * + * @param symbol symbol or asset ID to identify the asset to trade + * @param quantity number of shares to trade + * @param side buy or sell + * @param type market, limit, stop, or stop_limit + * @param timeInForce day, gtc, opg, cls, ioc, fok. Please see Understand Orders for more info. + * @param limitPrice required if type is limit or stop_limit + * @param stopPrice required if type is stop or stop_limit + * @param trailPrice this or trail_percent is required if type is trailing_stop + * @param trailPercent this or trail_price is required if type is trailing_stop + * @param extendedHours (default) false. If true, order will be eligible to execute in premarket/afterhours. + * Only works with type limit and time_in_force day. + * @param clientOrderId A unique identifier for the order. Automatically generated if not sent. + * @param orderClass Simple, bracket, oco, or oto. For details of non-simple order classes, please see + * Bracket Order Overview on the Alpaca Docs. + * @param takeProfitLimitPrice Additional parameter for take-profit leg of advanced orders. Required for bracket + * orders. + * @param stopLossStopPrice Additional parameters for stop-loss leg of advanced orders. Required for bracket + * orders. + * @param stopLossLimitPrice Additional parameters for stop-loss leg of advanced orders. The stop-loss order + * becomes a stop-limit order if specified. + * + * @return the order + * + * @throws AlpacaAPIRequestException the alpaca API exception + * @see Orders + */ + public Order requestNewOrder(String symbol, Integer quantity, OrderSide side, OrderType type, + OrderTimeInForce timeInForce, Double limitPrice, Double stopPrice, Double trailPrice, Double trailPercent, + Boolean extendedHours, String clientOrderId, OrderClass orderClass, Double takeProfitLimitPrice, + Double stopLossStopPrice, Double stopLossLimitPrice) + throws AlpacaAPIRequestException { + Preconditions.checkNotNull(symbol); + Preconditions.checkNotNull(quantity); + Preconditions.checkNotNull(side); + Preconditions.checkNotNull(type); + Preconditions.checkNotNull(timeInForce); + + AlpacaRequestBuilder urlBuilder = new AlpacaRequestBuilder(baseAPIURL, apiVersion, + AlpacaConstants.ORDERS_ENDPOINT); + + urlBuilder.appendJSONBodyProperty(AlpacaConstants.SYMBOL_PARAMETER, symbol); + urlBuilder.appendJSONBodyProperty(AlpacaConstants.QTY_PARAMETER, quantity.toString()); + urlBuilder.appendJSONBodyProperty(AlpacaConstants.SIDE_PARAMETER, side.getAPIName()); + urlBuilder.appendJSONBodyProperty(AlpacaConstants.TYPE_PARAMETER, type.getAPIName()); + urlBuilder.appendJSONBodyProperty(AlpacaConstants.TIME_IN_FORCE_PARAMETER, timeInForce.getAPIName()); + + if (limitPrice != null) { + urlBuilder.appendJSONBodyProperty(AlpacaConstants.LIMIT_PRICE_PARAMETER, + FormatUtil.toDecimalFormat(limitPrice)); + } + + if (stopPrice != null) { + urlBuilder.appendJSONBodyProperty(AlpacaConstants.STOP_PRICE_PARAMETER, + FormatUtil.toDecimalFormat(stopPrice)); + } + + if (trailPrice != null) { + urlBuilder.appendJSONBodyProperty(AlpacaConstants.TRAIL_PRICE_PARAMETER, + FormatUtil.toDecimalFormat(trailPrice)); + } + + if (trailPercent != null) { + urlBuilder.appendJSONBodyProperty(AlpacaConstants.TRAIL_PERCENT_PARAMETER, + FormatUtil.toDecimalFormat(trailPercent)); + } + + if (extendedHours != null) { + urlBuilder.appendJSONBodyJSONProperty(AlpacaConstants.EXTENDED_HOURS_PARAMETER, + new JsonPrimitive(extendedHours)); + } + + if (clientOrderId != null) { + urlBuilder.appendJSONBodyProperty(AlpacaConstants.CLIENT_ORDER_ID_PARAMETER, clientOrderId); + } + + if (orderClass != null) { + urlBuilder.appendJSONBodyProperty(AlpacaConstants.ORDER_CLASS_PARAMETER, orderClass.getAPIName()); + } + + if (takeProfitLimitPrice != null) { + JsonObject takeProfit = new JsonObject(); + takeProfit.addProperty(AlpacaConstants.LIMIT_PRICE_PARAMETER, takeProfitLimitPrice); + + urlBuilder.appendJSONBodyJSONProperty(AlpacaConstants.TAKE_PROFIT_PARAMETER, takeProfit); + } + + if (stopLossStopPrice != null || stopLossLimitPrice != null) { + JsonObject stopLoss = new JsonObject(); + + if (stopLossStopPrice != null) { + stopLoss.addProperty(AlpacaConstants.STOP_PRICE_PARAMETER, stopLossStopPrice); + } + if (stopLossLimitPrice != null) { + stopLoss.addProperty(AlpacaConstants.LIMIT_PRICE_PARAMETER, stopLossLimitPrice); + } + + urlBuilder.appendJSONBodyJSONProperty(AlpacaConstants.STOP_LOSS_PARAMETER, stopLoss); + } + + HttpResponse response = alpacaRequest.invokePost(urlBuilder); + + if (response.getStatus() != 200) { + throw new AlpacaAPIRequestException(response); + } + + return alpacaRequest.getResponseObject(response, Order.class); + } + + /** + * A market order is a request to buy or sell a security at the currently available market price. It provides the + * most likely method of filling an order. Market orders fill nearly instantaneously. This method calls {@link + * #requestNewOrder(String, Integer, OrderSide, OrderType, OrderTimeInForce, Double, Double, Double, Double, + * Boolean, String, OrderClass, Double, Double, Double)} with {@link OrderType#MARKET}. + * + * @param symbol symbol or asset ID to identify the asset to trade + * @param quantity number of shares to trade + * @param side buy or sell + * @param timeInForce day, gtc, opg, cls, ioc, fok. Please see Understand Orders for more info. + * + * @return the order + * + * @throws AlpacaAPIRequestException the alpaca api request exception + * @see Order Types + */ + public Order requestNewMarketOrder(String symbol, Integer quantity, OrderSide side, OrderTimeInForce timeInForce) + throws AlpacaAPIRequestException { + return requestNewOrder(symbol, quantity, side, OrderType.MARKET, timeInForce, null, null, null, null, null, + null, OrderClass.SIMPLE, null, null, null); + } + + /** + * A limit order is an order to buy or sell at a specified price or better. A buy limit order (a limit order to buy) + * is executed at the specified limit price or lower (i.e., better). Conversely, a sell limit order (a limit order + * to sell) is executed at the specified limit price or higher (better). Unlike a market order, you have to specify + * the limit price parameter when submitting your order. This method calls {@link #requestNewOrder(String, Integer, + * OrderSide, OrderType, OrderTimeInForce, Double, Double, Double, Double, Boolean, String, OrderClass, Double, + * Double, Double)} with {@link OrderType#LIMIT}. + * + * @param symbol symbol or asset ID to identify the asset to trade + * @param quantity number of shares to trade + * @param side buy or sell + * @param timeInForce day, gtc, opg, cls, ioc, fok. Please see Understand Orders for more info. + * @param limitPrice required if type is limit or stop_limit + * @param extendedHours (default) false. If true, order will be eligible to execute in premarket/afterhours. Only + * works with type limit and time_in_force day. + * + * @return the order + * + * @throws AlpacaAPIRequestException the alpaca api request exception + * @see Order Types + */ + public Order requestNewLimitOrder(String symbol, Integer quantity, OrderSide side, OrderTimeInForce timeInForce, + Double limitPrice, Boolean extendedHours) throws AlpacaAPIRequestException { + return requestNewOrder(symbol, quantity, side, OrderType.LIMIT, timeInForce, limitPrice, null, null, null, + extendedHours, null, OrderClass.SIMPLE, null, null, null); + } + + /** + * A stop (market) order is an order to buy or sell a security when its price moves past a particular point, + * ensuring a higher probability of achieving a predetermined entry or exit price. Once the market price crosses the + * specified stop price, the stop order becomes a market order. Alpaca converts buy stop orders into stop limit + * orders with a limit price that is 4% higher than a stop price < $50 (or 2.5% higher than a stop price >= + * $50). Sell stop orders are not converted into stop limit orders. This method calls {@link + * #requestNewOrder(String, Integer, OrderSide, OrderType, OrderTimeInForce, Double, Double, Double, Double, + * Boolean, String, OrderClass, Double, Double, Double)} with {@link OrderType#STOP}. + * + * @param symbol symbol or asset ID to identify the asset to trade + * @param quantity number of shares to trade + * @param side buy or sell + * @param timeInForce day, gtc, opg, cls, ioc, fok. Please see Understand Orders for more info. + * @param stopPrice required if type is stop or stop_limit + * @param extendedHours (default) false. If true, order will be eligible to execute in premarket/afterhours. Only + * works with type limit and time_in_force day. + * + * @return the order + * + * @throws AlpacaAPIRequestException the alpaca api request exception + * @see Order Types + */ + public Order requestNewStopOrder(String symbol, Integer quantity, OrderSide side, OrderTimeInForce timeInForce, + Double stopPrice, Boolean extendedHours) throws AlpacaAPIRequestException { + return requestNewOrder(symbol, quantity, side, OrderType.STOP, timeInForce, null, stopPrice, null, null, + extendedHours, null, OrderClass.SIMPLE, null, null, null); + } + + /** + * A stop-limit order is a conditional trade over a set time frame that combines the features of a stop order with + * those of a limit order and is used to mitigate risk. The stop-limit order will be executed at a specified limit + * price, or better, after a given stop price has been reached. Once the stop price is reached, the stop-limit order + * becomes a limit order to buy or sell at the limit price or better. This method calls {@link + * #requestNewOrder(String, Integer, OrderSide, OrderType, OrderTimeInForce, Double, Double, Double, Double, + * Boolean, String, OrderClass, Double, Double, Double)} with {@link OrderType#STOP_LIMIT}. + * + * @param symbol symbol or asset ID to identify the asset to trade + * @param quantity number of shares to trade + * @param side buy or sell + * @param timeInForce day, gtc, opg, cls, ioc, fok. Please see Understand Orders for more info. + * @param limitPrice required if type is limit or stop_limit + * @param stopPrice required if type is stop or stop_limit + * @param extendedHours (default) false. If true, order will be eligible to execute in premarket/afterhours. Only + * works with type limit and time_in_force day. + * + * @return the order + * + * @throws AlpacaAPIRequestException the alpaca api request exception + * @see Order Types + */ + public Order requestNewStopLimitOrder(String symbol, Integer quantity, OrderSide side, OrderTimeInForce timeInForce, + Double limitPrice, Double stopPrice, Boolean extendedHours) throws AlpacaAPIRequestException { + return requestNewOrder(symbol, quantity, side, OrderType.STOP_LIMIT, timeInForce, limitPrice, stopPrice, + null, null, extendedHours, null, OrderClass.SIMPLE, null, null, null); + } + + /** + * A bracket order is a chain of three orders that can be used to manage your position entry and exit. It is a + * common use case of an OTOCO (One Triggers OCO {One Cancels Other}) order. This method calls {@link + * #requestNewOrder(String, Integer, OrderSide, OrderType, OrderTimeInForce, Double, Double, Double, Double, + * Boolean, String, OrderClass, Double, Double, Double)} with {@link OrderType#MARKET} and with parameters for a + * bracket order. + * + * @param symbol symbol or asset ID to identify the asset to trade + * @param quantity number of shares to trade + * @param side buy or sell + * @param timeInForce day, gtc, opg, cls, ioc, fok. Please see Understand Orders for more info. + * @param extendedHours (default) false. If true, order will be eligible to execute in premarket/afterhours. + * Only works with type limit and time_in_force day. + * @param takeProfitLimitPrice Additional parameter for take-profit leg of advanced orders. Required for bracket + * orders. + * @param stopLossStopPrice Additional parameters for stop-loss leg of advanced orders. Required for bracket + * orders. + * @param stopLossLimitPrice Additional parameters for stop-loss leg of advanced orders. The stop-loss order + * becomes a stop-limit order if specified. + * + * @return the order + * + * @throws AlpacaAPIRequestException the alpaca api request exception + * @see Order Types + */ + public Order requestNewMarketBracketOrder(String symbol, Integer quantity, OrderSide side, + OrderTimeInForce timeInForce, Boolean extendedHours, Double takeProfitLimitPrice, Double stopLossStopPrice, + Double stopLossLimitPrice) throws AlpacaAPIRequestException { + return requestNewOrder(symbol, quantity, side, OrderType.MARKET, timeInForce, null, null, null, null, + extendedHours, null, OrderClass.BRACKET, takeProfitLimitPrice, stopLossStopPrice, + stopLossLimitPrice); + } + + /** + * A bracket order is a chain of three orders that can be used to manage your position entry and exit. It is a + * common use case of an OTOCO (One Triggers OCO {One Cancels Other}) order. This method calls {@link + * #requestNewOrder(String, Integer, OrderSide, OrderType, OrderTimeInForce, Double, Double, Double, Double, + * Boolean, String, OrderClass, Double, Double, Double)} with {@link OrderType#LIMIT} and with parameters for a + * bracket order. + * + * @param symbol symbol or asset ID to identify the asset to trade + * @param quantity number of shares to trade + * @param side buy or sell + * @param timeInForce day, gtc, opg, cls, ioc, fok. Please see Understand Orders for more info. + * @param limitPrice required if type is limit or stop_limit + * @param extendedHours (default) false. If true, order will be eligible to execute in premarket/afterhours. + * Only works with type limit and time_in_force day. + * @param takeProfitLimitPrice Additional parameter for take-profit leg of advanced orders. Required for bracket + * orders. + * @param stopLossStopPrice Additional parameters for stop-loss leg of advanced orders. Required for bracket + * orders. + * @param stopLossLimitPrice Additional parameters for stop-loss leg of advanced orders. The stop-loss order + * becomes a stop-limit order if specified. + * + * @return the order + * + * @throws AlpacaAPIRequestException the alpaca api request exception + * @see Order Types + */ + public Order requestNewLimitBracketOrder(String symbol, Integer quantity, OrderSide side, + OrderTimeInForce timeInForce, Double limitPrice, Boolean extendedHours, Double takeProfitLimitPrice, + Double stopLossStopPrice, Double stopLossLimitPrice) throws AlpacaAPIRequestException { + return requestNewOrder(symbol, quantity, side, OrderType.LIMIT, timeInForce, limitPrice, null, null, null, + extendedHours, null, OrderClass.BRACKET, takeProfitLimitPrice, stopLossStopPrice, stopLossLimitPrice); + } + + /** + * OCO (One-Cancels-Other) is another type of advanced order type. This is a set of two orders with the same side + * (buy/buy or sell/sell) and currently only exit order is supported. In other words, this is the second part of the + * bracket orders where the entry order is already filled, and you can submit the take-profit and stop-loss in one + * order submission. This method calls {@link #requestNewOrder(String, Integer, OrderSide, OrderType, + * OrderTimeInForce, Double, Double, Double, Double, Boolean, String, OrderClass, Double, Double, Double)} with + * parameters for an OCO order type. + * + * @param symbol symbol or asset ID to identify the asset to trade + * @param quantity number of shares to trade + * @param side buy or sell + * @param timeInForce day, gtc, opg, cls, ioc, fok. Please see Understand Orders for more info. + * @param extendedHours (default) false. If true, order will be eligible to execute in premarket/afterhours. + * Only works with type limit and time_in_force day. + * @param takeProfitLimitPrice Additional parameter for take-profit leg of advanced orders. + * @param stopLossStopPrice Additional parameters for stop-loss leg of advanced orders. + * @param stopLossLimitPrice Additional parameters for stop-loss leg of advanced orders. The stop-loss order + * becomes a stop-limit order if specified. + * + * @return the order + * + * @throws AlpacaAPIRequestException the alpaca api request exception + * @see Order Types + */ + public Order requestNewOCOOrder(String symbol, Integer quantity, OrderSide side, + OrderTimeInForce timeInForce, Boolean extendedHours, Double takeProfitLimitPrice, + Double stopLossStopPrice, Double stopLossLimitPrice) throws AlpacaAPIRequestException { + return requestNewOrder(symbol, quantity, side, OrderType.LIMIT, timeInForce, null, null, null, null, + extendedHours, null, OrderClass.OCO, takeProfitLimitPrice, stopLossStopPrice, stopLossLimitPrice); + } + + /** + * OTO (One-Triggers-Other) is a variant of bracket order. It takes one of the take-profit or stop-loss order in + * addition to the entry order. This method calls {@link #requestNewOrder(String, Integer, OrderSide, OrderType, + * OrderTimeInForce, Double, Double, Double, Double, Boolean, String, OrderClass, Double, Double, Double)} with + * {@link OrderType#LIMIT} and with parameters for an OTO order type. + * + * @param symbol symbol or asset ID to identify the asset to trade + * @param quantity number of shares to trade + * @param side buy or sell + * @param timeInForce day, gtc, opg, cls, ioc, fok. Please see Understand Orders for more info. + * @param extendedHours (default) false. If true, order will be eligible to execute in premarket/afterhours. + * Only works with type limit and time_in_force day. + * @param takeProfitLimitPrice Additional parameter for take-profit leg of advanced orders. + * @param stopLossStopPrice Additional parameters for stop-loss leg of advanced orders. + * @param stopLossLimitPrice Additional parameters for stop-loss leg of advanced orders. The stop-loss order + * becomes a stop-limit order if specified. + * + * @return the order + * + * @throws AlpacaAPIRequestException the alpaca api request exception + * @see Order Types + */ + public Order requestNewOTOMarketOrder(String symbol, Integer quantity, OrderSide side, + OrderTimeInForce timeInForce, Boolean extendedHours, Double takeProfitLimitPrice, + Double stopLossStopPrice, Double stopLossLimitPrice) throws AlpacaAPIRequestException { + return requestNewOrder(symbol, quantity, side, OrderType.MARKET, timeInForce, null, null, null, null, + extendedHours, null, OrderClass.OTO, takeProfitLimitPrice, stopLossStopPrice, stopLossLimitPrice); + } + + /** + * OTO (One-Triggers-Other) is a variant of bracket order. It takes one of the take-profit or stop-loss order in + * addition to the entry order. This method calls {@link #requestNewOrder(String, Integer, OrderSide, OrderType, + * OrderTimeInForce, Double, Double, Double, Double, Boolean, String, OrderClass, Double, Double, Double)} with + * {@link OrderType#LIMIT} and with parameters for an OTO order type. + * + * @param symbol symbol or asset ID to identify the asset to trade + * @param quantity number of shares to trade + * @param side buy or sell + * @param timeInForce day, gtc, opg, cls, ioc, fok. Please see Understand Orders for more info. + * @param limitPrice required if type is limit or stop_limit + * @param extendedHours (default) false. If true, order will be eligible to execute in premarket/afterhours. + * Only works with type limit and time_in_force day. + * @param takeProfitLimitPrice Additional parameter for take-profit leg of advanced orders. + * @param stopLossStopPrice Additional parameters for stop-loss leg of advanced orders. + * @param stopLossLimitPrice Additional parameters for stop-loss leg of advanced orders. The stop-loss order + * becomes a stop-limit order if specified. + * + * @return the order + * + * @throws AlpacaAPIRequestException the alpaca api request exception + * @see Order Types + */ + public Order requestNewOTOLimitOrder(String symbol, Integer quantity, OrderSide side, + OrderTimeInForce timeInForce, Double limitPrice, Boolean extendedHours, Double takeProfitLimitPrice, + Double stopLossStopPrice, Double stopLossLimitPrice) throws AlpacaAPIRequestException { + return requestNewOrder(symbol, quantity, side, OrderType.LIMIT, timeInForce, limitPrice, null, null, null, + extendedHours, null, OrderClass.OTO, takeProfitLimitPrice, stopLossStopPrice, stopLossLimitPrice); + } + + /** + * OTO (One-Triggers-Other) is a variant of bracket order. It takes one of the take-profit or stop-loss order in + * addition to the entry order. This method calls {@link #requestNewOrder(String, Integer, OrderSide, OrderType, + * OrderTimeInForce, Double, Double, Double, Double, Boolean, String, OrderClass, Double, Double, Double)} with + * {@link OrderType#STOP} and with parameters for an OTO order type. + * + * @param symbol symbol or asset ID to identify the asset to trade + * @param quantity number of shares to trade + * @param side buy or sell + * @param timeInForce day, gtc, opg, cls, ioc, fok. Please see Understand Orders for more info. + * @param stopPrice required if type is stop or stop_limit + * @param extendedHours (default) false. If true, order will be eligible to execute in premarket/afterhours. + * Only works with type limit and time_in_force day. + * @param takeProfitLimitPrice Additional parameter for take-profit leg of advanced orders. + * @param stopLossStopPrice Additional parameters for stop-loss leg of advanced orders. + * @param stopLossLimitPrice Additional parameters for stop-loss leg of advanced orders. The stop-loss order + * becomes a stop-limit order if specified. + * + * @return the order + * + * @throws AlpacaAPIRequestException the alpaca api request exception + * @see Order Types + */ + public Order requestNewOTOStopOrder(String symbol, Integer quantity, OrderSide side, + OrderTimeInForce timeInForce, Double stopPrice, Boolean extendedHours, Double takeProfitLimitPrice, + Double stopLossStopPrice, Double stopLossLimitPrice) throws AlpacaAPIRequestException { + return requestNewOrder(symbol, quantity, side, OrderType.STOP, timeInForce, null, stopPrice, null, null, + extendedHours, null, OrderClass.OTO, takeProfitLimitPrice, stopLossStopPrice, stopLossLimitPrice); + } + + /** + * OTO (One-Triggers-Other) is a variant of bracket order. It takes one of the take-profit or stop-loss order in + * addition to the entry order. This method calls {@link #requestNewOrder(String, Integer, OrderSide, OrderType, + * OrderTimeInForce, Double, Double, Double, Double, Boolean, String, OrderClass, Double, Double, Double)} with + * {@link OrderType#STOP_LIMIT} and with parameters for an OTO order type. + * + * @param symbol symbol or asset ID to identify the asset to trade + * @param quantity number of shares to trade + * @param side buy or sell + * @param timeInForce day, gtc, opg, cls, ioc, fok. Please see Understand Orders for more info. + * @param limitPrice required if type is limit or stop_limit + * @param stopPrice required if type is stop or stop_limit + * @param extendedHours (default) false. If true, order will be eligible to execute in premarket/afterhours. + * Only works with type limit and time_in_force day. + * @param takeProfitLimitPrice Additional parameter for take-profit leg of advanced orders. + * @param stopLossStopPrice Additional parameters for stop-loss leg of advanced orders. + * @param stopLossLimitPrice Additional parameters for stop-loss leg of advanced orders. The stop-loss order + * becomes a stop-limit order if specified. + * + * @return the order + * + * @throws AlpacaAPIRequestException the alpaca api request exception + * @see Order Types + */ + public Order requestNewOTOStopLimitOrder(String symbol, Integer quantity, OrderSide side, + OrderTimeInForce timeInForce, Double limitPrice, Double stopPrice, Boolean extendedHours, + Double takeProfitLimitPrice, Double stopLossStopPrice, Double stopLossLimitPrice) + throws AlpacaAPIRequestException { + return requestNewOrder(symbol, quantity, side, OrderType.STOP_LIMIT, timeInForce, limitPrice, stopPrice, + null, null, extendedHours, null, OrderClass.OTO, takeProfitLimitPrice, + stopLossStopPrice, stopLossLimitPrice); + } + + /** + * Trailing stop orders allow you to continuously and automatically keep updating the stop price threshold based on + * the stock price movement. This method calls {@link #requestNewOrder(String, Integer, OrderSide, OrderType, + * OrderTimeInForce, Double, Double, Double, Double, Boolean, String, OrderClass, Double, Double, Double)} with + * {@link OrderType#TRAILING_STOP} and with parameters for a Trailing Stop Price order type. + * + * @param symbol symbol or asset ID to identify the asset to trade + * @param quantity number of shares to trade + * @param side buy or sell + * @param timeInForce day, gtc, opg, cls, ioc, fok. Please see Understand Orders for more info. + * @param trailPrice this or trail_percent is required if type is trailing_stop + * @param extendedHours (default) false. If true, order will be eligible to execute in premarket/afterhours. Only + * works with type limit and time_in_force day. + * + * @return the order + * + * @throws AlpacaAPIRequestException the alpaca api request exception + * @see Order Types + */ + public Order requestNewTrailingStopPriceOrder(String symbol, Integer quantity, OrderSide side, + OrderTimeInForce timeInForce, Double trailPrice, Boolean extendedHours) throws AlpacaAPIRequestException { + return requestNewOrder(symbol, quantity, side, OrderType.TRAILING_STOP, timeInForce, null, null, + trailPrice, null, extendedHours, null, OrderClass.OTO, null, null, null); + } + + /** + * Trailing stop orders allow you to continuously and automatically keep updating the stop price threshold based on + * the stock price movement. This method calls {@link #requestNewOrder(String, Integer, OrderSide, OrderType, + * OrderTimeInForce, Double, Double, Double, Double, Boolean, String, OrderClass, Double, Double, Double)} with + * {@link OrderType#TRAILING_STOP} and with parameters for a Trailing Stop Percent order type. + * + * @param symbol symbol or asset ID to identify the asset to trade + * @param quantity number of shares to trade + * @param side buy or sell + * @param timeInForce day, gtc, opg, cls, ioc, fok. Please see Understand Orders for more info. + * @param trailPercent this or trail_price is required if type is trailing_stop + * @param extendedHours (default) false. If true, order will be eligible to execute in premarket/afterhours. Only + * works with type limit and time_in_force day. + * + * @return the order + * + * @throws AlpacaAPIRequestException the alpaca api request exception + * @see Order Types + */ + public Order requestNewTrailingStopPercentOrder(String symbol, Integer quantity, OrderSide side, + OrderTimeInForce timeInForce, Double trailPercent, Boolean extendedHours) throws AlpacaAPIRequestException { + return requestNewOrder(symbol, quantity, side, OrderType.TRAILING_STOP, timeInForce, null, null, + null, trailPercent, extendedHours, null, OrderClass.OTO, null, null, null); + } + + /** + * Retrieves a single order for the given order_id. + * + * @param orderID Order ID + * @param nested If true, the result will roll up multi-leg orders under the legs field of primary order. + * + * @return the order + * + * @throws AlpacaAPIRequestException the alpaca API exception + * @see Orders + */ + public Order getOrder(String orderID, Boolean nested) throws AlpacaAPIRequestException { + Preconditions.checkNotNull(orderID); + + AlpacaRequestBuilder urlBuilder = new AlpacaRequestBuilder(baseAPIURL, apiVersion, + AlpacaConstants.ORDERS_ENDPOINT, + orderID); + + if (nested != null) { + urlBuilder.appendURLParameter(AlpacaConstants.NESTED_PARAMETER, nested.toString()); + } + + HttpResponse response = alpacaRequest.invokeGet(urlBuilder); + + if (response.getStatus() != 200) { + throw new AlpacaAPIRequestException(response); + } + + return alpacaRequest.getResponseObject(response, Order.class); + } + + /** + * Retrieves a single order for the given client_order_id. + * + * @param clientOrderId the client order id + * + * @return the order by client id + * + * @throws AlpacaAPIRequestException the alpaca API exception + * @see Orders + */ + public Order getOrderByClientID(String clientOrderId) throws AlpacaAPIRequestException { + Preconditions.checkNotNull(clientOrderId); + + AlpacaRequestBuilder urlBuilder = new AlpacaRequestBuilder(baseAPIURL, apiVersion, + AlpacaConstants.ORDERS_BY_CLIENT_ORDER_ID_ENDPOINT); + urlBuilder.appendURLParameter(AlpacaConstants.CLIENT_ORDER_ID_PARAMETER, clientOrderId); + + HttpResponse response = alpacaRequest.invokeGet(urlBuilder); + + if (response.getStatus() != 200) { + throw new AlpacaAPIRequestException(response); + } + + return alpacaRequest.getResponseObject(response, Order.class); + } + + /** + * Replaces a single order with updated parameters. Each parameter overrides the corresponding attribute of the + * existing order. The other attributes remain the same as the existing order. + * + * @param orderID Order id + * @param quantity number of shares to trade + * @param timeInForce day, gtc, opg, cls, ioc, fok. Please see Understand Orders for more info. + * @param limitPrice required if type is limit or stop_limit + * @param stopPrice required if type is stop or stop_limit + * @param trail the new value of the trail_price or trail_percent value (works only for type=trailing_stop) + * @param clientOrderId A unique identifier for the order. Automatically generated if not sent. + * + * @return the order + * + * @throws AlpacaAPIRequestException the alpaca api request exception + * @see Orders + */ + public Order replaceOrder(String orderID, Integer quantity, OrderTimeInForce timeInForce, Double limitPrice, + Double stopPrice, Double trail, String clientOrderId) throws AlpacaAPIRequestException { + Preconditions.checkNotNull(orderID); + + AlpacaRequestBuilder urlBuilder = new AlpacaRequestBuilder(baseAPIURL, apiVersion, + AlpacaConstants.ORDERS_ENDPOINT, + orderID); + + if (quantity != null) { + urlBuilder.appendJSONBodyProperty(AlpacaConstants.QTY_PARAMETER, quantity.toString()); + } + + if (timeInForce != null) { + urlBuilder.appendJSONBodyProperty(AlpacaConstants.TIME_IN_FORCE_PARAMETER, timeInForce.getAPIName()); + } + + if (limitPrice != null) { + urlBuilder.appendJSONBodyProperty(AlpacaConstants.LIMIT_PRICE_PARAMETER, + FormatUtil.toDecimalFormat(limitPrice)); + } + + if (stopPrice != null) { + urlBuilder.appendJSONBodyProperty(AlpacaConstants.STOP_PRICE_PARAMETER, + FormatUtil.toDecimalFormat(stopPrice)); + } + + if (trail != null) { + urlBuilder.appendJSONBodyProperty(AlpacaConstants.TRAIL_PARAMETER, + FormatUtil.toDecimalFormat(trail)); + } + + if (clientOrderId != null) { + urlBuilder.appendJSONBodyProperty(AlpacaConstants.CLIENT_ORDER_ID_PARAMETER, clientOrderId); + } + + HttpResponse response = alpacaRequest.invokePatch(urlBuilder); + + if (response.getStatus() != 200) { + throw new AlpacaAPIRequestException(response); + } + + return alpacaRequest.getResponseObject(response, Order.class); + } + + /** + * Attempts to cancel all open orders. A response will be provided for each order that is attempted to be cancelled. + * If an order is no longer cancelable, the server will respond with status 500 and reject the request. + * + * @return the array list of cancelled orders + * + * @throws AlpacaAPIRequestException the alpaca api request exception + * @see Orders + */ + public ArrayList cancelAllOrders() throws AlpacaAPIRequestException { + AlpacaRequestBuilder urlBuilder = new AlpacaRequestBuilder(baseAPIURL, apiVersion, + AlpacaConstants.ORDERS_ENDPOINT); + + HttpResponse response = alpacaRequest.invokeDelete(urlBuilder); + + if (response.getStatus() != 207) { // This returns a 207 multi-status message + throw new AlpacaAPIRequestException(response); + } + + Type arrayListType = new TypeToken>() {}.getType(); + + return alpacaRequest.getResponseObject(response, arrayListType); + } + + /** + * Attempts to cancel an open order. If the order is no longer cancelable (example: status=order_filled), the server + * will respond with status 422, and reject the request. + * + * @param orderId Order ID + * + * @return true, if successful + * + * @throws AlpacaAPIRequestException the alpaca API exception + * @see Orders + */ + public boolean cancelOrder(String orderId) throws AlpacaAPIRequestException { + Preconditions.checkNotNull(orderId); + + AlpacaRequestBuilder urlBuilder = new AlpacaRequestBuilder(baseAPIURL, apiVersion, + AlpacaConstants.ORDERS_ENDPOINT, + orderId); + + HttpResponse response = alpacaRequest.invokeDelete(urlBuilder); + + if ((response.getStatus() != 200 && response.getStatus() != 204)) { + throw new AlpacaAPIRequestException(response); + } + + return response.getStatus() == 200 || response.getStatus() == 204; + } + + /** + * Gets the open positions. + * + * @return the open positions + * + * @throws AlpacaAPIRequestException the alpaca API exception + * @see Positions + */ + public ArrayList getOpenPositions() throws AlpacaAPIRequestException { + AlpacaRequestBuilder urlBuilder = new AlpacaRequestBuilder(baseAPIURL, apiVersion, + AlpacaConstants.POSITIONS_ENDPOINT); + + HttpResponse response = alpacaRequest.invokeGet(urlBuilder); + + if (response.getStatus() != 200) { + throw new AlpacaAPIRequestException(response); + } + + Type arrayListType = new TypeToken>() {}.getType(); + + return alpacaRequest.getResponseObject(response, arrayListType); + } + + /** + * Gets the open position by symbol. + * + * @param symbol the symbol or asset_id (required) + * + * @return the open position by symbol + * + * @throws AlpacaAPIRequestException the alpaca API exception + * @see Positions + */ + public Position getOpenPositionBySymbol(String symbol) throws AlpacaAPIRequestException { + Preconditions.checkNotNull(symbol); + + AlpacaRequestBuilder urlBuilder = new AlpacaRequestBuilder(baseAPIURL, apiVersion, + AlpacaConstants.POSITIONS_ENDPOINT, + symbol.trim()); + + HttpResponse response = alpacaRequest.invokeGet(urlBuilder); + + if (response.getStatus() != 200) { + throw new AlpacaAPIRequestException(response); + } + + return alpacaRequest.getResponseObject(response, Position.class); + } + + /** + * Closes (liquidates) all of the account’s open long and short positions. A response will be provided for each + * order that is attempted to be cancelled. If an order is no longer cancelable, the server will respond with status + * 500 and reject the request. + * + * @return a list of closing position orders + * + * @throws AlpacaAPIRequestException the alpaca API exception + * @see Positions + */ + public ArrayList closeAllPositions() throws AlpacaAPIRequestException { + AlpacaRequestBuilder urlBuilder = new AlpacaRequestBuilder(baseAPIURL, apiVersion, + AlpacaConstants.POSITIONS_ENDPOINT); + + HttpResponse response = alpacaRequest.invokeDelete(urlBuilder); + + if (response.getStatus() != 207) { // This returns a 207 multi-status message + throw new AlpacaAPIRequestException(response); + } + + Type arrayListType = new TypeToken>() {}.getType(); + + return alpacaRequest.getResponseObject(response, arrayListType); + } + + /** + * Closes (liquidates) the account’s open position for the given symbol. Works for both long and short positions. + * + * @param symbol symbol or asset_id + * + * @return a closing position order + * + * @throws AlpacaAPIRequestException the alpaca API exception + * @see Positions + */ + public Order closePosition(String symbol) throws AlpacaAPIRequestException { + Preconditions.checkNotNull(symbol); + + AlpacaRequestBuilder urlBuilder = new AlpacaRequestBuilder(baseAPIURL, apiVersion, + AlpacaConstants.POSITIONS_ENDPOINT, + symbol); + + HttpResponse response = alpacaRequest.invokeDelete(urlBuilder); + + if (response.getStatus() != 200) { + throw new AlpacaAPIRequestException(response); + } + + return alpacaRequest.getResponseObject(response, Order.class); + } + + /** + * Get a list of assets. + * + * @param assetStatus e.g. “active”. By default, all statuses are included. + * @param assetClass Defaults to us_equity. + * + * @return the assets + * + * @throws AlpacaAPIRequestException the alpaca API exception + * @see Assets + */ + public ArrayList getAssets(AssetStatus assetStatus, String assetClass) throws AlpacaAPIRequestException { + AlpacaRequestBuilder urlBuilder = new AlpacaRequestBuilder(baseAPIURL, apiVersion, + AlpacaConstants.ASSETS_ENDPOINT); + + if (assetStatus != null) { + urlBuilder.appendURLParameter(AlpacaConstants.STATUS_PARAMETER, assetStatus.getAPIName()); + } + + if (assetClass != null) { + urlBuilder.appendURLParameter(AlpacaConstants.ASSET_CLASS_PARAMETER, assetClass.trim()); + } + + HttpResponse response = alpacaRequest.invokeGet(urlBuilder); + + if (response.getStatus() != 200) { + throw new AlpacaAPIRequestException(response); + } + + Type arrayListType = new TypeToken>() {}.getType(); + + return alpacaRequest.getResponseObject(response, arrayListType); + } + + /** + * Gets the asset by symbol or asset_id. + * + * @param symbolOrAssetID the symbol or asset_id (required) + * + * @return the asset by symbol + * + * @throws AlpacaAPIRequestException the alpaca API exception + * @see Assets + */ + public Asset getAssetBySymbol(String symbolOrAssetID) throws AlpacaAPIRequestException { + Preconditions.checkNotNull(symbolOrAssetID); + + AlpacaRequestBuilder urlBuilder = new AlpacaRequestBuilder(baseAPIURL, apiVersion, + AlpacaConstants.ASSETS_ENDPOINT, + symbolOrAssetID.trim()); + + HttpResponse response = alpacaRequest.invokeGet(urlBuilder); + + if (response.getStatus() != 200) { + throw new AlpacaAPIRequestException(response); + } + + return alpacaRequest.getResponseObject(response, Asset.class); + } + + /** + * Returns the list of watchlists registered under the account. + * + * @return the watch lists + * + * @throws AlpacaAPIRequestException the alpaca api exception + * @see Watchlists + */ + public ArrayList getWatchlists() throws AlpacaAPIRequestException { + AlpacaRequestBuilder urlBuilder = new AlpacaRequestBuilder(baseAPIURL, apiVersion, + AlpacaConstants.WATCHLISTS_ENDPOINT); + + HttpResponse response = alpacaRequest.invokeGet(urlBuilder); + + if (response.getStatus() != 200) { + throw new AlpacaAPIRequestException(response); + } + + Type arrayListType = new TypeToken>() {}.getType(); + return alpacaRequest.getResponseObject(response, arrayListType); + } + + /** + * Create a new watchlist with initial set of assets. + * + * @param name arbitrary name string, up to 64 characters + * @param symbols set of symbol string + * + * @return the created watchlist + * + * @throws AlpacaAPIRequestException the alpaca api exception + * @see Watchlists + */ + public Watchlist createWatchlist(String name, String... symbols) throws AlpacaAPIRequestException { + Preconditions.checkNotNull(name); + Preconditions.checkState(name.length() <= 64); + + AlpacaRequestBuilder urlBuilder = new AlpacaRequestBuilder(baseAPIURL, apiVersion, + AlpacaConstants.WATCHLISTS_ENDPOINT); + + urlBuilder.appendJSONBodyProperty("name", name); + + if (symbols != null) { + JsonArray symbolsArray = new JsonArray(); + Arrays.stream(symbols).forEach(symbolsArray::add); + + urlBuilder.appendJSONBodyJSONProperty("symbols", symbolsArray); + } + + HttpResponse response = alpacaRequest.invokePost(urlBuilder); + + if (response.getStatus() != 200) { + throw new AlpacaAPIRequestException(response); + } + + return alpacaRequest.getResponseObject(response, Watchlist.class); + } + + /** + * Returns a watchlist identified by the ID. + * + * @param watchlistID Watchlist ID + * + * @return the watchlist + * + * @throws AlpacaAPIRequestException the alpaca api exception + * @see Watchlists + */ + public Watchlist getWatchlist(String watchlistID) throws AlpacaAPIRequestException { + Preconditions.checkNotNull(watchlistID); + + AlpacaRequestBuilder urlBuilder = new AlpacaRequestBuilder(baseAPIURL, apiVersion, + AlpacaConstants.WATCHLISTS_ENDPOINT, + watchlistID); + + HttpResponse response = alpacaRequest.invokeGet(urlBuilder); + + if (response.getStatus() != 200) { + throw new AlpacaAPIRequestException(response); + } + + return alpacaRequest.getResponseObject(response, Watchlist.class); + } + + /** + * Update the name and/or content of watchlist. + * + * @param watchlistID Watchlist ID + * @param name the new watchlist name + * @param symbols the new list of symbol names to replace the watchlist content + * + * @return the updated watchlist + * + * @throws AlpacaAPIRequestException the alpaca api exception + * @see Watchlists + */ + public Watchlist updateWatchlist(String watchlistID, String name, String... symbols) + throws AlpacaAPIRequestException { + Preconditions.checkNotNull(watchlistID); + + AlpacaRequestBuilder urlBuilder = new AlpacaRequestBuilder(baseAPIURL, apiVersion, + AlpacaConstants.WATCHLISTS_ENDPOINT, + watchlistID); + + if (name != null) { + urlBuilder.appendJSONBodyProperty("name", name); + } + + if (symbols != null) { + JsonArray symbolsArray = new JsonArray(); + Arrays.stream(symbols).forEach(symbolsArray::add); + + urlBuilder.appendJSONBodyJSONProperty("symbols", symbolsArray); + } + + HttpResponse response = alpacaRequest.invokePut(urlBuilder); + + if (response.getStatus() != 200) { + throw new AlpacaAPIRequestException(response); + } + + return alpacaRequest.getResponseObject(response, Watchlist.class); + } + + /** + * Append an asset for the symbol to the end of watchlist asset list + * + * @param watchlistID Watchlist ID + * @param symbol the symbol name to add to the watchlist + * + * @return the watchlist + * + * @throws AlpacaAPIRequestException the alpaca api exception + * @see Watchlists + */ + public Watchlist addWatchlistAsset(String watchlistID, String symbol) throws AlpacaAPIRequestException { + Preconditions.checkNotNull(watchlistID); + Preconditions.checkNotNull(symbol); + + AlpacaRequestBuilder urlBuilder = new AlpacaRequestBuilder(baseAPIURL, apiVersion, + AlpacaConstants.WATCHLISTS_ENDPOINT, + watchlistID); + + urlBuilder.appendJSONBodyProperty("symbol", symbol); + + HttpResponse response = alpacaRequest.invokePost(urlBuilder); + + if (response.getStatus() != 200) { + throw new AlpacaAPIRequestException(response); + } + + return alpacaRequest.getResponseObject(response, Watchlist.class); + } + + /** + * Delete a watchlist. This is a permanent deletion. + * + * @param watchlistID Watchlist ID + * + * @return if the watchlist was deleted + * + * @throws AlpacaAPIRequestException the alpaca api exception + * @see Watchlists + */ + public boolean deleteWatchlist(String watchlistID) throws AlpacaAPIRequestException { + Preconditions.checkNotNull(watchlistID); + + AlpacaRequestBuilder urlBuilder = new AlpacaRequestBuilder(baseAPIURL, apiVersion, + AlpacaConstants.WATCHLISTS_ENDPOINT, + watchlistID); + + HttpResponse response = alpacaRequest.invokeDelete(urlBuilder); + + if ((response.getStatus() != 200 && response.getStatus() != 204)) { + throw new AlpacaAPIRequestException(response); + } + + return response.getStatus() == 200 || response.getStatus() == 204; + } + + /** + * Delete one entry for an asset by symbol name. + * + * @param watchlistID Watchlist ID + * @param symbol symbol name to remove from the watchlist content + * + * @return the updated watchlist + * + * @throws AlpacaAPIRequestException the alpaca api exception + * @see Watchlists + */ + public Watchlist removeSymbolFromWatchlist(String watchlistID, String symbol) throws AlpacaAPIRequestException { + Preconditions.checkNotNull(watchlistID); + Preconditions.checkNotNull(symbol); + + AlpacaRequestBuilder urlBuilder = new AlpacaRequestBuilder(baseAPIURL, apiVersion, + AlpacaConstants.WATCHLISTS_ENDPOINT, + watchlistID, + symbol); + + HttpResponse response = alpacaRequest.invokeDelete(urlBuilder); + + if (response.getStatus() != 200) { + throw new AlpacaAPIRequestException(response); + } + + return alpacaRequest.getResponseObject(response, Watchlist.class); + } + + /** + * Returns timeseries data about equity and profit/loss (P/L) of the account in requested timespan. + * + * @param periodLength The duration of the data in "number + unit", such as 1D, where unit can be D for day, W for + * week, M for month and A for year. Defaults to 1M. This parameter is the number. + * @param periodUnit The duration of the data in "number + unit", such as 1D, where unit can be D for day, W for + * week, M for month and A for year. Defaults to 1M. This parameter is the unit. + * @param timeFrame The resolution of time window. 1Min, 5Min, 15Min, 1H, or 1D. If omitted, 1Min for less than + * 7 days period, 15Min for less than 30 days, or otherwise 1D. + * @param dateEnd The date the data is returned up to, in "YYYY-MM-DD" format. Defaults to the current market + * date (rolls over at the market open if extended_hours is false, otherwise at 7am ET) + * @param extendedHours If true, include extended hours in the result. This is effective only for timeframe less + * than 1D. + * + * @return the portfolio history + * + * @throws AlpacaAPIRequestException the alpaca api request exception + */ + public PortfolioHistory getPortfolioHistory(Integer periodLength, PortfolioPeriodUnit periodUnit, + PortfolioTimeFrame timeFrame, LocalDate dateEnd, Boolean extendedHours) throws AlpacaAPIRequestException { + AlpacaRequestBuilder urlBuilder = new AlpacaRequestBuilder(baseAPIURL, apiVersion, + AlpacaConstants.ACCOUNT_ENDPOINT, + AlpacaConstants.PORTFOLIO_ENDPOINT, + AlpacaConstants.HISTORY_ENDPOINT); + + if (periodLength != null && periodUnit != null) { + urlBuilder.appendURLParameter(AlpacaConstants.PERIOD_PARAMETER, periodLength + periodUnit.getAPIName()); + } + + if (timeFrame != null) { + urlBuilder.appendURLParameter(AlpacaConstants.TIMEFRAME_PARAMETER, timeFrame.getAPIName()); + } + + if (dateEnd != null) { + urlBuilder.appendURLParameter(AlpacaConstants.DATE_END_PARAMETER, FormatUtil.toDateString(dateEnd)); + } + + if (extendedHours != null) { + urlBuilder.appendURLParameter(AlpacaConstants.EXTENDED_HOURS_PARAMETER, extendedHours.toString()); + } + + HttpResponse response = alpacaRequest.invokeGet(urlBuilder); + + if (response.getStatus() != 200) { + throw new AlpacaAPIRequestException(response); + } + + return alpacaRequest.getResponseObject(response, PortfolioHistory.class); + } + + /** + * Returns the market calendar. + * + * @return the calendar + * + * @throws AlpacaAPIRequestException the alpaca API exception + * @see Calendar + */ + public ArrayList getCalendar() throws AlpacaAPIRequestException { + AlpacaRequestBuilder urlBuilder = new AlpacaRequestBuilder(baseAPIURL, apiVersion, + AlpacaConstants.CALENDAR_ENDPOINT); + + HttpResponse response = alpacaRequest.invokeGet(urlBuilder); + + if (response.getStatus() != 200) { + throw new AlpacaAPIRequestException(response); + } + + Type arrayListType = new TypeToken>() {}.getType(); + + return alpacaRequest.getResponseObject(response, arrayListType); + } + + /** + * Returns the market calendar. + * + * @param start The first date to retrieve data for (inclusive) + * @param end The last date to retrieve data for (inclusive) + * + * @return the calendar + * + * @throws AlpacaAPIRequestException the alpaca API exception + * @see Calendar + */ + public ArrayList getCalendar(LocalDate start, LocalDate end) throws AlpacaAPIRequestException { + AlpacaRequestBuilder urlBuilder = new AlpacaRequestBuilder(baseAPIURL, apiVersion, + AlpacaConstants.CALENDAR_ENDPOINT); + + if (start != null) { + urlBuilder.appendURLParameter(AlpacaConstants.START_PARAMETER, FormatUtil.toDateString(start)); + } + + if (end != null) { + urlBuilder.appendURLParameter(AlpacaConstants.END_PARAMETER, FormatUtil.toDateString(end)); + } + + HttpResponse response = alpacaRequest.invokeGet(urlBuilder); + + if (response.getStatus() != 200) { + throw new AlpacaAPIRequestException(response); + } + + Type arrayListType = new TypeToken>() {}.getType(); + + return alpacaRequest.getResponseObject(response, arrayListType); + } + + /** + * Returns the market clock. + * + * @return the clock + * + * @throws AlpacaAPIRequestException the alpaca API exception + * @see Clock + */ + public Clock getClock() throws AlpacaAPIRequestException { + AlpacaRequestBuilder urlBuilder = new AlpacaRequestBuilder(baseAPIURL, apiVersion, + AlpacaConstants.CLOCK_ENDPOINT); + + HttpResponse response = alpacaRequest.invokeGet(urlBuilder); + + if (response.getStatus() != 200) { + throw new AlpacaAPIRequestException(response); + } + + return alpacaRequest.getResponseObject(response, Clock.class); + } + + /** + * Retrieves a list of bars for each requested symbol. It is guaranteed all bars are in ascending order by time. + *

+ * Currently, no “incomplete” bars are returned. For example, a 1 minute bar for 09:30 will not be returned until + * 09:31. + * + * @param timeframe One of minute, 1Min, 5Min, 15Min, day or 1D. minute is an alias of 1Min. Similarly, day is of + * 1D. + * @param symbol One symbol name. + * @param limit The maximum number of bars to be returned for each symbol. It can be between 1 and 1000. Default + * is 100 if parameter is unspecified or 0. + * @param start Filter bars equal to or after this time. Cannot be used with after. + * @param end Filter bars equal to or before this time. Cannot be used with until. + * @param after Filter bars after this time. Cannot be used with start. + * @param until Filter bars before this time. Cannot be used with end. + * + * @return An object with a key for each symbol and the Bars object as the values. Note that it returns status 200 + * with an empty object if no requested symbol is found. + * + * @throws AlpacaAPIRequestException the alpaca API exception + * @see Bars + */ + public Map> getBars(BarsTimeFrame timeframe, String symbol, Integer limit, + ZonedDateTime start, ZonedDateTime end, ZonedDateTime after, ZonedDateTime until) + throws AlpacaAPIRequestException { + return this.getBars(timeframe, new String[]{symbol}, limit, start, end, after, until); + } + + /** + * Retrieves a list of bars for each requested symbol. It is guaranteed all bars are in ascending order by time. + *

+ * Currently, no “incomplete” bars are returned. For example, a 1 minute bar for 09:30 will not be returned until + * 09:31. + * + * @param timeframe One of minute, 1Min, 5Min, 15Min, day or 1D. minute is an alias of 1Min. Similarly, day is of + * 1D. + * @param symbols One or more (max 200) symbol names split by commas (“,”). + * @param limit The maximum number of bars to be returned for each symbol. It can be between 1 and 1000. Default + * is 100 if parameter is unspecified or 0. + * @param start Filter bars equal to or after this time. Cannot be used with after. + * @param end Filter bars equal to or before this time. Cannot be used with until. + * @param after Filter bars after this time. Cannot be used with start. + * @param until Filter bars before this time. Cannot be used with end. + * + * @return @return An object with a key for each symbol and the Bars object as the values. Note that it returns + * status 200 with an empty object if no requested symbol is found. + * + * @throws AlpacaAPIRequestException the alpaca API exception + * @see Bars + */ + public Map> getBars(BarsTimeFrame timeframe, String[] symbols, Integer limit, + ZonedDateTime start, ZonedDateTime end, ZonedDateTime after, ZonedDateTime until) + throws AlpacaAPIRequestException { + AlpacaRequestBuilder urlBuilder = new AlpacaRequestBuilder(baseDataUrl, AlpacaConstants.VERSION_1_ENDPOINT, + AlpacaConstants.BARS_ENDPOINT); + + if (timeframe != null) { + urlBuilder.appendEndpoint(timeframe.getAPIName()); + } + + if (symbols != null) { + urlBuilder.appendURLParameter(AlpacaConstants.SYMBOLS_PARAMETER, String.join(",", symbols)); + } + + if (limit != null) { + urlBuilder.appendURLParameter(AlpacaConstants.LIMIT_PARAMETER, limit.toString()); + } + + if (start != null) { + urlBuilder.appendURLParameter(AlpacaConstants.START_PARAMETER, + start.format(DateTimeFormatter.ISO_OFFSET_DATE_TIME)); + } + + if (end != null) { + urlBuilder.appendURLParameter(AlpacaConstants.END_PARAMETER, + end.format(DateTimeFormatter.ISO_OFFSET_DATE_TIME)); + } + + if (after != null) { + urlBuilder.appendURLParameter(AlpacaConstants.AFTER_PARAMETER, + after.format(DateTimeFormatter.ISO_OFFSET_DATE_TIME)); + } + + if (until != null) { + urlBuilder.appendURLParameter(AlpacaConstants.UNTIL_PARAMETER, + until.format(DateTimeFormatter.ISO_OFFSET_DATE_TIME)); + } + + HttpResponse response = alpacaRequest.invokeGet(urlBuilder); + + if (response.getStatus() != 200) { + throw new AlpacaAPIRequestException(response); + } + + Type mapType = new TypeToken>>() {}.getType(); + + return alpacaRequest.getResponseObject(response, mapType); + } + + /** + * Retrieves the last trade for the requested symbol. + * + * @param symbol A stock ticker symbol to retrieve the last trade of + * + * @return the last trade response object + * + * @throws AlpacaAPIRequestException the alpaca api request exception + * @see Last Trade + */ + public LastTradeResponse getLastTrade(String symbol) throws AlpacaAPIRequestException { + Preconditions.checkNotNull(symbol); + + AlpacaRequestBuilder urlBuilder = new AlpacaRequestBuilder(baseDataUrl, AlpacaConstants.VERSION_1_ENDPOINT, + AlpacaConstants.LAST_ENDPOINT, + AlpacaConstants.STOCKS_ENDPOINT, + symbol); + + HttpResponse response = alpacaRequest.invokeGet(urlBuilder); + + if (response.getStatus() != 200) { + throw new AlpacaAPIRequestException(response); + } + + return alpacaRequest.getResponseObject(response, LastTradeResponse.class); + } + + /** + * Retrieves the last quote for the requested symbol. + * + * @param symbol A stock ticker symbol to retrieve the last trade of + * + * @return the last quote response object + * + * @throws AlpacaAPIRequestException the alpaca api request exception + * @see (); + } + + /** + * Instantiates a new Alpaca websocket client. + * + * @param token the auth token + * @param baseAPIURL the base API URL + */ + public AlpacaWebsocketClient(String token, String baseAPIURL) { + this.auth_token = token; + this.keyId = null; + this.secret = null; this.streamAPIURL = baseAPIURL.replace("https", "wss") + "/stream"; this.listeners = new ArrayList<>(); @@ -146,8 +165,13 @@ public void sendAuthenticationMessage() { authRequest.addProperty("action", "authenticate"); JsonObject payload = new JsonObject(); - payload.addProperty("key_id", keyId); - payload.addProperty("secret_key", secret); + + if (keyId == null && secret == null && auth_token != null) { + payload.addProperty("oauth_token", auth_token); + } else { + payload.addProperty("key_id", keyId); + payload.addProperty("secret_key", secret); + } authRequest.add("data", payload); diff --git a/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/client/MarketDataWebsocketClient.java b/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/client/MarketDataWebsocketClient.java index 2ef1ed21..30000013 100644 --- a/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/client/MarketDataWebsocketClient.java +++ b/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/client/MarketDataWebsocketClient.java @@ -74,6 +74,9 @@ public class MarketDataWebsocketClient implements WebsocketClient { /** The secret. */ private final String secret; + /** The auth token. */ + private final String auth_token; + /** The Stream API URL. */ private final String streamAPIURL; @@ -96,6 +99,22 @@ public class MarketDataWebsocketClient implements WebsocketClient { public MarketDataWebsocketClient(String keyId, String secret, String baseDataUrl) { this.keyId = keyId; this.secret = secret; + this.auth_token = null; + this.streamAPIURL = baseDataUrl.replace("https", "wss") + "/stream"; + + this.listeners = new ArrayList<>(); + } + + /** + * Instantiates a new Market data websocket client. + * + * @param token the auth token + * @param baseDataUrl the base data url + */ + public MarketDataWebsocketClient(String token, String baseDataUrl) { + this.auth_token = token; + this.keyId = null; + this.secret = null; this.streamAPIURL = baseDataUrl.replace("https", "wss") + "/stream"; this.listeners = new ArrayList<>(); @@ -171,8 +190,13 @@ public void sendAuthenticationMessage() { authRequest.addProperty("action", "authenticate"); JsonObject dataJson = new JsonObject(); - dataJson.addProperty("key_id", keyId); - dataJson.addProperty("secret_key", secret); + if (keyId == null && secret == null && auth_token != null) { + dataJson.addProperty("oauth_token", auth_token); + + } else { + dataJson.addProperty("key_id", keyId); + dataJson.addProperty("secret_key", secret); + } authRequest.add("data", dataJson); From ce75dbdf469704dec2bc64cd3342bbc8228e8311 Mon Sep 17 00:00:00 2001 From: Jacob Peterson Date: Wed, 4 Nov 2020 11:34:04 -0700 Subject: [PATCH 055/271] Remove duplicate code and add OAuth --- .../net/jacobpeterson/alpaca/AlpacaAPI.java | 76 +- .../jacobpeterson/alpaca/AlpacaOAuthAPI.java | 1738 ----------------- .../alpaca/properties/AlpacaProperties.java | 7 - .../alpaca/rest/AlpacaRequest.java | 16 +- .../broker/client/AlpacaWebsocketClient.java | 22 +- .../client/MarketDataWebsocketClient.java | 21 +- 6 files changed, 74 insertions(+), 1806 deletions(-) delete mode 100644 src/main/java/net/jacobpeterson/alpaca/AlpacaOAuthAPI.java diff --git a/src/main/java/net/jacobpeterson/alpaca/AlpacaAPI.java b/src/main/java/net/jacobpeterson/alpaca/AlpacaAPI.java index 627a3257..61219ce6 100644 --- a/src/main/java/net/jacobpeterson/alpaca/AlpacaAPI.java +++ b/src/main/java/net/jacobpeterson/alpaca/AlpacaAPI.java @@ -71,8 +71,8 @@ public class AlpacaAPI { /** The version. */ private final String apiVersion; - /** The key id. */ - private final String keyId; + /** The key ID. */ + private final String keyID; /** The base API url. */ private final String baseAPIURL; @@ -90,67 +90,77 @@ public class AlpacaAPI { private final MarketDataWebsocketClient marketDataWebSocketClient; /** - * Instantiates a new Alpaca API using properties specified in alpaca.properties file (or relevant defaults) + * Instantiates a new Alpaca API using properties specified in alpaca.properties file (or their associated + * defaults) */ public AlpacaAPI() { this(AlpacaProperties.API_VERSION_VALUE, AlpacaProperties.KEY_ID_VALUE, - AlpacaProperties.SECRET_VALUE, AlpacaProperties.BASE_API_URL_VALUE, + AlpacaProperties.SECRET_VALUE, null, AlpacaProperties.BASE_API_URL_VALUE, AlpacaProperties.BASE_DATA_URL_VALUE); LOGGER.debug(AlpacaProperties.staticToString()); } /** - * Instantiates a new Alpaca API using the specified apiVersion + * Instantiates a new AlpacaAPI using the specified keyID and secret. * - * @param apiVersion the api version + * @param keyID the key ID + * @param secret the secret */ - public AlpacaAPI(String apiVersion) { - this(apiVersion, AlpacaProperties.KEY_ID_VALUE, AlpacaProperties.SECRET_VALUE, - AlpacaProperties.BASE_API_URL_VALUE, AlpacaProperties.BASE_DATA_URL_VALUE); + public AlpacaAPI(String keyID, String secret) { + this(AlpacaProperties.API_VERSION_VALUE, keyID, secret, null, AlpacaProperties.BASE_API_URL_VALUE, + AlpacaProperties.BASE_DATA_URL_VALUE); } /** - * Instantiates a new Alpaca API using the specified apiVersion, keyId, and secret. + * Instantiates a new AlpacaAPI using the specified keyID, secret, and baseAPIURL. * - * @param keyId the key id - * @param secret the secret + * @param keyID the key ID + * @param secret the secret + * @param baseAPIURL the api account url */ - public AlpacaAPI(String apiVersion, String keyId, String secret) { - this(apiVersion, keyId, secret, AlpacaProperties.BASE_API_URL_VALUE, - AlpacaProperties.BASE_DATA_URL_VALUE); + public AlpacaAPI(String keyID, String secret, String baseAPIURL) { + this(AlpacaProperties.API_VERSION_VALUE, keyID, secret, null, baseAPIURL, AlpacaProperties.BASE_DATA_URL_VALUE); } /** - * Instantiates a new Alpaca API using the specified apiVersion, keyId, secret, and baseAPIURL. + * Instantiates a new AlpacaAPI using the specified OAuth token. * - * @param apiVersion the api version - * @param keyId the key id - * @param secret the secret - * @param baseAPIURL the api account url + * @param oAuthToken the OAuth token */ - public AlpacaAPI(String apiVersion, String keyId, String secret, String baseAPIURL) { - this(apiVersion, keyId, secret, baseAPIURL, AlpacaProperties.BASE_DATA_URL_VALUE); + public AlpacaAPI(String oAuthToken) { + this(AlpacaProperties.API_VERSION_VALUE, null, null, oAuthToken, AlpacaProperties.BASE_API_URL_VALUE, + AlpacaProperties.BASE_DATA_URL_VALUE); } /** - * Instantiates a new Alpaca API using the specified keyId, secret, baseAPIURL, and baseDataUrl. + * Instantiates a new AlpacaAPI using the specified apiVersion, keyId, secret, OAuth token (if there is one), + * baseAPIURL, and baseDataURL. * * @param apiVersion the api version - * @param keyId the key id + * @param keyID the key ID * @param secret the secret - * @param baseAPIURL the base api url - * @param baseDataUrl the base data url + * @param oAuthToken the OAuth token + * @param baseAPIURL the base API URL + * @param baseDataURL the base data URL */ - public AlpacaAPI(String apiVersion, String keyId, String secret, String baseAPIURL, String baseDataUrl) { + public AlpacaAPI(String apiVersion, String keyID, String secret, String oAuthToken, String baseAPIURL, + String baseDataURL) { + Preconditions.checkState((keyID != null && secret != null) || oAuthToken != null); + this.apiVersion = apiVersion; - this.keyId = keyId; + this.keyID = keyID; this.baseAPIURL = baseAPIURL; - this.baseDataUrl = baseDataUrl; + this.baseDataUrl = baseDataURL; + + boolean isOAuth = oAuthToken != null; - alpacaRequest = new AlpacaRequest(keyId, secret); - alpacaWebSocketClient = new AlpacaWebsocketClient(keyId, secret, baseAPIURL); - marketDataWebSocketClient = new MarketDataWebsocketClient(keyId, secret, baseDataUrl); + alpacaRequest = isOAuth ? new AlpacaRequest(oAuthToken) : + new AlpacaRequest(keyID, secret); + alpacaWebSocketClient = isOAuth ? new AlpacaWebsocketClient(oAuthToken, baseAPIURL) : + new AlpacaWebsocketClient(keyID, secret, baseAPIURL); + marketDataWebSocketClient = isOAuth ? new MarketDataWebsocketClient(oAuthToken, baseDataURL) : + new MarketDataWebsocketClient(keyID, secret, baseDataURL); LOGGER.debug(this.toString()); } @@ -1736,7 +1746,7 @@ public String toString() { .add("apiVersion = " + apiVersion) .add("baseAPIURL = " + baseAPIURL) .add("baseDataUrl = " + baseDataUrl) - .add("keyId = " + keyId) + .add("keyID = " + keyID) .toString(); } } diff --git a/src/main/java/net/jacobpeterson/alpaca/AlpacaOAuthAPI.java b/src/main/java/net/jacobpeterson/alpaca/AlpacaOAuthAPI.java deleted file mode 100644 index 89bbc834..00000000 --- a/src/main/java/net/jacobpeterson/alpaca/AlpacaOAuthAPI.java +++ /dev/null @@ -1,1738 +0,0 @@ -package net.jacobpeterson.alpaca; - -import com.google.common.base.Preconditions; -import com.google.gson.JsonArray; -import com.google.gson.JsonElement; -import com.google.gson.JsonObject; -import com.google.gson.JsonPrimitive; -import com.google.gson.reflect.TypeToken; -import com.mashape.unirest.http.HttpResponse; -import net.jacobpeterson.abstracts.websocket.exception.WebsocketException; -import net.jacobpeterson.alpaca.enums.ActivityType; -import net.jacobpeterson.alpaca.enums.AssetStatus; -import net.jacobpeterson.alpaca.enums.BarsTimeFrame; -import net.jacobpeterson.alpaca.enums.Direction; -import net.jacobpeterson.alpaca.enums.OrderClass; -import net.jacobpeterson.alpaca.enums.OrderSide; -import net.jacobpeterson.alpaca.enums.OrderStatus; -import net.jacobpeterson.alpaca.enums.OrderTimeInForce; -import net.jacobpeterson.alpaca.enums.OrderType; -import net.jacobpeterson.alpaca.enums.PortfolioPeriodUnit; -import net.jacobpeterson.alpaca.enums.PortfolioTimeFrame; -import net.jacobpeterson.alpaca.properties.AlpacaProperties; -import net.jacobpeterson.alpaca.rest.AlpacaRequest; -import net.jacobpeterson.alpaca.rest.AlpacaRequestBuilder; -import net.jacobpeterson.alpaca.rest.exception.AlpacaAPIRequestException; -import net.jacobpeterson.alpaca.websocket.broker.client.AlpacaWebsocketClient; -import net.jacobpeterson.alpaca.websocket.broker.listener.AlpacaStreamListener; -import net.jacobpeterson.alpaca.websocket.marketdata.client.MarketDataWebsocketClient; -import net.jacobpeterson.alpaca.websocket.marketdata.listener.MarketDataStreamListener; -import net.jacobpeterson.domain.alpaca.account.Account; -import net.jacobpeterson.domain.alpaca.accountactivities.AccountActivity; -import net.jacobpeterson.domain.alpaca.accountactivities.NonTradeActivity; -import net.jacobpeterson.domain.alpaca.accountactivities.TradeActivity; -import net.jacobpeterson.domain.alpaca.accountconfiguration.AccountConfiguration; -import net.jacobpeterson.domain.alpaca.asset.Asset; -import net.jacobpeterson.domain.alpaca.calendar.Calendar; -import net.jacobpeterson.domain.alpaca.clock.Clock; -import net.jacobpeterson.domain.alpaca.marketdata.Bar; -import net.jacobpeterson.domain.alpaca.marketdata.LastQuoteResponse; -import net.jacobpeterson.domain.alpaca.marketdata.LastTradeResponse; -import net.jacobpeterson.domain.alpaca.order.CancelledOrder; -import net.jacobpeterson.domain.alpaca.order.Order; -import net.jacobpeterson.domain.alpaca.portfoliohistory.PortfolioHistory; -import net.jacobpeterson.domain.alpaca.position.ClosePositionOrder; -import net.jacobpeterson.domain.alpaca.position.Position; -import net.jacobpeterson.domain.alpaca.watchlist.Watchlist; -import net.jacobpeterson.util.format.FormatUtil; -import net.jacobpeterson.util.gson.GsonUtil; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; - -import java.io.InputStream; -import java.lang.reflect.Type; -import java.time.LocalDate; -import java.time.ZonedDateTime; -import java.time.format.DateTimeFormatter; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Map; -import java.util.StringJoiner; -import java.util.stream.Collectors; - -/** - * The class AlpacaOAuthAPI. Note that most of these methods are blocking methods and this class in NOT thread-safe. - */ -public class AlpacaOAuthAPI { - - /** The logger. */ - private static final Logger LOGGER = LogManager.getLogger(AlpacaOAuthAPI.class); - - /** The version. */ - private final String apiVersion; - - /** The auth token. */ - private final String auth_token; - - /** The base API url. */ - private final String baseAPIURL; - - /** The alpaca request. */ - private final AlpacaRequest alpacaRequest; - - /** The base data url. */ - private final String baseDataUrl; - - /** The alpaca websocket client. */ - private final AlpacaWebsocketClient alpacaWebSocketClient; - - /** The market data websocket client. */ - private final MarketDataWebsocketClient marketDataWebSocketClient; - - /** - * Instantiates a new Alpaca API using properties specified in alpaca.properties file (or relevant defaults) - */ - public AlpacaOAuthAPI() { - this(AlpacaProperties.API_VERSION_VALUE, AlpacaProperties.AUTH_TOKEN_VALUE, AlpacaProperties.BASE_API_URL_VALUE, - AlpacaProperties.BASE_DATA_URL_VALUE); - - LOGGER.debug(AlpacaProperties.staticToString()); - } - - /** - * Instantiates a new Alpaca API using the specified apiVersion - * - * @param apiVersion the api version - */ - public AlpacaOAuthAPI(String apiVersion) { - this(apiVersion, AlpacaProperties.AUTH_TOKEN_VALUE, - AlpacaProperties.BASE_API_URL_VALUE, AlpacaProperties.BASE_DATA_URL_VALUE); - } - - /** - * Instantiates a new Alpaca API using the specified apiVersion, keyId, and secret. - * - * @param token the auth token - */ - public AlpacaOAuthAPI(String apiVersion, String token) { - this(apiVersion, token, AlpacaProperties.BASE_API_URL_VALUE, - AlpacaProperties.BASE_DATA_URL_VALUE); - } - - /** - * Instantiates a new Alpaca API using the specified apiVersion, keyId, secret, and baseAPIURL. - * - * @param apiVersion the api version - * @param token the auth token - * @param baseAPIURL the api account url - */ - public AlpacaOAuthAPI(String apiVersion, String token, String baseAPIURL) { - this(apiVersion, token, baseAPIURL, AlpacaProperties.BASE_DATA_URL_VALUE); - } - - /** - * Instantiates a new Alpaca API using the specified keyId, secret, baseAPIURL, and baseDataUrl. - * - * @param apiVersion the api version - * @param token the auth token - * @param baseAPIURL the base api url - * @param baseDataUrl the base data url - */ - public AlpacaOAuthAPI(String apiVersion, String token, String baseAPIURL, String baseDataUrl) { - this.apiVersion = apiVersion; - this.auth_token = token; - this.baseAPIURL = baseAPIURL; - this.baseDataUrl = baseDataUrl; - - alpacaRequest = new AlpacaRequest(token); - alpacaWebSocketClient = new AlpacaWebsocketClient(token, baseAPIURL); - marketDataWebSocketClient = new MarketDataWebsocketClient(token, baseDataUrl); - - LOGGER.debug(this.toString()); - } - - /** - * Returns the account associated with the API key. - * - * @return the account - * - * @throws AlpacaAPIRequestException the alpaca API exception - * @see Get the Account - */ - public Account getAccount() throws AlpacaAPIRequestException { - AlpacaRequestBuilder urlBuilder = new AlpacaRequestBuilder(baseAPIURL, apiVersion, - AlpacaConstants.ACCOUNT_ENDPOINT); - - HttpResponse response = alpacaRequest.invokeGet(urlBuilder); - - if (response.getStatus() != 200) { - throw new AlpacaAPIRequestException(response); - } - - return alpacaRequest.getResponseObject(response, Account.class); - } - - /** - * Returns account activity entries for many or for a specific type of activity. - * - * @param date The date for which you want to see activities. - * @param until The response will contain only activities submitted before this date. (Cannot be used with - * date.) - * @param after The response will contain only activities submitted after this date. (Cannot be used with - * date.) - * @param direction asc or desc (default desc if unspecified.) - * @param pageSize The maximum number of entries to return in the response. (See the section on paging above.) - * @param pageToken The ID of the end of your current page of results. (See the section on paging above.) - * @param activityTypes the activity types (null for all activities) - * - * @return the account activities - * - * @throws AlpacaAPIRequestException the alpaca api exception - * @see Gets the Account - * Activities - */ - public ArrayList getAccountActivities(ZonedDateTime date, ZonedDateTime until, ZonedDateTime after, - Direction direction, Integer pageSize, String pageToken, ActivityType... activityTypes) - throws AlpacaAPIRequestException { - AlpacaRequestBuilder urlBuilder = new AlpacaRequestBuilder(baseAPIURL, AlpacaConstants.VERSION_2_ENDPOINT, - AlpacaConstants.ACCOUNT_ENDPOINT, - AlpacaConstants.ACTIVITIES_ENDPOINT); - - if (activityTypes != null) { // Check if we don't want all activity types - if (activityTypes.length == 1) { // Get one activity - urlBuilder.appendEndpoint(activityTypes[0].getAPIName()); - } else { // Get list of activities - urlBuilder.appendURLParameter("activity_types", Arrays.stream(activityTypes) - .map(ActivityType::getAPIName).collect(Collectors.joining(","))); // Makes comma-separated list - } - } - - if (date != null) { - urlBuilder.appendURLParameter(AlpacaConstants.DATE_PARAMETER, - date.format(DateTimeFormatter.ISO_OFFSET_DATE_TIME)); - } - - if (until != null) { - urlBuilder.appendURLParameter(AlpacaConstants.UNTIL_PARAMETER, - until.format(DateTimeFormatter.ISO_OFFSET_DATE_TIME)); - } - - if (after != null) { - urlBuilder.appendURLParameter(AlpacaConstants.AFTER_PARAMETER, - after.format(DateTimeFormatter.ISO_OFFSET_DATE_TIME)); - } - - if (direction != null) { - urlBuilder.appendURLParameter(AlpacaConstants.DIRECTION_PARAMETER, direction.getAPIName()); - } - - if (pageSize != null) { - urlBuilder.appendURLParameter(AlpacaConstants.PAGE_SIZE_PARAMETER, pageSize.toString()); - } - - if (pageToken != null) { - urlBuilder.appendURLParameter(AlpacaConstants.PAGE_TOKEN_PARAMETER, pageToken); - } - - HttpResponse response = alpacaRequest.invokeGet(urlBuilder); - - if (response.getStatus() != 200) { - throw new AlpacaAPIRequestException(response); - } - JsonElement responseJsonElement = alpacaRequest.getResponseJSON(response); - ArrayList accountActivities = new ArrayList<>(); - - if (responseJsonElement instanceof JsonArray) { - JsonArray responseJsonArray = (JsonArray) responseJsonElement; - - for (JsonElement arrayJsonElement : responseJsonArray) { // Loop through response array - if (arrayJsonElement instanceof JsonObject) { - JsonObject arrayJsonObject = (JsonObject) arrayJsonElement; - - if (GsonUtil.doesGsonPOJOMatch(TradeActivity.class, arrayJsonObject)) { - accountActivities.add(GsonUtil.GSON.fromJson(arrayJsonObject, TradeActivity.class)); - } else if (GsonUtil.doesGsonPOJOMatch(NonTradeActivity.class, arrayJsonObject)) { - accountActivities.add(GsonUtil.GSON.fromJson(arrayJsonObject, NonTradeActivity.class)); - } else { - LOGGER.warn("Received unknown JSON Object in response!"); - } - } else { - throw new IllegalStateException("All array elements must be objects!"); - } - } - return accountActivities; - } else { - throw new IllegalStateException("The response must be an array!"); - } - } - - /** - * Returns the current account configuration values. - * - * @return the account configurations - * - * @throws AlpacaAPIRequestException the alpaca api exception - * @see Account - * Configuration - */ - public AccountConfiguration getAccountConfiguration() throws AlpacaAPIRequestException { - AlpacaRequestBuilder urlBuilder = new AlpacaRequestBuilder(baseAPIURL, apiVersion, - AlpacaConstants.ACCOUNT_ENDPOINT, - AlpacaConstants.CONFIGURATIONS_ENDPOINT); - - HttpResponse response = alpacaRequest.invokeGet(urlBuilder); - - if (response.getStatus() != 200) { - throw new AlpacaAPIRequestException(response); - } - - return alpacaRequest.getResponseObject(response, AccountConfiguration.class); - } - - /** - * Sets account configuration. - * - * @param accountConfiguration the account configuration - * - * @return the updated account configuration - * - * @throws AlpacaAPIRequestException the alpaca api exception - * @see Account - * Configuration - */ - public AccountConfiguration setAccountConfiguration(AccountConfiguration accountConfiguration) - throws AlpacaAPIRequestException { - Preconditions.checkNotNull(accountConfiguration); - - AlpacaRequestBuilder urlBuilder = new AlpacaRequestBuilder(baseAPIURL, apiVersion, - AlpacaConstants.ACCOUNT_ENDPOINT, - AlpacaConstants.CONFIGURATIONS_ENDPOINT); - urlBuilder.setCustomBody(GsonUtil.GSON.toJson(accountConfiguration)); - - HttpResponse response = alpacaRequest.invokePatch(urlBuilder); - - if (response.getStatus() != 200) { - throw new AlpacaAPIRequestException(response); - } - - return alpacaRequest.getResponseObject(response, AccountConfiguration.class); - } - - /** - * Retrieves a list of orders for the account, filtered by the supplied query parameters. - * - * @param status Order status to be queried. open, closed or all. Defaults to open. - * @param limit The maximum number of orders in response. Defaults to 50 and max is 500. - * @param after The response will include only ones submitted after this timestamp (exclusive.) - * @param until The response will include only ones submitted until this timestamp (exclusive.) - * @param direction The chronological order of response based on the submission time. asc or desc. Defaults to - * desc. - * @param nested If true, the result will roll up multi-leg orders under the legs field of primary order. - * - * @return the orders - * - * @throws AlpacaAPIRequestException the alpaca API exception - * @see Orders - */ - public ArrayList getOrders(OrderStatus status, Integer limit, ZonedDateTime after, ZonedDateTime until, - Direction direction, Boolean nested) throws AlpacaAPIRequestException { - AlpacaRequestBuilder urlBuilder = new AlpacaRequestBuilder(baseAPIURL, apiVersion, - AlpacaConstants.ORDERS_ENDPOINT); - - if (status != null) { - urlBuilder.appendURLParameter(AlpacaConstants.STATUS_PARAMETER, status.getAPIName()); - } - - if (limit != null) { - urlBuilder.appendURLParameter(AlpacaConstants.LIMIT_PARAMETER, limit.toString()); - } - - if (after != null) { - urlBuilder.appendURLParameter(AlpacaConstants.AFTER_PARAMETER, - after.format(DateTimeFormatter.ISO_OFFSET_DATE_TIME)); - } - - if (until != null) { - urlBuilder.appendURLParameter(AlpacaConstants.UNTIL_PARAMETER, - until.format(DateTimeFormatter.ISO_OFFSET_DATE_TIME)); - } - - if (direction != null) { - urlBuilder.appendURLParameter(AlpacaConstants.DIRECTION_PARAMETER, direction.getAPIName()); - } - - if (nested != null) { - urlBuilder.appendURLParameter(AlpacaConstants.NESTED_PARAMETER, nested.toString()); - } - - HttpResponse response = alpacaRequest.invokeGet(urlBuilder); - - if (response.getStatus() != 200) { - throw new AlpacaAPIRequestException(response); - } - - Type arrayListType = new TypeToken>() {}.getType(); - - return alpacaRequest.getResponseObject(response, arrayListType); - } - - /** - * Places a new order for the given account. An order request may be rejected if the account is not authorized for - * trading, or if the tradable balance is insufficient to fill the order. Note: many of the parameters for this - * method can be set to null if they aren't required for your order type. - * - * @param symbol symbol or asset ID to identify the asset to trade - * @param quantity number of shares to trade - * @param side buy or sell - * @param type market, limit, stop, or stop_limit - * @param timeInForce day, gtc, opg, cls, ioc, fok. Please see Understand Orders for more info. - * @param limitPrice required if type is limit or stop_limit - * @param stopPrice required if type is stop or stop_limit - * @param trailPrice this or trail_percent is required if type is trailing_stop - * @param trailPercent this or trail_price is required if type is trailing_stop - * @param extendedHours (default) false. If true, order will be eligible to execute in premarket/afterhours. - * Only works with type limit and time_in_force day. - * @param clientOrderId A unique identifier for the order. Automatically generated if not sent. - * @param orderClass Simple, bracket, oco, or oto. For details of non-simple order classes, please see - * Bracket Order Overview on the Alpaca Docs. - * @param takeProfitLimitPrice Additional parameter for take-profit leg of advanced orders. Required for bracket - * orders. - * @param stopLossStopPrice Additional parameters for stop-loss leg of advanced orders. Required for bracket - * orders. - * @param stopLossLimitPrice Additional parameters for stop-loss leg of advanced orders. The stop-loss order - * becomes a stop-limit order if specified. - * - * @return the order - * - * @throws AlpacaAPIRequestException the alpaca API exception - * @see Orders - */ - public Order requestNewOrder(String symbol, Integer quantity, OrderSide side, OrderType type, - OrderTimeInForce timeInForce, Double limitPrice, Double stopPrice, Double trailPrice, Double trailPercent, - Boolean extendedHours, String clientOrderId, OrderClass orderClass, Double takeProfitLimitPrice, - Double stopLossStopPrice, Double stopLossLimitPrice) - throws AlpacaAPIRequestException { - Preconditions.checkNotNull(symbol); - Preconditions.checkNotNull(quantity); - Preconditions.checkNotNull(side); - Preconditions.checkNotNull(type); - Preconditions.checkNotNull(timeInForce); - - AlpacaRequestBuilder urlBuilder = new AlpacaRequestBuilder(baseAPIURL, apiVersion, - AlpacaConstants.ORDERS_ENDPOINT); - - urlBuilder.appendJSONBodyProperty(AlpacaConstants.SYMBOL_PARAMETER, symbol); - urlBuilder.appendJSONBodyProperty(AlpacaConstants.QTY_PARAMETER, quantity.toString()); - urlBuilder.appendJSONBodyProperty(AlpacaConstants.SIDE_PARAMETER, side.getAPIName()); - urlBuilder.appendJSONBodyProperty(AlpacaConstants.TYPE_PARAMETER, type.getAPIName()); - urlBuilder.appendJSONBodyProperty(AlpacaConstants.TIME_IN_FORCE_PARAMETER, timeInForce.getAPIName()); - - if (limitPrice != null) { - urlBuilder.appendJSONBodyProperty(AlpacaConstants.LIMIT_PRICE_PARAMETER, - FormatUtil.toDecimalFormat(limitPrice)); - } - - if (stopPrice != null) { - urlBuilder.appendJSONBodyProperty(AlpacaConstants.STOP_PRICE_PARAMETER, - FormatUtil.toDecimalFormat(stopPrice)); - } - - if (trailPrice != null) { - urlBuilder.appendJSONBodyProperty(AlpacaConstants.TRAIL_PRICE_PARAMETER, - FormatUtil.toDecimalFormat(trailPrice)); - } - - if (trailPercent != null) { - urlBuilder.appendJSONBodyProperty(AlpacaConstants.TRAIL_PERCENT_PARAMETER, - FormatUtil.toDecimalFormat(trailPercent)); - } - - if (extendedHours != null) { - urlBuilder.appendJSONBodyJSONProperty(AlpacaConstants.EXTENDED_HOURS_PARAMETER, - new JsonPrimitive(extendedHours)); - } - - if (clientOrderId != null) { - urlBuilder.appendJSONBodyProperty(AlpacaConstants.CLIENT_ORDER_ID_PARAMETER, clientOrderId); - } - - if (orderClass != null) { - urlBuilder.appendJSONBodyProperty(AlpacaConstants.ORDER_CLASS_PARAMETER, orderClass.getAPIName()); - } - - if (takeProfitLimitPrice != null) { - JsonObject takeProfit = new JsonObject(); - takeProfit.addProperty(AlpacaConstants.LIMIT_PRICE_PARAMETER, takeProfitLimitPrice); - - urlBuilder.appendJSONBodyJSONProperty(AlpacaConstants.TAKE_PROFIT_PARAMETER, takeProfit); - } - - if (stopLossStopPrice != null || stopLossLimitPrice != null) { - JsonObject stopLoss = new JsonObject(); - - if (stopLossStopPrice != null) { - stopLoss.addProperty(AlpacaConstants.STOP_PRICE_PARAMETER, stopLossStopPrice); - } - if (stopLossLimitPrice != null) { - stopLoss.addProperty(AlpacaConstants.LIMIT_PRICE_PARAMETER, stopLossLimitPrice); - } - - urlBuilder.appendJSONBodyJSONProperty(AlpacaConstants.STOP_LOSS_PARAMETER, stopLoss); - } - - HttpResponse response = alpacaRequest.invokePost(urlBuilder); - - if (response.getStatus() != 200) { - throw new AlpacaAPIRequestException(response); - } - - return alpacaRequest.getResponseObject(response, Order.class); - } - - /** - * A market order is a request to buy or sell a security at the currently available market price. It provides the - * most likely method of filling an order. Market orders fill nearly instantaneously. This method calls {@link - * #requestNewOrder(String, Integer, OrderSide, OrderType, OrderTimeInForce, Double, Double, Double, Double, - * Boolean, String, OrderClass, Double, Double, Double)} with {@link OrderType#MARKET}. - * - * @param symbol symbol or asset ID to identify the asset to trade - * @param quantity number of shares to trade - * @param side buy or sell - * @param timeInForce day, gtc, opg, cls, ioc, fok. Please see Understand Orders for more info. - * - * @return the order - * - * @throws AlpacaAPIRequestException the alpaca api request exception - * @see Order Types - */ - public Order requestNewMarketOrder(String symbol, Integer quantity, OrderSide side, OrderTimeInForce timeInForce) - throws AlpacaAPIRequestException { - return requestNewOrder(symbol, quantity, side, OrderType.MARKET, timeInForce, null, null, null, null, null, - null, OrderClass.SIMPLE, null, null, null); - } - - /** - * A limit order is an order to buy or sell at a specified price or better. A buy limit order (a limit order to buy) - * is executed at the specified limit price or lower (i.e., better). Conversely, a sell limit order (a limit order - * to sell) is executed at the specified limit price or higher (better). Unlike a market order, you have to specify - * the limit price parameter when submitting your order. This method calls {@link #requestNewOrder(String, Integer, - * OrderSide, OrderType, OrderTimeInForce, Double, Double, Double, Double, Boolean, String, OrderClass, Double, - * Double, Double)} with {@link OrderType#LIMIT}. - * - * @param symbol symbol or asset ID to identify the asset to trade - * @param quantity number of shares to trade - * @param side buy or sell - * @param timeInForce day, gtc, opg, cls, ioc, fok. Please see Understand Orders for more info. - * @param limitPrice required if type is limit or stop_limit - * @param extendedHours (default) false. If true, order will be eligible to execute in premarket/afterhours. Only - * works with type limit and time_in_force day. - * - * @return the order - * - * @throws AlpacaAPIRequestException the alpaca api request exception - * @see Order Types - */ - public Order requestNewLimitOrder(String symbol, Integer quantity, OrderSide side, OrderTimeInForce timeInForce, - Double limitPrice, Boolean extendedHours) throws AlpacaAPIRequestException { - return requestNewOrder(symbol, quantity, side, OrderType.LIMIT, timeInForce, limitPrice, null, null, null, - extendedHours, null, OrderClass.SIMPLE, null, null, null); - } - - /** - * A stop (market) order is an order to buy or sell a security when its price moves past a particular point, - * ensuring a higher probability of achieving a predetermined entry or exit price. Once the market price crosses the - * specified stop price, the stop order becomes a market order. Alpaca converts buy stop orders into stop limit - * orders with a limit price that is 4% higher than a stop price < $50 (or 2.5% higher than a stop price >= - * $50). Sell stop orders are not converted into stop limit orders. This method calls {@link - * #requestNewOrder(String, Integer, OrderSide, OrderType, OrderTimeInForce, Double, Double, Double, Double, - * Boolean, String, OrderClass, Double, Double, Double)} with {@link OrderType#STOP}. - * - * @param symbol symbol or asset ID to identify the asset to trade - * @param quantity number of shares to trade - * @param side buy or sell - * @param timeInForce day, gtc, opg, cls, ioc, fok. Please see Understand Orders for more info. - * @param stopPrice required if type is stop or stop_limit - * @param extendedHours (default) false. If true, order will be eligible to execute in premarket/afterhours. Only - * works with type limit and time_in_force day. - * - * @return the order - * - * @throws AlpacaAPIRequestException the alpaca api request exception - * @see Order Types - */ - public Order requestNewStopOrder(String symbol, Integer quantity, OrderSide side, OrderTimeInForce timeInForce, - Double stopPrice, Boolean extendedHours) throws AlpacaAPIRequestException { - return requestNewOrder(symbol, quantity, side, OrderType.STOP, timeInForce, null, stopPrice, null, null, - extendedHours, null, OrderClass.SIMPLE, null, null, null); - } - - /** - * A stop-limit order is a conditional trade over a set time frame that combines the features of a stop order with - * those of a limit order and is used to mitigate risk. The stop-limit order will be executed at a specified limit - * price, or better, after a given stop price has been reached. Once the stop price is reached, the stop-limit order - * becomes a limit order to buy or sell at the limit price or better. This method calls {@link - * #requestNewOrder(String, Integer, OrderSide, OrderType, OrderTimeInForce, Double, Double, Double, Double, - * Boolean, String, OrderClass, Double, Double, Double)} with {@link OrderType#STOP_LIMIT}. - * - * @param symbol symbol or asset ID to identify the asset to trade - * @param quantity number of shares to trade - * @param side buy or sell - * @param timeInForce day, gtc, opg, cls, ioc, fok. Please see Understand Orders for more info. - * @param limitPrice required if type is limit or stop_limit - * @param stopPrice required if type is stop or stop_limit - * @param extendedHours (default) false. If true, order will be eligible to execute in premarket/afterhours. Only - * works with type limit and time_in_force day. - * - * @return the order - * - * @throws AlpacaAPIRequestException the alpaca api request exception - * @see Order Types - */ - public Order requestNewStopLimitOrder(String symbol, Integer quantity, OrderSide side, OrderTimeInForce timeInForce, - Double limitPrice, Double stopPrice, Boolean extendedHours) throws AlpacaAPIRequestException { - return requestNewOrder(symbol, quantity, side, OrderType.STOP_LIMIT, timeInForce, limitPrice, stopPrice, - null, null, extendedHours, null, OrderClass.SIMPLE, null, null, null); - } - - /** - * A bracket order is a chain of three orders that can be used to manage your position entry and exit. It is a - * common use case of an OTOCO (One Triggers OCO {One Cancels Other}) order. This method calls {@link - * #requestNewOrder(String, Integer, OrderSide, OrderType, OrderTimeInForce, Double, Double, Double, Double, - * Boolean, String, OrderClass, Double, Double, Double)} with {@link OrderType#MARKET} and with parameters for a - * bracket order. - * - * @param symbol symbol or asset ID to identify the asset to trade - * @param quantity number of shares to trade - * @param side buy or sell - * @param timeInForce day, gtc, opg, cls, ioc, fok. Please see Understand Orders for more info. - * @param extendedHours (default) false. If true, order will be eligible to execute in premarket/afterhours. - * Only works with type limit and time_in_force day. - * @param takeProfitLimitPrice Additional parameter for take-profit leg of advanced orders. Required for bracket - * orders. - * @param stopLossStopPrice Additional parameters for stop-loss leg of advanced orders. Required for bracket - * orders. - * @param stopLossLimitPrice Additional parameters for stop-loss leg of advanced orders. The stop-loss order - * becomes a stop-limit order if specified. - * - * @return the order - * - * @throws AlpacaAPIRequestException the alpaca api request exception - * @see Order Types - */ - public Order requestNewMarketBracketOrder(String symbol, Integer quantity, OrderSide side, - OrderTimeInForce timeInForce, Boolean extendedHours, Double takeProfitLimitPrice, Double stopLossStopPrice, - Double stopLossLimitPrice) throws AlpacaAPIRequestException { - return requestNewOrder(symbol, quantity, side, OrderType.MARKET, timeInForce, null, null, null, null, - extendedHours, null, OrderClass.BRACKET, takeProfitLimitPrice, stopLossStopPrice, - stopLossLimitPrice); - } - - /** - * A bracket order is a chain of three orders that can be used to manage your position entry and exit. It is a - * common use case of an OTOCO (One Triggers OCO {One Cancels Other}) order. This method calls {@link - * #requestNewOrder(String, Integer, OrderSide, OrderType, OrderTimeInForce, Double, Double, Double, Double, - * Boolean, String, OrderClass, Double, Double, Double)} with {@link OrderType#LIMIT} and with parameters for a - * bracket order. - * - * @param symbol symbol or asset ID to identify the asset to trade - * @param quantity number of shares to trade - * @param side buy or sell - * @param timeInForce day, gtc, opg, cls, ioc, fok. Please see Understand Orders for more info. - * @param limitPrice required if type is limit or stop_limit - * @param extendedHours (default) false. If true, order will be eligible to execute in premarket/afterhours. - * Only works with type limit and time_in_force day. - * @param takeProfitLimitPrice Additional parameter for take-profit leg of advanced orders. Required for bracket - * orders. - * @param stopLossStopPrice Additional parameters for stop-loss leg of advanced orders. Required for bracket - * orders. - * @param stopLossLimitPrice Additional parameters for stop-loss leg of advanced orders. The stop-loss order - * becomes a stop-limit order if specified. - * - * @return the order - * - * @throws AlpacaAPIRequestException the alpaca api request exception - * @see Order Types - */ - public Order requestNewLimitBracketOrder(String symbol, Integer quantity, OrderSide side, - OrderTimeInForce timeInForce, Double limitPrice, Boolean extendedHours, Double takeProfitLimitPrice, - Double stopLossStopPrice, Double stopLossLimitPrice) throws AlpacaAPIRequestException { - return requestNewOrder(symbol, quantity, side, OrderType.LIMIT, timeInForce, limitPrice, null, null, null, - extendedHours, null, OrderClass.BRACKET, takeProfitLimitPrice, stopLossStopPrice, stopLossLimitPrice); - } - - /** - * OCO (One-Cancels-Other) is another type of advanced order type. This is a set of two orders with the same side - * (buy/buy or sell/sell) and currently only exit order is supported. In other words, this is the second part of the - * bracket orders where the entry order is already filled, and you can submit the take-profit and stop-loss in one - * order submission. This method calls {@link #requestNewOrder(String, Integer, OrderSide, OrderType, - * OrderTimeInForce, Double, Double, Double, Double, Boolean, String, OrderClass, Double, Double, Double)} with - * parameters for an OCO order type. - * - * @param symbol symbol or asset ID to identify the asset to trade - * @param quantity number of shares to trade - * @param side buy or sell - * @param timeInForce day, gtc, opg, cls, ioc, fok. Please see Understand Orders for more info. - * @param extendedHours (default) false. If true, order will be eligible to execute in premarket/afterhours. - * Only works with type limit and time_in_force day. - * @param takeProfitLimitPrice Additional parameter for take-profit leg of advanced orders. - * @param stopLossStopPrice Additional parameters for stop-loss leg of advanced orders. - * @param stopLossLimitPrice Additional parameters for stop-loss leg of advanced orders. The stop-loss order - * becomes a stop-limit order if specified. - * - * @return the order - * - * @throws AlpacaAPIRequestException the alpaca api request exception - * @see Order Types - */ - public Order requestNewOCOOrder(String symbol, Integer quantity, OrderSide side, - OrderTimeInForce timeInForce, Boolean extendedHours, Double takeProfitLimitPrice, - Double stopLossStopPrice, Double stopLossLimitPrice) throws AlpacaAPIRequestException { - return requestNewOrder(symbol, quantity, side, OrderType.LIMIT, timeInForce, null, null, null, null, - extendedHours, null, OrderClass.OCO, takeProfitLimitPrice, stopLossStopPrice, stopLossLimitPrice); - } - - /** - * OTO (One-Triggers-Other) is a variant of bracket order. It takes one of the take-profit or stop-loss order in - * addition to the entry order. This method calls {@link #requestNewOrder(String, Integer, OrderSide, OrderType, - * OrderTimeInForce, Double, Double, Double, Double, Boolean, String, OrderClass, Double, Double, Double)} with - * {@link OrderType#LIMIT} and with parameters for an OTO order type. - * - * @param symbol symbol or asset ID to identify the asset to trade - * @param quantity number of shares to trade - * @param side buy or sell - * @param timeInForce day, gtc, opg, cls, ioc, fok. Please see Understand Orders for more info. - * @param extendedHours (default) false. If true, order will be eligible to execute in premarket/afterhours. - * Only works with type limit and time_in_force day. - * @param takeProfitLimitPrice Additional parameter for take-profit leg of advanced orders. - * @param stopLossStopPrice Additional parameters for stop-loss leg of advanced orders. - * @param stopLossLimitPrice Additional parameters for stop-loss leg of advanced orders. The stop-loss order - * becomes a stop-limit order if specified. - * - * @return the order - * - * @throws AlpacaAPIRequestException the alpaca api request exception - * @see Order Types - */ - public Order requestNewOTOMarketOrder(String symbol, Integer quantity, OrderSide side, - OrderTimeInForce timeInForce, Boolean extendedHours, Double takeProfitLimitPrice, - Double stopLossStopPrice, Double stopLossLimitPrice) throws AlpacaAPIRequestException { - return requestNewOrder(symbol, quantity, side, OrderType.MARKET, timeInForce, null, null, null, null, - extendedHours, null, OrderClass.OTO, takeProfitLimitPrice, stopLossStopPrice, stopLossLimitPrice); - } - - /** - * OTO (One-Triggers-Other) is a variant of bracket order. It takes one of the take-profit or stop-loss order in - * addition to the entry order. This method calls {@link #requestNewOrder(String, Integer, OrderSide, OrderType, - * OrderTimeInForce, Double, Double, Double, Double, Boolean, String, OrderClass, Double, Double, Double)} with - * {@link OrderType#LIMIT} and with parameters for an OTO order type. - * - * @param symbol symbol or asset ID to identify the asset to trade - * @param quantity number of shares to trade - * @param side buy or sell - * @param timeInForce day, gtc, opg, cls, ioc, fok. Please see Understand Orders for more info. - * @param limitPrice required if type is limit or stop_limit - * @param extendedHours (default) false. If true, order will be eligible to execute in premarket/afterhours. - * Only works with type limit and time_in_force day. - * @param takeProfitLimitPrice Additional parameter for take-profit leg of advanced orders. - * @param stopLossStopPrice Additional parameters for stop-loss leg of advanced orders. - * @param stopLossLimitPrice Additional parameters for stop-loss leg of advanced orders. The stop-loss order - * becomes a stop-limit order if specified. - * - * @return the order - * - * @throws AlpacaAPIRequestException the alpaca api request exception - * @see Order Types - */ - public Order requestNewOTOLimitOrder(String symbol, Integer quantity, OrderSide side, - OrderTimeInForce timeInForce, Double limitPrice, Boolean extendedHours, Double takeProfitLimitPrice, - Double stopLossStopPrice, Double stopLossLimitPrice) throws AlpacaAPIRequestException { - return requestNewOrder(symbol, quantity, side, OrderType.LIMIT, timeInForce, limitPrice, null, null, null, - extendedHours, null, OrderClass.OTO, takeProfitLimitPrice, stopLossStopPrice, stopLossLimitPrice); - } - - /** - * OTO (One-Triggers-Other) is a variant of bracket order. It takes one of the take-profit or stop-loss order in - * addition to the entry order. This method calls {@link #requestNewOrder(String, Integer, OrderSide, OrderType, - * OrderTimeInForce, Double, Double, Double, Double, Boolean, String, OrderClass, Double, Double, Double)} with - * {@link OrderType#STOP} and with parameters for an OTO order type. - * - * @param symbol symbol or asset ID to identify the asset to trade - * @param quantity number of shares to trade - * @param side buy or sell - * @param timeInForce day, gtc, opg, cls, ioc, fok. Please see Understand Orders for more info. - * @param stopPrice required if type is stop or stop_limit - * @param extendedHours (default) false. If true, order will be eligible to execute in premarket/afterhours. - * Only works with type limit and time_in_force day. - * @param takeProfitLimitPrice Additional parameter for take-profit leg of advanced orders. - * @param stopLossStopPrice Additional parameters for stop-loss leg of advanced orders. - * @param stopLossLimitPrice Additional parameters for stop-loss leg of advanced orders. The stop-loss order - * becomes a stop-limit order if specified. - * - * @return the order - * - * @throws AlpacaAPIRequestException the alpaca api request exception - * @see Order Types - */ - public Order requestNewOTOStopOrder(String symbol, Integer quantity, OrderSide side, - OrderTimeInForce timeInForce, Double stopPrice, Boolean extendedHours, Double takeProfitLimitPrice, - Double stopLossStopPrice, Double stopLossLimitPrice) throws AlpacaAPIRequestException { - return requestNewOrder(symbol, quantity, side, OrderType.STOP, timeInForce, null, stopPrice, null, null, - extendedHours, null, OrderClass.OTO, takeProfitLimitPrice, stopLossStopPrice, stopLossLimitPrice); - } - - /** - * OTO (One-Triggers-Other) is a variant of bracket order. It takes one of the take-profit or stop-loss order in - * addition to the entry order. This method calls {@link #requestNewOrder(String, Integer, OrderSide, OrderType, - * OrderTimeInForce, Double, Double, Double, Double, Boolean, String, OrderClass, Double, Double, Double)} with - * {@link OrderType#STOP_LIMIT} and with parameters for an OTO order type. - * - * @param symbol symbol or asset ID to identify the asset to trade - * @param quantity number of shares to trade - * @param side buy or sell - * @param timeInForce day, gtc, opg, cls, ioc, fok. Please see Understand Orders for more info. - * @param limitPrice required if type is limit or stop_limit - * @param stopPrice required if type is stop or stop_limit - * @param extendedHours (default) false. If true, order will be eligible to execute in premarket/afterhours. - * Only works with type limit and time_in_force day. - * @param takeProfitLimitPrice Additional parameter for take-profit leg of advanced orders. - * @param stopLossStopPrice Additional parameters for stop-loss leg of advanced orders. - * @param stopLossLimitPrice Additional parameters for stop-loss leg of advanced orders. The stop-loss order - * becomes a stop-limit order if specified. - * - * @return the order - * - * @throws AlpacaAPIRequestException the alpaca api request exception - * @see Order Types - */ - public Order requestNewOTOStopLimitOrder(String symbol, Integer quantity, OrderSide side, - OrderTimeInForce timeInForce, Double limitPrice, Double stopPrice, Boolean extendedHours, - Double takeProfitLimitPrice, Double stopLossStopPrice, Double stopLossLimitPrice) - throws AlpacaAPIRequestException { - return requestNewOrder(symbol, quantity, side, OrderType.STOP_LIMIT, timeInForce, limitPrice, stopPrice, - null, null, extendedHours, null, OrderClass.OTO, takeProfitLimitPrice, - stopLossStopPrice, stopLossLimitPrice); - } - - /** - * Trailing stop orders allow you to continuously and automatically keep updating the stop price threshold based on - * the stock price movement. This method calls {@link #requestNewOrder(String, Integer, OrderSide, OrderType, - * OrderTimeInForce, Double, Double, Double, Double, Boolean, String, OrderClass, Double, Double, Double)} with - * {@link OrderType#TRAILING_STOP} and with parameters for a Trailing Stop Price order type. - * - * @param symbol symbol or asset ID to identify the asset to trade - * @param quantity number of shares to trade - * @param side buy or sell - * @param timeInForce day, gtc, opg, cls, ioc, fok. Please see Understand Orders for more info. - * @param trailPrice this or trail_percent is required if type is trailing_stop - * @param extendedHours (default) false. If true, order will be eligible to execute in premarket/afterhours. Only - * works with type limit and time_in_force day. - * - * @return the order - * - * @throws AlpacaAPIRequestException the alpaca api request exception - * @see Order Types - */ - public Order requestNewTrailingStopPriceOrder(String symbol, Integer quantity, OrderSide side, - OrderTimeInForce timeInForce, Double trailPrice, Boolean extendedHours) throws AlpacaAPIRequestException { - return requestNewOrder(symbol, quantity, side, OrderType.TRAILING_STOP, timeInForce, null, null, - trailPrice, null, extendedHours, null, OrderClass.OTO, null, null, null); - } - - /** - * Trailing stop orders allow you to continuously and automatically keep updating the stop price threshold based on - * the stock price movement. This method calls {@link #requestNewOrder(String, Integer, OrderSide, OrderType, - * OrderTimeInForce, Double, Double, Double, Double, Boolean, String, OrderClass, Double, Double, Double)} with - * {@link OrderType#TRAILING_STOP} and with parameters for a Trailing Stop Percent order type. - * - * @param symbol symbol or asset ID to identify the asset to trade - * @param quantity number of shares to trade - * @param side buy or sell - * @param timeInForce day, gtc, opg, cls, ioc, fok. Please see Understand Orders for more info. - * @param trailPercent this or trail_price is required if type is trailing_stop - * @param extendedHours (default) false. If true, order will be eligible to execute in premarket/afterhours. Only - * works with type limit and time_in_force day. - * - * @return the order - * - * @throws AlpacaAPIRequestException the alpaca api request exception - * @see Order Types - */ - public Order requestNewTrailingStopPercentOrder(String symbol, Integer quantity, OrderSide side, - OrderTimeInForce timeInForce, Double trailPercent, Boolean extendedHours) throws AlpacaAPIRequestException { - return requestNewOrder(symbol, quantity, side, OrderType.TRAILING_STOP, timeInForce, null, null, - null, trailPercent, extendedHours, null, OrderClass.OTO, null, null, null); - } - - /** - * Retrieves a single order for the given order_id. - * - * @param orderID Order ID - * @param nested If true, the result will roll up multi-leg orders under the legs field of primary order. - * - * @return the order - * - * @throws AlpacaAPIRequestException the alpaca API exception - * @see Orders - */ - public Order getOrder(String orderID, Boolean nested) throws AlpacaAPIRequestException { - Preconditions.checkNotNull(orderID); - - AlpacaRequestBuilder urlBuilder = new AlpacaRequestBuilder(baseAPIURL, apiVersion, - AlpacaConstants.ORDERS_ENDPOINT, - orderID); - - if (nested != null) { - urlBuilder.appendURLParameter(AlpacaConstants.NESTED_PARAMETER, nested.toString()); - } - - HttpResponse response = alpacaRequest.invokeGet(urlBuilder); - - if (response.getStatus() != 200) { - throw new AlpacaAPIRequestException(response); - } - - return alpacaRequest.getResponseObject(response, Order.class); - } - - /** - * Retrieves a single order for the given client_order_id. - * - * @param clientOrderId the client order id - * - * @return the order by client id - * - * @throws AlpacaAPIRequestException the alpaca API exception - * @see Orders - */ - public Order getOrderByClientID(String clientOrderId) throws AlpacaAPIRequestException { - Preconditions.checkNotNull(clientOrderId); - - AlpacaRequestBuilder urlBuilder = new AlpacaRequestBuilder(baseAPIURL, apiVersion, - AlpacaConstants.ORDERS_BY_CLIENT_ORDER_ID_ENDPOINT); - urlBuilder.appendURLParameter(AlpacaConstants.CLIENT_ORDER_ID_PARAMETER, clientOrderId); - - HttpResponse response = alpacaRequest.invokeGet(urlBuilder); - - if (response.getStatus() != 200) { - throw new AlpacaAPIRequestException(response); - } - - return alpacaRequest.getResponseObject(response, Order.class); - } - - /** - * Replaces a single order with updated parameters. Each parameter overrides the corresponding attribute of the - * existing order. The other attributes remain the same as the existing order. - * - * @param orderID Order id - * @param quantity number of shares to trade - * @param timeInForce day, gtc, opg, cls, ioc, fok. Please see Understand Orders for more info. - * @param limitPrice required if type is limit or stop_limit - * @param stopPrice required if type is stop or stop_limit - * @param trail the new value of the trail_price or trail_percent value (works only for type=trailing_stop) - * @param clientOrderId A unique identifier for the order. Automatically generated if not sent. - * - * @return the order - * - * @throws AlpacaAPIRequestException the alpaca api request exception - * @see Orders - */ - public Order replaceOrder(String orderID, Integer quantity, OrderTimeInForce timeInForce, Double limitPrice, - Double stopPrice, Double trail, String clientOrderId) throws AlpacaAPIRequestException { - Preconditions.checkNotNull(orderID); - - AlpacaRequestBuilder urlBuilder = new AlpacaRequestBuilder(baseAPIURL, apiVersion, - AlpacaConstants.ORDERS_ENDPOINT, - orderID); - - if (quantity != null) { - urlBuilder.appendJSONBodyProperty(AlpacaConstants.QTY_PARAMETER, quantity.toString()); - } - - if (timeInForce != null) { - urlBuilder.appendJSONBodyProperty(AlpacaConstants.TIME_IN_FORCE_PARAMETER, timeInForce.getAPIName()); - } - - if (limitPrice != null) { - urlBuilder.appendJSONBodyProperty(AlpacaConstants.LIMIT_PRICE_PARAMETER, - FormatUtil.toDecimalFormat(limitPrice)); - } - - if (stopPrice != null) { - urlBuilder.appendJSONBodyProperty(AlpacaConstants.STOP_PRICE_PARAMETER, - FormatUtil.toDecimalFormat(stopPrice)); - } - - if (trail != null) { - urlBuilder.appendJSONBodyProperty(AlpacaConstants.TRAIL_PARAMETER, - FormatUtil.toDecimalFormat(trail)); - } - - if (clientOrderId != null) { - urlBuilder.appendJSONBodyProperty(AlpacaConstants.CLIENT_ORDER_ID_PARAMETER, clientOrderId); - } - - HttpResponse response = alpacaRequest.invokePatch(urlBuilder); - - if (response.getStatus() != 200) { - throw new AlpacaAPIRequestException(response); - } - - return alpacaRequest.getResponseObject(response, Order.class); - } - - /** - * Attempts to cancel all open orders. A response will be provided for each order that is attempted to be cancelled. - * If an order is no longer cancelable, the server will respond with status 500 and reject the request. - * - * @return the array list of cancelled orders - * - * @throws AlpacaAPIRequestException the alpaca api request exception - * @see Orders - */ - public ArrayList cancelAllOrders() throws AlpacaAPIRequestException { - AlpacaRequestBuilder urlBuilder = new AlpacaRequestBuilder(baseAPIURL, apiVersion, - AlpacaConstants.ORDERS_ENDPOINT); - - HttpResponse response = alpacaRequest.invokeDelete(urlBuilder); - - if (response.getStatus() != 207) { // This returns a 207 multi-status message - throw new AlpacaAPIRequestException(response); - } - - Type arrayListType = new TypeToken>() {}.getType(); - - return alpacaRequest.getResponseObject(response, arrayListType); - } - - /** - * Attempts to cancel an open order. If the order is no longer cancelable (example: status=order_filled), the server - * will respond with status 422, and reject the request. - * - * @param orderId Order ID - * - * @return true, if successful - * - * @throws AlpacaAPIRequestException the alpaca API exception - * @see Orders - */ - public boolean cancelOrder(String orderId) throws AlpacaAPIRequestException { - Preconditions.checkNotNull(orderId); - - AlpacaRequestBuilder urlBuilder = new AlpacaRequestBuilder(baseAPIURL, apiVersion, - AlpacaConstants.ORDERS_ENDPOINT, - orderId); - - HttpResponse response = alpacaRequest.invokeDelete(urlBuilder); - - if ((response.getStatus() != 200 && response.getStatus() != 204)) { - throw new AlpacaAPIRequestException(response); - } - - return response.getStatus() == 200 || response.getStatus() == 204; - } - - /** - * Gets the open positions. - * - * @return the open positions - * - * @throws AlpacaAPIRequestException the alpaca API exception - * @see Positions - */ - public ArrayList getOpenPositions() throws AlpacaAPIRequestException { - AlpacaRequestBuilder urlBuilder = new AlpacaRequestBuilder(baseAPIURL, apiVersion, - AlpacaConstants.POSITIONS_ENDPOINT); - - HttpResponse response = alpacaRequest.invokeGet(urlBuilder); - - if (response.getStatus() != 200) { - throw new AlpacaAPIRequestException(response); - } - - Type arrayListType = new TypeToken>() {}.getType(); - - return alpacaRequest.getResponseObject(response, arrayListType); - } - - /** - * Gets the open position by symbol. - * - * @param symbol the symbol or asset_id (required) - * - * @return the open position by symbol - * - * @throws AlpacaAPIRequestException the alpaca API exception - * @see Positions - */ - public Position getOpenPositionBySymbol(String symbol) throws AlpacaAPIRequestException { - Preconditions.checkNotNull(symbol); - - AlpacaRequestBuilder urlBuilder = new AlpacaRequestBuilder(baseAPIURL, apiVersion, - AlpacaConstants.POSITIONS_ENDPOINT, - symbol.trim()); - - HttpResponse response = alpacaRequest.invokeGet(urlBuilder); - - if (response.getStatus() != 200) { - throw new AlpacaAPIRequestException(response); - } - - return alpacaRequest.getResponseObject(response, Position.class); - } - - /** - * Closes (liquidates) all of the account’s open long and short positions. A response will be provided for each - * order that is attempted to be cancelled. If an order is no longer cancelable, the server will respond with status - * 500 and reject the request. - * - * @return a list of closing position orders - * - * @throws AlpacaAPIRequestException the alpaca API exception - * @see Positions - */ - public ArrayList closeAllPositions() throws AlpacaAPIRequestException { - AlpacaRequestBuilder urlBuilder = new AlpacaRequestBuilder(baseAPIURL, apiVersion, - AlpacaConstants.POSITIONS_ENDPOINT); - - HttpResponse response = alpacaRequest.invokeDelete(urlBuilder); - - if (response.getStatus() != 207) { // This returns a 207 multi-status message - throw new AlpacaAPIRequestException(response); - } - - Type arrayListType = new TypeToken>() {}.getType(); - - return alpacaRequest.getResponseObject(response, arrayListType); - } - - /** - * Closes (liquidates) the account’s open position for the given symbol. Works for both long and short positions. - * - * @param symbol symbol or asset_id - * - * @return a closing position order - * - * @throws AlpacaAPIRequestException the alpaca API exception - * @see Positions - */ - public Order closePosition(String symbol) throws AlpacaAPIRequestException { - Preconditions.checkNotNull(symbol); - - AlpacaRequestBuilder urlBuilder = new AlpacaRequestBuilder(baseAPIURL, apiVersion, - AlpacaConstants.POSITIONS_ENDPOINT, - symbol); - - HttpResponse response = alpacaRequest.invokeDelete(urlBuilder); - - if (response.getStatus() != 200) { - throw new AlpacaAPIRequestException(response); - } - - return alpacaRequest.getResponseObject(response, Order.class); - } - - /** - * Get a list of assets. - * - * @param assetStatus e.g. “active”. By default, all statuses are included. - * @param assetClass Defaults to us_equity. - * - * @return the assets - * - * @throws AlpacaAPIRequestException the alpaca API exception - * @see Assets - */ - public ArrayList getAssets(AssetStatus assetStatus, String assetClass) throws AlpacaAPIRequestException { - AlpacaRequestBuilder urlBuilder = new AlpacaRequestBuilder(baseAPIURL, apiVersion, - AlpacaConstants.ASSETS_ENDPOINT); - - if (assetStatus != null) { - urlBuilder.appendURLParameter(AlpacaConstants.STATUS_PARAMETER, assetStatus.getAPIName()); - } - - if (assetClass != null) { - urlBuilder.appendURLParameter(AlpacaConstants.ASSET_CLASS_PARAMETER, assetClass.trim()); - } - - HttpResponse response = alpacaRequest.invokeGet(urlBuilder); - - if (response.getStatus() != 200) { - throw new AlpacaAPIRequestException(response); - } - - Type arrayListType = new TypeToken>() {}.getType(); - - return alpacaRequest.getResponseObject(response, arrayListType); - } - - /** - * Gets the asset by symbol or asset_id. - * - * @param symbolOrAssetID the symbol or asset_id (required) - * - * @return the asset by symbol - * - * @throws AlpacaAPIRequestException the alpaca API exception - * @see Assets - */ - public Asset getAssetBySymbol(String symbolOrAssetID) throws AlpacaAPIRequestException { - Preconditions.checkNotNull(symbolOrAssetID); - - AlpacaRequestBuilder urlBuilder = new AlpacaRequestBuilder(baseAPIURL, apiVersion, - AlpacaConstants.ASSETS_ENDPOINT, - symbolOrAssetID.trim()); - - HttpResponse response = alpacaRequest.invokeGet(urlBuilder); - - if (response.getStatus() != 200) { - throw new AlpacaAPIRequestException(response); - } - - return alpacaRequest.getResponseObject(response, Asset.class); - } - - /** - * Returns the list of watchlists registered under the account. - * - * @return the watch lists - * - * @throws AlpacaAPIRequestException the alpaca api exception - * @see Watchlists - */ - public ArrayList getWatchlists() throws AlpacaAPIRequestException { - AlpacaRequestBuilder urlBuilder = new AlpacaRequestBuilder(baseAPIURL, apiVersion, - AlpacaConstants.WATCHLISTS_ENDPOINT); - - HttpResponse response = alpacaRequest.invokeGet(urlBuilder); - - if (response.getStatus() != 200) { - throw new AlpacaAPIRequestException(response); - } - - Type arrayListType = new TypeToken>() {}.getType(); - return alpacaRequest.getResponseObject(response, arrayListType); - } - - /** - * Create a new watchlist with initial set of assets. - * - * @param name arbitrary name string, up to 64 characters - * @param symbols set of symbol string - * - * @return the created watchlist - * - * @throws AlpacaAPIRequestException the alpaca api exception - * @see Watchlists - */ - public Watchlist createWatchlist(String name, String... symbols) throws AlpacaAPIRequestException { - Preconditions.checkNotNull(name); - Preconditions.checkState(name.length() <= 64); - - AlpacaRequestBuilder urlBuilder = new AlpacaRequestBuilder(baseAPIURL, apiVersion, - AlpacaConstants.WATCHLISTS_ENDPOINT); - - urlBuilder.appendJSONBodyProperty("name", name); - - if (symbols != null) { - JsonArray symbolsArray = new JsonArray(); - Arrays.stream(symbols).forEach(symbolsArray::add); - - urlBuilder.appendJSONBodyJSONProperty("symbols", symbolsArray); - } - - HttpResponse response = alpacaRequest.invokePost(urlBuilder); - - if (response.getStatus() != 200) { - throw new AlpacaAPIRequestException(response); - } - - return alpacaRequest.getResponseObject(response, Watchlist.class); - } - - /** - * Returns a watchlist identified by the ID. - * - * @param watchlistID Watchlist ID - * - * @return the watchlist - * - * @throws AlpacaAPIRequestException the alpaca api exception - * @see Watchlists - */ - public Watchlist getWatchlist(String watchlistID) throws AlpacaAPIRequestException { - Preconditions.checkNotNull(watchlistID); - - AlpacaRequestBuilder urlBuilder = new AlpacaRequestBuilder(baseAPIURL, apiVersion, - AlpacaConstants.WATCHLISTS_ENDPOINT, - watchlistID); - - HttpResponse response = alpacaRequest.invokeGet(urlBuilder); - - if (response.getStatus() != 200) { - throw new AlpacaAPIRequestException(response); - } - - return alpacaRequest.getResponseObject(response, Watchlist.class); - } - - /** - * Update the name and/or content of watchlist. - * - * @param watchlistID Watchlist ID - * @param name the new watchlist name - * @param symbols the new list of symbol names to replace the watchlist content - * - * @return the updated watchlist - * - * @throws AlpacaAPIRequestException the alpaca api exception - * @see Watchlists - */ - public Watchlist updateWatchlist(String watchlistID, String name, String... symbols) - throws AlpacaAPIRequestException { - Preconditions.checkNotNull(watchlistID); - - AlpacaRequestBuilder urlBuilder = new AlpacaRequestBuilder(baseAPIURL, apiVersion, - AlpacaConstants.WATCHLISTS_ENDPOINT, - watchlistID); - - if (name != null) { - urlBuilder.appendJSONBodyProperty("name", name); - } - - if (symbols != null) { - JsonArray symbolsArray = new JsonArray(); - Arrays.stream(symbols).forEach(symbolsArray::add); - - urlBuilder.appendJSONBodyJSONProperty("symbols", symbolsArray); - } - - HttpResponse response = alpacaRequest.invokePut(urlBuilder); - - if (response.getStatus() != 200) { - throw new AlpacaAPIRequestException(response); - } - - return alpacaRequest.getResponseObject(response, Watchlist.class); - } - - /** - * Append an asset for the symbol to the end of watchlist asset list - * - * @param watchlistID Watchlist ID - * @param symbol the symbol name to add to the watchlist - * - * @return the watchlist - * - * @throws AlpacaAPIRequestException the alpaca api exception - * @see Watchlists - */ - public Watchlist addWatchlistAsset(String watchlistID, String symbol) throws AlpacaAPIRequestException { - Preconditions.checkNotNull(watchlistID); - Preconditions.checkNotNull(symbol); - - AlpacaRequestBuilder urlBuilder = new AlpacaRequestBuilder(baseAPIURL, apiVersion, - AlpacaConstants.WATCHLISTS_ENDPOINT, - watchlistID); - - urlBuilder.appendJSONBodyProperty("symbol", symbol); - - HttpResponse response = alpacaRequest.invokePost(urlBuilder); - - if (response.getStatus() != 200) { - throw new AlpacaAPIRequestException(response); - } - - return alpacaRequest.getResponseObject(response, Watchlist.class); - } - - /** - * Delete a watchlist. This is a permanent deletion. - * - * @param watchlistID Watchlist ID - * - * @return if the watchlist was deleted - * - * @throws AlpacaAPIRequestException the alpaca api exception - * @see Watchlists - */ - public boolean deleteWatchlist(String watchlistID) throws AlpacaAPIRequestException { - Preconditions.checkNotNull(watchlistID); - - AlpacaRequestBuilder urlBuilder = new AlpacaRequestBuilder(baseAPIURL, apiVersion, - AlpacaConstants.WATCHLISTS_ENDPOINT, - watchlistID); - - HttpResponse response = alpacaRequest.invokeDelete(urlBuilder); - - if ((response.getStatus() != 200 && response.getStatus() != 204)) { - throw new AlpacaAPIRequestException(response); - } - - return response.getStatus() == 200 || response.getStatus() == 204; - } - - /** - * Delete one entry for an asset by symbol name. - * - * @param watchlistID Watchlist ID - * @param symbol symbol name to remove from the watchlist content - * - * @return the updated watchlist - * - * @throws AlpacaAPIRequestException the alpaca api exception - * @see Watchlists - */ - public Watchlist removeSymbolFromWatchlist(String watchlistID, String symbol) throws AlpacaAPIRequestException { - Preconditions.checkNotNull(watchlistID); - Preconditions.checkNotNull(symbol); - - AlpacaRequestBuilder urlBuilder = new AlpacaRequestBuilder(baseAPIURL, apiVersion, - AlpacaConstants.WATCHLISTS_ENDPOINT, - watchlistID, - symbol); - - HttpResponse response = alpacaRequest.invokeDelete(urlBuilder); - - if (response.getStatus() != 200) { - throw new AlpacaAPIRequestException(response); - } - - return alpacaRequest.getResponseObject(response, Watchlist.class); - } - - /** - * Returns timeseries data about equity and profit/loss (P/L) of the account in requested timespan. - * - * @param periodLength The duration of the data in "number + unit", such as 1D, where unit can be D for day, W for - * week, M for month and A for year. Defaults to 1M. This parameter is the number. - * @param periodUnit The duration of the data in "number + unit", such as 1D, where unit can be D for day, W for - * week, M for month and A for year. Defaults to 1M. This parameter is the unit. - * @param timeFrame The resolution of time window. 1Min, 5Min, 15Min, 1H, or 1D. If omitted, 1Min for less than - * 7 days period, 15Min for less than 30 days, or otherwise 1D. - * @param dateEnd The date the data is returned up to, in "YYYY-MM-DD" format. Defaults to the current market - * date (rolls over at the market open if extended_hours is false, otherwise at 7am ET) - * @param extendedHours If true, include extended hours in the result. This is effective only for timeframe less - * than 1D. - * - * @return the portfolio history - * - * @throws AlpacaAPIRequestException the alpaca api request exception - */ - public PortfolioHistory getPortfolioHistory(Integer periodLength, PortfolioPeriodUnit periodUnit, - PortfolioTimeFrame timeFrame, LocalDate dateEnd, Boolean extendedHours) throws AlpacaAPIRequestException { - AlpacaRequestBuilder urlBuilder = new AlpacaRequestBuilder(baseAPIURL, apiVersion, - AlpacaConstants.ACCOUNT_ENDPOINT, - AlpacaConstants.PORTFOLIO_ENDPOINT, - AlpacaConstants.HISTORY_ENDPOINT); - - if (periodLength != null && periodUnit != null) { - urlBuilder.appendURLParameter(AlpacaConstants.PERIOD_PARAMETER, periodLength + periodUnit.getAPIName()); - } - - if (timeFrame != null) { - urlBuilder.appendURLParameter(AlpacaConstants.TIMEFRAME_PARAMETER, timeFrame.getAPIName()); - } - - if (dateEnd != null) { - urlBuilder.appendURLParameter(AlpacaConstants.DATE_END_PARAMETER, FormatUtil.toDateString(dateEnd)); - } - - if (extendedHours != null) { - urlBuilder.appendURLParameter(AlpacaConstants.EXTENDED_HOURS_PARAMETER, extendedHours.toString()); - } - - HttpResponse response = alpacaRequest.invokeGet(urlBuilder); - - if (response.getStatus() != 200) { - throw new AlpacaAPIRequestException(response); - } - - return alpacaRequest.getResponseObject(response, PortfolioHistory.class); - } - - /** - * Returns the market calendar. - * - * @return the calendar - * - * @throws AlpacaAPIRequestException the alpaca API exception - * @see Calendar - */ - public ArrayList getCalendar() throws AlpacaAPIRequestException { - AlpacaRequestBuilder urlBuilder = new AlpacaRequestBuilder(baseAPIURL, apiVersion, - AlpacaConstants.CALENDAR_ENDPOINT); - - HttpResponse response = alpacaRequest.invokeGet(urlBuilder); - - if (response.getStatus() != 200) { - throw new AlpacaAPIRequestException(response); - } - - Type arrayListType = new TypeToken>() {}.getType(); - - return alpacaRequest.getResponseObject(response, arrayListType); - } - - /** - * Returns the market calendar. - * - * @param start The first date to retrieve data for (inclusive) - * @param end The last date to retrieve data for (inclusive) - * - * @return the calendar - * - * @throws AlpacaAPIRequestException the alpaca API exception - * @see Calendar - */ - public ArrayList getCalendar(LocalDate start, LocalDate end) throws AlpacaAPIRequestException { - AlpacaRequestBuilder urlBuilder = new AlpacaRequestBuilder(baseAPIURL, apiVersion, - AlpacaConstants.CALENDAR_ENDPOINT); - - if (start != null) { - urlBuilder.appendURLParameter(AlpacaConstants.START_PARAMETER, FormatUtil.toDateString(start)); - } - - if (end != null) { - urlBuilder.appendURLParameter(AlpacaConstants.END_PARAMETER, FormatUtil.toDateString(end)); - } - - HttpResponse response = alpacaRequest.invokeGet(urlBuilder); - - if (response.getStatus() != 200) { - throw new AlpacaAPIRequestException(response); - } - - Type arrayListType = new TypeToken>() {}.getType(); - - return alpacaRequest.getResponseObject(response, arrayListType); - } - - /** - * Returns the market clock. - * - * @return the clock - * - * @throws AlpacaAPIRequestException the alpaca API exception - * @see Clock - */ - public Clock getClock() throws AlpacaAPIRequestException { - AlpacaRequestBuilder urlBuilder = new AlpacaRequestBuilder(baseAPIURL, apiVersion, - AlpacaConstants.CLOCK_ENDPOINT); - - HttpResponse response = alpacaRequest.invokeGet(urlBuilder); - - if (response.getStatus() != 200) { - throw new AlpacaAPIRequestException(response); - } - - return alpacaRequest.getResponseObject(response, Clock.class); - } - - /** - * Retrieves a list of bars for each requested symbol. It is guaranteed all bars are in ascending order by time. - *

- * Currently, no “incomplete” bars are returned. For example, a 1 minute bar for 09:30 will not be returned until - * 09:31. - * - * @param timeframe One of minute, 1Min, 5Min, 15Min, day or 1D. minute is an alias of 1Min. Similarly, day is of - * 1D. - * @param symbol One symbol name. - * @param limit The maximum number of bars to be returned for each symbol. It can be between 1 and 1000. Default - * is 100 if parameter is unspecified or 0. - * @param start Filter bars equal to or after this time. Cannot be used with after. - * @param end Filter bars equal to or before this time. Cannot be used with until. - * @param after Filter bars after this time. Cannot be used with start. - * @param until Filter bars before this time. Cannot be used with end. - * - * @return An object with a key for each symbol and the Bars object as the values. Note that it returns status 200 - * with an empty object if no requested symbol is found. - * - * @throws AlpacaAPIRequestException the alpaca API exception - * @see Bars - */ - public Map> getBars(BarsTimeFrame timeframe, String symbol, Integer limit, - ZonedDateTime start, ZonedDateTime end, ZonedDateTime after, ZonedDateTime until) - throws AlpacaAPIRequestException { - return this.getBars(timeframe, new String[]{symbol}, limit, start, end, after, until); - } - - /** - * Retrieves a list of bars for each requested symbol. It is guaranteed all bars are in ascending order by time. - *

- * Currently, no “incomplete” bars are returned. For example, a 1 minute bar for 09:30 will not be returned until - * 09:31. - * - * @param timeframe One of minute, 1Min, 5Min, 15Min, day or 1D. minute is an alias of 1Min. Similarly, day is of - * 1D. - * @param symbols One or more (max 200) symbol names split by commas (“,”). - * @param limit The maximum number of bars to be returned for each symbol. It can be between 1 and 1000. Default - * is 100 if parameter is unspecified or 0. - * @param start Filter bars equal to or after this time. Cannot be used with after. - * @param end Filter bars equal to or before this time. Cannot be used with until. - * @param after Filter bars after this time. Cannot be used with start. - * @param until Filter bars before this time. Cannot be used with end. - * - * @return @return An object with a key for each symbol and the Bars object as the values. Note that it returns - * status 200 with an empty object if no requested symbol is found. - * - * @throws AlpacaAPIRequestException the alpaca API exception - * @see Bars - */ - public Map> getBars(BarsTimeFrame timeframe, String[] symbols, Integer limit, - ZonedDateTime start, ZonedDateTime end, ZonedDateTime after, ZonedDateTime until) - throws AlpacaAPIRequestException { - AlpacaRequestBuilder urlBuilder = new AlpacaRequestBuilder(baseDataUrl, AlpacaConstants.VERSION_1_ENDPOINT, - AlpacaConstants.BARS_ENDPOINT); - - if (timeframe != null) { - urlBuilder.appendEndpoint(timeframe.getAPIName()); - } - - if (symbols != null) { - urlBuilder.appendURLParameter(AlpacaConstants.SYMBOLS_PARAMETER, String.join(",", symbols)); - } - - if (limit != null) { - urlBuilder.appendURLParameter(AlpacaConstants.LIMIT_PARAMETER, limit.toString()); - } - - if (start != null) { - urlBuilder.appendURLParameter(AlpacaConstants.START_PARAMETER, - start.format(DateTimeFormatter.ISO_OFFSET_DATE_TIME)); - } - - if (end != null) { - urlBuilder.appendURLParameter(AlpacaConstants.END_PARAMETER, - end.format(DateTimeFormatter.ISO_OFFSET_DATE_TIME)); - } - - if (after != null) { - urlBuilder.appendURLParameter(AlpacaConstants.AFTER_PARAMETER, - after.format(DateTimeFormatter.ISO_OFFSET_DATE_TIME)); - } - - if (until != null) { - urlBuilder.appendURLParameter(AlpacaConstants.UNTIL_PARAMETER, - until.format(DateTimeFormatter.ISO_OFFSET_DATE_TIME)); - } - - HttpResponse response = alpacaRequest.invokeGet(urlBuilder); - - if (response.getStatus() != 200) { - throw new AlpacaAPIRequestException(response); - } - - Type mapType = new TypeToken>>() {}.getType(); - - return alpacaRequest.getResponseObject(response, mapType); - } - - /** - * Retrieves the last trade for the requested symbol. - * - * @param symbol A stock ticker symbol to retrieve the last trade of - * - * @return the last trade response object - * - * @throws AlpacaAPIRequestException the alpaca api request exception - * @see Last Trade - */ - public LastTradeResponse getLastTrade(String symbol) throws AlpacaAPIRequestException { - Preconditions.checkNotNull(symbol); - - AlpacaRequestBuilder urlBuilder = new AlpacaRequestBuilder(baseDataUrl, AlpacaConstants.VERSION_1_ENDPOINT, - AlpacaConstants.LAST_ENDPOINT, - AlpacaConstants.STOCKS_ENDPOINT, - symbol); - - HttpResponse response = alpacaRequest.invokeGet(urlBuilder); - - if (response.getStatus() != 200) { - throw new AlpacaAPIRequestException(response); - } - - return alpacaRequest.getResponseObject(response, LastTradeResponse.class); - } - - /** - * Retrieves the last quote for the requested symbol. - * - * @param symbol A stock ticker symbol to retrieve the last trade of - * - * @return the last quote response object - * - * @throws AlpacaAPIRequestException the alpaca api request exception - * @see (); } /** - * Instantiates a new Alpaca websocket client. + * Instantiates a new AlpacaWebsocketClient. * - * @param token the auth token + * @param oAuthToken the OAuth token * @param baseAPIURL the base API URL */ - public AlpacaWebsocketClient(String token, String baseAPIURL) { - this.auth_token = token; + public AlpacaWebsocketClient(String oAuthToken, String baseAPIURL) { this.keyId = null; this.secret = null; + this.oAuthToken = oAuthToken; this.streamAPIURL = baseAPIURL.replace("https", "wss") + "/stream"; this.listeners = new ArrayList<>(); @@ -166,8 +166,8 @@ public void sendAuthenticationMessage() { JsonObject payload = new JsonObject(); - if (keyId == null && secret == null && auth_token != null) { - payload.addProperty("oauth_token", auth_token); + if (oAuthToken != null) { + payload.addProperty("oauth_token", oAuthToken); } else { payload.addProperty("key_id", keyId); payload.addProperty("secret_key", secret); diff --git a/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/client/MarketDataWebsocketClient.java b/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/client/MarketDataWebsocketClient.java index 30000013..f2636d7e 100644 --- a/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/client/MarketDataWebsocketClient.java +++ b/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/client/MarketDataWebsocketClient.java @@ -74,8 +74,8 @@ public class MarketDataWebsocketClient implements WebsocketClient { /** The secret. */ private final String secret; - /** The auth token. */ - private final String auth_token; + /** The OAuth token. */ + private final String oAuthToken; /** The Stream API URL. */ private final String streamAPIURL; @@ -90,7 +90,7 @@ public class MarketDataWebsocketClient implements WebsocketClient { private boolean authenticated; /** - * Instantiates a new Market data websocket client. + * Instantiates a new MarketDataWebsocketClient. * * @param keyId the key id * @param secret the secret @@ -99,22 +99,22 @@ public class MarketDataWebsocketClient implements WebsocketClient { public MarketDataWebsocketClient(String keyId, String secret, String baseDataUrl) { this.keyId = keyId; this.secret = secret; - this.auth_token = null; + this.oAuthToken = null; this.streamAPIURL = baseDataUrl.replace("https", "wss") + "/stream"; this.listeners = new ArrayList<>(); } /** - * Instantiates a new Market data websocket client. + * Instantiates a new MarketDataWebsocketClient. * - * @param token the auth token + * @param oAuthToken the OAuth token * @param baseDataUrl the base data url */ - public MarketDataWebsocketClient(String token, String baseDataUrl) { - this.auth_token = token; + public MarketDataWebsocketClient(String oAuthToken, String baseDataUrl) { this.keyId = null; this.secret = null; + this.oAuthToken = oAuthToken; this.streamAPIURL = baseDataUrl.replace("https", "wss") + "/stream"; this.listeners = new ArrayList<>(); @@ -190,9 +190,8 @@ public void sendAuthenticationMessage() { authRequest.addProperty("action", "authenticate"); JsonObject dataJson = new JsonObject(); - if (keyId == null && secret == null && auth_token != null) { - dataJson.addProperty("oauth_token", auth_token); - + if (oAuthToken != null) { + dataJson.addProperty("oauth_token", oAuthToken); } else { dataJson.addProperty("key_id", keyId); dataJson.addProperty("secret_key", secret); From 1882f5e481fa173d4cc5cb86e300ab3240c6748e Mon Sep 17 00:00:00 2001 From: Jacob Peterson Date: Mon, 9 Nov 2020 00:21:57 -0700 Subject: [PATCH 056/271] Format Polygon javadoc a bit Note that the PolygonAPI does not need any modifications for OAuth as Alpaca's OAuth handles the generation of a PolygonAPI keyID --- .../net/jacobpeterson/polygon/PolygonAPI.java | 24 +++++++++---------- 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/src/main/java/net/jacobpeterson/polygon/PolygonAPI.java b/src/main/java/net/jacobpeterson/polygon/PolygonAPI.java index a06b8fad..442a4395 100644 --- a/src/main/java/net/jacobpeterson/polygon/PolygonAPI.java +++ b/src/main/java/net/jacobpeterson/polygon/PolygonAPI.java @@ -61,23 +61,23 @@ public class PolygonAPI { /** The logger. */ private static final Logger LOGGER = LogManager.getLogger(PolygonAPI.class); - /** The Polygon websocket client. */ + /** The PolygonWebsocketClient. */ private final PolygonWebsocketClient polygonWebsocketClient; /** The polygon request. */ private final PolygonRequest polygonRequest; - /** The base api url. */ + /** The base API URL. */ private final String baseAPIURL; - /** The Websocket url. */ + /** The Websocket URL. */ private final String websocketURL; - /** The Key id. */ + /** The Key ID. */ private final String keyID; /** - * Instantiates a new polygon API. + * Instantiates a new PolygonAPI. */ public PolygonAPI() { this(PolygonProperties.KEY_ID_VALUE); @@ -86,20 +86,20 @@ public PolygonAPI() { } /** - * Instantiates a new polygon API. + * Instantiates a new PolygonAPI. * - * @param keyId the key id + * @param keyID the key ID */ - public PolygonAPI(String keyId) { - this(PolygonProperties.BASE_API_URL_VALUE, PolygonProperties.POLYGON_WEB_SOCKET_SERVER_URL_VALUE, keyId); + public PolygonAPI(String keyID) { + this(PolygonProperties.BASE_API_URL_VALUE, PolygonProperties.POLYGON_WEB_SOCKET_SERVER_URL_VALUE, keyID); } /** * Instantiates a new polygon API. * - * @param baseAPIURL the base api url - * @param websocketURL the websocket url - * @param keyID the key id + * @param baseAPIURL the base API URL + * @param websocketURL the websocket URL + * @param keyID the key ID */ public PolygonAPI(String baseAPIURL, String websocketURL, String keyID) { this.baseAPIURL = baseAPIURL; From ae71eadaca00cf7521a33956b775319df5a965ff Mon Sep 17 00:00:00 2001 From: Jacob Peterson Date: Sat, 21 Nov 2020 15:57:23 -0700 Subject: [PATCH 057/271] Change trade ID to Long type and fix typo --- build.gradle | 2 +- .../alpaca/market_data/streaming/trade/trade_message.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/build.gradle b/build.gradle index e8398c88..41d16af3 100644 --- a/build.gradle +++ b/build.gradle @@ -144,7 +144,7 @@ processResources { // // START allJar task // -// Used for creating a far jar (a jar packaged with dependencies) +// Used for creating a fat jar (a jar packaged with dependencies) task allJar(type: Jar) { from { configurations.compile.collect { it.isDirectory() ? it : zipTree(it) } diff --git a/schema_json/alpaca/market_data/streaming/trade/trade_message.json b/schema_json/alpaca/market_data/streaming/trade/trade_message.json index 9cb9742c..59a4735b 100644 --- a/schema_json/alpaca/market_data/streaming/trade/trade_message.json +++ b/schema_json/alpaca/market_data/streaming/trade/trade_message.json @@ -6,7 +6,7 @@ "title": "See https://alpaca.markets/docs/api-documentation/api-v2/market-data/streaming/", "properties": { "i": { - "existingJavaType": "java.lang.Integer", + "existingJavaType": "java.lang.Long", "title": "Trade ID" }, "x": { From 54f7b6e82a99fecfcef1c156868b8ffffbe80c5a Mon Sep 17 00:00:00 2001 From: Jacob Peterson Date: Sat, 21 Nov 2020 15:59:43 -0700 Subject: [PATCH 058/271] Update to 6.0.1 --- README.md | 10 +++++----- build.gradle | 2 +- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 16c339e3..7ff96d83 100644 --- a/README.md +++ b/README.md @@ -36,7 +36,7 @@ Add the following dependency to your build.gradle file: ``` dependencies { - compile "net.jacobpeterson:alpaca-java:6.0" + compile "net.jacobpeterson:alpaca-java:6.0.1" } ``` @@ -48,7 +48,7 @@ Add the following dependency to your pom.xml file: net.jacobpeterson alpaca-java - 6.0 + 6.0.1 compile ``` @@ -65,12 +65,12 @@ that you use in your trading algorithm projects is indeed legitimate, we need to with the Jar MD5 checksum from this source code: 1. Clone the repo somewhere: `git clone https://github.com/Petersoj/alpaca-java` 2. `cd alpaca-java` -3. Checkout/reset to the latest version: `git reset 6.0 --hard` +3. Checkout/reset to the latest version: `git reset 6.0.1 --hard` 4. Build the project: `./gradlew build` 1. Note: if you build the project twice without a `clean` task in between (e.g. `./gradlew build; ./gradlew build`), the MD5 checksum will change. -5. Generate the MD5 checksum: `md5 build/libs/alpaca-java-6.0.jar` +5. Generate the MD5 checksum: `md5 build/libs/alpaca-java-6.0.1.jar` 6. Confirm that the checksum value in Maven Central is the same as in step 4: -`curl https://repo1.maven.org/maven2/net/jacobpeterson/alpaca-java/6.0/alpaca-java-6.0.jar.md5` +`curl https://repo1.maven.org/maven2/net/jacobpeterson/alpaca-java/6.0.1/alpaca-java-6.0.1.jar.md5` If the checksums are different, please [create a new issue](https://github.com/Petersoj/alpaca-java/issues/new)! diff --git a/build.gradle b/build.gradle index 41d16af3..78bfc3ec 100644 --- a/build.gradle +++ b/build.gradle @@ -26,7 +26,7 @@ tasks.getByName("generateJsonSchema2Pojo").enabled(false) // Disable default jso archivesBaseName = 'alpaca-java' group = 'net.jacobpeterson' -version = '6.0' +version = '6.0.1' repositories { mavenLocal() From 8aeb620fa90a5422a75cdecedeaee2496ba1e085 Mon Sep 17 00:00:00 2001 From: Jacob Peterson Date: Fri, 11 Dec 2020 10:57:43 -0700 Subject: [PATCH 059/271] Update README --- README.md | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 7ff96d83..cf17bd63 100644 --- a/README.md +++ b/README.md @@ -5,7 +5,7 @@

## Overview -This is a Java implementation for Alpaca. Alpaca lets you build and trade with real-time market data for free. This library is community developed and if you have any questions, please ask them on the [Alpaca Slack #dev-alpaca-java channel](https://alpaca.markets/slack) or on the [Alpaca Forums](https://forum.alpaca.markets/). +This is a Java implementation for the Alpaca API. Alpaca lets you build and trade with real-time market data for free. This library is community developed and if you have any questions, please ask them on [Github Discussions](https://github.com/Petersoj/alpaca-java/discussions), the [Alpaca Slack #dev-alpaca-java channel](https://alpaca.markets/slack), or on the [Alpaca Forums](https://forum.alpaca.markets/). ## Table of Contents 1. [Building](#building) @@ -67,12 +67,14 @@ with the Jar MD5 checksum from this source code: 2. `cd alpaca-java` 3. Checkout/reset to the latest version: `git reset 6.0.1 --hard` 4. Build the project: `./gradlew build` - 1. Note: if you build the project twice without a `clean` task in between (e.g. `./gradlew build; ./gradlew build`), the MD5 checksum will change. + 1. If you build the project twice without a `clean` task in between (e.g. `./gradlew build; ./gradlew build`), the MD5 checksum will change. + 2. Use Java 8 (JDK 1.8) to build, otherwise the MD5 checksums will differ. 5. Generate the MD5 checksum: `md5 build/libs/alpaca-java-6.0.1.jar` 6. Confirm that the checksum value in Maven Central is the same as in step 4: `curl https://repo1.maven.org/maven2/net/jacobpeterson/alpaca-java/6.0.1/alpaca-java-6.0.1.jar.md5` If the checksums are different, please [create a new issue](https://github.com/Petersoj/alpaca-java/issues/new)! +The current MD5 checksum of build `6.0.1` is: `425532e598e3d812b29a3270cf4c5e73` ## Configuration From 3e6661e60de2dc8d9f52d8fc4df62f2ee2c0b329 Mon Sep 17 00:00:00 2001 From: Jacob Peterson Date: Fri, 11 Dec 2020 11:55:51 -0700 Subject: [PATCH 060/271] Modify README, format Javadocs, fix request order bug --- README.md | 10 +- .../abstracts/rest/AbstractRequest.java | 2 +- .../rest/AbstractRequestBuilder.java | 2 +- .../AbstractAPIRequestException.java | 4 +- .../AbstractWebsocketClientEndpoint.java | 4 +- .../websocket/client/WebsocketClient.java | 2 +- .../exception/WebsocketException.java | 8 +- .../websocket/listener/StreamListener.java | 2 +- .../websocket/message/StreamMessage.java | 2 +- .../websocket/message/StreamMessageType.java | 2 +- .../net/jacobpeterson/alpaca/AlpacaAPI.java | 112 ++++++++---------- .../jacobpeterson/alpaca/AlpacaConstants.java | 3 +- .../alpaca/enums/AccountStatus.java | 33 ++++-- .../alpaca/enums/ActivityType.java | 5 +- .../alpaca/enums/AssetStatus.java | 10 +- .../alpaca/enums/BarsTimeFrame.java | 14 +-- .../jacobpeterson/alpaca/enums/Direction.java | 10 +- .../alpaca/enums/OrderClass.java | 16 ++- .../jacobpeterson/alpaca/enums/OrderSide.java | 10 +- .../alpaca/enums/OrderStatus.java | 12 +- .../alpaca/enums/OrderTimeInForce.java | 47 ++++++-- .../jacobpeterson/alpaca/enums/OrderType.java | 43 +++++-- .../alpaca/enums/PortfolioPeriodUnit.java | 14 +-- .../alpaca/enums/PortfolioTimeFrame.java | 13 +- .../alpaca/properties/AlpacaProperties.java | 10 +- .../alpaca/rest/AlpacaRequest.java | 6 +- .../alpaca/rest/AlpacaRequestBuilder.java | 4 +- .../exception/AlpacaAPIRequestException.java | 4 +- .../broker/client/AlpacaWebsocketClient.java | 6 +- .../client/AlpacaWebsocketClientEndpoint.java | 4 +- .../listener/AlpacaStreamListenerAdapter.java | 4 +- .../message/AlpacaStreamMessageType.java | 14 +-- .../client/MarketDataWebsocketClient.java | 6 +- .../MarketDataWebsocketClientEndpoint.java | 4 +- .../MarketDataStreamListenerAdapter.java | 8 +- .../message/MarketDataStreamMessageType.java | 14 +-- src/main/resources/alpaca.properties.default | 2 - src/main/resources/polygon.properties.default | 3 +- 38 files changed, 257 insertions(+), 212 deletions(-) diff --git a/README.md b/README.md index cf17bd63..ba1b8810 100644 --- a/README.md +++ b/README.md @@ -78,9 +78,9 @@ The current MD5 checksum of build `6.0.1` is: `425532e598e3d812b29a3270cf4c5e73` ## Configuration -If you plan on using the alpaca.properties, set the following properties in an alpaca.properties file on the classpath: +Creating an `alpaca.properties` file on the classpath of the following format allows you to easily load various properties +using the `AlpacaAPI()` default constructor: ``` -api_version = key_id = secret = base_api_url = https://paper-api.alpaca.markets @@ -89,10 +89,10 @@ user_agent=a_user_agent ``` The default values for `alpaca.properties` can be found [here](https://github.com/Petersoj/alpaca-java/tree/master/src/main/resources). -Similarly, set the following properties in a polygon.properties file on the classpath for using the PolygonAPI: +Similarly, creating a `polygon.properties` file on the classpath of the following format allows you to easily load various properties +using the `PolygonAPI()` default constructor: ``` -#key_id will default to what is set in alpaca.properties -key_id = +key_id = (Note that this will default to what is set in alpaca.properties) base_api_url = https://api.polygon.io web_socket_server_url = wss://alpaca.socket.polygon.io/stocks user_agent=a_user_agent diff --git a/src/main/java/net/jacobpeterson/abstracts/rest/AbstractRequest.java b/src/main/java/net/jacobpeterson/abstracts/rest/AbstractRequest.java index eb4d87e5..36f3a817 100644 --- a/src/main/java/net/jacobpeterson/abstracts/rest/AbstractRequest.java +++ b/src/main/java/net/jacobpeterson/abstracts/rest/AbstractRequest.java @@ -18,7 +18,7 @@ import java.util.Map; /** - * The type Abstract request. + * The type {@link AbstractRequest}. */ public abstract class AbstractRequest { diff --git a/src/main/java/net/jacobpeterson/abstracts/rest/AbstractRequestBuilder.java b/src/main/java/net/jacobpeterson/abstracts/rest/AbstractRequestBuilder.java index 81e8f407..17917971 100644 --- a/src/main/java/net/jacobpeterson/abstracts/rest/AbstractRequestBuilder.java +++ b/src/main/java/net/jacobpeterson/abstracts/rest/AbstractRequestBuilder.java @@ -9,7 +9,7 @@ import java.util.Map; /** - * The type Abstract request builder. + * The type {@link AbstractRequestBuilder}. */ public abstract class AbstractRequestBuilder { diff --git a/src/main/java/net/jacobpeterson/abstracts/rest/exception/AbstractAPIRequestException.java b/src/main/java/net/jacobpeterson/abstracts/rest/exception/AbstractAPIRequestException.java index 34175106..59388c7e 100644 --- a/src/main/java/net/jacobpeterson/abstracts/rest/exception/AbstractAPIRequestException.java +++ b/src/main/java/net/jacobpeterson/abstracts/rest/exception/AbstractAPIRequestException.java @@ -9,7 +9,7 @@ import java.io.InputStreamReader; /** - * The type Abstract API request exception. + * The type {@link AbstractAPIRequestException}. */ public abstract class AbstractAPIRequestException extends Exception { @@ -41,7 +41,7 @@ public abstract class AbstractAPIRequestException extends Exception { protected String apiResponseMessage; /** - * Instantiates a new Abstract api request exception. + * Instantiates a new {@link AbstractAPIRequestException}. * * @param apiName the api name * @param httpResponse the http response diff --git a/src/main/java/net/jacobpeterson/abstracts/websocket/client/AbstractWebsocketClientEndpoint.java b/src/main/java/net/jacobpeterson/abstracts/websocket/client/AbstractWebsocketClientEndpoint.java index f9ccdd5a..4b8a8a12 100644 --- a/src/main/java/net/jacobpeterson/abstracts/websocket/client/AbstractWebsocketClientEndpoint.java +++ b/src/main/java/net/jacobpeterson/abstracts/websocket/client/AbstractWebsocketClientEndpoint.java @@ -15,7 +15,7 @@ import java.util.concurrent.ExecutorService; /** - * The type Abstract websocket client endpoint. + * The type {@link AbstractWebsocketClientEndpoint}. *
* NOTES: You MUST annotate a subclass with {@link javax.websocket.ClientEndpoint} and the appropriate websocket * subprotocols because websocket annotations don't work with inheritance. The subclass must also contain separate @@ -49,7 +49,7 @@ public abstract class AbstractWebsocketClientEndpoint { private Session userSession; /** - * Instantiates a new Abstract websocket client endpoint. + * Instantiates a new {@link AbstractWebsocketClientEndpoint}. * * @param websocketClient the websocket client * @param endpointURI the endpoint uri diff --git a/src/main/java/net/jacobpeterson/abstracts/websocket/client/WebsocketClient.java b/src/main/java/net/jacobpeterson/abstracts/websocket/client/WebsocketClient.java index a25ccb55..aae14f1f 100644 --- a/src/main/java/net/jacobpeterson/abstracts/websocket/client/WebsocketClient.java +++ b/src/main/java/net/jacobpeterson/abstracts/websocket/client/WebsocketClient.java @@ -6,7 +6,7 @@ import net.jacobpeterson.abstracts.websocket.message.StreamMessageType; /** - * The type Websocket client. + * The type {@link WebsocketClient}. */ public interface WebsocketClient { diff --git a/src/main/java/net/jacobpeterson/abstracts/websocket/exception/WebsocketException.java b/src/main/java/net/jacobpeterson/abstracts/websocket/exception/WebsocketException.java index e9581037..a2b0511e 100644 --- a/src/main/java/net/jacobpeterson/abstracts/websocket/exception/WebsocketException.java +++ b/src/main/java/net/jacobpeterson/abstracts/websocket/exception/WebsocketException.java @@ -3,12 +3,12 @@ import java.io.IOException; /** - * The type WebsocketException. + * The type {@link WebsocketException}. */ public class WebsocketException extends IOException { /** - * Instantiates a new WebsocketException. + * Instantiates a new {@link WebsocketException}. * * @param message the message */ @@ -17,7 +17,7 @@ public WebsocketException(String message) { } /** - * Instantiates a new WebsocketException. + * Instantiates a new {@link WebsocketException}. * * @param message the message * @param cause the cause @@ -27,7 +27,7 @@ public WebsocketException(String message, Throwable cause) { } /** - * Instantiates a new WebsocketException. + * Instantiates a new {@link WebsocketException}. * * @param exception the Exception */ diff --git a/src/main/java/net/jacobpeterson/abstracts/websocket/listener/StreamListener.java b/src/main/java/net/jacobpeterson/abstracts/websocket/listener/StreamListener.java index 62f728da..717bcc56 100644 --- a/src/main/java/net/jacobpeterson/abstracts/websocket/listener/StreamListener.java +++ b/src/main/java/net/jacobpeterson/abstracts/websocket/listener/StreamListener.java @@ -4,7 +4,7 @@ import net.jacobpeterson.abstracts.websocket.message.StreamMessageType; /** - * The type Stream listener. + * The type {@link StreamListener}. * * @param the stream message type type parameter * @param the stream message type parameter diff --git a/src/main/java/net/jacobpeterson/abstracts/websocket/message/StreamMessage.java b/src/main/java/net/jacobpeterson/abstracts/websocket/message/StreamMessage.java index f0721826..1d831690 100644 --- a/src/main/java/net/jacobpeterson/abstracts/websocket/message/StreamMessage.java +++ b/src/main/java/net/jacobpeterson/abstracts/websocket/message/StreamMessage.java @@ -1,7 +1,7 @@ package net.jacobpeterson.abstracts.websocket.message; /** - * The type stream message. + * The type {@link StreamMessage}. */ public interface StreamMessage { } diff --git a/src/main/java/net/jacobpeterson/abstracts/websocket/message/StreamMessageType.java b/src/main/java/net/jacobpeterson/abstracts/websocket/message/StreamMessageType.java index 0f808b72..cd649070 100644 --- a/src/main/java/net/jacobpeterson/abstracts/websocket/message/StreamMessageType.java +++ b/src/main/java/net/jacobpeterson/abstracts/websocket/message/StreamMessageType.java @@ -1,7 +1,7 @@ package net.jacobpeterson.abstracts.websocket.message; /** - * The type stream message type. + * The type {@link StreamMessageType}. */ public interface StreamMessageType { } diff --git a/src/main/java/net/jacobpeterson/alpaca/AlpacaAPI.java b/src/main/java/net/jacobpeterson/alpaca/AlpacaAPI.java index 61219ce6..7fe2a243 100644 --- a/src/main/java/net/jacobpeterson/alpaca/AlpacaAPI.java +++ b/src/main/java/net/jacobpeterson/alpaca/AlpacaAPI.java @@ -61,16 +61,15 @@ import java.util.stream.Collectors; /** - * The class AlpacaAPI. Note that most of these methods are blocking methods and this class in NOT thread-safe. + * The {@link AlpacaAPI} class. This class contains methods to interface with the Alpaca API. + *
+ * Note that most of these methods are blocking methods and this class in NOT thread-safe. */ public class AlpacaAPI { /** The logger. */ private static final Logger LOGGER = LogManager.getLogger(AlpacaAPI.class); - /** The version. */ - private final String apiVersion; - /** The key ID. */ private final String keyID; @@ -90,65 +89,60 @@ public class AlpacaAPI { private final MarketDataWebsocketClient marketDataWebSocketClient; /** - * Instantiates a new Alpaca API using properties specified in alpaca.properties file (or their associated + * Instantiates a new {@link AlpacaAPI} using properties specified in alpaca.properties file (or their associated * defaults) */ public AlpacaAPI() { - this(AlpacaProperties.API_VERSION_VALUE, AlpacaProperties.KEY_ID_VALUE, - AlpacaProperties.SECRET_VALUE, null, AlpacaProperties.BASE_API_URL_VALUE, + this(AlpacaProperties.KEY_ID_VALUE, AlpacaProperties.SECRET_VALUE, null, AlpacaProperties.BASE_API_URL_VALUE, AlpacaProperties.BASE_DATA_URL_VALUE); LOGGER.debug(AlpacaProperties.staticToString()); } /** - * Instantiates a new AlpacaAPI using the specified keyID and secret. + * Instantiates a new {@link AlpacaAPI} using the specified keyID and secret. * * @param keyID the key ID * @param secret the secret */ public AlpacaAPI(String keyID, String secret) { - this(AlpacaProperties.API_VERSION_VALUE, keyID, secret, null, AlpacaProperties.BASE_API_URL_VALUE, - AlpacaProperties.BASE_DATA_URL_VALUE); + this(keyID, secret, null, AlpacaProperties.BASE_API_URL_VALUE, AlpacaProperties.BASE_DATA_URL_VALUE); } /** - * Instantiates a new AlpacaAPI using the specified keyID, secret, and baseAPIURL. + * Instantiates a new {@link AlpacaAPI} using the specified keyID, secret, and baseAPIURL. * * @param keyID the key ID * @param secret the secret * @param baseAPIURL the api account url */ public AlpacaAPI(String keyID, String secret, String baseAPIURL) { - this(AlpacaProperties.API_VERSION_VALUE, keyID, secret, null, baseAPIURL, AlpacaProperties.BASE_DATA_URL_VALUE); + this(keyID, secret, null, baseAPIURL, AlpacaProperties.BASE_DATA_URL_VALUE); } /** - * Instantiates a new AlpacaAPI using the specified OAuth token. + * Instantiates a new {@link AlpacaAPI} using the specified OAuth token. * * @param oAuthToken the OAuth token */ public AlpacaAPI(String oAuthToken) { - this(AlpacaProperties.API_VERSION_VALUE, null, null, oAuthToken, AlpacaProperties.BASE_API_URL_VALUE, - AlpacaProperties.BASE_DATA_URL_VALUE); + this(null, null, oAuthToken, AlpacaProperties.BASE_API_URL_VALUE, AlpacaProperties.BASE_DATA_URL_VALUE); } /** - * Instantiates a new AlpacaAPI using the specified apiVersion, keyId, secret, OAuth token (if there is one), - * baseAPIURL, and baseDataURL. + * Instantiates a new {@link AlpacaAPI} using the specified AlpacaConstants.VERSION_2_ENDPOINT, keyId, secret, OAuth + * token (if there is one), baseAPIURL, and baseDataURL. * - * @param apiVersion the api version * @param keyID the key ID * @param secret the secret * @param oAuthToken the OAuth token * @param baseAPIURL the base API URL * @param baseDataURL the base data URL */ - public AlpacaAPI(String apiVersion, String keyID, String secret, String oAuthToken, String baseAPIURL, + public AlpacaAPI(String keyID, String secret, String oAuthToken, String baseAPIURL, String baseDataURL) { Preconditions.checkState((keyID != null && secret != null) || oAuthToken != null); - this.apiVersion = apiVersion; this.keyID = keyID; this.baseAPIURL = baseAPIURL; this.baseDataUrl = baseDataURL; @@ -174,7 +168,7 @@ public AlpacaAPI(String apiVersion, String keyID, String secret, String oAuthTok * @see Get the Account */ public Account getAccount() throws AlpacaAPIRequestException { - AlpacaRequestBuilder urlBuilder = new AlpacaRequestBuilder(baseAPIURL, apiVersion, + AlpacaRequestBuilder urlBuilder = new AlpacaRequestBuilder(baseAPIURL, AlpacaConstants.VERSION_2_ENDPOINT, AlpacaConstants.ACCOUNT_ENDPOINT); HttpResponse response = alpacaRequest.invokeGet(urlBuilder); @@ -290,7 +284,7 @@ public ArrayList getAccountActivities(ZonedDateTime date, Zoned * Configuration */ public AccountConfiguration getAccountConfiguration() throws AlpacaAPIRequestException { - AlpacaRequestBuilder urlBuilder = new AlpacaRequestBuilder(baseAPIURL, apiVersion, + AlpacaRequestBuilder urlBuilder = new AlpacaRequestBuilder(baseAPIURL, AlpacaConstants.VERSION_2_ENDPOINT, AlpacaConstants.ACCOUNT_ENDPOINT, AlpacaConstants.CONFIGURATIONS_ENDPOINT); @@ -318,7 +312,7 @@ public AccountConfiguration setAccountConfiguration(AccountConfiguration account throws AlpacaAPIRequestException { Preconditions.checkNotNull(accountConfiguration); - AlpacaRequestBuilder urlBuilder = new AlpacaRequestBuilder(baseAPIURL, apiVersion, + AlpacaRequestBuilder urlBuilder = new AlpacaRequestBuilder(baseAPIURL, AlpacaConstants.VERSION_2_ENDPOINT, AlpacaConstants.ACCOUNT_ENDPOINT, AlpacaConstants.CONFIGURATIONS_ENDPOINT); urlBuilder.setCustomBody(GsonUtil.GSON.toJson(accountConfiguration)); @@ -350,7 +344,7 @@ public AccountConfiguration setAccountConfiguration(AccountConfiguration account */ public ArrayList getOrders(OrderStatus status, Integer limit, ZonedDateTime after, ZonedDateTime until, Direction direction, Boolean nested) throws AlpacaAPIRequestException { - AlpacaRequestBuilder urlBuilder = new AlpacaRequestBuilder(baseAPIURL, apiVersion, + AlpacaRequestBuilder urlBuilder = new AlpacaRequestBuilder(baseAPIURL, AlpacaConstants.VERSION_2_ENDPOINT, AlpacaConstants.ORDERS_ENDPOINT); if (status != null) { @@ -432,7 +426,7 @@ public Order requestNewOrder(String symbol, Integer quantity, OrderSide side, Or Preconditions.checkNotNull(type); Preconditions.checkNotNull(timeInForce); - AlpacaRequestBuilder urlBuilder = new AlpacaRequestBuilder(baseAPIURL, apiVersion, + AlpacaRequestBuilder urlBuilder = new AlpacaRequestBuilder(baseAPIURL, AlpacaConstants.VERSION_2_ENDPOINT, AlpacaConstants.ORDERS_ENDPOINT); urlBuilder.appendJSONBodyProperty(AlpacaConstants.SYMBOL_PARAMETER, symbol); @@ -619,8 +613,6 @@ public Order requestNewStopLimitOrder(String symbol, Integer quantity, OrderSide * @param quantity number of shares to trade * @param side buy or sell * @param timeInForce day, gtc, opg, cls, ioc, fok. Please see Understand Orders for more info. - * @param extendedHours (default) false. If true, order will be eligible to execute in premarket/afterhours. - * Only works with type limit and time_in_force day. * @param takeProfitLimitPrice Additional parameter for take-profit leg of advanced orders. Required for bracket * orders. * @param stopLossStopPrice Additional parameters for stop-loss leg of advanced orders. Required for bracket @@ -634,11 +626,10 @@ public Order requestNewStopLimitOrder(String symbol, Integer quantity, OrderSide * @see Order Types */ public Order requestNewMarketBracketOrder(String symbol, Integer quantity, OrderSide side, - OrderTimeInForce timeInForce, Boolean extendedHours, Double takeProfitLimitPrice, Double stopLossStopPrice, + OrderTimeInForce timeInForce, Double takeProfitLimitPrice, Double stopLossStopPrice, Double stopLossLimitPrice) throws AlpacaAPIRequestException { - return requestNewOrder(symbol, quantity, side, OrderType.MARKET, timeInForce, null, null, null, null, - extendedHours, null, OrderClass.BRACKET, takeProfitLimitPrice, stopLossStopPrice, - stopLossLimitPrice); + return requestNewOrder(symbol, quantity, side, OrderType.MARKET, timeInForce, null, null, null, null, null, + null, OrderClass.BRACKET, takeProfitLimitPrice, stopLossStopPrice, stopLossLimitPrice); } /** @@ -715,8 +706,6 @@ public Order requestNewOCOOrder(String symbol, Integer quantity, OrderSide side, * @param quantity number of shares to trade * @param side buy or sell * @param timeInForce day, gtc, opg, cls, ioc, fok. Please see Understand Orders for more info. - * @param extendedHours (default) false. If true, order will be eligible to execute in premarket/afterhours. - * Only works with type limit and time_in_force day. * @param takeProfitLimitPrice Additional parameter for take-profit leg of advanced orders. * @param stopLossStopPrice Additional parameters for stop-loss leg of advanced orders. * @param stopLossLimitPrice Additional parameters for stop-loss leg of advanced orders. The stop-loss order @@ -728,10 +717,10 @@ public Order requestNewOCOOrder(String symbol, Integer quantity, OrderSide side, * @see Order Types */ public Order requestNewOTOMarketOrder(String symbol, Integer quantity, OrderSide side, - OrderTimeInForce timeInForce, Boolean extendedHours, Double takeProfitLimitPrice, - Double stopLossStopPrice, Double stopLossLimitPrice) throws AlpacaAPIRequestException { - return requestNewOrder(symbol, quantity, side, OrderType.MARKET, timeInForce, null, null, null, null, - extendedHours, null, OrderClass.OTO, takeProfitLimitPrice, stopLossStopPrice, stopLossLimitPrice); + OrderTimeInForce timeInForce, Double takeProfitLimitPrice, Double stopLossStopPrice, + Double stopLossLimitPrice) throws AlpacaAPIRequestException { + return requestNewOrder(symbol, quantity, side, OrderType.MARKET, timeInForce, null, null, null, null, null, + null, OrderClass.OTO, takeProfitLimitPrice, stopLossStopPrice, stopLossLimitPrice); } /** @@ -849,7 +838,7 @@ public Order requestNewOTOStopLimitOrder(String symbol, Integer quantity, OrderS public Order requestNewTrailingStopPriceOrder(String symbol, Integer quantity, OrderSide side, OrderTimeInForce timeInForce, Double trailPrice, Boolean extendedHours) throws AlpacaAPIRequestException { return requestNewOrder(symbol, quantity, side, OrderType.TRAILING_STOP, timeInForce, null, null, - trailPrice, null, extendedHours, null, OrderClass.OTO, null, null, null); + trailPrice, null, extendedHours, null, null, null, null, null); } /** @@ -874,7 +863,7 @@ public Order requestNewTrailingStopPriceOrder(String symbol, Integer quantity, O public Order requestNewTrailingStopPercentOrder(String symbol, Integer quantity, OrderSide side, OrderTimeInForce timeInForce, Double trailPercent, Boolean extendedHours) throws AlpacaAPIRequestException { return requestNewOrder(symbol, quantity, side, OrderType.TRAILING_STOP, timeInForce, null, null, - null, trailPercent, extendedHours, null, OrderClass.OTO, null, null, null); + null, trailPercent, extendedHours, null, null, null, null, null); } /** @@ -891,7 +880,7 @@ public Order requestNewTrailingStopPercentOrder(String symbol, Integer quantity, public Order getOrder(String orderID, Boolean nested) throws AlpacaAPIRequestException { Preconditions.checkNotNull(orderID); - AlpacaRequestBuilder urlBuilder = new AlpacaRequestBuilder(baseAPIURL, apiVersion, + AlpacaRequestBuilder urlBuilder = new AlpacaRequestBuilder(baseAPIURL, AlpacaConstants.VERSION_2_ENDPOINT, AlpacaConstants.ORDERS_ENDPOINT, orderID); @@ -921,7 +910,7 @@ public Order getOrder(String orderID, Boolean nested) throws AlpacaAPIRequestExc public Order getOrderByClientID(String clientOrderId) throws AlpacaAPIRequestException { Preconditions.checkNotNull(clientOrderId); - AlpacaRequestBuilder urlBuilder = new AlpacaRequestBuilder(baseAPIURL, apiVersion, + AlpacaRequestBuilder urlBuilder = new AlpacaRequestBuilder(baseAPIURL, AlpacaConstants.VERSION_2_ENDPOINT, AlpacaConstants.ORDERS_BY_CLIENT_ORDER_ID_ENDPOINT); urlBuilder.appendURLParameter(AlpacaConstants.CLIENT_ORDER_ID_PARAMETER, clientOrderId); @@ -955,7 +944,7 @@ public Order replaceOrder(String orderID, Integer quantity, OrderTimeInForce tim Double stopPrice, Double trail, String clientOrderId) throws AlpacaAPIRequestException { Preconditions.checkNotNull(orderID); - AlpacaRequestBuilder urlBuilder = new AlpacaRequestBuilder(baseAPIURL, apiVersion, + AlpacaRequestBuilder urlBuilder = new AlpacaRequestBuilder(baseAPIURL, AlpacaConstants.VERSION_2_ENDPOINT, AlpacaConstants.ORDERS_ENDPOINT, orderID); @@ -1005,7 +994,7 @@ public Order replaceOrder(String orderID, Integer quantity, OrderTimeInForce tim * @see Orders */ public ArrayList cancelAllOrders() throws AlpacaAPIRequestException { - AlpacaRequestBuilder urlBuilder = new AlpacaRequestBuilder(baseAPIURL, apiVersion, + AlpacaRequestBuilder urlBuilder = new AlpacaRequestBuilder(baseAPIURL, AlpacaConstants.VERSION_2_ENDPOINT, AlpacaConstants.ORDERS_ENDPOINT); HttpResponse response = alpacaRequest.invokeDelete(urlBuilder); @@ -1033,7 +1022,7 @@ public ArrayList cancelAllOrders() throws AlpacaAPIRequestExcept public boolean cancelOrder(String orderId) throws AlpacaAPIRequestException { Preconditions.checkNotNull(orderId); - AlpacaRequestBuilder urlBuilder = new AlpacaRequestBuilder(baseAPIURL, apiVersion, + AlpacaRequestBuilder urlBuilder = new AlpacaRequestBuilder(baseAPIURL, AlpacaConstants.VERSION_2_ENDPOINT, AlpacaConstants.ORDERS_ENDPOINT, orderId); @@ -1055,7 +1044,7 @@ public boolean cancelOrder(String orderId) throws AlpacaAPIRequestException { * @see Positions */ public ArrayList getOpenPositions() throws AlpacaAPIRequestException { - AlpacaRequestBuilder urlBuilder = new AlpacaRequestBuilder(baseAPIURL, apiVersion, + AlpacaRequestBuilder urlBuilder = new AlpacaRequestBuilder(baseAPIURL, AlpacaConstants.VERSION_2_ENDPOINT, AlpacaConstants.POSITIONS_ENDPOINT); HttpResponse response = alpacaRequest.invokeGet(urlBuilder); @@ -1082,7 +1071,7 @@ public ArrayList getOpenPositions() throws AlpacaAPIRequestException { public Position getOpenPositionBySymbol(String symbol) throws AlpacaAPIRequestException { Preconditions.checkNotNull(symbol); - AlpacaRequestBuilder urlBuilder = new AlpacaRequestBuilder(baseAPIURL, apiVersion, + AlpacaRequestBuilder urlBuilder = new AlpacaRequestBuilder(baseAPIURL, AlpacaConstants.VERSION_2_ENDPOINT, AlpacaConstants.POSITIONS_ENDPOINT, symbol.trim()); @@ -1106,7 +1095,7 @@ public Position getOpenPositionBySymbol(String symbol) throws AlpacaAPIRequestEx * @see Positions */ public ArrayList closeAllPositions() throws AlpacaAPIRequestException { - AlpacaRequestBuilder urlBuilder = new AlpacaRequestBuilder(baseAPIURL, apiVersion, + AlpacaRequestBuilder urlBuilder = new AlpacaRequestBuilder(baseAPIURL, AlpacaConstants.VERSION_2_ENDPOINT, AlpacaConstants.POSITIONS_ENDPOINT); HttpResponse response = alpacaRequest.invokeDelete(urlBuilder); @@ -1133,7 +1122,7 @@ public ArrayList closeAllPositions() throws AlpacaAPIRequest public Order closePosition(String symbol) throws AlpacaAPIRequestException { Preconditions.checkNotNull(symbol); - AlpacaRequestBuilder urlBuilder = new AlpacaRequestBuilder(baseAPIURL, apiVersion, + AlpacaRequestBuilder urlBuilder = new AlpacaRequestBuilder(baseAPIURL, AlpacaConstants.VERSION_2_ENDPOINT, AlpacaConstants.POSITIONS_ENDPOINT, symbol); @@ -1158,7 +1147,7 @@ public Order closePosition(String symbol) throws AlpacaAPIRequestException { * @see Assets */ public ArrayList getAssets(AssetStatus assetStatus, String assetClass) throws AlpacaAPIRequestException { - AlpacaRequestBuilder urlBuilder = new AlpacaRequestBuilder(baseAPIURL, apiVersion, + AlpacaRequestBuilder urlBuilder = new AlpacaRequestBuilder(baseAPIURL, AlpacaConstants.VERSION_2_ENDPOINT, AlpacaConstants.ASSETS_ENDPOINT); if (assetStatus != null) { @@ -1193,7 +1182,7 @@ public ArrayList getAssets(AssetStatus assetStatus, String assetClass) th public Asset getAssetBySymbol(String symbolOrAssetID) throws AlpacaAPIRequestException { Preconditions.checkNotNull(symbolOrAssetID); - AlpacaRequestBuilder urlBuilder = new AlpacaRequestBuilder(baseAPIURL, apiVersion, + AlpacaRequestBuilder urlBuilder = new AlpacaRequestBuilder(baseAPIURL, AlpacaConstants.VERSION_2_ENDPOINT, AlpacaConstants.ASSETS_ENDPOINT, symbolOrAssetID.trim()); @@ -1215,7 +1204,7 @@ public Asset getAssetBySymbol(String symbolOrAssetID) throws AlpacaAPIRequestExc * @see Watchlists */ public ArrayList getWatchlists() throws AlpacaAPIRequestException { - AlpacaRequestBuilder urlBuilder = new AlpacaRequestBuilder(baseAPIURL, apiVersion, + AlpacaRequestBuilder urlBuilder = new AlpacaRequestBuilder(baseAPIURL, AlpacaConstants.VERSION_2_ENDPOINT, AlpacaConstants.WATCHLISTS_ENDPOINT); HttpResponse response = alpacaRequest.invokeGet(urlBuilder); @@ -1243,7 +1232,7 @@ public Watchlist createWatchlist(String name, String... symbols) throws AlpacaAP Preconditions.checkNotNull(name); Preconditions.checkState(name.length() <= 64); - AlpacaRequestBuilder urlBuilder = new AlpacaRequestBuilder(baseAPIURL, apiVersion, + AlpacaRequestBuilder urlBuilder = new AlpacaRequestBuilder(baseAPIURL, AlpacaConstants.VERSION_2_ENDPOINT, AlpacaConstants.WATCHLISTS_ENDPOINT); urlBuilder.appendJSONBodyProperty("name", name); @@ -1277,7 +1266,7 @@ public Watchlist createWatchlist(String name, String... symbols) throws AlpacaAP public Watchlist getWatchlist(String watchlistID) throws AlpacaAPIRequestException { Preconditions.checkNotNull(watchlistID); - AlpacaRequestBuilder urlBuilder = new AlpacaRequestBuilder(baseAPIURL, apiVersion, + AlpacaRequestBuilder urlBuilder = new AlpacaRequestBuilder(baseAPIURL, AlpacaConstants.VERSION_2_ENDPOINT, AlpacaConstants.WATCHLISTS_ENDPOINT, watchlistID); @@ -1306,7 +1295,7 @@ public Watchlist updateWatchlist(String watchlistID, String name, String... symb throws AlpacaAPIRequestException { Preconditions.checkNotNull(watchlistID); - AlpacaRequestBuilder urlBuilder = new AlpacaRequestBuilder(baseAPIURL, apiVersion, + AlpacaRequestBuilder urlBuilder = new AlpacaRequestBuilder(baseAPIURL, AlpacaConstants.VERSION_2_ENDPOINT, AlpacaConstants.WATCHLISTS_ENDPOINT, watchlistID); @@ -1345,7 +1334,7 @@ public Watchlist addWatchlistAsset(String watchlistID, String symbol) throws Alp Preconditions.checkNotNull(watchlistID); Preconditions.checkNotNull(symbol); - AlpacaRequestBuilder urlBuilder = new AlpacaRequestBuilder(baseAPIURL, apiVersion, + AlpacaRequestBuilder urlBuilder = new AlpacaRequestBuilder(baseAPIURL, AlpacaConstants.VERSION_2_ENDPOINT, AlpacaConstants.WATCHLISTS_ENDPOINT, watchlistID); @@ -1373,7 +1362,7 @@ public Watchlist addWatchlistAsset(String watchlistID, String symbol) throws Alp public boolean deleteWatchlist(String watchlistID) throws AlpacaAPIRequestException { Preconditions.checkNotNull(watchlistID); - AlpacaRequestBuilder urlBuilder = new AlpacaRequestBuilder(baseAPIURL, apiVersion, + AlpacaRequestBuilder urlBuilder = new AlpacaRequestBuilder(baseAPIURL, AlpacaConstants.VERSION_2_ENDPOINT, AlpacaConstants.WATCHLISTS_ENDPOINT, watchlistID); @@ -1401,7 +1390,7 @@ public Watchlist removeSymbolFromWatchlist(String watchlistID, String symbol) th Preconditions.checkNotNull(watchlistID); Preconditions.checkNotNull(symbol); - AlpacaRequestBuilder urlBuilder = new AlpacaRequestBuilder(baseAPIURL, apiVersion, + AlpacaRequestBuilder urlBuilder = new AlpacaRequestBuilder(baseAPIURL, AlpacaConstants.VERSION_2_ENDPOINT, AlpacaConstants.WATCHLISTS_ENDPOINT, watchlistID, symbol); @@ -1435,7 +1424,7 @@ public Watchlist removeSymbolFromWatchlist(String watchlistID, String symbol) th */ public PortfolioHistory getPortfolioHistory(Integer periodLength, PortfolioPeriodUnit periodUnit, PortfolioTimeFrame timeFrame, LocalDate dateEnd, Boolean extendedHours) throws AlpacaAPIRequestException { - AlpacaRequestBuilder urlBuilder = new AlpacaRequestBuilder(baseAPIURL, apiVersion, + AlpacaRequestBuilder urlBuilder = new AlpacaRequestBuilder(baseAPIURL, AlpacaConstants.VERSION_2_ENDPOINT, AlpacaConstants.ACCOUNT_ENDPOINT, AlpacaConstants.PORTFOLIO_ENDPOINT, AlpacaConstants.HISTORY_ENDPOINT); @@ -1474,7 +1463,7 @@ public PortfolioHistory getPortfolioHistory(Integer periodLength, PortfolioPerio * @see Calendar */ public ArrayList getCalendar() throws AlpacaAPIRequestException { - AlpacaRequestBuilder urlBuilder = new AlpacaRequestBuilder(baseAPIURL, apiVersion, + AlpacaRequestBuilder urlBuilder = new AlpacaRequestBuilder(baseAPIURL, AlpacaConstants.VERSION_2_ENDPOINT, AlpacaConstants.CALENDAR_ENDPOINT); HttpResponse response = alpacaRequest.invokeGet(urlBuilder); @@ -1500,7 +1489,7 @@ public ArrayList getCalendar() throws AlpacaAPIRequestException { * @see Calendar */ public ArrayList getCalendar(LocalDate start, LocalDate end) throws AlpacaAPIRequestException { - AlpacaRequestBuilder urlBuilder = new AlpacaRequestBuilder(baseAPIURL, apiVersion, + AlpacaRequestBuilder urlBuilder = new AlpacaRequestBuilder(baseAPIURL, AlpacaConstants.VERSION_2_ENDPOINT, AlpacaConstants.CALENDAR_ENDPOINT); if (start != null) { @@ -1531,7 +1520,7 @@ public ArrayList getCalendar(LocalDate start, LocalDate end) throws Al * @see Clock */ public Clock getClock() throws AlpacaAPIRequestException { - AlpacaRequestBuilder urlBuilder = new AlpacaRequestBuilder(baseAPIURL, apiVersion, + AlpacaRequestBuilder urlBuilder = new AlpacaRequestBuilder(baseAPIURL, AlpacaConstants.VERSION_2_ENDPOINT, AlpacaConstants.CLOCK_ENDPOINT); HttpResponse response = alpacaRequest.invokeGet(urlBuilder); @@ -1743,7 +1732,6 @@ public void removeMarketDataStreamListener(MarketDataStreamListener streamListen @Override public String toString() { return new StringJoiner(", ", this.getClass().getSimpleName() + "[", "]") - .add("apiVersion = " + apiVersion) .add("baseAPIURL = " + baseAPIURL) .add("baseDataUrl = " + baseDataUrl) .add("keyID = " + keyID) diff --git a/src/main/java/net/jacobpeterson/alpaca/AlpacaConstants.java b/src/main/java/net/jacobpeterson/alpaca/AlpacaConstants.java index 4e231c6c..96e12dd8 100644 --- a/src/main/java/net/jacobpeterson/alpaca/AlpacaConstants.java +++ b/src/main/java/net/jacobpeterson/alpaca/AlpacaConstants.java @@ -1,7 +1,7 @@ package net.jacobpeterson.alpaca; /** - * The Class AlpacaConstants. + * The Class {@link AlpacaConstants}. */ public class AlpacaConstants { @@ -154,5 +154,4 @@ public class AlpacaConstants { /** The Constant UTF_ENCODING. */ public static final String UTF_ENCODING = "UTF-8"; - } diff --git a/src/main/java/net/jacobpeterson/alpaca/enums/AccountStatus.java b/src/main/java/net/jacobpeterson/alpaca/enums/AccountStatus.java index da7d2a75..fa772fef 100644 --- a/src/main/java/net/jacobpeterson/alpaca/enums/AccountStatus.java +++ b/src/main/java/net/jacobpeterson/alpaca/enums/AccountStatus.java @@ -3,53 +3,62 @@ import net.jacobpeterson.abstracts.enums.APIName; /** - * The enum Account status. + * The enum {@link AccountStatus}. * * @see Account Status */ public enum AccountStatus implements APIName { - // The names of these enums must be the API name exactly - // @see AccountStatus#getAPIName() - /** * The account is onboarding. */ - ONBOARDING, + ONBOARDING("ONBOARDING"), /** * The account application submission failed for some reason. */ - SUBMISSION_FAILED, + SUBMISSION_FAILED(""), /** * The account application has been submitted for review. */ - SUBMITTED, + SUBMITTED("SUBMITTED"), /** * The account information is being updated. */ - ACCOUNT_UPDATED, + ACCOUNT_UPDATED("ACCOUNT_UPDATED"), /** * The final account approval is pending. */ - APPROVAL_PENDING, + APPROVAL_PENDING("APPROVAL_PENDING"), /** * The account is active for trading. */ - ACTIVE, + ACTIVE("ACTIVE"), /** * The account application has been rejected. */ - REJECTED; + REJECTED("REJECTED"); + + /** The API name. */ + String apiName; + + /** + * Instantiates a new {@link AccountStatus}. + * + * @param apiName the api name + */ + AccountStatus(String apiName) { + this.apiName = apiName; + } @Override public String getAPIName() { - return this.name(); + return apiName; } } diff --git a/src/main/java/net/jacobpeterson/alpaca/enums/ActivityType.java b/src/main/java/net/jacobpeterson/alpaca/enums/ActivityType.java index 1cff6ae6..3eb22377 100644 --- a/src/main/java/net/jacobpeterson/alpaca/enums/ActivityType.java +++ b/src/main/java/net/jacobpeterson/alpaca/enums/ActivityType.java @@ -3,13 +3,16 @@ import net.jacobpeterson.abstracts.enums.APIName; /** - * The enum Activity type. + * The enum {@link AccountStatus}. * * @see * https://docs.alpaca.markets/api-documentation/api-v2/account-activities/ */ public enum ActivityType implements APIName { + // The names of these enums must be the API name exactly + // @see AccountStatus#getAPIName() + /** Order fills (both partial and full fills) */ FILL, diff --git a/src/main/java/net/jacobpeterson/alpaca/enums/AssetStatus.java b/src/main/java/net/jacobpeterson/alpaca/enums/AssetStatus.java index b043cc65..8c697886 100644 --- a/src/main/java/net/jacobpeterson/alpaca/enums/AssetStatus.java +++ b/src/main/java/net/jacobpeterson/alpaca/enums/AssetStatus.java @@ -3,21 +3,21 @@ import net.jacobpeterson.abstracts.enums.APIName; /** - * The Enum AssetStatus. + * The Enum {@link AssetStatus}. */ public enum AssetStatus implements APIName { - /** The active. */ + /** The active {@link AssetStatus}. */ ACTIVE("active"), - /** The inactive. */ + /** The inactive {@link AssetStatus}. */ INACTIVE("inactive"); - /** The api name. */ + /** The API name. */ String apiName; /** - * Instantiates a new asset status. + * Instantiates a new {@link AssetStatus}. * * @param apiName the api name */ diff --git a/src/main/java/net/jacobpeterson/alpaca/enums/BarsTimeFrame.java b/src/main/java/net/jacobpeterson/alpaca/enums/BarsTimeFrame.java index d73eb3bd..7e8522dc 100644 --- a/src/main/java/net/jacobpeterson/alpaca/enums/BarsTimeFrame.java +++ b/src/main/java/net/jacobpeterson/alpaca/enums/BarsTimeFrame.java @@ -3,27 +3,27 @@ import net.jacobpeterson.abstracts.enums.APIName; /** - * The Enum BarsTimeFrame. + * The Enum {@link BarsTimeFrame}. */ public enum BarsTimeFrame implements APIName { - /** One min bars time frame. */ + /** 1 minute {@link BarsTimeFrame}. */ ONE_MIN("1Min"), - /** Five minute bars time frame. */ + /** 5 minute {@link BarsTimeFrame}. */ FIVE_MINUTE("5Min"), - /** Fifteen minute bars time frame. */ + /** 15 minute {@link BarsTimeFrame}. */ FIFTEEN_MINUTE("15Min"), - /** One day bars time frame. */ + /** 1 day {@link BarsTimeFrame}. */ ONE_DAY("1D"); - /** The api name. */ + /** The API name. */ String apiName; /** - * Instantiates a new bars time frame. + * Instantiates a new {@link BarsTimeFrame}. * * @param apiName the api name */ diff --git a/src/main/java/net/jacobpeterson/alpaca/enums/Direction.java b/src/main/java/net/jacobpeterson/alpaca/enums/Direction.java index 74f0fa7b..e02ce802 100644 --- a/src/main/java/net/jacobpeterson/alpaca/enums/Direction.java +++ b/src/main/java/net/jacobpeterson/alpaca/enums/Direction.java @@ -3,21 +3,21 @@ import net.jacobpeterson.abstracts.enums.APIName; /** - * The Enum Direction. + * The Enum {@link Direction}. */ public enum Direction implements APIName { - /** The asc. */ + /** The Ascending {@link Direction}. */ ASCENDING("asc"), - /** The desc. */ + /** The Descending {@link Direction}. */ DESCENDING("desc"); - /** The api name. */ + /** The API name. */ String apiName; /** - * Instantiates a new direction. + * Instantiates a new {@link Direction}. * * @param apiName the api name */ diff --git a/src/main/java/net/jacobpeterson/alpaca/enums/OrderClass.java b/src/main/java/net/jacobpeterson/alpaca/enums/OrderClass.java index 9db37313..9f1cd253 100644 --- a/src/main/java/net/jacobpeterson/alpaca/enums/OrderClass.java +++ b/src/main/java/net/jacobpeterson/alpaca/enums/OrderClass.java @@ -3,29 +3,27 @@ import net.jacobpeterson.abstracts.enums.APIName; /** - * The enum Account status. - * - * @see Account Status + * The enum {@link OrderClass}. */ public enum OrderClass implements APIName { - /** Simple order class. */ + /** Simple {@link OrderClass}. */ SIMPLE("simple"), - /** Bracket order class. */ + /** Bracket {@link OrderClass}. */ BRACKET("bracket"), - /** One-Cancels-Other order class. */ + /** One-Cancels-Other {@link OrderClass}. */ OCO("oco"), - /** One-Triggers-Other order class. */ + /** One-Triggers-Other {@link OrderClass}. */ OTO("oto"); - /** The api name. */ + /** The API name. */ String apiName; /** - * Instantiates a new order class. + * Instantiates a new {@link OrderClass}. * * @param apiName the api name */ diff --git a/src/main/java/net/jacobpeterson/alpaca/enums/OrderSide.java b/src/main/java/net/jacobpeterson/alpaca/enums/OrderSide.java index 6e01a342..0f0eccce 100644 --- a/src/main/java/net/jacobpeterson/alpaca/enums/OrderSide.java +++ b/src/main/java/net/jacobpeterson/alpaca/enums/OrderSide.java @@ -3,21 +3,21 @@ import net.jacobpeterson.abstracts.enums.APIName; /** - * The Enum OrderSide. + * The Enum {@link OrderSide}. */ public enum OrderSide implements APIName { - /** The buy. */ + /** The buy {@link OrderSide}. */ BUY("buy"), - /** The sell. */ + /** The sell {@link OrderSide}. */ SELL("sell"); - /** The api name. */ + /** The API name. */ String apiName; /** - * Instantiates a new order side. + * Instantiates a new {@link OrderSide}. * * @param apiName the api name */ diff --git a/src/main/java/net/jacobpeterson/alpaca/enums/OrderStatus.java b/src/main/java/net/jacobpeterson/alpaca/enums/OrderStatus.java index 4661eb10..fe048091 100644 --- a/src/main/java/net/jacobpeterson/alpaca/enums/OrderStatus.java +++ b/src/main/java/net/jacobpeterson/alpaca/enums/OrderStatus.java @@ -3,24 +3,24 @@ import net.jacobpeterson.abstracts.enums.APIName; /** - * The Enum OrderStatus. + * The Enum {@link OrderStatus}. */ public enum OrderStatus implements APIName { - /** The open. */ + /** Open {@link OrderStatus}. */ OPEN("open"), - /** The closed. */ + /** Closed {@link OrderStatus}. */ CLOSED("closed"), - /** The all. */ + /** All {@link OrderStatus}es. */ ALL("all"); - /** The api name. */ + /** The API name. */ String apiName; /** - * Instantiates a new order status. + * Instantiates a new {@link OrderStatus}. * * @param apiName the api name */ diff --git a/src/main/java/net/jacobpeterson/alpaca/enums/OrderTimeInForce.java b/src/main/java/net/jacobpeterson/alpaca/enums/OrderTimeInForce.java index a476eaca..82f15d17 100644 --- a/src/main/java/net/jacobpeterson/alpaca/enums/OrderTimeInForce.java +++ b/src/main/java/net/jacobpeterson/alpaca/enums/OrderTimeInForce.java @@ -3,33 +3,62 @@ import net.jacobpeterson.abstracts.enums.APIName; /** - * The Enum OrderTimeInForce. + * The Enum {@link OrderTimeInForce}. + * + * @see Time in Force */ public enum OrderTimeInForce implements APIName { - /** The day. */ + /** + * A day order is eligible for execution only on the day it is live. By default, the order is only valid during + * Regular Trading Hours (9:30am - 4:00pm ET). If unfilled after the closing auction, it is automatically canceled. + * If submitted after the close, it is queued and submitted the following trading day. However, if marked as + * eligible for extended hours, the order can also execute during supported extended hours. + */ DAY("day"), - /** The gtc. */ + /** + * The order is good until canceled. Non-marketable GTC limit orders are subject to price adjustments to offset + * corporate actions affecting the issue. We do not currently support Do Not Reduce(DNR) orders to opt out of such + * price adjustments. + */ GTC("gtc"), - /** The opg. */ + /** + * Use this TIF with a market/limit order type to submit “market on open” (MOO) and “limit on open” (LOO) orders. + * This order is eligible to execute only in the market opening auction. Any unfilled orders after the open will be + * cancelled. OPG orders submitted after 9:28am but before 7:00pm ET will be rejected. OPG orders submitted after + * 7:00pm will be queued and routed to the following day’s opening auction. On open/on close orders are routed to + * the primary exchange. Such orders do not necessarily execute exactly at 9:30am / 4:00pm ET but execute per the + * exchange’s auction rules. + */ OPG("opg"), - /** Cls order time in force. */ + /** + * Use this TIF with a market/limit order type to submit “market on close” (MOC) and “limit on close” (LOC) orders. + * This order is eligible to execute only in the market closing auction. Any unfilled orders after the close will be + * cancelled. CLS orders submitted after 3:50pm but before 7:00pm ET will be rejected. CLS orders submitted after + * 7:00pm will be queued and routed to the following day’s closing auction. Only available with API v2. + */ CLS("cls"), - /** Ioc order time in force. */ + /** + * An Immediate Or Cancel (IOC) order requires all or part of the order to be executed immediately. Any unfilled + * portion of the order is canceled. Only available with API v2. + */ IOC("ioc"), - /** Fok order time in force. */ + /** + * A Fill or Kill (FOK) order is only executed if the entire order quantity can be filled, otherwise the order is + * canceled. Only available with API v2. + */ FOK("fok"); - /** The api name. */ + /** The API name. */ String apiName; /** - * Instantiates a new order time in force. + * Instantiates a new {@link OrderTimeInForce}. * * @param apiName the api name */ diff --git a/src/main/java/net/jacobpeterson/alpaca/enums/OrderType.java b/src/main/java/net/jacobpeterson/alpaca/enums/OrderType.java index b83a3c24..1413fdcb 100644 --- a/src/main/java/net/jacobpeterson/alpaca/enums/OrderType.java +++ b/src/main/java/net/jacobpeterson/alpaca/enums/OrderType.java @@ -3,30 +3,57 @@ import net.jacobpeterson.abstracts.enums.APIName; /** - * The Enum OrderType. + * The Enum {@link OrderType}. + * + * @see Order Types */ public enum OrderType implements APIName { - /** The market. */ + /** + * A market order is a request to buy or sell a security at the currently available market price. It provides the + * most likely method of filling an order. Market orders fill nearly instantaneously. + */ MARKET("market"), - /** The limit. */ + /** + * A limit order is an order to buy or sell at a specified price or better. A buy limit order (a limit order to buy) + * is executed at the specified limit price or lower (i.e., better). Conversely, a sell limit order (a limit order + * to sell) is executed at the specified limit price or higher (better). Unlike a market order, you have to specify + * the limit price parameter when submitting your order. + */ LIMIT("limit"), - /** The stop. */ + /** + * A stop (market) order is an order to buy or sell a security when its price moves past a particular point, + * ensuring a higher probability of achieving a predetermined entry or exit price. Once the market price crosses the + * specified stop price, the stop order becomes a market order. Alpaca converts buy stop orders into stop limit + * orders with a limit price that is 4% higher than a stop price < $50 (or 2.5% higher than a stop price >= $50). + * Sell stop orders are not converted into stop limit orders. + */ STOP("stop"), - /** The stop limit. */ + /** + * A stop-limit order is a conditional trade over a set time frame that combines the features of a stop order with + * those of a limit order and is used to mitigate risk. The stop-limit order will be executed at a specified limit + * price, or better, after a given stop price has been reached. Once the stop price is reached, the stop-limit order + * becomes a limit order to buy or sell at the limit price or better + */ STOP_LIMIT("stop_limit"), - /** The trailing stop. */ + /** + * Trailing stop orders allow you to continuously and automatically keep updating the stop price threshold based on + * the stock price movement. You request a single order with a dollar offset value or percentage value as the trail + * and the actual stop price for this order changes as the stock price moves in your favorable way, or stay at the + * last level otherwise. This way, you don’t need to monitor the price movement and keep sending replace requests to + * update the stop price close to the latest market movement. + */ TRAILING_STOP("trailing_stop"); - /** The api name. */ + /** The API name. */ String apiName; /** - * Instantiates a new order type. + * Instantiates a new {@link OrderType}. * * @param apiName the api name */ diff --git a/src/main/java/net/jacobpeterson/alpaca/enums/PortfolioPeriodUnit.java b/src/main/java/net/jacobpeterson/alpaca/enums/PortfolioPeriodUnit.java index 35e640ae..7d51319e 100644 --- a/src/main/java/net/jacobpeterson/alpaca/enums/PortfolioPeriodUnit.java +++ b/src/main/java/net/jacobpeterson/alpaca/enums/PortfolioPeriodUnit.java @@ -3,27 +3,27 @@ import net.jacobpeterson.abstracts.enums.APIName; /** - * The enum Portfolio period unit. + * The enum {@link PortfolioPeriodUnit}. */ public enum PortfolioPeriodUnit implements APIName { - /** Day portfolio period unit. */ + /** Day {@link PortfolioPeriodUnit}. */ DAY("D"), - /** Week portfolio period unit. */ + /** Week {@link PortfolioPeriodUnit}. */ WEEK("W"), - /** Month portfolio period unit. */ + /** Month {@link PortfolioPeriodUnit}. */ MONTH("M"), - /** Year portfolio period unit. */ + /** Year {@link PortfolioPeriodUnit}. */ YEAR("A"); - /** The api name. */ + /** The API name. */ String apiName; /** - * Instantiates a new Portfolio period unit. + * Instantiates a new {@link PortfolioPeriodUnit}. * * @param apiName the api name */ diff --git a/src/main/java/net/jacobpeterson/alpaca/enums/PortfolioTimeFrame.java b/src/main/java/net/jacobpeterson/alpaca/enums/PortfolioTimeFrame.java index 236d819f..8ca998ff 100644 --- a/src/main/java/net/jacobpeterson/alpaca/enums/PortfolioTimeFrame.java +++ b/src/main/java/net/jacobpeterson/alpaca/enums/PortfolioTimeFrame.java @@ -4,25 +4,28 @@ import net.jacobpeterson.abstracts.enums.APIName; import net.jacobpeterson.util.gson.GsonUtil; +/** + * The enum {@link PortfolioTimeFrame}. + */ public enum PortfolioTimeFrame implements APIName { - /** One min portfolio time frame. */ + /** 1 min {@link PortfolioTimeFrame}. */ @SerializedName("1Min") ONE_MIN, - /** Five minute portfolio time frame. */ + /** 5 minute {@link PortfolioTimeFrame}. */ @SerializedName("5Min") FIVE_MINUTE, - /** Fifteen minute portfolio time frame. */ + /** 15 minute {@link PortfolioTimeFrame}. */ @SerializedName("15Min") FIFTEEN_MINUTE, - /** One hour portfolio time frame. */ + /** 1 hour {@link PortfolioTimeFrame}. */ @SerializedName("1H") ONE_HOUR, - /** One day portfolio time frame. */ + /** 1 day {@link PortfolioTimeFrame}. */ @SerializedName("1D") ONE_DAY; diff --git a/src/main/java/net/jacobpeterson/alpaca/properties/AlpacaProperties.java b/src/main/java/net/jacobpeterson/alpaca/properties/AlpacaProperties.java index ce95c2d3..e89dfdd4 100644 --- a/src/main/java/net/jacobpeterson/alpaca/properties/AlpacaProperties.java +++ b/src/main/java/net/jacobpeterson/alpaca/properties/AlpacaProperties.java @@ -5,18 +5,12 @@ import static net.jacobpeterson.util.properties.PropertyUtil.getProperty; /** - * The Class AlpacaProperties. + * The Class {@link AlpacaProperties}. */ public class AlpacaProperties { private static final String ALPACA_PROPERTIES_FILE = "alpaca.properties"; - /** The Constant API_VERSION_KEY. */ - private static final String API_VERSION_KEY = "api_version"; - - /** The Constant API_VERSION_VALUE. */ - public static final String API_VERSION_VALUE = getProperty(ALPACA_PROPERTIES_FILE, API_VERSION_KEY); - /** The Constant KEY_ID_KEY. */ private static final String KEY_ID_KEY = "key_id"; @@ -55,8 +49,6 @@ public class AlpacaProperties { public static String staticToString() { return new StringJoiner(", ", AlpacaProperties.class.getSimpleName() + "[", "]") .add("ALPACA_PROPERTIES_FILE = " + ALPACA_PROPERTIES_FILE) - .add("API_VERSION_KEY = " + API_VERSION_KEY) - .add("API_VERSION_VALUE = " + API_VERSION_VALUE) .add("BASE_API_URL_KEY = " + BASE_API_URL_KEY) .add("BASE_API_URL_VALUE = " + BASE_API_URL_VALUE) .add("BASE_DATA_URL_KEY = " + BASE_DATA_URL_KEY) diff --git a/src/main/java/net/jacobpeterson/alpaca/rest/AlpacaRequest.java b/src/main/java/net/jacobpeterson/alpaca/rest/AlpacaRequest.java index b11f4479..ae364c2d 100644 --- a/src/main/java/net/jacobpeterson/alpaca/rest/AlpacaRequest.java +++ b/src/main/java/net/jacobpeterson/alpaca/rest/AlpacaRequest.java @@ -5,7 +5,7 @@ import net.jacobpeterson.alpaca.properties.AlpacaProperties; /** - * The Class AlpacaRequest. + * The Class {@link AlpacaRequest}. */ public class AlpacaRequest extends AbstractRequest { @@ -22,7 +22,7 @@ public class AlpacaRequest extends AbstractRequest { private static final String AUTH_TOKEN = "Authorization"; /** - * Instantiates a new AlpacaRequest. + * Instantiates a new {@link AlpacaRequest}. * * @param keyID the key ID * @param secretKey the secret key @@ -38,7 +38,7 @@ public AlpacaRequest(String keyID, String secretKey) { } /** - * Instantiates a new AlpacaRequest. + * Instantiates a new {@link AlpacaRequest}. * * @param oAuthToken the OAuth token */ diff --git a/src/main/java/net/jacobpeterson/alpaca/rest/AlpacaRequestBuilder.java b/src/main/java/net/jacobpeterson/alpaca/rest/AlpacaRequestBuilder.java index d6a0eabe..14747796 100644 --- a/src/main/java/net/jacobpeterson/alpaca/rest/AlpacaRequestBuilder.java +++ b/src/main/java/net/jacobpeterson/alpaca/rest/AlpacaRequestBuilder.java @@ -5,12 +5,12 @@ import java.util.Arrays; /** - * The Class AlpacaRequestBuilder. + * The Class {@link AlpacaRequestBuilder}. */ public class AlpacaRequestBuilder extends AbstractRequestBuilder { /** - * Instantiates a new alpaca request builder. + * Instantiates a new {@link AlpacaRequestBuilder}. * * @param baseUrl the base url * @param apiVersion the api version diff --git a/src/main/java/net/jacobpeterson/alpaca/rest/exception/AlpacaAPIRequestException.java b/src/main/java/net/jacobpeterson/alpaca/rest/exception/AlpacaAPIRequestException.java index 42414399..7bb10544 100644 --- a/src/main/java/net/jacobpeterson/alpaca/rest/exception/AlpacaAPIRequestException.java +++ b/src/main/java/net/jacobpeterson/alpaca/rest/exception/AlpacaAPIRequestException.java @@ -6,12 +6,12 @@ import java.io.InputStream; /** - * The Class AlpacaAPIException. + * The Class {@link AlpacaAPIRequestException}. */ public class AlpacaAPIRequestException extends AbstractAPIRequestException { /** - * Instantiates a new Alpaca api exception. + * Instantiates a new {@link AlpacaAPIRequestException}. * * @param httpResponse the http response */ diff --git a/src/main/java/net/jacobpeterson/alpaca/websocket/broker/client/AlpacaWebsocketClient.java b/src/main/java/net/jacobpeterson/alpaca/websocket/broker/client/AlpacaWebsocketClient.java index 4d7d5550..81b9b2c2 100644 --- a/src/main/java/net/jacobpeterson/alpaca/websocket/broker/client/AlpacaWebsocketClient.java +++ b/src/main/java/net/jacobpeterson/alpaca/websocket/broker/client/AlpacaWebsocketClient.java @@ -33,7 +33,7 @@ import java.util.stream.Collectors; /** - * The type Alpaca websocket client. + * The type {@link AlpacaWebsocketClient}. */ public class AlpacaWebsocketClient implements WebsocketClient { @@ -65,7 +65,7 @@ public class AlpacaWebsocketClient implements WebsocketClient { private boolean authenticated; /** - * Instantiates a new AlpacaWebsocketClient. + * Instantiates a new {@link AlpacaWebsocketClient}. * * @param keyId the key ID * @param secret the secret @@ -81,7 +81,7 @@ public AlpacaWebsocketClient(String keyId, String secret, String baseAPIURL) { } /** - * Instantiates a new AlpacaWebsocketClient. + * Instantiates a new {@link AlpacaWebsocketClient}. * * @param oAuthToken the OAuth token * @param baseAPIURL the base API URL diff --git a/src/main/java/net/jacobpeterson/alpaca/websocket/broker/client/AlpacaWebsocketClientEndpoint.java b/src/main/java/net/jacobpeterson/alpaca/websocket/broker/client/AlpacaWebsocketClientEndpoint.java index 1416503f..8ae7fbe1 100644 --- a/src/main/java/net/jacobpeterson/alpaca/websocket/broker/client/AlpacaWebsocketClientEndpoint.java +++ b/src/main/java/net/jacobpeterson/alpaca/websocket/broker/client/AlpacaWebsocketClientEndpoint.java @@ -14,13 +14,13 @@ import java.nio.charset.StandardCharsets; /** - * The type Alpaca websocket client endpoint. + * The type {@link AlpacaWebsocketClientEndpoint}. */ @ClientEndpoint(subprotocols = "BINARY") public class AlpacaWebsocketClientEndpoint extends AbstractWebsocketClientEndpoint { /** - * Instantiates a new Alpaca websocket client endpoint. + * Instantiates a new {@link AlpacaWebsocketClientEndpoint}. * * @param websocketClient the websocket client * @param endpointURI the endpoint uri diff --git a/src/main/java/net/jacobpeterson/alpaca/websocket/broker/listener/AlpacaStreamListenerAdapter.java b/src/main/java/net/jacobpeterson/alpaca/websocket/broker/listener/AlpacaStreamListenerAdapter.java index 6b84cc99..0cab6413 100644 --- a/src/main/java/net/jacobpeterson/alpaca/websocket/broker/listener/AlpacaStreamListenerAdapter.java +++ b/src/main/java/net/jacobpeterson/alpaca/websocket/broker/listener/AlpacaStreamListenerAdapter.java @@ -8,7 +8,7 @@ import java.util.Set; /** - * The Class AlpacaStreamListenerAdapter. + * The Class {@link AlpacaStreamListenerAdapter}. */ public class AlpacaStreamListenerAdapter implements AlpacaStreamListener { @@ -16,7 +16,7 @@ public class AlpacaStreamListenerAdapter implements AlpacaStreamListener { private final HashSet streamUpdateTypes = new HashSet<>(); /** - * Instantiates a new Alpaca stream listener adapter. + * Instantiates a new {@link AlpacaStreamListenerAdapter}. * * @param streamUpdateTypes the stream update types */ diff --git a/src/main/java/net/jacobpeterson/alpaca/websocket/broker/message/AlpacaStreamMessageType.java b/src/main/java/net/jacobpeterson/alpaca/websocket/broker/message/AlpacaStreamMessageType.java index 6d1cbb4e..5f389ab7 100644 --- a/src/main/java/net/jacobpeterson/alpaca/websocket/broker/message/AlpacaStreamMessageType.java +++ b/src/main/java/net/jacobpeterson/alpaca/websocket/broker/message/AlpacaStreamMessageType.java @@ -6,31 +6,31 @@ import net.jacobpeterson.util.gson.GsonUtil; /** - * The enum Alpaca stream message type. + * The enum {@link AlpacaStreamMessageType}. */ public enum AlpacaStreamMessageType implements StreamMessageType, APIName { - /** Listening alpaca stream message type. */ + /** Listening {@link AlpacaStreamMessageType}. */ @SerializedName("listening") LISTENING(false), - /** Authorization alpaca stream message type. */ + /** Authorization {@link AlpacaStreamMessageType}. */ @SerializedName("authorization") AUTHORIZATION(false), - /** Trade updates alpaca stream message type. */ + /** Trade updates {@link AlpacaStreamMessageType}. */ @SerializedName("trade_updates") TRADE_UPDATES(true), - /** Account updates alpaca stream message type. */ + /** Account updates {@link AlpacaStreamMessageType}. */ @SerializedName("account_updates") ACCOUNT_UPDATES(true); - /** The is api subscribable. */ + /** Is API subscribable. */ private final boolean isAPISubscribable; /** - * Instantiates a new Alpaca stream message type. + * Instantiates a new {@link AlpacaStreamMessageType}. * * @param isAPISubscribable the is api subscribable */ diff --git a/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/client/MarketDataWebsocketClient.java b/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/client/MarketDataWebsocketClient.java index f2636d7e..e6053a09 100644 --- a/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/client/MarketDataWebsocketClient.java +++ b/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/client/MarketDataWebsocketClient.java @@ -37,7 +37,7 @@ import java.util.stream.Collectors; /** - * The type Alpaca websocket client. + * The type {@link MarketDataWebsocketClient}. */ public class MarketDataWebsocketClient implements WebsocketClient { @@ -90,7 +90,7 @@ public class MarketDataWebsocketClient implements WebsocketClient { private boolean authenticated; /** - * Instantiates a new MarketDataWebsocketClient. + * Instantiates a new {@link MarketDataWebsocketClient}. * * @param keyId the key id * @param secret the secret @@ -106,7 +106,7 @@ public MarketDataWebsocketClient(String keyId, String secret, String baseDataUrl } /** - * Instantiates a new MarketDataWebsocketClient. + * Instantiates a new {@link MarketDataWebsocketClient}. * * @param oAuthToken the OAuth token * @param baseDataUrl the base data url diff --git a/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/client/MarketDataWebsocketClientEndpoint.java b/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/client/MarketDataWebsocketClientEndpoint.java index 7ad69b49..b39a2751 100644 --- a/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/client/MarketDataWebsocketClientEndpoint.java +++ b/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/client/MarketDataWebsocketClientEndpoint.java @@ -13,13 +13,13 @@ import java.net.URI; /** - * The type Market data websocket client endpoint. + * The type {@link MarketDataWebsocketClientEndpoint}. */ @ClientEndpoint(subprotocols = "STRING") public class MarketDataWebsocketClientEndpoint extends AbstractWebsocketClientEndpoint { /** - * Instantiates a new Market data websocket client endpoint. + * Instantiates a new {@link MarketDataWebsocketClientEndpoint}. * * @param websocketClient the websocket client * @param endpointURI the endpoint uri diff --git a/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/listener/MarketDataStreamListenerAdapter.java b/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/listener/MarketDataStreamListenerAdapter.java index aee3bac8..18c73700 100644 --- a/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/listener/MarketDataStreamListenerAdapter.java +++ b/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/listener/MarketDataStreamListenerAdapter.java @@ -10,7 +10,7 @@ import java.util.Set; /** - * The type Market data stream listener adapter. + * The type {@link MarketDataStreamListenerAdapter}. */ public class MarketDataStreamListenerAdapter implements MarketDataStreamListener { @@ -18,7 +18,7 @@ public class MarketDataStreamListenerAdapter implements MarketDataStreamListener private final Map> dataStreams = new HashMap<>(); /** - * Instantiates a new Market data stream listener adapter. + * Instantiates a new {@link MarketDataStreamListenerAdapter}. * * @param ticker the ticker * @param dataMessageTypes the data message types @@ -32,7 +32,7 @@ public MarketDataStreamListenerAdapter(String ticker, MarketDataStreamMessageTyp } /** - * Instantiates a new Market data stream listener adapter. + * Instantiates a new {@link MarketDataStreamListenerAdapter}. * * @param tickers the tickers * @param dataMessageTypes the data message types @@ -48,7 +48,7 @@ public MarketDataStreamListenerAdapter(Set tickers, MarketDataStreamMess } /** - * Instantiates a new Market data stream listener adapter. + * Instantiates a new {@link MarketDataStreamListenerAdapter}. * * @param dataStreams the data streams */ diff --git a/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/message/MarketDataStreamMessageType.java b/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/message/MarketDataStreamMessageType.java index 5fd4b215..f9649ba3 100644 --- a/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/message/MarketDataStreamMessageType.java +++ b/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/message/MarketDataStreamMessageType.java @@ -6,27 +6,27 @@ import net.jacobpeterson.util.gson.GsonUtil; /** - * The enum Market data stream message type. + * The enum {@link MarketDataStreamMessageType}. */ public enum MarketDataStreamMessageType implements StreamMessageType, APIName { - /** Listening market data stream message type. */ + /** Listening {@link MarketDataStreamMessageType}. */ @SerializedName("listening") LISTENING(false), - /** Authorization market data stream message type. */ + /** Authorization {@link MarketDataStreamMessageType}. */ @SerializedName("authorization") AUTHORIZATION(false), - /** Trades market data stream message type. */ + /** Trades {@link MarketDataStreamMessageType}. */ @SerializedName("T") TRADES(true), - /** Quotes market data stream message type. */ + /** Quotes {@link MarketDataStreamMessageType}. */ @SerializedName("Q") QUOTES(true), - /** Aggregate minute market data stream message type. */ + /** Aggregate {@link MarketDataStreamMessageType}. */ @SerializedName("AM") AGGREGATE_MINUTE(true); @@ -34,7 +34,7 @@ public enum MarketDataStreamMessageType implements StreamMessageType, APIName { private final boolean isAPISubscribable; /** - * Instantiates a new Market data stream message type. + * Instantiates a new {@link MarketDataStreamMessageType}. * * @param isAPISubscribable the is api subscribable */ diff --git a/src/main/resources/alpaca.properties.default b/src/main/resources/alpaca.properties.default index af829844..5cfcfca7 100644 --- a/src/main/resources/alpaca.properties.default +++ b/src/main/resources/alpaca.properties.default @@ -1,5 +1,4 @@ #Example: -#api_version = #key_id = #secret = #base_api_url = https://paper-api.alpaca.markets @@ -7,7 +6,6 @@ #user_agent=Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/67.0.3396.87 Safari/537.36 #Defaults: -api_version = v2 base_api_url = https://paper-api.alpaca.markets base_data_url = https://data.alpaca.markets user_agent = Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/67.0.3396.87 Safari/537.36 diff --git a/src/main/resources/polygon.properties.default b/src/main/resources/polygon.properties.default index 33560344..ea9c28db 100644 --- a/src/main/resources/polygon.properties.default +++ b/src/main/resources/polygon.properties.default @@ -1,6 +1,5 @@ #Example: -#key_id will default to what is set in alpaca.properties -#key_id = +#key_id = (Note that this will default to what is set in alpaca.properties) #base_api_url = https://api.polygon.io #web_socket_server_url = wss://alpaca.socket.polygon.io/stocks #user_agent=Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/67.0.3396.87 Safari/537.36 From e4a0d6b8c942b946b7ee95b1a157b84f4acadd4a Mon Sep 17 00:00:00 2001 From: Jacob Peterson Date: Fri, 11 Dec 2020 12:20:47 -0700 Subject: [PATCH 061/271] Format Javadoc in Polygon package --- .../net/jacobpeterson/alpaca/AlpacaAPI.java | 4 +-- .../net/jacobpeterson/polygon/PolygonAPI.java | 17 +++++----- .../polygon/PolygonConstants.java | 2 +- .../polygon/enums/ConditionMappingsType.java | 10 +++--- .../polygon/enums/FinancialReportType.java | 18 +++++------ .../polygon/enums/FinancialSort.java | 14 ++++---- .../polygon/enums/GainersLosersDirection.java | 9 ++++-- .../jacobpeterson/polygon/enums/Market.java | 20 ++++++------ .../polygon/enums/StockType.java | 32 ------------------- .../polygon/enums/TickerSort.java | 14 ++++---- .../jacobpeterson/polygon/enums/Timespan.java | 20 ++++++------ .../polygon/properties/PolygonProperties.java | 2 +- .../polygon/rest/PolygonRequest.java | 4 +-- .../polygon/rest/PolygonRequestBuilder.java | 4 +-- .../exception/PolygonAPIRequestException.java | 4 +-- .../client/PolygonWebsocketClient.java | 14 ++++---- .../PolygonWebsocketClientEndpoint.java | 4 +-- .../PolygonStreamListenerAdapter.java | 8 ++--- .../message/PolygonStreamMessageType.java | 18 +++++------ .../util/concurrency/ExecutorTracer.java | 10 +++--- .../ScheduledThreadPoolExecutorTracer.java | 6 ++-- .../concurrency/ThreadPoolExecutorTracer.java | 6 ++-- .../util/doc/Documentation2JSONSchema.java | 3 ++ .../jacobpeterson/util/format/FormatUtil.java | 2 +- .../net/jacobpeterson/util/gson/GsonUtil.java | 11 ++++--- .../util/gson/ZonedDateTimeAdapter.java | 2 +- .../util/properties/PropertyUtil.java | 2 +- 27 files changed, 119 insertions(+), 141 deletions(-) delete mode 100644 src/main/java/net/jacobpeterson/polygon/enums/StockType.java diff --git a/src/main/java/net/jacobpeterson/alpaca/AlpacaAPI.java b/src/main/java/net/jacobpeterson/alpaca/AlpacaAPI.java index 7fe2a243..ee2cbdbc 100644 --- a/src/main/java/net/jacobpeterson/alpaca/AlpacaAPI.java +++ b/src/main/java/net/jacobpeterson/alpaca/AlpacaAPI.java @@ -189,8 +189,8 @@ public Account getAccount() throws AlpacaAPIRequestException { * @param after The response will contain only activities submitted after this date. (Cannot be used with * date.) * @param direction asc or desc (default desc if unspecified.) - * @param pageSize The maximum number of entries to return in the response. (See the section on paging above.) - * @param pageToken The ID of the end of your current page of results. (See the section on paging above.) + * @param pageSize The maximum number of entries to return in the response. (See the section on paging.) + * @param pageToken The ID of the end of your current page of results. (See the section on paging.) * @param activityTypes the activity types (null for all activities) * * @return the account activities diff --git a/src/main/java/net/jacobpeterson/polygon/PolygonAPI.java b/src/main/java/net/jacobpeterson/polygon/PolygonAPI.java index 442a4395..14c4842b 100644 --- a/src/main/java/net/jacobpeterson/polygon/PolygonAPI.java +++ b/src/main/java/net/jacobpeterson/polygon/PolygonAPI.java @@ -34,7 +34,6 @@ import net.jacobpeterson.polygon.enums.FinancialSort; import net.jacobpeterson.polygon.enums.GainersLosersDirection; import net.jacobpeterson.polygon.enums.Market; -import net.jacobpeterson.polygon.enums.StockType; import net.jacobpeterson.polygon.enums.TickerSort; import net.jacobpeterson.polygon.enums.Timespan; import net.jacobpeterson.polygon.properties.PolygonProperties; @@ -54,7 +53,7 @@ import java.util.StringJoiner; /** - * The class PolygonAPI. Note that most of these methods are blocking methods and this class in NOT thread-safe. + * The class {@link PolygonAPI}. Note that most of these methods are blocking methods and this class in NOT thread-safe. */ public class PolygonAPI { @@ -77,7 +76,7 @@ public class PolygonAPI { private final String keyID; /** - * Instantiates a new PolygonAPI. + * Instantiates a new {@link PolygonAPI}. */ public PolygonAPI() { this(PolygonProperties.KEY_ID_VALUE); @@ -86,7 +85,7 @@ public PolygonAPI() { } /** - * Instantiates a new PolygonAPI. + * Instantiates a new {@link PolygonAPI}. * * @param keyID the key ID */ @@ -95,7 +94,7 @@ public PolygonAPI(String keyID) { } /** - * Instantiates a new polygon API. + * Instantiates a new {@link PolygonAPI}. * * @param baseAPIURL the base API URL * @param websocketURL the websocket URL @@ -116,7 +115,7 @@ public PolygonAPI(String baseAPIURL, String websocketURL, String keyID) { * Query all ticker symbols which are supported by Polygon.io. * * @param tickerSort Which field to sort by. - * @param stockType If you want the results to only container a certain type. + * @param tickerType Get tickers of a certain type. See Ticker Types for available types. * @param market Get tickers for a specific market * @param locale Get tickers for a specific region/locale * @param search Search the name of tickers @@ -129,7 +128,7 @@ public PolygonAPI(String baseAPIURL, String websocketURL, String keyID) { * @throws PolygonAPIRequestException the polygon API exception * @see Tickers */ - public TickersResponse getTickers(TickerSort tickerSort, StockType stockType, Market market, String locale, + public TickersResponse getTickers(TickerSort tickerSort, String tickerType, Market market, String locale, String search, Integer perpage, Integer page, Boolean active) throws PolygonAPIRequestException { PolygonRequestBuilder builder = new PolygonRequestBuilder(baseAPIURL, PolygonConstants.VERSION_2_ENDPOINT, PolygonConstants.REFERENCE_ENDPOINT, @@ -139,8 +138,8 @@ public TickersResponse getTickers(TickerSort tickerSort, StockType stockType, Ma builder.appendURLParameter(PolygonConstants.SORT_PARAMETER, tickerSort.getAPIName()); } - if (stockType != null) { - builder.appendURLParameter(PolygonConstants.TYPE_PARAMETER, stockType.getAPIName()); + if (tickerType != null) { + builder.appendURLParameter(PolygonConstants.TYPE_PARAMETER, tickerType); } if (market != null) { diff --git a/src/main/java/net/jacobpeterson/polygon/PolygonConstants.java b/src/main/java/net/jacobpeterson/polygon/PolygonConstants.java index ac772fb3..7e76d634 100644 --- a/src/main/java/net/jacobpeterson/polygon/PolygonConstants.java +++ b/src/main/java/net/jacobpeterson/polygon/PolygonConstants.java @@ -1,7 +1,7 @@ package net.jacobpeterson.polygon; /** - * The type Polygon constants. + * The type {@link PolygonConstants}. */ public class PolygonConstants { diff --git a/src/main/java/net/jacobpeterson/polygon/enums/ConditionMappingsType.java b/src/main/java/net/jacobpeterson/polygon/enums/ConditionMappingsType.java index aff8606a..4ac1ffb5 100644 --- a/src/main/java/net/jacobpeterson/polygon/enums/ConditionMappingsType.java +++ b/src/main/java/net/jacobpeterson/polygon/enums/ConditionMappingsType.java @@ -3,21 +3,21 @@ import net.jacobpeterson.abstracts.enums.APIName; /** - * The enum Condition mappings type. + * The enum {@link ConditionMappingsType}. */ public enum ConditionMappingsType implements APIName { - /** Trades condition mapping type. */ + /** Trades {@link ConditionMappingsType}. */ TRADES("trades"), - /** Quotes condition mapping type. */ + /** Quotes {@link ConditionMappingsType}. */ QUOTES("quotes"); - /** The api name. */ + /** The API name. */ String apiName; /** - * Instantiates a new Condition mappings type. + * Instantiates a new {@link ConditionMappingsType}. * * @param apiName the api name */ diff --git a/src/main/java/net/jacobpeterson/polygon/enums/FinancialReportType.java b/src/main/java/net/jacobpeterson/polygon/enums/FinancialReportType.java index 03a145ed..08cf1d7d 100644 --- a/src/main/java/net/jacobpeterson/polygon/enums/FinancialReportType.java +++ b/src/main/java/net/jacobpeterson/polygon/enums/FinancialReportType.java @@ -3,33 +3,33 @@ import net.jacobpeterson.abstracts.enums.APIName; /** - * The enum Financial report type. + * The enum {@link FinancialReportType}. */ public enum FinancialReportType implements APIName { - /** Y financial report type. */ + /** Y {@link FinancialReportType}. */ Y("Y"), - /** YA financial report type. */ + /** YA {@link FinancialReportType}. */ YA("YA"), - /** Q financial report type. */ + /** Q {@link FinancialReportType}. */ Q("Q"), - /** Qa financial report type. */ + /** QA {@link FinancialReportType}. */ QA("QA"), - /** T financial report type. */ + /** T {@link FinancialReportType}. */ T("T"), - /** Ta financial report type. */ + /** TA {@link FinancialReportType}. */ TA("TA"); - /** The api name. */ + /** The API name. */ String apiName; /** - * Instantiates a new Financial report type. + * Instantiates a new {@link FinancialReportType}. * * @param apiName the api name */ diff --git a/src/main/java/net/jacobpeterson/polygon/enums/FinancialSort.java b/src/main/java/net/jacobpeterson/polygon/enums/FinancialSort.java index 0e933f85..dfcd7b75 100644 --- a/src/main/java/net/jacobpeterson/polygon/enums/FinancialSort.java +++ b/src/main/java/net/jacobpeterson/polygon/enums/FinancialSort.java @@ -3,27 +3,27 @@ import net.jacobpeterson.abstracts.enums.APIName; /** - * The enum Financial sort. + * The enum {@link FinancialSort}. */ public enum FinancialSort implements APIName { - /** Report period ascending financial sort. */ + /** Report period ascending {@link FinancialSort}. */ REPORT_PERIOD_ASCENDING("reportPeriod"), - /** Report period descending financial sort */ + /** Report period descending {@link FinancialSort} */ REPORT_PERIOD_DESCENDING("-reportPeriod"), - /** Calendar date ascending financial sort. */ + /** Calendar date ascending {@link FinancialSort}. */ CALENDAR_DATE_ASCENDING("calendarDate"), - /** Calendar date descending financial sort. */ + /** Calendar date descending {@link FinancialSort}. */ CALENDAR_DATE_DESCENDING("-calendarDate"); - /** The api name. */ + /** The API name. */ String apiName; /** - * Instantiates a new Financial sort. + * Instantiates a new {@link FinancialSort}. * * @param apiName the api name */ diff --git a/src/main/java/net/jacobpeterson/polygon/enums/GainersLosersDirection.java b/src/main/java/net/jacobpeterson/polygon/enums/GainersLosersDirection.java index 16ce1131..557561ee 100644 --- a/src/main/java/net/jacobpeterson/polygon/enums/GainersLosersDirection.java +++ b/src/main/java/net/jacobpeterson/polygon/enums/GainersLosersDirection.java @@ -2,17 +2,22 @@ import net.jacobpeterson.abstracts.enums.APIName; +/** + * The enum {@link GainersLosersDirection}. + */ public enum GainersLosersDirection implements APIName { + /** Gainers {@link GainersLosersDirection}. */ GAINERS("gainers"), + /** Losers {@link GainersLosersDirection}. */ LOSERS("losers"); - /** The api name. */ + /** The API name. */ String apiName; /** - * Instantiates a new Gainers losers direction. + * Instantiates a new {@link GainersLosersDirection}. * * @param apiName the api name */ diff --git a/src/main/java/net/jacobpeterson/polygon/enums/Market.java b/src/main/java/net/jacobpeterson/polygon/enums/Market.java index 96324552..24baa60d 100644 --- a/src/main/java/net/jacobpeterson/polygon/enums/Market.java +++ b/src/main/java/net/jacobpeterson/polygon/enums/Market.java @@ -3,36 +3,36 @@ import net.jacobpeterson.abstracts.enums.APIName; /** - * The Enum SymbolsMarket. + * The Enum {@link Market}. */ public enum Market implements APIName { - /** Stocks market. */ + /** Stocks {@link Market}. */ STOCKS("stocks"), - /** Indices market. */ + /** Indices {@link Market}. */ INDICES("indices"), - /** Crypto market */ + /** Crypto {@link Market} */ CRYPTO("crypto"), - /** Fx market. */ + /** Forex {@link Market}. */ FX("fx"), - /** Bonds market. */ + /** Bonds {@link Market}. */ BONDS("bonds"), - /** Mf market. */ + /** Mutual Funds {@link Market}. */ MF("mf"), - /** Mmf market. */ + /** Money Market Funds {@link Market}. */ MMF("mmf"); - /** The api name. */ + /** The API name. */ String apiName; /** - * Instantiates a new order type. + * Instantiates a new {@link Market}. * * @param apiName the api name */ diff --git a/src/main/java/net/jacobpeterson/polygon/enums/StockType.java b/src/main/java/net/jacobpeterson/polygon/enums/StockType.java deleted file mode 100644 index d12e3d16..00000000 --- a/src/main/java/net/jacobpeterson/polygon/enums/StockType.java +++ /dev/null @@ -1,32 +0,0 @@ -package net.jacobpeterson.polygon.enums; - -import net.jacobpeterson.abstracts.enums.APIName; - -/** - * The Enum StockType. - */ -public enum StockType implements APIName { - - /** The etfs. */ - ETFS("etp"), - - /** The common stocks. */ - COMMON_STOCKS("cs"); - - /** The api name. */ - String apiName; - - /** - * Instantiates a new stock type. - * - * @param apiName the api name - */ - StockType(String apiName) { - this.apiName = apiName; - } - - @Override - public String getAPIName() { - return apiName; - } -} diff --git a/src/main/java/net/jacobpeterson/polygon/enums/TickerSort.java b/src/main/java/net/jacobpeterson/polygon/enums/TickerSort.java index 8a884dea..a35d0183 100644 --- a/src/main/java/net/jacobpeterson/polygon/enums/TickerSort.java +++ b/src/main/java/net/jacobpeterson/polygon/enums/TickerSort.java @@ -3,27 +3,27 @@ import net.jacobpeterson.abstracts.enums.APIName; /** - * The enum Ticker sort. + * The enum {@link TickerSort}. */ public enum TickerSort implements APIName { - /** The ticker ascending. */ + /** The ticker ascending {@link TickerSort}. */ TICKER_ASCENDING("ticker"), - /** The ticker descending. */ + /** The ticker descending {@link TickerSort}. */ TICKER_DESCENDING("-ticker"), - /** The type ascending. */ + /** The type ascending {@link TickerSort}. */ TYPE_ASCENDING("type"), - /** The type descending. */ + /** The type descending {@link TickerSort}. */ TYPE_DESCENDING("-type"); - /** The api name. */ + /** The API name. */ String apiName; /** - * Instantiates a new order type. + * Instantiates a new {@link TickerSort}. * * @param apiName the api name */ diff --git a/src/main/java/net/jacobpeterson/polygon/enums/Timespan.java b/src/main/java/net/jacobpeterson/polygon/enums/Timespan.java index 83d0f3c9..3212b83b 100644 --- a/src/main/java/net/jacobpeterson/polygon/enums/Timespan.java +++ b/src/main/java/net/jacobpeterson/polygon/enums/Timespan.java @@ -3,36 +3,36 @@ import net.jacobpeterson.abstracts.enums.APIName; /** - * The Enum Timespan. + * The Enum {@link Timespan}. */ public enum Timespan implements APIName { - /** The Minute. */ + /** The Minute {@link Timespan}. */ MINUTE("minute"), - /** The Hour. */ + /** The Hour {@link Timespan}. */ HOUR("hour"), - /** The Day. */ + /** The Day {@link Timespan}. */ DAY("day"), - /** The Week. */ + /** The Week {@link Timespan}. */ WEEK("week"), - /** The Month. */ + /** The Month {@link Timespan}. */ MONTH("month"), - /** The Quarter. */ + /** The Quarter {@link Timespan}. */ QUARTER("quarter"), - /** The Year. */ + /** The Year {@link Timespan}. */ YEAR("year"); - /** The api name. */ + /** The API name. */ String apiName; /** - * Instantiates a new order type. + * Instantiates a new {@link Timespan}. * * @param apiName the api name */ diff --git a/src/main/java/net/jacobpeterson/polygon/properties/PolygonProperties.java b/src/main/java/net/jacobpeterson/polygon/properties/PolygonProperties.java index 3d165e22..fcd506ac 100644 --- a/src/main/java/net/jacobpeterson/polygon/properties/PolygonProperties.java +++ b/src/main/java/net/jacobpeterson/polygon/properties/PolygonProperties.java @@ -7,7 +7,7 @@ import static net.jacobpeterson.util.properties.PropertyUtil.getProperty; /** - * The Class AlpacaProperties. + * The Class {@link PolygonProperties}. */ public class PolygonProperties { diff --git a/src/main/java/net/jacobpeterson/polygon/rest/PolygonRequest.java b/src/main/java/net/jacobpeterson/polygon/rest/PolygonRequest.java index 5ecb3feb..c82d47fd 100644 --- a/src/main/java/net/jacobpeterson/polygon/rest/PolygonRequest.java +++ b/src/main/java/net/jacobpeterson/polygon/rest/PolygonRequest.java @@ -8,7 +8,7 @@ import java.io.InputStream; /** - * The Class PolygonRequest. + * The Class {@link PolygonRequest}. */ public class PolygonRequest extends AbstractRequest { @@ -22,7 +22,7 @@ public class PolygonRequest extends AbstractRequest { private final String keyId; /** - * Instantiates a new polygon request. + * Instantiates a new {@link PolygonRequest}. * * @param keyID the key id */ diff --git a/src/main/java/net/jacobpeterson/polygon/rest/PolygonRequestBuilder.java b/src/main/java/net/jacobpeterson/polygon/rest/PolygonRequestBuilder.java index 2d379643..8e93798c 100644 --- a/src/main/java/net/jacobpeterson/polygon/rest/PolygonRequestBuilder.java +++ b/src/main/java/net/jacobpeterson/polygon/rest/PolygonRequestBuilder.java @@ -5,12 +5,12 @@ import java.util.Arrays; /** - * The Class PolygonRequestBuilder. + * The Class {@link PolygonRequestBuilder}. */ public class PolygonRequestBuilder extends AbstractRequestBuilder { /** - * Instantiates a new Polygon request builder. + * Instantiates a new {@link PolygonRequestBuilder}. * * @param baseUrl the base url * @param apiVersion the api version diff --git a/src/main/java/net/jacobpeterson/polygon/rest/exception/PolygonAPIRequestException.java b/src/main/java/net/jacobpeterson/polygon/rest/exception/PolygonAPIRequestException.java index d2e7328a..6bdbcdc1 100644 --- a/src/main/java/net/jacobpeterson/polygon/rest/exception/PolygonAPIRequestException.java +++ b/src/main/java/net/jacobpeterson/polygon/rest/exception/PolygonAPIRequestException.java @@ -6,12 +6,12 @@ import java.io.InputStream; /** - * The Class PolygonAPIException. + * The Class {@link PolygonAPIRequestException}. */ public class PolygonAPIRequestException extends AbstractAPIRequestException { /** - * Instantiates a new Polygon api request exception. + * Instantiates a new {@link PolygonAPIRequestException}. * * @param httpResponse the http response */ diff --git a/src/main/java/net/jacobpeterson/polygon/websocket/client/PolygonWebsocketClient.java b/src/main/java/net/jacobpeterson/polygon/websocket/client/PolygonWebsocketClient.java index 92e6765b..868c7c6d 100644 --- a/src/main/java/net/jacobpeterson/polygon/websocket/client/PolygonWebsocketClient.java +++ b/src/main/java/net/jacobpeterson/polygon/websocket/client/PolygonWebsocketClient.java @@ -37,7 +37,7 @@ import java.util.stream.Collectors; /** - * The Class WebsocketClient. + * The Class {@link PolygonWebsocketClient}. */ public class PolygonWebsocketClient implements WebsocketClient { @@ -66,7 +66,7 @@ public class PolygonWebsocketClient implements WebsocketClient { private boolean authenticated; /** - * Instantiates a new Polygon websocket client. + * Instantiates a new {@link PolygonWebsocketClient}. * * @param keyId the key id * @param websocketURL the websocket url @@ -382,7 +382,7 @@ private Map> getRegisteredTickerChannels(P } /** - * The enum Polygon stream action. + * The enum {@link PolygonStreamAction}. */ private enum PolygonStreamAction { /** The subscribe. */ @@ -391,11 +391,11 @@ private enum PolygonStreamAction { /** The unsubscribe. */ UNSUBSCRIBE("unsubscribe"); - /** The api name. */ + /** The API name. */ String apiName; /** - * Instantiates a new stream action. + * Instantiates a new {@link PolygonStreamAction}. * * @param apiName the api name */ @@ -404,9 +404,9 @@ private enum PolygonStreamAction { } /** - * Gets api name. + * Gets API name. * - * @return the api name + * @return the API name */ public String getAPIName() { return apiName; diff --git a/src/main/java/net/jacobpeterson/polygon/websocket/client/PolygonWebsocketClientEndpoint.java b/src/main/java/net/jacobpeterson/polygon/websocket/client/PolygonWebsocketClientEndpoint.java index ed214c10..631a40a6 100644 --- a/src/main/java/net/jacobpeterson/polygon/websocket/client/PolygonWebsocketClientEndpoint.java +++ b/src/main/java/net/jacobpeterson/polygon/websocket/client/PolygonWebsocketClientEndpoint.java @@ -13,13 +13,13 @@ import java.net.URI; /** - * The type Polygon websocket client endpoint. + * The type {@link PolygonWebsocketClientEndpoint}. */ @ClientEndpoint(subprotocols = "TEXT") public class PolygonWebsocketClientEndpoint extends AbstractWebsocketClientEndpoint { /** - * Instantiates a new Polygon websocket client endpoint. + * Instantiates a new {@link PolygonWebsocketClientEndpoint}. * * @param websocketClient the websocket client * @param endpointURI the endpoint uri diff --git a/src/main/java/net/jacobpeterson/polygon/websocket/listener/PolygonStreamListenerAdapter.java b/src/main/java/net/jacobpeterson/polygon/websocket/listener/PolygonStreamListenerAdapter.java index abb8f1d4..6d970abb 100644 --- a/src/main/java/net/jacobpeterson/polygon/websocket/listener/PolygonStreamListenerAdapter.java +++ b/src/main/java/net/jacobpeterson/polygon/websocket/listener/PolygonStreamListenerAdapter.java @@ -11,7 +11,7 @@ import java.util.Set; /** - * The Class PolygonStreamListenerAdapter. + * The Class {@link PolygonStreamListenerAdapter}. */ public class PolygonStreamListenerAdapter implements PolygonStreamListener { @@ -19,7 +19,7 @@ public class PolygonStreamListenerAdapter implements PolygonStreamListener { private final Map> stockChannels = new HashMap<>(); /** - * Instantiates a new polygon stream listener adapter. + * Instantiates a new {@link PolygonStreamListenerAdapter}. * * @param ticker the ticker * @param channelTypes the channel types @@ -33,7 +33,7 @@ public PolygonStreamListenerAdapter(String ticker, PolygonStreamMessageType... c } /** - * Instantiates a new polygon stream listener adapter. + * Instantiates a new {@link PolygonStreamListenerAdapter}. * * @param tickers the tickers * @param channelTypes the channel types @@ -49,7 +49,7 @@ public PolygonStreamListenerAdapter(Set tickers, PolygonStreamMessageTyp } /** - * Instantiates a new polygon stream listener adapter. + * Instantiates a new {@link PolygonStreamListenerAdapter}. * * @param stockChannels the stock channel types */ diff --git a/src/main/java/net/jacobpeterson/polygon/websocket/message/PolygonStreamMessageType.java b/src/main/java/net/jacobpeterson/polygon/websocket/message/PolygonStreamMessageType.java index 18cce2e6..64d651bb 100644 --- a/src/main/java/net/jacobpeterson/polygon/websocket/message/PolygonStreamMessageType.java +++ b/src/main/java/net/jacobpeterson/polygon/websocket/message/PolygonStreamMessageType.java @@ -6,35 +6,35 @@ import net.jacobpeterson.util.gson.GsonUtil; /** - * The enum Polygon stream message type. + * The enum {@link PolygonStreamMessageType}. */ public enum PolygonStreamMessageType implements StreamMessageType, APIName { - /** Status polygon stream message type. */ + /** Status {@link PolygonStreamMessageType}. */ @SerializedName("status") STATUS(false), - /** Trade polygon stream message type. */ + /** Trade {@link PolygonStreamMessageType}. */ @SerializedName("T") TRADE(true), - /** Quote polygon stream message type. */ + /** Quote {@link PolygonStreamMessageType}. */ @SerializedName("Q") QUOTE(true), - /** Aggregate per second polygon stream message type. */ + /** Aggregate per second {@link PolygonStreamMessageType}. */ @SerializedName("A") AGGREGATE_PER_SECOND(true), - /** Aggregate per minute polygon stream message type. */ + /** Aggregate per minute {@link PolygonStreamMessageType}. */ @SerializedName("AM") AGGREGATE_PER_MINUTE(true); - /** The is api subscribable. */ + /** Is API subscribable. */ private final boolean isAPISubscribable; /** - * Instantiates a new Polygon stream message type. + * Instantiates a new {@link PolygonStreamMessageType}. * * @param isAPISubscribable the is api subscribable */ @@ -48,7 +48,7 @@ public String getAPIName() { } /** - * Is api subscribable boolean. + * Is API subscribable boolean. * * @return the boolean */ diff --git a/src/main/java/net/jacobpeterson/util/concurrency/ExecutorTracer.java b/src/main/java/net/jacobpeterson/util/concurrency/ExecutorTracer.java index 5d5dad80..4bc0fefb 100644 --- a/src/main/java/net/jacobpeterson/util/concurrency/ExecutorTracer.java +++ b/src/main/java/net/jacobpeterson/util/concurrency/ExecutorTracer.java @@ -16,12 +16,12 @@ import java.util.concurrent.TimeoutException; /** - * The Class ExecutorTracer. + * The Class {@link ExecutorTracer}. */ public class ExecutorTracer { /** - * Instantiates a new executor tracer. + * Instantiates a new {@link ExecutorTracer}. */ private ExecutorTracer() { } @@ -129,7 +129,7 @@ public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize, } /** - * The Class DelegatedExecutorService. + * The Class {@link DelegatedExecutorService}. */ public static class DelegatedExecutorService extends AbstractExecutorService { @@ -272,7 +272,7 @@ public T invokeAny(Collection> tasks, long timeout, Ti } /** - * The Class FinalizableDelegatedExecutorService. + * The Class {@link FinalizableDelegatedExecutorService}. */ public static class FinalizableDelegatedExecutorService extends DelegatedExecutorService { @@ -296,7 +296,7 @@ protected void finalize() { } /** - * The Class DelegatedScheduledExecutorService. + * The Class {@link DelegatedScheduledExecutorService}. */ static class DelegatedScheduledExecutorService extends DelegatedExecutorService implements ScheduledExecutorService { diff --git a/src/main/java/net/jacobpeterson/util/concurrency/ScheduledThreadPoolExecutorTracer.java b/src/main/java/net/jacobpeterson/util/concurrency/ScheduledThreadPoolExecutorTracer.java index 1b9de9e5..9f3d6d99 100644 --- a/src/main/java/net/jacobpeterson/util/concurrency/ScheduledThreadPoolExecutorTracer.java +++ b/src/main/java/net/jacobpeterson/util/concurrency/ScheduledThreadPoolExecutorTracer.java @@ -7,12 +7,12 @@ import java.util.concurrent.ThreadFactory; /** - * The Class ScheduledThreadPoolExecutorTracer. + * The Class {@link ScheduledThreadPoolExecutorTracer}. */ class ScheduledThreadPoolExecutorTracer extends ScheduledThreadPoolExecutor { /** - * Instantiates a new scheduled thread pool executor tracer. + * Instantiates a new {@link ScheduledThreadPoolExecutorTracer}. * * @param corePoolSize the core pool size * @param threadFactory the thread factory @@ -22,7 +22,7 @@ public ScheduledThreadPoolExecutorTracer(int corePoolSize, ThreadFactory threadF } /** - * Instantiates a new scheduled thread pool executor tracer. + * Instantiates a new {@link ScheduledThreadPoolExecutorTracer}. * * @param corePoolSize the core pool size */ diff --git a/src/main/java/net/jacobpeterson/util/concurrency/ThreadPoolExecutorTracer.java b/src/main/java/net/jacobpeterson/util/concurrency/ThreadPoolExecutorTracer.java index 3109e7bf..57f8e633 100644 --- a/src/main/java/net/jacobpeterson/util/concurrency/ThreadPoolExecutorTracer.java +++ b/src/main/java/net/jacobpeterson/util/concurrency/ThreadPoolExecutorTracer.java @@ -9,12 +9,12 @@ import java.util.concurrent.TimeUnit; /** - * The Class ThreadPoolExecutorTracer. + * The Class {@link ThreadPoolExecutorTracer}. */ public class ThreadPoolExecutorTracer extends ThreadPoolExecutor { /** - * Instantiates a new thread pool executor tracer. + * Instantiates a new {@link ThreadPoolExecutorTracer}. * * @param corePoolSize the core pool size * @param maximumPoolSize the maximum pool size @@ -28,7 +28,7 @@ public ThreadPoolExecutorTracer(int corePoolSize, int maximumPoolSize, long keep } /** - * Instantiates a new thread pool executor tracer. + * Instantiates a new {@link ThreadPoolExecutorTracer}. * * @param corePoolSize the core pool size * @param maximumPoolSize the maximum pool size diff --git a/src/main/java/net/jacobpeterson/util/doc/Documentation2JSONSchema.java b/src/main/java/net/jacobpeterson/util/doc/Documentation2JSONSchema.java index 5deab579..2fc23c8f 100644 --- a/src/main/java/net/jacobpeterson/util/doc/Documentation2JSONSchema.java +++ b/src/main/java/net/jacobpeterson/util/doc/Documentation2JSONSchema.java @@ -15,6 +15,9 @@ import java.io.ByteArrayInputStream; import java.io.IOException; +/** + * The type {@link Documentation2JSONSchema}. + */ public class Documentation2JSONSchema { /** diff --git a/src/main/java/net/jacobpeterson/util/format/FormatUtil.java b/src/main/java/net/jacobpeterson/util/format/FormatUtil.java index 0af76a5b..0febc589 100644 --- a/src/main/java/net/jacobpeterson/util/format/FormatUtil.java +++ b/src/main/java/net/jacobpeterson/util/format/FormatUtil.java @@ -9,7 +9,7 @@ import java.time.format.DateTimeFormatter; /** - * The Class FormatUtil. + * The Class {@link FormatUtil}. */ public class FormatUtil { diff --git a/src/main/java/net/jacobpeterson/util/gson/GsonUtil.java b/src/main/java/net/jacobpeterson/util/gson/GsonUtil.java index 6d3c03cc..50873111 100644 --- a/src/main/java/net/jacobpeterson/util/gson/GsonUtil.java +++ b/src/main/java/net/jacobpeterson/util/gson/GsonUtil.java @@ -16,13 +16,16 @@ import java.util.List; import java.util.Set; +/** + * The type {@link GsonUtil}. + */ public class GsonUtil { /** The logger. */ private static final Logger LOGGER = LogManager.getLogger(GsonUtil.class); /** The constant CLASS_ANNOTATION_CACHE. */ - private static final HashMap> CLASS_ANNOTATION_CACHE = new HashMap<>(); + private static final HashMap, ArrayList> CLASS_ANNOTATION_CACHE = new HashMap<>(); /** The constant GSON which include ISO date time to ZonedDateTime objects */ public static final Gson GSON = new GsonBuilder() @@ -43,7 +46,7 @@ public class GsonUtil { * * @return the boolean */ - public static boolean doesGsonPOJOMatch(Class jsonPOJOClass, JsonObject jsonObject) { + public static boolean doesGsonPOJOMatch(Class jsonPOJOClass, JsonObject jsonObject) { ArrayList gsonSerializedNameAnnotations = getGsonSerializedNameAnnotations(jsonPOJOClass); Set jsonObjectKeys = jsonObject.keySet(); @@ -65,7 +68,7 @@ public static boolean doesGsonPOJOMatch(Class jsonPOJOClass, JsonObject jsonObje * * @return the gson serialized name annotations */ - private static synchronized ArrayList getGsonSerializedNameAnnotations(Class theClass) { + private static synchronized ArrayList getGsonSerializedNameAnnotations(Class theClass) { // Use a caching system because Reflection can be quite expensive if (CLASS_ANNOTATION_CACHE.containsKey(theClass)) { return CLASS_ANNOTATION_CACHE.get(theClass); @@ -77,7 +80,7 @@ private static synchronized ArrayList getGsonSerializedNameAnnot ArrayList serializedNameAnnotations = new ArrayList<>(); - Class currentClass = theClass; + Class currentClass = theClass; do { for (Field field : currentClass.getDeclaredFields()) { // Loop through all the fields for (Annotation annotation : field.getDeclaredAnnotations()) { // Loop through all the field annotations diff --git a/src/main/java/net/jacobpeterson/util/gson/ZonedDateTimeAdapter.java b/src/main/java/net/jacobpeterson/util/gson/ZonedDateTimeAdapter.java index c7794564..f33c58cb 100644 --- a/src/main/java/net/jacobpeterson/util/gson/ZonedDateTimeAdapter.java +++ b/src/main/java/net/jacobpeterson/util/gson/ZonedDateTimeAdapter.java @@ -13,7 +13,7 @@ import java.time.format.DateTimeFormatter; /** - * The GSON ZonedDateTime (de)serialization adapter. + * The GSON {@link ZonedDateTimeAdapter} (de)serialization adapter. */ public class ZonedDateTimeAdapter implements JsonSerializer, JsonDeserializer { diff --git a/src/main/java/net/jacobpeterson/util/properties/PropertyUtil.java b/src/main/java/net/jacobpeterson/util/properties/PropertyUtil.java index 3ff46855..c619baf0 100644 --- a/src/main/java/net/jacobpeterson/util/properties/PropertyUtil.java +++ b/src/main/java/net/jacobpeterson/util/properties/PropertyUtil.java @@ -11,7 +11,7 @@ import java.util.Properties; /** - * The type Property util. + * The type {@link PropertyUtil}. */ public class PropertyUtil { From d3ee31258a4303faf36afd965cdd948e99c0c02a Mon Sep 17 00:00:00 2001 From: Jacob Peterson Date: Fri, 11 Dec 2020 12:40:39 -0700 Subject: [PATCH 062/271] Fix getDailyOpenClose --- .../daily_open_close/daily_open_close.json | 46 +++++++++++++++++++ .../daily_open_close_response.json | 22 --------- .../daily_open_close_trade.json | 38 --------------- .../net/jacobpeterson/polygon/PolygonAPI.java | 9 ++-- .../net/jacobpeterson/util/gson/GsonUtil.java | 2 + .../util/gson/LocalDateAdapter.java | 30 ++++++++++++ 6 files changed, 83 insertions(+), 64 deletions(-) create mode 100644 schema_json/polygon/daily_open_close/daily_open_close.json delete mode 100644 schema_json/polygon/daily_open_close/daily_open_close_response.json delete mode 100644 schema_json/polygon/daily_open_close/daily_open_close_trade.json create mode 100644 src/main/java/net/jacobpeterson/util/gson/LocalDateAdapter.java diff --git a/schema_json/polygon/daily_open_close/daily_open_close.json b/schema_json/polygon/daily_open_close/daily_open_close.json new file mode 100644 index 00000000..f81fc790 --- /dev/null +++ b/schema_json/polygon/daily_open_close/daily_open_close.json @@ -0,0 +1,46 @@ +{ + "type": "object", + "title": "See https://polygon.io/docs/get_v1_open-close__stocksTicker___date__anchor", + "properties": { + "status": { + "existingJavaType": "java.lang.String", + "title": "The status of this request's response." + }, + "from": { + "existingJavaType": "java.time.LocalDate", + "title": "The requested date." + }, + "symbol": { + "existingJavaType": "java.lang.String", + "title": "The exchange symbol that this item is traded under." + }, + "open": { + "existingJavaType": "java.lang.Double", + "title": "The open price for the symbol in the given time period." + }, + "high": { + "existingJavaType": "java.lang.Double", + "title": "The highest price for the symbol in the given time period." + }, + "low": { + "existingJavaType": "java.lang.Double", + "title": "The lowest price for the symbol in the given time period." + }, + "close": { + "existingJavaType": "java.lang.Double", + "title": "The close price for the symbol in the given time period." + }, + "volume": { + "existingJavaType": "java.lang.Long", + "title": "The trading volume of the symbol in the given time period." + }, + "preMarket": { + "existingJavaType": "java.lang.Double", + "title": "The open price of the ticker symbol in pre-market trading." + }, + "afterHours": { + "existingJavaType": "java.lang.Double", + "title": "The close price of the ticker symbol in after hours trading." + } + } +} diff --git a/schema_json/polygon/daily_open_close/daily_open_close_response.json b/schema_json/polygon/daily_open_close/daily_open_close_response.json deleted file mode 100644 index 8b2a3dea..00000000 --- a/schema_json/polygon/daily_open_close/daily_open_close_response.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "type": "object", - "title": "See https://polygon.io/docs/#!/Stocks--Equities/get_v1_open_close_symbol_date", - "properties": { - "symbol": { - "existingJavaType": "java.lang.String", - "title": "Symbol that was evaluated from the request" - }, - "open": { - "existingJavaType": "net.jacobpeterson.domain.polygon.dailyopenclose.DailyOpenCloseTrade", - "title": "open" - }, - "close": { - "existingJavaType": "net.jacobpeterson.domain.polygon.dailyopenclose.DailyOpenCloseTrade", - "title": "close" - }, - "afterHours": { - "existingJavaType": "net.jacobpeterson.domain.polygon.dailyopenclose.DailyOpenCloseTrade", - "title": "afterHours" - } - } -} diff --git a/schema_json/polygon/daily_open_close/daily_open_close_trade.json b/schema_json/polygon/daily_open_close/daily_open_close_trade.json deleted file mode 100644 index f4e650e9..00000000 --- a/schema_json/polygon/daily_open_close/daily_open_close_trade.json +++ /dev/null @@ -1,38 +0,0 @@ -{ - "type": "object", - "title": "See https://polygon.io/docs/#!/Stocks--Equities/get_v1_open_close_symbol_date", - "properties": { - "condition1": { - "existingJavaType": "java.lang.Integer", - "title": "Condition 1 of this trade" - }, - "condition2": { - "existingJavaType": "java.lang.Integer", - "title": "Condition 2 of this trade" - }, - "condition3": { - "existingJavaType": "java.lang.Integer", - "title": "Condition 3 of this trade" - }, - "condition4": { - "existingJavaType": "java.lang.Integer", - "title": "Condition 4 of this trade" - }, - "exchange": { - "existingJavaType": "java.lang.Integer", - "title": "The exchange this trade happened on" - }, - "price": { - "existingJavaType": "java.lang.Double", - "title": "Price of the trade" - }, - "size": { - "existingJavaType": "java.lang.Integer", - "title": "Size of the trade" - }, - "timestamp": { - "existingJavaType": "java.time.ZonedDateTime", - "title": "Timestamp of this trade" - } - } -} diff --git a/src/main/java/net/jacobpeterson/polygon/PolygonAPI.java b/src/main/java/net/jacobpeterson/polygon/PolygonAPI.java index 14c4842b..e53f550b 100644 --- a/src/main/java/net/jacobpeterson/polygon/PolygonAPI.java +++ b/src/main/java/net/jacobpeterson/polygon/PolygonAPI.java @@ -6,7 +6,7 @@ import net.jacobpeterson.abstracts.websocket.exception.WebsocketException; import net.jacobpeterson.domain.polygon.aggregates.AggregatesResponse; import net.jacobpeterson.domain.polygon.conditionsmapping.ConditionsMapping; -import net.jacobpeterson.domain.polygon.dailyopenclose.DailyOpenCloseResponse; +import net.jacobpeterson.domain.polygon.dailyopenclose.DailyOpenClose; import net.jacobpeterson.domain.polygon.exchanges.Exchange; import net.jacobpeterson.domain.polygon.groupeddaily.GroupedDailyResponse; import net.jacobpeterson.domain.polygon.historicquotes.HistoricQuotesResponse; @@ -53,7 +53,8 @@ import java.util.StringJoiner; /** - * The class {@link PolygonAPI}. Note that most of these methods are blocking methods and this class in NOT thread-safe. + * The class {@link PolygonAPI}. Note that most of these methods are blocking methods and this class in NOT + * thread-safe. */ public class PolygonAPI { @@ -651,7 +652,7 @@ public LastQuoteResponse getLastQuote(String symbol) throws PolygonAPIRequestExc * @throws PolygonAPIRequestException the polygon API exception * @see Daily Open/Close */ - public DailyOpenCloseResponse getDailyOpenClose(String symbol, LocalDate date) throws PolygonAPIRequestException { + public DailyOpenClose getDailyOpenClose(String symbol, LocalDate date) throws PolygonAPIRequestException { Preconditions.checkNotNull(symbol); Preconditions.checkNotNull(date); @@ -666,7 +667,7 @@ public DailyOpenCloseResponse getDailyOpenClose(String symbol, LocalDate date) t throw new PolygonAPIRequestException(response); } - return polygonRequest.getResponseObject(response, DailyOpenCloseResponse.class); + return polygonRequest.getResponseObject(response, DailyOpenClose.class); } /** diff --git a/src/main/java/net/jacobpeterson/util/gson/GsonUtil.java b/src/main/java/net/jacobpeterson/util/gson/GsonUtil.java index 50873111..3855faba 100644 --- a/src/main/java/net/jacobpeterson/util/gson/GsonUtil.java +++ b/src/main/java/net/jacobpeterson/util/gson/GsonUtil.java @@ -10,6 +10,7 @@ import java.lang.annotation.Annotation; import java.lang.reflect.Field; +import java.time.LocalDate; import java.time.ZonedDateTime; import java.util.ArrayList; import java.util.HashMap; @@ -30,6 +31,7 @@ public class GsonUtil { /** The constant GSON which include ISO date time to ZonedDateTime objects */ public static final Gson GSON = new GsonBuilder() .registerTypeAdapter(ZonedDateTime.class, new ZonedDateTimeAdapter()) + .registerTypeAdapter(LocalDate.class, new LocalDateAdapter()) .enableComplexMapKeySerialization() .setLenient() .create(); diff --git a/src/main/java/net/jacobpeterson/util/gson/LocalDateAdapter.java b/src/main/java/net/jacobpeterson/util/gson/LocalDateAdapter.java new file mode 100644 index 00000000..2799c1f2 --- /dev/null +++ b/src/main/java/net/jacobpeterson/util/gson/LocalDateAdapter.java @@ -0,0 +1,30 @@ +package net.jacobpeterson.util.gson; + +import com.google.gson.JsonDeserializationContext; +import com.google.gson.JsonDeserializer; +import com.google.gson.JsonElement; +import com.google.gson.JsonParseException; +import com.google.gson.JsonPrimitive; +import com.google.gson.JsonSerializationContext; +import com.google.gson.JsonSerializer; + +import java.lang.reflect.Type; +import java.time.LocalDate; +import java.time.format.DateTimeFormatter; + +/** + * The GSON {@link LocalDateAdapter} (de)serialization adapter. + */ +public class LocalDateAdapter implements JsonSerializer, JsonDeserializer { + + @Override + public JsonElement serialize(LocalDate src, Type typeOfSrc, JsonSerializationContext context) { + return new JsonPrimitive(src.format(DateTimeFormatter.ISO_LOCAL_DATE)); + } + + @Override + public LocalDate deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) + throws JsonParseException { + return LocalDate.parse(json.getAsJsonPrimitive().getAsString(), DateTimeFormatter.ISO_LOCAL_DATE); + } +} From 95289fdcd2005269b7b06df286caf4cc9b277069 Mon Sep 17 00:00:00 2001 From: Jacob Peterson Date: Wed, 30 Dec 2020 12:46:28 -0700 Subject: [PATCH 063/271] Fix OrderType javadoc --- src/main/java/net/jacobpeterson/alpaca/enums/OrderType.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/net/jacobpeterson/alpaca/enums/OrderType.java b/src/main/java/net/jacobpeterson/alpaca/enums/OrderType.java index 1413fdcb..1b371ac8 100644 --- a/src/main/java/net/jacobpeterson/alpaca/enums/OrderType.java +++ b/src/main/java/net/jacobpeterson/alpaca/enums/OrderType.java @@ -27,8 +27,8 @@ public enum OrderType implements APIName { * A stop (market) order is an order to buy or sell a security when its price moves past a particular point, * ensuring a higher probability of achieving a predetermined entry or exit price. Once the market price crosses the * specified stop price, the stop order becomes a market order. Alpaca converts buy stop orders into stop limit - * orders with a limit price that is 4% higher than a stop price < $50 (or 2.5% higher than a stop price >= $50). - * Sell stop orders are not converted into stop limit orders. + * orders with a limit price that is 4% higher than a stop price < $50 (or 2.5% higher than a stop price >= + * $50). Sell stop orders are not converted into stop limit orders. */ STOP("stop"), From bd4b3836a01cf6363849515e9ea1990b1960e4f7 Mon Sep 17 00:00:00 2001 From: Sean Miller Date: Wed, 30 Dec 2020 16:11:40 -0600 Subject: [PATCH 064/271] Added List of Symbols to getOrders() --- src/main/java/net/jacobpeterson/alpaca/AlpacaAPI.java | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/main/java/net/jacobpeterson/alpaca/AlpacaAPI.java b/src/main/java/net/jacobpeterson/alpaca/AlpacaAPI.java index 61219ce6..3b125ac1 100644 --- a/src/main/java/net/jacobpeterson/alpaca/AlpacaAPI.java +++ b/src/main/java/net/jacobpeterson/alpaca/AlpacaAPI.java @@ -342,6 +342,7 @@ public AccountConfiguration setAccountConfiguration(AccountConfiguration account * @param direction The chronological order of response based on the submission time. asc or desc. Defaults to * desc. * @param nested If true, the result will roll up multi-leg orders under the legs field of primary order. + * @param symbols Optional, a list of symbols to only return orders for those symbols passed in * * @return the orders * @@ -349,7 +350,7 @@ public AccountConfiguration setAccountConfiguration(AccountConfiguration account * @see Orders */ public ArrayList getOrders(OrderStatus status, Integer limit, ZonedDateTime after, ZonedDateTime until, - Direction direction, Boolean nested) throws AlpacaAPIRequestException { + Direction direction, Boolean nested, ArrayList symbols) throws AlpacaAPIRequestException { AlpacaRequestBuilder urlBuilder = new AlpacaRequestBuilder(baseAPIURL, apiVersion, AlpacaConstants.ORDERS_ENDPOINT); @@ -379,6 +380,11 @@ public ArrayList getOrders(OrderStatus status, Integer limit, ZonedDateTi urlBuilder.appendURLParameter(AlpacaConstants.NESTED_PARAMETER, nested.toString()); } + if (symbols != null) { + urlBuilder.appendURLParameter(AlpacaConstants.SYMBOLS_PARAMETER, symbols.toString(). + replace(", ", ",").replaceAll("[\\[.\\]]", "")); + } + HttpResponse response = alpacaRequest.invokeGet(urlBuilder); if (response.getStatus() != 200) { From f26f54adc01dcc60abb0c944570a445477485972 Mon Sep 17 00:00:00 2001 From: Jacob Peterson Date: Tue, 5 Jan 2021 02:29:23 -0700 Subject: [PATCH 065/271] Use SLF4j instead of Log4j --- build.gradle | 61 ++++++++++--------- gradle/wrapper/gradle-wrapper.properties | 2 +- .../abstracts/rest/AbstractRequest.java | 6 +- .../AbstractWebsocketClientEndpoint.java | 12 ++-- .../net/jacobpeterson/alpaca/AlpacaAPI.java | 6 +- .../broker/client/AlpacaWebsocketClient.java | 10 +-- .../client/MarketDataWebsocketClient.java | 16 ++--- .../net/jacobpeterson/polygon/PolygonAPI.java | 6 +- .../client/PolygonWebsocketClient.java | 12 ++-- .../net/jacobpeterson/util/gson/GsonUtil.java | 6 +- .../util/properties/PropertyUtil.java | 6 +- src/main/resources/log4j2.properties | 12 ---- 12 files changed, 73 insertions(+), 82 deletions(-) delete mode 100644 src/main/resources/log4j2.properties diff --git a/build.gradle b/build.gradle index 78bfc3ec..2fc7c7e8 100644 --- a/build.gradle +++ b/build.gradle @@ -7,8 +7,8 @@ buildscript { } dependencies { - classpath 'com.bmuschko:gradle-nexus-plugin:2.3.1' - classpath 'org.jsonschema2pojo:jsonschema2pojo-gradle-plugin:1.0.2' + classpath group: 'com.bmuschko', name: 'gradle-nexus-plugin', version: '2.3.1' + classpath group: 'org.jsonschema2pojo', name: 'jsonschema2pojo-gradle-plugin', version: '1.0.2' } } @@ -20,9 +20,9 @@ plugins { id 'io.codearte.nexus-staging' version '0.11.0' } -// Calling 'apply' here because jsonschema2pojo doesn't exist in Gradle Central Plugin Repository +// Calling 'apply' here because 'jsonschema2pojo' doesn't exist in Gradle Central Plugin Repository apply plugin: 'jsonschema2pojo' -tasks.getByName("generateJsonSchema2Pojo").enabled(false) // Disable default jsonSchema2Pojo task +tasks.getByName("generateJsonSchema2Pojo").enabled(false) // Disable default 'jsonSchema2Pojo' task archivesBaseName = 'alpaca-java' group = 'net.jacobpeterson' @@ -35,15 +35,20 @@ repositories { } dependencies { - compile 'com.google.guava:guava:20.0' - compile "com.mashape.unirest:unirest-java:1.4.9" - compile "com.google.code.gson:gson:2.8.5" - compile "commons-lang:commons-lang:2.6" - compile 'org.apache.logging.log4j:log4j-api:2.11.1' - compile 'org.apache.logging.log4j:log4j-core:2.11.1' - compile "org.eclipse.jetty.websocket:javax-websocket-client-impl:9.4.28.v20200408" + implementation group: 'org.slf4j', name: 'slf4j-api', version: '1.7.30' + // This is a bridge for the Jakarta Commons Logging library used in dependencies to actually use SLF4j instead + implementation group: 'org.slf4j', name: 'jcl-over-slf4j', version: '1.7.30' + implementation group: 'com.google.guava', name: 'guava', version: '20.0' + implementation group: 'com.mashape.unirest', name: 'unirest-java', version: '1.4.9' + implementation group: 'com.google.code.gson', name: 'gson', version: '2.8.5' + implementation group: 'org.eclipse.jetty.websocket', name: 'javax-websocket-client-impl', version: '9.4.28.v20200408' - testCompile 'junit:junit:4.12' + testImplementation group: 'junit', name: 'junit', version: '4.12' +} + +// Exclude any SLF4j-implementation transitive dependencies so users can use a logging framework of their choice +configurations.implementation { + exclude group: 'commons-logging', module: 'commons-logging' } // @@ -51,10 +56,11 @@ dependencies { // sourceCompatibility = 1.8 targetCompatibility = 1.8 -[compileJava, compileTestJava, javadoc]*.options*.encoding = 'UTF-8' +[compileJava, compileTestJava]*.options*.encoding = 'UTF-8' // Used to suppress java doc warnings javadoc.options.addStringOption('Xdoclint:none', '-quiet') +// Explicitly define Charset for Javadocs javadoc.options.addStringOption("charset", "UTF-8") // // END Build options @@ -82,7 +88,6 @@ tasks.withType(AbstractArchiveTask) { // // START publishing // - modifyPom { project { name = 'alpaca-java' @@ -144,14 +149,15 @@ processResources { // // START allJar task // -// Used for creating a fat jar (a jar packaged with dependencies) -task allJar(type: Jar) { +// Used for creating a fat jar (a jar packaged with all dependencies) +task fatJar(type: Jar) { from { configurations.compile.collect { it.isDirectory() ? it : zipTree(it) } + configurations.implemenation.collect { it.isDirectory() ? it : zipTree(it) } configurations.runtimeClasspath.collect { it.isDirectory() ? it : zipTree(it) } } - archiveClassifier.set "all" + archiveClassifier.set "fat" with jar } @@ -165,9 +171,15 @@ task allJar(type: Jar) { // The generated POJOs will be in a package structure analogous to the path in the 'schema_json/' directory // See: https://github.com/joelittlejohn/jsonschema2pojo/wiki/Reference task generatePOJOs() { - final def targetDirectory = "${project.buildDir}" + File.separator + "generated-sources" + File.separator + "schemajson" - final def jsonSourceDirectory = "${project.projectDir}" + File.separator + "schema_json" + File.separator - final def jsonPackageNameStart = "net.jacobpeterson.domain." + final def targetDirectory = file("${project.buildDir}/generated-sources/schemajson").getPath() + final def jsonSourceDirectory = file("${project.projectDir}/schema_json").getPath() + final def jsonPackageNameStart = "net.jacobpeterson.domain" + + // Configure this task to participate in incremental builds and build caching so that it only executes when changes occur + configure { + inputs.files(fileTree(targetDirectory)).withPathSensitivity(PathSensitivity.RELATIVE) + outputs.cacheIf { true } + } // Loop through all files in schema JSON file tree file(jsonSourceDirectory).eachFileRecurse { jsonFile -> @@ -207,23 +219,14 @@ sourceSets { jsonSchema2Pojo { includeAdditionalProperties = true - targetDirectory = file('build/generated-sources/schemajson') - propertyWordDelimiters = ['-', '_'] as char[] - annotationStyle = 'gson' - sourceType = 'jsonschema' - customDateTimePattern = "yyyy-MM-ddTHH:mm:ssZ" - includeConstructors = true - serializable = true - includeGetters = true - includeSetters = true } // diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 33682bbb..1f3fdbc5 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,5 +1,5 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-6.6.1-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-6.7.1-all.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/src/main/java/net/jacobpeterson/abstracts/rest/AbstractRequest.java b/src/main/java/net/jacobpeterson/abstracts/rest/AbstractRequest.java index 36f3a817..2d0033dd 100644 --- a/src/main/java/net/jacobpeterson/abstracts/rest/AbstractRequest.java +++ b/src/main/java/net/jacobpeterson/abstracts/rest/AbstractRequest.java @@ -8,8 +8,8 @@ import com.mashape.unirest.request.GetRequest; import com.mashape.unirest.request.HttpRequestWithBody; import net.jacobpeterson.util.gson.GsonUtil; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import java.io.InputStream; import java.io.InputStreamReader; @@ -23,7 +23,7 @@ public abstract class AbstractRequest { /** The logger. */ - private static final Logger LOGGER = LogManager.getLogger(AbstractRequest.class); + private static final Logger LOGGER = LoggerFactory.getLogger(AbstractRequest.class); /** The headers. */ protected final Map headers = new HashMap<>(); diff --git a/src/main/java/net/jacobpeterson/abstracts/websocket/client/AbstractWebsocketClientEndpoint.java b/src/main/java/net/jacobpeterson/abstracts/websocket/client/AbstractWebsocketClientEndpoint.java index 4b8a8a12..38a56ab0 100644 --- a/src/main/java/net/jacobpeterson/abstracts/websocket/client/AbstractWebsocketClientEndpoint.java +++ b/src/main/java/net/jacobpeterson/abstracts/websocket/client/AbstractWebsocketClientEndpoint.java @@ -1,9 +1,9 @@ package net.jacobpeterson.abstracts.websocket.client; import net.jacobpeterson.util.concurrency.ExecutorTracer; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; import org.eclipse.jetty.util.component.LifeCycle; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import javax.websocket.CloseReason; import javax.websocket.ContainerProvider; @@ -25,7 +25,7 @@ public abstract class AbstractWebsocketClientEndpoint { /** The constant LOGGER. */ - private static final Logger LOGGER = LogManager.getLogger(AbstractWebsocketClientEndpoint.class); + private static final Logger LOGGER = LoggerFactory.getLogger(AbstractWebsocketClientEndpoint.class); /** The Websocket client. */ private final WebsocketClient websocketClient; @@ -130,8 +130,8 @@ protected void onClose(Session userSession, CloseReason reason) { try { connect(); websocketClient.handleResubscribing(); - } catch (Exception e) { - LOGGER.catching(e); + } catch (Exception exception) { + LOGGER.error("Could not reconnect!", exception); } } else { this.userSession = null; @@ -154,7 +154,7 @@ protected void onMessage(String message) { * @param throwable the throwable */ protected void onError(Throwable throwable) { - LOGGER.error(throwable); + LOGGER.error("Websocket Error!", throwable); } /** diff --git a/src/main/java/net/jacobpeterson/alpaca/AlpacaAPI.java b/src/main/java/net/jacobpeterson/alpaca/AlpacaAPI.java index f4d70521..d2ee8d32 100644 --- a/src/main/java/net/jacobpeterson/alpaca/AlpacaAPI.java +++ b/src/main/java/net/jacobpeterson/alpaca/AlpacaAPI.java @@ -46,8 +46,8 @@ import net.jacobpeterson.domain.alpaca.watchlist.Watchlist; import net.jacobpeterson.util.format.FormatUtil; import net.jacobpeterson.util.gson.GsonUtil; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import java.io.InputStream; import java.lang.reflect.Type; @@ -69,7 +69,7 @@ public class AlpacaAPI { /** The logger. */ - private static final Logger LOGGER = LogManager.getLogger(AlpacaAPI.class); + private static final Logger LOGGER = LoggerFactory.getLogger(AlpacaAPI.class); /** The key ID. */ private final String keyID; diff --git a/src/main/java/net/jacobpeterson/alpaca/websocket/broker/client/AlpacaWebsocketClient.java b/src/main/java/net/jacobpeterson/alpaca/websocket/broker/client/AlpacaWebsocketClient.java index 81b9b2c2..1bc08c9f 100644 --- a/src/main/java/net/jacobpeterson/alpaca/websocket/broker/client/AlpacaWebsocketClient.java +++ b/src/main/java/net/jacobpeterson/alpaca/websocket/broker/client/AlpacaWebsocketClient.java @@ -19,8 +19,8 @@ import net.jacobpeterson.domain.alpaca.streaming.listening.ListeningMessage; import net.jacobpeterson.domain.alpaca.streaming.trade.TradeUpdateMessage; import net.jacobpeterson.util.gson.GsonUtil; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import javax.websocket.DeploymentException; import java.io.IOException; @@ -38,7 +38,7 @@ public class AlpacaWebsocketClient implements WebsocketClient { /** The logger. */ - private static final Logger LOGGER = LogManager.getLogger(AlpacaWebsocketClient.class); + private static final Logger LOGGER = LoggerFactory.getLogger(AlpacaWebsocketClient.class); /** The constant STREAM_KEY. */ private static final String STREAM_KEY = "stream"; @@ -205,14 +205,14 @@ public void handleWebsocketMessage(String message) { authenticated = isAuthorizationMessageSuccess(authorizationMessage); - LOGGER.debug(authorizationMessage); + if (LOGGER.isDebugEnabled()) { LOGGER.debug(authorizationMessage.toString()); } break; case LISTENING: ListeningMessage listeningMessage = GsonUtil.GSON.fromJson(messageJsonObject, ListeningMessage.class); sendStreamMessageToListeners(alpacaStreamMessageType, listeningMessage); - LOGGER.debug(listeningMessage); + if (LOGGER.isDebugEnabled()) { LOGGER.debug(listeningMessage.toString()); } break; case TRADE_UPDATES: sendStreamMessageToListeners(alpacaStreamMessageType, GsonUtil.GSON.fromJson(messageJsonObject, diff --git a/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/client/MarketDataWebsocketClient.java b/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/client/MarketDataWebsocketClient.java index e6053a09..6a189daf 100644 --- a/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/client/MarketDataWebsocketClient.java +++ b/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/client/MarketDataWebsocketClient.java @@ -22,8 +22,8 @@ import net.jacobpeterson.domain.alpaca.marketdata.streaming.quote.QuoteMessage; import net.jacobpeterson.domain.alpaca.marketdata.streaming.trade.TradeMessage; import net.jacobpeterson.util.gson.GsonUtil; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import javax.websocket.DeploymentException; import java.io.IOException; @@ -42,7 +42,7 @@ public class MarketDataWebsocketClient implements WebsocketClient { /** The logger. */ - private static final Logger LOGGER = LogManager.getLogger(MarketDataWebsocketClient.class); + private static final Logger LOGGER = LoggerFactory.getLogger(MarketDataWebsocketClient.class); /** The constant STREAM_KEY. */ private static final String STREAM_KEY = "stream"; @@ -256,35 +256,35 @@ public void handleWebsocketMessage(String message) { authenticated = isAuthorizationMessageSuccess(authorizationMessage); - LOGGER.debug(authorizationMessage); + if (LOGGER.isDebugEnabled()) { LOGGER.debug(authorizationMessage.toString()); } break; case LISTENING: ListeningMessage listeningMessage = GsonUtil.GSON.fromJson(messageJsonObject, ListeningMessage.class); sendStreamMessageToListeners(marketDataStreamMessageType, listeningMessage); - LOGGER.debug(listeningMessage); + if (LOGGER.isDebugEnabled()) { LOGGER.debug(listeningMessage.toString()); } break; case TRADES: TradeMessage tradeMessage = GsonUtil.GSON.fromJson(messageJsonObject.get(DATA_KEY), TradeMessage.class); sendStreamMessageToListeners(marketDataStreamMessageType, tradeMessage); - LOGGER.debug(tradeMessage); + if (LOGGER.isDebugEnabled()) { LOGGER.debug(tradeMessage.toString()); } break; case QUOTES: QuoteMessage quoteMessage = GsonUtil.GSON.fromJson(messageJsonObject.get(DATA_KEY), QuoteMessage.class); sendStreamMessageToListeners(marketDataStreamMessageType, quoteMessage); - LOGGER.debug(quoteMessage); + if (LOGGER.isDebugEnabled()) { LOGGER.debug(quoteMessage.toString()); } break; case AGGREGATE_MINUTE: AggregateMinuteMessage aggregateMinuteMessage = GsonUtil.GSON.fromJson( messageJsonObject.get(DATA_KEY), AggregateMinuteMessage.class); sendStreamMessageToListeners(marketDataStreamMessageType, aggregateMinuteMessage); - LOGGER.debug(aggregateMinuteMessage); + if (LOGGER.isDebugEnabled()) { LOGGER.debug(aggregateMinuteMessage.toString()); } break; default: LOGGER.error("Unhandled stream type: " + marketDataStreamMessageType); diff --git a/src/main/java/net/jacobpeterson/polygon/PolygonAPI.java b/src/main/java/net/jacobpeterson/polygon/PolygonAPI.java index e53f550b..b8b2acd3 100644 --- a/src/main/java/net/jacobpeterson/polygon/PolygonAPI.java +++ b/src/main/java/net/jacobpeterson/polygon/PolygonAPI.java @@ -43,8 +43,8 @@ import net.jacobpeterson.polygon.websocket.client.PolygonWebsocketClient; import net.jacobpeterson.polygon.websocket.listener.PolygonStreamListener; import net.jacobpeterson.util.format.FormatUtil; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import java.io.InputStream; import java.lang.reflect.Type; @@ -59,7 +59,7 @@ public class PolygonAPI { /** The logger. */ - private static final Logger LOGGER = LogManager.getLogger(PolygonAPI.class); + private static final Logger LOGGER = LoggerFactory.getLogger(PolygonAPI.class); /** The PolygonWebsocketClient. */ private final PolygonWebsocketClient polygonWebsocketClient; diff --git a/src/main/java/net/jacobpeterson/polygon/websocket/client/PolygonWebsocketClient.java b/src/main/java/net/jacobpeterson/polygon/websocket/client/PolygonWebsocketClient.java index 868c7c6d..b117cafc 100644 --- a/src/main/java/net/jacobpeterson/polygon/websocket/client/PolygonWebsocketClient.java +++ b/src/main/java/net/jacobpeterson/polygon/websocket/client/PolygonWebsocketClient.java @@ -20,8 +20,8 @@ import net.jacobpeterson.polygon.websocket.listener.PolygonStreamListener; import net.jacobpeterson.polygon.websocket.message.PolygonStreamMessageType; import net.jacobpeterson.util.gson.GsonUtil; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import javax.websocket.DeploymentException; import java.io.IOException; @@ -42,7 +42,7 @@ public class PolygonWebsocketClient implements WebsocketClient { /** The logger. */ - private static final Logger LOGGER = LogManager.getLogger(PolygonWebsocketClient.class); + private static final Logger LOGGER = LoggerFactory.getLogger(PolygonWebsocketClient.class); /** The all tickers. */ private static final String ALL_TICKERS = "*"; @@ -172,7 +172,7 @@ public void handleWebsocketMessage(String message) { authenticated = isAuthenticatedStatusMessage(statusMessage); - LOGGER.debug(statusMessage); + if (LOGGER.isDebugEnabled()) { LOGGER.debug(statusMessage.toString()); } break; case TRADE: sendStreamMessageToListeners(polygonStreamMessageType, @@ -193,8 +193,8 @@ public void handleWebsocketMessage(String message) { default: LOGGER.error("Unknown stream object: {}", messageJsonObject); } - } catch (JsonSyntaxException e) { - LOGGER.throwing(e); + } catch (JsonSyntaxException exception) { + LOGGER.error("Could not parse Websocket message!", exception); } } else { LOGGER.error("Unknown stream message: {}", messageJsonObject); diff --git a/src/main/java/net/jacobpeterson/util/gson/GsonUtil.java b/src/main/java/net/jacobpeterson/util/gson/GsonUtil.java index 3855faba..a71224ff 100644 --- a/src/main/java/net/jacobpeterson/util/gson/GsonUtil.java +++ b/src/main/java/net/jacobpeterson/util/gson/GsonUtil.java @@ -5,8 +5,8 @@ import com.google.gson.JsonObject; import com.google.gson.JsonParser; import com.google.gson.annotations.SerializedName; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import java.lang.annotation.Annotation; import java.lang.reflect.Field; @@ -23,7 +23,7 @@ public class GsonUtil { /** The logger. */ - private static final Logger LOGGER = LogManager.getLogger(GsonUtil.class); + private static final Logger LOGGER = LoggerFactory.getLogger(GsonUtil.class); /** The constant CLASS_ANNOTATION_CACHE. */ private static final HashMap, ArrayList> CLASS_ANNOTATION_CACHE = new HashMap<>(); diff --git a/src/main/java/net/jacobpeterson/util/properties/PropertyUtil.java b/src/main/java/net/jacobpeterson/util/properties/PropertyUtil.java index c619baf0..78ac1c3a 100644 --- a/src/main/java/net/jacobpeterson/util/properties/PropertyUtil.java +++ b/src/main/java/net/jacobpeterson/util/properties/PropertyUtil.java @@ -1,7 +1,7 @@ package net.jacobpeterson.util.properties; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import java.io.IOException; import java.io.InputStream; @@ -16,7 +16,7 @@ public class PropertyUtil { /** The logger. */ - private static final Logger LOGGER = LogManager.getLogger(PropertyUtil.class); + private static final Logger LOGGER = LoggerFactory.getLogger(PropertyUtil.class); /** The property file. */ public static final Map CACHED_PROPERTIES = Collections.synchronizedMap(new HashMap<>()); diff --git a/src/main/resources/log4j2.properties b/src/main/resources/log4j2.properties deleted file mode 100644 index 1f51154e..00000000 --- a/src/main/resources/log4j2.properties +++ /dev/null @@ -1,12 +0,0 @@ -name=PropertiesConfig -property.filename = logs -appenders = console - -appender.console.type = Console -appender.console.name = STDOUT -appender.console.layout.type = PatternLayout -appender.console.layout.pattern = [%-5level] %d{yyyy-MM-dd HH:mm:ss.SSS} [%t] %c{1} - %msg%n - -rootLogger.level = info -rootLogger.appenderRefs = stdout -rootLogger.appenderRef.stdout.ref = STDOUT \ No newline at end of file From a6e062c3428fe679e28b25159c8044423ed318d1 Mon Sep 17 00:00:00 2001 From: Jacob Peterson Date: Tue, 5 Jan 2021 17:02:57 -0700 Subject: [PATCH 066/271] Rewrite README and add Logo --- README.md | 731 +++++++++--------- build.gradle | 12 +- schema_json/alpaca/order/cancelled_order.json | 1 + .../net/jacobpeterson/alpaca/AlpacaAPI.java | 10 +- upload_to_maven_central.sh | 2 +- 5 files changed, 380 insertions(+), 376 deletions(-) diff --git a/README.md b/README.md index ba1b8810..5004d55c 100644 --- a/README.md +++ b/README.md @@ -1,48 +1,47 @@ -

+

Maven Central Javadocs Build Status GitHub

## Overview -This is a Java implementation for the Alpaca API. Alpaca lets you build and trade with real-time market data for free. This library is community developed and if you have any questions, please ask them on [Github Discussions](https://github.com/Petersoj/alpaca-java/discussions), the [Alpaca Slack #dev-alpaca-java channel](https://alpaca.markets/slack), or on the [Alpaca Forums](https://forum.alpaca.markets/). +This is a Java implementation for the Alpaca API and the Polygon API. Alpaca lets you trade with algorithms, connect with apps, and build services all with a commission-free stock trading API and Polygon provides real-time and historical market data for free. This library is community developed and if you have any questions, please ask them on [Github Discussions](https://github.com/Petersoj/alpaca-java/discussions), the [Alpaca Slack #dev-alpaca-java channel](https://alpaca.markets/slack), or on the [Alpaca Forums](https://forum.alpaca.markets/). ## Table of Contents +1. [Gradle and Maven Integration](#Gradle-and-Maven-Integration) +1. [Configuration](#Configuration) +1. [Logger](#Logger) +1. [AlpacaAPI](#AlpacaAPI) + - [AlpacaAPIRequestException](#AlpacaAPIRequestException) + - [Account](#Account) + - [AccountActivity](#AccountActivity) + - [AccountConfiguration](#AccountConfiguration) + - [Orders](#Orders) + - [Positions](#Positions) + - [Assets](#Assets) + - [Watchlist](#Watchlist) + - [PortfolioHistory](#PortfolioHistory) + - [Calendar](#Calendar) + - [Clock](#Clock) + - [Bars](#Bars) + - [LastTrade](#LastTrade) + - [LastQuote](#LastQuote) + - [Alpaca Streaming](#Alpaca-Streaming) + - [Alpaca Market Data Streaming](#Alpaca-Market-Data-Streaming) +1. [PolygonAPI](#polygonapi) + - TODO 1. [Building](#building) -2. [Gradle Integration](#gradle-integration) -3. [Maven Integration](#maven-integration) -4. [Maven Central Integrity](#maven-central-integrity) -5. [Configuration](#configuration) -6. [AlpacaAPI Example](#alpacaapi-example) -7. [PolygonAPI Example](#polygonapi-example) -## Building - -To build this project yourself, clone the project and run: - -``` -./gradlew build -``` - -To build this project and install it to your local maven repo, run: - -``` -./gradlew build install -``` - -## Gradle Integration - -Add the following dependency to your build.gradle file: +## Gradle and Maven Integration +If you are using Gradle as your build tool, add the following dependency to your `build.gradle` file: ``` dependencies { - compile "net.jacobpeterson:alpaca-java:6.0.1" + implementation group: 'net.jacobpeterson', name: 'alpaca-java', version: '6.0.1' } ``` -## Maven Integration - -Add the following dependency to your pom.xml file: +If you are using Maven as your build tool, add the following dependency to your `pom.xml` file: ``` @@ -53,410 +52,414 @@ Add the following dependency to your pom.xml file: ``` -## Maven Central Integrity - -Since this community-developed library uses sensitive information (e.g. your Alpaca API keys), -it's important to verify that the jar in Maven Central that is downloaded via Gradle or Maven is indeed the jar that is built -from this source code that you see on this Github repository. This library has been configured to create reproducible builds -that provide the same file checksums when built on any machine. Maven Central has a way to verify that artifacts are uploaded -by the domain holder of the artifact group ID (e.g. `jacobpeterson.net`) via PGP artifact signing, but lacks the ability -to verify the legitimacy of those artifacts in the context of an open-source library. To verify that this dependency -that you use in your trading algorithm projects is indeed legitimate, we need to compare the Jar MD5 checksum in Maven Central -with the Jar MD5 checksum from this source code: -1. Clone the repo somewhere: `git clone https://github.com/Petersoj/alpaca-java` -2. `cd alpaca-java` -3. Checkout/reset to the latest version: `git reset 6.0.1 --hard` -4. Build the project: `./gradlew build` - 1. If you build the project twice without a `clean` task in between (e.g. `./gradlew build; ./gradlew build`), the MD5 checksum will change. - 2. Use Java 8 (JDK 1.8) to build, otherwise the MD5 checksums will differ. -5. Generate the MD5 checksum: `md5 build/libs/alpaca-java-6.0.1.jar` -6. Confirm that the checksum value in Maven Central is the same as in step 4: -`curl https://repo1.maven.org/maven2/net/jacobpeterson/alpaca-java/6.0.1/alpaca-java-6.0.1.jar.md5` - -If the checksums are different, please [create a new issue](https://github.com/Petersoj/alpaca-java/issues/new)! -The current MD5 checksum of build `6.0.1` is: `425532e598e3d812b29a3270cf4c5e73` +Note that you don't have to use the Maven Central artifacts and instead can just install a clone of this project to your local Maven repository as shown in the [Building](#building) section. ## Configuration - -Creating an `alpaca.properties` file on the classpath of the following format allows you to easily load various properties -using the `AlpacaAPI()` default constructor: +Creating an `alpaca.properties` file on the classpath with the following format allows you to easily load properties using the `AlpacaAPI()` default constructor: ``` key_id = secret = -base_api_url = https://paper-api.alpaca.markets -base_data_url = https://data.alpaca.markets -user_agent=a_user_agent ``` The default values for `alpaca.properties` can be found [here](https://github.com/Petersoj/alpaca-java/tree/master/src/main/resources). -Similarly, creating a `polygon.properties` file on the classpath of the following format allows you to easily load various properties -using the `PolygonAPI()` default constructor: +Similarly, creating a `polygon.properties` file on the classpath with the following format allows you to easily load properties using the `PolygonAPI()` default constructor: ``` -key_id = (Note that this will default to what is set in alpaca.properties) -base_api_url = https://api.polygon.io -web_socket_server_url = wss://alpaca.socket.polygon.io/stocks -user_agent=a_user_agent +key_id = (Note that this will default to what is set in 'alpaca.properties') ``` The default values for `polygon.properties` can be found [here](https://github.com/Petersoj/alpaca-java/tree/master/src/main/resources). -## AlpacaAPI Example +## Logger +For logging, this library uses [SLF4j](http://www.slf4j.org/) which serves as an interface for various logging frameworks. This enables you to use whatever logging framework you would like. However, if you do not add a logging framework as a dependency in your project, the console will output a message stating that SLF4j is defaulting to a no-operation (NOP) logger implementation. To enable logging, add a logging framework of your choice as a dependency to your project such as [Log4j 2](http://logging.apache.org/log4j/2.x/index.html), [SLF4j-simple](http://www.slf4j.org/manual.html), or [Apache Commons Logging](https://commons.apache.org/proper/commons-logging/). -This example uses the `AlpacaAPI` class to subscribe to the Account and Trade Updates stream, print out the account information, submit a limit order, create a watchlist, and print out bars data. Click [here](https://docs.alpaca.markets/api-documentation/api-v2/) for the general Alpaca API documentation and click [here](https://javadoc.io/doc/net.jacobpeterson/alpaca-java/latest/net/jacobpeterson/alpaca/AlpacaAPI.html) for the `AlpacaAPI` javadoc. +## AlpacaAPI +The `AlpacaAPI` class contains several methods to interface with Alpaca. You will generally only need one instance of it in your application. Note that many methods allow `null` to be passed in as a parameter if it is optional. The Alpaca API specification is located [here](https://docs.alpaca.markets/api-documentation/api-v2/) and the `AlpacaAPI` Javadoc is located [here](https://javadoc.io/doc/net.jacobpeterson/alpaca-java/latest/net/jacobpeterson/alpaca/AlpacaAPI.html). +Example usage: ```java -// This logs into Alpaca using the alpaca.properties file on the classpath. +// This constructor uses the 'alpaca.properties' file on the classpath for configuration AlpacaAPI alpacaAPI = new AlpacaAPI(); -// Register explicitly for ACCOUNT_UPDATES and ORDER_UPDATES Messages via stream listener +// This constructor passes in a 'keyID' and 'secret' String +String keyID = ""; +String secret = ""; +AlpacaAPI alpacaAPI = new AlpacaAPI(keyID, secret); + +// This constructor is for OAuth tokens +String oAuthToken = ""; +AlpacaAPI alpacaAPI = new AlpacaAPI(oAuthToken); +``` + +Note that the example uses of the `AlpacaAPI` class methods below are not exhaustive. Refer to the `AlpacaAPI` [Javadoc](https://javadoc.io/doc/net.jacobpeterson/alpaca-java/latest/net/jacobpeterson/alpaca/AlpacaAPI.html) for all method signatures. + +### AlpacaAPIRequestException +The `AlpacaAPIRequestException` is thrown anytime an exception occurs when using various `AlpacaAPI` methods. It should be caught and handled within your trading algorithm application accordingly. + +### Account +The Account API serves important information related to an account, including account status, funds available for trade, funds available for withdrawal, and various flags relevant to an account's ability to trade. + +Example usage: +```java try { - alpacaAPI.addAlpacaStreamListener(new AlpacaStreamListenerAdapter( - AlpacaStreamMessageType.ACCOUNT_UPDATES, - AlpacaStreamMessageType.TRADE_UPDATES) { - @Override - public void onStreamUpdate(AlpacaStreamMessageType streamMessageType, AlpacaStreamMessage streamMessage) { - switch (streamMessageType) { - case ACCOUNT_UPDATES: - AccountUpdateMessage accountUpdateMessage = (AccountUpdateMessage) streamMessage; - System.out.println("\nReceived Account Update: \n\t" + - accountUpdateMessage.toString().replace(",", ",\n\t")); - break; - case TRADE_UPDATES: - TradeUpdateMessage tradeUpdateMessage = (TradeUpdateMessage) streamMessage; - System.out.println("\nReceived Order Update: \n\t" + - tradeUpdateMessage.toString().replace(",", ",\n\t")); - break; - } - } - }); -} catch (WebsocketException e) { + // Get 'Account' information + Account account = alpacaAPI.getAccount(); +} catch (AlpacaAPIRequestException e) { e.printStackTrace(); } +``` + +### AccountActivity +The Account Activities API provides access to a historical record of transaction activities that have impacted your account. -// Add an Alpaca Market data stream listener to listen to "T.AAPL", "Q.AAPL", and "AM.AAPL" messages +Example usage: +```java try { - alpacaAPI.addMarketDataStreamListener( - new MarketDataStreamListenerAdapter("AAPL", MarketDataStreamMessageType.values()) { - @Override - public void onStreamUpdate(MarketDataStreamMessageType streamMessageType, - MarketDataStreamMessage streamMessage) { - switch (streamMessageType) { - case QUOTES: - QuoteMessage quoteMessage = (QuoteMessage) streamMessage; - System.out.println("\nQuote Update: \n\t" + - quoteMessage.toString().replace(",", ",\n\t")); - break; - case TRADES: - TradeMessage tradeMessage = (TradeMessage) streamMessage; - System.out.println("\nTrade Update: \n\t" + - tradeMessage.toString().replace(",", ",\n\t")); - break; - case AGGREGATE_MINUTE: - AggregateMinuteMessage aggregateMinuteMessage = (AggregateMinuteMessage) streamMessage; - System.out.println("\nAggregate Minute Update: \n\t" + - aggregateMinuteMessage.toString().replace(",", ",\n\t")); - break; - } - } - }); -} catch (WebsocketException e) { + // Print all 'AccountActivity's on 12/23/2020 + List accountActivities = alpacaAPI.getAccountActivities( + null, + null, + ZonedDateTime.of(2020, 12, 23, 0, 0, 0, 0, ZoneId.of("America/New_York")), + Direction.ASCENDING, + null, + null, + (ActivityType[]) null); + for (AccountActivity accountActivity : accountActivities) { + if (accountActivity instanceof TradeActivity) { + System.out.println("TradeActivity: " + (TradeActivity) accountActivity); + } else if (accountActivity instanceof NonTradeActivity) { + System.out.println("NonTradeActivity: " + (NonTradeActivity) accountActivity); + } + } +} catch (AlpacaAPIRequestException e) { e.printStackTrace(); } +``` -// Get Account Information +### AccountConfiguration +The Account Configuration API provides custom configurations about your trading account settings. These configurations control various allow you to modify settings to suit your trading needs. + +Example usage: +```java try { - Account alpacaAccount = alpacaAPI.getAccount(); + // Update the 'AccountConfiguration' to block new orders + AccountConfiguration accountConfiguration = alpacaAPI.getAccountConfiguration(); + accountConfiguration.setSuspendTrade(true); + + alpacaAPI.setAccountConfiguration(accountConfiguration); +} catch (AlpacaAPIRequestException e) { + e.printStackTrace(); +} +``` - System.out.println("\n\nAccount Information:"); - System.out.println("\t" + alpacaAccount.toString().replace(",", ",\n\t")); +### Orders +The Orders API allows a user to monitor, place, and cancel their orders with Alpaca. + +Example usage: +```java +try { + // Print all the 'Order's of AAPL and TSLA after 12/23/2020 + // in ascending (oldest to newest) order + List orders = alpacaAPI.getOrders( + OrderStatus.ALL, + null, + ZonedDateTime.of(2020, 12, 23, 0, 0, 0, 0, ZoneId.of("America/New_York")), + null, + Direction.ASCENDING, + true, + Arrays.asList("AAPL", "TSLA")); + orders.forEach(System.out::println); + + // Request a new buy limit order for TSLA for 100 shares at a limit price + // of 600.00 and TIF of DAY. + Order limitOrderTSLA = alpacaAPI.requestNewLimitOrder( + "TSLA", + 100, + OrderSide.BUY, + OrderTimeInForce.DAY, + 600.00, + false); + + // Check if my TSLA limit order has filled 5 seconds later and if it hasn't + // replace it with a market order so it fills! + Thread.sleep(5000); + limitOrderTSLA = alpacaAPI.getOrder(limitOrderTSLA.getId(), false); + if (limitOrderTSLA.getFilledAt() == null) { + Order replacedOrder = alpacaAPI.replaceOrder( + limitOrderTSLA.getId(), + 100, + OrderTimeInForce.DAY, + null, + null, + null, + null); + } + + // Cancel all open orders + List cancelledOrders = alpacaAPI.cancelAllOrders(); + for (CancelledOrder cancelledOrder : cancelledOrders) { + System.out.println("Cancelled Order: " + cancelledOrder.getOrder()); + } } catch (AlpacaAPIRequestException e) { e.printStackTrace(); } +``` + +### Positions +The Positions API provides information about an account's current open positions. -// Request an Order +Example usage: +```java try { - Order aaplLimitOrder = alpacaAPI.requestNewLimitOrder("AAPL", 1, OrderSide.BUY, OrderTimeInForce.DAY, - 201.30, true, null); + // Cancel TSLA and AAPL positions if they are open + List openPositions = alpacaAPI.getOpenPositions(); + for (Position openPosition : openPositions) { + if (openPosition.getSymbol().equals("TSLA") || + openPosition.getSymbol().equals("AAPL")) { + Order closePositionOrder = alpacaAPI.closePosition(openPosition.getSymbol()); + System.out.println("Closing position: " + closePositionOrder); + } + } +} catch (AlpacaAPIRequestException e) { + e.printStackTrace(); +} +``` + +### Assets +The Assets API serves as the master list of assets available for trade and data consumption from Alpaca. - System.out.println("\n\nNew AAPL Order:"); - System.out.println("\t" + aaplLimitOrder.toString().replace(",", ",\n\t")); +Example usage: +```java +try { + // Print out a CSV of all active US equity 'Asset's + List activeUSEquities = alpacaAPI.getAssets(AssetStatus.ACTIVE, "us_equity"); + System.out.println(activeUSEquities + .stream().map(Asset::getSymbol) + .collect(Collectors.joining(", "))); + + // Print out TSLA 'Asset' information + Asset tslaAsset = alpacaAPI.getAssetBySymbol("TSLA"); + System.out.println(tslaAsset); } catch (AlpacaAPIRequestException e) { e.printStackTrace(); } +``` + +### Watchlist +The watchlist API provides CRUD operation for the account's watchlist. An account can have multiple watchlists and each is uniquely identified by ID but can also be addressed by user-defined name. Each watchlist is an ordered list of assets. -// Create watchlist +Example usage: +```java try { - Watchlist dayTradeWatchlist = alpacaAPI.createWatchlist("Day Trade", "AAPL"); + // Print out all 'Watchlist' names + List watchlists = alpacaAPI.getWatchlists(); + watchlists.forEach(watchlist -> System.out.println(watchlist.getName())); + + // Create a watchlist for day trading with TSLA and AAPL initially + Watchlist dayTradeWatchlist = alpacaAPI.createWatchlist("Day Trade", "TSLA", "AAPL"); + + // Remove TSLA and add MSFT to 'dayTradeWatchlist' + alpacaAPI.removeSymbolFromWatchlist(dayTradeWatchlist.getId(), "TSLA"); + alpacaAPI.addWatchlistAsset(dayTradeWatchlist.getId(), "MSFT"); - System.out.println("\n\nDay Trade Watchlist:"); - System.out.println("\t" + dayTradeWatchlist.toString().replace(",", ",\n\t")); + // Set the updated watchlist variable + dayTradeWatchlist = alpacaAPI.getWatchlist(dayTradeWatchlist.getId()); + + // Print CSV of 'dayTradeWatchlist' 'Asset's + System.out.println(dayTradeWatchlist.getAssets() + .stream().map(Asset::getSymbol) + .collect(Collectors.joining(", "))); + + // Delete the 'dayTradeWatchlist' + alpacaAPI.deleteWatchlist(dayTradeWatchlist.getId()); } catch (AlpacaAPIRequestException e) { e.printStackTrace(); } +``` + +### PortfolioHistory +The portfolio history API returns the timeseries data for equity and profit loss information of the account. -// Get bars +Example usage: +```java try { - ZonedDateTime start = ZonedDateTime.of(2019, 11, 18, 0, 0, 0, 0, ZoneId.of("America/New_York")); - ZonedDateTime end = ZonedDateTime.of(2019, 11, 22, 23, 59, 0, 0, ZoneId.of("America/New_York")); - - Map> bars = alpacaAPI.getBars(BarsTimeFrame.ONE_DAY, "AAPL", null, start, end, - null, null); - - System.out.println("\n\nBars response:"); - for (Bar bar : bars.get("AAPL")) { - System.out.println("\t=========="); - System.out.println("\tUnix Time " + ZonedDateTime.ofInstant(Instant.ofEpochSecond(bar.getT()), - ZoneOffset.UTC)); - System.out.println("\tOpen: $" + bar.getO()); - System.out.println("\tHigh: $" + bar.getH()); - System.out.println("\tLow: $" + bar.getL()); - System.out.println("\tClose: $" + bar.getC()); - System.out.println("\tVolume: " + bar.getV()); + // Get the 'PortfolioHistory' and print out static information + PortfolioHistory portfolioHistory = alpacaAPI.getPortfolioHistory( + 3, + PortfolioPeriodUnit.DAY, + PortfolioTimeFrame.ONE_HOUR, + LocalDate.of(2020, 12, 23), + false); + System.out.printf("Timeframe: %s, Base value: %s \n", + portfolioHistory.getTimeframe(), + portfolioHistory.getBaseValue()); + + // Loop through all indices and print the dynamic historical information uniformly + int historyUnitSize = portfolioHistory.getTimestamp().size(); + for (int historyIndex = 0; historyIndex < historyUnitSize; historyIndex++) { + System.out.printf("Timestamp: %s, Equity: %s, PnL: %s, PnL%%: %s \n", + portfolioHistory.getTimestamp().get(historyIndex), + portfolioHistory.getEquity().get(historyIndex), + portfolioHistory.getProfitLoss().get(historyIndex), + portfolioHistory.getProfitLossPct().get(historyIndex)); } } catch (AlpacaAPIRequestException e) { e.printStackTrace(); } ``` -This code will output the following: +### Calendar +The calendar API serves the full list of market days from 1970 to 2029. It can also be queried by specifying a start and/or end time to narrow down the results. In addition to the dates, the response also contains the specific open and close times for the market days, taking into account early closures. +Example usage: +```java +try { + // Get the 'Calendar' of week of Christmas and print out information + List calendar = alpacaAPI.getCalendar( + LocalDate.of(2020, 12, 20), + LocalDate.of(2020, 12, 27)); + calendar.forEach(System.out::println); +} catch (AlpacaAPIRequestException e) { + e.printStackTrace(); +} ``` -Account Information: - Account@626abbd0[id=, - accountNumber=, - status=ACTIVE, - currency=USD, - cash=6982.96, - portfolioValue=99717.04, - patternDayTrader=false, - tradeSuspendedByUser=false, - tradingBlocked=false, - transfersBlocked=false, - accountBlocked=false, - createdAt=2019-08-11T22:19:17.959522Z, - shortingEnabled=true, - longMarketValue=106700, - shortMarketValue=0, - equity=99717.04, - lastEquity=97753.04, - multiplier=4, - buyingPower=265328.96, - initialMargin=53350, - maintenanceMargin=32010, - sma=0, - daytradeCount=0, - lastMaintenanceMargin=31420.8, - daytradingBuyingPower=265328.96, - regtBuyingPower=92734.08] - -New AAPL Order: - Order@37313c65[id=, - clientOrderId=929e412f-32b6-4ddf-9729-d77a59c19ec8, - createdAt=2019-11-26T03:41:10.475959154Z, - updatedAt=2019-11-26T03:41:10.481465924Z, - submittedAt=2019-11-26T03:41:10.464752859Z, - filledAt=, - expiredAt=, - canceledAt=, - failedAt=, - assetId=b0b6dd9d-8b9b-48a9-ba46-b9d54906e415, - symbol=AAPL, - assetClass=us_equity, - qty=1, - filledQty=0, - type=limit, - side=buy, - timeInForce=day, - limitPrice=201.3, - stopPrice=, - filledAvgPrice=, - status=new, - extendedHours=true] - -Received Order Update: - OrderUpdateMessage [event=NEW, - price=null, - timestamp=null, - order=net.jacobpeterson.alpaca.domain.Order@5c728415[id=, - clientOrderId=e23ca503-ebec-4754-b65a-ee42806fc67d, - createdAt=2019-11-23T14:41:05.503352Z, - updatedAt=2019-11-23T14:41:05.599835871Z, - submittedAt=2019-11-23T14:41:05.445148Z, - filledAt=, - expiredAt=, - canceledAt=, - failedAt=, - assetId=b0b6dd9d-8b9b-48a9-ba46-b9d54906e415, - symbol=AAPL, - exchange=, - assetClass=us_equity, - qty=1, - filledQty=0, - type=limit, - side=buy, - timeInForce=day, - limitPrice=100, - stopPrice=, - filledAvgPrice=, - status=new]] - - -Day Trade Watchlist: - Watchlist@34bde49d[id=7c414350-79d8-4527-8892-f1667faa712a, - createdAt=2019-11-26T03:20:31.953679Z, - updatedAt=2019-11-26T03:20:31.953679Z, - name=Day Trade, - accountId=, - assets=[Asset@1b1cfb87[id=b0b6dd9d-8b9b-48a9-ba46-b9d54906e415, - _class=us_equity, - exchange=NASDAQ, - symbol=AAPL, - status=active, - tradable=true, - marginable=true, - shortable=true, - easyToBorrow=true]]] - -Bars response: - ========== - Unix Time 2019-11-18T05:00Z - Open: $265.8 - High: $267.43 - Low: $264.23 - Close: $267.12 - Volume: 1.8045658E7 - ========== - Unix Time 2019-11-19T05:00Z - Open: $267.9 - High: $268.0 - Low: $265.3926 - Close: $266.29 - Volume: 1.4685205E7 - ========== - Unix Time 2019-11-20T05:00Z - Open: $265.54 - High: $266.083 - Low: $260.4 - Close: $263.22 - Volume: 2.2181038E7 - ========== - Unix Time 2019-11-21T05:00Z - Open: $263.71 - High: $264.005 - Low: $261.18 - Close: $262.01 - Volume: 2.5411879E7 - ========== - Unix Time 2019-11-22T05:00Z - Open: $262.59 - High: $263.18 - Low: $260.84 - Close: $261.84 - Volume: 1.4021695E7 -``` - -## PolygonAPI Example -This example uses the `PolygonAPI` class to subscribe to the Polygon websocket stream, get stocks splits, and get aggregates. Click [here](https://polygon.io/docs/) for the general Polygon API documentation and click [here](https://javadoc.io/doc/net.jacobpeterson/alpaca-java/latest/net/jacobpeterson/polygon/PolygonAPI.html) for the `PolygonAPI` javadoc. +### Clock +The clock API serves the current market timestamp, whether the market is currently open, as well as the times of the next market open and close. +Example usage: ```java -// This will use the key_id in the alpaca.properties file by default -PolygonAPI polygonAPI = new PolygonAPI(); +try { + // Get the market 'Clock' and print it out + Clock clock = alpacaAPI.getClock(); + System.out.println(clock); +} catch (AlpacaAPIRequestException e) { + e.printStackTrace(); +} +``` -String aaplTicker = "AAPL"; +### Bars +The bars API provides time-aggregated price and volume data from the IEX exchange. -// Add a Polygon stream listener to listen to "T.AAPL", "Q.AAPL", "A.AAPL", "AM.AAPL", and status messages +Example usage: +```java try { - polygonAPI.addPolygonStreamListener(new PolygonStreamListenerAdapter(aaplTicker, - PolygonStreamMessageType.values()) { - @Override - public void onStreamUpdate(PolygonStreamMessageType streamMessageType, PolygonStreamMessage streamMessage) { - System.out.println("===> streamUpdate " + streamMessageType + " " + streamMessage); - } - }); -} catch (WebsocketException e) { + // Get 15min bars of AAPL and TSLA from 12/23/2020 at 9:30 AM to + // 12/24/2020 at 4 PM and print them out + Map> bars = alpacaAPI.getBars( + BarsTimeFrame.FIFTEEN_MINUTE, + new String[]{"AAPL", "TSLA"}, + null, + ZonedDateTime.of(2020, 12, 23, 9, 30, 0, 0, ZoneId.of("America/New_York")), + ZonedDateTime.of(2020, 12, 24, 12 + 4, 0, 0, 0, ZoneId.of("America/New_York")), + null, + null); + bars.entrySet().forEach(System.out::println); +} catch (AlpacaAPIRequestException e) { e.printStackTrace(); } +``` -try { - StockSplitsResponse stockSplitsResponse = polygonAPI.getStockSplits(aaplTicker); +### LastTrade +The Last Trade API provides last trade details for a symbol. - System.out.println(aaplTicker + " Stock Split Response:"); - System.out.println("\tStatus: " + stockSplitsResponse.getStatus()); - System.out.println("\tCount: " + stockSplitsResponse.getCount()); - for (StockSplit stockSplit : stockSplitsResponse.getResults()) { - System.out.println("\t" + stockSplit.toString().replace(",", ",\n\t")); - } -} catch (PolygonAPIRequestException e) { +Example usage: +```java +try { + // Print out the last trade of AAPL + System.out.println(alpacaAPI.getLastTrade("AAPL")); +} catch (AlpacaAPIRequestException e) { e.printStackTrace(); } +``` +### LastQuote +The Last Quote API provides last quote details for a symbol. + +Example usage: +```java try { - AggregatesResponse aggregatesResponse = polygonAPI.getAggregates(aaplTicker, 1, Timespan.DAY, - LocalDate.of(2019, 11, 18), LocalDate.of(2019, 11, 25), false); - - System.out.println("Aggregate Response:"); - System.out.println("\tStatus: " + aggregatesResponse.getStatus()); - System.out.println("\tCount: " + aggregatesResponse.getResultsCount()); - for (Aggregate aggregate : aggregatesResponse.getResults()) { - System.out.println("\t" + aggregate.toString().replace(",", ",\n\t")); - } -} catch (PolygonAPIRequestException e) { + // Print out the last quote of AAPL + System.out.println(alpacaAPI.getLastQuote("AAPL")); +} catch (AlpacaAPIRequestException e) { e.printStackTrace(); } ``` -This code will output the following: + +### Alpaca Streaming +Alpaca offers WebSocket streaming for account and order updates. + +Example usage: +```java +// List to account updates and trade updates from Alpaca and print their outputs +alpacaAPI.addAlpacaStreamListener(new AlpacaStreamListenerAdapter( + AlpacaStreamMessageType.ACCOUNT_UPDATES, + AlpacaStreamMessageType.TRADE_UPDATES) { + @Override + public void onStreamUpdate(AlpacaStreamMessageType streamMessageType, + AlpacaStreamMessage streamMessage) { + switch (streamMessageType) { + case ACCOUNT_UPDATES: + System.out.println((AccountUpdateMessage) streamMessage); + break; + case TRADE_UPDATES: + System.out.println((TradeUpdateMessage) streamMessage); + break; + } + } +}); +``` + +### Alpaca Market Data Streaming +Alpaca's Data API provides websocket streaming for trades, quotes and minute bars. This helps receive the most up to date market information that could help your trading strategy to act upon certain market movement. + +Example usage: +```java +// List to TSLA quotes, trades, and minute aggregates and print it out +alpacaAPI.addMarketDataStreamListener(new MarketDataStreamListenerAdapter( + "TSLA", MarketDataStreamMessageType.values()) { + @Override + public void onStreamUpdate(MarketDataStreamMessageType streamMessageType, + MarketDataStreamMessage streamMessage) { + switch (streamMessageType) { + case QUOTES: + System.out.println((QuoteMessage) streamMessage); + break; + case TRADES: + System.out.println((TradeMessage) streamMessage); + break; + case AGGREGATE_MINUTE: + System.out.println((AggregateMinuteMessage) streamMessage); + break; + } + } +}); +``` + +## PolygonAPI +The `PolygonAPI` class contains several methods to interface with Polygon. You will generally only need one instance of it in your application. The Polygon API specification is located [here](https://polygon.io/docs/) and the `PolygonAPI` javadoc is located [here](https://javadoc.io/doc/net.jacobpeterson/alpaca-java/latest/net/jacobpeterson/polygon/PolygonAPI.html). + +Example usage: +```java +// This constructor uses the 'polygon.properties' file on the classpath for configuration +Polygon polygonAPI = new PolygonAPI(); + +// This constructor passes in a 'keyID' String +String keyID = ""; +PolygonAPI polygonAPI = new PolygonAPI(keyID); +``` + +Note that the example uses of the `PolygonAPI` class methods below are not exhaustive. Refer to the `PolygonAPI` [Javadoc](https://javadoc.io/doc/net.jacobpeterson/alpaca-java/latest/net/jacobpeterson/polygon/PolygonAPI.html) for all method signatures. + +// TODO add examples of methods + +## Building +To build this project yourself, clone the project and run: ``` -[INFO ] 2019-11-25 21:33:20.456 [main] PolygonWebsocketClient - Connected. -[DEBUG] 2019-11-25 21:33:20.485 [main] PolygonWebsocketClientEndpoint - sendMessage {"action":"auth","params":""} -[INFO ] 2019-11-25 21:33:20.489 [main] PolygonWebsocketClient - Subscribing to A.AAPL -[INFO ] 2019-11-25 21:33:20.489 [main] PolygonWebsocketClient - Subscribing to T.AAPL -[INFO ] 2019-11-25 21:33:20.489 [main] PolygonWebsocketClient - Subscribing to AM.AAPL -[INFO ] 2019-11-25 21:33:20.489 [main] PolygonWebsocketClient - Subscribing to Q.AAPL -[DEBUG] 2019-11-25 21:33:20.491 [main] PolygonWebsocketClientEndpoint - sendMessage {"action":"subscribe","params":"A.AAPL,T.AAPL,AM.AAPL,Q.AAPL"} -[INFO ] 2019-11-25 21:33:20.492 [main] PolygonWebsocketClient - Subscriptions updated to {AAPL=[AGGREGATE_PER_SECOND, TRADES, AGGREGATE_PER_MINUTE, QUOTES]} -[INFO] 2019-11-25 21:33:20.508 [PolygonWebsocketThread] PolygonWebsocketClient - Channel status: net.jacobpeterson.domain.polygon.websocket.ChannelStatus@78428bcb[ev=status,status=connected,message=Connected Successfully] -[INFO] 2019-11-25 21:33:20.667 [PolygonWebsocketThread] PolygonWebsocketClient - Channel status: net.jacobpeterson.domain.polygon.websocket.ChannelStatus@629dc521[ev=status,status=auth_success,message=authenticated] -[INFO] 2019-11-25 21:33:20.735 [PolygonWebsocketThread] PolygonWebsocketClient - Channel status: net.jacobpeterson.domain.polygon.websocket.ChannelStatus@7f4d9c50[ev=status,status=success,message=subscribed to: A.AAPL] -[INFO] 2019-11-25 21:33:20.737 [PolygonWebsocketThread] PolygonWebsocketClient - Channel status: net.jacobpeterson.domain.polygon.websocket.ChannelStatus@2aa9728c[ev=status,status=success,message=subscribed to: T.AAPL] -[INFO] 2019-11-25 21:33:20.738 [PolygonWebsocketThread] PolygonWebsocketClient - Channel status: net.jacobpeterson.domain.polygon.websocket.ChannelStatus@4659b8c1[ev=status,status=success,message=subscribed to: AM.AAPL] -[INFO] 2019-11-25 21:33:20.738 [PolygonWebsocketThread] PolygonWebsocketClient - Channel status: net.jacobpeterson.domain.polygon.websocket.ChannelStatus@4a61d9e6[ev=status,status=success,message=subscribed to: Q.AAPL] -===> streamUpdate QUOTES QuotesMessage [ticker=AAPL, channelType=QUOTES, stockQuote=net.jacobpeterson.polygon.domain.StockQuote@4d5b710e[ev=,sym=AAPL,bx=12,bp=124.86,bs=6,ax=12,ap=124.87,as=5,c=1,t=1559143439610], timestamp=2019-05-29T10:23:59.610] -===> streamUpdate QUOTES QuotesMessage [ticker=AAPL, channelType=QUOTES, stockQuote=net.jacobpeterson.polygon.domain.StockQuote@16b79976[ev=,sym=AAPL,bx=12,bp=124.86,bs=4,ax=12,ap=124.87,as=5,c=1,t=1559143439712], timestamp=2019-05-29T10:23:59.712] -===> streamUpdate TRADES TradesMessage [ticker=AAPL, channelType=TRADES, stockTrade=net.jacobpeterson.polygon.domain.StockTrade@32b4aa49[ev=,sym=AAPL,x=4,p=124.86,s=100,c=[],t=1559143439772], timestamp=2019-05-29T10:23:59.772] -===> streamUpdate AGGREGATE_PER_SECOND AggregateMessage [ticker=AAPL, channelType=AGGREGATE_PER_SECOND, stockAggregate=net.jacobpeterson.polygon.domain.StockAggregate@5ace47f7[ev=,sym=AAPL,v=1131,av=6602041,op=125.5,vw=124.9778,o=124.8665,c=124.86,h=124.8665,l=124.86,a=124.8636,s=1559143439000,e=1559143440000], start=2019-05-29T10:23:59, end=2019-05-29T10:24] - -AAPL Stock Split Response: - Status: OK - Count: 3 - StockSplit@6b09fb41[ticker=AAPL, - exDate=2014-06-09, - paymentDate=2014-06-09, - recordDate=, - declaredDate=, - ratio=0.14285714285714285, - tofactor=, - forfactor=] - ... - -Aggregate Response: - Status: OK - Count: 5.0 - Aggregate@6cd24612[ticker=, - v=23984783, - o=265.8, - c=267.1, - h=267.43, - l=264.23, - t=1574053200000, - n=1] - Aggregate@5dafbe45[ticker=, - v=21208039, - o=267.9, - c=266.29, - h=268.0, - l=265.3926, - t=1574139600000, - n=1] - ... +./gradlew build +``` + +To build this project and install it to your local maven repo, run: + +``` +./gradlew build install ``` diff --git a/build.gradle b/build.gradle index 2fc7c7e8..31e1e0c6 100644 --- a/build.gradle +++ b/build.gradle @@ -175,12 +175,6 @@ task generatePOJOs() { final def jsonSourceDirectory = file("${project.projectDir}/schema_json").getPath() final def jsonPackageNameStart = "net.jacobpeterson.domain" - // Configure this task to participate in incremental builds and build caching so that it only executes when changes occur - configure { - inputs.files(fileTree(targetDirectory)).withPathSensitivity(PathSensitivity.RELATIVE) - outputs.cacheIf { true } - } - // Loop through all files in schema JSON file tree file(jsonSourceDirectory).eachFileRecurse { jsonFile -> if (jsonFile.getName().endsWith(".json")) { @@ -193,6 +187,12 @@ task generatePOJOs() { def jsonToPOJOTask = tasks.create('json2POJOTask-' + targetPackage + "." + jsonFile.getName(), GenerateJsonSchemaJavaTask) + // Configure this task to participate in incremental builds so that it only executes when changes occur + jsonToPOJOTask.configure { + inputs.file(jsonFile) + outputs.dir(targetDirectory) + } + jsonToPOJOTask.doFirst { jsonToPOJOTask.configuration.source = files(jsonFile.getAbsolutePath()) jsonToPOJOTask.configuration.targetDirectory = file(targetDirectory) diff --git a/schema_json/alpaca/order/cancelled_order.json b/schema_json/alpaca/order/cancelled_order.json index 1cd5c244..31266216 100644 --- a/schema_json/alpaca/order/cancelled_order.json +++ b/schema_json/alpaca/order/cancelled_order.json @@ -12,6 +12,7 @@ }, "body": { "existingJavaType": "net.jacobpeterson.domain.alpaca.order.Order", + "javaName": "order", "title": "The cancelled order" } } diff --git a/src/main/java/net/jacobpeterson/alpaca/AlpacaAPI.java b/src/main/java/net/jacobpeterson/alpaca/AlpacaAPI.java index d2ee8d32..2c5dbd1d 100644 --- a/src/main/java/net/jacobpeterson/alpaca/AlpacaAPI.java +++ b/src/main/java/net/jacobpeterson/alpaca/AlpacaAPI.java @@ -1461,7 +1461,7 @@ public PortfolioHistory getPortfolioHistory(Integer periodLength, PortfolioPerio } /** - * Returns the market calendar. + * Returns the market calendar from 1970 to 2029. * * @return the calendar * @@ -1560,8 +1560,8 @@ public Clock getClock() throws AlpacaAPIRequestException { * @throws AlpacaAPIRequestException the alpaca API exception * @see Bars */ - public Map> getBars(BarsTimeFrame timeframe, String symbol, Integer limit, - ZonedDateTime start, ZonedDateTime end, ZonedDateTime after, ZonedDateTime until) + public Map> getBars(BarsTimeFrame timeframe, String symbol, Integer limit, ZonedDateTime start, + ZonedDateTime end, ZonedDateTime after, ZonedDateTime until) throws AlpacaAPIRequestException { return this.getBars(timeframe, new String[]{symbol}, limit, start, end, after, until); } @@ -1588,8 +1588,8 @@ public Map> getBars(BarsTimeFrame timeframe, String symbo * @throws AlpacaAPIRequestException the alpaca API exception * @see Bars */ - public Map> getBars(BarsTimeFrame timeframe, String[] symbols, Integer limit, - ZonedDateTime start, ZonedDateTime end, ZonedDateTime after, ZonedDateTime until) + public Map> getBars(BarsTimeFrame timeframe, String[] symbols, Integer limit, ZonedDateTime start, + ZonedDateTime end, ZonedDateTime after, ZonedDateTime until) throws AlpacaAPIRequestException { AlpacaRequestBuilder urlBuilder = new AlpacaRequestBuilder(baseDataUrl, AlpacaConstants.VERSION_1_ENDPOINT, AlpacaConstants.BARS_ENDPOINT); diff --git a/upload_to_maven_central.sh b/upload_to_maven_central.sh index 32e339be..4de8adfe 100755 --- a/upload_to_maven_central.sh +++ b/upload_to_maven_central.sh @@ -1,2 +1,2 @@ -./gradlew clean build uploadArchives # uploadArchives MUST be done with a clean build otherwise the checksums of the files will differ +./gradlew clean build uploadArchives ./gradlew closeAndReleaseRepository From 1da14f2a02648361fe013f33b697eb8fcd978fc0 Mon Sep 17 00:00:00 2001 From: Jacob Peterson Date: Tue, 5 Jan 2021 17:06:20 -0700 Subject: [PATCH 067/271] Small logo change --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 5004d55c..d835502a 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -

+

Maven Central Javadocs Build Status GitHub From 060f42d7f5393b31ff34881e8a5b950a1047cac7 Mon Sep 17 00:00:00 2001 From: Jacob Peterson Date: Tue, 5 Jan 2021 23:42:05 -0700 Subject: [PATCH 068/271] Modify README and bug fixes for PolygonAPI --- README.md | 138 ++++++++++++------ .../polygon/tickers/ticker/attributes.json | 22 +++ schema_json/polygon/tickers/ticker/codes.json | 26 ++++ .../polygon/tickers/ticker/forex_ticker.json | 13 -- .../polygon/tickers/ticker/stock_ticker.json | 17 --- .../polygon/tickers/ticker/ticker.json | 34 +++-- .../polygon/tickers/tickers_response.json | 14 +- .../net/jacobpeterson/polygon/PolygonAPI.java | 16 +- .../jacobpeterson/polygon/enums/Market.java | 2 +- .../polygon/enums/TickerSort.java | 38 ----- .../net/jacobpeterson/util/gson/GsonUtil.java | 9 +- .../util/gson/ZonedDateTimeAdapter.java | 2 +- 12 files changed, 180 insertions(+), 151 deletions(-) create mode 100644 schema_json/polygon/tickers/ticker/attributes.json create mode 100644 schema_json/polygon/tickers/ticker/codes.json delete mode 100644 schema_json/polygon/tickers/ticker/forex_ticker.json delete mode 100644 schema_json/polygon/tickers/ticker/stock_ticker.json delete mode 100644 src/main/java/net/jacobpeterson/polygon/enums/TickerSort.java diff --git a/README.md b/README.md index d835502a..a693b9b0 100644 --- a/README.md +++ b/README.md @@ -29,7 +29,9 @@ This is a Java implementation for the Alpaca"; PolygonAPI polygonAPI = new PolygonAPI(keyID); ``` -Note that the example uses of the `PolygonAPI` class methods below are not exhaustive. Refer to the `PolygonAPI` [Javadoc](https://javadoc.io/doc/net.jacobpeterson/alpaca-java/latest/net/jacobpeterson/polygon/PolygonAPI.html) for all method signatures. +### PolygonAPIRequestException +The `PolygonAPIRequestException` is thrown anytime an exception occurs when using various `PolygonAPI` methods. It should be caught and handled within your trading algorithm application accordingly. -// TODO add examples of methods +### Usage +There are many Polygon endpoints (too many to show here in the README), but only certain ones are available to Alpaca users. The usage of the `PolygonAPI` class is very similar to the `AlpacaAPI` class so refer to the `PolygonAPI` [Javadoc](https://javadoc.io/doc/net.jacobpeterson/alpaca-java/latest/net/jacobpeterson/polygon/PolygonAPI.html) for all the available methods and endpoints accessible by Alpaca users. -## Building -To build this project yourself, clone the project and run: +### Basic Example +Here is a very basic example for requesting aggregate data and streaming real-time market data from Polygon: +```java +try { + // Get adjusted hour aggregates/bars on 12/23/2020 of AAPL and print them out + AggregatesResponse aaplAggregates = polygonAPI.getAggregates( + "AAPL", + 1, + Timespan.HOUR, + LocalDate.of(2020, 12, 23), + LocalDate.of(2020, 12, 24), + false); + aaplAggregates.getResults().forEach(System.out::println); +} catch (PolygonAPIRequestException exception) { + exception.printStackTrace(); +} +try { + // Add a Polygon stream listener to listen to "T.AAPL", "Q.AAPL", + // "A.AAPL", "AM.AAPL", and status messages + polygonAPI.addPolygonStreamListener(new PolygonStreamListenerAdapter("AAPL", + PolygonStreamMessageType.values()) { + @Override + public void onStreamUpdate(PolygonStreamMessageType streamMessageType, PolygonStreamMessage streamMessage) { + System.out.println("===> streamUpdate " + streamMessageType + " " + streamMessage); + } + }); +} catch (WebsocketException e) { + e.printStackTrace(); +} +``` +Again, refer to the `PolygonAPI` [Javadoc](https://javadoc.io/doc/net.jacobpeterson/alpaca-java/latest/net/jacobpeterson/polygon/PolygonAPI.html) for an exhaustive list of all available methods. + +## Building +To build this project yourself, clone this repository and run: ``` ./gradlew build ``` -To build this project and install it to your local maven repo, run: - +To install built artifacts to your local maven repo, run: ``` ./gradlew build install ``` + +Contributions are welcome! diff --git a/schema_json/polygon/tickers/ticker/attributes.json b/schema_json/polygon/tickers/ticker/attributes.json new file mode 100644 index 00000000..f40b87c5 --- /dev/null +++ b/schema_json/polygon/tickers/ticker/attributes.json @@ -0,0 +1,22 @@ +{ + "type": "object", + "title": "See https://polygon.io/docs/get_v2_reference_tickers_anchor", + "properties": { + "currencyName": { + "existingJavaType": "java.lang.String", + "title": "The name of the variable/quote currency." + }, + "currency": { + "existingJavaType": "java.lang.String", + "title": "The currency code of the variable/quote currency." + }, + "baseName": { + "existingJavaType": "java.lang.String", + "title": "The name of the base currency." + }, + "base": { + "existingJavaType": "java.lang.String", + "title": "The currency code of the base currency." + } + } +} diff --git a/schema_json/polygon/tickers/ticker/codes.json b/schema_json/polygon/tickers/ticker/codes.json new file mode 100644 index 00000000..16947b1e --- /dev/null +++ b/schema_json/polygon/tickers/ticker/codes.json @@ -0,0 +1,26 @@ +{ + "type": "object", + "title": "See https://polygon.io/docs/get_v2_reference_tickers_anchor", + "properties": { + "cik": { + "existingJavaType": "java.lang.String", + "title": "The CIK number for this ticker." + }, + "figi": { + "existingJavaType": "java.lang.String", + "title": "The OpenFIGI number for this ticker." + }, + "cfigi": { + "existingJavaType": "java.lang.String", + "title": "The composite OpenFIGI number for this ticker." + }, + "scfigi": { + "existingJavaType": "java.lang.String", + "title": "The shared Class OpenFIGI number for this ticker." + }, + "figiuid": { + "existingJavaType": "java.lang.String", + "title": "The unique OpenFIGI ID number for this ticker." + } + } +} diff --git a/schema_json/polygon/tickers/ticker/forex_ticker.json b/schema_json/polygon/tickers/ticker/forex_ticker.json deleted file mode 100644 index b640a665..00000000 --- a/schema_json/polygon/tickers/ticker/forex_ticker.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "type": "object", - "title": "See https://polygon.io/docs/#!/Reference/get_v2_reference_tickers", - "extends": { - "existingJavaType": "net.jacobpeterson.domain.polygon.tickers.ticker.Ticker" - }, - "properties": { - "attrs": { - "existingJavaType": "java.util.HashMap", - "title": "Attributes" - } - } -} diff --git a/schema_json/polygon/tickers/ticker/stock_ticker.json b/schema_json/polygon/tickers/ticker/stock_ticker.json deleted file mode 100644 index 7735d79c..00000000 --- a/schema_json/polygon/tickers/ticker/stock_ticker.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "type": "object", - "title": "See https://polygon.io/docs/#!/Reference/get_v2_reference_tickers", - "extends": { - "existingJavaType": "net.jacobpeterson.domain.polygon.tickers.ticker.Ticker" - }, - "properties": { - "type": { - "existingJavaType": "java.lang.String", - "title": "Type of symbol this is. See symbol types." - }, - "codes": { - "existingJavaType": "java.util.HashMap", - "title": "Codes" - } - } -} diff --git a/schema_json/polygon/tickers/ticker/ticker.json b/schema_json/polygon/tickers/ticker/ticker.json index f2ab579e..e792dcf4 100644 --- a/schema_json/polygon/tickers/ticker/ticker.json +++ b/schema_json/polygon/tickers/ticker/ticker.json @@ -1,42 +1,50 @@ { "type": "object", - "title": "See https://polygon.io/docs/#!/Reference/get_v2_reference_tickers", + "title": "See https://polygon.io/docs/get_v2_reference_tickers_anchor", "properties": { "ticker": { "existingJavaType": "java.lang.String", - "title": "Ticker" + "title": "The exchange symbol that this item is traded under." }, "name": { "existingJavaType": "java.lang.String", - "title": "Name of the item." + "title": "The name of the item." }, "market": { "existingJavaType": "java.lang.String", - "title": "Market" + "title": "The market in which this ticker participates." }, "locale": { "existingJavaType": "java.lang.String", - "title": "Locale" + "title": "The locale of where this ticker is traded." }, "currency": { "existingJavaType": "java.lang.String", - "title": "Currency" + "title": "The currency this ticker is traded in." }, "active": { "existingJavaType": "java.lang.Boolean", - "title": "Active" + "title": "Whether or not the ticker is active. False means the ticker has been delisted." }, "primaryExch": { "existingJavaType": "java.lang.String", - "title": "Primary Exchange" - }, - "updated": { - "existingJavaType": "java.time.ZonedDateTime", - "title": "Last time this company record was updated." + "title": "The listing exchange for this ticker." }, "url": { "existingJavaType": "java.lang.String", - "title": "URL of this symbol. Use this to get this symbols endpoints." + "title": "A URL that can be used to get more detailed information about the ticker." + }, + "updated": { + "existingJavaType": "java.time.LocalDate", + "title": "The last time this ticker record was updated." + }, + "attrs": { + "existingJavaType": "net.jacobpeterson.domain.polygon.tickers.ticker.Attributes", + "title": "The attributes for a currency." + }, + "codes": { + "existingJavaType": "net.jacobpeterson.domain.polygon.tickers.ticker.Codes", + "title": "The codes for a ticker." } } } diff --git a/schema_json/polygon/tickers/tickers_response.json b/schema_json/polygon/tickers/tickers_response.json index 3dab0aa3..9f9d1b42 100644 --- a/schema_json/polygon/tickers/tickers_response.json +++ b/schema_json/polygon/tickers/tickers_response.json @@ -1,26 +1,26 @@ { "type": "object", - "title": "See https://polygon.io/docs/#!/Reference/get_v2_reference_tickers", + "title": "See https://polygon.io/docs/get_v2_reference_tickers_anchor", "properties": { "status": { "existingJavaType": "java.lang.String", - "title": "Status" + "title": "The status of this request's response." }, "page": { "existingJavaType": "java.lang.Integer", - "title": "Page" + "title": "The page number of the current response." }, "perPage": { "existingJavaType": "java.lang.Integer", - "title": "Per Page" + "title": "The number of results per page." }, "count": { "existingJavaType": "java.lang.Integer", - "title": "Count" + "title": "The total number of results for this request." }, - "results": { + "tickers": { "existingJavaType": "java.util.ArrayList", - "title": "Results" + "title": "The tickers." } } } diff --git a/src/main/java/net/jacobpeterson/polygon/PolygonAPI.java b/src/main/java/net/jacobpeterson/polygon/PolygonAPI.java index b8b2acd3..79f494b1 100644 --- a/src/main/java/net/jacobpeterson/polygon/PolygonAPI.java +++ b/src/main/java/net/jacobpeterson/polygon/PolygonAPI.java @@ -34,7 +34,6 @@ import net.jacobpeterson.polygon.enums.FinancialSort; import net.jacobpeterson.polygon.enums.GainersLosersDirection; import net.jacobpeterson.polygon.enums.Market; -import net.jacobpeterson.polygon.enums.TickerSort; import net.jacobpeterson.polygon.enums.Timespan; import net.jacobpeterson.polygon.properties.PolygonProperties; import net.jacobpeterson.polygon.rest.PolygonRequest; @@ -113,30 +112,31 @@ public PolygonAPI(String baseAPIURL, String websocketURL, String keyID) { } /** - * Query all ticker symbols which are supported by Polygon.io. + * Query all ticker symbols which are supported by Polygon.io. Returns basic reference data for each matched ticker + * symbol. This API includes Crypto, Forex, and Stocks/Equities. * - * @param tickerSort Which field to sort by. + * @param sort Which field to sort by. * @param tickerType Get tickers of a certain type. See Ticker Types for available types. * @param market Get tickers for a specific market * @param locale Get tickers for a specific region/locale * @param search Search the name of tickers - * @param perpage How many items to be on each page during pagination. Max 50 + * @param perpage How many items to be on each page during pagination. Max 50 and default 50. * @param page Which page of results to return * @param active Filter for only active or inactive symbols * * @return the tickers * * @throws PolygonAPIRequestException the polygon API exception - * @see Tickers + * @see Tickers */ - public TickersResponse getTickers(TickerSort tickerSort, String tickerType, Market market, String locale, + public TickersResponse getTickers(String sort, String tickerType, Market market, String locale, String search, Integer perpage, Integer page, Boolean active) throws PolygonAPIRequestException { PolygonRequestBuilder builder = new PolygonRequestBuilder(baseAPIURL, PolygonConstants.VERSION_2_ENDPOINT, PolygonConstants.REFERENCE_ENDPOINT, PolygonConstants.TICKERS_ENDPOINT); - if (tickerSort != null) { - builder.appendURLParameter(PolygonConstants.SORT_PARAMETER, tickerSort.getAPIName()); + if (sort != null) { + builder.appendURLParameter(PolygonConstants.SORT_PARAMETER, sort); } if (tickerType != null) { diff --git a/src/main/java/net/jacobpeterson/polygon/enums/Market.java b/src/main/java/net/jacobpeterson/polygon/enums/Market.java index 24baa60d..0daeb76b 100644 --- a/src/main/java/net/jacobpeterson/polygon/enums/Market.java +++ b/src/main/java/net/jacobpeterson/polygon/enums/Market.java @@ -34,7 +34,7 @@ public enum Market implements APIName { /** * Instantiates a new {@link Market}. * - * @param apiName the api name + * @param apiName the API name */ Market(String apiName) { this.apiName = apiName; diff --git a/src/main/java/net/jacobpeterson/polygon/enums/TickerSort.java b/src/main/java/net/jacobpeterson/polygon/enums/TickerSort.java deleted file mode 100644 index a35d0183..00000000 --- a/src/main/java/net/jacobpeterson/polygon/enums/TickerSort.java +++ /dev/null @@ -1,38 +0,0 @@ -package net.jacobpeterson.polygon.enums; - -import net.jacobpeterson.abstracts.enums.APIName; - -/** - * The enum {@link TickerSort}. - */ -public enum TickerSort implements APIName { - - /** The ticker ascending {@link TickerSort}. */ - TICKER_ASCENDING("ticker"), - - /** The ticker descending {@link TickerSort}. */ - TICKER_DESCENDING("-ticker"), - - /** The type ascending {@link TickerSort}. */ - TYPE_ASCENDING("type"), - - /** The type descending {@link TickerSort}. */ - TYPE_DESCENDING("-type"); - - /** The API name. */ - String apiName; - - /** - * Instantiates a new {@link TickerSort}. - * - * @param apiName the api name - */ - TickerSort(String apiName) { - this.apiName = apiName; - } - - @Override - public String getAPIName() { - return apiName; - } -} diff --git a/src/main/java/net/jacobpeterson/util/gson/GsonUtil.java b/src/main/java/net/jacobpeterson/util/gson/GsonUtil.java index a71224ff..f6f49236 100644 --- a/src/main/java/net/jacobpeterson/util/gson/GsonUtil.java +++ b/src/main/java/net/jacobpeterson/util/gson/GsonUtil.java @@ -18,25 +18,18 @@ import java.util.Set; /** - * The type {@link GsonUtil}. + * {@link GsonUtil} contains utility methods relating to {@link Gson}. */ public class GsonUtil { - /** The logger. */ private static final Logger LOGGER = LoggerFactory.getLogger(GsonUtil.class); - - /** The constant CLASS_ANNOTATION_CACHE. */ private static final HashMap, ArrayList> CLASS_ANNOTATION_CACHE = new HashMap<>(); - - /** The constant GSON which include ISO date time to ZonedDateTime objects */ public static final Gson GSON = new GsonBuilder() .registerTypeAdapter(ZonedDateTime.class, new ZonedDateTimeAdapter()) .registerTypeAdapter(LocalDate.class, new LocalDateAdapter()) .enableComplexMapKeySerialization() .setLenient() .create(); - - /** The constant JSON_PARSER. */ public static final JsonParser JSON_PARSER = new JsonParser(); /** diff --git a/src/main/java/net/jacobpeterson/util/gson/ZonedDateTimeAdapter.java b/src/main/java/net/jacobpeterson/util/gson/ZonedDateTimeAdapter.java index f33c58cb..b4b8a5e6 100644 --- a/src/main/java/net/jacobpeterson/util/gson/ZonedDateTimeAdapter.java +++ b/src/main/java/net/jacobpeterson/util/gson/ZonedDateTimeAdapter.java @@ -25,6 +25,6 @@ public JsonElement serialize(ZonedDateTime src, Type typeOfSrc, JsonSerializatio @Override public ZonedDateTime deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException { - return ZonedDateTime.parse(json.getAsJsonPrimitive().getAsString(), DateTimeFormatter.ISO_OFFSET_DATE_TIME); + return ZonedDateTime.parse(json.getAsJsonPrimitive().getAsString(), DateTimeFormatter.ISO_DATE_TIME); } } From 82ad42b61d1480f84483464a081c3e22a75c3082 Mon Sep 17 00:00:00 2001 From: Jacob Peterson Date: Tue, 5 Jan 2021 23:44:20 -0700 Subject: [PATCH 069/271] Fix README link --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index a693b9b0..46713fed 100644 --- a/README.md +++ b/README.md @@ -31,7 +31,7 @@ This is a Java implementation for the Alpaca Date: Tue, 5 Jan 2021 23:47:55 -0700 Subject: [PATCH 070/271] Update to 6.1 --- README.md | 4 ++-- build.gradle | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 46713fed..9faa4864 100644 --- a/README.md +++ b/README.md @@ -39,7 +39,7 @@ If you are using Gradle as your build tool, add the following dependency to your ``` dependencies { - implementation group: 'net.jacobpeterson', name: 'alpaca-java', version: '6.0.1' + implementation group: 'net.jacobpeterson', name: 'alpaca-java', version: '6.1' } ``` @@ -49,7 +49,7 @@ If you are using Maven as your build tool, add the following dependency to your net.jacobpeterson alpaca-java - 6.0.1 + 6.1 compile ``` diff --git a/build.gradle b/build.gradle index 31e1e0c6..fee18506 100644 --- a/build.gradle +++ b/build.gradle @@ -26,7 +26,7 @@ tasks.getByName("generateJsonSchema2Pojo").enabled(false) // Disable default 'js archivesBaseName = 'alpaca-java' group = 'net.jacobpeterson' -version = '6.0.1' +version = '6.1' repositories { mavenLocal() From 30cdb83b7e12ead124707e9bc01622bda0b7c668 Mon Sep 17 00:00:00 2001 From: Jacob Peterson Date: Wed, 6 Jan 2021 00:27:59 -0700 Subject: [PATCH 071/271] Add clarity for Websocket streaming to README --- README.md | 48 ++++++++++++++++++++++++++++++++++++++---------- build.gradle | 19 ------------------- 2 files changed, 38 insertions(+), 29 deletions(-) diff --git a/README.md b/README.md index 46713fed..72a641d3 100644 --- a/README.md +++ b/README.md @@ -392,8 +392,8 @@ Alpaca offers WebSocket streaming for account and order updates. Example usage: ```java try { - // List to account updates and trade updates from Alpaca and print their outputs - alpacaAPI.addAlpacaStreamListener(new AlpacaStreamListenerAdapter( + // List to account updates and trade updates from Alpaca and print their messages out + AlpacaStreamListener alpacaStreamListener = new AlpacaStreamListenerAdapter( AlpacaStreamMessageType.ACCOUNT_UPDATES, AlpacaStreamMessageType.TRADE_UPDATES) { @Override @@ -408,7 +408,20 @@ try { break; } } - }); + }; + + // Add the 'AlpacaStreamListener' + // Note that when the first 'AlpacaStreamListener' is added, the Websocket + // connection is created. + alpacaAPI.addAlpacaStreamListener(alpacaStreamListener); + + // Wait for 5 seconds + Thread.sleep(5000); + + // Remove the 'AlpacaStreamListener' + // Note that when the last 'AlpacaStreamListener' is removed, the Websocket + // connection is closed. + alpacaAPI.removeAlpacaStreamListener(alpacaStreamListener); } catch (WebsocketException exception) { exception.printStackTrace(); } @@ -420,8 +433,8 @@ Alpaca's Data API provides websocket streaming for trades, quotes and minute bar Example usage: ```java try { - // Print TSLA quotes, trades, and minute aggregates - alpacaAPI.addMarketDataStreamListener(new MarketDataStreamListenerAdapter( + // Listen to TSLA quotes, trades, and minute aggregates and print their messages out + MarketDataStreamListener streamListenerTSLA = new MarketDataStreamListenerAdapter( "TSLA", MarketDataStreamMessageType.QUOTES, MarketDataStreamMessageType.TRADES, @@ -441,7 +454,20 @@ try { break; } } - }); + }; + + // Add the 'MarketDataStreamListener' + // Note that when the first 'MarketDataStreamListener' is added, the Websocket + // connection is created. + alpacaAPI.addMarketDataStreamListener(streamListenerTSLA); + + // Wait for 5 seconds + Thread.sleep(5000); + + // Remove the 'MarketDataStreamListener' + // Note that when the last 'MarketDataStreamListener' is removed, the Websocket + // connection is closed. + alpacaAPI.removeMarketDataStreamListener(streamListenerTSLA); } catch (WebsocketException exception) { exception.printStackTrace(); } @@ -489,12 +515,14 @@ try { polygonAPI.addPolygonStreamListener(new PolygonStreamListenerAdapter("AAPL", PolygonStreamMessageType.values()) { @Override - public void onStreamUpdate(PolygonStreamMessageType streamMessageType, PolygonStreamMessage streamMessage) { - System.out.println("===> streamUpdate " + streamMessageType + " " + streamMessage); + public void onStreamUpdate(PolygonStreamMessageType streamMessageType, + PolygonStreamMessage streamMessage) { + System.out.printf("%s stream update: %s \n", + streamMessageType, streamMessage); } }); -} catch (WebsocketException e) { - e.printStackTrace(); +} catch (WebsocketException exception) { + exception.printStackTrace(); } ``` Again, refer to the `PolygonAPI` [Javadoc](https://javadoc.io/doc/net.jacobpeterson/alpaca-java/latest/net/jacobpeterson/polygon/PolygonAPI.html) for an exhaustive list of all available methods. diff --git a/build.gradle b/build.gradle index 31e1e0c6..c502901e 100644 --- a/build.gradle +++ b/build.gradle @@ -146,25 +146,6 @@ processResources { // END publishing // -// -// START allJar task -// -// Used for creating a fat jar (a jar packaged with all dependencies) -task fatJar(type: Jar) { - from { - configurations.compile.collect { it.isDirectory() ? it : zipTree(it) } - configurations.implemenation.collect { it.isDirectory() ? it : zipTree(it) } - configurations.runtimeClasspath.collect { it.isDirectory() ? it : zipTree(it) } - } - - archiveClassifier.set "fat" - - with jar -} -// -// END allJar task -// - // // START POJO generation // From 6d35a6a3aaa39504903e513d472538fa7376160a Mon Sep 17 00:00:00 2001 From: Jacob Peterson Date: Wed, 6 Jan 2021 18:03:02 -0700 Subject: [PATCH 072/271] Update Javadocs in abstracts package and modify build.gradle --- build.gradle | 36 +- .../abstracts/enums/APIName.java | 3 + .../abstracts/rest/AbstractRequest.java | 303 ++++++++------- .../rest/AbstractRequestBuilder.java | 87 ++--- .../AbstractAPIRequestException.java | 34 +- .../AbstractWebsocketClientEndpoint.java | 38 +- .../exception/WebsocketException.java | 4 +- .../websocket/listener/StreamListener.java | 12 +- .../websocket/message/StreamMessage.java | 2 +- .../websocket/message/StreamMessageType.java | 2 +- .../net/jacobpeterson/alpaca/AlpacaAPI.java | 3 +- .../jacobpeterson/alpaca/AlpacaConstants.java | 102 +---- .../util/concurrency/ExecutorTracer.java | 359 ------------------ .../ScheduledThreadPoolExecutorTracer.java | 60 --- .../concurrency/ThreadPoolExecutorTracer.java | 69 ---- 15 files changed, 233 insertions(+), 881 deletions(-) delete mode 100644 src/main/java/net/jacobpeterson/util/concurrency/ExecutorTracer.java delete mode 100644 src/main/java/net/jacobpeterson/util/concurrency/ScheduledThreadPoolExecutorTracer.java delete mode 100644 src/main/java/net/jacobpeterson/util/concurrency/ThreadPoolExecutorTracer.java diff --git a/build.gradle b/build.gradle index c502901e..145c8e6b 100644 --- a/build.gradle +++ b/build.gradle @@ -44,6 +44,7 @@ dependencies { implementation group: 'org.eclipse.jetty.websocket', name: 'javax-websocket-client-impl', version: '9.4.28.v20200408' testImplementation group: 'junit', name: 'junit', version: '4.12' + testImplementation group: 'org.mockito', name: 'mockito-core', version: '3.7.0' } // Exclude any SLF4j-implementation transitive dependencies so users can use a logging framework of their choice @@ -51,39 +52,20 @@ configurations.implementation { exclude group: 'commons-logging', module: 'commons-logging' } -// -// START Build options -// sourceCompatibility = 1.8 targetCompatibility = 1.8 [compileJava, compileTestJava]*.options*.encoding = 'UTF-8' -// Used to suppress java doc warnings -javadoc.options.addStringOption('Xdoclint:none', '-quiet') -// Explicitly define Charset for Javadocs -javadoc.options.addStringOption("charset", "UTF-8") -// -// END Build options -// +javadoc { + // Used to suppress Javadoc linting warnings + options.addStringOption('Xdoclint:none', '-quiet') + // Explicitly define Charset for Javadocs + options.addStringOption("charset", "UTF-8") -// -// START reproducible builds -// -// Reference: https://dzone.com/articles/reproducible-builds-in-java -// Prevent manifest from changing every build -project.tasks.withType(Jar) { - manifest.attributes Date: '' - preserveFileTimestamps = false -} - -// Prevent timestamps from appearing in JAR and use reproducible file order -tasks.withType(AbstractArchiveTask) { - preserveFileTimestamps = false - reproducibleFileOrder = true + options { + links 'https://docs.oracle.com/javase/8/docs/api/' + } } -// -// END reproducible builds -// // // START publishing diff --git a/src/main/java/net/jacobpeterson/abstracts/enums/APIName.java b/src/main/java/net/jacobpeterson/abstracts/enums/APIName.java index bfabe725..b921793d 100644 --- a/src/main/java/net/jacobpeterson/abstracts/enums/APIName.java +++ b/src/main/java/net/jacobpeterson/abstracts/enums/APIName.java @@ -1,5 +1,8 @@ package net.jacobpeterson.abstracts.enums; +/** + * The {@link APIName} interface is for enums classes to define enum members with API names. + */ public interface APIName { /** diff --git a/src/main/java/net/jacobpeterson/abstracts/rest/AbstractRequest.java b/src/main/java/net/jacobpeterson/abstracts/rest/AbstractRequest.java index 2d0033dd..3bad064d 100644 --- a/src/main/java/net/jacobpeterson/abstracts/rest/AbstractRequest.java +++ b/src/main/java/net/jacobpeterson/abstracts/rest/AbstractRequest.java @@ -11,6 +11,7 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.lang.reflect.Type; @@ -18,291 +19,283 @@ import java.util.Map; /** - * The type {@link AbstractRequest}. + * {@link AbstractRequest} contains methods for HTTP requests. */ public abstract class AbstractRequest { - /** The logger. */ private static final Logger LOGGER = LoggerFactory.getLogger(AbstractRequest.class); - /** The headers. */ - protected final Map headers = new HashMap<>(); + protected final Map headers; /** - * Invoke get. + * Instantiates a new {@link AbstractRequest}. + */ + public AbstractRequest() { + headers = new HashMap<>(); + } + + /** + * Invoke GET request. * - * @param abstractRequestBuilder the abstract request builder + * @param abstractRequestBuilder the {@link AbstractRequestBuilder} * - * @return the http response + * @return the {@link HttpResponse} */ public HttpResponse invokeGet(AbstractRequestBuilder abstractRequestBuilder) { - try { - String url = abstractRequestBuilder.getURL(); + String url = abstractRequestBuilder.buildURL(); - LOGGER.debug("GET URL " + url); + LOGGER.debug("GET URL {}", url); - GetRequest request = Unirest.get(url); + GetRequest request = Unirest.get(url); - if (!headers.isEmpty()) { - request.headers(headers); + if (!headers.isEmpty()) { + request.headers(headers); - LOGGER.debug("GET Headers: " + headers); - } + LOGGER.debug("GET Headers: {}", headers); + } + try { return request.asBinary(); - } catch (UnirestException e) { - LOGGER.error("UnirestException", e); + } catch (UnirestException exception) { + LOGGER.error("Could not get request as binary!", exception); + return null; } - - return null; } /** - * Invoke head. + * Invoke HEAD request. * - * @param abstractRequestBuilder the abstract request builder + * @param abstractRequestBuilder the {@link AbstractRequestBuilder} * - * @return the http response + * @return the {@link HttpResponse} */ public HttpResponse invokeHead(AbstractRequestBuilder abstractRequestBuilder) { - try { - String url = abstractRequestBuilder.getURL(); + String url = abstractRequestBuilder.buildURL(); - LOGGER.debug("HEAD URL " + url); + LOGGER.debug("HEAD URL {}", url); - GetRequest request = Unirest.head(url); + GetRequest request = Unirest.head(url); - if (!headers.isEmpty()) { - request.headers(headers); + if (!headers.isEmpty()) { + request.headers(headers); - LOGGER.debug("HEAD Headers: " + headers); - } + LOGGER.debug("HEAD Headers: {}", headers); + } + try { return request.asBinary(); - } catch (UnirestException e) { - LOGGER.error("UnirestException", e); + } catch (UnirestException exception) { + LOGGER.error("Could not get request as binary!", exception); + return null; } - - return null; } /** - * Invoke post. + * Invoke POST request. * - * @param abstractRequestBuilder the abstract request builder + * @param abstractRequestBuilder the {@link AbstractRequestBuilder} * - * @return the http response + * @return the {@link HttpResponse} */ public HttpResponse invokePost(AbstractRequestBuilder abstractRequestBuilder) { - try { - String url = abstractRequestBuilder.getURL(); + String url = abstractRequestBuilder.buildURL(); - LOGGER.debug("POST URL: " + url); + LOGGER.debug("POST URL: {}", url); - HttpRequestWithBody request = Unirest.post(url); + HttpRequestWithBody request = Unirest.post(url); - if (!headers.isEmpty()) { - request.headers(headers); + if (!headers.isEmpty()) { + request.headers(headers); - LOGGER.debug("POST Headers: " + headers); - } + LOGGER.debug("POST Headers: {}", headers); + } - String body = abstractRequestBuilder.getBody(); - if (body != null) { - request.body(body); + String body = abstractRequestBuilder.buildBody(); + if (body != null) { + request.body(body); - LOGGER.debug("POST Body: " + body); - } + LOGGER.debug("POST Body: {}", body); + } + try { return request.asBinary(); - } catch (UnirestException e) { - LOGGER.error("UnirestException", e); + } catch (UnirestException exception) { + LOGGER.error("Could not get request as binary!", exception); + return null; } - - return null; } /** - * Invoke patch. + * Invoke PATCH request. * - * @param abstractRequestBuilder the abstract request builder + * @param abstractRequestBuilder the {@link AbstractRequestBuilder} * - * @return the http response + * @return the {@link HttpResponse} */ public HttpResponse invokePatch(AbstractRequestBuilder abstractRequestBuilder) { - try { - String url = abstractRequestBuilder.getURL(); + String url = abstractRequestBuilder.buildURL(); - LOGGER.debug("PATCH URL " + url); + LOGGER.debug("PATCH URL {}", url); - HttpRequestWithBody request = Unirest.patch(url); + HttpRequestWithBody request = Unirest.patch(url); - if (!headers.isEmpty()) { - request.headers(headers); + if (!headers.isEmpty()) { + request.headers(headers); - LOGGER.debug("PATCH Headers: " + headers); - } + LOGGER.debug("PATCH Headers: {}", headers); + } - String body = abstractRequestBuilder.getBody(); - if (body != null) { - request.body(body); + String body = abstractRequestBuilder.buildBody(); + if (body != null) { + request.body(body); - LOGGER.debug("PATCH Body: " + body); - } + LOGGER.debug("PATCH Body: {}", body); + } + try { return request.asBinary(); - } catch (UnirestException e) { - LOGGER.error("UnirestException", e); + } catch (UnirestException exception) { + LOGGER.error("Could not get request as binary!", exception); + return null; } - - return null; } /** - * Invoke put http response. + * Invoke PUT request. * - * @param abstractRequestBuilder the abstract request builder + * @param abstractRequestBuilder the {@link AbstractRequestBuilder} * - * @return the http response + * @return the {@link HttpResponse} */ public HttpResponse invokePut(AbstractRequestBuilder abstractRequestBuilder) { - try { - String url = abstractRequestBuilder.getURL(); + String url = abstractRequestBuilder.buildURL(); - LOGGER.debug("PUT URL " + url); + LOGGER.debug("PUT URL {}", url); - HttpRequestWithBody request = Unirest.put(url); + HttpRequestWithBody request = Unirest.put(url); - if (!headers.isEmpty()) { - request.headers(headers); + if (!headers.isEmpty()) { + request.headers(headers); - LOGGER.debug("PUT Headers: " + headers); - } + LOGGER.debug("PUT Headers: {}", headers); + } - String body = abstractRequestBuilder.getBody(); - if (body != null) { - request.body(body); + String body = abstractRequestBuilder.buildBody(); + if (body != null) { + request.body(body); - LOGGER.debug("PUT Body: " + body); - } + LOGGER.debug("PUT Body: " + body); + } + try { return request.asBinary(); - } catch (UnirestException e) { - LOGGER.error("UnirestException", e); + } catch (UnirestException exception) { + LOGGER.error("Could not get request as binary!", exception); + return null; } - - return null; } /** - * Invoke delete. + * Invoke DELETE request. * - * @param abstractRequestBuilder the abstract request builder + * @param abstractRequestBuilder the {@link AbstractRequestBuilder} * - * @return the http response + * @return the {@link HttpResponse} */ public HttpResponse invokeDelete(AbstractRequestBuilder abstractRequestBuilder) { - try { - String url = abstractRequestBuilder.getURL(); + String url = abstractRequestBuilder.buildURL(); - LOGGER.debug("DELETE URL " + url); + LOGGER.debug("DELETE URL {}", url); - HttpRequestWithBody request = Unirest.delete(url); + HttpRequestWithBody request = Unirest.delete(url); - if (!headers.isEmpty()) { - request.headers(headers); + if (!headers.isEmpty()) { + request.headers(headers); - LOGGER.debug("DELETE Headers: " + headers); - } + LOGGER.debug("DELETE Headers: {}", headers); + } - String body = abstractRequestBuilder.getBody(); - if (body != null) { - request.body(body); + String body = abstractRequestBuilder.buildBody(); + if (body != null) { + request.body(body); - LOGGER.debug("DELETE Body: " + body); - } + LOGGER.debug("DELETE Body: {}", body); + } + try { return request.asBinary(); - } catch (UnirestException e) { - LOGGER.error("UnirestException", e); + } catch (UnirestException exception) { + LOGGER.error("Could not get request as binary!", exception); + return null; } - - return null; } /** - * Invoke options. + * Invoke OPTIONS request. * - * @param abstractRequestBuilder the abstract request builder + * @param abstractRequestBuilder the {@link AbstractRequestBuilder} * - * @return the http response + * @return the {@link HttpResponse} */ public HttpResponse invokeOptions(AbstractRequestBuilder abstractRequestBuilder) { - try { - String url = abstractRequestBuilder.getURL(); + String url = abstractRequestBuilder.buildURL(); - LOGGER.debug("OPTIONS URL " + url); + LOGGER.debug("OPTIONS URL {}", url); - HttpRequestWithBody request = Unirest.options(url); + HttpRequestWithBody request = Unirest.options(url); - if (!headers.isEmpty()) { - request.headers(headers); + if (!headers.isEmpty()) { + request.headers(headers); - LOGGER.debug("OPTIONS Headers: " + headers); - } + LOGGER.debug("OPTIONS Headers: {}", headers); + } - String body = abstractRequestBuilder.getBody(); - if (body != null) { - request.body(body); + String body = abstractRequestBuilder.buildBody(); + if (body != null) { + request.body(body); - LOGGER.debug("OPTIONS Body: " + body); - } + LOGGER.debug("OPTIONS Body: {}", body); + } + try { return request.asBinary(); - } catch (UnirestException e) { - LOGGER.error("UnirestException", e); + } catch (UnirestException exception) { + LOGGER.error("Could not get request as binary!", exception); + return null; } - - return null; } /** - * Gets the response object. + * Gets a parsed Object with {@link GsonUtil#GSON} given {@link HttpResponse#getRawBody()} JSON. * * @param the generic type - * @param httpResponse the http response - * @param type the type + * @param httpResponse the {@link HttpResponse} + * @param type the type of object * - * @return the response object + * @return the {@link HttpResponse#getRawBody()} parsed Object */ public T getResponseObject(HttpResponse httpResponse, Type type) { - T responseObjectFromJson = null; - try (JsonReader jsonReader = new JsonReader(new InputStreamReader(httpResponse.getRawBody()))) { - responseObjectFromJson = GsonUtil.GSON.fromJson(jsonReader, type); - } catch (Exception e) { - LOGGER.error("Exception", e); + return GsonUtil.GSON.fromJson(jsonReader, type); + } catch (IOException ioException) { + LOGGER.error("Could not parse response JSON object", ioException); + return null; } - - return responseObjectFromJson; } /** - * Gets response json. + * Gets a {@link JsonElement} with {@link GsonUtil#GSON} given {@link HttpResponse#getRawBody()} JSON. * - * @param httpResponse the http response + * @param httpResponse the {@link HttpResponse} * - * @return the response json + * @return the {@link HttpResponse#getRawBody()} {@link JsonElement} */ public JsonElement getResponseJSON(HttpResponse httpResponse) { - JsonElement responseJsonElement = null; - try (JsonReader jsonReader = new JsonReader(new InputStreamReader(httpResponse.getRawBody()))) { - responseJsonElement = GsonUtil.JSON_PARSER.parse(jsonReader); - } catch (Exception e) { - LOGGER.error("Exception", e); + return GsonUtil.JSON_PARSER.parse(jsonReader); + } catch (IOException ioException) { + LOGGER.error("Could not parse response JSON object", ioException); + return null; } - - return responseJsonElement; } } diff --git a/src/main/java/net/jacobpeterson/abstracts/rest/AbstractRequestBuilder.java b/src/main/java/net/jacobpeterson/abstracts/rest/AbstractRequestBuilder.java index 17917971..b0863e30 100644 --- a/src/main/java/net/jacobpeterson/abstracts/rest/AbstractRequestBuilder.java +++ b/src/main/java/net/jacobpeterson/abstracts/rest/AbstractRequestBuilder.java @@ -9,95 +9,81 @@ import java.util.Map; /** - * The type {@link AbstractRequestBuilder}. + * {@link AbstractRequestBuilder} is a builder for URL Strings. */ public abstract class AbstractRequestBuilder { - /** The Constant URL_SEPARATOR. */ - private final static String URL_SEPARATOR = "/"; + private static final String URL_SEPARATOR = "/"; - /** The appended endpoints. */ - private final List appendedEndpoints = new ArrayList<>(); + private final String baseURL; + private final List appendedEndpoints; + private final Map urlParameters; + private final JsonObject bodyJsonObject; - /** The url parameters. */ - private final Map urlParameters = new LinkedHashMap<>(); - - /** The Body json object. */ - private final JsonObject bodyJsonObject = new JsonObject(); - - /** The base url. */ - private final String baseUrl; - - /** The custom body. */ private String customBody; /** - * Instantiates a new abstract request builder. + * Instantiates a new {@link AbstractRequestBuilder}. * - * @param baseUrl the base url + * @param baseURL the base URL */ - public AbstractRequestBuilder(String baseUrl) { - this.baseUrl = baseUrl; + public AbstractRequestBuilder(String baseURL) { + this.baseURL = baseURL; + + appendedEndpoints = new ArrayList<>(); + urlParameters = new LinkedHashMap<>(); + bodyJsonObject = new JsonObject(); } /** - * Append endpoint. + * Appends an endpoint. * * @param endpoint the endpoint */ public void appendEndpoint(String endpoint) { - if (endpoint != null) { - appendedEndpoints.add(endpoint); - } + appendedEndpoints.add(endpoint); } /** - * Append URL parameter. + * Appends a URL parameter. * - * @param urlKey the url key - * @param urlValue the url value + * @param urlKey the URL key + * @param urlValue the URL value */ public void appendURLParameter(String urlKey, String urlValue) { - if (urlValue != null) { - urlParameters.put(urlKey, urlValue); - } + urlParameters.put(urlKey, urlValue); } /** - * Append json body property. + * Appends a JSON string property to {@link #buildBody()}. * * @param bodyKey the body key * @param bodyValue the body value */ public void appendJSONBodyProperty(String bodyKey, String bodyValue) { - if (bodyValue != null) { - bodyJsonObject.addProperty(bodyKey, bodyValue); - } + bodyJsonObject.addProperty(bodyKey, bodyValue); } /** - * Append body json property. + * Appends a {@link JsonElement} property to {@link #buildBody()}. * * @param bodyKey the body key - * @param bodyValue the body value + * @param bodyValue the {@link JsonElement} body value */ public void appendJSONBodyJSONProperty(String bodyKey, JsonElement bodyValue) { - if (bodyValue != null) { - bodyJsonObject.add(bodyKey, bodyValue); - } + bodyJsonObject.add(bodyKey, bodyValue); } /** - * Gets the url. + * Builds the URL string {@link AbstractRequestBuilder}. * - * @return the url + * @return the built URL string */ - public String getURL() { - StringBuilder builder = new StringBuilder(baseUrl); + public String buildURL() { + StringBuilder builder = new StringBuilder(baseURL); for (String appendedEndpoint : appendedEndpoints) { - builder.append(URL_SEPARATOR); - builder.append(appendedEndpoint); + builder.append(URL_SEPARATOR).append(appendedEndpoint); } if (!urlParameters.isEmpty()) { @@ -109,7 +95,8 @@ public String getURL() { builder.append(entry.getValue().trim()); builder.append('&'); } - // removes last & + + // removes last '&' builder.deleteCharAt(builder.length() - 1); } @@ -117,11 +104,11 @@ public String getURL() { } /** - * Gets the body. + * Builds the body string of this {@link AbstractRequestBuilder}. * - * @return the body + * @return the body string */ - public String getBody() { + public String buildBody() { if (customBody != null) { return customBody; } else if (bodyJsonObject.size() > 0) { @@ -132,9 +119,9 @@ public String getBody() { } /** - * Sets custom body. + * Sets a custom body string for {@link #buildBody()}. * - * @param customBody the custom body + * @param customBody the custom body string */ public void setCustomBody(String customBody) { this.customBody = customBody; diff --git a/src/main/java/net/jacobpeterson/abstracts/rest/exception/AbstractAPIRequestException.java b/src/main/java/net/jacobpeterson/abstracts/rest/exception/AbstractAPIRequestException.java index 59388c7e..2fd621b1 100644 --- a/src/main/java/net/jacobpeterson/abstracts/rest/exception/AbstractAPIRequestException.java +++ b/src/main/java/net/jacobpeterson/abstracts/rest/exception/AbstractAPIRequestException.java @@ -9,35 +9,18 @@ import java.io.InputStreamReader; /** - * The type {@link AbstractAPIRequestException}. + * {@link AbstractAPIRequestException} represents {@link HttpResponse} request exceptions. */ public abstract class AbstractAPIRequestException extends Exception { - /** The Constant serialVersionUID. */ - private static final long serialVersionUID = 1L; - - /** The constant CODE_KEY. */ protected static final String CODE_KEY = "code"; - - /** The constant MESSAGE_KEY. */ protected static final String MESSAGE_KEY = "message"; - /** The Api name. */ protected final String apiName; - - /** The Http response. */ protected HttpResponse httpResponse; - - /** The Request code. */ protected int requestStatusCode; - - /** The Request status text. */ protected String requestStatusText; - - /** The API response code. */ protected Integer apiResponseCode; - - /** The API response message. */ protected String apiResponseMessage; /** @@ -65,9 +48,7 @@ public AbstractAPIRequestException(String apiName, HttpResponse htt protected abstract void parseAPIExceptionMessage(); /** - * Parse a standard API exception response. - *

- * The following format is parsed: + * Parse a standard API exception response of the following format: *

      *  {
      *     "code": 40010001,
@@ -96,6 +77,9 @@ protected void parseStandardAPIExceptionResponse() {
         }
     }
 
+    /**
+     * {@inheritDoc} This will call {@link #parseAPIExceptionMessage()} and return a formatted message.
+     */
     @Override
     public String getMessage() {
         this.parseAPIExceptionMessage();
@@ -134,18 +118,18 @@ public String getRequestStatusText() {
     }
 
     /**
-     * Gets api response code.
+     * Gets API response code.
      *
-     * @return the api response code
+     * @return the API response code
      */
     public int getAPIResponseCode() {
         return apiResponseCode;
     }
 
     /**
-     * Gets api response message.
+     * Gets API response message.
      *
-     * @return the api response message
+     * @return the API response message
      */
     public String getAPIResponseMessage() {
         return apiResponseMessage;
diff --git a/src/main/java/net/jacobpeterson/abstracts/websocket/client/AbstractWebsocketClientEndpoint.java b/src/main/java/net/jacobpeterson/abstracts/websocket/client/AbstractWebsocketClientEndpoint.java
index 38a56ab0..edc39ac2 100644
--- a/src/main/java/net/jacobpeterson/abstracts/websocket/client/AbstractWebsocketClientEndpoint.java
+++ b/src/main/java/net/jacobpeterson/abstracts/websocket/client/AbstractWebsocketClientEndpoint.java
@@ -1,6 +1,5 @@
 package net.jacobpeterson.abstracts.websocket.client;
 
-import net.jacobpeterson.util.concurrency.ExecutorTracer;
 import org.eclipse.jetty.util.component.LifeCycle;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -13,54 +12,45 @@
 import java.io.IOException;
 import java.net.URI;
 import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
 
 /**
- * The type {@link AbstractWebsocketClientEndpoint}.
+ * {@link AbstractWebsocketClientEndpoint} is used for handling the Websockets.
  * 
- * NOTES: You MUST annotate a subclass with {@link javax.websocket.ClientEndpoint} and the appropriate websocket - * subprotocols because websocket annotations don't work with inheritance. The subclass must also contain separate - * methods with the following annotations: {@link javax.websocket.OnOpen}, {@link javax.websocket.OnClose}, {@link - * javax.websocket.OnMessage}, and {@link javax.websocket.OnError}. + * NOTES: You MUST annotate a subclass with @see and the appropriate websocket sub-protocols because Websocket + * annotations don't work with inheritance. The subclass must also contain separate methods with the following + * annotations: {@link javax.websocket.OnOpen}, {@link javax.websocket.OnClose}, {@link javax.websocket.OnMessage}, and + * {@link javax.websocket.OnError}. */ public abstract class AbstractWebsocketClientEndpoint { - /** The constant LOGGER. */ private static final Logger LOGGER = LoggerFactory.getLogger(AbstractWebsocketClientEndpoint.class); - /** The Websocket client. */ private final WebsocketClient websocketClient; - - /** The Endpoint uri. */ private final URI endpointURI; - - /** The Message thread name. */ private final String messageThreadName; /** - * The Executor service, which passes message handlers to a different thread, will prevent overflow of server - * buffers (causing a disconnect) from not consuming data fast enough on the client end. + * {@link ExecutorService} passes message handlers to a different thread, which will prevent overflow of server + * buffers which may cause a disconnect from not consuming data fast enough on the client end. */ private ExecutorService executorService; - - /** The automatically reconnect boolean. */ private boolean automaticallyReconnect; - - /** The User session. */ private Session userSession; /** * Instantiates a new {@link AbstractWebsocketClientEndpoint}. * - * @param websocketClient the websocket client - * @param endpointURI the endpoint uri + * @param websocketClient the {@link WebsocketClient} + * @param endpointURI the endpoint {@link URI} * @param messageThreadName the message thread name */ - public AbstractWebsocketClientEndpoint(WebsocketClient websocketClient, URI endpointURI, - String messageThreadName) { + public AbstractWebsocketClientEndpoint(WebsocketClient websocketClient, URI endpointURI, String messageThreadName) { this.websocketClient = websocketClient; this.endpointURI = endpointURI; this.messageThreadName = messageThreadName; - this.automaticallyReconnect = true; + + automaticallyReconnect = true; } /** @@ -70,7 +60,7 @@ public AbstractWebsocketClientEndpoint(WebsocketClient websocketClient, URI endp * @throws IOException Signals that an I/O exception has occurred. */ public void connect() throws DeploymentException, IOException { - executorService = ExecutorTracer.newSingleThreadExecutor(r -> new Thread(r, messageThreadName)); + executorService = Executors.newSingleThreadExecutor(runnable -> new Thread(runnable, messageThreadName)); WebSocketContainer container = ContainerProvider.getWebSocketContainer(); diff --git a/src/main/java/net/jacobpeterson/abstracts/websocket/exception/WebsocketException.java b/src/main/java/net/jacobpeterson/abstracts/websocket/exception/WebsocketException.java index a2b0511e..0c7d57ff 100644 --- a/src/main/java/net/jacobpeterson/abstracts/websocket/exception/WebsocketException.java +++ b/src/main/java/net/jacobpeterson/abstracts/websocket/exception/WebsocketException.java @@ -3,7 +3,7 @@ import java.io.IOException; /** - * The type {@link WebsocketException}. + * {@link WebsocketException} is used for Websocket-related exceptions. */ public class WebsocketException extends IOException { @@ -29,7 +29,7 @@ public WebsocketException(String message, Throwable cause) { /** * Instantiates a new {@link WebsocketException}. * - * @param exception the Exception + * @param exception the {@link Exception} */ public WebsocketException(Exception exception) { super(exception.getMessage(), exception.getCause()); diff --git a/src/main/java/net/jacobpeterson/abstracts/websocket/listener/StreamListener.java b/src/main/java/net/jacobpeterson/abstracts/websocket/listener/StreamListener.java index 717bcc56..558a0dce 100644 --- a/src/main/java/net/jacobpeterson/abstracts/websocket/listener/StreamListener.java +++ b/src/main/java/net/jacobpeterson/abstracts/websocket/listener/StreamListener.java @@ -4,18 +4,18 @@ import net.jacobpeterson.abstracts.websocket.message.StreamMessageType; /** - * The type {@link StreamListener}. + * {@link StreamListener} is an interface for listening to stream updates. * - * @param the stream message type type parameter - * @param the stream message type parameter + * @param the {@link StreamMessageType} type parameter + * @param the {@link StreamMessage} type parameter */ public interface StreamListener { /** - * On stream update. + * Called on a stream update. * - * @param streamMessageType the stream message type - * @param streamMessage the stream message + * @param streamMessageType the {@link StreamMessageType} + * @param streamMessage the {@link StreamMessage} */ void onStreamUpdate(T streamMessageType, M streamMessage); } diff --git a/src/main/java/net/jacobpeterson/abstracts/websocket/message/StreamMessage.java b/src/main/java/net/jacobpeterson/abstracts/websocket/message/StreamMessage.java index 1d831690..ab14452b 100644 --- a/src/main/java/net/jacobpeterson/abstracts/websocket/message/StreamMessage.java +++ b/src/main/java/net/jacobpeterson/abstracts/websocket/message/StreamMessage.java @@ -1,7 +1,7 @@ package net.jacobpeterson.abstracts.websocket.message; /** - * The type {@link StreamMessage}. + * {@link StreamMessage} is an interface to define stream messages. */ public interface StreamMessage { } diff --git a/src/main/java/net/jacobpeterson/abstracts/websocket/message/StreamMessageType.java b/src/main/java/net/jacobpeterson/abstracts/websocket/message/StreamMessageType.java index cd649070..14409939 100644 --- a/src/main/java/net/jacobpeterson/abstracts/websocket/message/StreamMessageType.java +++ b/src/main/java/net/jacobpeterson/abstracts/websocket/message/StreamMessageType.java @@ -1,7 +1,7 @@ package net.jacobpeterson.abstracts.websocket.message; /** - * The type {@link StreamMessageType}. + * {@link StreamMessageType} is an interface to define a stream message type. */ public interface StreamMessageType { } diff --git a/src/main/java/net/jacobpeterson/alpaca/AlpacaAPI.java b/src/main/java/net/jacobpeterson/alpaca/AlpacaAPI.java index 2c5dbd1d..04952db3 100644 --- a/src/main/java/net/jacobpeterson/alpaca/AlpacaAPI.java +++ b/src/main/java/net/jacobpeterson/alpaca/AlpacaAPI.java @@ -248,6 +248,7 @@ public ArrayList getAccountActivities(ZonedDateTime date, Zoned if (response.getStatus() != 200) { throw new AlpacaAPIRequestException(response); } + JsonElement responseJsonElement = alpacaRequest.getResponseJSON(response); ArrayList accountActivities = new ArrayList<>(); @@ -1672,7 +1673,7 @@ public LastTradeResponse getLastTrade(String symbol) throws AlpacaAPIRequestExce * @return the last quote response object * * @throws AlpacaAPIRequestException the alpaca api request exception - * @see
Last Quote */ public LastQuoteResponse getLastQuote(String symbol) throws AlpacaAPIRequestException { Preconditions.checkNotNull(symbol); diff --git a/src/main/java/net/jacobpeterson/alpaca/AlpacaConstants.java b/src/main/java/net/jacobpeterson/alpaca/AlpacaConstants.java index 96e12dd8..df37aa99 100644 --- a/src/main/java/net/jacobpeterson/alpaca/AlpacaConstants.java +++ b/src/main/java/net/jacobpeterson/alpaca/AlpacaConstants.java @@ -1,157 +1,57 @@ package net.jacobpeterson.alpaca; /** - * The Class {@link AlpacaConstants}. + * {@link AlpacaConstants} are constants use by {@link AlpacaAPI}. */ public class AlpacaConstants { - /** The Constant VERSION_1. */ public static final String VERSION_1_ENDPOINT = "v1"; - - /** The Constant VERSION_2. */ public static final String VERSION_2_ENDPOINT = "v2"; - - /** The Constant SYMBOLS_PARAMETER. */ public static final String SYMBOLS_PARAMETER = "symbols"; - - /** The Constant END_PARAMETER. */ public static final String END_PARAMETER = "end"; - - /** The Constant START_PARAMETER. */ public static final String START_PARAMETER = "start"; - - /** The constant DATE_PARAMETER. */ public static final String DATE_PARAMETER = "date"; - - /** The Constant ASSET_CLASS_PARAMETER. */ public static final String ASSET_CLASS_PARAMETER = "asset_class"; - - /** The Constant CLIENT_ORDER_ID_PARAMETER. */ public static final String CLIENT_ORDER_ID_PARAMETER = "client_order_id"; - - /** The constant ORDER_CLASS_PARAMETER. */ public static final String ORDER_CLASS_PARAMETER = "order_class"; - - /** The constant TAKE_PROFIT_PARAMETER. */ public static final String TAKE_PROFIT_PARAMETER = "take_profit"; - - /** The constant STOP_LOSS_PARAMETER. */ public static final String STOP_LOSS_PARAMETER = "stop_loss"; - - /** The Constant STOP_PRICE_PARAMETER. */ public static final String STOP_PRICE_PARAMETER = "stop_price"; - - /** The Constant TRAIL_PRICE_PARAMETER. */ public static final String TRAIL_PRICE_PARAMETER = "trail_price"; - - /** The Constant TRAIL_PERCENT_PARAMETER. */ public static final String TRAIL_PERCENT_PARAMETER = "trail_percent"; - - /** The Constant TRAIL_PARAMETER. */ public static final String TRAIL_PARAMETER = "trail"; - - /** The Constant LIMIT_PRICE_PARAMETER. */ public static final String LIMIT_PRICE_PARAMETER = "limit_price"; - - /** The Constant TIME_IN_FORCE_PARAMETER. */ public static final String TIME_IN_FORCE_PARAMETER = "time_in_force"; - - /** The constant EXTENDED_HOURS_PARAMETER. */ public static final String EXTENDED_HOURS_PARAMETER = "extended_hours"; - - /** The Constant TYPE_PARAMETER. */ public static final String TYPE_PARAMETER = "type"; - - /** The Constant SIDE_PARAMETER. */ public static final String SIDE_PARAMETER = "side"; - - /** The Constant QTY_PARAMETER. */ public static final String QTY_PARAMETER = "qty"; - - /** The Constant SYMBOL_PARAMETER. */ public static final String SYMBOL_PARAMETER = "symbol"; - - /** The Constant DIRECTION_PARAMETER. */ public static final String DIRECTION_PARAMETER = "direction"; - - /** The constant NESTED_PARAMETER. */ public static final String NESTED_PARAMETER = "nested"; - - /** The Constant UNTIL_PARAMETER. */ public static final String UNTIL_PARAMETER = "until"; - - /** The Constant AFTER_PARAMETER. */ public static final String AFTER_PARAMETER = "after"; - - /** The Constant LIMIT_PARAMETER. */ public static final String LIMIT_PARAMETER = "limit"; - - /** The Constant STATUS_PARAMETER. */ public static final String STATUS_PARAMETER = "status"; - - /** The constant PERIOD_PARAMETER. */ public static final String PERIOD_PARAMETER = "period"; - - /** The constant TIMEFRAME_PARAMETER. */ public static final String TIMEFRAME_PARAMETER = "timeframe"; - - /** The constant DATE_END_PARAMETER. */ public static final String DATE_END_PARAMETER = "date_end"; - - /** The Constant BARS_ENDPOINT. */ public static final String BARS_ENDPOINT = "bars"; - - /** The constant LAST_ENDPOINT. */ public static final String LAST_ENDPOINT = "last"; - - /** The constant LAST_QUOTE_ENDPOINT. */ public static final String LAST_QUOTE_ENDPOINT = "last_quote"; - - /** The constant STOCKS_ENDPOINT. */ public static final String STOCKS_ENDPOINT = "stocks"; - - /** The Constant CLOCK_ENDPOINT. */ public static final String CLOCK_ENDPOINT = "clock"; - - /** The Constant CALENDAR_ENDPOINT. */ public static final String CALENDAR_ENDPOINT = "calendar"; - - /** The Constant ASSETS_ENDPOINT. */ public static final String ASSETS_ENDPOINT = "assets"; - - /** The Constant POSITIONS_ENDPOINT. */ public static final String POSITIONS_ENDPOINT = "positions"; - - /** The Constant ACCOUNT_ENDPOINT. */ public static final String ACCOUNT_ENDPOINT = "account"; - - /** The Constant ACTIVITIES_ENDPOINT. */ public static final String ACTIVITIES_ENDPOINT = "activities"; - - /** The Constant CONFIGURATIONS_ENDPOINT. */ public static final String CONFIGURATIONS_ENDPOINT = "configurations"; - - /** The Constant ORDERS_BY_CLIENT_ORDER_ID_ENDPOINT. */ public static final String ORDERS_BY_CLIENT_ORDER_ID_ENDPOINT = "orders:by_client_order_id"; - - /** The constant PAGE_SIZE_PARAMETER. */ public static final String PAGE_SIZE_PARAMETER = "page_size"; - - /** The constant PAGE_TOKEN_PARAMETER. */ public static final String PAGE_TOKEN_PARAMETER = "page_token"; - - /** The Constant ORDERS_ENDPOINT. */ public static final String ORDERS_ENDPOINT = "orders"; - - /** The Constant WATCHLISTS_ENDPOINT. */ public static final String WATCHLISTS_ENDPOINT = "watchlists"; - - /** The constant PORTFOLIO_ENDPOINT. */ public static final String PORTFOLIO_ENDPOINT = "portfolio"; - - /** The constant HISTORY_ENDPOINT. */ public static final String HISTORY_ENDPOINT = "history"; - - /** The Constant UTF_ENCODING. */ - public static final String UTF_ENCODING = "UTF-8"; } diff --git a/src/main/java/net/jacobpeterson/util/concurrency/ExecutorTracer.java b/src/main/java/net/jacobpeterson/util/concurrency/ExecutorTracer.java deleted file mode 100644 index 4bc0fefb..00000000 --- a/src/main/java/net/jacobpeterson/util/concurrency/ExecutorTracer.java +++ /dev/null @@ -1,359 +0,0 @@ -package net.jacobpeterson.util.concurrency; - -import java.util.Collection; -import java.util.List; -import java.util.concurrent.AbstractExecutorService; -import java.util.concurrent.Callable; -import java.util.concurrent.ExecutionException; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Future; -import java.util.concurrent.LinkedBlockingQueue; -import java.util.concurrent.ScheduledExecutorService; -import java.util.concurrent.ScheduledFuture; -import java.util.concurrent.SynchronousQueue; -import java.util.concurrent.ThreadFactory; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.TimeoutException; - -/** - * The Class {@link ExecutorTracer}. - */ -public class ExecutorTracer { - - /** - * Instantiates a new {@link ExecutorTracer}. - */ - private ExecutorTracer() { - } - - /** - * New fixed thread pool. - * - * @param nThreads the n threads - * - * @return the executor service - */ - public static ExecutorService newFixedThreadPool(int nThreads) { - return new ThreadPoolExecutorTracer(nThreads, nThreads, 0L, TimeUnit.MILLISECONDS, - new LinkedBlockingQueue()); - } - - /** - * New fixed thread pool. - * - * @param nThreads the n threads - * @param threadFactory the thread factory - * - * @return the executor service - */ - public static ExecutorService newFixedThreadPool(int nThreads, ThreadFactory threadFactory) { - return new ThreadPoolExecutorTracer(nThreads, nThreads, 0L, TimeUnit.MILLISECONDS, - new LinkedBlockingQueue(), threadFactory); - } - - /** - * New cached thread pool. - * - * @param threadFactory the thread factory - * - * @return the executor service - */ - public static ExecutorService newCachedThreadPool(ThreadFactory threadFactory) { - return new ThreadPoolExecutorTracer(0, Integer.MAX_VALUE, 60L, TimeUnit.SECONDS, - new SynchronousQueue(), threadFactory); - } - - /** - * New cached thread pool. - * - * @return the executor service - */ - public static ExecutorService newCachedThreadPool() { - return new ThreadPoolExecutorTracer(0, Integer.MAX_VALUE, 60L, TimeUnit.SECONDS, - new SynchronousQueue()); - } - - /** - * New single thread executor. - * - * @param threadFactory the thread factory - * - * @return the executor service - */ - public static ExecutorService newSingleThreadExecutor(ThreadFactory threadFactory) { - return new FinalizableDelegatedExecutorService(new ThreadPoolExecutorTracer(1, 1, 0L, - TimeUnit.MILLISECONDS, new LinkedBlockingQueue(), threadFactory)); - } - - /** - * New single thread executor. - * - * @return the executor service - */ - public static ExecutorService newSingleThreadExecutor() { - return new FinalizableDelegatedExecutorService(new ThreadPoolExecutorTracer(1, 1, 0L, - TimeUnit.MILLISECONDS, new LinkedBlockingQueue())); - } - - /** - * New single thread scheduled executor. - * - * @return the scheduled executor service - */ - public static ScheduledExecutorService newSingleThreadScheduledExecutor() { - return new DelegatedScheduledExecutorService(new ScheduledThreadPoolExecutorTracer(1)); - } - - /** - * New scheduled thread pool. - * - * @param corePoolSize the core pool size - * - * @return the scheduled executor service - */ - public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) { - return new ScheduledThreadPoolExecutorTracer(corePoolSize); - } - - /** - * New scheduled thread pool. - * - * @param corePoolSize the core pool size - * @param threadFactory the thread factory - * - * @return the scheduled executor service - */ - public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize, - ThreadFactory threadFactory) { - return new ScheduledThreadPoolExecutorTracer(corePoolSize, threadFactory); - } - - /** - * The Class {@link DelegatedExecutorService}. - */ - public static class DelegatedExecutorService extends AbstractExecutorService { - - /** The e. */ - private final ExecutorService e; - - /** - * Instantiates a new delegated executor service. - * - * @param executor the executor - */ - DelegatedExecutorService(ExecutorService executor) { - e = executor; - } - - /* - * (non-Javadoc) - * - * @see java.util.concurrent.Executor#execute(java.lang.Runnable) - */ - public void execute(Runnable command) { - e.execute(command); - } - - /* - * (non-Javadoc) - * - * @see java.util.concurrent.ExecutorService#shutdown() - */ - public void shutdown() { - e.shutdown(); - } - - /* - * (non-Javadoc) - * - * @see java.util.concurrent.ExecutorService#shutdownNow() - */ - public List shutdownNow() { - return e.shutdownNow(); - } - - /* - * (non-Javadoc) - * - * @see java.util.concurrent.ExecutorService#isShutdown() - */ - public boolean isShutdown() { - return e.isShutdown(); - } - - /* - * (non-Javadoc) - * - * @see java.util.concurrent.ExecutorService#isTerminated() - */ - public boolean isTerminated() { - return e.isTerminated(); - } - - /* - * (non-Javadoc) - * - * @see java.util.concurrent.ExecutorService#awaitTermination(long, - * java.util.concurrent.TimeUnit) - */ - public boolean awaitTermination(long timeout, TimeUnit unit) throws InterruptedException { - return e.awaitTermination(timeout, unit); - } - - /* - * (non-Javadoc) - * - * @see java.util.concurrent.AbstractExecutorService#submit(java.lang.Runnable) - */ - public Future submit(Runnable task) { - return e.submit(task); - } - - /* - * (non-Javadoc) - * - * @see java.util.concurrent.AbstractExecutorService#submit(java.util.concurrent.Callable) - */ - public Future submit(Callable task) { - return e.submit(task); - } - - /* - * (non-Javadoc) - * - * @see java.util.concurrent.AbstractExecutorService#submit(java.lang.Runnable, - * java.lang.Object) - */ - public Future submit(Runnable task, T result) { - return e.submit(task, result); - } - - /* - * (non-Javadoc) - * - * @see java.util.concurrent.AbstractExecutorService#invokeAll(java.util.Collection) - */ - public List> invokeAll(Collection> tasks) - throws InterruptedException { - return e.invokeAll(tasks); - } - - /* - * (non-Javadoc) - * - * @see java.util.concurrent.AbstractExecutorService#invokeAll(java.util.Collection, long, - * java.util.concurrent.TimeUnit) - */ - public List> invokeAll(Collection> tasks, long timeout, - TimeUnit unit) throws InterruptedException { - return e.invokeAll(tasks, timeout, unit); - } - - /* - * (non-Javadoc) - * - * @see java.util.concurrent.AbstractExecutorService#invokeAny(java.util.Collection) - */ - public T invokeAny(Collection> tasks) - throws InterruptedException, ExecutionException { - return e.invokeAny(tasks); - } - - /* - * (non-Javadoc) - * - * @see java.util.concurrent.AbstractExecutorService#invokeAny(java.util.Collection, long, - * java.util.concurrent.TimeUnit) - */ - public T invokeAny(Collection> tasks, long timeout, TimeUnit unit) - throws InterruptedException, ExecutionException, TimeoutException { - return e.invokeAny(tasks, timeout, unit); - } - } - - /** - * The Class {@link FinalizableDelegatedExecutorService}. - */ - public static class FinalizableDelegatedExecutorService extends DelegatedExecutorService { - - /** - * Instantiates a new finalizable delegated executor service. - * - * @param executor the executor - */ - FinalizableDelegatedExecutorService(ExecutorService executor) { - super(executor); - } - - /* - * (non-Javadoc) - * - * @see java.lang.Object#finalize() - */ - protected void finalize() { - super.shutdown(); - } - } - - /** - * The Class {@link DelegatedScheduledExecutorService}. - */ - static class DelegatedScheduledExecutorService extends DelegatedExecutorService - implements ScheduledExecutorService { - - /** The e. */ - private final ScheduledExecutorService e; - - /** - * Instantiates a new delegated scheduled executor service. - * - * @param executor the executor - */ - DelegatedScheduledExecutorService(ScheduledExecutorService executor) { - super(executor); - e = executor; - } - - /* - * (non-Javadoc) - * - * @see java.util.concurrent.ScheduledExecutorService#schedule(java.lang.Runnable, long, - * java.util.concurrent.TimeUnit) - */ - public ScheduledFuture schedule(Runnable command, long delay, TimeUnit unit) { - return e.schedule(command, delay, unit); - } - - /* - * (non-Javadoc) - * - * @see java.util.concurrent.ScheduledExecutorService#schedule(java.util.concurrent.Callable, - * long, java.util.concurrent.TimeUnit) - */ - public ScheduledFuture schedule(Callable callable, long delay, TimeUnit unit) { - return e.schedule(callable, delay, unit); - } - - /* - * (non-Javadoc) - * - * @see java.util.concurrent.ScheduledExecutorService#scheduleAtFixedRate(java.lang.Runnable, - * long, long, java.util.concurrent.TimeUnit) - */ - public ScheduledFuture scheduleAtFixedRate(Runnable command, long initialDelay, long period, - TimeUnit unit) { - return e.scheduleAtFixedRate(command, initialDelay, period, unit); - } - - /* - * (non-Javadoc) - * - * @see java.util.concurrent.ScheduledExecutorService#scheduleWithFixedDelay(java.lang.Runnable, - * long, long, java.util.concurrent.TimeUnit) - */ - public ScheduledFuture scheduleWithFixedDelay(Runnable command, long initialDelay, - long delay, TimeUnit unit) { - return e.scheduleWithFixedDelay(command, initialDelay, delay, unit); - } - } -} diff --git a/src/main/java/net/jacobpeterson/util/concurrency/ScheduledThreadPoolExecutorTracer.java b/src/main/java/net/jacobpeterson/util/concurrency/ScheduledThreadPoolExecutorTracer.java deleted file mode 100644 index 9f3d6d99..00000000 --- a/src/main/java/net/jacobpeterson/util/concurrency/ScheduledThreadPoolExecutorTracer.java +++ /dev/null @@ -1,60 +0,0 @@ -package net.jacobpeterson.util.concurrency; - -import java.util.concurrent.CancellationException; -import java.util.concurrent.ExecutionException; -import java.util.concurrent.Future; -import java.util.concurrent.ScheduledThreadPoolExecutor; -import java.util.concurrent.ThreadFactory; - -/** - * The Class {@link ScheduledThreadPoolExecutorTracer}. - */ -class ScheduledThreadPoolExecutorTracer extends ScheduledThreadPoolExecutor { - - /** - * Instantiates a new {@link ScheduledThreadPoolExecutorTracer}. - * - * @param corePoolSize the core pool size - * @param threadFactory the thread factory - */ - public ScheduledThreadPoolExecutorTracer(int corePoolSize, ThreadFactory threadFactory) { - super(corePoolSize, threadFactory); - } - - /** - * Instantiates a new {@link ScheduledThreadPoolExecutorTracer}. - * - * @param corePoolSize the core pool size - */ - public ScheduledThreadPoolExecutorTracer(int corePoolSize) { - super(corePoolSize); - } - - /* - * (non-Javadoc) - * - * @see java.util.concurrent.ThreadPoolExecutor#afterExecute(java.lang.Runnable, - * java.lang.Throwable) - */ - @Override - protected void afterExecute(final Runnable r, Throwable t) { - super.afterExecute(r, t); - if (t == null && r instanceof Future) { - try { - final Future future = (Future) r; - if (future.isDone() && !future.isCancelled()) { - future.get(); - } - } catch (final CancellationException ce) { - t = ce; - } catch (final ExecutionException ee) { - t = ee.getCause(); - } catch (final InterruptedException ie) { - Thread.currentThread().interrupt(); - } - } - if (t != null) { - t.printStackTrace(); - } - } -} diff --git a/src/main/java/net/jacobpeterson/util/concurrency/ThreadPoolExecutorTracer.java b/src/main/java/net/jacobpeterson/util/concurrency/ThreadPoolExecutorTracer.java deleted file mode 100644 index 57f8e633..00000000 --- a/src/main/java/net/jacobpeterson/util/concurrency/ThreadPoolExecutorTracer.java +++ /dev/null @@ -1,69 +0,0 @@ -package net.jacobpeterson.util.concurrency; - -import java.util.concurrent.BlockingQueue; -import java.util.concurrent.CancellationException; -import java.util.concurrent.ExecutionException; -import java.util.concurrent.Future; -import java.util.concurrent.ThreadFactory; -import java.util.concurrent.ThreadPoolExecutor; -import java.util.concurrent.TimeUnit; - -/** - * The Class {@link ThreadPoolExecutorTracer}. - */ -public class ThreadPoolExecutorTracer extends ThreadPoolExecutor { - - /** - * Instantiates a new {@link ThreadPoolExecutorTracer}. - * - * @param corePoolSize the core pool size - * @param maximumPoolSize the maximum pool size - * @param keepAliveTime the keep alive time - * @param unit the unit - * @param workQueue the work queue - */ - public ThreadPoolExecutorTracer(int corePoolSize, int maximumPoolSize, long keepAliveTime, - TimeUnit unit, BlockingQueue workQueue) { - super(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue); - } - - /** - * Instantiates a new {@link ThreadPoolExecutorTracer}. - * - * @param corePoolSize the core pool size - * @param maximumPoolSize the maximum pool size - * @param keepAliveTime the keep alive time - * @param unit the unit - * @param workQueue the work queue - * @param threadFactory the thread factory - */ - public ThreadPoolExecutorTracer(int corePoolSize, int maximumPoolSize, long keepAliveTime, - TimeUnit unit, BlockingQueue workQueue, ThreadFactory threadFactory) { - super(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue, threadFactory); - } - - /* - * (non-Javadoc) - * - * @see java.util.concurrent.ThreadPoolExecutor#afterExecute(java.lang.Runnable, - * java.lang.Throwable) - */ - protected void afterExecute(Runnable r, Throwable t) { - super.afterExecute(r, t); - if (t == null && r instanceof Future) { - try { - Future future = (Future) r; - if (future.isDone()) { future.get(); } - } catch (CancellationException ce) { - t = ce; - } catch (ExecutionException ee) { - t = ee.getCause(); - } catch (InterruptedException ie) { - Thread.currentThread().interrupt(); - } - } - if (t != null && !(t instanceof CancellationException)) { - t.printStackTrace(); - } - } -} From 296e6a0e7858e9b1b89e25640ab4a1440966e171 Mon Sep 17 00:00:00 2001 From: Jacob Peterson Date: Wed, 6 Jan 2021 19:21:11 -0700 Subject: [PATCH 073/271] Refactor and clear up Javadocs for alpaca package --- .../AbstractWebsocketClientEndpoint.java | 45 ++- .../websocket/client/WebsocketClient.java | 42 +- .../net/jacobpeterson/alpaca/AlpacaAPI.java | 375 +++++++++--------- .../jacobpeterson/alpaca/AlpacaConstants.java | 111 +++--- .../alpaca/enums/AccountStatus.java | 4 +- .../alpaca/enums/ActivityType.java | 4 +- .../alpaca/enums/AssetStatus.java | 4 +- .../alpaca/enums/BarsTimeFrame.java | 4 +- .../jacobpeterson/alpaca/enums/Direction.java | 2 +- .../alpaca/enums/OrderClass.java | 4 +- .../jacobpeterson/alpaca/enums/OrderSide.java | 4 +- .../alpaca/enums/OrderStatus.java | 4 +- .../alpaca/enums/OrderTimeInForce.java | 4 +- .../jacobpeterson/alpaca/enums/OrderType.java | 4 +- .../alpaca/enums/PortfolioPeriodUnit.java | 6 +- .../alpaca/enums/PortfolioTimeFrame.java | 4 +- .../alpaca/properties/AlpacaProperties.java | 21 +- .../alpaca/rest/AlpacaRequest.java | 9 +- .../alpaca/rest/AlpacaRequestBuilder.java | 4 +- .../exception/AlpacaAPIRequestException.java | 5 +- .../broker/client/AlpacaWebsocketClient.java | 63 ++- .../client/AlpacaWebsocketClientEndpoint.java | 7 +- .../broker/listener/AlpacaStreamListener.java | 5 +- .../listener/AlpacaStreamListenerAdapter.java | 12 +- .../message/AlpacaStreamMessageType.java | 18 +- .../client/MarketDataWebsocketClient.java | 83 ++-- .../MarketDataWebsocketClientEndpoint.java | 7 +- .../listener/MarketDataStreamListener.java | 5 +- .../MarketDataStreamListenerAdapter.java | 16 +- .../message/MarketDataStreamMessageType.java | 20 +- .../polygon/properties/PolygonProperties.java | 19 +- .../polygon/rest/PolygonRequest.java | 2 +- .../polygon/rest/PolygonRequestBuilder.java | 2 +- .../exception/PolygonAPIRequestException.java | 5 +- 34 files changed, 442 insertions(+), 482 deletions(-) diff --git a/src/main/java/net/jacobpeterson/abstracts/websocket/client/AbstractWebsocketClientEndpoint.java b/src/main/java/net/jacobpeterson/abstracts/websocket/client/AbstractWebsocketClientEndpoint.java index edc39ac2..e754aac3 100644 --- a/src/main/java/net/jacobpeterson/abstracts/websocket/client/AbstractWebsocketClientEndpoint.java +++ b/src/main/java/net/jacobpeterson/abstracts/websocket/client/AbstractWebsocketClientEndpoint.java @@ -15,7 +15,7 @@ import java.util.concurrent.Executors; /** - * {@link AbstractWebsocketClientEndpoint} is used for handling the Websockets. + * {@link AbstractWebsocketClientEndpoint} is used for handling a Websocket directly. *
* NOTES: You MUST annotate a subclass with @see and the appropriate websocket sub-protocols because Websocket * annotations don't work with inheritance. The subclass must also contain separate methods with the following @@ -54,10 +54,10 @@ public AbstractWebsocketClientEndpoint(WebsocketClient websocketClient, URI endp } /** - * Connect. + * Connects this Websocket. * - * @throws DeploymentException the deployment exception - * @throws IOException Signals that an I/O exception has occurred. + * @throws DeploymentException throw for {@link DeploymentException}s + * @throws IOException throw for {@link IOException}s */ public void connect() throws DeploymentException, IOException { executorService = Executors.newSingleThreadExecutor(runnable -> new Thread(runnable, messageThreadName)); @@ -69,9 +69,9 @@ public void connect() throws DeploymentException, IOException { } /** - * Disconnect. + * Disconnects this Websocket. * - * @throws IOException the io exception + * @throws IOException throw for {@link IOException}s */ public void disconnect() throws Exception { automaticallyReconnect = false; @@ -91,9 +91,9 @@ public void disconnect() throws Exception { } /** - * On open. + * Handles the Websocket open. * - * @param userSession the user session + * @param userSession the user {@link Session} */ protected void onOpen(Session userSession) { this.userSession = userSession; @@ -104,10 +104,10 @@ protected void onOpen(Session userSession) { } /** - * On close. + * Handles the Websocket close. * - * @param userSession the user session - * @param reason the reason + * @param userSession the user {@link Session} + * @param reason the {@link CloseReason} */ protected void onClose(Session userSession, CloseReason reason) { LOGGER.debug("onClose {}", userSession); @@ -130,7 +130,7 @@ protected void onClose(Session userSession, CloseReason reason) { } /** - * On message. + * Called when the Websocket message is received. * * @param message the message */ @@ -139,16 +139,16 @@ protected void onMessage(String message) { } /** - * On error. + * Handles the Websocket error. * - * @param throwable the throwable + * @param throwable the {@link Throwable} */ protected void onError(Throwable throwable) { LOGGER.error("Websocket Error!", throwable); } /** - * Send a message. + * Sends a message through the Websocket. * * @param message the message */ @@ -158,27 +158,30 @@ public void sendMessage(String message) { } /** - * Gets user session. + * Gets user {@link Session}. * - * @return the user session + * @return the user {@link Session} */ public Session getUserSession() { return userSession; } /** - * Does automatically reconnect boolean. + * Returns true if the Websocket is automatically reconnected except when {@link CloseReason} is {@link + * CloseReason.CloseCodes#NORMAL_CLOSURE}. * - * @return the boolean + * @return true if the Websocket is automatically reconnected except when {@link CloseReason} is {@link + * CloseReason.CloseCodes#NORMAL_CLOSURE}. */ public boolean doesAutomaticallyReconnect() { return automaticallyReconnect; } /** - * Sets automatically reconnect. + * Sets the Websocket to automatically reconnected except when {@link CloseReason} is {@link + * CloseReason.CloseCodes#NORMAL_CLOSURE}. * - * @param automaticallyReconnect the automatically reconnect + * @param automaticallyReconnect true to automatically reconnect */ public void setAutomaticallyReconnect(boolean automaticallyReconnect) { this.automaticallyReconnect = automaticallyReconnect; diff --git a/src/main/java/net/jacobpeterson/abstracts/websocket/client/WebsocketClient.java b/src/main/java/net/jacobpeterson/abstracts/websocket/client/WebsocketClient.java index aae14f1f..b42267dc 100644 --- a/src/main/java/net/jacobpeterson/abstracts/websocket/client/WebsocketClient.java +++ b/src/main/java/net/jacobpeterson/abstracts/websocket/client/WebsocketClient.java @@ -6,78 +6,78 @@ import net.jacobpeterson.abstracts.websocket.message.StreamMessageType; /** - * The type {@link WebsocketClient}. + * {@link WebsocketClient} represents a client for a Websocket. */ public interface WebsocketClient { /** - * Adds a listener. Note that this method is blocking. + * Adds a {@link StreamListener}. Note that this method is blocking. * - * @param streamListener the stream listener + * @param streamListener the {@link StreamListener} * - * @throws WebsocketException the WebsocketException + * @throws WebsocketException thrown for {@link WebsocketException}s */ void addListener(StreamListener streamListener) throws WebsocketException; /** - * Remove listener. Note that this method is blocking. + * Removes a {@link StreamListener}. Note that this method is blocking. * - * @param streamListener the stream listener + * @param streamListener the {@link StreamListener} * - * @throws WebsocketException the WebsocketException + * @throws WebsocketException thrown for {@link WebsocketException}s */ void removeListener(StreamListener streamListener) throws WebsocketException; /** - * Connects. Note that this method is blocking. + * Connects the Websocket. Note that this method is blocking. * - * @throws Exception the Exception + * @throws Exception thrown for {@link Exception}s */ void connect() throws Exception; /** - * Disconnects. Note that this method is blocking. + * Disconnects the Websocket. Note that this method is blocking. * - * @throws Exception the Exception + * @throws Exception thrown for {@link Exception}s */ void disconnect() throws Exception; /** - * Send authentication message. Note that this method is blocking. + * Sends the authentication message. Note that this method is blocking. */ void sendAuthenticationMessage(); /** - * Handle resubscribing. Note that this method is blocking. + * Handles resubscribing. Note that this method is blocking. */ void handleResubscribing(); /** - * Handle websocket message. + * Handles a Websocket message. * * @param message the message */ void handleWebsocketMessage(String message); /** - * Send stream message to listeners. + * Sends a {@link StreamMessage} to {@link StreamListener}s. * - * @param streamMessageType the stream message type - * @param streamMessage the stream message + * @param streamMessageType the {@link StreamMessageType} + * @param streamMessage the {@link StreamMessage} */ void sendStreamMessageToListeners(StreamMessageType streamMessageType, StreamMessage streamMessage); /** - * Returns true if the websocket is connected. + * Returns true if the Websocket is connected. * - * @return the boolean + * @return true if the Websocket is connected */ boolean isConnected(); /** - * Returns true if the websocket is authenticated. + * Returns true if the Websocket is authenticated. * - * @return the boolean + * @return true if the Websocket is authenticated */ boolean isAuthenticated(); } diff --git a/src/main/java/net/jacobpeterson/alpaca/AlpacaAPI.java b/src/main/java/net/jacobpeterson/alpaca/AlpacaAPI.java index 04952db3..c380e4c6 100644 --- a/src/main/java/net/jacobpeterson/alpaca/AlpacaAPI.java +++ b/src/main/java/net/jacobpeterson/alpaca/AlpacaAPI.java @@ -8,6 +8,8 @@ import com.google.gson.reflect.TypeToken; import com.mashape.unirest.http.HttpResponse; import net.jacobpeterson.abstracts.websocket.exception.WebsocketException; +import net.jacobpeterson.alpaca.AlpacaConstants.Endpoints; +import net.jacobpeterson.alpaca.AlpacaConstants.Parameters; import net.jacobpeterson.alpaca.enums.ActivityType; import net.jacobpeterson.alpaca.enums.AssetStatus; import net.jacobpeterson.alpaca.enums.BarsTimeFrame; @@ -62,36 +64,28 @@ import java.util.stream.Collectors; /** - * The {@link AlpacaAPI} class. This class contains methods to interface with the Alpaca API. + * {@link AlpacaAPI} contains several methods to interface with Alpaca. You will generally only need one instance of it + * in your application. Note that many methods allow null to be passed in as a parameter if it is + * optional. *
- * Note that most of these methods are blocking methods and this class in NOT thread-safe. + * Note that most of these methods are blocking methods and this class in not thread-safe. + * + * @see Alpaca API documentation */ public class AlpacaAPI { - /** The logger. */ private static final Logger LOGGER = LoggerFactory.getLogger(AlpacaAPI.class); - /** The key ID. */ private final String keyID; - - /** The base API url. */ private final String baseAPIURL; - - /** The alpaca request. */ private final AlpacaRequest alpacaRequest; - - /** The base data url. */ private final String baseDataUrl; - - /** The alpaca websocket client. */ private final AlpacaWebsocketClient alpacaWebSocketClient; - - /** The market data websocket client. */ private final MarketDataWebsocketClient marketDataWebSocketClient; /** - * Instantiates a new {@link AlpacaAPI} using properties specified in alpaca.properties file (or their associated - * defaults) + * Instantiates a new {@link AlpacaAPI} using properties specified in alpaca.properties file (or their + * associated defaults) */ public AlpacaAPI() { this(AlpacaProperties.KEY_ID_VALUE, AlpacaProperties.SECRET_VALUE, null, AlpacaProperties.BASE_API_URL_VALUE, @@ -101,7 +95,7 @@ public AlpacaAPI() { } /** - * Instantiates a new {@link AlpacaAPI} using the specified keyID and secret. + * Instantiates a new {@link AlpacaAPI}. * * @param keyID the key ID * @param secret the secret @@ -111,18 +105,18 @@ public AlpacaAPI(String keyID, String secret) { } /** - * Instantiates a new {@link AlpacaAPI} using the specified keyID, secret, and baseAPIURL. + * Instantiates a new {@link AlpacaAPI}. * * @param keyID the key ID * @param secret the secret - * @param baseAPIURL the api account url + * @param baseAPIURL the API URL */ public AlpacaAPI(String keyID, String secret, String baseAPIURL) { this(keyID, secret, null, baseAPIURL, AlpacaProperties.BASE_DATA_URL_VALUE); } /** - * Instantiates a new {@link AlpacaAPI} using the specified OAuth token. + * Instantiates a new {@link AlpacaAPI}. * * @param oAuthToken the OAuth token */ @@ -131,8 +125,7 @@ public AlpacaAPI(String oAuthToken) { } /** - * Instantiates a new {@link AlpacaAPI} using the specified AlpacaConstants.VERSION_2_ENDPOINT, keyId, secret, OAuth - * token (if there is one), baseAPIURL, and baseDataURL. + * Instantiates a new {@link AlpacaAPI}. * * @param keyID the key ID * @param secret the secret @@ -165,12 +158,12 @@ public AlpacaAPI(String keyID, String secret, String oAuthToken, String baseAPIU * * @return the account * - * @throws AlpacaAPIRequestException the alpaca API exception + * @throws AlpacaAPIRequestException thrown for {@link AlpacaAPIRequestException}s * @see Get the Account */ public Account getAccount() throws AlpacaAPIRequestException { - AlpacaRequestBuilder urlBuilder = new AlpacaRequestBuilder(baseAPIURL, AlpacaConstants.VERSION_2_ENDPOINT, - AlpacaConstants.ACCOUNT_ENDPOINT); + AlpacaRequestBuilder urlBuilder = new AlpacaRequestBuilder(baseAPIURL, Endpoints.VERSION_2, + Endpoints.ACCOUNT); HttpResponse response = alpacaRequest.invokeGet(urlBuilder); @@ -203,9 +196,9 @@ public Account getAccount() throws AlpacaAPIRequestException { public ArrayList getAccountActivities(ZonedDateTime date, ZonedDateTime until, ZonedDateTime after, Direction direction, Integer pageSize, String pageToken, ActivityType... activityTypes) throws AlpacaAPIRequestException { - AlpacaRequestBuilder urlBuilder = new AlpacaRequestBuilder(baseAPIURL, AlpacaConstants.VERSION_2_ENDPOINT, - AlpacaConstants.ACCOUNT_ENDPOINT, - AlpacaConstants.ACTIVITIES_ENDPOINT); + AlpacaRequestBuilder urlBuilder = new AlpacaRequestBuilder(baseAPIURL, Endpoints.VERSION_2, + Endpoints.ACCOUNT, + Endpoints.ACTIVITIES); if (activityTypes != null) { // Check if we don't want all activity types if (activityTypes.length == 1) { // Get one activity @@ -217,30 +210,30 @@ public ArrayList getAccountActivities(ZonedDateTime date, Zoned } if (date != null) { - urlBuilder.appendURLParameter(AlpacaConstants.DATE_PARAMETER, + urlBuilder.appendURLParameter(Parameters.DATE, date.format(DateTimeFormatter.ISO_OFFSET_DATE_TIME)); } if (until != null) { - urlBuilder.appendURLParameter(AlpacaConstants.UNTIL_PARAMETER, + urlBuilder.appendURLParameter(Parameters.UNTIL, until.format(DateTimeFormatter.ISO_OFFSET_DATE_TIME)); } if (after != null) { - urlBuilder.appendURLParameter(AlpacaConstants.AFTER_PARAMETER, + urlBuilder.appendURLParameter(Parameters.AFTER, after.format(DateTimeFormatter.ISO_OFFSET_DATE_TIME)); } if (direction != null) { - urlBuilder.appendURLParameter(AlpacaConstants.DIRECTION_PARAMETER, direction.getAPIName()); + urlBuilder.appendURLParameter(Parameters.DIRECTION, direction.getAPIName()); } if (pageSize != null) { - urlBuilder.appendURLParameter(AlpacaConstants.PAGE_SIZE_PARAMETER, pageSize.toString()); + urlBuilder.appendURLParameter(Parameters.PAGE_SIZE, pageSize.toString()); } if (pageToken != null) { - urlBuilder.appendURLParameter(AlpacaConstants.PAGE_TOKEN_PARAMETER, pageToken); + urlBuilder.appendURLParameter(Parameters.PAGE_TOKEN, pageToken); } HttpResponse response = alpacaRequest.invokeGet(urlBuilder); @@ -286,9 +279,9 @@ public ArrayList getAccountActivities(ZonedDateTime date, Zoned * Configuration */ public AccountConfiguration getAccountConfiguration() throws AlpacaAPIRequestException { - AlpacaRequestBuilder urlBuilder = new AlpacaRequestBuilder(baseAPIURL, AlpacaConstants.VERSION_2_ENDPOINT, - AlpacaConstants.ACCOUNT_ENDPOINT, - AlpacaConstants.CONFIGURATIONS_ENDPOINT); + AlpacaRequestBuilder urlBuilder = new AlpacaRequestBuilder(baseAPIURL, Endpoints.VERSION_2, + Endpoints.ACCOUNT, + Endpoints.CONFIGURATIONS); HttpResponse response = alpacaRequest.invokeGet(urlBuilder); @@ -314,9 +307,9 @@ public AccountConfiguration setAccountConfiguration(AccountConfiguration account throws AlpacaAPIRequestException { Preconditions.checkNotNull(accountConfiguration); - AlpacaRequestBuilder urlBuilder = new AlpacaRequestBuilder(baseAPIURL, AlpacaConstants.VERSION_2_ENDPOINT, - AlpacaConstants.ACCOUNT_ENDPOINT, - AlpacaConstants.CONFIGURATIONS_ENDPOINT); + AlpacaRequestBuilder urlBuilder = new AlpacaRequestBuilder(baseAPIURL, Endpoints.VERSION_2, + Endpoints.ACCOUNT, + Endpoints.CONFIGURATIONS); urlBuilder.setCustomBody(GsonUtil.GSON.toJson(accountConfiguration)); HttpResponse response = alpacaRequest.invokePatch(urlBuilder); @@ -342,42 +335,42 @@ public AccountConfiguration setAccountConfiguration(AccountConfiguration account * * @return the orders * - * @throws AlpacaAPIRequestException the alpaca API exception + * @throws AlpacaAPIRequestException thrown for {@link AlpacaAPIRequestException}s * @see Orders */ public ArrayList getOrders(OrderStatus status, Integer limit, ZonedDateTime after, ZonedDateTime until, Direction direction, Boolean nested, List symbols) throws AlpacaAPIRequestException { - AlpacaRequestBuilder urlBuilder = new AlpacaRequestBuilder(baseAPIURL, AlpacaConstants.VERSION_2_ENDPOINT, - AlpacaConstants.ORDERS_ENDPOINT); + AlpacaRequestBuilder urlBuilder = new AlpacaRequestBuilder(baseAPIURL, Endpoints.VERSION_2, + Endpoints.ORDERS); if (status != null) { - urlBuilder.appendURLParameter(AlpacaConstants.STATUS_PARAMETER, status.getAPIName()); + urlBuilder.appendURLParameter(Parameters.STATUS, status.getAPIName()); } if (limit != null) { - urlBuilder.appendURLParameter(AlpacaConstants.LIMIT_PARAMETER, limit.toString()); + urlBuilder.appendURLParameter(Parameters.LIMIT, limit.toString()); } if (after != null) { - urlBuilder.appendURLParameter(AlpacaConstants.AFTER_PARAMETER, + urlBuilder.appendURLParameter(Parameters.AFTER, after.format(DateTimeFormatter.ISO_OFFSET_DATE_TIME)); } if (until != null) { - urlBuilder.appendURLParameter(AlpacaConstants.UNTIL_PARAMETER, + urlBuilder.appendURLParameter(Parameters.UNTIL, until.format(DateTimeFormatter.ISO_OFFSET_DATE_TIME)); } if (direction != null) { - urlBuilder.appendURLParameter(AlpacaConstants.DIRECTION_PARAMETER, direction.getAPIName()); + urlBuilder.appendURLParameter(Parameters.DIRECTION, direction.getAPIName()); } if (nested != null) { - urlBuilder.appendURLParameter(AlpacaConstants.NESTED_PARAMETER, nested.toString()); + urlBuilder.appendURLParameter(Parameters.NESTED, nested.toString()); } if (symbols != null && !symbols.isEmpty()) { - urlBuilder.appendURLParameter(AlpacaConstants.SYMBOLS_PARAMETER, String.join(",", symbols)); + urlBuilder.appendURLParameter(Parameters.SYMBOLS, String.join(",", symbols)); } HttpResponse response = alpacaRequest.invokeGet(urlBuilder); @@ -419,7 +412,7 @@ public ArrayList getOrders(OrderStatus status, Integer limit, ZonedDateTi * * @return the order * - * @throws AlpacaAPIRequestException the alpaca API exception + * @throws AlpacaAPIRequestException thrown for {@link AlpacaAPIRequestException}s * @see Orders */ public Order requestNewOrder(String symbol, Integer quantity, OrderSide side, OrderType type, @@ -433,66 +426,66 @@ public Order requestNewOrder(String symbol, Integer quantity, OrderSide side, Or Preconditions.checkNotNull(type); Preconditions.checkNotNull(timeInForce); - AlpacaRequestBuilder urlBuilder = new AlpacaRequestBuilder(baseAPIURL, AlpacaConstants.VERSION_2_ENDPOINT, - AlpacaConstants.ORDERS_ENDPOINT); + AlpacaRequestBuilder urlBuilder = new AlpacaRequestBuilder(baseAPIURL, Endpoints.VERSION_2, + Endpoints.ORDERS); - urlBuilder.appendJSONBodyProperty(AlpacaConstants.SYMBOL_PARAMETER, symbol); - urlBuilder.appendJSONBodyProperty(AlpacaConstants.QTY_PARAMETER, quantity.toString()); - urlBuilder.appendJSONBodyProperty(AlpacaConstants.SIDE_PARAMETER, side.getAPIName()); - urlBuilder.appendJSONBodyProperty(AlpacaConstants.TYPE_PARAMETER, type.getAPIName()); - urlBuilder.appendJSONBodyProperty(AlpacaConstants.TIME_IN_FORCE_PARAMETER, timeInForce.getAPIName()); + urlBuilder.appendJSONBodyProperty(Parameters.SYMBOL, symbol); + urlBuilder.appendJSONBodyProperty(Parameters.QTY, quantity.toString()); + urlBuilder.appendJSONBodyProperty(Parameters.SIDE, side.getAPIName()); + urlBuilder.appendJSONBodyProperty(Parameters.TYPE, type.getAPIName()); + urlBuilder.appendJSONBodyProperty(Parameters.TIME_IN_FORCE, timeInForce.getAPIName()); if (limitPrice != null) { - urlBuilder.appendJSONBodyProperty(AlpacaConstants.LIMIT_PRICE_PARAMETER, + urlBuilder.appendJSONBodyProperty(Parameters.LIMIT_PRICE, FormatUtil.toDecimalFormat(limitPrice)); } if (stopPrice != null) { - urlBuilder.appendJSONBodyProperty(AlpacaConstants.STOP_PRICE_PARAMETER, + urlBuilder.appendJSONBodyProperty(Parameters.STOP_PRICE, FormatUtil.toDecimalFormat(stopPrice)); } if (trailPrice != null) { - urlBuilder.appendJSONBodyProperty(AlpacaConstants.TRAIL_PRICE_PARAMETER, + urlBuilder.appendJSONBodyProperty(Parameters.TRAIL_PRICE, FormatUtil.toDecimalFormat(trailPrice)); } if (trailPercent != null) { - urlBuilder.appendJSONBodyProperty(AlpacaConstants.TRAIL_PERCENT_PARAMETER, + urlBuilder.appendJSONBodyProperty(Parameters.TRAIL_PERCENT, FormatUtil.toDecimalFormat(trailPercent)); } if (extendedHours != null) { - urlBuilder.appendJSONBodyJSONProperty(AlpacaConstants.EXTENDED_HOURS_PARAMETER, + urlBuilder.appendJSONBodyJSONProperty(Parameters.EXTENDED_HOURS, new JsonPrimitive(extendedHours)); } if (clientOrderId != null) { - urlBuilder.appendJSONBodyProperty(AlpacaConstants.CLIENT_ORDER_ID_PARAMETER, clientOrderId); + urlBuilder.appendJSONBodyProperty(Parameters.CLIENT_ORDER_ID, clientOrderId); } if (orderClass != null) { - urlBuilder.appendJSONBodyProperty(AlpacaConstants.ORDER_CLASS_PARAMETER, orderClass.getAPIName()); + urlBuilder.appendJSONBodyProperty(Parameters.ORDER_CLASS, orderClass.getAPIName()); } if (takeProfitLimitPrice != null) { JsonObject takeProfit = new JsonObject(); - takeProfit.addProperty(AlpacaConstants.LIMIT_PRICE_PARAMETER, takeProfitLimitPrice); + takeProfit.addProperty(Parameters.LIMIT_PRICE, takeProfitLimitPrice); - urlBuilder.appendJSONBodyJSONProperty(AlpacaConstants.TAKE_PROFIT_PARAMETER, takeProfit); + urlBuilder.appendJSONBodyJSONProperty(Parameters.TAKE_PROFIT, takeProfit); } if (stopLossStopPrice != null || stopLossLimitPrice != null) { JsonObject stopLoss = new JsonObject(); if (stopLossStopPrice != null) { - stopLoss.addProperty(AlpacaConstants.STOP_PRICE_PARAMETER, stopLossStopPrice); + stopLoss.addProperty(Parameters.STOP_PRICE, stopLossStopPrice); } if (stopLossLimitPrice != null) { - stopLoss.addProperty(AlpacaConstants.LIMIT_PRICE_PARAMETER, stopLossLimitPrice); + stopLoss.addProperty(Parameters.LIMIT_PRICE, stopLossLimitPrice); } - urlBuilder.appendJSONBodyJSONProperty(AlpacaConstants.STOP_LOSS_PARAMETER, stopLoss); + urlBuilder.appendJSONBodyJSONProperty(Parameters.STOP_LOSS, stopLoss); } HttpResponse response = alpacaRequest.invokePost(urlBuilder); @@ -517,7 +510,7 @@ public Order requestNewOrder(String symbol, Integer quantity, OrderSide side, Or * * @return the order * - * @throws AlpacaAPIRequestException the alpaca api request exception + * @throws AlpacaAPIRequestException thrown for {@link AlpacaAPIRequestException}s * @see Order Types */ public Order requestNewMarketOrder(String symbol, Integer quantity, OrderSide side, OrderTimeInForce timeInForce) @@ -544,7 +537,7 @@ public Order requestNewMarketOrder(String symbol, Integer quantity, OrderSide si * * @return the order * - * @throws AlpacaAPIRequestException the alpaca api request exception + * @throws AlpacaAPIRequestException thrown for {@link AlpacaAPIRequestException}s * @see Order Types */ public Order requestNewLimitOrder(String symbol, Integer quantity, OrderSide side, OrderTimeInForce timeInForce, @@ -572,7 +565,7 @@ public Order requestNewLimitOrder(String symbol, Integer quantity, OrderSide sid * * @return the order * - * @throws AlpacaAPIRequestException the alpaca api request exception + * @throws AlpacaAPIRequestException thrown for {@link AlpacaAPIRequestException}s * @see Order Types */ public Order requestNewStopOrder(String symbol, Integer quantity, OrderSide side, OrderTimeInForce timeInForce, @@ -600,7 +593,7 @@ public Order requestNewStopOrder(String symbol, Integer quantity, OrderSide side * * @return the order * - * @throws AlpacaAPIRequestException the alpaca api request exception + * @throws AlpacaAPIRequestException thrown for {@link AlpacaAPIRequestException}s * @see Order Types */ public Order requestNewStopLimitOrder(String symbol, Integer quantity, OrderSide side, OrderTimeInForce timeInForce, @@ -629,7 +622,7 @@ public Order requestNewStopLimitOrder(String symbol, Integer quantity, OrderSide * * @return the order * - * @throws AlpacaAPIRequestException the alpaca api request exception + * @throws AlpacaAPIRequestException thrown for {@link AlpacaAPIRequestException}s * @see Order Types */ public Order requestNewMarketBracketOrder(String symbol, Integer quantity, OrderSide side, @@ -662,7 +655,7 @@ public Order requestNewMarketBracketOrder(String symbol, Integer quantity, Order * * @return the order * - * @throws AlpacaAPIRequestException the alpaca api request exception + * @throws AlpacaAPIRequestException thrown for {@link AlpacaAPIRequestException}s * @see Order Types */ public Order requestNewLimitBracketOrder(String symbol, Integer quantity, OrderSide side, @@ -693,7 +686,7 @@ public Order requestNewLimitBracketOrder(String symbol, Integer quantity, OrderS * * @return the order * - * @throws AlpacaAPIRequestException the alpaca api request exception + * @throws AlpacaAPIRequestException thrown for {@link AlpacaAPIRequestException}s * @see Order Types */ public Order requestNewOCOOrder(String symbol, Integer quantity, OrderSide side, @@ -720,7 +713,7 @@ public Order requestNewOCOOrder(String symbol, Integer quantity, OrderSide side, * * @return the order * - * @throws AlpacaAPIRequestException the alpaca api request exception + * @throws AlpacaAPIRequestException thrown for {@link AlpacaAPIRequestException}s * @see Order Types */ public Order requestNewOTOMarketOrder(String symbol, Integer quantity, OrderSide side, @@ -750,7 +743,7 @@ public Order requestNewOTOMarketOrder(String symbol, Integer quantity, OrderSide * * @return the order * - * @throws AlpacaAPIRequestException the alpaca api request exception + * @throws AlpacaAPIRequestException thrown for {@link AlpacaAPIRequestException}s * @see Order Types */ public Order requestNewOTOLimitOrder(String symbol, Integer quantity, OrderSide side, @@ -780,7 +773,7 @@ public Order requestNewOTOLimitOrder(String symbol, Integer quantity, OrderSide * * @return the order * - * @throws AlpacaAPIRequestException the alpaca api request exception + * @throws AlpacaAPIRequestException thrown for {@link AlpacaAPIRequestException}s * @see Order Types */ public Order requestNewOTOStopOrder(String symbol, Integer quantity, OrderSide side, @@ -811,7 +804,7 @@ public Order requestNewOTOStopOrder(String symbol, Integer quantity, OrderSide s * * @return the order * - * @throws AlpacaAPIRequestException the alpaca api request exception + * @throws AlpacaAPIRequestException thrown for {@link AlpacaAPIRequestException}s * @see Order Types */ public Order requestNewOTOStopLimitOrder(String symbol, Integer quantity, OrderSide side, @@ -839,7 +832,7 @@ public Order requestNewOTOStopLimitOrder(String symbol, Integer quantity, OrderS * * @return the order * - * @throws AlpacaAPIRequestException the alpaca api request exception + * @throws AlpacaAPIRequestException thrown for {@link AlpacaAPIRequestException}s * @see Order Types */ public Order requestNewTrailingStopPriceOrder(String symbol, Integer quantity, OrderSide side, @@ -864,7 +857,7 @@ public Order requestNewTrailingStopPriceOrder(String symbol, Integer quantity, O * * @return the order * - * @throws AlpacaAPIRequestException the alpaca api request exception + * @throws AlpacaAPIRequestException thrown for {@link AlpacaAPIRequestException}s * @see Order Types */ public Order requestNewTrailingStopPercentOrder(String symbol, Integer quantity, OrderSide side, @@ -881,18 +874,18 @@ public Order requestNewTrailingStopPercentOrder(String symbol, Integer quantity, * * @return the order * - * @throws AlpacaAPIRequestException the alpaca API exception + * @throws AlpacaAPIRequestException thrown for {@link AlpacaAPIRequestException}s * @see Orders */ public Order getOrder(String orderID, Boolean nested) throws AlpacaAPIRequestException { Preconditions.checkNotNull(orderID); - AlpacaRequestBuilder urlBuilder = new AlpacaRequestBuilder(baseAPIURL, AlpacaConstants.VERSION_2_ENDPOINT, - AlpacaConstants.ORDERS_ENDPOINT, + AlpacaRequestBuilder urlBuilder = new AlpacaRequestBuilder(baseAPIURL, Endpoints.VERSION_2, + Endpoints.ORDERS, orderID); if (nested != null) { - urlBuilder.appendURLParameter(AlpacaConstants.NESTED_PARAMETER, nested.toString()); + urlBuilder.appendURLParameter(Parameters.NESTED, nested.toString()); } HttpResponse response = alpacaRequest.invokeGet(urlBuilder); @@ -911,15 +904,15 @@ public Order getOrder(String orderID, Boolean nested) throws AlpacaAPIRequestExc * * @return the order by client id * - * @throws AlpacaAPIRequestException the alpaca API exception + * @throws AlpacaAPIRequestException thrown for {@link AlpacaAPIRequestException}s * @see Orders */ public Order getOrderByClientID(String clientOrderId) throws AlpacaAPIRequestException { Preconditions.checkNotNull(clientOrderId); - AlpacaRequestBuilder urlBuilder = new AlpacaRequestBuilder(baseAPIURL, AlpacaConstants.VERSION_2_ENDPOINT, - AlpacaConstants.ORDERS_BY_CLIENT_ORDER_ID_ENDPOINT); - urlBuilder.appendURLParameter(AlpacaConstants.CLIENT_ORDER_ID_PARAMETER, clientOrderId); + AlpacaRequestBuilder urlBuilder = new AlpacaRequestBuilder(baseAPIURL, Endpoints.VERSION_2, + Endpoints.ORDERS_BY_CLIENT_ORDER_ID); + urlBuilder.appendURLParameter(Parameters.CLIENT_ORDER_ID, clientOrderId); HttpResponse response = alpacaRequest.invokeGet(urlBuilder); @@ -944,42 +937,42 @@ public Order getOrderByClientID(String clientOrderId) throws AlpacaAPIRequestExc * * @return the order * - * @throws AlpacaAPIRequestException the alpaca api request exception + * @throws AlpacaAPIRequestException thrown for {@link AlpacaAPIRequestException}s * @see Orders */ public Order replaceOrder(String orderID, Integer quantity, OrderTimeInForce timeInForce, Double limitPrice, Double stopPrice, Double trail, String clientOrderId) throws AlpacaAPIRequestException { Preconditions.checkNotNull(orderID); - AlpacaRequestBuilder urlBuilder = new AlpacaRequestBuilder(baseAPIURL, AlpacaConstants.VERSION_2_ENDPOINT, - AlpacaConstants.ORDERS_ENDPOINT, + AlpacaRequestBuilder urlBuilder = new AlpacaRequestBuilder(baseAPIURL, Endpoints.VERSION_2, + Endpoints.ORDERS, orderID); if (quantity != null) { - urlBuilder.appendJSONBodyProperty(AlpacaConstants.QTY_PARAMETER, quantity.toString()); + urlBuilder.appendJSONBodyProperty(Parameters.QTY, quantity.toString()); } if (timeInForce != null) { - urlBuilder.appendJSONBodyProperty(AlpacaConstants.TIME_IN_FORCE_PARAMETER, timeInForce.getAPIName()); + urlBuilder.appendJSONBodyProperty(Parameters.TIME_IN_FORCE, timeInForce.getAPIName()); } if (limitPrice != null) { - urlBuilder.appendJSONBodyProperty(AlpacaConstants.LIMIT_PRICE_PARAMETER, + urlBuilder.appendJSONBodyProperty(Parameters.LIMIT_PRICE, FormatUtil.toDecimalFormat(limitPrice)); } if (stopPrice != null) { - urlBuilder.appendJSONBodyProperty(AlpacaConstants.STOP_PRICE_PARAMETER, + urlBuilder.appendJSONBodyProperty(Parameters.STOP_PRICE, FormatUtil.toDecimalFormat(stopPrice)); } if (trail != null) { - urlBuilder.appendJSONBodyProperty(AlpacaConstants.TRAIL_PARAMETER, + urlBuilder.appendJSONBodyProperty(Parameters.TRAIL, FormatUtil.toDecimalFormat(trail)); } if (clientOrderId != null) { - urlBuilder.appendJSONBodyProperty(AlpacaConstants.CLIENT_ORDER_ID_PARAMETER, clientOrderId); + urlBuilder.appendJSONBodyProperty(Parameters.CLIENT_ORDER_ID, clientOrderId); } HttpResponse response = alpacaRequest.invokePatch(urlBuilder); @@ -997,12 +990,12 @@ public Order replaceOrder(String orderID, Integer quantity, OrderTimeInForce tim * * @return the array list of cancelled orders * - * @throws AlpacaAPIRequestException the alpaca api request exception + * @throws AlpacaAPIRequestException thrown for {@link AlpacaAPIRequestException}s * @see Orders */ public ArrayList cancelAllOrders() throws AlpacaAPIRequestException { - AlpacaRequestBuilder urlBuilder = new AlpacaRequestBuilder(baseAPIURL, AlpacaConstants.VERSION_2_ENDPOINT, - AlpacaConstants.ORDERS_ENDPOINT); + AlpacaRequestBuilder urlBuilder = new AlpacaRequestBuilder(baseAPIURL, Endpoints.VERSION_2, + Endpoints.ORDERS); HttpResponse response = alpacaRequest.invokeDelete(urlBuilder); @@ -1023,14 +1016,14 @@ public ArrayList cancelAllOrders() throws AlpacaAPIRequestExcept * * @return true, if successful * - * @throws AlpacaAPIRequestException the alpaca API exception + * @throws AlpacaAPIRequestException thrown for {@link AlpacaAPIRequestException}s * @see Orders */ public boolean cancelOrder(String orderId) throws AlpacaAPIRequestException { Preconditions.checkNotNull(orderId); - AlpacaRequestBuilder urlBuilder = new AlpacaRequestBuilder(baseAPIURL, AlpacaConstants.VERSION_2_ENDPOINT, - AlpacaConstants.ORDERS_ENDPOINT, + AlpacaRequestBuilder urlBuilder = new AlpacaRequestBuilder(baseAPIURL, Endpoints.VERSION_2, + Endpoints.ORDERS, orderId); HttpResponse response = alpacaRequest.invokeDelete(urlBuilder); @@ -1047,12 +1040,12 @@ public boolean cancelOrder(String orderId) throws AlpacaAPIRequestException { * * @return the open positions * - * @throws AlpacaAPIRequestException the alpaca API exception + * @throws AlpacaAPIRequestException thrown for {@link AlpacaAPIRequestException}s * @see Positions */ public ArrayList getOpenPositions() throws AlpacaAPIRequestException { - AlpacaRequestBuilder urlBuilder = new AlpacaRequestBuilder(baseAPIURL, AlpacaConstants.VERSION_2_ENDPOINT, - AlpacaConstants.POSITIONS_ENDPOINT); + AlpacaRequestBuilder urlBuilder = new AlpacaRequestBuilder(baseAPIURL, Endpoints.VERSION_2, + Endpoints.POSITIONS); HttpResponse response = alpacaRequest.invokeGet(urlBuilder); @@ -1072,14 +1065,14 @@ public ArrayList getOpenPositions() throws AlpacaAPIRequestException { * * @return the open position by symbol * - * @throws AlpacaAPIRequestException the alpaca API exception + * @throws AlpacaAPIRequestException thrown for {@link AlpacaAPIRequestException}s * @see Positions */ public Position getOpenPositionBySymbol(String symbol) throws AlpacaAPIRequestException { Preconditions.checkNotNull(symbol); - AlpacaRequestBuilder urlBuilder = new AlpacaRequestBuilder(baseAPIURL, AlpacaConstants.VERSION_2_ENDPOINT, - AlpacaConstants.POSITIONS_ENDPOINT, + AlpacaRequestBuilder urlBuilder = new AlpacaRequestBuilder(baseAPIURL, Endpoints.VERSION_2, + Endpoints.POSITIONS, symbol.trim()); HttpResponse response = alpacaRequest.invokeGet(urlBuilder); @@ -1098,12 +1091,12 @@ public Position getOpenPositionBySymbol(String symbol) throws AlpacaAPIRequestEx * * @return a list of closing position orders * - * @throws AlpacaAPIRequestException the alpaca API exception + * @throws AlpacaAPIRequestException thrown for {@link AlpacaAPIRequestException}s * @see Positions */ public ArrayList closeAllPositions() throws AlpacaAPIRequestException { - AlpacaRequestBuilder urlBuilder = new AlpacaRequestBuilder(baseAPIURL, AlpacaConstants.VERSION_2_ENDPOINT, - AlpacaConstants.POSITIONS_ENDPOINT); + AlpacaRequestBuilder urlBuilder = new AlpacaRequestBuilder(baseAPIURL, Endpoints.VERSION_2, + Endpoints.POSITIONS); HttpResponse response = alpacaRequest.invokeDelete(urlBuilder); @@ -1123,14 +1116,14 @@ public ArrayList closeAllPositions() throws AlpacaAPIRequest * * @return a closing position order * - * @throws AlpacaAPIRequestException the alpaca API exception + * @throws AlpacaAPIRequestException thrown for {@link AlpacaAPIRequestException}s * @see Positions */ public Order closePosition(String symbol) throws AlpacaAPIRequestException { Preconditions.checkNotNull(symbol); - AlpacaRequestBuilder urlBuilder = new AlpacaRequestBuilder(baseAPIURL, AlpacaConstants.VERSION_2_ENDPOINT, - AlpacaConstants.POSITIONS_ENDPOINT, + AlpacaRequestBuilder urlBuilder = new AlpacaRequestBuilder(baseAPIURL, Endpoints.VERSION_2, + Endpoints.POSITIONS, symbol); HttpResponse response = alpacaRequest.invokeDelete(urlBuilder); @@ -1150,19 +1143,19 @@ public Order closePosition(String symbol) throws AlpacaAPIRequestException { * * @return the assets * - * @throws AlpacaAPIRequestException the alpaca API exception + * @throws AlpacaAPIRequestException thrown for {@link AlpacaAPIRequestException}s * @see Assets */ public ArrayList getAssets(AssetStatus assetStatus, String assetClass) throws AlpacaAPIRequestException { - AlpacaRequestBuilder urlBuilder = new AlpacaRequestBuilder(baseAPIURL, AlpacaConstants.VERSION_2_ENDPOINT, - AlpacaConstants.ASSETS_ENDPOINT); + AlpacaRequestBuilder urlBuilder = new AlpacaRequestBuilder(baseAPIURL, Endpoints.VERSION_2, + Endpoints.ASSETS); if (assetStatus != null) { - urlBuilder.appendURLParameter(AlpacaConstants.STATUS_PARAMETER, assetStatus.getAPIName()); + urlBuilder.appendURLParameter(Parameters.STATUS, assetStatus.getAPIName()); } if (assetClass != null) { - urlBuilder.appendURLParameter(AlpacaConstants.ASSET_CLASS_PARAMETER, assetClass.trim()); + urlBuilder.appendURLParameter(Parameters.ASSET_CLASS, assetClass.trim()); } HttpResponse response = alpacaRequest.invokeGet(urlBuilder); @@ -1183,14 +1176,14 @@ public ArrayList getAssets(AssetStatus assetStatus, String assetClass) th * * @return the asset by symbol * - * @throws AlpacaAPIRequestException the alpaca API exception + * @throws AlpacaAPIRequestException thrown for {@link AlpacaAPIRequestException}s * @see Assets */ public Asset getAssetBySymbol(String symbolOrAssetID) throws AlpacaAPIRequestException { Preconditions.checkNotNull(symbolOrAssetID); - AlpacaRequestBuilder urlBuilder = new AlpacaRequestBuilder(baseAPIURL, AlpacaConstants.VERSION_2_ENDPOINT, - AlpacaConstants.ASSETS_ENDPOINT, + AlpacaRequestBuilder urlBuilder = new AlpacaRequestBuilder(baseAPIURL, Endpoints.VERSION_2, + Endpoints.ASSETS, symbolOrAssetID.trim()); HttpResponse response = alpacaRequest.invokeGet(urlBuilder); @@ -1211,8 +1204,8 @@ public Asset getAssetBySymbol(String symbolOrAssetID) throws AlpacaAPIRequestExc * @see Watchlists */ public ArrayList getWatchlists() throws AlpacaAPIRequestException { - AlpacaRequestBuilder urlBuilder = new AlpacaRequestBuilder(baseAPIURL, AlpacaConstants.VERSION_2_ENDPOINT, - AlpacaConstants.WATCHLISTS_ENDPOINT); + AlpacaRequestBuilder urlBuilder = new AlpacaRequestBuilder(baseAPIURL, Endpoints.VERSION_2, + Endpoints.WATCHLISTS); HttpResponse response = alpacaRequest.invokeGet(urlBuilder); @@ -1239,8 +1232,8 @@ public Watchlist createWatchlist(String name, String... symbols) throws AlpacaAP Preconditions.checkNotNull(name); Preconditions.checkState(name.length() <= 64); - AlpacaRequestBuilder urlBuilder = new AlpacaRequestBuilder(baseAPIURL, AlpacaConstants.VERSION_2_ENDPOINT, - AlpacaConstants.WATCHLISTS_ENDPOINT); + AlpacaRequestBuilder urlBuilder = new AlpacaRequestBuilder(baseAPIURL, Endpoints.VERSION_2, + Endpoints.WATCHLISTS); urlBuilder.appendJSONBodyProperty("name", name); @@ -1273,8 +1266,8 @@ public Watchlist createWatchlist(String name, String... symbols) throws AlpacaAP public Watchlist getWatchlist(String watchlistID) throws AlpacaAPIRequestException { Preconditions.checkNotNull(watchlistID); - AlpacaRequestBuilder urlBuilder = new AlpacaRequestBuilder(baseAPIURL, AlpacaConstants.VERSION_2_ENDPOINT, - AlpacaConstants.WATCHLISTS_ENDPOINT, + AlpacaRequestBuilder urlBuilder = new AlpacaRequestBuilder(baseAPIURL, Endpoints.VERSION_2, + Endpoints.WATCHLISTS, watchlistID); HttpResponse response = alpacaRequest.invokeGet(urlBuilder); @@ -1302,8 +1295,8 @@ public Watchlist updateWatchlist(String watchlistID, String name, String... symb throws AlpacaAPIRequestException { Preconditions.checkNotNull(watchlistID); - AlpacaRequestBuilder urlBuilder = new AlpacaRequestBuilder(baseAPIURL, AlpacaConstants.VERSION_2_ENDPOINT, - AlpacaConstants.WATCHLISTS_ENDPOINT, + AlpacaRequestBuilder urlBuilder = new AlpacaRequestBuilder(baseAPIURL, Endpoints.VERSION_2, + Endpoints.WATCHLISTS, watchlistID); if (name != null) { @@ -1341,8 +1334,8 @@ public Watchlist addWatchlistAsset(String watchlistID, String symbol) throws Alp Preconditions.checkNotNull(watchlistID); Preconditions.checkNotNull(symbol); - AlpacaRequestBuilder urlBuilder = new AlpacaRequestBuilder(baseAPIURL, AlpacaConstants.VERSION_2_ENDPOINT, - AlpacaConstants.WATCHLISTS_ENDPOINT, + AlpacaRequestBuilder urlBuilder = new AlpacaRequestBuilder(baseAPIURL, Endpoints.VERSION_2, + Endpoints.WATCHLISTS, watchlistID); urlBuilder.appendJSONBodyProperty("symbol", symbol); @@ -1369,8 +1362,8 @@ public Watchlist addWatchlistAsset(String watchlistID, String symbol) throws Alp public boolean deleteWatchlist(String watchlistID) throws AlpacaAPIRequestException { Preconditions.checkNotNull(watchlistID); - AlpacaRequestBuilder urlBuilder = new AlpacaRequestBuilder(baseAPIURL, AlpacaConstants.VERSION_2_ENDPOINT, - AlpacaConstants.WATCHLISTS_ENDPOINT, + AlpacaRequestBuilder urlBuilder = new AlpacaRequestBuilder(baseAPIURL, Endpoints.VERSION_2, + Endpoints.WATCHLISTS, watchlistID); HttpResponse response = alpacaRequest.invokeDelete(urlBuilder); @@ -1397,8 +1390,8 @@ public Watchlist removeSymbolFromWatchlist(String watchlistID, String symbol) th Preconditions.checkNotNull(watchlistID); Preconditions.checkNotNull(symbol); - AlpacaRequestBuilder urlBuilder = new AlpacaRequestBuilder(baseAPIURL, AlpacaConstants.VERSION_2_ENDPOINT, - AlpacaConstants.WATCHLISTS_ENDPOINT, + AlpacaRequestBuilder urlBuilder = new AlpacaRequestBuilder(baseAPIURL, Endpoints.VERSION_2, + Endpoints.WATCHLISTS, watchlistID, symbol); @@ -1427,29 +1420,29 @@ public Watchlist removeSymbolFromWatchlist(String watchlistID, String symbol) th * * @return the portfolio history * - * @throws AlpacaAPIRequestException the alpaca api request exception + * @throws AlpacaAPIRequestException thrown for {@link AlpacaAPIRequestException}s */ public PortfolioHistory getPortfolioHistory(Integer periodLength, PortfolioPeriodUnit periodUnit, PortfolioTimeFrame timeFrame, LocalDate dateEnd, Boolean extendedHours) throws AlpacaAPIRequestException { - AlpacaRequestBuilder urlBuilder = new AlpacaRequestBuilder(baseAPIURL, AlpacaConstants.VERSION_2_ENDPOINT, - AlpacaConstants.ACCOUNT_ENDPOINT, - AlpacaConstants.PORTFOLIO_ENDPOINT, - AlpacaConstants.HISTORY_ENDPOINT); + AlpacaRequestBuilder urlBuilder = new AlpacaRequestBuilder(baseAPIURL, Endpoints.VERSION_2, + Endpoints.ACCOUNT, + Endpoints.PORTFOLIO, + Endpoints.HISTORY); if (periodLength != null && periodUnit != null) { - urlBuilder.appendURLParameter(AlpacaConstants.PERIOD_PARAMETER, periodLength + periodUnit.getAPIName()); + urlBuilder.appendURLParameter(Parameters.PERIOD, periodLength + periodUnit.getAPIName()); } if (timeFrame != null) { - urlBuilder.appendURLParameter(AlpacaConstants.TIMEFRAME_PARAMETER, timeFrame.getAPIName()); + urlBuilder.appendURLParameter(Parameters.TIMEFRAME, timeFrame.getAPIName()); } if (dateEnd != null) { - urlBuilder.appendURLParameter(AlpacaConstants.DATE_END_PARAMETER, FormatUtil.toDateString(dateEnd)); + urlBuilder.appendURLParameter(Parameters.DATE_END, FormatUtil.toDateString(dateEnd)); } if (extendedHours != null) { - urlBuilder.appendURLParameter(AlpacaConstants.EXTENDED_HOURS_PARAMETER, extendedHours.toString()); + urlBuilder.appendURLParameter(Parameters.EXTENDED_HOURS, extendedHours.toString()); } HttpResponse response = alpacaRequest.invokeGet(urlBuilder); @@ -1466,12 +1459,12 @@ public PortfolioHistory getPortfolioHistory(Integer periodLength, PortfolioPerio * * @return the calendar * - * @throws AlpacaAPIRequestException the alpaca API exception + * @throws AlpacaAPIRequestException thrown for {@link AlpacaAPIRequestException}s * @see Calendar */ public ArrayList getCalendar() throws AlpacaAPIRequestException { - AlpacaRequestBuilder urlBuilder = new AlpacaRequestBuilder(baseAPIURL, AlpacaConstants.VERSION_2_ENDPOINT, - AlpacaConstants.CALENDAR_ENDPOINT); + AlpacaRequestBuilder urlBuilder = new AlpacaRequestBuilder(baseAPIURL, Endpoints.VERSION_2, + Endpoints.CALENDAR); HttpResponse response = alpacaRequest.invokeGet(urlBuilder); @@ -1492,19 +1485,19 @@ public ArrayList getCalendar() throws AlpacaAPIRequestException { * * @return the calendar * - * @throws AlpacaAPIRequestException the alpaca API exception + * @throws AlpacaAPIRequestException thrown for {@link AlpacaAPIRequestException}s * @see Calendar */ public ArrayList getCalendar(LocalDate start, LocalDate end) throws AlpacaAPIRequestException { - AlpacaRequestBuilder urlBuilder = new AlpacaRequestBuilder(baseAPIURL, AlpacaConstants.VERSION_2_ENDPOINT, - AlpacaConstants.CALENDAR_ENDPOINT); + AlpacaRequestBuilder urlBuilder = new AlpacaRequestBuilder(baseAPIURL, Endpoints.VERSION_2, + Endpoints.CALENDAR); if (start != null) { - urlBuilder.appendURLParameter(AlpacaConstants.START_PARAMETER, FormatUtil.toDateString(start)); + urlBuilder.appendURLParameter(Parameters.START, FormatUtil.toDateString(start)); } if (end != null) { - urlBuilder.appendURLParameter(AlpacaConstants.END_PARAMETER, FormatUtil.toDateString(end)); + urlBuilder.appendURLParameter(Parameters.END, FormatUtil.toDateString(end)); } HttpResponse response = alpacaRequest.invokeGet(urlBuilder); @@ -1523,12 +1516,12 @@ public ArrayList getCalendar(LocalDate start, LocalDate end) throws Al * * @return the clock * - * @throws AlpacaAPIRequestException the alpaca API exception + * @throws AlpacaAPIRequestException thrown for {@link AlpacaAPIRequestException}s * @see Clock */ public Clock getClock() throws AlpacaAPIRequestException { - AlpacaRequestBuilder urlBuilder = new AlpacaRequestBuilder(baseAPIURL, AlpacaConstants.VERSION_2_ENDPOINT, - AlpacaConstants.CLOCK_ENDPOINT); + AlpacaRequestBuilder urlBuilder = new AlpacaRequestBuilder(baseAPIURL, Endpoints.VERSION_2, + Endpoints.CLOCK); HttpResponse response = alpacaRequest.invokeGet(urlBuilder); @@ -1558,7 +1551,7 @@ public Clock getClock() throws AlpacaAPIRequestException { * @return An object with a key for each symbol and the Bars object as the values. Note that it returns status 200 * with an empty object if no requested symbol is found. * - * @throws AlpacaAPIRequestException the alpaca API exception + * @throws AlpacaAPIRequestException thrown for {@link AlpacaAPIRequestException}s * @see Bars */ public Map> getBars(BarsTimeFrame timeframe, String symbol, Integer limit, ZonedDateTime start, @@ -1586,44 +1579,44 @@ public Map> getBars(BarsTimeFrame timeframe, String symbol, In * @return @return An object with a key for each symbol and the Bars object as the values. Note that it returns * status 200 with an empty object if no requested symbol is found. * - * @throws AlpacaAPIRequestException the alpaca API exception + * @throws AlpacaAPIRequestException thrown for {@link AlpacaAPIRequestException}s * @see Bars */ public Map> getBars(BarsTimeFrame timeframe, String[] symbols, Integer limit, ZonedDateTime start, ZonedDateTime end, ZonedDateTime after, ZonedDateTime until) throws AlpacaAPIRequestException { - AlpacaRequestBuilder urlBuilder = new AlpacaRequestBuilder(baseDataUrl, AlpacaConstants.VERSION_1_ENDPOINT, - AlpacaConstants.BARS_ENDPOINT); + AlpacaRequestBuilder urlBuilder = new AlpacaRequestBuilder(baseDataUrl, Endpoints.VERSION_1, + Endpoints.BARS); if (timeframe != null) { urlBuilder.appendEndpoint(timeframe.getAPIName()); } if (symbols != null) { - urlBuilder.appendURLParameter(AlpacaConstants.SYMBOLS_PARAMETER, String.join(",", symbols)); + urlBuilder.appendURLParameter(Parameters.SYMBOLS, String.join(",", symbols)); } if (limit != null) { - urlBuilder.appendURLParameter(AlpacaConstants.LIMIT_PARAMETER, limit.toString()); + urlBuilder.appendURLParameter(Parameters.LIMIT, limit.toString()); } if (start != null) { - urlBuilder.appendURLParameter(AlpacaConstants.START_PARAMETER, + urlBuilder.appendURLParameter(Parameters.START, start.format(DateTimeFormatter.ISO_OFFSET_DATE_TIME)); } if (end != null) { - urlBuilder.appendURLParameter(AlpacaConstants.END_PARAMETER, + urlBuilder.appendURLParameter(Parameters.END, end.format(DateTimeFormatter.ISO_OFFSET_DATE_TIME)); } if (after != null) { - urlBuilder.appendURLParameter(AlpacaConstants.AFTER_PARAMETER, + urlBuilder.appendURLParameter(Parameters.AFTER, after.format(DateTimeFormatter.ISO_OFFSET_DATE_TIME)); } if (until != null) { - urlBuilder.appendURLParameter(AlpacaConstants.UNTIL_PARAMETER, + urlBuilder.appendURLParameter(Parameters.UNTIL, until.format(DateTimeFormatter.ISO_OFFSET_DATE_TIME)); } @@ -1645,15 +1638,15 @@ public Map> getBars(BarsTimeFrame timeframe, String[] symbols, * * @return the last trade response object * - * @throws AlpacaAPIRequestException the alpaca api request exception + * @throws AlpacaAPIRequestException thrown for {@link AlpacaAPIRequestException}s * @see Last Trade */ public LastTradeResponse getLastTrade(String symbol) throws AlpacaAPIRequestException { Preconditions.checkNotNull(symbol); - AlpacaRequestBuilder urlBuilder = new AlpacaRequestBuilder(baseDataUrl, AlpacaConstants.VERSION_1_ENDPOINT, - AlpacaConstants.LAST_ENDPOINT, - AlpacaConstants.STOCKS_ENDPOINT, + AlpacaRequestBuilder urlBuilder = new AlpacaRequestBuilder(baseDataUrl, Endpoints.VERSION_1, + Endpoints.LAST, + Endpoints.STOCKS, symbol); HttpResponse response = alpacaRequest.invokeGet(urlBuilder); @@ -1672,15 +1665,15 @@ public LastTradeResponse getLastTrade(String symbol) throws AlpacaAPIRequestExce * * @return the last quote response object * - * @throws AlpacaAPIRequestException the alpaca api request exception + * @throws AlpacaAPIRequestException thrown for {@link AlpacaAPIRequestException}s * @see Last Quote */ public LastQuoteResponse getLastQuote(String symbol) throws AlpacaAPIRequestException { Preconditions.checkNotNull(symbol); - AlpacaRequestBuilder urlBuilder = new AlpacaRequestBuilder(baseDataUrl, AlpacaConstants.VERSION_1_ENDPOINT, - AlpacaConstants.LAST_QUOTE_ENDPOINT, - AlpacaConstants.STOCKS_ENDPOINT, + AlpacaRequestBuilder urlBuilder = new AlpacaRequestBuilder(baseDataUrl, Endpoints.VERSION_1, + Endpoints.LAST_QUOTE, + Endpoints.STOCKS, symbol); HttpResponse response = alpacaRequest.invokeGet(urlBuilder); @@ -1693,44 +1686,48 @@ public LastQuoteResponse getLastQuote(String symbol) throws AlpacaAPIRequestExce } /** - * Adds the alpaca stream listener. + * Adds the {@link AlpacaStreamListener}. Note that when the first {@link AlpacaStreamListener} is added, the + * Websocket connection is created. * * @param streamListener the stream listener * - * @throws WebsocketException the WebsocketException + * @throws WebsocketException thrown for {@link WebsocketException}s */ public void addAlpacaStreamListener(AlpacaStreamListener streamListener) throws WebsocketException { alpacaWebSocketClient.addListener(streamListener); } /** - * Removes the alpaca stream listener. + * Removes the {@link AlpacaStreamListener}. Note that when the last {@link AlpacaStreamListener} is removed, the + * Websocket connection is closed. * - * @param streamListener the stream listener + * @param streamListener the {@link AlpacaStreamListener} * - * @throws WebsocketException the WebsocketException + * @throws WebsocketException thrown for {@link WebsocketException}s */ public void removeAlpacaStreamListener(AlpacaStreamListener streamListener) throws WebsocketException { alpacaWebSocketClient.removeListener(streamListener); } /** - * Adds the alpaca stream listener. + * Adds the {@link MarketDataStreamListener}. Note that when the first {@link AlpacaStreamListener} is added, the + * Websocket connection is created. * - * @param streamListener the stream listener + * @param streamListener the {@link MarketDataStreamListener} * - * @throws WebsocketException the WebsocketException + * @throws WebsocketException thrown for {@link WebsocketException}s */ public void addMarketDataStreamListener(MarketDataStreamListener streamListener) throws WebsocketException { marketDataWebSocketClient.addListener(streamListener); } /** - * Removes the alpaca stream listener. + * Removes the {@link MarketDataStreamListener}. Note that when the last {@link MarketDataStreamListener} is + * removed, the Websocket connection is closed. * - * @param streamListener the stream listener + * @param streamListener the {@link MarketDataStreamListener} * - * @throws WebsocketException the WebsocketException + * @throws WebsocketException thrown for {@link WebsocketException}s */ public void removeMarketDataStreamListener(MarketDataStreamListener streamListener) throws WebsocketException { marketDataWebSocketClient.removeListener(streamListener); diff --git a/src/main/java/net/jacobpeterson/alpaca/AlpacaConstants.java b/src/main/java/net/jacobpeterson/alpaca/AlpacaConstants.java index df37aa99..00c24185 100644 --- a/src/main/java/net/jacobpeterson/alpaca/AlpacaConstants.java +++ b/src/main/java/net/jacobpeterson/alpaca/AlpacaConstants.java @@ -5,53 +5,66 @@ */ public class AlpacaConstants { - public static final String VERSION_1_ENDPOINT = "v1"; - public static final String VERSION_2_ENDPOINT = "v2"; - public static final String SYMBOLS_PARAMETER = "symbols"; - public static final String END_PARAMETER = "end"; - public static final String START_PARAMETER = "start"; - public static final String DATE_PARAMETER = "date"; - public static final String ASSET_CLASS_PARAMETER = "asset_class"; - public static final String CLIENT_ORDER_ID_PARAMETER = "client_order_id"; - public static final String ORDER_CLASS_PARAMETER = "order_class"; - public static final String TAKE_PROFIT_PARAMETER = "take_profit"; - public static final String STOP_LOSS_PARAMETER = "stop_loss"; - public static final String STOP_PRICE_PARAMETER = "stop_price"; - public static final String TRAIL_PRICE_PARAMETER = "trail_price"; - public static final String TRAIL_PERCENT_PARAMETER = "trail_percent"; - public static final String TRAIL_PARAMETER = "trail"; - public static final String LIMIT_PRICE_PARAMETER = "limit_price"; - public static final String TIME_IN_FORCE_PARAMETER = "time_in_force"; - public static final String EXTENDED_HOURS_PARAMETER = "extended_hours"; - public static final String TYPE_PARAMETER = "type"; - public static final String SIDE_PARAMETER = "side"; - public static final String QTY_PARAMETER = "qty"; - public static final String SYMBOL_PARAMETER = "symbol"; - public static final String DIRECTION_PARAMETER = "direction"; - public static final String NESTED_PARAMETER = "nested"; - public static final String UNTIL_PARAMETER = "until"; - public static final String AFTER_PARAMETER = "after"; - public static final String LIMIT_PARAMETER = "limit"; - public static final String STATUS_PARAMETER = "status"; - public static final String PERIOD_PARAMETER = "period"; - public static final String TIMEFRAME_PARAMETER = "timeframe"; - public static final String DATE_END_PARAMETER = "date_end"; - public static final String BARS_ENDPOINT = "bars"; - public static final String LAST_ENDPOINT = "last"; - public static final String LAST_QUOTE_ENDPOINT = "last_quote"; - public static final String STOCKS_ENDPOINT = "stocks"; - public static final String CLOCK_ENDPOINT = "clock"; - public static final String CALENDAR_ENDPOINT = "calendar"; - public static final String ASSETS_ENDPOINT = "assets"; - public static final String POSITIONS_ENDPOINT = "positions"; - public static final String ACCOUNT_ENDPOINT = "account"; - public static final String ACTIVITIES_ENDPOINT = "activities"; - public static final String CONFIGURATIONS_ENDPOINT = "configurations"; - public static final String ORDERS_BY_CLIENT_ORDER_ID_ENDPOINT = "orders:by_client_order_id"; - public static final String PAGE_SIZE_PARAMETER = "page_size"; - public static final String PAGE_TOKEN_PARAMETER = "page_token"; - public static final String ORDERS_ENDPOINT = "orders"; - public static final String WATCHLISTS_ENDPOINT = "watchlists"; - public static final String PORTFOLIO_ENDPOINT = "portfolio"; - public static final String HISTORY_ENDPOINT = "history"; + /** + * Defines endpoints of {@link AlpacaAPI}. + */ + public static class Endpoints { + + public static final String VERSION_1 = "v1"; + public static final String VERSION_2 = "v2"; + public static final String BARS = "bars"; + public static final String LAST = "last"; + public static final String LAST_QUOTE = "last_quote"; + public static final String STOCKS = "stocks"; + public static final String CLOCK = "clock"; + public static final String CALENDAR = "calendar"; + public static final String ASSETS = "assets"; + public static final String POSITIONS = "positions"; + public static final String ACCOUNT = "account"; + public static final String ACTIVITIES = "activities"; + public static final String CONFIGURATIONS = "configurations"; + public static final String ORDERS_BY_CLIENT_ORDER_ID = "orders:by_client_order_id"; + public static final String ORDERS = "orders"; + public static final String WATCHLISTS = "watchlists"; + public static final String PORTFOLIO = "portfolio"; + public static final String HISTORY = "history"; + } + + /** + * Defines parameters of {@link AlpacaAPI}. + */ + public static class Parameters { + + public static final String SYMBOLS = "symbols"; + public static final String END = "end"; + public static final String START = "start"; + public static final String DATE = "date"; + public static final String ASSET_CLASS = "asset_class"; + public static final String CLIENT_ORDER_ID = "client_order_id"; + public static final String ORDER_CLASS = "order_class"; + public static final String TAKE_PROFIT = "take_profit"; + public static final String STOP_LOSS = "stop_loss"; + public static final String STOP_PRICE = "stop_price"; + public static final String TRAIL_PRICE = "trail_price"; + public static final String TRAIL_PERCENT = "trail_percent"; + public static final String TRAIL = "trail"; + public static final String LIMIT_PRICE = "limit_price"; + public static final String TIME_IN_FORCE = "time_in_force"; + public static final String EXTENDED_HOURS = "extended_hours"; + public static final String TYPE = "type"; + public static final String SIDE = "side"; + public static final String QTY = "qty"; + public static final String SYMBOL = "symbol"; + public static final String DIRECTION = "direction"; + public static final String NESTED = "nested"; + public static final String UNTIL = "until"; + public static final String AFTER = "after"; + public static final String LIMIT = "limit"; + public static final String STATUS = "status"; + public static final String PERIOD = "period"; + public static final String TIMEFRAME = "timeframe"; + public static final String DATE_END = "date_end"; + public static final String PAGE_SIZE = "page_size"; + public static final String PAGE_TOKEN = "page_token"; + } } diff --git a/src/main/java/net/jacobpeterson/alpaca/enums/AccountStatus.java b/src/main/java/net/jacobpeterson/alpaca/enums/AccountStatus.java index fa772fef..25f53982 100644 --- a/src/main/java/net/jacobpeterson/alpaca/enums/AccountStatus.java +++ b/src/main/java/net/jacobpeterson/alpaca/enums/AccountStatus.java @@ -3,7 +3,7 @@ import net.jacobpeterson.abstracts.enums.APIName; /** - * The enum {@link AccountStatus}. + * {@link AccountStatus} defines enums for various account statuses. * * @see Account Status */ @@ -50,7 +50,7 @@ public enum AccountStatus implements APIName { /** * Instantiates a new {@link AccountStatus}. * - * @param apiName the api name + * @param apiName the API name */ AccountStatus(String apiName) { this.apiName = apiName; diff --git a/src/main/java/net/jacobpeterson/alpaca/enums/ActivityType.java b/src/main/java/net/jacobpeterson/alpaca/enums/ActivityType.java index 3eb22377..f87a8960 100644 --- a/src/main/java/net/jacobpeterson/alpaca/enums/ActivityType.java +++ b/src/main/java/net/jacobpeterson/alpaca/enums/ActivityType.java @@ -3,7 +3,7 @@ import net.jacobpeterson.abstracts.enums.APIName; /** - * The enum {@link AccountStatus}. + * {@link AccountStatus} defines enums for various account activity types. * * @see * https://docs.alpaca.markets/api-documentation/api-v2/account-activities/ @@ -11,7 +11,7 @@ public enum ActivityType implements APIName { // The names of these enums must be the API name exactly - // @see AccountStatus#getAPIName() + // @see ActivityType#getAPIName() /** Order fills (both partial and full fills) */ FILL, diff --git a/src/main/java/net/jacobpeterson/alpaca/enums/AssetStatus.java b/src/main/java/net/jacobpeterson/alpaca/enums/AssetStatus.java index 8c697886..a1a03ef8 100644 --- a/src/main/java/net/jacobpeterson/alpaca/enums/AssetStatus.java +++ b/src/main/java/net/jacobpeterson/alpaca/enums/AssetStatus.java @@ -3,7 +3,7 @@ import net.jacobpeterson.abstracts.enums.APIName; /** - * The Enum {@link AssetStatus}. + * {@link AssetStatus} defines enums of various asset statuses. */ public enum AssetStatus implements APIName { @@ -19,7 +19,7 @@ public enum AssetStatus implements APIName { /** * Instantiates a new {@link AssetStatus}. * - * @param apiName the api name + * @param apiName the API name */ AssetStatus(String apiName) { this.apiName = apiName; diff --git a/src/main/java/net/jacobpeterson/alpaca/enums/BarsTimeFrame.java b/src/main/java/net/jacobpeterson/alpaca/enums/BarsTimeFrame.java index 7e8522dc..1ac45f35 100644 --- a/src/main/java/net/jacobpeterson/alpaca/enums/BarsTimeFrame.java +++ b/src/main/java/net/jacobpeterson/alpaca/enums/BarsTimeFrame.java @@ -3,7 +3,7 @@ import net.jacobpeterson.abstracts.enums.APIName; /** - * The Enum {@link BarsTimeFrame}. + * {@link BarsTimeFrame} defines enums for time frames of a {@link net.jacobpeterson.domain.alpaca.marketdata.Bar}. */ public enum BarsTimeFrame implements APIName { @@ -25,7 +25,7 @@ public enum BarsTimeFrame implements APIName { /** * Instantiates a new {@link BarsTimeFrame}. * - * @param apiName the api name + * @param apiName the API name */ BarsTimeFrame(String apiName) { this.apiName = apiName; diff --git a/src/main/java/net/jacobpeterson/alpaca/enums/Direction.java b/src/main/java/net/jacobpeterson/alpaca/enums/Direction.java index e02ce802..4af3bd7b 100644 --- a/src/main/java/net/jacobpeterson/alpaca/enums/Direction.java +++ b/src/main/java/net/jacobpeterson/alpaca/enums/Direction.java @@ -3,7 +3,7 @@ import net.jacobpeterson.abstracts.enums.APIName; /** - * The Enum {@link Direction}. + * {@link Direction} defines enums of various directional orderings. */ public enum Direction implements APIName { diff --git a/src/main/java/net/jacobpeterson/alpaca/enums/OrderClass.java b/src/main/java/net/jacobpeterson/alpaca/enums/OrderClass.java index 9f1cd253..3c1d6530 100644 --- a/src/main/java/net/jacobpeterson/alpaca/enums/OrderClass.java +++ b/src/main/java/net/jacobpeterson/alpaca/enums/OrderClass.java @@ -3,7 +3,7 @@ import net.jacobpeterson.abstracts.enums.APIName; /** - * The enum {@link OrderClass}. + * {@link OrderClass} defines enums of various {@link net.jacobpeterson.domain.alpaca.order.Order} classes. */ public enum OrderClass implements APIName { @@ -25,7 +25,7 @@ public enum OrderClass implements APIName { /** * Instantiates a new {@link OrderClass}. * - * @param apiName the api name + * @param apiName the API name */ OrderClass(String apiName) { this.apiName = apiName; diff --git a/src/main/java/net/jacobpeterson/alpaca/enums/OrderSide.java b/src/main/java/net/jacobpeterson/alpaca/enums/OrderSide.java index 0f0eccce..717e9d92 100644 --- a/src/main/java/net/jacobpeterson/alpaca/enums/OrderSide.java +++ b/src/main/java/net/jacobpeterson/alpaca/enums/OrderSide.java @@ -3,7 +3,7 @@ import net.jacobpeterson.abstracts.enums.APIName; /** - * The Enum {@link OrderSide}. + * {@link OrderSide} defines enums for various {@link net.jacobpeterson.domain.alpaca.order.Order} sides. */ public enum OrderSide implements APIName { @@ -19,7 +19,7 @@ public enum OrderSide implements APIName { /** * Instantiates a new {@link OrderSide}. * - * @param apiName the api name + * @param apiName the API name */ OrderSide(String apiName) { this.apiName = apiName; diff --git a/src/main/java/net/jacobpeterson/alpaca/enums/OrderStatus.java b/src/main/java/net/jacobpeterson/alpaca/enums/OrderStatus.java index fe048091..0f8b9b0e 100644 --- a/src/main/java/net/jacobpeterson/alpaca/enums/OrderStatus.java +++ b/src/main/java/net/jacobpeterson/alpaca/enums/OrderStatus.java @@ -3,7 +3,7 @@ import net.jacobpeterson.abstracts.enums.APIName; /** - * The Enum {@link OrderStatus}. + * {@link OrderStatus} defines enums for various {@link net.jacobpeterson.domain.alpaca.order.Order} statuses. */ public enum OrderStatus implements APIName { @@ -22,7 +22,7 @@ public enum OrderStatus implements APIName { /** * Instantiates a new {@link OrderStatus}. * - * @param apiName the api name + * @param apiName the API name */ OrderStatus(String apiName) { this.apiName = apiName; diff --git a/src/main/java/net/jacobpeterson/alpaca/enums/OrderTimeInForce.java b/src/main/java/net/jacobpeterson/alpaca/enums/OrderTimeInForce.java index 82f15d17..88723625 100644 --- a/src/main/java/net/jacobpeterson/alpaca/enums/OrderTimeInForce.java +++ b/src/main/java/net/jacobpeterson/alpaca/enums/OrderTimeInForce.java @@ -3,7 +3,7 @@ import net.jacobpeterson.abstracts.enums.APIName; /** - * The Enum {@link OrderTimeInForce}. + * {@link OrderTimeInForce} defines enums for various {@link net.jacobpeterson.domain.alpaca.order.Order} TIFs. * * @see Time in Force */ @@ -60,7 +60,7 @@ public enum OrderTimeInForce implements APIName { /** * Instantiates a new {@link OrderTimeInForce}. * - * @param apiName the api name + * @param apiName the API name */ OrderTimeInForce(String apiName) { this.apiName = apiName; diff --git a/src/main/java/net/jacobpeterson/alpaca/enums/OrderType.java b/src/main/java/net/jacobpeterson/alpaca/enums/OrderType.java index 1b371ac8..0923a8d2 100644 --- a/src/main/java/net/jacobpeterson/alpaca/enums/OrderType.java +++ b/src/main/java/net/jacobpeterson/alpaca/enums/OrderType.java @@ -3,7 +3,7 @@ import net.jacobpeterson.abstracts.enums.APIName; /** - * The Enum {@link OrderType}. + * {@link OrderType} defines enums for various {@link net.jacobpeterson.domain.alpaca.order.Order} types. * * @see Order Types */ @@ -55,7 +55,7 @@ public enum OrderType implements APIName { /** * Instantiates a new {@link OrderType}. * - * @param apiName the api name + * @param apiName the API name */ OrderType(String apiName) { this.apiName = apiName; diff --git a/src/main/java/net/jacobpeterson/alpaca/enums/PortfolioPeriodUnit.java b/src/main/java/net/jacobpeterson/alpaca/enums/PortfolioPeriodUnit.java index 7d51319e..e14c1881 100644 --- a/src/main/java/net/jacobpeterson/alpaca/enums/PortfolioPeriodUnit.java +++ b/src/main/java/net/jacobpeterson/alpaca/enums/PortfolioPeriodUnit.java @@ -3,7 +3,9 @@ import net.jacobpeterson.abstracts.enums.APIName; /** - * The enum {@link PortfolioPeriodUnit}. + * {@link PortfolioPeriodUnit} defines enums for various + * {@link net.jacobpeterson.domain.alpaca.portfoliohistory.PortfolioHistory} + * period units. */ public enum PortfolioPeriodUnit implements APIName { @@ -25,7 +27,7 @@ public enum PortfolioPeriodUnit implements APIName { /** * Instantiates a new {@link PortfolioPeriodUnit}. * - * @param apiName the api name + * @param apiName the API name */ PortfolioPeriodUnit(String apiName) { this.apiName = apiName; diff --git a/src/main/java/net/jacobpeterson/alpaca/enums/PortfolioTimeFrame.java b/src/main/java/net/jacobpeterson/alpaca/enums/PortfolioTimeFrame.java index 8ca998ff..52c22fd4 100644 --- a/src/main/java/net/jacobpeterson/alpaca/enums/PortfolioTimeFrame.java +++ b/src/main/java/net/jacobpeterson/alpaca/enums/PortfolioTimeFrame.java @@ -5,7 +5,9 @@ import net.jacobpeterson.util.gson.GsonUtil; /** - * The enum {@link PortfolioTimeFrame}. + * {@link PortfolioTimeFrame} defines enums for various + * {@link net.jacobpeterson.domain.alpaca.portfoliohistory.PortfolioHistory} + * time frames. */ public enum PortfolioTimeFrame implements APIName { diff --git a/src/main/java/net/jacobpeterson/alpaca/properties/AlpacaProperties.java b/src/main/java/net/jacobpeterson/alpaca/properties/AlpacaProperties.java index e89dfdd4..0a768fb0 100644 --- a/src/main/java/net/jacobpeterson/alpaca/properties/AlpacaProperties.java +++ b/src/main/java/net/jacobpeterson/alpaca/properties/AlpacaProperties.java @@ -5,46 +5,31 @@ import static net.jacobpeterson.util.properties.PropertyUtil.getProperty; /** - * The Class {@link AlpacaProperties}. + * {@link AlpacaProperties} defines properties for {@link net.jacobpeterson.alpaca.AlpacaAPI}. */ public class AlpacaProperties { private static final String ALPACA_PROPERTIES_FILE = "alpaca.properties"; - /** The Constant KEY_ID_KEY. */ private static final String KEY_ID_KEY = "key_id"; - - /** The Constant KEY_ID_VALUE. */ public static final String KEY_ID_VALUE = getProperty(ALPACA_PROPERTIES_FILE, KEY_ID_KEY); - /** The Constant SECRET_KEY. */ private static final String SECRET_KEY = "secret"; - - /** The Constant SECRET_VALUE. */ public static final String SECRET_VALUE = getProperty(ALPACA_PROPERTIES_FILE, SECRET_KEY); - /** The Constant BASE_API_URL_KEY. */ private static final String BASE_API_URL_KEY = "base_api_url"; - - /** The Constant BASE_API_URL_VALUE. */ public static final String BASE_API_URL_VALUE = getProperty(ALPACA_PROPERTIES_FILE, BASE_API_URL_KEY); - /** The Constant BASE_DATA_URL_KEY. */ private static final String BASE_DATA_URL_KEY = "base_data_url"; - - /** The base data url value. */ public static final String BASE_DATA_URL_VALUE = getProperty(ALPACA_PROPERTIES_FILE, BASE_DATA_URL_KEY); - /** The Constant USER_AGENT_KEY. */ private static final String USER_AGENT_KEY = "user_agent"; - - /** The Constant USER_AGENT_VALUE. */ public static final String USER_AGENT_VALUE = getProperty(ALPACA_PROPERTIES_FILE, USER_AGENT_KEY); /** - * Static to string string. + * Static {@link #toString()}. * - * @return the string + * @return the static {@link #toString()} */ public static String staticToString() { return new StringJoiner(", ", AlpacaProperties.class.getSimpleName() + "[", "]") diff --git a/src/main/java/net/jacobpeterson/alpaca/rest/AlpacaRequest.java b/src/main/java/net/jacobpeterson/alpaca/rest/AlpacaRequest.java index ae364c2d..29fa29e8 100644 --- a/src/main/java/net/jacobpeterson/alpaca/rest/AlpacaRequest.java +++ b/src/main/java/net/jacobpeterson/alpaca/rest/AlpacaRequest.java @@ -5,20 +5,13 @@ import net.jacobpeterson.alpaca.properties.AlpacaProperties; /** - * The Class {@link AlpacaRequest}. + * {@link AlpacaRequest} contains methods for HTTP requests for {@link net.jacobpeterson.alpaca.AlpacaAPI}. */ public class AlpacaRequest extends AbstractRequest { - /** The Constant USER_AGENT_KEY. */ private static final String USER_AGENT_KEY = "user-agent"; - - /** The Constant API_KEY_ID. */ private static final String API_KEY_ID = "APCA-API-KEY-ID"; - - /** The Constant API_SECRET_KEY. */ private static final String API_SECRET_KEY = "APCA-API-SECRET-KEY"; - - /** The Constant AUTH_TOKEN. */ private static final String AUTH_TOKEN = "Authorization"; /** diff --git a/src/main/java/net/jacobpeterson/alpaca/rest/AlpacaRequestBuilder.java b/src/main/java/net/jacobpeterson/alpaca/rest/AlpacaRequestBuilder.java index 14747796..6d4b7a12 100644 --- a/src/main/java/net/jacobpeterson/alpaca/rest/AlpacaRequestBuilder.java +++ b/src/main/java/net/jacobpeterson/alpaca/rest/AlpacaRequestBuilder.java @@ -5,7 +5,7 @@ import java.util.Arrays; /** - * The Class {@link AlpacaRequestBuilder}. + * {@link AlpacaRequestBuilder} is a builder for URL Strings for {@link net.jacobpeterson.alpaca.AlpacaAPI}. */ public class AlpacaRequestBuilder extends AbstractRequestBuilder { @@ -13,7 +13,7 @@ public class AlpacaRequestBuilder extends AbstractRequestBuilder { * Instantiates a new {@link AlpacaRequestBuilder}. * * @param baseUrl the base url - * @param apiVersion the api version + * @param apiVersion the API version * @param endpoints the endpoints */ public AlpacaRequestBuilder(String baseUrl, String apiVersion, String... endpoints) { diff --git a/src/main/java/net/jacobpeterson/alpaca/rest/exception/AlpacaAPIRequestException.java b/src/main/java/net/jacobpeterson/alpaca/rest/exception/AlpacaAPIRequestException.java index 7bb10544..f52d1e8b 100644 --- a/src/main/java/net/jacobpeterson/alpaca/rest/exception/AlpacaAPIRequestException.java +++ b/src/main/java/net/jacobpeterson/alpaca/rest/exception/AlpacaAPIRequestException.java @@ -6,14 +6,15 @@ import java.io.InputStream; /** - * The Class {@link AlpacaAPIRequestException}. + * {@link AlpacaAPIRequestException} represents {@link HttpResponse} request exceptions for {@link + * net.jacobpeterson.alpaca.AlpacaAPI}. */ public class AlpacaAPIRequestException extends AbstractAPIRequestException { /** * Instantiates a new {@link AlpacaAPIRequestException}. * - * @param httpResponse the http response + * @param httpResponse the {@link HttpResponse} */ public AlpacaAPIRequestException(HttpResponse httpResponse) { super("Alpaca", httpResponse); diff --git a/src/main/java/net/jacobpeterson/alpaca/websocket/broker/client/AlpacaWebsocketClient.java b/src/main/java/net/jacobpeterson/alpaca/websocket/broker/client/AlpacaWebsocketClient.java index 1bc08c9f..baed5ff4 100644 --- a/src/main/java/net/jacobpeterson/alpaca/websocket/broker/client/AlpacaWebsocketClient.java +++ b/src/main/java/net/jacobpeterson/alpaca/websocket/broker/client/AlpacaWebsocketClient.java @@ -33,51 +33,32 @@ import java.util.stream.Collectors; /** - * The type {@link AlpacaWebsocketClient}. + * {@link AlpacaWebsocketClient} represents a client for the {@link net.jacobpeterson.alpaca.AlpacaAPI} Websocket + * stream. */ public class AlpacaWebsocketClient implements WebsocketClient { - /** The logger. */ private static final Logger LOGGER = LoggerFactory.getLogger(AlpacaWebsocketClient.class); - - /** The constant STREAM_KEY. */ private static final String STREAM_KEY = "stream"; - /** The key id. */ - private final String keyId; - - /** The secret. */ + private final String keyID; private final String secret; - - /** The OAuth token. */ private final String oAuthToken; - - /** The Base api url. */ private final String streamAPIURL; - - /** The observers. */ private final List listeners; - /** The client end point. */ private AlpacaWebsocketClientEndpoint alpacaWebsocketClientEndpoint; - - /** The Authenticated. */ private boolean authenticated; /** * Instantiates a new {@link AlpacaWebsocketClient}. * - * @param keyId the key ID + * @param keyID the key ID * @param secret the secret * @param baseAPIURL the base API URL */ - public AlpacaWebsocketClient(String keyId, String secret, String baseAPIURL) { - this.keyId = keyId; - this.secret = secret; - this.oAuthToken = null; - this.streamAPIURL = baseAPIURL.replace("https", "wss") + "/stream"; - - this.listeners = new ArrayList<>(); + public AlpacaWebsocketClient(String keyID, String secret, String baseAPIURL) { + this(keyID, secret, null, baseAPIURL); } /** @@ -87,12 +68,24 @@ public AlpacaWebsocketClient(String keyId, String secret, String baseAPIURL) { * @param baseAPIURL the base API URL */ public AlpacaWebsocketClient(String oAuthToken, String baseAPIURL) { - this.keyId = null; - this.secret = null; + this(null, null, oAuthToken, baseAPIURL); + } + + /** + * Instantiates a new {@link AlpacaWebsocketClient}. + * + * @param keyID the key ID + * @param secret the secret + * @param oAuthToken the OAuth token + * @param baseAPIURL the base API URL + */ + private AlpacaWebsocketClient(String keyID, String secret, String oAuthToken, String baseAPIURL) { + this.keyID = keyID; + this.secret = secret; this.oAuthToken = oAuthToken; this.streamAPIURL = baseAPIURL.replace("https", "wss") + "/stream"; - this.listeners = new ArrayList<>(); + listeners = new ArrayList<>(); } @Override @@ -169,7 +162,7 @@ public void sendAuthenticationMessage() { if (oAuthToken != null) { payload.addProperty("oauth_token", oAuthToken); } else { - payload.addProperty("key_id", keyId); + payload.addProperty("key_id", keyID); payload.addProperty("secret_key", secret); } @@ -261,11 +254,11 @@ public boolean isAuthenticated() { } /** - * Is authorization message success boolean. + * Returns true if {@link AuthorizationMessage} states success. * - * @param authorizationMessage the authorization message + * @param authorizationMessage the {@link AuthorizationMessage} * - * @return the boolean + * @return true if {@link AuthorizationMessage} states success */ private boolean isAuthorizationMessageSuccess(AuthorizationMessage authorizationMessage) { return authorizationMessage.getData().getStatus().equalsIgnoreCase("authorized") && @@ -273,7 +266,7 @@ private boolean isAuthorizationMessageSuccess(AuthorizationMessage authorization } /** - * Submit stream request. + * Submits a stream request. */ private void submitStreamRequestUpdate() { // Stream request example: @@ -302,9 +295,9 @@ private void submitStreamRequestUpdate() { } /** - * Gets the registered message types. + * Gets the registered {@link AlpacaStreamMessageType}. * - * @return the registered message types + * @return the registered {@link AlpacaStreamMessageType} */ public Set getRegisteredMessageTypes() { Set registeredStreamMessageTypes = new HashSet<>(); diff --git a/src/main/java/net/jacobpeterson/alpaca/websocket/broker/client/AlpacaWebsocketClientEndpoint.java b/src/main/java/net/jacobpeterson/alpaca/websocket/broker/client/AlpacaWebsocketClientEndpoint.java index 8ae7fbe1..b72d21ae 100644 --- a/src/main/java/net/jacobpeterson/alpaca/websocket/broker/client/AlpacaWebsocketClientEndpoint.java +++ b/src/main/java/net/jacobpeterson/alpaca/websocket/broker/client/AlpacaWebsocketClientEndpoint.java @@ -14,7 +14,8 @@ import java.nio.charset.StandardCharsets; /** - * The type {@link AlpacaWebsocketClientEndpoint}. + * {@link AlpacaWebsocketClientEndpoint} is used for handling a Websocket directly for {@link + * net.jacobpeterson.alpaca.AlpacaAPI}. */ @ClientEndpoint(subprotocols = "BINARY") public class AlpacaWebsocketClientEndpoint extends AbstractWebsocketClientEndpoint { @@ -22,8 +23,8 @@ public class AlpacaWebsocketClientEndpoint extends AbstractWebsocketClientEndpoi /** * Instantiates a new {@link AlpacaWebsocketClientEndpoint}. * - * @param websocketClient the websocket client - * @param endpointURI the endpoint uri + * @param websocketClient the {@link WebsocketClient} + * @param endpointURI the endpoint {@link URI} */ public AlpacaWebsocketClientEndpoint(WebsocketClient websocketClient, URI endpointURI) { super(websocketClient, endpointURI, "AlpacaWebsocketThread"); diff --git a/src/main/java/net/jacobpeterson/alpaca/websocket/broker/listener/AlpacaStreamListener.java b/src/main/java/net/jacobpeterson/alpaca/websocket/broker/listener/AlpacaStreamListener.java index 752cbcaf..ef55d49e 100644 --- a/src/main/java/net/jacobpeterson/alpaca/websocket/broker/listener/AlpacaStreamListener.java +++ b/src/main/java/net/jacobpeterson/alpaca/websocket/broker/listener/AlpacaStreamListener.java @@ -13,9 +13,10 @@ public interface AlpacaStreamListener extends StreamListener { /** - * Gets the stream message types for this listener. Null or empty to listen to all other stream messages. + * Gets the {@link AlpacaStreamMessageType}s for this {@link AlpacaStreamListener}. Null or empty to listen to all + * other stream messages. * - * @return the stream message types + * @return the {@link AlpacaStreamMessageType}s */ Set getStreamMessageTypes(); } diff --git a/src/main/java/net/jacobpeterson/alpaca/websocket/broker/listener/AlpacaStreamListenerAdapter.java b/src/main/java/net/jacobpeterson/alpaca/websocket/broker/listener/AlpacaStreamListenerAdapter.java index 0cab6413..facd5458 100644 --- a/src/main/java/net/jacobpeterson/alpaca/websocket/broker/listener/AlpacaStreamListenerAdapter.java +++ b/src/main/java/net/jacobpeterson/alpaca/websocket/broker/listener/AlpacaStreamListenerAdapter.java @@ -8,19 +8,20 @@ import java.util.Set; /** - * The Class {@link AlpacaStreamListenerAdapter}. + * {@link AlpacaStreamListenerAdapter} is an adapter for {@link AlpacaStreamListener}. */ public class AlpacaStreamListenerAdapter implements AlpacaStreamListener { - /** The message types. */ - private final HashSet streamUpdateTypes = new HashSet<>(); + private final HashSet streamUpdateTypes; /** * Instantiates a new {@link AlpacaStreamListenerAdapter}. * - * @param streamUpdateTypes the stream update types + * @param streamUpdateTypes the {@link AlpacaStreamMessageType}s */ public AlpacaStreamListenerAdapter(AlpacaStreamMessageType... streamUpdateTypes) { + this.streamUpdateTypes = new HashSet<>(); + if (streamUpdateTypes != null && streamUpdateTypes.length > 0) { this.streamUpdateTypes.addAll(Arrays.asList(streamUpdateTypes)); } @@ -32,6 +33,5 @@ public Set getStreamMessageTypes() { } @Override - public void onStreamUpdate(AlpacaStreamMessageType streamMessageType, AlpacaStreamMessage streamMessage) { - } + public void onStreamUpdate(AlpacaStreamMessageType streamMessageType, AlpacaStreamMessage streamMessage) { } } diff --git a/src/main/java/net/jacobpeterson/alpaca/websocket/broker/message/AlpacaStreamMessageType.java b/src/main/java/net/jacobpeterson/alpaca/websocket/broker/message/AlpacaStreamMessageType.java index 5f389ab7..0a68e4d9 100644 --- a/src/main/java/net/jacobpeterson/alpaca/websocket/broker/message/AlpacaStreamMessageType.java +++ b/src/main/java/net/jacobpeterson/alpaca/websocket/broker/message/AlpacaStreamMessageType.java @@ -6,33 +6,33 @@ import net.jacobpeterson.util.gson.GsonUtil; /** - * The enum {@link AlpacaStreamMessageType}. + * {@link AlpacaStreamMessageType} defines enums for various message types for {@link + * net.jacobpeterson.alpaca.websocket.broker.listener.AlpacaStreamListener}. */ public enum AlpacaStreamMessageType implements StreamMessageType, APIName { - /** Listening {@link AlpacaStreamMessageType}. */ + /** The listening status {@link AlpacaStreamMessageType}. */ @SerializedName("listening") LISTENING(false), - /** Authorization {@link AlpacaStreamMessageType}. */ + /** The authorization status {@link AlpacaStreamMessageType}. */ @SerializedName("authorization") AUTHORIZATION(false), - /** Trade updates {@link AlpacaStreamMessageType}. */ + /** The trade updates {@link AlpacaStreamMessageType}. */ @SerializedName("trade_updates") TRADE_UPDATES(true), - /** Account updates {@link AlpacaStreamMessageType}. */ + /** The account updates {@link AlpacaStreamMessageType}. */ @SerializedName("account_updates") ACCOUNT_UPDATES(true); - /** Is API subscribable. */ private final boolean isAPISubscribable; /** * Instantiates a new {@link AlpacaStreamMessageType}. * - * @param isAPISubscribable the is api subscribable + * @param isAPISubscribable true if API subscribable */ AlpacaStreamMessageType(boolean isAPISubscribable) { this.isAPISubscribable = isAPISubscribable; @@ -44,9 +44,9 @@ public String getAPIName() { } /** - * Is the API subscribable. + * Returns true if API subscribable. * - * @return the boolean + * @return true if API subscribable */ public boolean isAPISubscribable() { return isAPISubscribable; diff --git a/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/client/MarketDataWebsocketClient.java b/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/client/MarketDataWebsocketClient.java index 6a189daf..13e3eff8 100644 --- a/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/client/MarketDataWebsocketClient.java +++ b/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/client/MarketDataWebsocketClient.java @@ -11,6 +11,7 @@ import net.jacobpeterson.abstracts.websocket.listener.StreamListener; import net.jacobpeterson.abstracts.websocket.message.StreamMessage; import net.jacobpeterson.abstracts.websocket.message.StreamMessageType; +import net.jacobpeterson.alpaca.websocket.broker.client.AlpacaWebsocketClient; import net.jacobpeterson.alpaca.websocket.marketdata.listener.MarketDataStreamListener; import net.jacobpeterson.alpaca.websocket.marketdata.message.MarketDataStreamMessageType; import net.jacobpeterson.domain.alpaca.marketdata.streaming.MarketDataStreamMessage; @@ -37,87 +38,69 @@ import java.util.stream.Collectors; /** - * The type {@link MarketDataWebsocketClient}. + * {@link MarketDataWebsocketClient} represents a client for the {@link net.jacobpeterson.alpaca.AlpacaAPI} market data + * Websocket stream. */ public class MarketDataWebsocketClient implements WebsocketClient { - /** The logger. */ private static final Logger LOGGER = LoggerFactory.getLogger(MarketDataWebsocketClient.class); - /** The constant STREAM_KEY. */ private static final String STREAM_KEY = "stream"; - - /** The constant EVENT_TYPE_KEY. */ private static final String EVENT_TYPE_KEY = "ev"; - - /** The constant EVENT_TYPE_KEY. */ private static final String DATA_KEY = "data"; - - /** The constant ALL_TICKERS. */ private static final String ALL_TICKERS = "*"; - - /** The constant TRADES_STREAM_PREFIX. */ private static final String TRADES_STREAM_PREFIX = GsonUtil.GSON.toJson(MarketDataStreamMessageType.TRADES).replace("\"", "") + "."; - - /** The constant QUOTES_STREAM_PREFIX. */ private static final String QUOTES_STREAM_PREFIX = GsonUtil.GSON.toJson(MarketDataStreamMessageType.QUOTES).replace("\"", "") + "."; - - /** The constant AGGREGATE_MINUTE_STREAM_PREFIX. */ private static final String AGGREGATE_MINUTE_STREAM_PREFIX = GsonUtil.GSON.toJson(MarketDataStreamMessageType.AGGREGATE_MINUTE).replace("\"", "") + "."; - /** The key id. */ - private final String keyId; - - /** The secret. */ + private final String keyID; private final String secret; - - /** The OAuth token. */ private final String oAuthToken; - - /** The Stream API URL. */ private final String streamAPIURL; - - /** The observers. */ private final List listeners; - /** The client end point. */ private MarketDataWebsocketClientEndpoint marketDataWebsocketClientEndpoint; - - /** The Authenticated. */ private boolean authenticated; /** - * Instantiates a new {@link MarketDataWebsocketClient}. + * Instantiates a new {@link AlpacaWebsocketClient}. * - * @param keyId the key id + * @param keyID the key ID * @param secret the secret - * @param baseDataUrl the base data url + * @param baseDataUrl the base data URL */ - public MarketDataWebsocketClient(String keyId, String secret, String baseDataUrl) { - this.keyId = keyId; - this.secret = secret; - this.oAuthToken = null; - this.streamAPIURL = baseDataUrl.replace("https", "wss") + "/stream"; + public MarketDataWebsocketClient(String keyID, String secret, String baseDataUrl) { + this(keyID, secret, null, baseDataUrl); + } - this.listeners = new ArrayList<>(); + /** + * Instantiates a new {@link AlpacaWebsocketClient}. + * + * @param oAuthToken the OAuth token + * @param baseAPIURL the base API URL + */ + public MarketDataWebsocketClient(String oAuthToken, String baseAPIURL) { + this(null, null, oAuthToken, baseAPIURL); } /** - * Instantiates a new {@link MarketDataWebsocketClient}. + * Instantiates a new {@link AlpacaWebsocketClient}. * + * @param keyID the key ID + * @param secret the secret * @param oAuthToken the OAuth token - * @param baseDataUrl the base data url + * @param baseDataUrl the base data URL */ - public MarketDataWebsocketClient(String oAuthToken, String baseDataUrl) { - this.keyId = null; - this.secret = null; + private MarketDataWebsocketClient(String keyID, String secret, String oAuthToken, String baseDataUrl) { + this.keyID = keyID; + this.secret = secret; this.oAuthToken = oAuthToken; this.streamAPIURL = baseDataUrl.replace("https", "wss") + "/stream"; - this.listeners = new ArrayList<>(); + listeners = new ArrayList<>(); } @Override @@ -193,7 +176,7 @@ public void sendAuthenticationMessage() { if (oAuthToken != null) { dataJson.addProperty("oauth_token", oAuthToken); } else { - dataJson.addProperty("key_id", keyId); + dataJson.addProperty("key_id", keyID); dataJson.addProperty("secret_key", secret); } @@ -356,11 +339,11 @@ public boolean isAuthenticated() { } /** - * Is authorization message success boolean. + * Returns true if {@link AuthorizationMessage} states success. * - * @param authorizationMessage the authorization message + * @param authorizationMessage the {@link AuthorizationMessage} * - * @return the boolean + * @return true if {@link AuthorizationMessage} states success */ private boolean isAuthorizationMessageSuccess(AuthorizationMessage authorizationMessage) { return authorizationMessage.getData().getStatus().equalsIgnoreCase("authorized") && @@ -368,7 +351,7 @@ private boolean isAuthorizationMessageSuccess(AuthorizationMessage authorization } /** - * Submit stream request. + * Submits a stream request. */ private void submitStreamRequest() { /* Stream request example: @@ -397,9 +380,9 @@ private void submitStreamRequest() { } /** - * Gets the registered message types. May contain duplicates. + * Gets the registered {@link MarketDataStreamMessageType}s of tickers. May contain duplicates. * - * @return the registered message types + * @return the registered {@link MarketDataStreamMessageType}s of tickers */ public HashMap> getRegisteredMessageTypes() { HashMap> allMarketDataStreamMessageTypes = new HashMap<>(); diff --git a/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/client/MarketDataWebsocketClientEndpoint.java b/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/client/MarketDataWebsocketClientEndpoint.java index b39a2751..c2c7cab2 100644 --- a/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/client/MarketDataWebsocketClientEndpoint.java +++ b/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/client/MarketDataWebsocketClientEndpoint.java @@ -13,7 +13,8 @@ import java.net.URI; /** - * The type {@link MarketDataWebsocketClientEndpoint}. + * {@link MarketDataWebsocketClientEndpoint} is used for handling a Websocket directly for {@link + * net.jacobpeterson.alpaca.AlpacaAPI} market data. */ @ClientEndpoint(subprotocols = "STRING") public class MarketDataWebsocketClientEndpoint extends AbstractWebsocketClientEndpoint { @@ -21,8 +22,8 @@ public class MarketDataWebsocketClientEndpoint extends AbstractWebsocketClientEn /** * Instantiates a new {@link MarketDataWebsocketClientEndpoint}. * - * @param websocketClient the websocket client - * @param endpointURI the endpoint uri + * @param websocketClient the {@link WebsocketClient} + * @param endpointURI the endpoint {@link URI} */ public MarketDataWebsocketClientEndpoint(WebsocketClient websocketClient, URI endpointURI) { super(websocketClient, endpointURI, "MarketDataWebsocketThread"); diff --git a/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/listener/MarketDataStreamListener.java b/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/listener/MarketDataStreamListener.java index 8bb0bc4c..03bd0f40 100644 --- a/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/listener/MarketDataStreamListener.java +++ b/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/listener/MarketDataStreamListener.java @@ -14,9 +14,10 @@ public interface MarketDataStreamListener extends StreamListener { /** - * Gets the data streams for this listener. Null or empty to listen to all stream messages. + * Gets the {@link MarketDataStreamMessageType} of tickers {@link Map} for this {@link MarketDataStreamListener}. + * Null or empty to listen to all stream messages. * - * @return the stream message types + * @return the {@link MarketDataStreamMessageType} of tickers {@link Map} */ Map> getDataStreams(); } diff --git a/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/listener/MarketDataStreamListenerAdapter.java b/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/listener/MarketDataStreamListenerAdapter.java index 18c73700..34167cf1 100644 --- a/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/listener/MarketDataStreamListenerAdapter.java +++ b/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/listener/MarketDataStreamListenerAdapter.java @@ -10,18 +10,17 @@ import java.util.Set; /** - * The type {@link MarketDataStreamListenerAdapter}. + * {@link MarketDataStreamListenerAdapter} is an adapter for {@link MarketDataStreamListener}. */ public class MarketDataStreamListenerAdapter implements MarketDataStreamListener { - /** The Data streams. */ private final Map> dataStreams = new HashMap<>(); /** * Instantiates a new {@link MarketDataStreamListenerAdapter}. * * @param ticker the ticker - * @param dataMessageTypes the data message types + * @param dataMessageTypes the {@link MarketDataStreamMessageType}s */ public MarketDataStreamListenerAdapter(String ticker, MarketDataStreamMessageType... dataMessageTypes) { if (dataMessageTypes != null && dataMessageTypes.length > 0) { @@ -35,7 +34,7 @@ public MarketDataStreamListenerAdapter(String ticker, MarketDataStreamMessageTyp * Instantiates a new {@link MarketDataStreamListenerAdapter}. * * @param tickers the tickers - * @param dataMessageTypes the data message types + * @param dataMessageTypes the {@link MarketDataStreamMessageType}s */ public MarketDataStreamListenerAdapter(Set tickers, MarketDataStreamMessageType... dataMessageTypes) { for (String ticker : tickers) { @@ -50,12 +49,10 @@ public MarketDataStreamListenerAdapter(Set tickers, MarketDataStreamMess /** * Instantiates a new {@link MarketDataStreamListenerAdapter}. * - * @param dataStreams the data streams + * @param dataStreams the {@link MarketDataStreamMessageType}s of the ticker {@link Map} */ public MarketDataStreamListenerAdapter(Map> dataStreams) { - for (Map.Entry> entry : dataStreams.entrySet()) { - dataStreams.put(entry.getKey(), new HashSet<>(entry.getValue())); - } + dataStreams.replaceAll((k, v) -> new HashSet<>(v)); } @Override @@ -64,6 +61,5 @@ public Map> getDataStreams() { } @Override - public void onStreamUpdate(MarketDataStreamMessageType streamMessageType, MarketDataStreamMessage streamMessage) { - } + public void onStreamUpdate(MarketDataStreamMessageType streamMessageType, MarketDataStreamMessage streamMessage) { } } diff --git a/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/message/MarketDataStreamMessageType.java b/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/message/MarketDataStreamMessageType.java index f9649ba3..ff90d09d 100644 --- a/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/message/MarketDataStreamMessageType.java +++ b/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/message/MarketDataStreamMessageType.java @@ -6,37 +6,37 @@ import net.jacobpeterson.util.gson.GsonUtil; /** - * The enum {@link MarketDataStreamMessageType}. + * {@link MarketDataStreamMessageType} defines enums for various message types for {@link + * net.jacobpeterson.alpaca.websocket.marketdata.listener.MarketDataStreamListener}. */ public enum MarketDataStreamMessageType implements StreamMessageType, APIName { - /** Listening {@link MarketDataStreamMessageType}. */ + /** The listening status {@link MarketDataStreamMessageType}. */ @SerializedName("listening") LISTENING(false), - /** Authorization {@link MarketDataStreamMessageType}. */ + /** The authorization status {@link MarketDataStreamMessageType}. */ @SerializedName("authorization") AUTHORIZATION(false), - /** Trades {@link MarketDataStreamMessageType}. */ + /** The trades {@link MarketDataStreamMessageType}. */ @SerializedName("T") TRADES(true), - /** Quotes {@link MarketDataStreamMessageType}. */ + /** The quotes {@link MarketDataStreamMessageType}. */ @SerializedName("Q") QUOTES(true), - /** Aggregate {@link MarketDataStreamMessageType}. */ + /** The aggregate minute {@link MarketDataStreamMessageType}. */ @SerializedName("AM") AGGREGATE_MINUTE(true); - /** The is api subscribable. */ private final boolean isAPISubscribable; /** * Instantiates a new {@link MarketDataStreamMessageType}. * - * @param isAPISubscribable the is api subscribable + * @param isAPISubscribable true if API subscribable */ MarketDataStreamMessageType(boolean isAPISubscribable) { this.isAPISubscribable = isAPISubscribable; @@ -48,9 +48,9 @@ public String getAPIName() { } /** - * Is the API subscribable. + * Returns true if API subscribable. * - * @return the boolean + * @return true if API subscribable */ public boolean isAPISubscribable() { return isAPISubscribable; diff --git a/src/main/java/net/jacobpeterson/polygon/properties/PolygonProperties.java b/src/main/java/net/jacobpeterson/polygon/properties/PolygonProperties.java index fcd506ac..4057f049 100644 --- a/src/main/java/net/jacobpeterson/polygon/properties/PolygonProperties.java +++ b/src/main/java/net/jacobpeterson/polygon/properties/PolygonProperties.java @@ -7,44 +7,31 @@ import static net.jacobpeterson.util.properties.PropertyUtil.getProperty; /** - * The Class {@link PolygonProperties}. + * {@link PolygonProperties} defines properties for {@link net.jacobpeterson.polygon.PolygonAPI}. */ public class PolygonProperties { - /** The constant POLYGON_PROPERTIES_FILE. */ private static final String POLYGON_PROPERTIES_FILE = "polygon.properties"; - /** The Constant KEY_ID_KEY. */ private static final String KEY_ID_KEY = "key_id"; - - /** The Constant KEY_ID_VALUE. */ public static final String KEY_ID_VALUE = getProperty(POLYGON_PROPERTIES_FILE, KEY_ID_KEY, AlpacaProperties.KEY_ID_VALUE); - /** The Constant BASE_API_URL_KEY. */ private static final String BASE_API_URL_KEY = "base_api_url"; - - /** The Constant BASE_API_URL_VALUE. */ public static final String BASE_API_URL_VALUE = getProperty(POLYGON_PROPERTIES_FILE, BASE_API_URL_KEY); - /** The polygon web socket server url key. */ private static final String POLYGON_WEB_SOCKET_SERVER_URL_KEY = "web_socket_server_url"; - - /** The polygon web socket server url value. */ public static final String POLYGON_WEB_SOCKET_SERVER_URL_VALUE = getProperty(POLYGON_PROPERTIES_FILE, POLYGON_WEB_SOCKET_SERVER_URL_KEY); - /** The Constant USER_AGENT_KEY. */ private static final String USER_AGENT_KEY = "user_agent"; - - /** The Constant USER_AGENT_VALUE. */ public static final String USER_AGENT_VALUE = getProperty(POLYGON_PROPERTIES_FILE, USER_AGENT_KEY); /** - * Static to string. + * Static {@link #toString()}. * - * @return the string + * @return the static {@link #toString()} */ public static String staticToString() { return new StringJoiner(", ", PolygonProperties.class.getSimpleName() + "[", "]") diff --git a/src/main/java/net/jacobpeterson/polygon/rest/PolygonRequest.java b/src/main/java/net/jacobpeterson/polygon/rest/PolygonRequest.java index c82d47fd..963d7a44 100644 --- a/src/main/java/net/jacobpeterson/polygon/rest/PolygonRequest.java +++ b/src/main/java/net/jacobpeterson/polygon/rest/PolygonRequest.java @@ -8,7 +8,7 @@ import java.io.InputStream; /** - * The Class {@link PolygonRequest}. + * {@link PolygonRequest} contains methods for HTTP requests for {@link net.jacobpeterson.polygon.PolygonAPI}. */ public class PolygonRequest extends AbstractRequest { diff --git a/src/main/java/net/jacobpeterson/polygon/rest/PolygonRequestBuilder.java b/src/main/java/net/jacobpeterson/polygon/rest/PolygonRequestBuilder.java index 8e93798c..708d0bb5 100644 --- a/src/main/java/net/jacobpeterson/polygon/rest/PolygonRequestBuilder.java +++ b/src/main/java/net/jacobpeterson/polygon/rest/PolygonRequestBuilder.java @@ -5,7 +5,7 @@ import java.util.Arrays; /** - * The Class {@link PolygonRequestBuilder}. + * {@link PolygonRequestBuilder} is a builder for URL Strings for {@link net.jacobpeterson.polygon.PolygonAPI}. */ public class PolygonRequestBuilder extends AbstractRequestBuilder { diff --git a/src/main/java/net/jacobpeterson/polygon/rest/exception/PolygonAPIRequestException.java b/src/main/java/net/jacobpeterson/polygon/rest/exception/PolygonAPIRequestException.java index 6bdbcdc1..12db199b 100644 --- a/src/main/java/net/jacobpeterson/polygon/rest/exception/PolygonAPIRequestException.java +++ b/src/main/java/net/jacobpeterson/polygon/rest/exception/PolygonAPIRequestException.java @@ -6,14 +6,15 @@ import java.io.InputStream; /** - * The Class {@link PolygonAPIRequestException}. + * {@link PolygonAPIRequestException} represents {@link HttpResponse} request exceptions for {@link + * net.jacobpeterson.polygon.PolygonAPI}. */ public class PolygonAPIRequestException extends AbstractAPIRequestException { /** * Instantiates a new {@link PolygonAPIRequestException}. * - * @param httpResponse the http response + * @param httpResponse the {@link HttpResponse} */ public PolygonAPIRequestException(HttpResponse httpResponse) { super("Polygon", httpResponse); From 056a5bfaabbf5b6d4cd64af15ebbe055a66a84fa Mon Sep 17 00:00:00 2001 From: Jacob Peterson Date: Thu, 7 Jan 2021 21:56:40 -0700 Subject: [PATCH 074/271] Add live unit test for getAccount() --- .gitignore | 2 + README.md | 17 ++- build.gradle | 31 +++++- .../AbstractWebsocketClientEndpoint.java | 19 ++-- .../alpaca/AlpacaAPIEndpointTest.java | 102 ++++++++++++++++++ 5 files changed, 159 insertions(+), 12 deletions(-) create mode 100644 src/test/java/live/net/jacobpeterson/alpaca/AlpacaAPIEndpointTest.java diff --git a/.gitignore b/.gitignore index 12a5664a..6a517410 100644 --- a/.gitignore +++ b/.gitignore @@ -4,3 +4,5 @@ build/ .settings .DS_Store .idea/ +alpaca.properties +polygon.properties diff --git a/README.md b/README.md index 72a641d3..aed80d66 100644 --- a/README.md +++ b/README.md @@ -32,7 +32,8 @@ This is a Java implementation for the Alpaca - * NOTES: You MUST annotate a subclass with @see and the appropriate websocket sub-protocols because Websocket - * annotations don't work with inheritance. The subclass must also contain separate methods with the following - * annotations: {@link javax.websocket.OnOpen}, {@link javax.websocket.OnClose}, {@link javax.websocket.OnMessage}, and - * {@link javax.websocket.OnError}. + * NOTES: You MUST annotate a subclass's {@link #onOpen(Session)}, {@link #onClose(Session, CloseReason)}, {@link + * #onMessage(String)}, and {@link #onError(Throwable)} with {@link javax.websocket.OnOpen}, {@link + * javax.websocket.OnClose}, {@link javax.websocket.OnMessage}, and {@link javax.websocket.OnError} respectively and + * with the appropriate websocket sub-protocols because Websocket annotations don't work with inheritance. */ public abstract class AbstractWebsocketClientEndpoint { @@ -112,10 +113,10 @@ protected void onOpen(Session userSession) { protected void onClose(Session userSession, CloseReason reason) { LOGGER.debug("onClose {}", userSession); - if (!reason.getCloseCode().equals(CloseReason.CloseCodes.NORMAL_CLOSURE) && automaticallyReconnect) { + if (!reason.getCloseCode().equals(CloseCodes.NORMAL_CLOSURE) && automaticallyReconnect) { LOGGER.info("Reconnecting due to closure: {}", - CloseReason.CloseCodes.getCloseCode(reason.getCloseCode().getCode())); + CloseCodes.getCloseCode(reason.getCloseCode().getCode())); try { connect(); @@ -168,10 +169,10 @@ public Session getUserSession() { /** * Returns true if the Websocket is automatically reconnected except when {@link CloseReason} is {@link - * CloseReason.CloseCodes#NORMAL_CLOSURE}. + * CloseCodes#NORMAL_CLOSURE}. * * @return true if the Websocket is automatically reconnected except when {@link CloseReason} is {@link - * CloseReason.CloseCodes#NORMAL_CLOSURE}. + * CloseCodes#NORMAL_CLOSURE}. */ public boolean doesAutomaticallyReconnect() { return automaticallyReconnect; @@ -179,7 +180,7 @@ public boolean doesAutomaticallyReconnect() { /** * Sets the Websocket to automatically reconnected except when {@link CloseReason} is {@link - * CloseReason.CloseCodes#NORMAL_CLOSURE}. + * CloseCodes#NORMAL_CLOSURE}. * * @param automaticallyReconnect true to automatically reconnect */ diff --git a/src/test/java/live/net/jacobpeterson/alpaca/AlpacaAPIEndpointTest.java b/src/test/java/live/net/jacobpeterson/alpaca/AlpacaAPIEndpointTest.java new file mode 100644 index 00000000..86c2ff1a --- /dev/null +++ b/src/test/java/live/net/jacobpeterson/alpaca/AlpacaAPIEndpointTest.java @@ -0,0 +1,102 @@ +package live.net.jacobpeterson.alpaca; + +import net.jacobpeterson.alpaca.AlpacaAPI; +import net.jacobpeterson.alpaca.rest.exception.AlpacaAPIRequestException; +import net.jacobpeterson.domain.alpaca.account.Account; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.MethodOrderer; +import org.junit.jupiter.api.Order; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestMethodOrder; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.fail; + +@TestMethodOrder(MethodOrderer.OrderAnnotation.class) +public class AlpacaAPIEndpointTest { + + private static final Logger LOGGER = LoggerFactory.getLogger(AlpacaAPIEndpointTest.class); + private static final int RATE_LIMIT_MILLIS = 200; // Wait 200ms between every test to prevent rate-limiting + + static { + // Log trace-level + System.setProperty("org.slf4j.simpleLogger.log.net.jacobpeterson", "trace"); + System.setProperty("org.slf4j.simpleLogger.log.live.net.jacobpeterson", "trace"); + } + + @BeforeEach + public void beforeEach() { + } + + @AfterEach + public void afterEach() { + try { + Thread.sleep(RATE_LIMIT_MILLIS); + } catch (InterruptedException exception) { + fail("Interrupted when tearing down!"); + } + } + + /** + * Tests {@link AlpacaAPI#getAccount()}. + * + * @throws AlpacaAPIRequestException thrown for {@link AlpacaAPIRequestException}s + * @throws NumberFormatException thrown for {@link NumberFormatException}s + */ + @Test + @Order(1) + public void testGetAccount() throws AlpacaAPIRequestException, NumberFormatException { + AlpacaAPI alpacaAPI = new AlpacaAPI(); + + Account account = alpacaAPI.getAccount(); + + LOGGER.debug(account.toString()); + + // Assert basic data exists + assertNotNull(account.getId()); + assertNotNull(account.getAccountNumber()); + assertNotNull(account.getStatus()); + assertNotNull(account.getCurrency()); + assertNotNull(account.getCash()); + assertNotNull(account.getPortfolioValue()); + assertNotNull(account.getPatternDayTrader()); + assertNotNull(account.getTradeSuspendedByUser()); + assertNotNull(account.getTradingBlocked()); + assertNotNull(account.getTransfersBlocked()); + assertNotNull(account.getAccountBlocked()); + assertNotNull(account.getCreatedAt()); + assertNotNull(account.getShortingEnabled()); + assertNotNull(account.getLongMarketValue()); + assertNotNull(account.getShortMarketValue()); + assertNotNull(account.getEquity()); + assertNotNull(account.getLastEquity()); + assertNotNull(account.getMultiplier()); + assertNotNull(account.getBuyingPower()); + assertNotNull(account.getInitialMargin()); + assertNotNull(account.getMaintenanceMargin()); + assertNotNull(account.getSma()); + assertNotNull(account.getDaytradeCount()); + assertNotNull(account.getLastMaintenanceMargin()); + assertNotNull(account.getDaytradingBuyingPower()); + assertNotNull(account.getRegtBuyingPower()); + + // Assert basic data integrity + Double.parseDouble(account.getCash()); + Double.parseDouble(account.getEquity()); + Double.parseDouble(account.getCash()); + Double.parseDouble(account.getPortfolioValue()); + Double.parseDouble(account.getLongMarketValue()); + Double.parseDouble(account.getShortMarketValue()); + Double.parseDouble(account.getEquity()); + Double.parseDouble(account.getLastEquity()); + Double.parseDouble(account.getBuyingPower()); + Double.parseDouble(account.getInitialMargin()); + Double.parseDouble(account.getMaintenanceMargin()); + Double.parseDouble(account.getLastMaintenanceMargin()); + Double.parseDouble(account.getDaytradingBuyingPower()); + Double.parseDouble(account.getRegtBuyingPower()); + } +} From 6dc87b783f9dca304bc4638d898f83616aa5b5d6 Mon Sep 17 00:00:00 2001 From: Jacob Peterson Date: Thu, 7 Jan 2021 22:12:24 -0700 Subject: [PATCH 075/271] Add constructor unit test for AlpacaAPI --- .../alpaca/AlpacaAPIEndpointTest.java | 8 +++ .../jacobpeterson/alpaca/AlpacaAPITest.java | 65 +++++++++++++++++++ 2 files changed, 73 insertions(+) create mode 100644 src/test/java/mock/net/jacobpeterson/alpaca/AlpacaAPITest.java diff --git a/src/test/java/live/net/jacobpeterson/alpaca/AlpacaAPIEndpointTest.java b/src/test/java/live/net/jacobpeterson/alpaca/AlpacaAPIEndpointTest.java index 86c2ff1a..a3405a53 100644 --- a/src/test/java/live/net/jacobpeterson/alpaca/AlpacaAPIEndpointTest.java +++ b/src/test/java/live/net/jacobpeterson/alpaca/AlpacaAPIEndpointTest.java @@ -15,6 +15,10 @@ import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.fail; +/** + * {@link AlpacaAPIEndpointTest} tests live endpoints using Alpaca Paper credentials given in the + * alpaca.properties file on the classpath. + */ @TestMethodOrder(MethodOrderer.OrderAnnotation.class) public class AlpacaAPIEndpointTest { @@ -31,6 +35,10 @@ public class AlpacaAPIEndpointTest { public void beforeEach() { } + /** + * Executed after each test. Note that this will {@link Thread#sleep(long)} for {@link #RATE_LIMIT_MILLIS} to + * protect against rate limiting. + */ @AfterEach public void afterEach() { try { diff --git a/src/test/java/mock/net/jacobpeterson/alpaca/AlpacaAPITest.java b/src/test/java/mock/net/jacobpeterson/alpaca/AlpacaAPITest.java new file mode 100644 index 00000000..f49abdc7 --- /dev/null +++ b/src/test/java/mock/net/jacobpeterson/alpaca/AlpacaAPITest.java @@ -0,0 +1,65 @@ +package mock.net.jacobpeterson.alpaca; + +import net.jacobpeterson.alpaca.AlpacaAPI; +import org.junit.jupiter.api.Test; + +/** + * {@link AlpacaAPITest} tests {@link AlpacaAPI} using mocked objects with Mockito. + */ +public class AlpacaAPITest { + + /** + * Tests {@link AlpacaAPI#AlpacaAPI()}. + */ + @Test + public void testAlpacaAPIConstructor_Default() { + new AlpacaAPI(); + } + + /** + * Tests {@link AlpacaAPI#AlpacaAPI(String, String)}. + */ + @Test + public void testAlpacaAPIConstructor_keyID_secret() { + String keyID = "ABCDEFGHIJKLM"; + String secret = "NOPQURSTUVWXYZ"; + + new AlpacaAPI(keyID, secret); + } + + /** + * Tests {@link AlpacaAPI#AlpacaAPI(String, String, String)}. + */ + @Test + public void testAlpacaAPIConstructor_keyID_secret_baseAPIURL() { + String keyID = "ABCDEFGHIJKLM"; + String secret = "NOPQURSTUVWXYZ"; + String baseAPIURL = "https://paper-api.foo.bar"; + + new AlpacaAPI(keyID, secret, baseAPIURL); + } + + /** + * Tests {@link AlpacaAPI#AlpacaAPI(String)}. + */ + @Test + public void testAlpacaAPIConstructor_oAuthToken() { + String oAuthToken = "ABCDEFGHIJKLMNOPQURSTUVWXYZ"; + + new AlpacaAPI(oAuthToken); + } + + /** + * Tests {@link AlpacaAPI#AlpacaAPI(String, String, String, String, String)}. + */ + @Test + public void testAlpacaAPIConstructor_keyID_secret_oAuthToken_baseAPIURL_baseDataURL() { + String keyID = "ABCDEFGHIJKLM"; + String secret = "NOPQURSTUVWXYZ"; + String oAuthToken = "ABCDEFGHIJKLMNOPQURSTUVWXYZ"; + String baseAPIURL = "https://paper-api.foo.bar"; + String baseDataURL = "https://data.foo.bar"; + + new AlpacaAPI(keyID, secret, oAuthToken, baseAPIURL, baseDataURL); + } +} From f40b425363ba2d880c6b98e10a32c47a30454d8e Mon Sep 17 00:00:00 2001 From: Jacob Peterson Date: Thu, 7 Jan 2021 23:14:31 -0700 Subject: [PATCH 076/271] Fix mock unit test and add Jacoco/CodeCov --- .travis.yml | 9 ++++++- build.gradle | 25 ++++++++++++++----- .../alpaca/AlpacaAPIEndpointTest.java | 1 + src/test/resources/alpaca.properties.default | 5 ++++ src/test/resources/polygon.properties.default | 4 +++ 5 files changed, 37 insertions(+), 7 deletions(-) create mode 100644 src/test/resources/alpaca.properties.default create mode 100644 src/test/resources/polygon.properties.default diff --git a/.travis.yml b/.travis.yml index 8168baab..c5fc7ad8 100644 --- a/.travis.yml +++ b/.travis.yml @@ -5,8 +5,15 @@ dist: trusty jdk: - oraclejdk8 +before_script: + - chmod +x gradlew + script: - - ./gradlew build -s + - ./gradlew build check --stacktrace + - ./gradlew codeCoverageReport + +after_success: + - bash <(curl -s https://codecov.io/bash) before_cache: - rm -f $HOME/.gradle/caches/modules-2/modules-2.lock diff --git a/build.gradle b/build.gradle index d1b33953..e7aa7940 100644 --- a/build.gradle +++ b/build.gradle @@ -15,9 +15,13 @@ buildscript { plugins { id 'java' id 'eclipse' + id 'maven-publish' id 'com.bmuschko.nexus' version '2.3.1' id 'io.codearte.nexus-staging' version '0.11.0' + + // For code coverage reports + id 'jacoco' } // Calling 'apply' here because 'jsonschema2pojo' doesn't exist in Gradle Central Plugin Repository @@ -96,6 +100,14 @@ test { } } +jacocoTestReport { + reports { + xml.enabled true + html.enabled false + } +} +check.dependsOn jacocoTestReport + // // START publishing // @@ -162,8 +174,10 @@ processResources { // // The generated POJOs will be in a package structure analogous to the path in the 'schema_json/' directory // See: https://github.com/joelittlejohn/jsonschema2pojo/wiki/Reference + +final def targetDirectoryPath = file("${project.buildDir}/generated-sources/schemajson").getPath() + task generatePOJOs() { - final def targetDirectory = file("${project.buildDir}/generated-sources/schemajson").getPath() final def jsonSourceDirectory = file("${project.projectDir}/schema_json").getPath() final def jsonPackageNameStart = "net.jacobpeterson.domain" @@ -182,12 +196,12 @@ task generatePOJOs() { // Configure this task to participate in incremental builds so that it only executes when changes occur jsonToPOJOTask.configure { inputs.file(jsonFile) - outputs.dir(targetDirectory) + outputs.dir(targetDirectoryPath) } jsonToPOJOTask.doFirst { jsonToPOJOTask.configuration.source = files(jsonFile.getAbsolutePath()) - jsonToPOJOTask.configuration.targetDirectory = file(targetDirectory) + jsonToPOJOTask.configuration.targetDirectory = file(targetDirectoryPath) jsonToPOJOTask.configuration.targetPackage = targetPackage } @@ -203,15 +217,14 @@ compileJava { sourceSets { main { java { - srcDir 'src/main/java' - srcDir 'build/generated-sources/schemajson' + srcDir targetDirectoryPath } } } jsonSchema2Pojo { includeAdditionalProperties = true - targetDirectory = file('build/generated-sources/schemajson') + targetDirectory = file(targetDirectoryPath) propertyWordDelimiters = ['-', '_'] as char[] annotationStyle = 'gson' sourceType = 'jsonschema' diff --git a/src/test/java/live/net/jacobpeterson/alpaca/AlpacaAPIEndpointTest.java b/src/test/java/live/net/jacobpeterson/alpaca/AlpacaAPIEndpointTest.java index a3405a53..a70f6ce7 100644 --- a/src/test/java/live/net/jacobpeterson/alpaca/AlpacaAPIEndpointTest.java +++ b/src/test/java/live/net/jacobpeterson/alpaca/AlpacaAPIEndpointTest.java @@ -56,6 +56,7 @@ public void afterEach() { */ @Test @Order(1) + @SuppressWarnings("ResultOfMethodCallIgnored") public void testGetAccount() throws AlpacaAPIRequestException, NumberFormatException { AlpacaAPI alpacaAPI = new AlpacaAPI(); diff --git a/src/test/resources/alpaca.properties.default b/src/test/resources/alpaca.properties.default new file mode 100644 index 00000000..91796fbf --- /dev/null +++ b/src/test/resources/alpaca.properties.default @@ -0,0 +1,5 @@ +key_id = ABCDEFGHIJKLM +secret = NOPQURSTUVWXYZ +base_api_url = https://paper-api.foo.bar +base_data_url = https://data.foo.bar +user_agent=a_user_agent diff --git a/src/test/resources/polygon.properties.default b/src/test/resources/polygon.properties.default new file mode 100644 index 00000000..2f8a6650 --- /dev/null +++ b/src/test/resources/polygon.properties.default @@ -0,0 +1,4 @@ +key_id = ABCDEFGHIJKLMNOPQURSTUVWXYZ +base_api_url = https://api.foo.bar +web_socket_server_url = wss://alpaca.socket.foo.bar/stocks +user_agent=a_user_agent From 310fcbae2375533ed488885ee0b901941caf32ca Mon Sep 17 00:00:00 2001 From: Jacob Peterson Date: Thu, 7 Jan 2021 23:23:00 -0700 Subject: [PATCH 077/271] Fix Travis CI code coverage report uploading --- .travis.yml | 1 - README.md | 2 +- build.gradle | 2 +- 3 files changed, 2 insertions(+), 3 deletions(-) diff --git a/.travis.yml b/.travis.yml index c5fc7ad8..cbdc6da1 100644 --- a/.travis.yml +++ b/.travis.yml @@ -10,7 +10,6 @@ before_script: script: - ./gradlew build check --stacktrace - - ./gradlew codeCoverageReport after_success: - bash <(curl -s https://codecov.io/bash) diff --git a/README.md b/README.md index 1f1e9d5a..45892916 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@

-Maven Central Javadocs Build Status +Maven Central Javadocs Build Status GitHub

diff --git a/build.gradle b/build.gradle index e7aa7940..41c7eee4 100644 --- a/build.gradle +++ b/build.gradle @@ -106,7 +106,7 @@ jacocoTestReport { html.enabled false } } -check.dependsOn jacocoTestReport +jacocoTestReport.dependsOn check // // START publishing From 686fde0e9454da1720612ebbffd47460aee03893 Mon Sep 17 00:00:00 2001 From: Jacob Peterson Date: Thu, 7 Jan 2021 23:24:12 -0700 Subject: [PATCH 078/271] Add jacocoTestReport command to Travis CI config --- .travis.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.travis.yml b/.travis.yml index cbdc6da1..70dd53e5 100644 --- a/.travis.yml +++ b/.travis.yml @@ -10,6 +10,7 @@ before_script: script: - ./gradlew build check --stacktrace + - ./gradlew jacocoTestReport after_success: - bash <(curl -s https://codecov.io/bash) From b591299951f31913abe7ba6abbe2cc3daf7049d4 Mon Sep 17 00:00:00 2001 From: belarusrulez Date: Fri, 29 Jan 2021 18:00:22 -0800 Subject: [PATCH 079/271] added vw to aggregate schema added limit and sort to polygon aggregates call --- schema_json/polygon/aggregates/aggregate.json | 4 ++++ .../net/jacobpeterson/polygon/PolygonAPI.java | 13 ++++++++++++- .../polygon/enums/AggregatesSort.java | 19 +++++++++++++++++++ 3 files changed, 35 insertions(+), 1 deletion(-) create mode 100644 src/main/java/net/jacobpeterson/polygon/enums/AggregatesSort.java diff --git a/schema_json/polygon/aggregates/aggregate.json b/schema_json/polygon/aggregates/aggregate.json index 0014d8db..769e9cf0 100644 --- a/schema_json/polygon/aggregates/aggregate.json +++ b/schema_json/polygon/aggregates/aggregate.json @@ -11,6 +11,10 @@ "existingJavaType": "java.lang.Long", "title": "Volume" }, + "vw": { + "existingJavaType": "java.lang.Double", + "title": "VWap" + }, "o": { "existingJavaType": "java.lang.Double", "title": "Open" diff --git a/src/main/java/net/jacobpeterson/polygon/PolygonAPI.java b/src/main/java/net/jacobpeterson/polygon/PolygonAPI.java index 79f494b1..cb0fa208 100644 --- a/src/main/java/net/jacobpeterson/polygon/PolygonAPI.java +++ b/src/main/java/net/jacobpeterson/polygon/PolygonAPI.java @@ -4,6 +4,8 @@ import com.google.gson.reflect.TypeToken; import com.mashape.unirest.http.HttpResponse; import net.jacobpeterson.abstracts.websocket.exception.WebsocketException; +import net.jacobpeterson.alpaca.AlpacaConstants; +import net.jacobpeterson.alpaca.enums.Direction; import net.jacobpeterson.domain.polygon.aggregates.AggregatesResponse; import net.jacobpeterson.domain.polygon.conditionsmapping.ConditionsMapping; import net.jacobpeterson.domain.polygon.dailyopenclose.DailyOpenClose; @@ -29,6 +31,7 @@ import net.jacobpeterson.domain.polygon.tickernews.TickerNews; import net.jacobpeterson.domain.polygon.tickers.TickersResponse; import net.jacobpeterson.domain.polygon.tickertypes.TickerTypes; +import net.jacobpeterson.polygon.enums.AggregatesSort; import net.jacobpeterson.polygon.enums.ConditionMappingsType; import net.jacobpeterson.polygon.enums.FinancialReportType; import net.jacobpeterson.polygon.enums.FinancialSort; @@ -880,7 +883,7 @@ public PreviousCloseResponse getPreviousClose(String ticker, Boolean unadjusted) * Aggregates */ public AggregatesResponse getAggregates(String ticker, Integer multiplier, Timespan timeSpan, LocalDate fromDate, - LocalDate toDate, Boolean unadjusted) throws PolygonAPIRequestException { + LocalDate toDate, Integer limit, AggregatesSort sort, Boolean unadjusted) throws PolygonAPIRequestException { Preconditions.checkNotNull(ticker); Preconditions.checkNotNull(timeSpan); Preconditions.checkNotNull(fromDate); @@ -900,6 +903,14 @@ public AggregatesResponse getAggregates(String ticker, Integer multiplier, Times builder.appendURLParameter(PolygonConstants.UNADJUSTED_PARAMETER, unadjusted.toString()); } + if (limit != null) { + builder.appendURLParameter(PolygonConstants.LIMIT_PARAMETER, String.valueOf(limit)); + } + + if (sort != null) { + builder.appendURLParameter(PolygonConstants.SORT_PARAMETER, sort.getAPIName()); + } + HttpResponse response = polygonRequest.invokeGet(builder); if (response.getStatus() != 200) { diff --git a/src/main/java/net/jacobpeterson/polygon/enums/AggregatesSort.java b/src/main/java/net/jacobpeterson/polygon/enums/AggregatesSort.java new file mode 100644 index 00000000..25d52847 --- /dev/null +++ b/src/main/java/net/jacobpeterson/polygon/enums/AggregatesSort.java @@ -0,0 +1,19 @@ +package net.jacobpeterson.polygon.enums; + +import net.jacobpeterson.abstracts.enums.APIName; + +public enum AggregatesSort implements APIName { + ASC("asc"), + DESC("desc"); + + String apiName; + + AggregatesSort(String apiName) { + this.apiName = apiName; + } + + @Override + public String getAPIName() { + return apiName; + } +} From d61b1fe4aa739269a9273cfc0bf2b9d7c9bb3abf Mon Sep 17 00:00:00 2001 From: "sir.daiver" Date: Mon, 1 Feb 2021 16:30:46 -0800 Subject: [PATCH 080/271] in conversation with polygon support found one undoccummented method. --- .../net/jacobpeterson/polygon/PolygonAPI.java | 54 +++++++++++++++++++ 1 file changed, 54 insertions(+) diff --git a/src/main/java/net/jacobpeterson/polygon/PolygonAPI.java b/src/main/java/net/jacobpeterson/polygon/PolygonAPI.java index cb0fa208..7ecbfcbf 100644 --- a/src/main/java/net/jacobpeterson/polygon/PolygonAPI.java +++ b/src/main/java/net/jacobpeterson/polygon/PolygonAPI.java @@ -920,6 +920,60 @@ public AggregatesResponse getAggregates(String ticker, Integer multiplier, Times return polygonRequest.getResponseObject(response, AggregatesResponse.class); } + /** + * Get aggregates for a date range, in custom time window sizes + * + * @param ticker Ticker symbol of the request + * @param multiplier Size of the timespan multiplier + * @param timeSpan Size of the time window + * @param fromDateUNIX long UTC timestamp + * @param toDateUNIX long UTC timestamp + * @param unadjusted Set to true if the results should NOT be adjusted for splits + * + * @return the aggregates + * + * @throws PolygonAPIRequestException the polygon API exception + * @see + * Aggregates + */ + public AggregatesResponse getAggregates(String ticker, Integer multiplier, Timespan timeSpan, Long fromDateUNIX, + Long toDateUNIX, Integer limit, AggregatesSort sort, Boolean unadjusted) throws PolygonAPIRequestException { + Preconditions.checkNotNull(ticker); + Preconditions.checkNotNull(timeSpan); + Preconditions.checkNotNull(fromDateUNIX); + Preconditions.checkNotNull(toDateUNIX); + + PolygonRequestBuilder builder = new PolygonRequestBuilder(baseAPIURL, PolygonConstants.VERSION_2_ENDPOINT, + PolygonConstants.AGGS_ENDPOINT, + PolygonConstants.TICKER_ENDPOINT, + ticker, + PolygonConstants.RANGE_ENDPOINT, + Integer.toString((multiplier == null) ? 1 : multiplier), + timeSpan.getAPIName(), + fromDateUNIX.toString(), + toDateUNIX.toString()); + + if (unadjusted != null) { + builder.appendURLParameter(PolygonConstants.UNADJUSTED_PARAMETER, unadjusted.toString()); + } + + if (limit != null) { + builder.appendURLParameter(PolygonConstants.LIMIT_PARAMETER, String.valueOf(limit)); + } + + if (sort != null) { + builder.appendURLParameter(PolygonConstants.SORT_PARAMETER, sort.getAPIName()); + } + + HttpResponse response = polygonRequest.invokeGet(builder); + + if (response.getStatus() != 200) { + throw new PolygonAPIRequestException(response); + } + + return polygonRequest.getResponseObject(response, AggregatesResponse.class); + } + /** * Get the daily OHLC for entire markets. * From 2743c04e88406f1f5bd2e07c5e0ce577c74bd3f6 Mon Sep 17 00:00:00 2001 From: Jacob Peterson Date: Sat, 13 Feb 2021 10:55:54 -0700 Subject: [PATCH 081/271] Make live account test more concise --- .travis.yml | 3 +- README.md | 7 ++- .../alpaca/AlpacaAPIEndpointTest.java | 44 +++++++------------ 3 files changed, 21 insertions(+), 33 deletions(-) diff --git a/.travis.yml b/.travis.yml index 70dd53e5..d54435b1 100644 --- a/.travis.yml +++ b/.travis.yml @@ -9,8 +9,7 @@ before_script: - chmod +x gradlew script: - - ./gradlew build check --stacktrace - - ./gradlew jacocoTestReport + - ./gradlew build check jacocoTestReport --stacktrace after_success: - bash <(curl -s https://codecov.io/bash) diff --git a/README.md b/README.md index 45892916..35990a6e 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,10 @@

-Maven Central Javadocs Build Status - GitHub + Maven Central + Javadocs + Build Status + + GitHub

## Overview diff --git a/src/test/java/live/net/jacobpeterson/alpaca/AlpacaAPIEndpointTest.java b/src/test/java/live/net/jacobpeterson/alpaca/AlpacaAPIEndpointTest.java index a70f6ce7..9449fdd6 100644 --- a/src/test/java/live/net/jacobpeterson/alpaca/AlpacaAPIEndpointTest.java +++ b/src/test/java/live/net/jacobpeterson/alpaca/AlpacaAPIEndpointTest.java @@ -64,13 +64,25 @@ public void testGetAccount() throws AlpacaAPIRequestException, NumberFormatExcep LOGGER.debug(account.toString()); - // Assert basic data exists + // Assert basic data integrity and not null + Double.parseDouble(account.getCash()); + Double.parseDouble(account.getPortfolioValue()); + Double.parseDouble(account.getLongMarketValue()); + Double.parseDouble(account.getShortMarketValue()); + Double.parseDouble(account.getEquity()); + Double.parseDouble(account.getLastEquity()); + Double.parseDouble(account.getBuyingPower()); + Double.parseDouble(account.getInitialMargin()); + Double.parseDouble(account.getMaintenanceMargin()); + Double.parseDouble(account.getLastMaintenanceMargin()); + Double.parseDouble(account.getDaytradingBuyingPower()); + Double.parseDouble(account.getRegtBuyingPower()); + + // Assert other data exists assertNotNull(account.getId()); assertNotNull(account.getAccountNumber()); assertNotNull(account.getStatus()); assertNotNull(account.getCurrency()); - assertNotNull(account.getCash()); - assertNotNull(account.getPortfolioValue()); assertNotNull(account.getPatternDayTrader()); assertNotNull(account.getTradeSuspendedByUser()); assertNotNull(account.getTradingBlocked()); @@ -78,34 +90,8 @@ public void testGetAccount() throws AlpacaAPIRequestException, NumberFormatExcep assertNotNull(account.getAccountBlocked()); assertNotNull(account.getCreatedAt()); assertNotNull(account.getShortingEnabled()); - assertNotNull(account.getLongMarketValue()); - assertNotNull(account.getShortMarketValue()); - assertNotNull(account.getEquity()); - assertNotNull(account.getLastEquity()); assertNotNull(account.getMultiplier()); - assertNotNull(account.getBuyingPower()); - assertNotNull(account.getInitialMargin()); - assertNotNull(account.getMaintenanceMargin()); assertNotNull(account.getSma()); assertNotNull(account.getDaytradeCount()); - assertNotNull(account.getLastMaintenanceMargin()); - assertNotNull(account.getDaytradingBuyingPower()); - assertNotNull(account.getRegtBuyingPower()); - - // Assert basic data integrity - Double.parseDouble(account.getCash()); - Double.parseDouble(account.getEquity()); - Double.parseDouble(account.getCash()); - Double.parseDouble(account.getPortfolioValue()); - Double.parseDouble(account.getLongMarketValue()); - Double.parseDouble(account.getShortMarketValue()); - Double.parseDouble(account.getEquity()); - Double.parseDouble(account.getLastEquity()); - Double.parseDouble(account.getBuyingPower()); - Double.parseDouble(account.getInitialMargin()); - Double.parseDouble(account.getMaintenanceMargin()); - Double.parseDouble(account.getLastMaintenanceMargin()); - Double.parseDouble(account.getDaytradingBuyingPower()); - Double.parseDouble(account.getRegtBuyingPower()); } } From 47c662b421c71bf22e1d8d2e1cb60710f2076707 Mon Sep 17 00:00:00 2001 From: Jacob Peterson Date: Sat, 13 Feb 2021 11:23:48 -0700 Subject: [PATCH 082/271] Clean up Constants and documentation --- schema_json/polygon/aggregates/aggregate.json | 18 +- .../abstracts/enums/SortDirection.java | 30 ++ .../net/jacobpeterson/alpaca/AlpacaAPI.java | 49 +-- .../jacobpeterson/alpaca/enums/Direction.java | 32 -- .../net/jacobpeterson/polygon/PolygonAPI.java | 339 +++++++++--------- .../polygon/PolygonConstants.java | 220 ++++-------- .../polygon/enums/AggregatesSort.java | 19 - 7 files changed, 288 insertions(+), 419 deletions(-) create mode 100644 src/main/java/net/jacobpeterson/abstracts/enums/SortDirection.java delete mode 100644 src/main/java/net/jacobpeterson/alpaca/enums/Direction.java delete mode 100644 src/main/java/net/jacobpeterson/polygon/enums/AggregatesSort.java diff --git a/schema_json/polygon/aggregates/aggregate.json b/schema_json/polygon/aggregates/aggregate.json index 769e9cf0..01f795c4 100644 --- a/schema_json/polygon/aggregates/aggregate.json +++ b/schema_json/polygon/aggregates/aggregate.json @@ -5,39 +5,39 @@ "T": { "existingJavaType": "java.lang.String", "javaName": "ticker", - "title": "Ticker symbol" + "title": "The ticker symbol." }, "v": { "existingJavaType": "java.lang.Long", - "title": "Volume" + "title": "The trading volume of the symbol in the given time period." }, "vw": { "existingJavaType": "java.lang.Double", - "title": "VWap" + "title": "The volume weighted average price." }, "o": { "existingJavaType": "java.lang.Double", - "title": "Open" + "title": "The open price for the symbol in the given time period." }, "c": { "existingJavaType": "java.lang.Double", - "title": "Close" + "title": "The close price for the symbol in the given time period." }, "h": { "existingJavaType": "java.lang.Double", - "title": "High" + "title": "The highest price for the symbol in the given time period." }, "l": { "existingJavaType": "java.lang.Double", - "title": "Low" + "title": "The lowest price for the symbol in the given time period." }, "t": { "existingJavaType": "java.lang.Long", - "title": "Unix Msec Timestamp ( Start of Aggregate window )" + "title": "The Unix Msec timestamp for the start of the aggregate window." }, "n": { "existingJavaType": "java.lang.Integer", - "title": "Number of items in aggregate window" + "title": "The number of items in the aggregate window." } } } diff --git a/src/main/java/net/jacobpeterson/abstracts/enums/SortDirection.java b/src/main/java/net/jacobpeterson/abstracts/enums/SortDirection.java new file mode 100644 index 00000000..6b1b92ab --- /dev/null +++ b/src/main/java/net/jacobpeterson/abstracts/enums/SortDirection.java @@ -0,0 +1,30 @@ +package net.jacobpeterson.abstracts.enums; + +/** + * {@link SortDirection} defines enums for directional sorting. + */ +public enum SortDirection implements APIName { + + /** The Ascending {@link SortDirection}. */ + ASCENDING("asc"), + + /** The Descending {@link SortDirection}. */ + DESCENDING("desc"); + + /** The API name. */ + String apiName; + + /** + * Instantiates a new {@link SortDirection}. + * + * @param apiName the api name + */ + SortDirection(String apiName) { + this.apiName = apiName; + } + + @Override + public String getAPIName() { + return apiName; + } +} diff --git a/src/main/java/net/jacobpeterson/alpaca/AlpacaAPI.java b/src/main/java/net/jacobpeterson/alpaca/AlpacaAPI.java index c380e4c6..d1e6aec9 100644 --- a/src/main/java/net/jacobpeterson/alpaca/AlpacaAPI.java +++ b/src/main/java/net/jacobpeterson/alpaca/AlpacaAPI.java @@ -7,20 +7,11 @@ import com.google.gson.JsonPrimitive; import com.google.gson.reflect.TypeToken; import com.mashape.unirest.http.HttpResponse; +import net.jacobpeterson.abstracts.enums.SortDirection; import net.jacobpeterson.abstracts.websocket.exception.WebsocketException; import net.jacobpeterson.alpaca.AlpacaConstants.Endpoints; import net.jacobpeterson.alpaca.AlpacaConstants.Parameters; -import net.jacobpeterson.alpaca.enums.ActivityType; -import net.jacobpeterson.alpaca.enums.AssetStatus; -import net.jacobpeterson.alpaca.enums.BarsTimeFrame; -import net.jacobpeterson.alpaca.enums.Direction; -import net.jacobpeterson.alpaca.enums.OrderClass; -import net.jacobpeterson.alpaca.enums.OrderSide; -import net.jacobpeterson.alpaca.enums.OrderStatus; -import net.jacobpeterson.alpaca.enums.OrderTimeInForce; -import net.jacobpeterson.alpaca.enums.OrderType; -import net.jacobpeterson.alpaca.enums.PortfolioPeriodUnit; -import net.jacobpeterson.alpaca.enums.PortfolioTimeFrame; +import net.jacobpeterson.alpaca.enums.*; import net.jacobpeterson.alpaca.properties.AlpacaProperties; import net.jacobpeterson.alpaca.rest.AlpacaRequest; import net.jacobpeterson.alpaca.rest.AlpacaRequestBuilder; @@ -56,11 +47,7 @@ import java.time.LocalDate; import java.time.ZonedDateTime; import java.time.format.DateTimeFormatter; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; -import java.util.Map; -import java.util.StringJoiner; +import java.util.*; import java.util.stream.Collectors; /** @@ -182,7 +169,7 @@ public Account getAccount() throws AlpacaAPIRequestException { * date.) * @param after The response will contain only activities submitted after this date. (Cannot be used with * date.) - * @param direction asc or desc (default desc if unspecified.) + * @param sortDirection asc or desc (default desc if unspecified.) * @param pageSize The maximum number of entries to return in the response. (See the section on paging.) * @param pageToken The ID of the end of your current page of results. (See the section on paging.) * @param activityTypes the activity types (null for all activities) @@ -194,7 +181,7 @@ public Account getAccount() throws AlpacaAPIRequestException { * Activities */ public ArrayList getAccountActivities(ZonedDateTime date, ZonedDateTime until, ZonedDateTime after, - Direction direction, Integer pageSize, String pageToken, ActivityType... activityTypes) + SortDirection sortDirection, Integer pageSize, String pageToken, ActivityType... activityTypes) throws AlpacaAPIRequestException { AlpacaRequestBuilder urlBuilder = new AlpacaRequestBuilder(baseAPIURL, Endpoints.VERSION_2, Endpoints.ACCOUNT, @@ -224,8 +211,8 @@ public ArrayList getAccountActivities(ZonedDateTime date, Zoned after.format(DateTimeFormatter.ISO_OFFSET_DATE_TIME)); } - if (direction != null) { - urlBuilder.appendURLParameter(Parameters.DIRECTION, direction.getAPIName()); + if (sortDirection != null) { + urlBuilder.appendURLParameter(Parameters.DIRECTION, sortDirection.getAPIName()); } if (pageSize != null) { @@ -324,14 +311,14 @@ public AccountConfiguration setAccountConfiguration(AccountConfiguration account /** * Retrieves a list of orders for the account, filtered by the supplied query parameters. * - * @param status Order status to be queried. open, closed or all. Defaults to open. - * @param limit The maximum number of orders in response. Defaults to 50 and max is 500. - * @param after The response will include only ones submitted after this timestamp (exclusive.) - * @param until The response will include only ones submitted until this timestamp (exclusive.) - * @param direction The chronological order of response based on the submission time. asc or desc. Defaults to - * desc. - * @param nested If true, the result will roll up multi-leg orders under the legs field of primary order. - * @param symbols A list of symbols to filter the result by (null for no filter). + * @param status Order status to be queried. open, closed or all. Defaults to open. + * @param limit The maximum number of orders in response. Defaults to 50 and max is 500. + * @param after The response will include only ones submitted after this timestamp (exclusive.) + * @param until The response will include only ones submitted until this timestamp (exclusive.) + * @param sortDirection The chronological order of response based on the submission time. asc or desc. Defaults to + * desc. + * @param nested If true, the result will roll up multi-leg orders under the legs field of primary order. + * @param symbols A list of symbols to filter the result by (null for no filter). * * @return the orders * @@ -339,7 +326,7 @@ public AccountConfiguration setAccountConfiguration(AccountConfiguration account * @see Orders */ public ArrayList getOrders(OrderStatus status, Integer limit, ZonedDateTime after, ZonedDateTime until, - Direction direction, Boolean nested, List symbols) throws AlpacaAPIRequestException { + SortDirection sortDirection, Boolean nested, List symbols) throws AlpacaAPIRequestException { AlpacaRequestBuilder urlBuilder = new AlpacaRequestBuilder(baseAPIURL, Endpoints.VERSION_2, Endpoints.ORDERS); @@ -361,8 +348,8 @@ public ArrayList getOrders(OrderStatus status, Integer limit, ZonedDateTi until.format(DateTimeFormatter.ISO_OFFSET_DATE_TIME)); } - if (direction != null) { - urlBuilder.appendURLParameter(Parameters.DIRECTION, direction.getAPIName()); + if (sortDirection != null) { + urlBuilder.appendURLParameter(Parameters.DIRECTION, sortDirection.getAPIName()); } if (nested != null) { diff --git a/src/main/java/net/jacobpeterson/alpaca/enums/Direction.java b/src/main/java/net/jacobpeterson/alpaca/enums/Direction.java deleted file mode 100644 index 4af3bd7b..00000000 --- a/src/main/java/net/jacobpeterson/alpaca/enums/Direction.java +++ /dev/null @@ -1,32 +0,0 @@ -package net.jacobpeterson.alpaca.enums; - -import net.jacobpeterson.abstracts.enums.APIName; - -/** - * {@link Direction} defines enums of various directional orderings. - */ -public enum Direction implements APIName { - - /** The Ascending {@link Direction}. */ - ASCENDING("asc"), - - /** The Descending {@link Direction}. */ - DESCENDING("desc"); - - /** The API name. */ - String apiName; - - /** - * Instantiates a new {@link Direction}. - * - * @param apiName the api name - */ - Direction(String apiName) { - this.apiName = apiName; - } - - @Override - public String getAPIName() { - return apiName; - } -} diff --git a/src/main/java/net/jacobpeterson/polygon/PolygonAPI.java b/src/main/java/net/jacobpeterson/polygon/PolygonAPI.java index 7ecbfcbf..699c1c39 100644 --- a/src/main/java/net/jacobpeterson/polygon/PolygonAPI.java +++ b/src/main/java/net/jacobpeterson/polygon/PolygonAPI.java @@ -3,9 +3,8 @@ import com.google.common.base.Preconditions; import com.google.gson.reflect.TypeToken; import com.mashape.unirest.http.HttpResponse; +import net.jacobpeterson.abstracts.enums.SortDirection; import net.jacobpeterson.abstracts.websocket.exception.WebsocketException; -import net.jacobpeterson.alpaca.AlpacaConstants; -import net.jacobpeterson.alpaca.enums.Direction; import net.jacobpeterson.domain.polygon.aggregates.AggregatesResponse; import net.jacobpeterson.domain.polygon.conditionsmapping.ConditionsMapping; import net.jacobpeterson.domain.polygon.dailyopenclose.DailyOpenClose; @@ -31,13 +30,9 @@ import net.jacobpeterson.domain.polygon.tickernews.TickerNews; import net.jacobpeterson.domain.polygon.tickers.TickersResponse; import net.jacobpeterson.domain.polygon.tickertypes.TickerTypes; -import net.jacobpeterson.polygon.enums.AggregatesSort; -import net.jacobpeterson.polygon.enums.ConditionMappingsType; -import net.jacobpeterson.polygon.enums.FinancialReportType; -import net.jacobpeterson.polygon.enums.FinancialSort; -import net.jacobpeterson.polygon.enums.GainersLosersDirection; -import net.jacobpeterson.polygon.enums.Market; -import net.jacobpeterson.polygon.enums.Timespan; +import net.jacobpeterson.polygon.PolygonConstants.Endpoints; +import net.jacobpeterson.polygon.PolygonConstants.Parameters; +import net.jacobpeterson.polygon.enums.*; import net.jacobpeterson.polygon.properties.PolygonProperties; import net.jacobpeterson.polygon.rest.PolygonRequest; import net.jacobpeterson.polygon.rest.PolygonRequestBuilder; @@ -129,45 +124,45 @@ public PolygonAPI(String baseAPIURL, String websocketURL, String keyID) { * * @return the tickers * - * @throws PolygonAPIRequestException the polygon API exception + * @throws PolygonAPIRequestException throw for {@link PolygonAPIRequestException}s * @see Tickers */ public TickersResponse getTickers(String sort, String tickerType, Market market, String locale, String search, Integer perpage, Integer page, Boolean active) throws PolygonAPIRequestException { - PolygonRequestBuilder builder = new PolygonRequestBuilder(baseAPIURL, PolygonConstants.VERSION_2_ENDPOINT, - PolygonConstants.REFERENCE_ENDPOINT, - PolygonConstants.TICKERS_ENDPOINT); + PolygonRequestBuilder builder = new PolygonRequestBuilder(baseAPIURL, Endpoints.VERSION_2, + Endpoints.REFERENCE, + Endpoints.TICKERS); if (sort != null) { - builder.appendURLParameter(PolygonConstants.SORT_PARAMETER, sort); + builder.appendURLParameter(Parameters.SORT, sort); } if (tickerType != null) { - builder.appendURLParameter(PolygonConstants.TYPE_PARAMETER, tickerType); + builder.appendURLParameter(Parameters.TYPE, tickerType); } if (market != null) { - builder.appendURLParameter(PolygonConstants.MARKET_PARAMETER, market.getAPIName()); + builder.appendURLParameter(Parameters.MARKET, market.getAPIName()); } if (locale != null) { - builder.appendURLParameter(PolygonConstants.LOCALE_PARAMETER, locale); + builder.appendURLParameter(Parameters.LOCALE, locale); } if (search != null) { - builder.appendURLParameter(PolygonConstants.SEARCH_PARAMETER, search); + builder.appendURLParameter(Parameters.SEARCH, search); } if (perpage != null) { - builder.appendURLParameter(PolygonConstants.PERPAGE_PARAMETER, String.valueOf(perpage)); + builder.appendURLParameter(Parameters.PERPAGE, String.valueOf(perpage)); } if (page != null) { - builder.appendURLParameter(PolygonConstants.PAGE_PARAMETER, String.valueOf(page)); + builder.appendURLParameter(Parameters.PAGE, String.valueOf(page)); } if (active != null) { - builder.appendURLParameter(PolygonConstants.ACTIVE_PARAMETER, String.valueOf(active)); + builder.appendURLParameter(Parameters.ACTIVE, String.valueOf(active)); } HttpResponse response = polygonRequest.invokeGet(builder); @@ -187,9 +182,9 @@ public TickersResponse getTickers(String sort, String tickerType, Market market, * @see Ticker Types */ public TickerTypes getTickerTypes() throws PolygonAPIRequestException { - PolygonRequestBuilder builder = new PolygonRequestBuilder(baseAPIURL, PolygonConstants.VERSION_2_ENDPOINT, - PolygonConstants.REFERENCE_ENDPOINT, - PolygonConstants.TYPES_ENDPOINT); + PolygonRequestBuilder builder = new PolygonRequestBuilder(baseAPIURL, Endpoints.VERSION_2, + Endpoints.REFERENCE, + Endpoints.TYPES); HttpResponse response = polygonRequest.invokeGet(builder); @@ -208,17 +203,17 @@ public TickerTypes getTickerTypes() throws PolygonAPIRequestException { * * @return the ticker details * - * @throws PolygonAPIRequestException the polygon API exception + * @throws PolygonAPIRequestException throw for {@link PolygonAPIRequestException}s * @see Ticker Details */ public TickerDetails getTickerDetails(String symbol) throws PolygonAPIRequestException { Preconditions.checkNotNull(symbol); - PolygonRequestBuilder builder = new PolygonRequestBuilder(baseAPIURL, PolygonConstants.VERSION_1_ENDPOINT, - PolygonConstants.META_ENDPOINT, - PolygonConstants.SYMBOLS_ENDPOINT, + PolygonRequestBuilder builder = new PolygonRequestBuilder(baseAPIURL, Endpoints.VERSION_1, + Endpoints.META, + Endpoints.SYMBOLS, symbol, - PolygonConstants.COMPANY_ENDPOINT); + Endpoints.COMPANY); HttpResponse response = polygonRequest.invokeGet(builder); @@ -238,25 +233,25 @@ public TickerDetails getTickerDetails(String symbol) throws PolygonAPIRequestExc * * @return the ticker news * - * @throws PolygonAPIRequestException the polygon API exception + * @throws PolygonAPIRequestException throw for {@link PolygonAPIRequestException}s * @see Ticker News */ public ArrayList getTickerNews(String symbol, Integer perpage, Integer page) throws PolygonAPIRequestException { Preconditions.checkNotNull(symbol); - PolygonRequestBuilder builder = new PolygonRequestBuilder(baseAPIURL, PolygonConstants.VERSION_1_ENDPOINT, - PolygonConstants.META_ENDPOINT, - PolygonConstants.SYMBOLS_ENDPOINT, + PolygonRequestBuilder builder = new PolygonRequestBuilder(baseAPIURL, Endpoints.VERSION_1, + Endpoints.META, + Endpoints.SYMBOLS, symbol, - PolygonConstants.NEWS_ENDPOINT); + Endpoints.NEWS); if (perpage != null) { - builder.appendURLParameter(PolygonConstants.PERPAGE_PARAMETER, String.valueOf(perpage)); + builder.appendURLParameter(Parameters.PERPAGE, String.valueOf(perpage)); } if (page != null) { - builder.appendURLParameter(PolygonConstants.PAGE_PARAMETER, String.valueOf(page)); + builder.appendURLParameter(Parameters.PAGE, String.valueOf(page)); } HttpResponse response = polygonRequest.invokeGet(builder); @@ -275,13 +270,13 @@ public ArrayList getTickerNews(String symbol, Integer perpage, Integ * * @return the markets * - * @throws PolygonAPIRequestException the polygon API exception + * @throws PolygonAPIRequestException throw for {@link PolygonAPIRequestException}s * @see Markets */ public MarketsResponse getMarkets() throws PolygonAPIRequestException { - PolygonRequestBuilder builder = new PolygonRequestBuilder(baseAPIURL, PolygonConstants.VERSION_2_ENDPOINT, - PolygonConstants.REFERENCE_ENDPOINT, - PolygonConstants.MARKETS_ENDPOINT); + PolygonRequestBuilder builder = new PolygonRequestBuilder(baseAPIURL, Endpoints.VERSION_2, + Endpoints.REFERENCE, + Endpoints.MARKETS); HttpResponse response = polygonRequest.invokeGet(builder); @@ -297,13 +292,13 @@ public MarketsResponse getMarkets() throws PolygonAPIRequestException { * * @return the locales * - * @throws PolygonAPIRequestException the polygon API exception + * @throws PolygonAPIRequestException throw for {@link PolygonAPIRequestException}s * @see Locales */ public LocalesResponse getLocales() throws PolygonAPIRequestException { - PolygonRequestBuilder builder = new PolygonRequestBuilder(baseAPIURL, PolygonConstants.VERSION_2_ENDPOINT, - PolygonConstants.REFERENCE_ENDPOINT, - PolygonConstants.LOCALES_ENDPOINT); + PolygonRequestBuilder builder = new PolygonRequestBuilder(baseAPIURL, Endpoints.VERSION_2, + Endpoints.REFERENCE, + Endpoints.LOCALES); HttpResponse response = polygonRequest.invokeGet(builder); @@ -321,15 +316,15 @@ public LocalesResponse getLocales() throws PolygonAPIRequestException { * * @return the stock splits * - * @throws PolygonAPIRequestException the polygon API exception + * @throws PolygonAPIRequestException throw for {@link PolygonAPIRequestException}s * @see Stock Splits */ public StockSplitsResponse getStockSplits(String symbol) throws PolygonAPIRequestException { Preconditions.checkNotNull(symbol); - PolygonRequestBuilder builder = new PolygonRequestBuilder(baseAPIURL, PolygonConstants.VERSION_2_ENDPOINT, - PolygonConstants.REFERENCE_ENDPOINT, - PolygonConstants.SPLITS_ENDPOINT, symbol); + PolygonRequestBuilder builder = new PolygonRequestBuilder(baseAPIURL, Endpoints.VERSION_2, + Endpoints.REFERENCE, + Endpoints.SPLITS, symbol); HttpResponse response = polygonRequest.invokeGet(builder); @@ -347,15 +342,15 @@ public StockSplitsResponse getStockSplits(String symbol) throws PolygonAPIReques * * @return the stock dividends * - * @throws PolygonAPIRequestException the polygon API exception + * @throws PolygonAPIRequestException throw for {@link PolygonAPIRequestException}s * @see Stock Dividends */ public StockDividendsResponse getStockDividends(String symbol) throws PolygonAPIRequestException { Preconditions.checkNotNull(symbol); - PolygonRequestBuilder builder = new PolygonRequestBuilder(baseAPIURL, PolygonConstants.VERSION_2_ENDPOINT, - PolygonConstants.REFERENCE_ENDPOINT, - PolygonConstants.DIVIDENDS_ENDPOINT, + PolygonRequestBuilder builder = new PolygonRequestBuilder(baseAPIURL, Endpoints.VERSION_2, + Endpoints.REFERENCE, + Endpoints.DIVIDENDS, symbol); HttpResponse response = polygonRequest.invokeGet(builder); @@ -377,28 +372,28 @@ public StockDividendsResponse getStockDividends(String symbol) throws PolygonAPI * * @return the symbol financials * - * @throws PolygonAPIRequestException the polygon API exception + * @throws PolygonAPIRequestException throw for {@link PolygonAPIRequestException}s * @see Stock Financials */ public StockFinancialsResponse getStockFinancials(String symbol, Integer limit, FinancialReportType financialReportType, FinancialSort financialSort) throws PolygonAPIRequestException { Preconditions.checkNotNull(symbol); - PolygonRequestBuilder builder = new PolygonRequestBuilder(baseAPIURL, PolygonConstants.VERSION_2_ENDPOINT, - PolygonConstants.REFERENCE_ENDPOINT, - PolygonConstants.FINANCIALS_ENDPOINT, + PolygonRequestBuilder builder = new PolygonRequestBuilder(baseAPIURL, Endpoints.VERSION_2, + Endpoints.REFERENCE, + Endpoints.FINANCIALS, symbol); if (limit != null) { - builder.appendURLParameter(PolygonConstants.LIMIT_PARAMETER, String.valueOf(limit)); + builder.appendURLParameter(Parameters.LIMIT, String.valueOf(limit)); } if (financialReportType != null) { - builder.appendURLParameter(PolygonConstants.TYPE_PARAMETER, financialReportType.getAPIName()); + builder.appendURLParameter(Parameters.TYPE, financialReportType.getAPIName()); } if (financialSort != null) { - builder.appendURLParameter(PolygonConstants.SORT_PARAMETER, financialSort.getAPIName()); + builder.appendURLParameter(Parameters.SORT, financialSort.getAPIName()); } HttpResponse response = polygonRequest.invokeGet(builder); @@ -419,9 +414,9 @@ public StockFinancialsResponse getStockFinancials(String symbol, Integer limit, * @see Market Status */ public MarketStatus getMarketStatus() throws PolygonAPIRequestException { - PolygonRequestBuilder builder = new PolygonRequestBuilder(baseAPIURL, PolygonConstants.VERSION_1_ENDPOINT, - PolygonConstants.MARKET_STATUS_ENDPOINT, - PolygonConstants.NOW_ENDPOINT); + PolygonRequestBuilder builder = new PolygonRequestBuilder(baseAPIURL, Endpoints.VERSION_1, + Endpoints.MARKET_STATUS, + Endpoints.NOW); HttpResponse response = polygonRequest.invokeGet(builder); @@ -441,9 +436,9 @@ public MarketStatus getMarketStatus() throws PolygonAPIRequestException { * @see Market Holidays */ public ArrayList getMarketHolidays() throws PolygonAPIRequestException { - PolygonRequestBuilder builder = new PolygonRequestBuilder(baseAPIURL, PolygonConstants.VERSION_1_ENDPOINT, - PolygonConstants.MARKET_STATUS_ENDPOINT, - PolygonConstants.UPCOMING_ENDPOINT); + PolygonRequestBuilder builder = new PolygonRequestBuilder(baseAPIURL, Endpoints.VERSION_1, + Endpoints.MARKET_STATUS, + Endpoints.UPCOMING); HttpResponse response = polygonRequest.invokeGet(builder); @@ -461,13 +456,13 @@ public ArrayList getMarketHolidays() throws PolygonAPIRequestExce * * @return the exchanges * - * @throws PolygonAPIRequestException the polygon API exception + * @throws PolygonAPIRequestException throw for {@link PolygonAPIRequestException}s * @see Exchanges */ public ArrayList getExchanges() throws PolygonAPIRequestException { - PolygonRequestBuilder builder = new PolygonRequestBuilder(baseAPIURL, PolygonConstants.VERSION_1_ENDPOINT, - PolygonConstants.META_ENDPOINT, - PolygonConstants.EXCHANGES_ENDPOINT); + PolygonRequestBuilder builder = new PolygonRequestBuilder(baseAPIURL, Endpoints.VERSION_1, + Endpoints.META, + Endpoints.EXCHANGES); HttpResponse response = polygonRequest.invokeGet(builder); @@ -494,7 +489,7 @@ public ArrayList getExchanges() throws PolygonAPIRequestException { * * @return the historic trades * - * @throws PolygonAPIRequestException the polygon API exception + * @throws PolygonAPIRequestException throw for {@link PolygonAPIRequestException}s * @see Historic * Trades */ @@ -503,27 +498,27 @@ public HistoricTradesResponse getHistoricTrades(String ticker, LocalDate date, L Preconditions.checkNotNull(ticker); Preconditions.checkNotNull(date); - PolygonRequestBuilder builder = new PolygonRequestBuilder(baseAPIURL, PolygonConstants.VERSION_2_ENDPOINT, - PolygonConstants.TICKS_ENDPOINT, - PolygonConstants.STOCKS_ENDPOINT, - PolygonConstants.TRADES_ENDPOINT, + PolygonRequestBuilder builder = new PolygonRequestBuilder(baseAPIURL, Endpoints.VERSION_2, + Endpoints.TICKS, + Endpoints.STOCKS, + Endpoints.TRADES, ticker, FormatUtil.toDateString(date)); if (timestamp != null) { - builder.appendURLParameter(PolygonConstants.TIMESTAMP_PARAMETER, String.valueOf(timestamp)); + builder.appendURLParameter(Parameters.TIMESTAMP, String.valueOf(timestamp)); } if (timestampLimit != null) { - builder.appendURLParameter(PolygonConstants.TIMESTAMP_LIMIT_PARAMETER, String.valueOf(timestampLimit)); + builder.appendURLParameter(Parameters.TIMESTAMP_LIMIT, String.valueOf(timestampLimit)); } if (reverse != null) { - builder.appendURLParameter(PolygonConstants.REVERSE_PARAMETER, String.valueOf(reverse)); + builder.appendURLParameter(Parameters.REVERSE, String.valueOf(reverse)); } if (limit != null) { - builder.appendURLParameter(PolygonConstants.LIMIT_PARAMETER, String.valueOf(limit)); + builder.appendURLParameter(Parameters.LIMIT, String.valueOf(limit)); } HttpResponse response = polygonRequest.invokeGet(builder); @@ -549,7 +544,7 @@ public HistoricTradesResponse getHistoricTrades(String ticker, LocalDate date, L * * @return the historic quotes * - * @throws PolygonAPIRequestException the polygon API exception + * @throws PolygonAPIRequestException throw for {@link PolygonAPIRequestException}s * @see Historic * Quotes */ @@ -558,27 +553,27 @@ public HistoricQuotesResponse getHistoricQuotes(String ticker, LocalDate date, L Preconditions.checkNotNull(ticker); Preconditions.checkNotNull(date); - PolygonRequestBuilder builder = new PolygonRequestBuilder(baseAPIURL, PolygonConstants.VERSION_2_ENDPOINT, - PolygonConstants.TICKS_ENDPOINT, - PolygonConstants.STOCKS_ENDPOINT, - PolygonConstants.NBBO_ENDPOINT, + PolygonRequestBuilder builder = new PolygonRequestBuilder(baseAPIURL, Endpoints.VERSION_2, + Endpoints.TICKS, + Endpoints.STOCKS, + Endpoints.NBBO, ticker, FormatUtil.toDateString(date)); if (timestamp != null) { - builder.appendURLParameter(PolygonConstants.TIMESTAMP_PARAMETER, String.valueOf(timestamp)); + builder.appendURLParameter(Parameters.TIMESTAMP, String.valueOf(timestamp)); } if (timestampLimit != null) { - builder.appendURLParameter(PolygonConstants.TIMESTAMP_LIMIT_PARAMETER, String.valueOf(timestampLimit)); + builder.appendURLParameter(Parameters.TIMESTAMP_LIMIT, String.valueOf(timestampLimit)); } if (reverse != null) { - builder.appendURLParameter(PolygonConstants.REVERSE_PARAMETER, String.valueOf(reverse)); + builder.appendURLParameter(Parameters.REVERSE, String.valueOf(reverse)); } if (limit != null) { - builder.appendURLParameter(PolygonConstants.LIMIT_PARAMETER, String.valueOf(limit)); + builder.appendURLParameter(Parameters.LIMIT, String.valueOf(limit)); } HttpResponse response = polygonRequest.invokeGet(builder); @@ -597,15 +592,15 @@ public HistoricQuotesResponse getHistoricQuotes(String ticker, LocalDate date, L * * @return the last trade * - * @throws PolygonAPIRequestException the polygon API exception + * @throws PolygonAPIRequestException throw for {@link PolygonAPIRequestException}s * @see Last Trade */ public LastTradeResponse getLastTrade(String symbol) throws PolygonAPIRequestException { Preconditions.checkNotNull(symbol); - PolygonRequestBuilder builder = new PolygonRequestBuilder(baseAPIURL, PolygonConstants.VERSION_1_ENDPOINT, - PolygonConstants.LAST_ENDPOINT, - PolygonConstants.STOCKS_ENDPOINT, + PolygonRequestBuilder builder = new PolygonRequestBuilder(baseAPIURL, Endpoints.VERSION_1, + Endpoints.LAST, + Endpoints.STOCKS, symbol); HttpResponse response = polygonRequest.invokeGet(builder); @@ -624,15 +619,15 @@ public LastTradeResponse getLastTrade(String symbol) throws PolygonAPIRequestExc * * @return the last quote * - * @throws PolygonAPIRequestException the polygon API exception + * @throws PolygonAPIRequestException throw for {@link PolygonAPIRequestException}s * @see Last Quote */ public LastQuoteResponse getLastQuote(String symbol) throws PolygonAPIRequestException { Preconditions.checkNotNull(symbol); - PolygonRequestBuilder builder = new PolygonRequestBuilder(baseAPIURL, PolygonConstants.VERSION_1_ENDPOINT, - PolygonConstants.LAST_QUOTE_ENDPOINT, - PolygonConstants.STOCKS_ENDPOINT, + PolygonRequestBuilder builder = new PolygonRequestBuilder(baseAPIURL, Endpoints.VERSION_1, + Endpoints.LAST_QUOTE, + Endpoints.STOCKS, symbol); HttpResponse response = polygonRequest.invokeGet(builder); @@ -652,15 +647,15 @@ public LastQuoteResponse getLastQuote(String symbol) throws PolygonAPIRequestExc * * @return the daily open close * - * @throws PolygonAPIRequestException the polygon API exception + * @throws PolygonAPIRequestException throw for {@link PolygonAPIRequestException}s * @see Daily Open/Close */ public DailyOpenClose getDailyOpenClose(String symbol, LocalDate date) throws PolygonAPIRequestException { Preconditions.checkNotNull(symbol); Preconditions.checkNotNull(date); - PolygonRequestBuilder builder = new PolygonRequestBuilder(baseAPIURL, PolygonConstants.VERSION_1_ENDPOINT, - PolygonConstants.OPEN_CLOSE_ENDPOINT, + PolygonRequestBuilder builder = new PolygonRequestBuilder(baseAPIURL, Endpoints.VERSION_1, + Endpoints.OPEN_CLOSE, symbol, FormatUtil.toDateString(date)); @@ -687,9 +682,9 @@ public ConditionsMapping getConditionsMapping(ConditionMappingsType conditionMap throws PolygonAPIRequestException { Preconditions.checkNotNull(conditionMappingsType); - PolygonRequestBuilder builder = new PolygonRequestBuilder(baseAPIURL, PolygonConstants.VERSION_1_ENDPOINT, - PolygonConstants.META_ENDPOINT, - PolygonConstants.CONDITIONS_ENDPOINT, + PolygonRequestBuilder builder = new PolygonRequestBuilder(baseAPIURL, Endpoints.VERSION_1, + Endpoints.META, + Endpoints.CONDITIONS, conditionMappingsType.getAPIName()); HttpResponse response = polygonRequest.invokeGet(builder); @@ -707,19 +702,19 @@ public ConditionsMapping getConditionsMapping(ConditionMappingsType conditionMap * * @return the snapshot all tickers * - * @throws PolygonAPIRequestException the polygon API exception + * @throws PolygonAPIRequestException throw for {@link PolygonAPIRequestException}s * @see * Snapshot * - All Tickers */ public SnapshotAllTickersResponse getSnapshotAllTickers() throws PolygonAPIRequestException { - PolygonRequestBuilder builder = new PolygonRequestBuilder(baseAPIURL, PolygonConstants.VERSION_2_ENDPOINT, - PolygonConstants.SNAPSHOT_ENDPOINT, - PolygonConstants.LOCALE_ENDPOINT, - PolygonConstants.US_ENDPOINT, - PolygonConstants.MARKETS_ENDPOINT, - PolygonConstants.STOCKS_ENDPOINT, - PolygonConstants.TICKERS_ENDPOINT); + PolygonRequestBuilder builder = new PolygonRequestBuilder(baseAPIURL, Endpoints.VERSION_2, + Endpoints.SNAPSHOT, + Endpoints.LOCALE, + Endpoints.US, + Endpoints.MARKETS, + Endpoints.STOCKS, + Endpoints.TICKERS); HttpResponse response = polygonRequest.invokeGet(builder); @@ -737,7 +732,7 @@ public SnapshotAllTickersResponse getSnapshotAllTickers() throws PolygonAPIReque * * @return the snapshot single ticker * - * @throws PolygonAPIRequestException the polygon API exception + * @throws PolygonAPIRequestException throw for {@link PolygonAPIRequestException}s * @see * Snapshot * - Single Ticker @@ -745,13 +740,13 @@ public SnapshotAllTickersResponse getSnapshotAllTickers() throws PolygonAPIReque public SnapshotSingleTickerResponse getSnapshotSingleTicker(String ticker) throws PolygonAPIRequestException { Preconditions.checkNotNull(ticker); - PolygonRequestBuilder builder = new PolygonRequestBuilder(baseAPIURL, PolygonConstants.VERSION_2_ENDPOINT, - PolygonConstants.SNAPSHOT_ENDPOINT, - PolygonConstants.LOCALE_ENDPOINT, - PolygonConstants.US_ENDPOINT, - PolygonConstants.MARKETS_ENDPOINT, - PolygonConstants.STOCKS_ENDPOINT, - PolygonConstants.TICKERS_ENDPOINT, + PolygonRequestBuilder builder = new PolygonRequestBuilder(baseAPIURL, Endpoints.VERSION_2, + Endpoints.SNAPSHOT, + Endpoints.LOCALE, + Endpoints.US, + Endpoints.MARKETS, + Endpoints.STOCKS, + Endpoints.TICKERS, ticker); HttpResponse response = polygonRequest.invokeGet(builder); @@ -770,7 +765,7 @@ public SnapshotSingleTickerResponse getSnapshotSingleTicker(String ticker) throw * * @return the snapshots gainers * - * @throws PolygonAPIRequestException the polygon API exception + * @throws PolygonAPIRequestException throw for {@link PolygonAPIRequestException}s * @see * Snapshot * - Gainers / Losers @@ -779,12 +774,12 @@ public SnapshotGainersLosersResponse getSnapshotsGainersLosers(GainersLosersDire throws PolygonAPIRequestException { Preconditions.checkNotNull(gainersLosersDirection); - PolygonRequestBuilder builder = new PolygonRequestBuilder(baseAPIURL, PolygonConstants.VERSION_2_ENDPOINT, - PolygonConstants.SNAPSHOT_ENDPOINT, - PolygonConstants.LOCALE_ENDPOINT, - PolygonConstants.US_ENDPOINT, - PolygonConstants.MARKETS_ENDPOINT, - PolygonConstants.STOCKS_ENDPOINT, + PolygonRequestBuilder builder = new PolygonRequestBuilder(baseAPIURL, Endpoints.VERSION_2, + Endpoints.SNAPSHOT, + Endpoints.LOCALE, + Endpoints.US, + Endpoints.MARKETS, + Endpoints.STOCKS, gainersLosersDirection.getAPIName()); HttpResponse response = polygonRequest.invokeGet(builder); @@ -805,7 +800,7 @@ public SnapshotGainersLosersResponse getSnapshotsGainersLosers(GainersLosersDire * * @return the snapshot ticker book * - * @throws PolygonAPIRequestException the polygon API exception + * @throws PolygonAPIRequestException throw for {@link PolygonAPIRequestException}s * @see Docs not public yet */ public SnapshotTickerBook getSnapshotTickerBook(String locale, Market market, String ticker) @@ -814,15 +809,15 @@ public SnapshotTickerBook getSnapshotTickerBook(String locale, Market market, St Preconditions.checkNotNull(market); Preconditions.checkNotNull(ticker); - PolygonRequestBuilder builder = new PolygonRequestBuilder(baseAPIURL, PolygonConstants.VERSION_2_ENDPOINT, - PolygonConstants.SNAPSHOT_ENDPOINT, - PolygonConstants.LOCALE_ENDPOINT, + PolygonRequestBuilder builder = new PolygonRequestBuilder(baseAPIURL, Endpoints.VERSION_2, + Endpoints.SNAPSHOT, + Endpoints.LOCALE, locale, - PolygonConstants.MARKETS_ENDPOINT, + Endpoints.MARKETS, market.getAPIName(), - PolygonConstants.TICKERS_ENDPOINT, + Endpoints.TICKERS, ticker, - PolygonConstants.BOOK_ENDPOINT); + Endpoints.BOOK); HttpResponse response = polygonRequest.invokeGet(builder); @@ -841,20 +836,20 @@ public SnapshotTickerBook getSnapshotTickerBook(String locale, Market market, St * * @return the previous close * - * @throws PolygonAPIRequestException the polygon API exception + * @throws PolygonAPIRequestException throw for {@link PolygonAPIRequestException}s * @see Previous Close */ public PreviousCloseResponse getPreviousClose(String ticker, Boolean unadjusted) throws PolygonAPIRequestException { Preconditions.checkNotNull(ticker); - PolygonRequestBuilder builder = new PolygonRequestBuilder(baseAPIURL, PolygonConstants.VERSION_2_ENDPOINT, - PolygonConstants.AGGS_ENDPOINT, - PolygonConstants.TICKER_ENDPOINT, + PolygonRequestBuilder builder = new PolygonRequestBuilder(baseAPIURL, Endpoints.VERSION_2, + Endpoints.AGGS, + Endpoints.TICKER, ticker, - PolygonConstants.PREV_ENDPOINT); + Endpoints.PREV); if (unadjusted != null) { - builder.appendURLParameter(PolygonConstants.UNADJUSTED_PARAMETER, unadjusted.toString()); + builder.appendURLParameter(Parameters.UNADJUSTED, unadjusted.toString()); } HttpResponse response = polygonRequest.invokeGet(builder); @@ -878,37 +873,38 @@ public PreviousCloseResponse getPreviousClose(String ticker, Boolean unadjusted) * * @return the aggregates * - * @throws PolygonAPIRequestException the polygon API exception + * @throws PolygonAPIRequestException throw for {@link PolygonAPIRequestException}s * @see * Aggregates */ public AggregatesResponse getAggregates(String ticker, Integer multiplier, Timespan timeSpan, LocalDate fromDate, - LocalDate toDate, Integer limit, AggregatesSort sort, Boolean unadjusted) throws PolygonAPIRequestException { + LocalDate toDate, Integer limit, SortDirection sortDirection, Boolean unadjusted) + throws PolygonAPIRequestException { Preconditions.checkNotNull(ticker); Preconditions.checkNotNull(timeSpan); Preconditions.checkNotNull(fromDate); Preconditions.checkNotNull(toDate); - PolygonRequestBuilder builder = new PolygonRequestBuilder(baseAPIURL, PolygonConstants.VERSION_2_ENDPOINT, - PolygonConstants.AGGS_ENDPOINT, - PolygonConstants.TICKER_ENDPOINT, + PolygonRequestBuilder builder = new PolygonRequestBuilder(baseAPIURL, Endpoints.VERSION_2, + Endpoints.AGGS, + Endpoints.TICKER, ticker, - PolygonConstants.RANGE_ENDPOINT, + Endpoints.RANGE, Integer.toString((multiplier == null) ? 1 : multiplier), timeSpan.getAPIName(), FormatUtil.toDateString(fromDate), FormatUtil.toDateString(toDate)); - if (unadjusted != null) { - builder.appendURLParameter(PolygonConstants.UNADJUSTED_PARAMETER, unadjusted.toString()); + if (limit != null) { + builder.appendURLParameter(Parameters.LIMIT, String.valueOf(limit)); } - if (limit != null) { - builder.appendURLParameter(PolygonConstants.LIMIT_PARAMETER, String.valueOf(limit)); + if (sortDirection != null) { + builder.appendURLParameter(Parameters.SORT, sortDirection.getAPIName()); } - if (sort != null) { - builder.appendURLParameter(PolygonConstants.SORT_PARAMETER, sort.getAPIName()); + if (unadjusted != null) { + builder.appendURLParameter(Parameters.UNADJUSTED, unadjusted.toString()); } HttpResponse response = polygonRequest.invokeGet(builder); @@ -923,46 +919,47 @@ public AggregatesResponse getAggregates(String ticker, Integer multiplier, Times /** * Get aggregates for a date range, in custom time window sizes * - * @param ticker Ticker symbol of the request - * @param multiplier Size of the timespan multiplier - * @param timeSpan Size of the time window + * @param ticker Ticker symbol of the request + * @param multiplier Size of the timespan multiplier + * @param timeSpan Size of the time window * @param fromDateUNIX long UTC timestamp - * @param toDateUNIX long UTC timestamp - * @param unadjusted Set to true if the results should NOT be adjusted for splits + * @param toDateUNIX long UTC timestamp + * @param unadjusted Set to true if the results should NOT be adjusted for splits * * @return the aggregates * - * @throws PolygonAPIRequestException the polygon API exception + * @throws PolygonAPIRequestException throw for {@link PolygonAPIRequestException}s * @see * Aggregates */ public AggregatesResponse getAggregates(String ticker, Integer multiplier, Timespan timeSpan, Long fromDateUNIX, - Long toDateUNIX, Integer limit, AggregatesSort sort, Boolean unadjusted) throws PolygonAPIRequestException { + Long toDateUNIX, Integer limit, SortDirection sortDirection, Boolean unadjusted) + throws PolygonAPIRequestException { Preconditions.checkNotNull(ticker); Preconditions.checkNotNull(timeSpan); Preconditions.checkNotNull(fromDateUNIX); Preconditions.checkNotNull(toDateUNIX); - PolygonRequestBuilder builder = new PolygonRequestBuilder(baseAPIURL, PolygonConstants.VERSION_2_ENDPOINT, - PolygonConstants.AGGS_ENDPOINT, - PolygonConstants.TICKER_ENDPOINT, + PolygonRequestBuilder builder = new PolygonRequestBuilder(baseAPIURL, Endpoints.VERSION_2, + Endpoints.AGGS, + Endpoints.TICKER, ticker, - PolygonConstants.RANGE_ENDPOINT, + Endpoints.RANGE, Integer.toString((multiplier == null) ? 1 : multiplier), timeSpan.getAPIName(), fromDateUNIX.toString(), toDateUNIX.toString()); if (unadjusted != null) { - builder.appendURLParameter(PolygonConstants.UNADJUSTED_PARAMETER, unadjusted.toString()); + builder.appendURLParameter(Parameters.UNADJUSTED, unadjusted.toString()); } if (limit != null) { - builder.appendURLParameter(PolygonConstants.LIMIT_PARAMETER, String.valueOf(limit)); + builder.appendURLParameter(Parameters.LIMIT, String.valueOf(limit)); } - if (sort != null) { - builder.appendURLParameter(PolygonConstants.SORT_PARAMETER, sort.getAPIName()); + if (sortDirection != null) { + builder.appendURLParameter(Parameters.SORT, sortDirection.getAPIName()); } HttpResponse response = polygonRequest.invokeGet(builder); @@ -984,7 +981,7 @@ public AggregatesResponse getAggregates(String ticker, Integer multiplier, Times * * @return the grouped daily * - * @throws PolygonAPIRequestException the polygon API exception + * @throws PolygonAPIRequestException throw for {@link PolygonAPIRequestException}s * @see * Grouped * Daily @@ -995,17 +992,17 @@ public GroupedDailyResponse getGroupedDaily(String locale, Market market, LocalD Preconditions.checkNotNull(market); Preconditions.checkNotNull(date); - PolygonRequestBuilder builder = new PolygonRequestBuilder(baseAPIURL, PolygonConstants.VERSION_2_ENDPOINT, - PolygonConstants.AGGS_ENDPOINT, - PolygonConstants.GROUPED_ENDPOINT, - PolygonConstants.LOCALE_ENDPOINT, + PolygonRequestBuilder builder = new PolygonRequestBuilder(baseAPIURL, Endpoints.VERSION_2, + Endpoints.AGGS, + Endpoints.GROUPED, + Endpoints.LOCALE, locale, - PolygonConstants.MARKET_ENDPOINT, + Endpoints.MARKET, market.getAPIName(), FormatUtil.toDateString(date)); if (unadjusted != null) { - builder.appendURLParameter(PolygonConstants.UNADJUSTED_PARAMETER, unadjusted.toString()); + builder.appendURLParameter(Parameters.UNADJUSTED, unadjusted.toString()); } HttpResponse response = polygonRequest.invokeGet(builder); diff --git a/src/main/java/net/jacobpeterson/polygon/PolygonConstants.java b/src/main/java/net/jacobpeterson/polygon/PolygonConstants.java index 7e76d634..a5bc6b7b 100644 --- a/src/main/java/net/jacobpeterson/polygon/PolygonConstants.java +++ b/src/main/java/net/jacobpeterson/polygon/PolygonConstants.java @@ -1,164 +1,70 @@ package net.jacobpeterson.polygon; /** - * The type {@link PolygonConstants}. + * {@link PolygonConstants} are constants used the the {@link PolygonAPI}. */ public class PolygonConstants { - /** The Constant META_ENDPOINT. */ - public static final String META_ENDPOINT = "meta"; - - /** The Constant SYMBOLS_ENDPOINT. */ - public static final String SYMBOLS_ENDPOINT = "symbols"; - - /** The Constant LOCALE_PARAMETER. */ - public static final String LOCALE_PARAMETER = "locale"; - - /** The Constant ACTIVE_PARAMETER. */ - public static final String ACTIVE_PARAMETER = "active"; - - /** The Constant FINANCIALS_ENDPOINT. */ - public static final String FINANCIALS_ENDPOINT = "financials"; - - /** The Constant DIVIDENDS_ENDPOINT. */ - public static final String DIVIDENDS_ENDPOINT = "dividends"; - - /** The Constant COMPANY_ENDPOINT. */ - public static final String COMPANY_ENDPOINT = "company"; - - /** The Constant MARKET_PARAMETER. */ - public static final String MARKET_PARAMETER = "market"; - - /** The Constant SEARCH_PARAMETER. */ - public static final String SEARCH_PARAMETER = "search"; - - /** The constant CONDITIONS_ENDPOINT. */ - public static final String CONDITIONS_ENDPOINT = "conditions"; - - /** The Constant TYPE_PARAMETER. */ - public static final String TYPE_PARAMETER = "type"; - - /** The Constant SORT_PARAMETER. */ - public static final String SORT_PARAMETER = "sort"; - - /** The Constant REFERENCE_ENDPOINT. */ - public static final String REFERENCE_ENDPOINT = "reference"; - - /** The Constant VERSION_1_ENDPOINT. */ - public static final String VERSION_1_ENDPOINT = "v1"; - - /** The Constant VERSION_2_ENDPOINT. */ - public static final String VERSION_2_ENDPOINT = "v2"; - - /** The Constant PAGE_PARAMETER. */ - public static final String PAGE_PARAMETER = "page"; - - /** The Constant PERPAGE_PARAMETER. */ - public static final String PERPAGE_PARAMETER = "perpage"; - - /** The Constant NEWS_ENDPOINT. */ - public static final String NEWS_ENDPOINT = "news"; - - /** The Constant GROUPED_ENDPOINT. */ - public static final String GROUPED_ENDPOINT = "grouped"; - - /** The Constant TICKER_ENDPOINT. */ - public static final String TICKER_ENDPOINT = "ticker"; - - /** The constant TICKS_ENDPOINT. */ - public static final String TICKS_ENDPOINT = "ticks"; - - /** The Constant AGGS_ENDPOINT. */ - public static final String AGGS_ENDPOINT = "aggs"; - - /** The constant NBBO_ENDPOINT. */ - public static final String NBBO_ENDPOINT = "nbbo"; - - /** The Constant OPEN_CLOSE_ENDPOINT. */ - public static final String OPEN_CLOSE_ENDPOINT = "open-close"; - - /** The Constant LAST_QUOTE_ENDPOINT. */ - public static final String LAST_QUOTE_ENDPOINT = "last_quote"; - - /** The Constant LAST_ENDPOINT. */ - public static final String LAST_ENDPOINT = "last"; - - /** The Constant QUOTES_ENDPOINT. */ - public static final String QUOTES_ENDPOINT = "quotes"; - - /** The Constant TRADES_ENDPOINT. */ - public static final String TRADES_ENDPOINT = "trades"; - - /** The Constant HISTORIC_ENDPOINT. */ - public static final String HISTORIC_ENDPOINT = "historic"; - - /** The Constant EXCHANGES_ENDPOINT. */ - public static final String EXCHANGES_ENDPOINT = "exchanges"; - - /** The Constant SPLITS_ENDPOINT. */ - public static final String SPLITS_ENDPOINT = "splits"; - - /** The Constant TYPES_ENDPOINT. */ - public static final String TYPES_ENDPOINT = "types"; - - /** The Constant LOCALES_ENDPOINT. */ - public static final String LOCALES_ENDPOINT = "locales"; - - /** The Constant MARKETS_ENDPOINT. */ - public static final String MARKETS_ENDPOINT = "markets"; - - /** The Constant MARKET_ENDPOINT. */ - public static final String MARKET_ENDPOINT = "market"; - - /** The constant MARKET_STATUS_ENDPOINT. */ - public static final String MARKET_STATUS_ENDPOINT = "marketstatus"; - - /** The constant NOW_ENDPOINT. */ - public static final String NOW_ENDPOINT = "now"; - - /** The constant UPCOMING_ENDPOINT. */ - public static final String UPCOMING_ENDPOINT = "upcoming"; - - /** The Constant TICKERS_ENDPOINT. */ - public static final String TICKERS_ENDPOINT = "tickers"; - - /** The Constant STOCKS_ENDPOINT. */ - public static final String STOCKS_ENDPOINT = "stocks"; - - /** The Constant US_ENDPOINT. */ - public static final String US_ENDPOINT = "us"; - - /** The Constant LOCALE_ENDPOINT. */ - public static final String LOCALE_ENDPOINT = "locale"; - - /** The Constant SNAPSHOT_ENDPOINT. */ - public static final String SNAPSHOT_ENDPOINT = "snapshot"; - - /** The constant BOOK_ENDPOINT. */ - public static final String BOOK_ENDPOINT = "book"; - - /** The Constant LIMIT_PARAMETER. */ - public static final String LIMIT_PARAMETER = "limit"; - - /** The constant TIMESTAMP_PARAMETER. */ - public static final String TIMESTAMP_PARAMETER = "timestamp"; - - /** The constant TIMESTAMP_LIMIT_PARAMETER. */ - public static final String TIMESTAMP_LIMIT_PARAMETER = "timestampLimit"; - - /** The Constant OFFSET_PARAMETER. */ - public static final String OFFSET_PARAMETER = "offset"; - - /** The Constant RANGE_ENDPOINT. */ - public static final String RANGE_ENDPOINT = "range"; - - /** The Constant UNADJUSTED_PARAMETER. */ - public static final String UNADJUSTED_PARAMETER = "unadjusted"; - - /** The Constant PREV_ENDPOINT. */ - public static final String PREV_ENDPOINT = "prev"; - - /** The constant REVERSE_PARAMETER. */ - public static final String REVERSE_PARAMETER = "reverse"; - + /** + * Defines endpoints for {@link PolygonAPI}. + */ + public static class Endpoints { + + public static final String META = "meta"; + public static final String SYMBOLS = "symbols"; + public static final String FINANCIALS = "financials"; + public static final String DIVIDENDS = "dividends"; + public static final String COMPANY = "company"; + public static final String CONDITIONS = "conditions"; + public static final String REFERENCE = "reference"; + public static final String VERSION_1 = "v1"; + public static final String VERSION_2 = "v2"; + public static final String NEWS = "news"; + public static final String GROUPED = "grouped"; + public static final String TICKER = "ticker"; + public static final String TICKS = "ticks"; + public static final String AGGS = "aggs"; + public static final String NBBO = "nbbo"; + public static final String OPEN_CLOSE = "open-close"; + public static final String LAST_QUOTE = "last_quote"; + public static final String LAST = "last"; + public static final String TRADES = "trades"; + public static final String EXCHANGES = "exchanges"; + public static final String SPLITS = "splits"; + public static final String TYPES = "types"; + public static final String LOCALES = "locales"; + public static final String MARKETS = "markets"; + public static final String MARKET = "market"; + public static final String MARKET_STATUS = "marketstatus"; + public static final String NOW = "now"; + public static final String UPCOMING = "upcoming"; + public static final String TICKERS = "tickers"; + public static final String STOCKS = "stocks"; + public static final String US = "us"; + public static final String LOCALE = "locale"; + public static final String SNAPSHOT = "snapshot"; + public static final String BOOK = "book"; + public static final String RANGE = "range"; + public static final String PREV = "prev"; + } + + /** + * Defines parameters for {@link PolygonAPI}. + */ + public static class Parameters { + + public static final String LOCALE = "locale"; + public static final String ACTIVE = "active"; + public static final String MARKET = "market"; + public static final String SEARCH = "search"; + public static final String TYPE = "type"; + public static final String SORT = "sort"; + public static final String PAGE = "page"; + public static final String PERPAGE = "perpage"; + public static final String LIMIT = "limit"; + public static final String TIMESTAMP = "timestamp"; + public static final String TIMESTAMP_LIMIT = "timestampLimit"; + public static final String UNADJUSTED = "unadjusted"; + public static final String REVERSE = "reverse"; + } } diff --git a/src/main/java/net/jacobpeterson/polygon/enums/AggregatesSort.java b/src/main/java/net/jacobpeterson/polygon/enums/AggregatesSort.java deleted file mode 100644 index 25d52847..00000000 --- a/src/main/java/net/jacobpeterson/polygon/enums/AggregatesSort.java +++ /dev/null @@ -1,19 +0,0 @@ -package net.jacobpeterson.polygon.enums; - -import net.jacobpeterson.abstracts.enums.APIName; - -public enum AggregatesSort implements APIName { - ASC("asc"), - DESC("desc"); - - String apiName; - - AggregatesSort(String apiName) { - this.apiName = apiName; - } - - @Override - public String getAPIName() { - return apiName; - } -} From c81d154519bc1c016f9a87ce70e0f3b49afe616c Mon Sep 17 00:00:00 2001 From: Jacob Peterson Date: Sat, 13 Feb 2021 12:31:03 -0700 Subject: [PATCH 083/271] Add live Clock test and change property files --- .../net/jacobpeterson/alpaca/AlpacaAPI.java | 42 ++++------ src/main/resources/alpaca.properties.default | 4 +- src/main/resources/polygon.properties.default | 4 +- .../alpaca/AlpacaAPIEndpointTest.java | 79 +++++++++++++++++-- src/test/resources/alpaca.properties.default | 6 +- src/test/resources/polygon.properties.default | 6 +- 6 files changed, 96 insertions(+), 45 deletions(-) diff --git a/src/main/java/net/jacobpeterson/alpaca/AlpacaAPI.java b/src/main/java/net/jacobpeterson/alpaca/AlpacaAPI.java index d1e6aec9..893ba29b 100644 --- a/src/main/java/net/jacobpeterson/alpaca/AlpacaAPI.java +++ b/src/main/java/net/jacobpeterson/alpaca/AlpacaAPI.java @@ -141,9 +141,9 @@ public AlpacaAPI(String keyID, String secret, String oAuthToken, String baseAPIU } /** - * Returns the account associated with the API key. + * Returns the {@link Account} associated with the API key. * - * @return the account + * @return the {@link Account} * * @throws AlpacaAPIRequestException thrown for {@link AlpacaAPIRequestException}s * @see Get the Account @@ -162,7 +162,7 @@ public Account getAccount() throws AlpacaAPIRequestException { } /** - * Returns account activity entries for many or for a specific type of activity. + * Returns {@link AccountActivity} entries for many or for a specific type of activity. * * @param date The date for which you want to see activities. * @param until The response will contain only activities submitted before this date. (Cannot be used with @@ -174,13 +174,13 @@ public Account getAccount() throws AlpacaAPIRequestException { * @param pageToken The ID of the end of your current page of results. (See the section on paging.) * @param activityTypes the activity types (null for all activities) * - * @return the account activities + * @return the {@link AccountActivity} {@link List} * * @throws AlpacaAPIRequestException the alpaca api exception * @see Gets the Account * Activities */ - public ArrayList getAccountActivities(ZonedDateTime date, ZonedDateTime until, ZonedDateTime after, + public List getAccountActivities(ZonedDateTime date, ZonedDateTime until, ZonedDateTime after, SortDirection sortDirection, Integer pageSize, String pageToken, ActivityType... activityTypes) throws AlpacaAPIRequestException { AlpacaRequestBuilder urlBuilder = new AlpacaRequestBuilder(baseAPIURL, Endpoints.VERSION_2, @@ -1442,42 +1442,30 @@ public PortfolioHistory getPortfolioHistory(Integer periodLength, PortfolioPerio } /** - * Returns the market calendar from 1970 to 2029. + * Returns the market {@link Calendar} from 1970 to 2029. * - * @return the calendar + * @return the {@link Calendar} {@link List} * * @throws AlpacaAPIRequestException thrown for {@link AlpacaAPIRequestException}s * @see Calendar */ - public ArrayList getCalendar() throws AlpacaAPIRequestException { - AlpacaRequestBuilder urlBuilder = new AlpacaRequestBuilder(baseAPIURL, Endpoints.VERSION_2, - Endpoints.CALENDAR); - - HttpResponse response = alpacaRequest.invokeGet(urlBuilder); - - if (response.getStatus() != 200) { - throw new AlpacaAPIRequestException(response); - } - - Type arrayListType = new TypeToken>() {}.getType(); - - return alpacaRequest.getResponseObject(response, arrayListType); + public List getCalendar() throws AlpacaAPIRequestException { + return getCalendar(null, null); } /** - * Returns the market calendar. + * Returns the market {@link Calendar}. * * @param start The first date to retrieve data for (inclusive) * @param end The last date to retrieve data for (inclusive) * - * @return the calendar + * @return the {@link Calendar} {@link List} * * @throws AlpacaAPIRequestException thrown for {@link AlpacaAPIRequestException}s * @see Calendar */ - public ArrayList getCalendar(LocalDate start, LocalDate end) throws AlpacaAPIRequestException { - AlpacaRequestBuilder urlBuilder = new AlpacaRequestBuilder(baseAPIURL, Endpoints.VERSION_2, - Endpoints.CALENDAR); + public List getCalendar(LocalDate start, LocalDate end) throws AlpacaAPIRequestException { + AlpacaRequestBuilder urlBuilder = new AlpacaRequestBuilder(baseAPIURL, Endpoints.VERSION_2, Endpoints.CALENDAR); if (start != null) { urlBuilder.appendURLParameter(Parameters.START, FormatUtil.toDateString(start)); @@ -1499,9 +1487,9 @@ public ArrayList getCalendar(LocalDate start, LocalDate end) throws Al } /** - * Returns the market clock. + * Returns the market {@link Clock}. * - * @return the clock + * @return the {@link Clock} * * @throws AlpacaAPIRequestException thrown for {@link AlpacaAPIRequestException}s * @see Clock diff --git a/src/main/resources/alpaca.properties.default b/src/main/resources/alpaca.properties.default index 5cfcfca7..6743623f 100644 --- a/src/main/resources/alpaca.properties.default +++ b/src/main/resources/alpaca.properties.default @@ -3,9 +3,9 @@ #secret = #base_api_url = https://paper-api.alpaca.markets #base_data_url = https://data.alpaca.markets -#user_agent=Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/67.0.3396.87 Safari/537.36 +#user_agent=Mozilla/5.0 AppleWebKit/537.36 (KHTML, like Gecko) Chrome/67.0.3396.87 Safari/537.36 #Defaults: base_api_url = https://paper-api.alpaca.markets base_data_url = https://data.alpaca.markets -user_agent = Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/67.0.3396.87 Safari/537.36 +user_agent = Java/ Unirest/1.4.9 diff --git a/src/main/resources/polygon.properties.default b/src/main/resources/polygon.properties.default index ea9c28db..a6ce6ec6 100644 --- a/src/main/resources/polygon.properties.default +++ b/src/main/resources/polygon.properties.default @@ -2,9 +2,9 @@ #key_id = (Note that this will default to what is set in alpaca.properties) #base_api_url = https://api.polygon.io #web_socket_server_url = wss://alpaca.socket.polygon.io/stocks -#user_agent=Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/67.0.3396.87 Safari/537.36 +#user_agent = Mozilla/5.0 AppleWebKit/537.36 (KHTML, like Gecko) Chrome/67.0.3396.87 Safari/537.36 #Defauls: base_api_url = https://api.polygon.io web_socket_server_url = wss://alpaca.socket.polygon.io/stocks -user_agent=Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/67.0.3396.87 Safari/537.36 +user_agent = Java/ Unirest/1.4.9 diff --git a/src/test/java/live/net/jacobpeterson/alpaca/AlpacaAPIEndpointTest.java b/src/test/java/live/net/jacobpeterson/alpaca/AlpacaAPIEndpointTest.java index 9449fdd6..987dc696 100644 --- a/src/test/java/live/net/jacobpeterson/alpaca/AlpacaAPIEndpointTest.java +++ b/src/test/java/live/net/jacobpeterson/alpaca/AlpacaAPIEndpointTest.java @@ -1,19 +1,23 @@ package live.net.jacobpeterson.alpaca; +import net.jacobpeterson.abstracts.enums.SortDirection; import net.jacobpeterson.alpaca.AlpacaAPI; +import net.jacobpeterson.alpaca.enums.ActivityType; import net.jacobpeterson.alpaca.rest.exception.AlpacaAPIRequestException; import net.jacobpeterson.domain.alpaca.account.Account; -import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.MethodOrderer; -import org.junit.jupiter.api.Order; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.TestMethodOrder; +import net.jacobpeterson.domain.alpaca.accountactivities.AccountActivity; +import net.jacobpeterson.domain.alpaca.clock.Clock; +import org.junit.jupiter.api.*; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import java.time.ZoneId; +import java.time.ZonedDateTime; +import java.util.List; + import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.fail; +import static org.junit.jupiter.api.Assumptions.assumeTrue; /** * {@link AlpacaAPIEndpointTest} tests live endpoints using Alpaca Paper credentials given in the @@ -24,6 +28,7 @@ public class AlpacaAPIEndpointTest { private static final Logger LOGGER = LoggerFactory.getLogger(AlpacaAPIEndpointTest.class); private static final int RATE_LIMIT_MILLIS = 200; // Wait 200ms between every test to prevent rate-limiting + private static final ZoneId NEW_YORK_ZONE_ID = ZoneId.of("America/New_York"); static { // Log trace-level @@ -31,6 +36,19 @@ public class AlpacaAPIEndpointTest { System.setProperty("org.slf4j.simpleLogger.log.live.net.jacobpeterson", "trace"); } + private static boolean marketOpen; + + /** + * Executed before all tests in this class. + */ + @BeforeAll + public static void beforeAll() { + marketOpen = false; + } + + /** + * Executed before each test. + */ @BeforeEach public void beforeEach() { } @@ -48,6 +66,34 @@ public void afterEach() { } } + /** + * Tests {@link AlpacaAPI#getCalendar()}. + * + * @throws AlpacaAPIRequestException thrown for {@link AlpacaAPIRequestException}s + */ + @Test + @Order(1) + public void testGetClock() throws AlpacaAPIRequestException { + AlpacaAPI alpacaAPI = new AlpacaAPI(); + + Clock clock = alpacaAPI.getClock(); + LOGGER.debug(clock.toString()); + + // Assert basic data integrity and not null + assertNotNull(clock); + assertNotNull(clock.getTimestamp()); + assertNotNull(clock.getIsOpen()); + assertNotNull(clock.getNextOpen()); + assertNotNull(clock.getNextClose()); + + marketOpen = clock.getIsOpen(); + if (marketOpen) { + LOGGER.info("Market is currently open! All live endpoints will be tested."); + } else { + LOGGER.info("Market is currently closed! Only some live endpoints will be tested."); + } + } + /** * Tests {@link AlpacaAPI#getAccount()}. * @@ -55,13 +101,12 @@ public void afterEach() { * @throws NumberFormatException thrown for {@link NumberFormatException}s */ @Test - @Order(1) + @Order(2) @SuppressWarnings("ResultOfMethodCallIgnored") public void testGetAccount() throws AlpacaAPIRequestException, NumberFormatException { AlpacaAPI alpacaAPI = new AlpacaAPI(); Account account = alpacaAPI.getAccount(); - LOGGER.debug(account.toString()); // Assert basic data integrity and not null @@ -94,4 +139,22 @@ public void testGetAccount() throws AlpacaAPIRequestException, NumberFormatExcep assertNotNull(account.getSma()); assertNotNull(account.getDaytradeCount()); } + + /** + * Tests @{@link AlpacaAPI#getAccountActivities(ZonedDateTime, ZonedDateTime, ZonedDateTime, SortDirection, Integer, + * String, ActivityType...)}. + * + * @throws AlpacaAPIRequestException thrown for {@link AlpacaAPIRequestException}s + */ + @Test + @Order(3) + public void testGetAccountActivities() throws AlpacaAPIRequestException { + assumeTrue(marketOpen, "Market is not open. Skipping!"); + + AlpacaAPI alpacaAPI = new AlpacaAPI(); + + // List accountActivities = alpacaAPI.getAccountActivities( + // + // ); + } } diff --git a/src/test/resources/alpaca.properties.default b/src/test/resources/alpaca.properties.default index 91796fbf..4c0253e9 100644 --- a/src/test/resources/alpaca.properties.default +++ b/src/test/resources/alpaca.properties.default @@ -1,5 +1,5 @@ key_id = ABCDEFGHIJKLM secret = NOPQURSTUVWXYZ -base_api_url = https://paper-api.foo.bar -base_data_url = https://data.foo.bar -user_agent=a_user_agent +base_api_url = https://paper-api.alpaca.markets +base_data_url = https://data.alpaca.markets +user_agent = Java/ Unirest/1.4.9 diff --git a/src/test/resources/polygon.properties.default b/src/test/resources/polygon.properties.default index 2f8a6650..73cac850 100644 --- a/src/test/resources/polygon.properties.default +++ b/src/test/resources/polygon.properties.default @@ -1,4 +1,4 @@ key_id = ABCDEFGHIJKLMNOPQURSTUVWXYZ -base_api_url = https://api.foo.bar -web_socket_server_url = wss://alpaca.socket.foo.bar/stocks -user_agent=a_user_agent +base_api_url = https://api.polygon.io +web_socket_server_url = wss://alpaca.socket.polygon.io/stocks +user_agent = Java/ Unirest/1.4.9 From c6eb72af2595ecd086add7da3ab477986b7e8703 Mon Sep 17 00:00:00 2001 From: Jacob Peterson Date: Sat, 13 Feb 2021 17:01:38 -0700 Subject: [PATCH 084/271] Add more endpoint tests --- README.md | 4 +- codecov.yml | 5 + .../net/jacobpeterson/alpaca/AlpacaAPI.java | 34 ++-- .../alpaca/AlpacaAPIEndpointTest.java | 168 +++++++++++++++--- 4 files changed, 169 insertions(+), 42 deletions(-) create mode 100644 codecov.yml diff --git a/README.md b/README.md index 35990a6e..f568dcc8 100644 --- a/README.md +++ b/README.md @@ -1,9 +1,9 @@ -

+

Logo

Maven Central Javadocs Build Status - + CodeCov badge GitHub

diff --git a/codecov.yml b/codecov.yml new file mode 100644 index 00000000..7a0562f8 --- /dev/null +++ b/codecov.yml @@ -0,0 +1,5 @@ +coverage: + status: + # Turn off Codecov status for now + project: off + patch: off diff --git a/src/main/java/net/jacobpeterson/alpaca/AlpacaAPI.java b/src/main/java/net/jacobpeterson/alpaca/AlpacaAPI.java index 893ba29b..fd12e287 100644 --- a/src/main/java/net/jacobpeterson/alpaca/AlpacaAPI.java +++ b/src/main/java/net/jacobpeterson/alpaca/AlpacaAPI.java @@ -176,7 +176,7 @@ public Account getAccount() throws AlpacaAPIRequestException { * * @return the {@link AccountActivity} {@link List} * - * @throws AlpacaAPIRequestException the alpaca api exception + * @throws AlpacaAPIRequestException thrown for {@link AlpacaAPIRequestException}s * @see Gets the Account * Activities */ @@ -257,11 +257,11 @@ public List getAccountActivities(ZonedDateTime date, ZonedDateT } /** - * Returns the current account configuration values. + * Returns the current {@link AccountConfiguration} values. * - * @return the account configurations + * @return the {@link AccountConfiguration} * - * @throws AlpacaAPIRequestException the alpaca api exception + * @throws AlpacaAPIRequestException thrown for {@link AlpacaAPIRequestException}s * @see Account * Configuration */ @@ -280,13 +280,13 @@ public AccountConfiguration getAccountConfiguration() throws AlpacaAPIRequestExc } /** - * Sets account configuration. + * Sets {@link AccountConfiguration}. * * @param accountConfiguration the account configuration * - * @return the updated account configuration + * @return the updated {@link AccountConfiguration} * - * @throws AlpacaAPIRequestException the alpaca api exception + * @throws AlpacaAPIRequestException thrown for {@link AlpacaAPIRequestException}s * @see Account * Configuration */ @@ -313,8 +313,8 @@ public AccountConfiguration setAccountConfiguration(AccountConfiguration account * * @param status Order status to be queried. open, closed or all. Defaults to open. * @param limit The maximum number of orders in response. Defaults to 50 and max is 500. - * @param after The response will include only ones submitted after this timestamp (exclusive.) - * @param until The response will include only ones submitted until this timestamp (exclusive.) + * @param after The response will include only ones submitted after this timestamp (exclusive) + * @param until The response will include only ones submitted until this timestamp (exclusive) * @param sortDirection The chronological order of response based on the submission time. asc or desc. Defaults to * desc. * @param nested If true, the result will roll up multi-leg orders under the legs field of primary order. @@ -325,7 +325,7 @@ public AccountConfiguration setAccountConfiguration(AccountConfiguration account * @throws AlpacaAPIRequestException thrown for {@link AlpacaAPIRequestException}s * @see Orders */ - public ArrayList getOrders(OrderStatus status, Integer limit, ZonedDateTime after, ZonedDateTime until, + public List getOrders(OrderStatus status, Integer limit, ZonedDateTime after, ZonedDateTime until, SortDirection sortDirection, Boolean nested, List symbols) throws AlpacaAPIRequestException { AlpacaRequestBuilder urlBuilder = new AlpacaRequestBuilder(baseAPIURL, Endpoints.VERSION_2, Endpoints.ORDERS); @@ -1187,7 +1187,7 @@ public Asset getAssetBySymbol(String symbolOrAssetID) throws AlpacaAPIRequestExc * * @return the watch lists * - * @throws AlpacaAPIRequestException the alpaca api exception + * @throws AlpacaAPIRequestException thrown for {@link AlpacaAPIRequestException}s * @see Watchlists */ public ArrayList getWatchlists() throws AlpacaAPIRequestException { @@ -1212,7 +1212,7 @@ public ArrayList getWatchlists() throws AlpacaAPIRequestException { * * @return the created watchlist * - * @throws AlpacaAPIRequestException the alpaca api exception + * @throws AlpacaAPIRequestException thrown for {@link AlpacaAPIRequestException}s * @see Watchlists */ public Watchlist createWatchlist(String name, String... symbols) throws AlpacaAPIRequestException { @@ -1247,7 +1247,7 @@ public Watchlist createWatchlist(String name, String... symbols) throws AlpacaAP * * @return the watchlist * - * @throws AlpacaAPIRequestException the alpaca api exception + * @throws AlpacaAPIRequestException thrown for {@link AlpacaAPIRequestException}s * @see Watchlists */ public Watchlist getWatchlist(String watchlistID) throws AlpacaAPIRequestException { @@ -1275,7 +1275,7 @@ public Watchlist getWatchlist(String watchlistID) throws AlpacaAPIRequestExcepti * * @return the updated watchlist * - * @throws AlpacaAPIRequestException the alpaca api exception + * @throws AlpacaAPIRequestException thrown for {@link AlpacaAPIRequestException}s * @see Watchlists */ public Watchlist updateWatchlist(String watchlistID, String name, String... symbols) @@ -1314,7 +1314,7 @@ public Watchlist updateWatchlist(String watchlistID, String name, String... symb * * @return the watchlist * - * @throws AlpacaAPIRequestException the alpaca api exception + * @throws AlpacaAPIRequestException thrown for {@link AlpacaAPIRequestException}s * @see Watchlists */ public Watchlist addWatchlistAsset(String watchlistID, String symbol) throws AlpacaAPIRequestException { @@ -1343,7 +1343,7 @@ public Watchlist addWatchlistAsset(String watchlistID, String symbol) throws Alp * * @return if the watchlist was deleted * - * @throws AlpacaAPIRequestException the alpaca api exception + * @throws AlpacaAPIRequestException thrown for {@link AlpacaAPIRequestException}s * @see Watchlists */ public boolean deleteWatchlist(String watchlistID) throws AlpacaAPIRequestException { @@ -1370,7 +1370,7 @@ public boolean deleteWatchlist(String watchlistID) throws AlpacaAPIRequestExcept * * @return the updated watchlist * - * @throws AlpacaAPIRequestException the alpaca api exception + * @throws AlpacaAPIRequestException thrown for {@link AlpacaAPIRequestException}s * @see Watchlists */ public Watchlist removeSymbolFromWatchlist(String watchlistID, String symbol) throws AlpacaAPIRequestException { diff --git a/src/test/java/live/net/jacobpeterson/alpaca/AlpacaAPIEndpointTest.java b/src/test/java/live/net/jacobpeterson/alpaca/AlpacaAPIEndpointTest.java index 987dc696..3927633a 100644 --- a/src/test/java/live/net/jacobpeterson/alpaca/AlpacaAPIEndpointTest.java +++ b/src/test/java/live/net/jacobpeterson/alpaca/AlpacaAPIEndpointTest.java @@ -3,20 +3,23 @@ import net.jacobpeterson.abstracts.enums.SortDirection; import net.jacobpeterson.alpaca.AlpacaAPI; import net.jacobpeterson.alpaca.enums.ActivityType; +import net.jacobpeterson.alpaca.enums.OrderStatus; import net.jacobpeterson.alpaca.rest.exception.AlpacaAPIRequestException; import net.jacobpeterson.domain.alpaca.account.Account; import net.jacobpeterson.domain.alpaca.accountactivities.AccountActivity; +import net.jacobpeterson.domain.alpaca.accountactivities.NonTradeActivity; +import net.jacobpeterson.domain.alpaca.accountactivities.TradeActivity; +import net.jacobpeterson.domain.alpaca.accountconfiguration.AccountConfiguration; import net.jacobpeterson.domain.alpaca.clock.Clock; +import net.jacobpeterson.domain.alpaca.order.Order; import org.junit.jupiter.api.*; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import java.time.ZoneId; import java.time.ZonedDateTime; import java.util.List; -import static org.junit.jupiter.api.Assertions.assertNotNull; -import static org.junit.jupiter.api.Assertions.fail; +import static org.junit.jupiter.api.Assertions.*; import static org.junit.jupiter.api.Assumptions.assumeTrue; /** @@ -28,7 +31,6 @@ public class AlpacaAPIEndpointTest { private static final Logger LOGGER = LoggerFactory.getLogger(AlpacaAPIEndpointTest.class); private static final int RATE_LIMIT_MILLIS = 200; // Wait 200ms between every test to prevent rate-limiting - private static final ZoneId NEW_YORK_ZONE_ID = ZoneId.of("America/New_York"); static { // Log trace-level @@ -36,13 +38,16 @@ public class AlpacaAPIEndpointTest { System.setProperty("org.slf4j.simpleLogger.log.live.net.jacobpeterson", "trace"); } + private static AlpacaAPI alpacaAPI; private static boolean marketOpen; + private static AccountConfiguration accountConfiguration; /** * Executed before all tests in this class. */ @BeforeAll public static void beforeAll() { + alpacaAPI = new AlpacaAPI(); marketOpen = false; } @@ -67,20 +72,18 @@ public void afterEach() { } /** - * Tests {@link AlpacaAPI#getCalendar()}. + * Tests {@link AlpacaAPI#getClock()}. * * @throws AlpacaAPIRequestException thrown for {@link AlpacaAPIRequestException}s */ @Test - @Order(1) - public void testGetClock() throws AlpacaAPIRequestException { - AlpacaAPI alpacaAPI = new AlpacaAPI(); - + @org.junit.jupiter.api.Order(1) + public void test_getClock() throws AlpacaAPIRequestException { Clock clock = alpacaAPI.getClock(); + assertNotNull(clock); + LOGGER.debug(clock.toString()); - // Assert basic data integrity and not null - assertNotNull(clock); assertNotNull(clock.getTimestamp()); assertNotNull(clock.getIsOpen()); assertNotNull(clock.getNextOpen()); @@ -101,12 +104,11 @@ public void testGetClock() throws AlpacaAPIRequestException { * @throws NumberFormatException thrown for {@link NumberFormatException}s */ @Test - @Order(2) @SuppressWarnings("ResultOfMethodCallIgnored") - public void testGetAccount() throws AlpacaAPIRequestException, NumberFormatException { - AlpacaAPI alpacaAPI = new AlpacaAPI(); - + public void test_getAccount() throws AlpacaAPIRequestException, NumberFormatException { Account account = alpacaAPI.getAccount(); + assertNotNull(account); + LOGGER.debug(account.toString()); // Assert basic data integrity and not null @@ -142,19 +144,139 @@ public void testGetAccount() throws AlpacaAPIRequestException, NumberFormatExcep /** * Tests @{@link AlpacaAPI#getAccountActivities(ZonedDateTime, ZonedDateTime, ZonedDateTime, SortDirection, Integer, - * String, ActivityType...)}. + * String, ActivityType...)} one {@link AccountActivity} exists until now. * * @throws AlpacaAPIRequestException thrown for {@link AlpacaAPIRequestException}s */ @Test - @Order(3) - public void testGetAccountActivities() throws AlpacaAPIRequestException { - assumeTrue(marketOpen, "Market is not open. Skipping!"); + public void test_getAccountActivities_One_Activity_Exists_Until_Now() throws AlpacaAPIRequestException { + List accountActivities = alpacaAPI.getAccountActivities( + null, + ZonedDateTime.now(), + null, + SortDirection.ASCENDING, + 1, + null, + (ActivityType[]) null); + assertNotNull(accountActivities); + assertFalse(accountActivities.isEmpty()); + + accountActivities.forEach(accountActivity -> LOGGER.debug(accountActivity.toString())); + + AccountActivity accountActivity = accountActivities.get(0); + if (accountActivity instanceof TradeActivity) { + TradeActivity tradeActivity = (TradeActivity) accountActivity; + assertNotNull(tradeActivity.getActivityType()); + assertNotNull(tradeActivity.getId()); + assertNotNull(tradeActivity.getCumQty()); + assertNotNull(tradeActivity.getLeavesQty()); + assertNotNull(tradeActivity.getPrice()); + assertNotNull(tradeActivity.getQty()); + assertNotNull(tradeActivity.getSide()); + assertNotNull(tradeActivity.getSymbol()); + assertNotNull(tradeActivity.getTransactionTime()); + assertNotNull(tradeActivity.getOrderId()); + assertNotNull(tradeActivity.getType()); + } else if (accountActivity instanceof NonTradeActivity) { + NonTradeActivity nonTradeActivity = (NonTradeActivity) accountActivity; + assertNotNull(nonTradeActivity.getActivityType()); + assertNotNull(nonTradeActivity.getId()); + assertNotNull(nonTradeActivity.getDate()); + assertNotNull(nonTradeActivity.getNetAmount()); + assertNotNull(nonTradeActivity.getSymbol()); + assertNotNull(nonTradeActivity.getQty()); + assertNotNull(nonTradeActivity.getPerShareAmount()); + assertNotNull(nonTradeActivity.getDescription()); + } + } + + /** + * Test {@link AlpacaAPI#getAccountConfiguration()}. + * + * @throws AlpacaAPIRequestException thrown for {@link AlpacaAPIRequestException}s + */ + @Test + @org.junit.jupiter.api.Order(1) + public void test_getAccountConfiguration() throws AlpacaAPIRequestException { + AccountConfiguration accountConfiguration = alpacaAPI.getAccountConfiguration(); + assertNotNull(accountConfiguration); + + LOGGER.debug(accountConfiguration.toString()); + + assertNotNull(accountConfiguration.getDtbpCheck()); + assertNotNull(accountConfiguration.getTradeConfirmEmail()); + assertNotNull(accountConfiguration.getSuspendTrade()); + assertNotNull(accountConfiguration.getNoShorting()); + + AlpacaAPIEndpointTest.accountConfiguration = accountConfiguration; + } - AlpacaAPI alpacaAPI = new AlpacaAPI(); + /** + * Test {@link AlpacaAPI#setAccountConfiguration(AccountConfiguration)}. + * + * @throws AlpacaAPIRequestException thrown for {@link AlpacaAPIRequestException}s + */ + @Test + @org.junit.jupiter.api.Order(2) + public void test_setAccountConfiguration() throws AlpacaAPIRequestException { + if (accountConfiguration == null) { + AccountConfiguration newAccountConfiguration = new AccountConfiguration( + "both", + "none", + false, + false); + LOGGER.info("Settings Account Configuration to: {}", newAccountConfiguration); + alpacaAPI.setAccountConfiguration(newAccountConfiguration); + } else { + alpacaAPI.setAccountConfiguration(accountConfiguration); + } + } - // List accountActivities = alpacaAPI.getAccountActivities( - // - // ); + /** + * Test {@link AlpacaAPI#getOrders(OrderStatus, Integer, ZonedDateTime, ZonedDateTime, SortDirection, Boolean, + * List)} one {@link Order} exists until now. + * + * @throws AlpacaAPIRequestException thrown for {@link AlpacaAPIRequestException}s + */ + @Test + public void test_getOrders_One_Order_Exists_Until_now() throws AlpacaAPIRequestException { + List orders = alpacaAPI.getOrders( + OrderStatus.ALL, + 1, + null, + ZonedDateTime.now(), + SortDirection.ASCENDING, + true, + null); + + assertNotNull(orders); + assertFalse(orders.isEmpty()); + + orders.forEach(order -> LOGGER.debug(order.toString())); + + // Assert required fields are present + Order order = orders.get(0); + assertNotNull(order.getId()); + assertNotNull(order.getClientOrderId()); + assertNotNull(order.getCreatedAt()); + assertNotNull(order.getUpdatedAt()); + assertNotNull(order.getSubmittedAt()); + assertNotNull(order.getFilledAt()); + assertNotNull(order.getAssetId()); + assertNotNull(order.getSymbol()); + assertNotNull(order.getAssetClass()); + assertNotNull(order.getQty()); + assertNotNull(order.getFilledQty()); + assertNotNull(order.getType()); + assertNotNull(order.getSide()); + assertNotNull(order.getTimeInForce()); + assertNotNull(order.getFilledAvgPrice()); + assertNotNull(order.getStatus()); + assertNotNull(order.getExtendedHours()); + } + + @Test + public void test() { + assumeTrue(marketOpen, "Market is not open. Skipping!"); } } From c5c82b029df760779778b53b9fa011efec3cd71f Mon Sep 17 00:00:00 2001 From: Jacob Peterson Date: Sun, 14 Feb 2021 15:21:53 -0700 Subject: [PATCH 085/271] Modify debug statements --- .../websocket/broker/client/AlpacaWebsocketClient.java | 4 ++-- .../marketdata/client/MarketDataWebsocketClient.java | 10 +++++----- .../websocket/client/PolygonWebsocketClient.java | 2 +- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/main/java/net/jacobpeterson/alpaca/websocket/broker/client/AlpacaWebsocketClient.java b/src/main/java/net/jacobpeterson/alpaca/websocket/broker/client/AlpacaWebsocketClient.java index baed5ff4..b76f299e 100644 --- a/src/main/java/net/jacobpeterson/alpaca/websocket/broker/client/AlpacaWebsocketClient.java +++ b/src/main/java/net/jacobpeterson/alpaca/websocket/broker/client/AlpacaWebsocketClient.java @@ -198,14 +198,14 @@ public void handleWebsocketMessage(String message) { authenticated = isAuthorizationMessageSuccess(authorizationMessage); - if (LOGGER.isDebugEnabled()) { LOGGER.debug(authorizationMessage.toString()); } + LOGGER.debug("{}", authorizationMessage.toString()); break; case LISTENING: ListeningMessage listeningMessage = GsonUtil.GSON.fromJson(messageJsonObject, ListeningMessage.class); sendStreamMessageToListeners(alpacaStreamMessageType, listeningMessage); - if (LOGGER.isDebugEnabled()) { LOGGER.debug(listeningMessage.toString()); } + LOGGER.debug("{}", listeningMessage.toString()); break; case TRADE_UPDATES: sendStreamMessageToListeners(alpacaStreamMessageType, GsonUtil.GSON.fromJson(messageJsonObject, diff --git a/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/client/MarketDataWebsocketClient.java b/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/client/MarketDataWebsocketClient.java index 13e3eff8..b4388946 100644 --- a/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/client/MarketDataWebsocketClient.java +++ b/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/client/MarketDataWebsocketClient.java @@ -239,35 +239,35 @@ public void handleWebsocketMessage(String message) { authenticated = isAuthorizationMessageSuccess(authorizationMessage); - if (LOGGER.isDebugEnabled()) { LOGGER.debug(authorizationMessage.toString()); } + LOGGER.debug("{}", authorizationMessage); break; case LISTENING: ListeningMessage listeningMessage = GsonUtil.GSON.fromJson(messageJsonObject, ListeningMessage.class); sendStreamMessageToListeners(marketDataStreamMessageType, listeningMessage); - if (LOGGER.isDebugEnabled()) { LOGGER.debug(listeningMessage.toString()); } + LOGGER.debug("{}", listeningMessage.toString()); break; case TRADES: TradeMessage tradeMessage = GsonUtil.GSON.fromJson(messageJsonObject.get(DATA_KEY), TradeMessage.class); sendStreamMessageToListeners(marketDataStreamMessageType, tradeMessage); - if (LOGGER.isDebugEnabled()) { LOGGER.debug(tradeMessage.toString()); } + LOGGER.debug("{}", tradeMessage.toString()); break; case QUOTES: QuoteMessage quoteMessage = GsonUtil.GSON.fromJson(messageJsonObject.get(DATA_KEY), QuoteMessage.class); sendStreamMessageToListeners(marketDataStreamMessageType, quoteMessage); - if (LOGGER.isDebugEnabled()) { LOGGER.debug(quoteMessage.toString()); } + LOGGER.debug("{}", quoteMessage.toString()); break; case AGGREGATE_MINUTE: AggregateMinuteMessage aggregateMinuteMessage = GsonUtil.GSON.fromJson( messageJsonObject.get(DATA_KEY), AggregateMinuteMessage.class); sendStreamMessageToListeners(marketDataStreamMessageType, aggregateMinuteMessage); - if (LOGGER.isDebugEnabled()) { LOGGER.debug(aggregateMinuteMessage.toString()); } + LOGGER.debug("{}", aggregateMinuteMessage.toString()); break; default: LOGGER.error("Unhandled stream type: " + marketDataStreamMessageType); diff --git a/src/main/java/net/jacobpeterson/polygon/websocket/client/PolygonWebsocketClient.java b/src/main/java/net/jacobpeterson/polygon/websocket/client/PolygonWebsocketClient.java index b117cafc..904a42fc 100644 --- a/src/main/java/net/jacobpeterson/polygon/websocket/client/PolygonWebsocketClient.java +++ b/src/main/java/net/jacobpeterson/polygon/websocket/client/PolygonWebsocketClient.java @@ -172,7 +172,7 @@ public void handleWebsocketMessage(String message) { authenticated = isAuthenticatedStatusMessage(statusMessage); - if (LOGGER.isDebugEnabled()) { LOGGER.debug(statusMessage.toString()); } + LOGGER.debug("{}", statusMessage.toString()); break; case TRADE: sendStreamMessageToListeners(polygonStreamMessageType, From dfbd3e06fb8685a259f1ab3b5ab68ad9fe6c49b6 Mon Sep 17 00:00:00 2001 From: Jacob Peterson Date: Sat, 20 Feb 2021 14:41:21 -0700 Subject: [PATCH 086/271] Remove PolygonAPI completely --- .gitignore | 1 - README.md | 70 +- build.gradle | 4 +- schema_json/polygon/aggregates/aggregate.json | 43 - .../aggregates/aggregates_response.json | 30 - .../conditions_mapping.json | 7 - .../daily_open_close/daily_open_close.json | 46 - schema_json/polygon/exchanges/exchange.json | 30 - .../grouped_daily/grouped_daily_response.json | 31 - .../historic_quotes/historic_quote.json | 69 -- .../historic_quotes_response.json | 26 - .../historic_trades/historic_trade.json | 51 - .../historic_trades_response.json | 26 - .../polygon/last_quote/last_quote.json | 34 - .../last_quote/last_quote_response.json | 18 - .../polygon/last_trade/last_trade.json | 38 - .../last_trade/last_trade_response.json | 18 - schema_json/polygon/locales/locale.json | 14 - .../polygon/locales/locales_response.json | 14 - .../market_holidays/market_holiday.json | 30 - .../polygon/market_status/market_status.json | 22 - schema_json/polygon/markets/market.json | 14 - .../polygon/markets/markets_response.json | 14 - .../previous_close_response.json | 31 - .../snapshot_all_tickers_response.json | 14 - .../snapshot_gainers_losers_response.json | 14 - .../snapshot_single_ticker_response.json | 14 - .../snapshot/snapshot_ticker_book.json | 14 - .../snapshot/ticker/book/book_data.json | 34 - .../snapshot/ticker/book/book_order.json | 14 - .../polygon/snapshot/ticker/last_quote.json | 30 - .../polygon/snapshot/ticker/last_trade.json | 38 - .../snapshot/ticker/snapshot_ticker.json | 42 - .../snapshot/ticker/ticker_candle.json | 26 - .../stock_dividends/stock_dividend.json | 42 - .../stock_dividends_response.json | 18 - .../stock_financials/stock_financials.json | 446 ------- .../stock_financials_response.json | 18 - .../polygon/stock_splits/stock_split.json | 38 - .../stock_splits/stock_splits_response.json | 18 - .../ticker_details/ticker_details.json | 94 -- .../polygon/ticker_news/ticker_news.json | 38 - .../polygon/ticker_types/ticker_types.json | 14 - .../ticker_types/ticket_types_response.json | 14 - .../polygon/tickers/ticker/attributes.json | 22 - schema_json/polygon/tickers/ticker/codes.json | 26 - .../polygon/tickers/ticker/ticker.json | 50 - .../polygon/tickers/tickers_response.json | 26 - .../aggregate/aggregate_message.json | 53 - .../aggregate_per_minute_message.json | 7 - .../aggregate_per_second_message.json | 7 - .../websocket/polygon_stream_message.json | 17 - .../websocket/quote/quote_message.json | 41 - .../websocket/status/status_message.json | 17 - .../websocket/trade/trade_message.json | 37 - .../net/jacobpeterson/polygon/PolygonAPI.java | 1047 ----------------- .../polygon/PolygonConstants.java | 70 -- .../polygon/enums/ConditionMappingsType.java | 32 - .../polygon/enums/FinancialReportType.java | 44 - .../polygon/enums/FinancialSort.java | 38 - .../polygon/enums/GainersLosersDirection.java | 32 - .../jacobpeterson/polygon/enums/Market.java | 47 - .../jacobpeterson/polygon/enums/Timespan.java | 47 - .../polygon/properties/PolygonProperties.java | 49 - .../polygon/rest/PolygonRequest.java | 77 -- .../polygon/rest/PolygonRequestBuilder.java | 30 - .../exception/PolygonAPIRequestException.java | 27 - .../client/PolygonWebsocketClient.java | 415 ------- .../PolygonWebsocketClientEndpoint.java | 50 - .../listener/PolygonStreamListener.java | 24 - .../PolygonStreamListenerAdapter.java | 70 -- .../message/PolygonStreamMessageType.java | 59 - .../util/doc/Documentation2JSONSchema.java | 30 +- src/main/resources/polygon.properties.default | 10 - src/test/resources/polygon.properties.default | 4 - 75 files changed, 6 insertions(+), 4160 deletions(-) delete mode 100644 schema_json/polygon/aggregates/aggregate.json delete mode 100644 schema_json/polygon/aggregates/aggregates_response.json delete mode 100644 schema_json/polygon/conditions_mapping/conditions_mapping.json delete mode 100644 schema_json/polygon/daily_open_close/daily_open_close.json delete mode 100644 schema_json/polygon/exchanges/exchange.json delete mode 100644 schema_json/polygon/grouped_daily/grouped_daily_response.json delete mode 100644 schema_json/polygon/historic_quotes/historic_quote.json delete mode 100644 schema_json/polygon/historic_quotes/historic_quotes_response.json delete mode 100644 schema_json/polygon/historic_trades/historic_trade.json delete mode 100644 schema_json/polygon/historic_trades/historic_trades_response.json delete mode 100644 schema_json/polygon/last_quote/last_quote.json delete mode 100644 schema_json/polygon/last_quote/last_quote_response.json delete mode 100644 schema_json/polygon/last_trade/last_trade.json delete mode 100644 schema_json/polygon/last_trade/last_trade_response.json delete mode 100644 schema_json/polygon/locales/locale.json delete mode 100644 schema_json/polygon/locales/locales_response.json delete mode 100644 schema_json/polygon/market_holidays/market_holiday.json delete mode 100644 schema_json/polygon/market_status/market_status.json delete mode 100644 schema_json/polygon/markets/market.json delete mode 100644 schema_json/polygon/markets/markets_response.json delete mode 100644 schema_json/polygon/previous_close/previous_close_response.json delete mode 100644 schema_json/polygon/snapshot/snapshot_all_tickers_response.json delete mode 100644 schema_json/polygon/snapshot/snapshot_gainers_losers_response.json delete mode 100644 schema_json/polygon/snapshot/snapshot_single_ticker_response.json delete mode 100644 schema_json/polygon/snapshot/snapshot_ticker_book.json delete mode 100644 schema_json/polygon/snapshot/ticker/book/book_data.json delete mode 100644 schema_json/polygon/snapshot/ticker/book/book_order.json delete mode 100644 schema_json/polygon/snapshot/ticker/last_quote.json delete mode 100644 schema_json/polygon/snapshot/ticker/last_trade.json delete mode 100644 schema_json/polygon/snapshot/ticker/snapshot_ticker.json delete mode 100644 schema_json/polygon/snapshot/ticker/ticker_candle.json delete mode 100644 schema_json/polygon/stock_dividends/stock_dividend.json delete mode 100644 schema_json/polygon/stock_dividends/stock_dividends_response.json delete mode 100644 schema_json/polygon/stock_financials/stock_financials.json delete mode 100644 schema_json/polygon/stock_financials/stock_financials_response.json delete mode 100644 schema_json/polygon/stock_splits/stock_split.json delete mode 100644 schema_json/polygon/stock_splits/stock_splits_response.json delete mode 100644 schema_json/polygon/ticker_details/ticker_details.json delete mode 100644 schema_json/polygon/ticker_news/ticker_news.json delete mode 100644 schema_json/polygon/ticker_types/ticker_types.json delete mode 100644 schema_json/polygon/ticker_types/ticket_types_response.json delete mode 100644 schema_json/polygon/tickers/ticker/attributes.json delete mode 100644 schema_json/polygon/tickers/ticker/codes.json delete mode 100644 schema_json/polygon/tickers/ticker/ticker.json delete mode 100644 schema_json/polygon/tickers/tickers_response.json delete mode 100644 schema_json/polygon/websocket/aggregate/aggregate_message.json delete mode 100644 schema_json/polygon/websocket/aggregate/aggregate_per_minute_message.json delete mode 100644 schema_json/polygon/websocket/aggregate/aggregate_per_second_message.json delete mode 100644 schema_json/polygon/websocket/polygon_stream_message.json delete mode 100644 schema_json/polygon/websocket/quote/quote_message.json delete mode 100644 schema_json/polygon/websocket/status/status_message.json delete mode 100644 schema_json/polygon/websocket/trade/trade_message.json delete mode 100644 src/main/java/net/jacobpeterson/polygon/PolygonAPI.java delete mode 100644 src/main/java/net/jacobpeterson/polygon/PolygonConstants.java delete mode 100644 src/main/java/net/jacobpeterson/polygon/enums/ConditionMappingsType.java delete mode 100644 src/main/java/net/jacobpeterson/polygon/enums/FinancialReportType.java delete mode 100644 src/main/java/net/jacobpeterson/polygon/enums/FinancialSort.java delete mode 100644 src/main/java/net/jacobpeterson/polygon/enums/GainersLosersDirection.java delete mode 100644 src/main/java/net/jacobpeterson/polygon/enums/Market.java delete mode 100644 src/main/java/net/jacobpeterson/polygon/enums/Timespan.java delete mode 100644 src/main/java/net/jacobpeterson/polygon/properties/PolygonProperties.java delete mode 100644 src/main/java/net/jacobpeterson/polygon/rest/PolygonRequest.java delete mode 100644 src/main/java/net/jacobpeterson/polygon/rest/PolygonRequestBuilder.java delete mode 100644 src/main/java/net/jacobpeterson/polygon/rest/exception/PolygonAPIRequestException.java delete mode 100644 src/main/java/net/jacobpeterson/polygon/websocket/client/PolygonWebsocketClient.java delete mode 100644 src/main/java/net/jacobpeterson/polygon/websocket/client/PolygonWebsocketClientEndpoint.java delete mode 100644 src/main/java/net/jacobpeterson/polygon/websocket/listener/PolygonStreamListener.java delete mode 100644 src/main/java/net/jacobpeterson/polygon/websocket/listener/PolygonStreamListenerAdapter.java delete mode 100644 src/main/java/net/jacobpeterson/polygon/websocket/message/PolygonStreamMessageType.java delete mode 100644 src/main/resources/polygon.properties.default delete mode 100644 src/test/resources/polygon.properties.default diff --git a/.gitignore b/.gitignore index 6a517410..fcbc20f3 100644 --- a/.gitignore +++ b/.gitignore @@ -5,4 +5,3 @@ build/ .DS_Store .idea/ alpaca.properties -polygon.properties diff --git a/README.md b/README.md index f568dcc8..2a3ee31d 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,7 @@

## Overview -This is a Java implementation for the Alpaca API and the Polygon API. Alpaca lets you trade with algorithms, connect with apps, and build services all with a commission-free stock trading API and Polygon provides real-time and historical market data for free. This library is community developed and if you have any questions, please ask them on [Github Discussions](https://github.com/Petersoj/alpaca-java/discussions), the [Alpaca Slack #dev-alpaca-java channel](https://alpaca.markets/slack), or on the [Alpaca Forums](https://forum.alpaca.markets/). +This is a Java implementation for the Alpaca API. Alpaca lets you trade with algorithms, connect with apps, and build services all with a commission-free stock trading API. This library is community developed and if you have any questions, please ask them on [Github Discussions](https://github.com/Petersoj/alpaca-java/discussions), the [Alpaca Slack #dev-alpaca-java channel](https://alpaca.markets/slack), or on the [Alpaca Forums](https://forum.alpaca.markets/). ## Table of Contents 1. [Gradle and Maven Integration](#Gradle-and-Maven-Integration) @@ -31,10 +31,6 @@ This is a Java implementation for the Alpaca ``` The default values for `alpaca.properties` can be found [here](https://github.com/Petersoj/alpaca-java/tree/master/src/main/resources). -Similarly, creating a `polygon.properties` file on the classpath with the following format allows you to easily load properties using the `PolygonAPI()` default constructor: -``` -key_id = (Note that this will default to what is set in 'alpaca.properties') -``` -The default values for `polygon.properties` can be found [here](https://github.com/Petersoj/alpaca-java/tree/master/src/main/resources). - ## Logger For logging, this library uses [SLF4j](http://www.slf4j.org/) which serves as an interface for various logging frameworks. This enables you to use whatever logging framework you would like. However, if you do not add a logging framework as a dependency in your project, the console will output a message stating that SLF4j is defaulting to a no-operation (NOP) logger implementation. To enable logging, add a logging framework of your choice as a dependency to your project such as [Log4j 2](http://logging.apache.org/log4j/2.x/index.html), [SLF4j-simple](http://www.slf4j.org/manual.html), or [Apache Commons Logging](https://commons.apache.org/proper/commons-logging/). @@ -230,7 +220,7 @@ try { ``` ### Assets -The Assets API serves as the master list of assets available for trade and data consumption from Alpaca. Assets are sorted by asset class, exchange and symbol. Some assets are only available for data consumption via Polygon, and are not tradable with Alpaca. These assets will be marked with the flag `tradable=false`. +The Assets API serves as the master list of assets available for trade and data consumption from Alpaca. Assets are sorted by asset class, exchange and symbol. Example usage: ```java @@ -477,60 +467,6 @@ try { } ``` -## PolygonAPI -The `PolygonAPI` class contains several methods to interface with Polygon. You will generally only need one instance of it in your application. The Polygon API specification is located [here](https://polygon.io/docs/) and the `PolygonAPI` javadoc is located [here](https://javadoc.io/doc/net.jacobpeterson/alpaca-java/latest/net/jacobpeterson/polygon/PolygonAPI.html). - -Example usage: -```java -// This constructor uses the 'polygon.properties' file on the classpath for configuration -Polygon polygonAPI = new PolygonAPI(); - -// This constructor passes in a 'keyID' String -String keyID = ""; -PolygonAPI polygonAPI = new PolygonAPI(keyID); -``` - -### PolygonAPIRequestException -The `PolygonAPIRequestException` is thrown anytime an exception occurs when using various `PolygonAPI` methods. It should be caught and handled within your trading algorithm application accordingly. - -### Usage -There are many Polygon endpoints (too many to show here in the README), but only certain ones are available to Alpaca users. The usage of the `PolygonAPI` class is very similar to the `AlpacaAPI` class so refer to the `PolygonAPI` [Javadoc](https://javadoc.io/doc/net.jacobpeterson/alpaca-java/latest/net/jacobpeterson/polygon/PolygonAPI.html) for all the available methods and endpoints accessible by Alpaca users. - -### Basic Example -Here is a very basic example for requesting aggregate data and streaming real-time market data from Polygon: -```java -try { - // Get adjusted hour aggregates/bars on 12/23/2020 of AAPL and print them out - AggregatesResponse aaplAggregates = polygonAPI.getAggregates( - "AAPL", - 1, - Timespan.HOUR, - LocalDate.of(2020, 12, 23), - LocalDate.of(2020, 12, 24), - false); - aaplAggregates.getResults().forEach(System.out::println); -} catch (PolygonAPIRequestException exception) { - exception.printStackTrace(); -} - -try { - // Add a Polygon stream listener to listen to "T.AAPL", "Q.AAPL", - // "A.AAPL", "AM.AAPL", and status messages - polygonAPI.addPolygonStreamListener(new PolygonStreamListenerAdapter("AAPL", - PolygonStreamMessageType.values()) { - @Override - public void onStreamUpdate(PolygonStreamMessageType streamMessageType, - PolygonStreamMessage streamMessage) { - System.out.printf("%s stream update: %s \n", - streamMessageType, streamMessage); - } - }); -} catch (WebsocketException exception) { - exception.printStackTrace(); -} -``` -Again, refer to the `PolygonAPI` [Javadoc](https://javadoc.io/doc/net.jacobpeterson/alpaca-java/latest/net/jacobpeterson/polygon/PolygonAPI.html) for an exhaustive list of all available methods. - ## Building To build this project yourself, clone this repository and run: ``` @@ -549,7 +485,7 @@ To run mocked tests using Mockito, run: ./gradlew test ``` -To run live endpoint tests with Alpaca Paper credentials, create `alpaca.properties` and `polygon.properties` files in `src/test/resources` with the corresponding credentials. Then run: +To run live endpoint tests with Alpaca Paper credentials, create the `alpaca.properties` file in `src/test/resources` with the corresponding credentials. Then run: ``` ./gradlew test -PtestPackage=live ``` diff --git a/build.gradle b/build.gradle index 41c7eee4..69db8f0e 100644 --- a/build.gradle +++ b/build.gradle @@ -114,7 +114,7 @@ jacocoTestReport.dependsOn check modifyPom { project { name = 'alpaca-java' - description = 'Java API for Alpaca and Polygon' + description = 'Java API for Alpaca' url = 'https://github.com/Petersoj/alpaca-java' inceptionYear = '2018' @@ -163,7 +163,7 @@ nexusStaging { // Used to prevent accidental publication of Alpaca keys processResources { - exclude('alpaca.properties', 'polygon.properties') + exclude('alpaca.properties') } // // END publishing diff --git a/schema_json/polygon/aggregates/aggregate.json b/schema_json/polygon/aggregates/aggregate.json deleted file mode 100644 index 01f795c4..00000000 --- a/schema_json/polygon/aggregates/aggregate.json +++ /dev/null @@ -1,43 +0,0 @@ -{ - "type": "object", - "title": "See https://polygon.io/docs/#!/Stocks--Equities/get_v2_aggs_ticker_ticker_range_multiplier_timespan_from_to", - "properties": { - "T": { - "existingJavaType": "java.lang.String", - "javaName": "ticker", - "title": "The ticker symbol." - }, - "v": { - "existingJavaType": "java.lang.Long", - "title": "The trading volume of the symbol in the given time period." - }, - "vw": { - "existingJavaType": "java.lang.Double", - "title": "The volume weighted average price." - }, - "o": { - "existingJavaType": "java.lang.Double", - "title": "The open price for the symbol in the given time period." - }, - "c": { - "existingJavaType": "java.lang.Double", - "title": "The close price for the symbol in the given time period." - }, - "h": { - "existingJavaType": "java.lang.Double", - "title": "The highest price for the symbol in the given time period." - }, - "l": { - "existingJavaType": "java.lang.Double", - "title": "The lowest price for the symbol in the given time period." - }, - "t": { - "existingJavaType": "java.lang.Long", - "title": "The Unix Msec timestamp for the start of the aggregate window." - }, - "n": { - "existingJavaType": "java.lang.Integer", - "title": "The number of items in the aggregate window." - } - } -} diff --git a/schema_json/polygon/aggregates/aggregates_response.json b/schema_json/polygon/aggregates/aggregates_response.json deleted file mode 100644 index dde56292..00000000 --- a/schema_json/polygon/aggregates/aggregates_response.json +++ /dev/null @@ -1,30 +0,0 @@ -{ - "type": "object", - "title": "See https://polygon.io/docs/#!/Stocks--Equities/get_v2_aggs_ticker_ticker_range_multiplier_timespan_from_to", - "properties": { - "ticker": { - "existingJavaType": "java.lang.String", - "title": "Ticker symbol requested" - }, - "status": { - "existingJavaType": "java.lang.String", - "title": "Status of the response" - }, - "adjusted": { - "existingJavaType": "java.lang.Boolean", - "title": "If this response was adjusted for splits" - }, - "queryCount": { - "existingJavaType": "java.lang.Double", - "title": "Number of aggregate ( min or day ) used to generate the response" - }, - "resultsCount": { - "existingJavaType": "java.lang.Double", - "title": "Total number of results generated" - }, - "results": { - "existingJavaType": "java.util.ArrayList", - "title": "results" - } - } -} diff --git a/schema_json/polygon/conditions_mapping/conditions_mapping.json b/schema_json/polygon/conditions_mapping/conditions_mapping.json deleted file mode 100644 index 00044e61..00000000 --- a/schema_json/polygon/conditions_mapping/conditions_mapping.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "type": "object", - "title": "See https://polygon.io/docs/#!/Stocks--Equities/get_v1_meta_conditions_ticktype", - "extends": { - "existingJavaType": "java.util.HashMap" - } -} diff --git a/schema_json/polygon/daily_open_close/daily_open_close.json b/schema_json/polygon/daily_open_close/daily_open_close.json deleted file mode 100644 index f81fc790..00000000 --- a/schema_json/polygon/daily_open_close/daily_open_close.json +++ /dev/null @@ -1,46 +0,0 @@ -{ - "type": "object", - "title": "See https://polygon.io/docs/get_v1_open-close__stocksTicker___date__anchor", - "properties": { - "status": { - "existingJavaType": "java.lang.String", - "title": "The status of this request's response." - }, - "from": { - "existingJavaType": "java.time.LocalDate", - "title": "The requested date." - }, - "symbol": { - "existingJavaType": "java.lang.String", - "title": "The exchange symbol that this item is traded under." - }, - "open": { - "existingJavaType": "java.lang.Double", - "title": "The open price for the symbol in the given time period." - }, - "high": { - "existingJavaType": "java.lang.Double", - "title": "The highest price for the symbol in the given time period." - }, - "low": { - "existingJavaType": "java.lang.Double", - "title": "The lowest price for the symbol in the given time period." - }, - "close": { - "existingJavaType": "java.lang.Double", - "title": "The close price for the symbol in the given time period." - }, - "volume": { - "existingJavaType": "java.lang.Long", - "title": "The trading volume of the symbol in the given time period." - }, - "preMarket": { - "existingJavaType": "java.lang.Double", - "title": "The open price of the ticker symbol in pre-market trading." - }, - "afterHours": { - "existingJavaType": "java.lang.Double", - "title": "The close price of the ticker symbol in after hours trading." - } - } -} diff --git a/schema_json/polygon/exchanges/exchange.json b/schema_json/polygon/exchanges/exchange.json deleted file mode 100644 index 37ed8d63..00000000 --- a/schema_json/polygon/exchanges/exchange.json +++ /dev/null @@ -1,30 +0,0 @@ -{ - "type": "object", - "title": "See https://polygon.io/docs/#!/Stocks--Equities/get_v1_meta_exchanges", - "properties": { - "id": { - "existingJavaType": "java.lang.Integer", - "title": "ID of the exchange" - }, - "type": { - "existingJavaType": "java.lang.String", - "title": "The type of exchange this is.
- TRF = Trade Reporting Facility
- exchange = Reporting exchange on the tape
Can be TRF or exchange" - }, - "market": { - "existingJavaType": "java.lang.String", - "title": "Market data type this exchange contains. Can be equities or indices" - }, - "mic": { - "existingJavaType": "java.lang.String", - "title": "Market Identification Code" - }, - "name": { - "existingJavaType": "java.lang.String", - "title": "Name of the exchange" - }, - "tape": { - "existingJavaType": "java.lang.String", - "title": "Tape id of the exchange" - } - } -} diff --git a/schema_json/polygon/grouped_daily/grouped_daily_response.json b/schema_json/polygon/grouped_daily/grouped_daily_response.json deleted file mode 100644 index dbe3fbd0..00000000 --- a/schema_json/polygon/grouped_daily/grouped_daily_response.json +++ /dev/null @@ -1,31 +0,0 @@ -{ - "type": "object", - "title": "See https://polygon.io/docs/#!/Stocks--Equities/get_v2_aggs_grouped_locale_locale_market_market_date", - "properties": { - "ticker": { - "existingJavaType": "java.lang.String", - "title": "Ticker symbol requested" - }, - "status": { - "existingJavaType": "java.lang.String", - "title": "Status of the response" - }, - "adjusted": { - "existingJavaType": "java.lang.Boolean", - "title": "If this response was adjusted for splits" - }, - "queryCount": { - "existingJavaType": "java.lang.Integer", - "title": "Number of aggregate ( min or day ) used to generate the response" - }, - "resultsCount": { - "existingJavaType": "java.lang.Integer", - "title": "Total number of results generated" - }, - "results": { - "existingJavaType": "java.util.ArrayList", - "title": "Results" - } - } -} - diff --git a/schema_json/polygon/historic_quotes/historic_quote.json b/schema_json/polygon/historic_quotes/historic_quote.json deleted file mode 100644 index fc1b56f7..00000000 --- a/schema_json/polygon/historic_quotes/historic_quote.json +++ /dev/null @@ -1,69 +0,0 @@ -{ - "type": "object", - "title": "See https://polygon.io/docs/#!/Stocks--Equities/get_v2_ticks_stocks_nbbo_ticker_date", - "properties": { - "T": { - "existingJavaType": "java.lang.String", - "javaName": "ticker", - "title": "Ticker of the object" - }, - "t": { - "existingJavaType": "java.lang.Long", - "title": "Nanosecond accuracy SIP Unix Timestamp" - }, - "y": { - "existingJavaType": "java.lang.Long", - "title": "Nanosecond accuracy Participant/Exchange Unix Timestamp" - }, - "f": { - "existingJavaType": "java.lang.Long", - "title": "Nanosecond accuracy TRF(Trade Reporting Facility) Unix Timestamp" - }, - "q": { - "existingJavaType": "java.lang.Integer", - "title": "Sequence Number" - }, - "c": { - "existingJavaType": "java.util.ArrayList", - "title": "Conditions" - }, - "i": { - "existingJavaType": "java.util.ArrayList", - "title": "Indicators" - }, - "p": { - "existingJavaType": "java.lang.Double", - "javaName": "bp", - "title": "BID Price" - }, - "x": { - "existingJavaType": "java.lang.Integer", - "javaName": "bx", - "title": "BID Exchange ID" - }, - "s": { - "existingJavaType": "java.lang.Integer", - "javaName": "bs", - "title": "BID Size ( In round lots )" - }, - "P": { - "existingJavaType": "java.lang.Double", - "javaName": "ap", - "title": "ASK Price" - }, - "X": { - "existingJavaType": "java.lang.Integer", - "javaName": "ax", - "title": "ASK Exchange ID" - }, - "S": { - "existingJavaType": "java.lang.Integer", - "javaName": "as", - "title": "ASK Size ( In round lots )" - }, - "z": { - "existingJavaType": "java.lang.Integer", - "title": "Tape where trade occured. ( 1,2 = CTA, 3 = UTP )" - } - } -} diff --git a/schema_json/polygon/historic_quotes/historic_quotes_response.json b/schema_json/polygon/historic_quotes/historic_quotes_response.json deleted file mode 100644 index 9319ef32..00000000 --- a/schema_json/polygon/historic_quotes/historic_quotes_response.json +++ /dev/null @@ -1,26 +0,0 @@ -{ - "type": "object", - "title": "See https://polygon.io/docs/#!/Stocks--Equities/get_v2_ticks_stocks_nbbo_ticker_date", - "properties": { - "results_count": { - "existingJavaType": "java.lang.Integer", - "title": "Total number of results in this response" - }, - "db_latency": { - "existingJavaType": "java.lang.Integer", - "title": "Milliseconds of latency for the query results from DB" - }, - "success": { - "existingJavaType": "java.lang.Boolean", - "title": "If this query was executed successfully" - }, - "ticker": { - "existingJavaType": "java.lang.String", - "title": "Ticker symbol that was evaluated from the request" - }, - "results": { - "existingJavaType": "java.util.ArrayList", - "title": "results" - } - } -} diff --git a/schema_json/polygon/historic_trades/historic_trade.json b/schema_json/polygon/historic_trades/historic_trade.json deleted file mode 100644 index 50ee0066..00000000 --- a/schema_json/polygon/historic_trades/historic_trade.json +++ /dev/null @@ -1,51 +0,0 @@ -{ - "type": "object", - "title": "See https://polygon.io/docs/#!/Stocks--Equities/get_v2_ticks_stocks_trades_ticker_date", - "properties": { - "T": { - "existingJavaType": "java.lang.String", - "javaName": "ticker", - "title": "Ticker of the object" - }, - "t": { - "existingJavaType": "java.lang.Long", - "title": "Nanosecond accuracy SIP Unix Timestamp" - }, - "y": { - "existingJavaType": "java.lang.Long", - "title": "Nanosecond accuracy Participant/Exchange Unix Timestamp" - }, - "f": { - "existingJavaType": "java.lang.Long", - "title": "Nanosecond accuracy TRF(Trade Reporting Facility) Unix Timestamp" - }, - "q": { - "existingJavaType": "java.lang.Integer", - "title": "Sequence Number" - }, - "i": { - "existingJavaType": "java.lang.String", - "title": "Trade ID" - }, - "x": { - "existingJavaType": "java.lang.Integer", - "title": "Exchange ID" - }, - "s": { - "existingJavaType": "java.lang.Integer", - "title": "Size/Volume of the trade" - }, - "c": { - "existingJavaType": "java.util.ArrayList", - "title": "Conditions" - }, - "p": { - "existingJavaType": "java.lang.Double", - "title": "Price of the trade" - }, - "z": { - "existingJavaType": "java.lang.Integer", - "title": "Tape where trade occured. ( 1,2 = CTA, 3 = UTP )" - } - } -} diff --git a/schema_json/polygon/historic_trades/historic_trades_response.json b/schema_json/polygon/historic_trades/historic_trades_response.json deleted file mode 100644 index 12dee80a..00000000 --- a/schema_json/polygon/historic_trades/historic_trades_response.json +++ /dev/null @@ -1,26 +0,0 @@ -{ - "type": "object", - "title": "See https://polygon.io/docs/#!/Stocks--Equities/get_v2_ticks_stocks_trades_ticker_date", - "properties": { - "results_count": { - "existingJavaType": "java.lang.Integer", - "title": "Total number of results in this response" - }, - "db_latency": { - "existingJavaType": "java.lang.Integer", - "title": "Milliseconds of latency for the query results from DB" - }, - "success": { - "existingJavaType": "java.lang.Boolean", - "title": "If this query was executed successfully" - }, - "ticker": { - "existingJavaType": "java.lang.String", - "title": "Ticker symbol that was evaluated from the request" - }, - "results": { - "existingJavaType": "java.util.ArrayList", - "title": "results" - } - } -} diff --git a/schema_json/polygon/last_quote/last_quote.json b/schema_json/polygon/last_quote/last_quote.json deleted file mode 100644 index 17dd795b..00000000 --- a/schema_json/polygon/last_quote/last_quote.json +++ /dev/null @@ -1,34 +0,0 @@ -{ - "type": "object", - "title": "See https://polygon.io/docs/#!/Stocks--Equities/get_v1_last_quote_stocks_symbol", - "properties": { - "askprice": { - "existingJavaType": "java.lang.Double", - "title": "Ask Price" - }, - "asksize": { - "existingJavaType": "java.lang.Long", - "title": "Ask Size" - }, - "askexchange": { - "existingJavaType": "java.lang.Integer", - "title": "Exchange the ask happened on" - }, - "bidprice": { - "existingJavaType": "java.lang.Double", - "title": "Bid Price" - }, - "bidsize": { - "existingJavaType": "java.lang.Long", - "title": "Bid Size" - }, - "bidexchange": { - "existingJavaType": "java.lang.Integer", - "title": "Exchange the bid happened on" - }, - "timestamp": { - "existingJavaType": "java.lang.Long", - "title": "Timestamp of this trade" - } - } -} diff --git a/schema_json/polygon/last_quote/last_quote_response.json b/schema_json/polygon/last_quote/last_quote_response.json deleted file mode 100644 index 9528b29d..00000000 --- a/schema_json/polygon/last_quote/last_quote_response.json +++ /dev/null @@ -1,18 +0,0 @@ -{ - "type": "object", - "title": "See https://polygon.io/docs/#!/Stocks--Equities/get_v1_last_quote_stocks_symbol", - "properties": { - "status": { - "existingJavaType": "java.lang.String", - "title": "Status of this requests response" - }, - "symbol": { - "existingJavaType": "java.lang.String", - "title": "Symbol that was evaluated from the request" - }, - "last": { - "existingJavaType": "net.jacobpeterson.domain.polygon.lastquote.LastQuote", - "title": "last" - } - } -} diff --git a/schema_json/polygon/last_trade/last_trade.json b/schema_json/polygon/last_trade/last_trade.json deleted file mode 100644 index 12e63250..00000000 --- a/schema_json/polygon/last_trade/last_trade.json +++ /dev/null @@ -1,38 +0,0 @@ -{ - "type": "object", - "title": "See https://polygon.io/docs/#!/Stocks--Equities/get_v1_last_stocks_symbol", - "properties": { - "price": { - "existingJavaType": "java.lang.Double", - "title": "Price of the trade" - }, - "size": { - "existingJavaType": "java.lang.Long", - "title": "Size of this trade" - }, - "exchange": { - "existingJavaType": "java.lang.Integer", - "title": "Exchange this trade happened on" - }, - "cond1": { - "existingJavaType": "java.lang.Integer", - "title": "Condition 1 of the trade" - }, - "cond2": { - "existingJavaType": "java.lang.Integer", - "title": "Condition 2 of the trade" - }, - "cond3": { - "existingJavaType": "java.lang.Integer", - "title": "Condition 3 of the trade" - }, - "cond4": { - "existingJavaType": "java.lang.Integer", - "title": "Condition 4 of the trade" - }, - "timestamp": { - "existingJavaType": "java.lang.Long", - "title": "Timestamp of this trade" - } - } -} diff --git a/schema_json/polygon/last_trade/last_trade_response.json b/schema_json/polygon/last_trade/last_trade_response.json deleted file mode 100644 index 96ac11b2..00000000 --- a/schema_json/polygon/last_trade/last_trade_response.json +++ /dev/null @@ -1,18 +0,0 @@ -{ - "type": "object", - "title": "See https://polygon.io/docs/#!/Stocks--Equities/get_v1_last_stocks_symbol", - "properties": { - "status": { - "existingJavaType": "java.lang.String", - "title": "Status of this requests response" - }, - "symbol": { - "existingJavaType": "java.lang.String", - "title": "Symbol that was evaluated from the request" - }, - "last": { - "existingJavaType": "net.jacobpeterson.domain.polygon.lasttrade.LastTrade", - "title": "last" - } - } -} diff --git a/schema_json/polygon/locales/locale.json b/schema_json/polygon/locales/locale.json deleted file mode 100644 index a8912865..00000000 --- a/schema_json/polygon/locales/locale.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "type": "object", - "title": "See https://polygon.io/docs/#!/Reference/get_v2_reference_locales", - "properties": { - "locale": { - "existingJavaType": "java.lang.String", - "title": "Locale" - }, - "name": { - "existingJavaType": "java.lang.String", - "title": "Name" - } - } -} diff --git a/schema_json/polygon/locales/locales_response.json b/schema_json/polygon/locales/locales_response.json deleted file mode 100644 index 6006bb64..00000000 --- a/schema_json/polygon/locales/locales_response.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "type": "object", - "title": "See https://polygon.io/docs/#!/Reference/get_v2_reference_locales", - "properties": { - "status": { - "existingJavaType": "java.lang.String", - "title": "Status" - }, - "results": { - "existingJavaType": "java.util.ArrayList", - "title": "Results" - } - } -} diff --git a/schema_json/polygon/market_holidays/market_holiday.json b/schema_json/polygon/market_holidays/market_holiday.json deleted file mode 100644 index 173e3405..00000000 --- a/schema_json/polygon/market_holidays/market_holiday.json +++ /dev/null @@ -1,30 +0,0 @@ -{ - "type": "object", - "title": "See https://polygon.io/docs/#!/Reference/get_v1_marketstatus_upcoming", - "properties": { - "exchange": { - "existingJavaType": "java.lang.String", - "title": "Which market this record is for. Can be NYSE, NASDAQ or OTC" - }, - "name": { - "existingJavaType": "java.lang.String", - "title": "Human readable description of the holiday" - }, - "status": { - "existingJavaType": "java.lang.String", - "title": "Status of the market on this holiday. Can be closed, early-close, late-close, early-open or late-open" - }, - "date": { - "existingJavaType": "java.time.ZonedDateTime", - "title": "Date of this holiday" - }, - "open": { - "existingJavaType": "java.time.ZonedDateTime", - "title": "Market open time on this holiday ( if it's not closed )" - }, - "close": { - "existingJavaType": "java.time.ZonedDateTime", - "title": "Market close time on this holiday ( if it's not closed )" - } - } -} diff --git a/schema_json/polygon/market_status/market_status.json b/schema_json/polygon/market_status/market_status.json deleted file mode 100644 index 2fccfb87..00000000 --- a/schema_json/polygon/market_status/market_status.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "type": "object", - "title": "See https://polygon.io/docs/#!/Reference/get_v1_marketstatus_now", - "properties": { - "market": { - "existingJavaType": "java.lang.String", - "title": "Status of the market as a whole. Can be open, closed or extended-hours" - }, - "serverTime": { - "existingJavaType": "java.time.ZonedDateTime", - "title": "Current time of the server" - }, - "exchanges": { - "existingJavaType": "java.util.HashMap", - "title": "exchanges" - }, - "currencies": { - "existingJavaType": "java.util.HashMap", - "title": "currencies" - } - } -} diff --git a/schema_json/polygon/markets/market.json b/schema_json/polygon/markets/market.json deleted file mode 100644 index 126bea59..00000000 --- a/schema_json/polygon/markets/market.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "type": "object", - "title": "See https://polygon.io/docs/#!/Reference/get_v2_reference_markets", - "properties": { - "market": { - "existingJavaType": "java.lang.String", - "title": "Market" - }, - "desc": { - "existingJavaType": "java.lang.String", - "title": "Description" - } - } -} diff --git a/schema_json/polygon/markets/markets_response.json b/schema_json/polygon/markets/markets_response.json deleted file mode 100644 index 6c45fee6..00000000 --- a/schema_json/polygon/markets/markets_response.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "type": "object", - "title": "See https://polygon.io/docs/#!/Reference/get_v2_reference_markets", - "properties": { - "status": { - "existingJavaType": "java.lang.String", - "title": "Status" - }, - "results": { - "existingJavaType": "java.util.ArrayList", - "title": "Results" - } - } -} diff --git a/schema_json/polygon/previous_close/previous_close_response.json b/schema_json/polygon/previous_close/previous_close_response.json deleted file mode 100644 index f31fc57a..00000000 --- a/schema_json/polygon/previous_close/previous_close_response.json +++ /dev/null @@ -1,31 +0,0 @@ -{ - "type": "object", - "title": "See https://polygon.io/docs/#!/Stocks--Equities/get_v2_aggs_ticker_ticker_prev", - "properties": { - "ticker": { - "existingJavaType": "java.lang.String", - "title": "Ticker symbol requested" - }, - "status": { - "existingJavaType": "java.lang.String", - "title": "Status of the response" - }, - "adjusted": { - "existingJavaType": "java.lang.Boolean", - "title": "If this response was adjusted for splits" - }, - "queryCount": { - "existingJavaType": "java.lang.Integer", - "title": "Number of aggregate ( min or day ) used to generate the response" - }, - "resultsCount": { - "existingJavaType": "java.lang.Integer", - "title": "Total number of results generated" - }, - "results": { - "existingJavaType": "java.util.ArrayList", - "title": "Results" - } - } -} - diff --git a/schema_json/polygon/snapshot/snapshot_all_tickers_response.json b/schema_json/polygon/snapshot/snapshot_all_tickers_response.json deleted file mode 100644 index 7d1a502c..00000000 --- a/schema_json/polygon/snapshot/snapshot_all_tickers_response.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "type": "object", - "title": "See https://polygon.io/docs/#!/Stocks--Equities/get_v2_snapshot_locale_us_markets_stocks_tickers", - "properties": { - "status": { - "existingJavaType": "java.lang.String", - "title": "Status of this requests response" - }, - "tickers": { - "existingJavaType": "java.util.ArrayList", - "title": "tickers" - } - } -} diff --git a/schema_json/polygon/snapshot/snapshot_gainers_losers_response.json b/schema_json/polygon/snapshot/snapshot_gainers_losers_response.json deleted file mode 100644 index e5bf71a1..00000000 --- a/schema_json/polygon/snapshot/snapshot_gainers_losers_response.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "type": "object", - "title": "See https://polygon.io/docs/#!/Stocks--Equities/get_v2_snapshot_locale_us_markets_stocks_direction", - "properties": { - "status": { - "existingJavaType": "java.lang.String", - "title": "Status of this requests response" - }, - "tickers": { - "existingJavaType": "java.util.ArrayList", - "title": "tickers" - } - } -} diff --git a/schema_json/polygon/snapshot/snapshot_single_ticker_response.json b/schema_json/polygon/snapshot/snapshot_single_ticker_response.json deleted file mode 100644 index 8861077b..00000000 --- a/schema_json/polygon/snapshot/snapshot_single_ticker_response.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "type": "object", - "title": "See https://polygon.io/docs/#!/Stocks--Equities/get_v2_snapshot_locale_us_markets_stocks_tickers_ticker", - "properties": { - "status": { - "existingJavaType": "java.lang.String", - "title": "Status of this requests response" - }, - "ticker": { - "existingJavaType": "net.jacobpeterson.domain.polygon.snapshot.ticker.SnapshotTicker", - "title": "tickers" - } - } -} diff --git a/schema_json/polygon/snapshot/snapshot_ticker_book.json b/schema_json/polygon/snapshot/snapshot_ticker_book.json deleted file mode 100644 index 3d14c5e5..00000000 --- a/schema_json/polygon/snapshot/snapshot_ticker_book.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "type": "object", - "title": "See (documentation not yet public)", - "properties": { - "status": { - "existingJavaType": "java.lang.String", - "title": "Status of this requests response" - }, - "data": { - "existingJavaType": "net.jacobpeterson.domain.polygon.snapshot.ticker.book.BookData", - "title": "The book data" - } - } -} diff --git a/schema_json/polygon/snapshot/ticker/book/book_data.json b/schema_json/polygon/snapshot/ticker/book/book_data.json deleted file mode 100644 index 3d5becdf..00000000 --- a/schema_json/polygon/snapshot/ticker/book/book_data.json +++ /dev/null @@ -1,34 +0,0 @@ -{ - "type": "object", - "title": "See (documentation not yet public)", - "properties": { - "ticker": { - "existingJavaType": "java.lang.String", - "title": "The ticker" - }, - "bids": { - "existingJavaType": "java.util.ArrayList", - "title": "The bids on the book" - }, - "asks": { - "existingJavaType": "java.util.ArrayList", - "title": "The asks on the book" - }, - "bidCount": { - "existingJavaType": "java.lang.Integer", - "title": "The volume of bids on the book" - }, - "askCount": { - "existingJavaType": "java.lang.Integer", - "title": "The volume of asks on the book" - }, - "spread": { - "existingJavaType": "java.lang.Double", - "title": "The spread" - }, - "updated": { - "existingJavaType": "java.lang.Long", - "title": "Updated timestamp" - } - } -} diff --git a/schema_json/polygon/snapshot/ticker/book/book_order.json b/schema_json/polygon/snapshot/ticker/book/book_order.json deleted file mode 100644 index baf1a054..00000000 --- a/schema_json/polygon/snapshot/ticker/book/book_order.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "type": "object", - "title": "See (documentation not yet public)", - "properties": { - "p": { - "existingJavaType": "java.lang.Double", - "title": "The price" - }, - "x": { - "existingJavaType": "java.util.HashMap", - "title": "The map of an exchange ID to its order volume" - } - } -} diff --git a/schema_json/polygon/snapshot/ticker/last_quote.json b/schema_json/polygon/snapshot/ticker/last_quote.json deleted file mode 100644 index 7e89f5b7..00000000 --- a/schema_json/polygon/snapshot/ticker/last_quote.json +++ /dev/null @@ -1,30 +0,0 @@ -{ - "type": "object", - "title": "See https://polygon.io/docs/#!/Stocks--Equities/get_v2_snapshot_locale_us_markets_stocks_tickers", - "properties": { - "p": { - "javaName": "bp", - "existingJavaType": "java.lang.Double", - "title": "Bid Price" - }, - "s": { - "javaName": "bs", - "existingJavaType": "java.lang.Integer", - "title": "Bid size in lots" - }, - "P": { - "javaName": "ap", - "existingJavaType": "java.lang.Double", - "title": "Ask Price" - }, - "S": { - "javaName": "as", - "existingJavaType": "java.lang.Integer", - "title": "Ask size in lots" - }, - "t": { - "existingJavaType": "java.lang.Long", - "title": "Last Updated timestamp ( Nanosecond Timestamp )" - } - } -} diff --git a/schema_json/polygon/snapshot/ticker/last_trade.json b/schema_json/polygon/snapshot/ticker/last_trade.json deleted file mode 100644 index c9c63ed4..00000000 --- a/schema_json/polygon/snapshot/ticker/last_trade.json +++ /dev/null @@ -1,38 +0,0 @@ -{ - "type": "object", - "title": "See https://polygon.io/docs/#!/Stocks--Equities/get_v2_snapshot_locale_us_markets_stocks_tickers", - "properties": { - "c1": { - "existingJavaType": "java.lang.Integer", - "title": "Condition 1 of this trade" - }, - "c2": { - "existingJavaType": "java.lang.Integer", - "title": "Condition 2 of this trade" - }, - "c3": { - "existingJavaType": "java.lang.Integer", - "title": "Condition 3 of this trade" - }, - "c4": { - "existingJavaType": "java.lang.Integer", - "title": "Condition 4 of this trade" - }, - "e": { - "existingJavaType": "java.lang.Integer", - "title": "The exchange this trade happened on" - }, - "p": { - "existingJavaType": "java.lang.Double", - "title": "Price of the trade" - }, - "s": { - "existingJavaType": "java.lang.Integer", - "title": "Size of the trade" - }, - "t": { - "existingJavaType": "java.lang.Long", - "title": "Timestamp of this trade" - } - } -} diff --git a/schema_json/polygon/snapshot/ticker/snapshot_ticker.json b/schema_json/polygon/snapshot/ticker/snapshot_ticker.json deleted file mode 100644 index 50bfd180..00000000 --- a/schema_json/polygon/snapshot/ticker/snapshot_ticker.json +++ /dev/null @@ -1,42 +0,0 @@ -{ - "type": "object", - "title": "See https://polygon.io/docs/#!/Stocks--Equities/get_v2_snapshot_locale_us_markets_stocks_tickers", - "properties": { - "ticker": { - "existingJavaType": "java.lang.String", - "title": "Ticker of the object" - }, - "day": { - "existingJavaType": "net.jacobpeterson.domain.polygon.snapshot.ticker.TickerCandle", - "title": "day" - }, - "lastTrade": { - "existingJavaType": "net.jacobpeterson.domain.polygon.snapshot.ticker.LastTrade", - "title": "lastTrade" - }, - "lastQuote": { - "existingJavaType": "net.jacobpeterson.domain.polygon.snapshot.ticker.LastQuote", - "title": "lastQuote" - }, - "min": { - "existingJavaType": "net.jacobpeterson.domain.polygon.snapshot.ticker.TickerCandle", - "title": "min" - }, - "prevDay": { - "existingJavaType": "net.jacobpeterson.domain.polygon.snapshot.ticker.TickerCandle", - "title": "prevDay" - }, - "todaysChange": { - "existingJavaType": "java.lang.Double", - "title": "Value of the change from previous day" - }, - "todaysChangePerc": { - "existingJavaType": "java.lang.Double", - "title": "Percentage change since previous day" - }, - "updated": { - "existingJavaType": "java.lang.Long", - "title": "Last Updated timestamp" - } - } -} diff --git a/schema_json/polygon/snapshot/ticker/ticker_candle.json b/schema_json/polygon/snapshot/ticker/ticker_candle.json deleted file mode 100644 index 4a0b63a8..00000000 --- a/schema_json/polygon/snapshot/ticker/ticker_candle.json +++ /dev/null @@ -1,26 +0,0 @@ -{ - "type": "object", - "title": "See https://polygon.io/docs/#!/Stocks--Equities/get_v2_snapshot_locale_us_markets_stocks_tickers", - "properties": { - "c": { - "existingJavaType": "java.lang.Double", - "title": "Close price" - }, - "h": { - "existingJavaType": "java.lang.Double", - "title": "High price" - }, - "l": { - "existingJavaType": "java.lang.Double", - "title": "Low price" - }, - "o": { - "existingJavaType": "java.lang.Double", - "title": "Open price" - }, - "v": { - "existingJavaType": "java.lang.Double", - "title": "Volume" - } - } -} diff --git a/schema_json/polygon/stock_dividends/stock_dividend.json b/schema_json/polygon/stock_dividends/stock_dividend.json deleted file mode 100644 index 89f6a893..00000000 --- a/schema_json/polygon/stock_dividends/stock_dividend.json +++ /dev/null @@ -1,42 +0,0 @@ -{ - "type": "object", - "title": "See https://polygon.io/docs/#!/Reference/get_v2_reference_dividends_symbol", - "properties": { - "symbol": { - "existingJavaType": "java.lang.String", - "title": "symbol" - }, - "type": { - "existingJavaType": "java.lang.String", - "title": "Refers to the dividend payment type
- Dividend income
- Interest income
- Stock dividend
- Short term capital gain
- Medium term capital gain
- Long term capital gain
- Unspecified term capital gain
" - }, - "exDate": { - "existingJavaType": "java.lang.String", - "title": "Execution date of the trade" - }, - "paymentDate": { - "existingJavaType": "java.time.ZonedDateTime", - "title": "Payment date of the trade" - }, - "recordDate": { - "existingJavaType": "java.time.ZonedDateTime", - "title": "Record date of the trade" - }, - "declaredDate": { - "existingJavaType": "java.time.ZonedDateTime", - "title": "Declared date of the trade" - }, - "amount": { - "existingJavaType": "java.lang.Double", - "title": "Amount of the dividend" - }, - "qualified": { - "existingJavaType": "java.lang.String", - "title": "Refers to the dividend income type
- P = Partially qualified income
- Q = Qualified income
- N = Unqualified income
- null = N/A or unknown
Can be P, Q, N or null" - }, - "flag": { - "existingJavaType": "java.lang.String", - "title": "Refers to the dividend flag, if set
FI = Final dividend, div ends or instrument ends
LI = Liquidation, instrument liquidates
PR = Proceeds of a sale of rights or shares
RE = Redemption of rights
AC = Accrued dividend
AR = Payment in arrears
AD = Additional payment
EX = Extra payment
SP = Special dividend
YE = Year end
UR = Unknown rate
SU = Regular dividend is suspended" - } - } -} diff --git a/schema_json/polygon/stock_dividends/stock_dividends_response.json b/schema_json/polygon/stock_dividends/stock_dividends_response.json deleted file mode 100644 index b35c9456..00000000 --- a/schema_json/polygon/stock_dividends/stock_dividends_response.json +++ /dev/null @@ -1,18 +0,0 @@ -{ - "type": "object", - "title": "See https://polygon.io/docs/#!/Reference/get_v2_reference_dividends_symbol", - "properties": { - "status": { - "existingJavaType": "java.lang.String", - "title": "Status" - }, - "count": { - "existingJavaType": "java.lang.Integer", - "title": "Count" - }, - "results": { - "existingJavaType": "java.util.ArrayList", - "title": "Results" - } - } -} diff --git a/schema_json/polygon/stock_financials/stock_financials.json b/schema_json/polygon/stock_financials/stock_financials.json deleted file mode 100644 index fbf25368..00000000 --- a/schema_json/polygon/stock_financials/stock_financials.json +++ /dev/null @@ -1,446 +0,0 @@ -{ - "type": "object", - "title": "See https://polygon.io/docs/#!/Reference/get_v2_reference_financials_symbol", - "properties": { - "ticker": { - "existingJavaType": "java.lang.String", - "title": "ticker" - }, - "period": { - "existingJavaType": "java.lang.String", - "title": "Reporting period. Can be Q, T, QA, TA, Y or YA" - }, - "calendarDate": { - "existingJavaType": "java.lang.String", - "title": "calendarDate" - }, - "reportPeriod": { - "existingJavaType": "java.lang.String", - "title": "reportPeriod" - }, - "updated": { - "existingJavaType": "java.lang.String", - "title": "updated" - }, - "accumulatedOtherComprehensiveIncome": { - "existingJavaType": "java.lang.Double", - "title": "accumulatedOtherComprehensiveIncome" - }, - "assets": { - "existingJavaType": "java.lang.Double", - "title": "assets" - }, - "assetsAverage": { - "existingJavaType": "java.lang.Double", - "title": "assetsAverage" - }, - "assetsCurrent": { - "existingJavaType": "java.lang.Double", - "title": "assetsCurrent" - }, - "assetTurnover": { - "existingJavaType": "java.lang.Double", - "title": "assetTurnover" - }, - "assetsNonCurrent": { - "existingJavaType": "java.lang.Double", - "title": "assetsNonCurrent" - }, - "bookValuePerShare": { - "existingJavaType": "java.lang.Double", - "title": "bookValuePerShare" - }, - "capitalExpenditure": { - "existingJavaType": "java.lang.Double", - "title": "capitalExpenditure" - }, - "cashAndEquivalents": { - "existingJavaType": "java.lang.Double", - "title": "cashAndEquivalents" - }, - "cashAndEquivalentsUSD": { - "existingJavaType": "java.lang.Double", - "title": "cashAndEquivalentsUSD" - }, - "costOfRevenue": { - "existingJavaType": "java.lang.Double", - "title": "costOfRevenue" - }, - "consolidatedIncome": { - "existingJavaType": "java.lang.Double", - "title": "consolidatedIncome" - }, - "currentRatio": { - "existingJavaType": "java.lang.Double", - "title": "currentRatio" - }, - "debtToEquityRatio": { - "existingJavaType": "java.lang.Double", - "title": "debtToEquityRatio" - }, - "debt": { - "existingJavaType": "java.lang.Double", - "title": "debt" - }, - "debtCurrent": { - "existingJavaType": "java.lang.Double", - "title": "debtCurrent" - }, - "debtNonCurrent": { - "existingJavaType": "java.lang.Double", - "title": "debtNonCurrent" - }, - "debtUSD": { - "existingJavaType": "java.lang.Double", - "title": "debtUSD" - }, - "deferredRevenue": { - "existingJavaType": "java.lang.Double", - "title": "deferredRevenue" - }, - "depreciationAmortizationAndAccretion": { - "existingJavaType": "java.lang.Double", - "title": "depreciationAmortizationAndAccretion" - }, - "deposits": { - "existingJavaType": "java.lang.Double", - "title": "deposits" - }, - "dividendYield": { - "existingJavaType": "java.lang.Double", - "title": "dividendYield" - }, - "dividendsPerBasicCommonShare": { - "existingJavaType": "java.lang.Double", - "title": "dividendsPerBasicCommonShare" - }, - "earningBeforeInterestTaxes": { - "existingJavaType": "java.lang.Double", - "title": "earningBeforeInterestTaxes" - }, - "earningsBeforeInterestTaxesDepreciationAmortization": { - "existingJavaType": "java.lang.Double", - "title": "earningsBeforeInterestTaxesDepreciationAmortization" - }, - "EBITDAMargin": { - "existingJavaType": "java.lang.Double", - "title": "EBITDAMargin" - }, - "earningsBeforeInterestTaxesDepreciationAmortizationUSD": { - "existingJavaType": "java.lang.Double", - "title": "earningsBeforeInterestTaxesDepreciationAmortizationUSD" - }, - "earningBeforeInterestTaxesUSD": { - "existingJavaType": "java.lang.Double", - "title": "earningBeforeInterestTaxesUSD" - }, - "earningsBeforeTax": { - "existingJavaType": "java.lang.Double", - "title": "earningsBeforeTax" - }, - "earningsPerBasicShare": { - "existingJavaType": "java.lang.Double", - "title": "earningsPerBasicShare" - }, - "earningsPerDilutedShare": { - "existingJavaType": "java.lang.Double", - "title": "earningsPerDilutedShare" - }, - "earningsPerBasicShareUSD": { - "existingJavaType": "java.lang.Double", - "title": "earningsPerBasicShareUSD" - }, - "shareholdersEquity": { - "existingJavaType": "java.lang.Double", - "title": "shareholdersEquity" - }, - "averageEquity": { - "existingJavaType": "java.lang.Double", - "title": "averageEquity" - }, - "shareholdersEquityUSD": { - "existingJavaType": "java.lang.Double", - "title": "shareholdersEquityUSD" - }, - "enterpriseValue": { - "existingJavaType": "java.lang.Double", - "title": "enterpriseValue" - }, - "enterpriseValueOverEBIT": { - "existingJavaType": "java.lang.Double", - "title": "enterpriseValueOverEBIT" - }, - "enterpriseValueOverEBITDA": { - "existingJavaType": "java.lang.Double", - "title": "enterpriseValueOverEBITDA" - }, - "freeCashFlow": { - "existingJavaType": "java.lang.Double", - "title": "freeCashFlow" - }, - "freeCashFlowPerShare": { - "existingJavaType": "java.lang.Double", - "title": "freeCashFlowPerShare" - }, - "foreignCurrencyUSDExchangeRate": { - "existingJavaType": "java.lang.Double", - "title": "foreignCurrencyUSDExchangeRate" - }, - "grossProfit": { - "existingJavaType": "java.lang.Double", - "title": "grossProfit" - }, - "grossMargin": { - "existingJavaType": "java.lang.Double", - "title": "grossMargin" - }, - "goodwillAndIntangibleAssets": { - "existingJavaType": "java.lang.Double", - "title": "goodwillAndIntangibleAssets" - }, - "interestExpense": { - "existingJavaType": "java.lang.Double", - "title": "interestExpense" - }, - "investedCapital": { - "existingJavaType": "java.lang.Double", - "title": "investedCapital" - }, - "investedCapitalAverage": { - "existingJavaType": "java.lang.Double", - "title": "investedCapitalAverage" - }, - "inventory": { - "existingJavaType": "java.lang.Double", - "title": "inventory" - }, - "investments": { - "existingJavaType": "java.lang.Double", - "title": "investments" - }, - "investmentsCurrent": { - "existingJavaType": "java.lang.Double", - "title": "investmentsCurrent" - }, - "investmentsNonCurrent": { - "existingJavaType": "java.lang.Double", - "title": "investmentsNonCurrent" - }, - "totalLiabilities": { - "existingJavaType": "java.lang.Double", - "title": "totalLiabilities" - }, - "currentLiabilities": { - "existingJavaType": "java.lang.Double", - "title": "currentLiabilities" - }, - "liabilitiesNonCurrent": { - "existingJavaType": "java.lang.Double", - "title": "liabilitiesNonCurrent" - }, - "marketCapitalization": { - "existingJavaType": "java.lang.Double", - "title": "marketCapitalization" - }, - "netCashFlow": { - "existingJavaType": "java.lang.Double", - "title": "netCashFlow" - }, - "netCashFlowBusinessAcquisitionsDisposals": { - "existingJavaType": "java.lang.Double", - "title": "netCashFlowBusinessAcquisitionsDisposals" - }, - "issuanceEquityShares": { - "existingJavaType": "java.lang.Double", - "title": "issuanceEquityShares" - }, - "issuanceDebtSecurities": { - "existingJavaType": "java.lang.Double", - "title": "issuanceDebtSecurities" - }, - "paymentDividendsOtherCashDistributions": { - "existingJavaType": "java.lang.Double", - "title": "paymentDividendsOtherCashDistributions" - }, - "netCashFlowFromFinancing": { - "existingJavaType": "java.lang.Double", - "title": "netCashFlowFromFinancing" - }, - "netCashFlowFromInvesting": { - "existingJavaType": "java.lang.Double", - "title": "netCashFlowFromInvesting" - }, - "netCashFlowInvestmentAcquisitionsDisposals": { - "existingJavaType": "java.lang.Double", - "title": "netCashFlowInvestmentAcquisitionsDisposals" - }, - "netCashFlowFromOperations": { - "existingJavaType": "java.lang.Double", - "title": "netCashFlowFromOperations" - }, - "effectOfExchangeRateChangesOnCash": { - "existingJavaType": "java.lang.Double", - "title": "effectOfExchangeRateChangesOnCash" - }, - "netIncome": { - "existingJavaType": "java.lang.Double", - "title": "netIncome" - }, - "netIncomeCommonStock": { - "existingJavaType": "java.lang.Double", - "title": "netIncomeCommonStock" - }, - "netIncomeCommonStockUSD": { - "existingJavaType": "java.lang.Double", - "title": "netIncomeCommonStockUSD" - }, - "netLossIncomeFromDiscontinuedOperations": { - "existingJavaType": "java.lang.Double", - "title": "netLossIncomeFromDiscontinuedOperations" - }, - "netIncomeToNonControllingInterests": { - "existingJavaType": "java.lang.Double", - "title": "netIncomeToNonControllingInterests" - }, - "profitMargin": { - "existingJavaType": "java.lang.Double", - "title": "profitMargin" - }, - "operatingExpenses": { - "existingJavaType": "java.lang.Double", - "title": "operatingExpenses" - }, - "operatingIncome": { - "existingJavaType": "java.lang.Double", - "title": "operatingIncome" - }, - "tradeAndNonTradePayables": { - "existingJavaType": "java.lang.Double", - "title": "tradeAndNonTradePayables" - }, - "payoutRatio": { - "existingJavaType": "java.lang.Double", - "title": "payoutRatio" - }, - "priceToBookValue": { - "existingJavaType": "java.lang.Double", - "title": "priceToBookValue" - }, - "priceEarnings": { - "existingJavaType": "java.lang.Double", - "title": "priceEarnings" - }, - "priceToEarningsRatio": { - "existingJavaType": "java.lang.Double", - "title": "priceToEarningsRatio" - }, - "propertyPlantEquipmentNet": { - "existingJavaType": "java.lang.Double", - "title": "propertyPlantEquipmentNet" - }, - "preferredDividendsIncomeStatementImpact": { - "existingJavaType": "java.lang.Double", - "title": "preferredDividendsIncomeStatementImpact" - }, - "sharePriceAdjustedClose": { - "existingJavaType": "java.lang.Double", - "title": "sharePriceAdjustedClose" - }, - "priceSales": { - "existingJavaType": "java.lang.Double", - "title": "priceSales" - }, - "priceToSalesRatio": { - "existingJavaType": "java.lang.Double", - "title": "priceToSalesRatio" - }, - "tradeAndNonTradeReceivables": { - "existingJavaType": "java.lang.Double", - "title": "tradeAndNonTradeReceivables" - }, - "accumulatedRetainedEarningsDeficit": { - "existingJavaType": "java.lang.Double", - "title": "accumulatedRetainedEarningsDeficit" - }, - "revenues": { - "existingJavaType": "java.lang.Double", - "title": "revenues" - }, - "revenuesUSD": { - "existingJavaType": "java.lang.Double", - "title": "revenuesUSD" - }, - "researchAndDevelopmentExpense": { - "existingJavaType": "java.lang.Double", - "title": "researchAndDevelopmentExpense" - }, - "returnOnAverageAssets": { - "existingJavaType": "java.lang.Double", - "title": "returnOnAverageAssets" - }, - "returnOnAverageEquity": { - "existingJavaType": "java.lang.Double", - "title": "returnOnAverageEquity" - }, - "returnOnInvestedCapital": { - "existingJavaType": "java.lang.Double", - "title": "returnOnInvestedCapital" - }, - "returnOnSales": { - "existingJavaType": "java.lang.Double", - "title": "returnOnSales" - }, - "shareBasedCompensation": { - "existingJavaType": "java.lang.Double", - "title": "shareBasedCompensation" - }, - "sellingGeneralAndAdministrativeExpense": { - "existingJavaType": "java.lang.Double", - "title": "sellingGeneralAndAdministrativeExpense" - }, - "shareFactor": { - "existingJavaType": "java.lang.Double", - "title": "shareFactor" - }, - "shares": { - "existingJavaType": "java.lang.Double", - "title": "shares" - }, - "weightedAverageShares": { - "existingJavaType": "java.lang.Double", - "title": "weightedAverageShares" - }, - "weightedAverageSharesDiluted": { - "existingJavaType": "java.lang.Double", - "title": "weightedAverageSharesDiluted" - }, - "salesPerShare": { - "existingJavaType": "java.lang.Double", - "title": "salesPerShare" - }, - "tangibleAssetValue": { - "existingJavaType": "java.lang.Double", - "title": "tangibleAssetValue" - }, - "taxAssets": { - "existingJavaType": "java.lang.Double", - "title": "taxAssets" - }, - "incomeTaxExpense": { - "existingJavaType": "java.lang.Double", - "title": "incomeTaxExpense" - }, - "taxLiabilities": { - "existingJavaType": "java.lang.Double", - "title": "taxLiabilities" - }, - "tangibleAssetsBookValuePerShare": { - "existingJavaType": "java.lang.Double", - "title": "tangibleAssetsBookValuePerShare" - }, - "workingCapital": { - "existingJavaType": "java.lang.Double", - "title": "workingCapital" - } - } -} diff --git a/schema_json/polygon/stock_financials/stock_financials_response.json b/schema_json/polygon/stock_financials/stock_financials_response.json deleted file mode 100644 index e8c86207..00000000 --- a/schema_json/polygon/stock_financials/stock_financials_response.json +++ /dev/null @@ -1,18 +0,0 @@ -{ - "type": "object", - "title": "See https://polygon.io/docs/#!/Reference/get_v2_reference_financials_symbol", - "properties": { - "status": { - "existingJavaType": "java.lang.String", - "title": "Status" - }, - "count": { - "existingJavaType": "java.lang.Integer", - "title": "Count" - }, - "results": { - "existingJavaType": "java.util.ArrayList", - "title": "Results" - } - } -} diff --git a/schema_json/polygon/stock_splits/stock_split.json b/schema_json/polygon/stock_splits/stock_split.json deleted file mode 100644 index 78783882..00000000 --- a/schema_json/polygon/stock_splits/stock_split.json +++ /dev/null @@ -1,38 +0,0 @@ -{ - "type": "object", - "title": "See https://polygon.io/docs/#!/Reference/get_v2_reference_splits_symbol", - "properties": { - "ticker": { - "existingJavaType": "java.lang.String", - "title": "ticker" - }, - "exDate": { - "existingJavaType": "java.lang.String", - "title": "Execution date of the split" - }, - "paymentDate": { - "existingJavaType": "java.lang.String", - "title": "Payment date of the split" - }, - "recordDate": { - "existingJavaType": "java.lang.String", - "title": "Payment date of the split" - }, - "declaredDate": { - "existingJavaType": "java.lang.String", - "title": "Payment date of the split" - }, - "ratio": { - "existingJavaType": "java.lang.Double", - "title": "refers to the split ratio. The split ratio is an inverse of the number of shares that a holder of the stock would have after the split divided by the number of shares that the holder had before. For example: Split ratio of .5 = 2 for 1 split." - }, - "tofactor": { - "existingJavaType": "java.lang.Double", - "title": "To factor of the split. Used to calculate the split ratio forfactor/tofactor = ratio (eg ½ = 0.5)" - }, - "forfactor": { - "existingJavaType": "java.lang.Double", - "title": "For factor of the split. Used to calculate the split ratio forfactor/tofactor = ratio (eg ½ = 0.5)" - } - } -} diff --git a/schema_json/polygon/stock_splits/stock_splits_response.json b/schema_json/polygon/stock_splits/stock_splits_response.json deleted file mode 100644 index 5437ad2a..00000000 --- a/schema_json/polygon/stock_splits/stock_splits_response.json +++ /dev/null @@ -1,18 +0,0 @@ -{ - "type": "object", - "title": "See https://polygon.io/docs/#!/Reference/get_v2_reference_splits_symbol", - "properties": { - "status": { - "existingJavaType": "java.lang.String", - "title": "Status" - }, - "count": { - "existingJavaType": "java.lang.Integer", - "title": "Count" - }, - "results": { - "existingJavaType": "java.util.ArrayList", - "title": "Results" - } - } -} diff --git a/schema_json/polygon/ticker_details/ticker_details.json b/schema_json/polygon/ticker_details/ticker_details.json deleted file mode 100644 index 6a232691..00000000 --- a/schema_json/polygon/ticker_details/ticker_details.json +++ /dev/null @@ -1,94 +0,0 @@ -{ - "type": "object", - "title": "See https://polygon.io/docs/#!/Reference/get_v1_meta_symbols_symbol_company", - "properties": { - "logo": { - "existingJavaType": "java.lang.String", - "title": "URL of the entities logo." - }, - "exchange": { - "existingJavaType": "java.lang.String", - "title": "Symbols primary exchange" - }, - "name": { - "existingJavaType": "java.lang.String", - "title": "Name of the company/entity" - }, - "symbol": { - "existingJavaType": "java.lang.String", - "title": "symbol" - }, - "listdate": { - "existingJavaType": "java.lang.String", - "title": "Date this symbol was listed on the exchange." - }, - "cik": { - "existingJavaType": "java.lang.String", - "title": "Official CIK guid used for SEC database / filings." - }, - "bloomberg": { - "existingJavaType": "java.lang.String", - "title": "Bloomberg guid for this symbol" - }, - "figi": { - "existingJavaType": "java.lang.String", - "title": "guid for the OpenFigi project ( https://openfigi.com/ )" - }, - "lei": { - "existingJavaType": "java.lang.String", - "title": "Legal Entity Identifier (LEI) guid for symbol ( https://en.wikipedia.org/wiki/Legal_Entity_Identifier )" - }, - "sic": { - "existingJavaType": "java.lang.Integer", - "title": "Standard Industrial Classification (SIC) id for symbol ( https://en.wikipedia.org/wiki/Standard_Industrial_Classification )" - }, - "country": { - "existingJavaType": "java.lang.String", - "title": "Country in which this company is registered" - }, - "industry": { - "existingJavaType": "java.lang.String", - "title": "Industry this company operates in" - }, - "sector": { - "existingJavaType": "java.lang.String", - "title": "Sector of the indsutry in which this symbol operates in" - }, - "marketcap": { - "existingJavaType": "java.lang.Double", - "title": "Current market cap for this company" - }, - "employees": { - "existingJavaType": "java.lang.Integer", - "title": "Approximate number of employees" - }, - "phone": { - "existingJavaType": "java.lang.String", - "title": "Phone number for this company. Usually corporate contact number." - }, - "ceo": { - "existingJavaType": "java.lang.String", - "title": "Name of the companies current CEO" - }, - "url": { - "existingJavaType": "java.lang.String", - "title": "URL of the companies website" - }, - "title": { - "existingJavaType": "java.lang.String", - "title": "A description of the company and what they do/offer" - }, - "similar": { - "existingJavaType": "java.util.ArrayList", - "title": "similar" - }, - "tags": { - "existingJavaType": "java.util.ArrayList", - "title": "tags" - }, - "updated": { - "existingJavaType": "java.lang.String", - "title": "Last time this company record was updated." - } - } -} diff --git a/schema_json/polygon/ticker_news/ticker_news.json b/schema_json/polygon/ticker_news/ticker_news.json deleted file mode 100644 index 773741a0..00000000 --- a/schema_json/polygon/ticker_news/ticker_news.json +++ /dev/null @@ -1,38 +0,0 @@ -{ - "type": "object", - "title": "See https://polygon.io/docs/#!/Reference/get_v1_meta_symbols_symbol_news", - "properties": { - "symbols": { - "existingJavaType": "java.util.ArrayList", - "title": "symbols" - }, - "title": { - "existingJavaType": "java.lang.String", - "title": "Name of the article" - }, - "url": { - "existingJavaType": "java.lang.String", - "title": "URL of this article" - }, - "source": { - "existingJavaType": "java.lang.String", - "title": "Source of this article" - }, - "summary": { - "existingJavaType": "java.lang.String", - "title": "Short summary of the article" - }, - "image": { - "existingJavaType": "java.lang.String", - "title": "URL of the image for this article, if found" - }, - "timestamp": { - "existingJavaType": "java.time.ZonedDateTime", - "title": "Timestamp of the article" - }, - "keywords": { - "existingJavaType": "java.util.ArrayList", - "title": "keywords" - } - } -} diff --git a/schema_json/polygon/ticker_types/ticker_types.json b/schema_json/polygon/ticker_types/ticker_types.json deleted file mode 100644 index a9571c6d..00000000 --- a/schema_json/polygon/ticker_types/ticker_types.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "type": "object", - "title": "See https://polygon.io/docs/#!/Reference/get_v2_reference_types", - "properties": { - "types": { - "existingJavaType": "java.util.HashMap", - "title": "Types" - }, - "indexTypes": { - "existingJavaType": "java.util.HashMap", - "title": "Index Types" - } - } -} diff --git a/schema_json/polygon/ticker_types/ticket_types_response.json b/schema_json/polygon/ticker_types/ticket_types_response.json deleted file mode 100644 index 4f22bb9c..00000000 --- a/schema_json/polygon/ticker_types/ticket_types_response.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "type": "object", - "title": "See https://polygon.io/docs/#!/Reference/get_v2_reference_types", - "properties": { - "status": { - "existingJavaType": "java.lang.String", - "title": "Status" - }, - "results": { - "existingJavaType": "net.jacobpeterson.domain.polygon.tickertypes.TickerTypes", - "title": "Results" - } - } -} diff --git a/schema_json/polygon/tickers/ticker/attributes.json b/schema_json/polygon/tickers/ticker/attributes.json deleted file mode 100644 index f40b87c5..00000000 --- a/schema_json/polygon/tickers/ticker/attributes.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "type": "object", - "title": "See https://polygon.io/docs/get_v2_reference_tickers_anchor", - "properties": { - "currencyName": { - "existingJavaType": "java.lang.String", - "title": "The name of the variable/quote currency." - }, - "currency": { - "existingJavaType": "java.lang.String", - "title": "The currency code of the variable/quote currency." - }, - "baseName": { - "existingJavaType": "java.lang.String", - "title": "The name of the base currency." - }, - "base": { - "existingJavaType": "java.lang.String", - "title": "The currency code of the base currency." - } - } -} diff --git a/schema_json/polygon/tickers/ticker/codes.json b/schema_json/polygon/tickers/ticker/codes.json deleted file mode 100644 index 16947b1e..00000000 --- a/schema_json/polygon/tickers/ticker/codes.json +++ /dev/null @@ -1,26 +0,0 @@ -{ - "type": "object", - "title": "See https://polygon.io/docs/get_v2_reference_tickers_anchor", - "properties": { - "cik": { - "existingJavaType": "java.lang.String", - "title": "The CIK number for this ticker." - }, - "figi": { - "existingJavaType": "java.lang.String", - "title": "The OpenFIGI number for this ticker." - }, - "cfigi": { - "existingJavaType": "java.lang.String", - "title": "The composite OpenFIGI number for this ticker." - }, - "scfigi": { - "existingJavaType": "java.lang.String", - "title": "The shared Class OpenFIGI number for this ticker." - }, - "figiuid": { - "existingJavaType": "java.lang.String", - "title": "The unique OpenFIGI ID number for this ticker." - } - } -} diff --git a/schema_json/polygon/tickers/ticker/ticker.json b/schema_json/polygon/tickers/ticker/ticker.json deleted file mode 100644 index e792dcf4..00000000 --- a/schema_json/polygon/tickers/ticker/ticker.json +++ /dev/null @@ -1,50 +0,0 @@ -{ - "type": "object", - "title": "See https://polygon.io/docs/get_v2_reference_tickers_anchor", - "properties": { - "ticker": { - "existingJavaType": "java.lang.String", - "title": "The exchange symbol that this item is traded under." - }, - "name": { - "existingJavaType": "java.lang.String", - "title": "The name of the item." - }, - "market": { - "existingJavaType": "java.lang.String", - "title": "The market in which this ticker participates." - }, - "locale": { - "existingJavaType": "java.lang.String", - "title": "The locale of where this ticker is traded." - }, - "currency": { - "existingJavaType": "java.lang.String", - "title": "The currency this ticker is traded in." - }, - "active": { - "existingJavaType": "java.lang.Boolean", - "title": "Whether or not the ticker is active. False means the ticker has been delisted." - }, - "primaryExch": { - "existingJavaType": "java.lang.String", - "title": "The listing exchange for this ticker." - }, - "url": { - "existingJavaType": "java.lang.String", - "title": "A URL that can be used to get more detailed information about the ticker." - }, - "updated": { - "existingJavaType": "java.time.LocalDate", - "title": "The last time this ticker record was updated." - }, - "attrs": { - "existingJavaType": "net.jacobpeterson.domain.polygon.tickers.ticker.Attributes", - "title": "The attributes for a currency." - }, - "codes": { - "existingJavaType": "net.jacobpeterson.domain.polygon.tickers.ticker.Codes", - "title": "The codes for a ticker." - } - } -} diff --git a/schema_json/polygon/tickers/tickers_response.json b/schema_json/polygon/tickers/tickers_response.json deleted file mode 100644 index 9f9d1b42..00000000 --- a/schema_json/polygon/tickers/tickers_response.json +++ /dev/null @@ -1,26 +0,0 @@ -{ - "type": "object", - "title": "See https://polygon.io/docs/get_v2_reference_tickers_anchor", - "properties": { - "status": { - "existingJavaType": "java.lang.String", - "title": "The status of this request's response." - }, - "page": { - "existingJavaType": "java.lang.Integer", - "title": "The page number of the current response." - }, - "perPage": { - "existingJavaType": "java.lang.Integer", - "title": "The number of results per page." - }, - "count": { - "existingJavaType": "java.lang.Integer", - "title": "The total number of results for this request." - }, - "tickers": { - "existingJavaType": "java.util.ArrayList", - "title": "The tickers." - } - } -} diff --git a/schema_json/polygon/websocket/aggregate/aggregate_message.json b/schema_json/polygon/websocket/aggregate/aggregate_message.json deleted file mode 100644 index 4b2e1aa7..00000000 --- a/schema_json/polygon/websocket/aggregate/aggregate_message.json +++ /dev/null @@ -1,53 +0,0 @@ -{ - "type": "object", - "extends": { - "existingJavaType": "net.jacobpeterson.domain.polygon.websocket.PolygonStreamMessage" - }, - "title": "See https://polygon.io/sockets", - "properties": { - "v": { - "existingJavaType": "java.lang.Long", - "title": "Tick Volume" - }, - "av": { - "existingJavaType": "java.lang.Long", - "title": "Accumulated Volume ( Today )" - }, - "op": { - "existingJavaType": "java.lang.Double", - "title": "Today's official opening price" - }, - "vw": { - "existingJavaType": "java.lang.Double", - "title": "VWAP (Volume Weighted Average Price)" - }, - "o": { - "existingJavaType": "java.lang.Double", - "title": "Tick Open Price" - }, - "c": { - "existingJavaType": "java.lang.Double", - "title": "Tick Close Price" - }, - "h": { - "existingJavaType": "java.lang.Double", - "title": "Tick High Price" - }, - "l": { - "existingJavaType": "java.lang.Double", - "title": "Tick Low Price" - }, - "a": { - "existingJavaType": "java.lang.Double", - "title": "Tick Average / VWAP Price" - }, - "s": { - "existingJavaType": "java.lang.Long", - "title": "Tick Start Timestamp ( Unix MS )" - }, - "e": { - "existingJavaType": "java.lang.Long", - "title": "Tick End Timestamp ( Unix MS )" - } - } -} diff --git a/schema_json/polygon/websocket/aggregate/aggregate_per_minute_message.json b/schema_json/polygon/websocket/aggregate/aggregate_per_minute_message.json deleted file mode 100644 index 0d4997b6..00000000 --- a/schema_json/polygon/websocket/aggregate/aggregate_per_minute_message.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "type": "object", - "extends": { - "existingJavaType": "net.jacobpeterson.domain.polygon.websocket.aggregate.AggregateMessage" - }, - "title": "See https://polygon.io/sockets" -} diff --git a/schema_json/polygon/websocket/aggregate/aggregate_per_second_message.json b/schema_json/polygon/websocket/aggregate/aggregate_per_second_message.json deleted file mode 100644 index 0d4997b6..00000000 --- a/schema_json/polygon/websocket/aggregate/aggregate_per_second_message.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "type": "object", - "extends": { - "existingJavaType": "net.jacobpeterson.domain.polygon.websocket.aggregate.AggregateMessage" - }, - "title": "See https://polygon.io/sockets" -} diff --git a/schema_json/polygon/websocket/polygon_stream_message.json b/schema_json/polygon/websocket/polygon_stream_message.json deleted file mode 100644 index 5f57f96b..00000000 --- a/schema_json/polygon/websocket/polygon_stream_message.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "type": "object", - "javaInterfaces": [ - "net.jacobpeterson.abstracts.websocket.message.StreamMessage" - ], - "title": "See https://polygon.io/sockets", - "properties": { - "ev": { - "existingJavaType": "net.jacobpeterson.polygon.websocket.message.PolygonStreamMessageType", - "title": "The event type" - }, - "sym": { - "existingJavaType": "java.lang.String", - "title": "Symbol Ticker" - } - } -} diff --git a/schema_json/polygon/websocket/quote/quote_message.json b/schema_json/polygon/websocket/quote/quote_message.json deleted file mode 100644 index e5492076..00000000 --- a/schema_json/polygon/websocket/quote/quote_message.json +++ /dev/null @@ -1,41 +0,0 @@ -{ - "type": "object", - "extends": { - "existingJavaType": "net.jacobpeterson.domain.polygon.websocket.PolygonStreamMessage" - }, - "title": "See https://polygon.io/sockets", - "properties": { - "bx": { - "existingJavaType": "java.lang.String", - "title": "Bix Exchange ID" - }, - "bp": { - "existingJavaType": "java.lang.Double", - "title": "Bid Price" - }, - "bs": { - "existingJavaType": "java.lang.Integer", - "title": "Bid Size" - }, - "ax": { - "existingJavaType": "java.lang.String", - "title": "Ask Exchange ID" - }, - "ap": { - "existingJavaType": "java.lang.Double", - "title": "Ask Price" - }, - "as": { - "existingJavaType": "java.lang.Integer", - "title": "Ask Size" - }, - "c": { - "existingJavaType": "java.lang.Integer", - "title": "Quote Condition" - }, - "t": { - "existingJavaType": "java.lang.Long", - "title": "Quote Timestamp ( Unix MS )" - } - } -} diff --git a/schema_json/polygon/websocket/status/status_message.json b/schema_json/polygon/websocket/status/status_message.json deleted file mode 100644 index 6d06364c..00000000 --- a/schema_json/polygon/websocket/status/status_message.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "type": "object", - "extends": { - "existingJavaType": "net.jacobpeterson.domain.polygon.websocket.PolygonStreamMessage" - }, - "title": "See https://polygon.io/sockets", - "properties": { - "status": { - "existingJavaType": "java.lang.String", - "title": "The status" - }, - "message": { - "existingJavaType": "java.lang.String", - "title": "The message" - } - } -} diff --git a/schema_json/polygon/websocket/trade/trade_message.json b/schema_json/polygon/websocket/trade/trade_message.json deleted file mode 100644 index 1e904582..00000000 --- a/schema_json/polygon/websocket/trade/trade_message.json +++ /dev/null @@ -1,37 +0,0 @@ -{ - "type": "object", - "extends": { - "existingJavaType": "net.jacobpeterson.domain.polygon.websocket.PolygonStreamMessage" - }, - "title": "See https://polygon.io/sockets", - "properties": { - "x": { - "existingJavaType": "java.lang.String", - "title": "Exchange ID" - }, - "i": { - "existingJavaType": "java.lang.String", - "title": "Trade ID" - }, - "z": { - "existingJavaType": "java.lang.Integer", - "title": "Tape ( 1=A 2=B 3=C)" - }, - "p": { - "existingJavaType": "java.lang.Double", - "title": "Price" - }, - "s": { - "existingJavaType": "java.lang.Integer", - "title": "Trade Size" - }, - "c": { - "existingJavaType": "java.util.ArrayList", - "title": "Trade Conditions" - }, - "t": { - "existingJavaType": "java.lang.Long", - "title": "Trade Timestamp ( Unix MS )" - } - } -} diff --git a/src/main/java/net/jacobpeterson/polygon/PolygonAPI.java b/src/main/java/net/jacobpeterson/polygon/PolygonAPI.java deleted file mode 100644 index 699c1c39..00000000 --- a/src/main/java/net/jacobpeterson/polygon/PolygonAPI.java +++ /dev/null @@ -1,1047 +0,0 @@ -package net.jacobpeterson.polygon; - -import com.google.common.base.Preconditions; -import com.google.gson.reflect.TypeToken; -import com.mashape.unirest.http.HttpResponse; -import net.jacobpeterson.abstracts.enums.SortDirection; -import net.jacobpeterson.abstracts.websocket.exception.WebsocketException; -import net.jacobpeterson.domain.polygon.aggregates.AggregatesResponse; -import net.jacobpeterson.domain.polygon.conditionsmapping.ConditionsMapping; -import net.jacobpeterson.domain.polygon.dailyopenclose.DailyOpenClose; -import net.jacobpeterson.domain.polygon.exchanges.Exchange; -import net.jacobpeterson.domain.polygon.groupeddaily.GroupedDailyResponse; -import net.jacobpeterson.domain.polygon.historicquotes.HistoricQuotesResponse; -import net.jacobpeterson.domain.polygon.historictrades.HistoricTradesResponse; -import net.jacobpeterson.domain.polygon.lastquote.LastQuoteResponse; -import net.jacobpeterson.domain.polygon.lasttrade.LastTradeResponse; -import net.jacobpeterson.domain.polygon.locales.LocalesResponse; -import net.jacobpeterson.domain.polygon.marketholidays.MarketHoliday; -import net.jacobpeterson.domain.polygon.markets.MarketsResponse; -import net.jacobpeterson.domain.polygon.marketstatus.MarketStatus; -import net.jacobpeterson.domain.polygon.previousclose.PreviousCloseResponse; -import net.jacobpeterson.domain.polygon.snapshot.SnapshotAllTickersResponse; -import net.jacobpeterson.domain.polygon.snapshot.SnapshotGainersLosersResponse; -import net.jacobpeterson.domain.polygon.snapshot.SnapshotSingleTickerResponse; -import net.jacobpeterson.domain.polygon.snapshot.SnapshotTickerBook; -import net.jacobpeterson.domain.polygon.stockdividends.StockDividendsResponse; -import net.jacobpeterson.domain.polygon.stockfinancials.StockFinancialsResponse; -import net.jacobpeterson.domain.polygon.stocksplits.StockSplitsResponse; -import net.jacobpeterson.domain.polygon.tickerdetails.TickerDetails; -import net.jacobpeterson.domain.polygon.tickernews.TickerNews; -import net.jacobpeterson.domain.polygon.tickers.TickersResponse; -import net.jacobpeterson.domain.polygon.tickertypes.TickerTypes; -import net.jacobpeterson.polygon.PolygonConstants.Endpoints; -import net.jacobpeterson.polygon.PolygonConstants.Parameters; -import net.jacobpeterson.polygon.enums.*; -import net.jacobpeterson.polygon.properties.PolygonProperties; -import net.jacobpeterson.polygon.rest.PolygonRequest; -import net.jacobpeterson.polygon.rest.PolygonRequestBuilder; -import net.jacobpeterson.polygon.rest.exception.PolygonAPIRequestException; -import net.jacobpeterson.polygon.websocket.client.PolygonWebsocketClient; -import net.jacobpeterson.polygon.websocket.listener.PolygonStreamListener; -import net.jacobpeterson.util.format.FormatUtil; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.io.InputStream; -import java.lang.reflect.Type; -import java.time.LocalDate; -import java.util.ArrayList; -import java.util.StringJoiner; - -/** - * The class {@link PolygonAPI}. Note that most of these methods are blocking methods and this class in NOT - * thread-safe. - */ -public class PolygonAPI { - - /** The logger. */ - private static final Logger LOGGER = LoggerFactory.getLogger(PolygonAPI.class); - - /** The PolygonWebsocketClient. */ - private final PolygonWebsocketClient polygonWebsocketClient; - - /** The polygon request. */ - private final PolygonRequest polygonRequest; - - /** The base API URL. */ - private final String baseAPIURL; - - /** The Websocket URL. */ - private final String websocketURL; - - /** The Key ID. */ - private final String keyID; - - /** - * Instantiates a new {@link PolygonAPI}. - */ - public PolygonAPI() { - this(PolygonProperties.KEY_ID_VALUE); - - LOGGER.debug(PolygonProperties.staticToString()); - } - - /** - * Instantiates a new {@link PolygonAPI}. - * - * @param keyID the key ID - */ - public PolygonAPI(String keyID) { - this(PolygonProperties.BASE_API_URL_VALUE, PolygonProperties.POLYGON_WEB_SOCKET_SERVER_URL_VALUE, keyID); - } - - /** - * Instantiates a new {@link PolygonAPI}. - * - * @param baseAPIURL the base API URL - * @param websocketURL the websocket URL - * @param keyID the key ID - */ - public PolygonAPI(String baseAPIURL, String websocketURL, String keyID) { - this.baseAPIURL = baseAPIURL; - this.websocketURL = websocketURL; - this.keyID = keyID; - - polygonRequest = new PolygonRequest(keyID); - polygonWebsocketClient = new PolygonWebsocketClient(keyID, websocketURL); - - LOGGER.debug(this.toString()); - } - - /** - * Query all ticker symbols which are supported by Polygon.io. Returns basic reference data for each matched ticker - * symbol. This API includes Crypto, Forex, and Stocks/Equities. - * - * @param sort Which field to sort by. - * @param tickerType Get tickers of a certain type. See Ticker Types for available types. - * @param market Get tickers for a specific market - * @param locale Get tickers for a specific region/locale - * @param search Search the name of tickers - * @param perpage How many items to be on each page during pagination. Max 50 and default 50. - * @param page Which page of results to return - * @param active Filter for only active or inactive symbols - * - * @return the tickers - * - * @throws PolygonAPIRequestException throw for {@link PolygonAPIRequestException}s - * @see Tickers - */ - public TickersResponse getTickers(String sort, String tickerType, Market market, String locale, - String search, Integer perpage, Integer page, Boolean active) throws PolygonAPIRequestException { - PolygonRequestBuilder builder = new PolygonRequestBuilder(baseAPIURL, Endpoints.VERSION_2, - Endpoints.REFERENCE, - Endpoints.TICKERS); - - if (sort != null) { - builder.appendURLParameter(Parameters.SORT, sort); - } - - if (tickerType != null) { - builder.appendURLParameter(Parameters.TYPE, tickerType); - } - - if (market != null) { - builder.appendURLParameter(Parameters.MARKET, market.getAPIName()); - } - - if (locale != null) { - builder.appendURLParameter(Parameters.LOCALE, locale); - } - - if (search != null) { - builder.appendURLParameter(Parameters.SEARCH, search); - } - - if (perpage != null) { - builder.appendURLParameter(Parameters.PERPAGE, String.valueOf(perpage)); - } - - if (page != null) { - builder.appendURLParameter(Parameters.PAGE, String.valueOf(page)); - } - - if (active != null) { - builder.appendURLParameter(Parameters.ACTIVE, String.valueOf(active)); - } - - HttpResponse response = polygonRequest.invokeGet(builder); - - if (response.getStatus() != 200) { - throw new PolygonAPIRequestException(response); - } - - return polygonRequest.getResponseObject(response, TickersResponse.class); - } - - /** - * Get the mapping of ticker types to descriptions / long names. - * - * @return the ticker types - * - * @see Ticker Types - */ - public TickerTypes getTickerTypes() throws PolygonAPIRequestException { - PolygonRequestBuilder builder = new PolygonRequestBuilder(baseAPIURL, Endpoints.VERSION_2, - Endpoints.REFERENCE, - Endpoints.TYPES); - - HttpResponse response = polygonRequest.invokeGet(builder); - - if (response.getStatus() != 200) { - throw new PolygonAPIRequestException(response); - } - - return polygonRequest.getResponseObject(response, TickersResponse.class); - } - - /** - * Get the details of the symbol company/entity. These are important details which offer an overview of the entity. - * Things like name, sector, description, logo and similar companies. - * - * @param symbol symbol we want details for - * - * @return the ticker details - * - * @throws PolygonAPIRequestException throw for {@link PolygonAPIRequestException}s - * @see Ticker Details - */ - public TickerDetails getTickerDetails(String symbol) throws PolygonAPIRequestException { - Preconditions.checkNotNull(symbol); - - PolygonRequestBuilder builder = new PolygonRequestBuilder(baseAPIURL, Endpoints.VERSION_1, - Endpoints.META, - Endpoints.SYMBOLS, - symbol, - Endpoints.COMPANY); - - HttpResponse response = polygonRequest.invokeGet(builder); - - if (response.getStatus() != 200) { - throw new PolygonAPIRequestException(response); - } - - return polygonRequest.getResponseObject(response, TickerDetails.class); - } - - /** - * Get news articles for this symbol. - * - * @param symbol the symbol we want details for - * @param perpage How many items to be on each page during pagination. Max 50 - * @param page Which page of results to return - * - * @return the ticker news - * - * @throws PolygonAPIRequestException throw for {@link PolygonAPIRequestException}s - * @see Ticker News - */ - public ArrayList getTickerNews(String symbol, Integer perpage, Integer page) - throws PolygonAPIRequestException { - Preconditions.checkNotNull(symbol); - - PolygonRequestBuilder builder = new PolygonRequestBuilder(baseAPIURL, Endpoints.VERSION_1, - Endpoints.META, - Endpoints.SYMBOLS, - symbol, - Endpoints.NEWS); - - if (perpage != null) { - builder.appendURLParameter(Parameters.PERPAGE, String.valueOf(perpage)); - } - - if (page != null) { - builder.appendURLParameter(Parameters.PAGE, String.valueOf(page)); - } - - HttpResponse response = polygonRequest.invokeGet(builder); - - if (response.getStatus() != 200) { - throw new PolygonAPIRequestException(response); - } - - Type listType = new TypeToken>() {}.getType(); - - return polygonRequest.getResponseObject(response, listType); - } - - /** - * Get the list of currently supported markets - * - * @return the markets - * - * @throws PolygonAPIRequestException throw for {@link PolygonAPIRequestException}s - * @see Markets - */ - public MarketsResponse getMarkets() throws PolygonAPIRequestException { - PolygonRequestBuilder builder = new PolygonRequestBuilder(baseAPIURL, Endpoints.VERSION_2, - Endpoints.REFERENCE, - Endpoints.MARKETS); - - HttpResponse response = polygonRequest.invokeGet(builder); - - if (response.getStatus() != 200) { - throw new PolygonAPIRequestException(response); - } - - return polygonRequest.getResponseObject(response, MarketsResponse.class); - } - - /** - * Get the list of currently supported locales - * - * @return the locales - * - * @throws PolygonAPIRequestException throw for {@link PolygonAPIRequestException}s - * @see Locales - */ - public LocalesResponse getLocales() throws PolygonAPIRequestException { - PolygonRequestBuilder builder = new PolygonRequestBuilder(baseAPIURL, Endpoints.VERSION_2, - Endpoints.REFERENCE, - Endpoints.LOCALES); - - HttpResponse response = polygonRequest.invokeGet(builder); - - if (response.getStatus() != 200) { - throw new PolygonAPIRequestException(response); - } - - return polygonRequest.getResponseObject(response, LocalesResponse.class); - } - - /** - * Get the historical splits for this symbol - * - * @param symbol symbol we want details for - * - * @return the stock splits - * - * @throws PolygonAPIRequestException throw for {@link PolygonAPIRequestException}s - * @see Stock Splits - */ - public StockSplitsResponse getStockSplits(String symbol) throws PolygonAPIRequestException { - Preconditions.checkNotNull(symbol); - - PolygonRequestBuilder builder = new PolygonRequestBuilder(baseAPIURL, Endpoints.VERSION_2, - Endpoints.REFERENCE, - Endpoints.SPLITS, symbol); - - HttpResponse response = polygonRequest.invokeGet(builder); - - if (response.getStatus() != 200) { - throw new PolygonAPIRequestException(response); - } - - return polygonRequest.getResponseObject(response, StockSplitsResponse.class); - } - - /** - * Get the historical dividends for this symbol. - * - * @param symbol symbol we want details for - * - * @return the stock dividends - * - * @throws PolygonAPIRequestException throw for {@link PolygonAPIRequestException}s - * @see Stock Dividends - */ - public StockDividendsResponse getStockDividends(String symbol) throws PolygonAPIRequestException { - Preconditions.checkNotNull(symbol); - - PolygonRequestBuilder builder = new PolygonRequestBuilder(baseAPIURL, Endpoints.VERSION_2, - Endpoints.REFERENCE, - Endpoints.DIVIDENDS, - symbol); - - HttpResponse response = polygonRequest.invokeGet(builder); - - if (response.getStatus() != 200) { - throw new PolygonAPIRequestException(response); - } - - return polygonRequest.getResponseObject(response, StockDividendsResponse.class); - } - - /** - * Get the historical financials for this ticker. - * - * @param symbol symbol we want details for - * @param limit limit the number of results - * @param financialReportType type of reports - * @param financialSort sort direction - * - * @return the symbol financials - * - * @throws PolygonAPIRequestException throw for {@link PolygonAPIRequestException}s - * @see Stock Financials - */ - public StockFinancialsResponse getStockFinancials(String symbol, Integer limit, - FinancialReportType financialReportType, FinancialSort financialSort) throws PolygonAPIRequestException { - Preconditions.checkNotNull(symbol); - - PolygonRequestBuilder builder = new PolygonRequestBuilder(baseAPIURL, Endpoints.VERSION_2, - Endpoints.REFERENCE, - Endpoints.FINANCIALS, - symbol); - - if (limit != null) { - builder.appendURLParameter(Parameters.LIMIT, String.valueOf(limit)); - } - - if (financialReportType != null) { - builder.appendURLParameter(Parameters.TYPE, financialReportType.getAPIName()); - } - - if (financialSort != null) { - builder.appendURLParameter(Parameters.SORT, financialSort.getAPIName()); - } - - HttpResponse response = polygonRequest.invokeGet(builder); - - if (response.getStatus() != 200) { - throw new PolygonAPIRequestException(response); - } - - return polygonRequest.getResponseObject(response, StockFinancialsResponse.class); - } - - /** - * Current status of each market. - * - * @return the market status - * - * @throws PolygonAPIRequestException the polygon api request exception - * @see Market Status - */ - public MarketStatus getMarketStatus() throws PolygonAPIRequestException { - PolygonRequestBuilder builder = new PolygonRequestBuilder(baseAPIURL, Endpoints.VERSION_1, - Endpoints.MARKET_STATUS, - Endpoints.NOW); - - HttpResponse response = polygonRequest.invokeGet(builder); - - if (response.getStatus() != 200) { - throw new PolygonAPIRequestException(response); - } - - return polygonRequest.getResponseObject(response, MarketStatus.class); - } - - /** - * Get upcoming market holidays and their open/close times. - * - * @return the market holidays - * - * @throws PolygonAPIRequestException the polygon api request exception - * @see Market Holidays - */ - public ArrayList getMarketHolidays() throws PolygonAPIRequestException { - PolygonRequestBuilder builder = new PolygonRequestBuilder(baseAPIURL, Endpoints.VERSION_1, - Endpoints.MARKET_STATUS, - Endpoints.UPCOMING); - - HttpResponse response = polygonRequest.invokeGet(builder); - - if (response.getStatus() != 200) { - throw new PolygonAPIRequestException(response); - } - - Type listType = new TypeToken>() {}.getType(); - - return polygonRequest.getResponseObject(response, listType); - } - - /** - * List of stock exchanges which are supported by Polygon.io - * - * @return the exchanges - * - * @throws PolygonAPIRequestException throw for {@link PolygonAPIRequestException}s - * @see Exchanges - */ - public ArrayList getExchanges() throws PolygonAPIRequestException { - PolygonRequestBuilder builder = new PolygonRequestBuilder(baseAPIURL, Endpoints.VERSION_1, - Endpoints.META, - Endpoints.EXCHANGES); - - HttpResponse response = polygonRequest.invokeGet(builder); - - if (response.getStatus() != 200) { - throw new PolygonAPIRequestException(response); - } - - Type listType = new TypeToken>() {}.getType(); - - return polygonRequest.getResponseObject(response, listType); - } - - /** - * Get historic trades for a symbol. - * - * @param ticker Ticker symbol we want ticks for - * @param date Date/Day of the historic ticks to retrieve - * @param timestamp Timestamp offset, used for pagination. This is the offset at which to start the results. - * Using the timestamp of the last result as the offset will give you the next page of - * results. - * @param timestampLimit Maximum timestamp allowed in the results. - * @param reverse Reverse the order of the results. This is useful in combination with timestamp param. - * @param limit Limit the size of response, Max 50000 - * - * @return the historic trades - * - * @throws PolygonAPIRequestException throw for {@link PolygonAPIRequestException}s - * @see Historic - * Trades - */ - public HistoricTradesResponse getHistoricTrades(String ticker, LocalDate date, Long timestamp, Long timestampLimit, - Boolean reverse, Integer limit) throws PolygonAPIRequestException { - Preconditions.checkNotNull(ticker); - Preconditions.checkNotNull(date); - - PolygonRequestBuilder builder = new PolygonRequestBuilder(baseAPIURL, Endpoints.VERSION_2, - Endpoints.TICKS, - Endpoints.STOCKS, - Endpoints.TRADES, - ticker, - FormatUtil.toDateString(date)); - - if (timestamp != null) { - builder.appendURLParameter(Parameters.TIMESTAMP, String.valueOf(timestamp)); - } - - if (timestampLimit != null) { - builder.appendURLParameter(Parameters.TIMESTAMP_LIMIT, String.valueOf(timestampLimit)); - } - - if (reverse != null) { - builder.appendURLParameter(Parameters.REVERSE, String.valueOf(reverse)); - } - - if (limit != null) { - builder.appendURLParameter(Parameters.LIMIT, String.valueOf(limit)); - } - - HttpResponse response = polygonRequest.invokeGet(builder); - - if (response.getStatus() != 200) { - throw new PolygonAPIRequestException(response); - } - - return polygonRequest.getResponseObject(response, HistoricTradesResponse.class); - } - - /** - * Get historic NBBO quotes for a ticker. - * - * @param ticker Ticker symbol we want ticks for - * @param date Date/Day of the historic ticks to retrieve - * @param timestamp Timestamp offset, used for pagination. This is the offset at which to start the results. - * Using the timestamp of the last result as the offset will give you the next page of - * results. - * @param timestampLimit Maximum timestamp allowed in the results. - * @param reverse Reverse the order of the results. This is useful in combination with timestamp param. - * @param limit Limit the size of response, Max 50000 - * - * @return the historic quotes - * - * @throws PolygonAPIRequestException throw for {@link PolygonAPIRequestException}s - * @see Historic - * Quotes - */ - public HistoricQuotesResponse getHistoricQuotes(String ticker, LocalDate date, Long timestamp, Long timestampLimit, - Boolean reverse, Integer limit) throws PolygonAPIRequestException { - Preconditions.checkNotNull(ticker); - Preconditions.checkNotNull(date); - - PolygonRequestBuilder builder = new PolygonRequestBuilder(baseAPIURL, Endpoints.VERSION_2, - Endpoints.TICKS, - Endpoints.STOCKS, - Endpoints.NBBO, - ticker, - FormatUtil.toDateString(date)); - - if (timestamp != null) { - builder.appendURLParameter(Parameters.TIMESTAMP, String.valueOf(timestamp)); - } - - if (timestampLimit != null) { - builder.appendURLParameter(Parameters.TIMESTAMP_LIMIT, String.valueOf(timestampLimit)); - } - - if (reverse != null) { - builder.appendURLParameter(Parameters.REVERSE, String.valueOf(reverse)); - } - - if (limit != null) { - builder.appendURLParameter(Parameters.LIMIT, String.valueOf(limit)); - } - - HttpResponse response = polygonRequest.invokeGet(builder); - - if (response.getStatus() != 200) { - throw new PolygonAPIRequestException(response); - } - - return polygonRequest.getResponseObject(response, HistoricQuotesResponse.class); - } - - /** - * Get the last trade for a given stock. - * - * @param symbol Symbol of the stock to get - * - * @return the last trade - * - * @throws PolygonAPIRequestException throw for {@link PolygonAPIRequestException}s - * @see Last Trade - */ - public LastTradeResponse getLastTrade(String symbol) throws PolygonAPIRequestException { - Preconditions.checkNotNull(symbol); - - PolygonRequestBuilder builder = new PolygonRequestBuilder(baseAPIURL, Endpoints.VERSION_1, - Endpoints.LAST, - Endpoints.STOCKS, - symbol); - - HttpResponse response = polygonRequest.invokeGet(builder); - - if (response.getStatus() != 200) { - throw new PolygonAPIRequestException(response); - } - - return polygonRequest.getResponseObject(response, LastTradeResponse.class); - } - - /** - * Get the last quote for a given stock. - * - * @param symbol Symbol of the stock to get - * - * @return the last quote - * - * @throws PolygonAPIRequestException throw for {@link PolygonAPIRequestException}s - * @see Last Quote - */ - public LastQuoteResponse getLastQuote(String symbol) throws PolygonAPIRequestException { - Preconditions.checkNotNull(symbol); - - PolygonRequestBuilder builder = new PolygonRequestBuilder(baseAPIURL, Endpoints.VERSION_1, - Endpoints.LAST_QUOTE, - Endpoints.STOCKS, - symbol); - - HttpResponse response = polygonRequest.invokeGet(builder); - - if (response.getStatus() != 200) { - throw new PolygonAPIRequestException(response); - } - - return polygonRequest.getResponseObject(response, LastQuoteResponse.class); - } - - /** - * Get the open, close and afterhours prices of a symbol on a certain date. - * - * @param symbol Symbol of the stock to get - * @param date Date of the requested open/close - * - * @return the daily open close - * - * @throws PolygonAPIRequestException throw for {@link PolygonAPIRequestException}s - * @see Daily Open/Close - */ - public DailyOpenClose getDailyOpenClose(String symbol, LocalDate date) throws PolygonAPIRequestException { - Preconditions.checkNotNull(symbol); - Preconditions.checkNotNull(date); - - PolygonRequestBuilder builder = new PolygonRequestBuilder(baseAPIURL, Endpoints.VERSION_1, - Endpoints.OPEN_CLOSE, - symbol, - FormatUtil.toDateString(date)); - - HttpResponse response = polygonRequest.invokeGet(builder); - - if (response.getStatus() != 200) { - throw new PolygonAPIRequestException(response); - } - - return polygonRequest.getResponseObject(response, DailyOpenClose.class); - } - - /** - * The mappings for conditions on trades and quotes. - * - * @param conditionMappingsType Ticker type we want mappings for - * - * @return the conditions mapping - * - * @throws PolygonAPIRequestException the polygon api request exception - * @see Condition Mappings - */ - public ConditionsMapping getConditionsMapping(ConditionMappingsType conditionMappingsType) - throws PolygonAPIRequestException { - Preconditions.checkNotNull(conditionMappingsType); - - PolygonRequestBuilder builder = new PolygonRequestBuilder(baseAPIURL, Endpoints.VERSION_1, - Endpoints.META, - Endpoints.CONDITIONS, - conditionMappingsType.getAPIName()); - - HttpResponse response = polygonRequest.invokeGet(builder); - - if (response.getStatus() != 200) { - throw new PolygonAPIRequestException(response); - } - - return polygonRequest.getResponseObject(response, ConditionsMapping.class); - } - - /** - * Snapshot allows you to see all tickers current minute aggregate, daily aggregate and last trade. As well as - * previous days aggregate and calculated change for today. - * - * @return the snapshot all tickers - * - * @throws PolygonAPIRequestException throw for {@link PolygonAPIRequestException}s - * @see - * Snapshot - * - All Tickers - */ - public SnapshotAllTickersResponse getSnapshotAllTickers() throws PolygonAPIRequestException { - PolygonRequestBuilder builder = new PolygonRequestBuilder(baseAPIURL, Endpoints.VERSION_2, - Endpoints.SNAPSHOT, - Endpoints.LOCALE, - Endpoints.US, - Endpoints.MARKETS, - Endpoints.STOCKS, - Endpoints.TICKERS); - - HttpResponse response = polygonRequest.invokeGet(builder); - - if (response.getStatus() != 200) { - throw new PolygonAPIRequestException(response); - } - - return polygonRequest.getResponseObject(response, SnapshotAllTickersResponse.class); - } - - /** - * See the current snapshot of a single ticker - * - * @param ticker Ticker of the snapshot - * - * @return the snapshot single ticker - * - * @throws PolygonAPIRequestException throw for {@link PolygonAPIRequestException}s - * @see - * Snapshot - * - Single Ticker - */ - public SnapshotSingleTickerResponse getSnapshotSingleTicker(String ticker) throws PolygonAPIRequestException { - Preconditions.checkNotNull(ticker); - - PolygonRequestBuilder builder = new PolygonRequestBuilder(baseAPIURL, Endpoints.VERSION_2, - Endpoints.SNAPSHOT, - Endpoints.LOCALE, - Endpoints.US, - Endpoints.MARKETS, - Endpoints.STOCKS, - Endpoints.TICKERS, - ticker); - - HttpResponse response = polygonRequest.invokeGet(builder); - - if (response.getStatus() != 200) { - throw new PolygonAPIRequestException(response); - } - - return polygonRequest.getResponseObject(response, SnapshotSingleTickerResponse.class); - } - - /** - * See the current snapshot of the top 20 gainers of the day at the moment. - * - * @param gainersLosersDirection the gainers losers direction - * - * @return the snapshots gainers - * - * @throws PolygonAPIRequestException throw for {@link PolygonAPIRequestException}s - * @see - * Snapshot - * - Gainers / Losers - */ - public SnapshotGainersLosersResponse getSnapshotsGainersLosers(GainersLosersDirection gainersLosersDirection) - throws PolygonAPIRequestException { - Preconditions.checkNotNull(gainersLosersDirection); - - PolygonRequestBuilder builder = new PolygonRequestBuilder(baseAPIURL, Endpoints.VERSION_2, - Endpoints.SNAPSHOT, - Endpoints.LOCALE, - Endpoints.US, - Endpoints.MARKETS, - Endpoints.STOCKS, - gainersLosersDirection.getAPIName()); - - HttpResponse response = polygonRequest.invokeGet(builder); - - if (response.getStatus() != 200) { - throw new PolygonAPIRequestException(response); - } - - return polygonRequest.getResponseObject(response, SnapshotGainersLosersResponse.class); - } - - /** - * See the current snapshot of Level II data on IEX for the given ticker. - * - * @param locale the locale - * @param market the market - * @param ticker the ticker - * - * @return the snapshot ticker book - * - * @throws PolygonAPIRequestException throw for {@link PolygonAPIRequestException}s - * @see Docs not public yet - */ - public SnapshotTickerBook getSnapshotTickerBook(String locale, Market market, String ticker) - throws PolygonAPIRequestException { - Preconditions.checkNotNull(locale); - Preconditions.checkNotNull(market); - Preconditions.checkNotNull(ticker); - - PolygonRequestBuilder builder = new PolygonRequestBuilder(baseAPIURL, Endpoints.VERSION_2, - Endpoints.SNAPSHOT, - Endpoints.LOCALE, - locale, - Endpoints.MARKETS, - market.getAPIName(), - Endpoints.TICKERS, - ticker, - Endpoints.BOOK); - - HttpResponse response = polygonRequest.invokeGet(builder); - - if (response.getStatus() != 200) { - throw new PolygonAPIRequestException(response); - } - - return polygonRequest.getResponseObject(response, SnapshotTickerBook.class); - } - - /** - * Get the previous day close for the specified ticker - * - * @param ticker Ticker symbol of the request - * @param unadjusted Set to true if the results should NOT be adjusted for splits. - * - * @return the previous close - * - * @throws PolygonAPIRequestException throw for {@link PolygonAPIRequestException}s - * @see Previous Close - */ - public PreviousCloseResponse getPreviousClose(String ticker, Boolean unadjusted) throws PolygonAPIRequestException { - Preconditions.checkNotNull(ticker); - - PolygonRequestBuilder builder = new PolygonRequestBuilder(baseAPIURL, Endpoints.VERSION_2, - Endpoints.AGGS, - Endpoints.TICKER, - ticker, - Endpoints.PREV); - - if (unadjusted != null) { - builder.appendURLParameter(Parameters.UNADJUSTED, unadjusted.toString()); - } - - HttpResponse response = polygonRequest.invokeGet(builder); - - if (response.getStatus() != 200) { - throw new PolygonAPIRequestException(response); - } - - return polygonRequest.getResponseObject(response, PreviousCloseResponse.class); - } - - /** - * Get aggregates for a date range, in custom time window sizes - * - * @param ticker Ticker symbol of the request - * @param multiplier Size of the timespan multiplier - * @param timeSpan Size of the time window - * @param fromDate From date - * @param toDate To date - * @param unadjusted Set to true if the results should NOT be adjusted for splits - * - * @return the aggregates - * - * @throws PolygonAPIRequestException throw for {@link PolygonAPIRequestException}s - * @see - * Aggregates - */ - public AggregatesResponse getAggregates(String ticker, Integer multiplier, Timespan timeSpan, LocalDate fromDate, - LocalDate toDate, Integer limit, SortDirection sortDirection, Boolean unadjusted) - throws PolygonAPIRequestException { - Preconditions.checkNotNull(ticker); - Preconditions.checkNotNull(timeSpan); - Preconditions.checkNotNull(fromDate); - Preconditions.checkNotNull(toDate); - - PolygonRequestBuilder builder = new PolygonRequestBuilder(baseAPIURL, Endpoints.VERSION_2, - Endpoints.AGGS, - Endpoints.TICKER, - ticker, - Endpoints.RANGE, - Integer.toString((multiplier == null) ? 1 : multiplier), - timeSpan.getAPIName(), - FormatUtil.toDateString(fromDate), - FormatUtil.toDateString(toDate)); - - if (limit != null) { - builder.appendURLParameter(Parameters.LIMIT, String.valueOf(limit)); - } - - if (sortDirection != null) { - builder.appendURLParameter(Parameters.SORT, sortDirection.getAPIName()); - } - - if (unadjusted != null) { - builder.appendURLParameter(Parameters.UNADJUSTED, unadjusted.toString()); - } - - HttpResponse response = polygonRequest.invokeGet(builder); - - if (response.getStatus() != 200) { - throw new PolygonAPIRequestException(response); - } - - return polygonRequest.getResponseObject(response, AggregatesResponse.class); - } - - /** - * Get aggregates for a date range, in custom time window sizes - * - * @param ticker Ticker symbol of the request - * @param multiplier Size of the timespan multiplier - * @param timeSpan Size of the time window - * @param fromDateUNIX long UTC timestamp - * @param toDateUNIX long UTC timestamp - * @param unadjusted Set to true if the results should NOT be adjusted for splits - * - * @return the aggregates - * - * @throws PolygonAPIRequestException throw for {@link PolygonAPIRequestException}s - * @see - * Aggregates - */ - public AggregatesResponse getAggregates(String ticker, Integer multiplier, Timespan timeSpan, Long fromDateUNIX, - Long toDateUNIX, Integer limit, SortDirection sortDirection, Boolean unadjusted) - throws PolygonAPIRequestException { - Preconditions.checkNotNull(ticker); - Preconditions.checkNotNull(timeSpan); - Preconditions.checkNotNull(fromDateUNIX); - Preconditions.checkNotNull(toDateUNIX); - - PolygonRequestBuilder builder = new PolygonRequestBuilder(baseAPIURL, Endpoints.VERSION_2, - Endpoints.AGGS, - Endpoints.TICKER, - ticker, - Endpoints.RANGE, - Integer.toString((multiplier == null) ? 1 : multiplier), - timeSpan.getAPIName(), - fromDateUNIX.toString(), - toDateUNIX.toString()); - - if (unadjusted != null) { - builder.appendURLParameter(Parameters.UNADJUSTED, unadjusted.toString()); - } - - if (limit != null) { - builder.appendURLParameter(Parameters.LIMIT, String.valueOf(limit)); - } - - if (sortDirection != null) { - builder.appendURLParameter(Parameters.SORT, sortDirection.getAPIName()); - } - - HttpResponse response = polygonRequest.invokeGet(builder); - - if (response.getStatus() != 200) { - throw new PolygonAPIRequestException(response); - } - - return polygonRequest.getResponseObject(response, AggregatesResponse.class); - } - - /** - * Get the daily OHLC for entire markets. - * - * @param locale Locale of the aggregates ( See 'Locales' API ) - * @param market Market of the aggregates ( See 'Markets' API ) - * @param date To date - * @param unadjusted Set to true if the results should NOT be adjusted for splits. - * - * @return the grouped daily - * - * @throws PolygonAPIRequestException throw for {@link PolygonAPIRequestException}s - * @see - * Grouped - * Daily - */ - public GroupedDailyResponse getGroupedDaily(String locale, Market market, LocalDate date, Boolean unadjusted) - throws PolygonAPIRequestException { - Preconditions.checkNotNull(locale); - Preconditions.checkNotNull(market); - Preconditions.checkNotNull(date); - - PolygonRequestBuilder builder = new PolygonRequestBuilder(baseAPIURL, Endpoints.VERSION_2, - Endpoints.AGGS, - Endpoints.GROUPED, - Endpoints.LOCALE, - locale, - Endpoints.MARKET, - market.getAPIName(), - FormatUtil.toDateString(date)); - - if (unadjusted != null) { - builder.appendURLParameter(Parameters.UNADJUSTED, unadjusted.toString()); - } - - HttpResponse response = polygonRequest.invokeGet(builder); - - if (response.getStatus() != 200) { - throw new PolygonAPIRequestException(response); - } - - return polygonRequest.getResponseObject(response, GroupedDailyResponse.class); - } - - /** - * Adds the polygon stream listener. - * - * @param streamListener the stream listener - * - * @throws WebsocketException the WebsocketException - */ - public void addPolygonStreamListener(PolygonStreamListener streamListener) throws WebsocketException { - polygonWebsocketClient.addListener(streamListener); - } - - /** - * Removes the polygon stream listener. - * - * @param streamListener the stream listener - * - * @throws WebsocketException the WebsocketException - */ - public void removePolygonStreamListener(PolygonStreamListener streamListener) throws WebsocketException { - polygonWebsocketClient.removeListener(streamListener); - } - - @Override - public String toString() { - return new StringJoiner(", ", this.getClass().getSimpleName() + "[", "]") - .add("baseAPIURL = " + baseAPIURL) - .add("keyID = " + keyID) - .add("websocketURL = " + websocketURL) - .toString(); - } -} diff --git a/src/main/java/net/jacobpeterson/polygon/PolygonConstants.java b/src/main/java/net/jacobpeterson/polygon/PolygonConstants.java deleted file mode 100644 index a5bc6b7b..00000000 --- a/src/main/java/net/jacobpeterson/polygon/PolygonConstants.java +++ /dev/null @@ -1,70 +0,0 @@ -package net.jacobpeterson.polygon; - -/** - * {@link PolygonConstants} are constants used the the {@link PolygonAPI}. - */ -public class PolygonConstants { - - /** - * Defines endpoints for {@link PolygonAPI}. - */ - public static class Endpoints { - - public static final String META = "meta"; - public static final String SYMBOLS = "symbols"; - public static final String FINANCIALS = "financials"; - public static final String DIVIDENDS = "dividends"; - public static final String COMPANY = "company"; - public static final String CONDITIONS = "conditions"; - public static final String REFERENCE = "reference"; - public static final String VERSION_1 = "v1"; - public static final String VERSION_2 = "v2"; - public static final String NEWS = "news"; - public static final String GROUPED = "grouped"; - public static final String TICKER = "ticker"; - public static final String TICKS = "ticks"; - public static final String AGGS = "aggs"; - public static final String NBBO = "nbbo"; - public static final String OPEN_CLOSE = "open-close"; - public static final String LAST_QUOTE = "last_quote"; - public static final String LAST = "last"; - public static final String TRADES = "trades"; - public static final String EXCHANGES = "exchanges"; - public static final String SPLITS = "splits"; - public static final String TYPES = "types"; - public static final String LOCALES = "locales"; - public static final String MARKETS = "markets"; - public static final String MARKET = "market"; - public static final String MARKET_STATUS = "marketstatus"; - public static final String NOW = "now"; - public static final String UPCOMING = "upcoming"; - public static final String TICKERS = "tickers"; - public static final String STOCKS = "stocks"; - public static final String US = "us"; - public static final String LOCALE = "locale"; - public static final String SNAPSHOT = "snapshot"; - public static final String BOOK = "book"; - public static final String RANGE = "range"; - public static final String PREV = "prev"; - } - - /** - * Defines parameters for {@link PolygonAPI}. - */ - public static class Parameters { - - public static final String LOCALE = "locale"; - public static final String ACTIVE = "active"; - public static final String MARKET = "market"; - public static final String SEARCH = "search"; - public static final String TYPE = "type"; - public static final String SORT = "sort"; - public static final String PAGE = "page"; - public static final String PERPAGE = "perpage"; - public static final String LIMIT = "limit"; - public static final String TIMESTAMP = "timestamp"; - public static final String TIMESTAMP_LIMIT = "timestampLimit"; - public static final String UNADJUSTED = "unadjusted"; - public static final String REVERSE = "reverse"; - } -} diff --git a/src/main/java/net/jacobpeterson/polygon/enums/ConditionMappingsType.java b/src/main/java/net/jacobpeterson/polygon/enums/ConditionMappingsType.java deleted file mode 100644 index 4ac1ffb5..00000000 --- a/src/main/java/net/jacobpeterson/polygon/enums/ConditionMappingsType.java +++ /dev/null @@ -1,32 +0,0 @@ -package net.jacobpeterson.polygon.enums; - -import net.jacobpeterson.abstracts.enums.APIName; - -/** - * The enum {@link ConditionMappingsType}. - */ -public enum ConditionMappingsType implements APIName { - - /** Trades {@link ConditionMappingsType}. */ - TRADES("trades"), - - /** Quotes {@link ConditionMappingsType}. */ - QUOTES("quotes"); - - /** The API name. */ - String apiName; - - /** - * Instantiates a new {@link ConditionMappingsType}. - * - * @param apiName the api name - */ - ConditionMappingsType(String apiName) { - this.apiName = apiName; - } - - @Override - public String getAPIName() { - return apiName; - } -} diff --git a/src/main/java/net/jacobpeterson/polygon/enums/FinancialReportType.java b/src/main/java/net/jacobpeterson/polygon/enums/FinancialReportType.java deleted file mode 100644 index 08cf1d7d..00000000 --- a/src/main/java/net/jacobpeterson/polygon/enums/FinancialReportType.java +++ /dev/null @@ -1,44 +0,0 @@ -package net.jacobpeterson.polygon.enums; - -import net.jacobpeterson.abstracts.enums.APIName; - -/** - * The enum {@link FinancialReportType}. - */ -public enum FinancialReportType implements APIName { - - /** Y {@link FinancialReportType}. */ - Y("Y"), - - /** YA {@link FinancialReportType}. */ - YA("YA"), - - /** Q {@link FinancialReportType}. */ - Q("Q"), - - /** QA {@link FinancialReportType}. */ - QA("QA"), - - /** T {@link FinancialReportType}. */ - T("T"), - - /** TA {@link FinancialReportType}. */ - TA("TA"); - - /** The API name. */ - String apiName; - - /** - * Instantiates a new {@link FinancialReportType}. - * - * @param apiName the api name - */ - FinancialReportType(String apiName) { - this.apiName = apiName; - } - - @Override - public String getAPIName() { - return apiName; - } -} diff --git a/src/main/java/net/jacobpeterson/polygon/enums/FinancialSort.java b/src/main/java/net/jacobpeterson/polygon/enums/FinancialSort.java deleted file mode 100644 index dfcd7b75..00000000 --- a/src/main/java/net/jacobpeterson/polygon/enums/FinancialSort.java +++ /dev/null @@ -1,38 +0,0 @@ -package net.jacobpeterson.polygon.enums; - -import net.jacobpeterson.abstracts.enums.APIName; - -/** - * The enum {@link FinancialSort}. - */ -public enum FinancialSort implements APIName { - - /** Report period ascending {@link FinancialSort}. */ - REPORT_PERIOD_ASCENDING("reportPeriod"), - - /** Report period descending {@link FinancialSort} */ - REPORT_PERIOD_DESCENDING("-reportPeriod"), - - /** Calendar date ascending {@link FinancialSort}. */ - CALENDAR_DATE_ASCENDING("calendarDate"), - - /** Calendar date descending {@link FinancialSort}. */ - CALENDAR_DATE_DESCENDING("-calendarDate"); - - /** The API name. */ - String apiName; - - /** - * Instantiates a new {@link FinancialSort}. - * - * @param apiName the api name - */ - FinancialSort(String apiName) { - this.apiName = apiName; - } - - @Override - public String getAPIName() { - return apiName; - } -} diff --git a/src/main/java/net/jacobpeterson/polygon/enums/GainersLosersDirection.java b/src/main/java/net/jacobpeterson/polygon/enums/GainersLosersDirection.java deleted file mode 100644 index 557561ee..00000000 --- a/src/main/java/net/jacobpeterson/polygon/enums/GainersLosersDirection.java +++ /dev/null @@ -1,32 +0,0 @@ -package net.jacobpeterson.polygon.enums; - -import net.jacobpeterson.abstracts.enums.APIName; - -/** - * The enum {@link GainersLosersDirection}. - */ -public enum GainersLosersDirection implements APIName { - - /** Gainers {@link GainersLosersDirection}. */ - GAINERS("gainers"), - - /** Losers {@link GainersLosersDirection}. */ - LOSERS("losers"); - - /** The API name. */ - String apiName; - - /** - * Instantiates a new {@link GainersLosersDirection}. - * - * @param apiName the api name - */ - GainersLosersDirection(String apiName) { - this.apiName = apiName; - } - - @Override - public String getAPIName() { - return apiName; - } -} diff --git a/src/main/java/net/jacobpeterson/polygon/enums/Market.java b/src/main/java/net/jacobpeterson/polygon/enums/Market.java deleted file mode 100644 index 0daeb76b..00000000 --- a/src/main/java/net/jacobpeterson/polygon/enums/Market.java +++ /dev/null @@ -1,47 +0,0 @@ -package net.jacobpeterson.polygon.enums; - -import net.jacobpeterson.abstracts.enums.APIName; - -/** - * The Enum {@link Market}. - */ -public enum Market implements APIName { - - /** Stocks {@link Market}. */ - STOCKS("stocks"), - - /** Indices {@link Market}. */ - INDICES("indices"), - - /** Crypto {@link Market} */ - CRYPTO("crypto"), - - /** Forex {@link Market}. */ - FX("fx"), - - /** Bonds {@link Market}. */ - BONDS("bonds"), - - /** Mutual Funds {@link Market}. */ - MF("mf"), - - /** Money Market Funds {@link Market}. */ - MMF("mmf"); - - /** The API name. */ - String apiName; - - /** - * Instantiates a new {@link Market}. - * - * @param apiName the API name - */ - Market(String apiName) { - this.apiName = apiName; - } - - @Override - public String getAPIName() { - return apiName; - } -} diff --git a/src/main/java/net/jacobpeterson/polygon/enums/Timespan.java b/src/main/java/net/jacobpeterson/polygon/enums/Timespan.java deleted file mode 100644 index 3212b83b..00000000 --- a/src/main/java/net/jacobpeterson/polygon/enums/Timespan.java +++ /dev/null @@ -1,47 +0,0 @@ -package net.jacobpeterson.polygon.enums; - -import net.jacobpeterson.abstracts.enums.APIName; - -/** - * The Enum {@link Timespan}. - */ -public enum Timespan implements APIName { - - /** The Minute {@link Timespan}. */ - MINUTE("minute"), - - /** The Hour {@link Timespan}. */ - HOUR("hour"), - - /** The Day {@link Timespan}. */ - DAY("day"), - - /** The Week {@link Timespan}. */ - WEEK("week"), - - /** The Month {@link Timespan}. */ - MONTH("month"), - - /** The Quarter {@link Timespan}. */ - QUARTER("quarter"), - - /** The Year {@link Timespan}. */ - YEAR("year"); - - /** The API name. */ - String apiName; - - /** - * Instantiates a new {@link Timespan}. - * - * @param apiName the api name - */ - Timespan(String apiName) { - this.apiName = apiName; - } - - @Override - public String getAPIName() { - return apiName; - } -} diff --git a/src/main/java/net/jacobpeterson/polygon/properties/PolygonProperties.java b/src/main/java/net/jacobpeterson/polygon/properties/PolygonProperties.java deleted file mode 100644 index 4057f049..00000000 --- a/src/main/java/net/jacobpeterson/polygon/properties/PolygonProperties.java +++ /dev/null @@ -1,49 +0,0 @@ -package net.jacobpeterson.polygon.properties; - -import net.jacobpeterson.alpaca.properties.AlpacaProperties; - -import java.util.StringJoiner; - -import static net.jacobpeterson.util.properties.PropertyUtil.getProperty; - -/** - * {@link PolygonProperties} defines properties for {@link net.jacobpeterson.polygon.PolygonAPI}. - */ -public class PolygonProperties { - - private static final String POLYGON_PROPERTIES_FILE = "polygon.properties"; - - private static final String KEY_ID_KEY = "key_id"; - public static final String KEY_ID_VALUE = - getProperty(POLYGON_PROPERTIES_FILE, KEY_ID_KEY, AlpacaProperties.KEY_ID_VALUE); - - private static final String BASE_API_URL_KEY = "base_api_url"; - public static final String BASE_API_URL_VALUE = - getProperty(POLYGON_PROPERTIES_FILE, BASE_API_URL_KEY); - - private static final String POLYGON_WEB_SOCKET_SERVER_URL_KEY = "web_socket_server_url"; - public static final String POLYGON_WEB_SOCKET_SERVER_URL_VALUE = - getProperty(POLYGON_PROPERTIES_FILE, POLYGON_WEB_SOCKET_SERVER_URL_KEY); - - private static final String USER_AGENT_KEY = "user_agent"; - public static final String USER_AGENT_VALUE = getProperty(POLYGON_PROPERTIES_FILE, USER_AGENT_KEY); - - /** - * Static {@link #toString()}. - * - * @return the static {@link #toString()} - */ - public static String staticToString() { - return new StringJoiner(", ", PolygonProperties.class.getSimpleName() + "[", "]") - .add("BASE_API_URL_KEY = " + BASE_API_URL_KEY) - .add("BASE_API_URL_VALUE = " + BASE_API_URL_VALUE) - .add("KEY_ID_KEY = " + KEY_ID_KEY) - .add("KEY_ID_VALUE = " + KEY_ID_VALUE) - .add("POLYGON_PROPERTIES_FILE = " + POLYGON_PROPERTIES_FILE) - .add("POLYGON_WEB_SOCKET_SERVER_URL_KEY = " + POLYGON_WEB_SOCKET_SERVER_URL_KEY) - .add("POLYGON_WEB_SOCKET_SERVER_URL_VALUE = " + POLYGON_WEB_SOCKET_SERVER_URL_VALUE) - .add("USER_AGENT_KEY = " + USER_AGENT_KEY) - .add("USER_AGENT_VALUE = " + USER_AGENT_VALUE) - .toString(); - } -} diff --git a/src/main/java/net/jacobpeterson/polygon/rest/PolygonRequest.java b/src/main/java/net/jacobpeterson/polygon/rest/PolygonRequest.java deleted file mode 100644 index 963d7a44..00000000 --- a/src/main/java/net/jacobpeterson/polygon/rest/PolygonRequest.java +++ /dev/null @@ -1,77 +0,0 @@ -package net.jacobpeterson.polygon.rest; - -import com.mashape.unirest.http.HttpResponse; -import net.jacobpeterson.abstracts.rest.AbstractRequest; -import net.jacobpeterson.abstracts.rest.AbstractRequestBuilder; -import net.jacobpeterson.polygon.properties.PolygonProperties; - -import java.io.InputStream; - -/** - * {@link PolygonRequest} contains methods for HTTP requests for {@link net.jacobpeterson.polygon.PolygonAPI}. - */ -public class PolygonRequest extends AbstractRequest { - - /** The Constant USER_AGENT_KEY. */ - private static final String USER_AGENT_KEY = "user-agent"; - - /** The Constant API_KEY_ID. */ - private static final String API_KEY_PARAMETER = "apiKey"; - - /** The key id. */ - private final String keyId; - - /** - * Instantiates a new {@link PolygonRequest}. - * - * @param keyID the key id - */ - public PolygonRequest(String keyID) { - super(); - this.keyId = keyID; - - headers.put(USER_AGENT_KEY, PolygonProperties.USER_AGENT_VALUE); - } - - @Override - public HttpResponse invokeGet(AbstractRequestBuilder abstractRequestBuilder) { - abstractRequestBuilder.appendURLParameter(API_KEY_PARAMETER, keyId); - return super.invokeGet(abstractRequestBuilder); - } - - @Override - public HttpResponse invokeHead(AbstractRequestBuilder abstractRequestBuilder) { - abstractRequestBuilder.appendURLParameter(API_KEY_PARAMETER, keyId); - return super.invokeHead(abstractRequestBuilder); - } - - @Override - public HttpResponse invokePost(AbstractRequestBuilder abstractRequestBuilder) { - abstractRequestBuilder.appendURLParameter(API_KEY_PARAMETER, keyId); - return super.invokePost(abstractRequestBuilder); - } - - @Override - public HttpResponse invokePatch(AbstractRequestBuilder abstractRequestBuilder) { - abstractRequestBuilder.appendURLParameter(API_KEY_PARAMETER, keyId); - return super.invokePatch(abstractRequestBuilder); - } - - @Override - public HttpResponse invokePut(AbstractRequestBuilder abstractRequestBuilder) { - abstractRequestBuilder.appendURLParameter(API_KEY_PARAMETER, keyId); - return super.invokePut(abstractRequestBuilder); - } - - @Override - public HttpResponse invokeDelete(AbstractRequestBuilder abstractRequestBuilder) { - abstractRequestBuilder.appendURLParameter(API_KEY_PARAMETER, keyId); - return super.invokeDelete(abstractRequestBuilder); - } - - @Override - public HttpResponse invokeOptions(AbstractRequestBuilder abstractRequestBuilder) { - abstractRequestBuilder.appendURLParameter(API_KEY_PARAMETER, keyId); - return super.invokeOptions(abstractRequestBuilder); - } -} diff --git a/src/main/java/net/jacobpeterson/polygon/rest/PolygonRequestBuilder.java b/src/main/java/net/jacobpeterson/polygon/rest/PolygonRequestBuilder.java deleted file mode 100644 index 708d0bb5..00000000 --- a/src/main/java/net/jacobpeterson/polygon/rest/PolygonRequestBuilder.java +++ /dev/null @@ -1,30 +0,0 @@ -package net.jacobpeterson.polygon.rest; - -import net.jacobpeterson.abstracts.rest.AbstractRequestBuilder; - -import java.util.Arrays; - -/** - * {@link PolygonRequestBuilder} is a builder for URL Strings for {@link net.jacobpeterson.polygon.PolygonAPI}. - */ -public class PolygonRequestBuilder extends AbstractRequestBuilder { - - /** - * Instantiates a new {@link PolygonRequestBuilder}. - * - * @param baseUrl the base url - * @param apiVersion the api version - * @param endpoints the endpoints - */ - public PolygonRequestBuilder(String baseUrl, String apiVersion, String... endpoints) { - super(baseUrl); - - if (apiVersion != null) { - super.appendEndpoint(apiVersion); - } - - if (endpoints != null) { - Arrays.stream(endpoints).forEach(super::appendEndpoint); - } - } -} diff --git a/src/main/java/net/jacobpeterson/polygon/rest/exception/PolygonAPIRequestException.java b/src/main/java/net/jacobpeterson/polygon/rest/exception/PolygonAPIRequestException.java deleted file mode 100644 index 12db199b..00000000 --- a/src/main/java/net/jacobpeterson/polygon/rest/exception/PolygonAPIRequestException.java +++ /dev/null @@ -1,27 +0,0 @@ -package net.jacobpeterson.polygon.rest.exception; - -import com.mashape.unirest.http.HttpResponse; -import net.jacobpeterson.abstracts.rest.exception.AbstractAPIRequestException; - -import java.io.InputStream; - -/** - * {@link PolygonAPIRequestException} represents {@link HttpResponse} request exceptions for {@link - * net.jacobpeterson.polygon.PolygonAPI}. - */ -public class PolygonAPIRequestException extends AbstractAPIRequestException { - - /** - * Instantiates a new {@link PolygonAPIRequestException}. - * - * @param httpResponse the {@link HttpResponse} - */ - public PolygonAPIRequestException(HttpResponse httpResponse) { - super("Polygon", httpResponse); - } - - @Override - protected void parseAPIExceptionMessage() { - super.parseStandardAPIExceptionResponse(); - } -} diff --git a/src/main/java/net/jacobpeterson/polygon/websocket/client/PolygonWebsocketClient.java b/src/main/java/net/jacobpeterson/polygon/websocket/client/PolygonWebsocketClient.java deleted file mode 100644 index 904a42fc..00000000 --- a/src/main/java/net/jacobpeterson/polygon/websocket/client/PolygonWebsocketClient.java +++ /dev/null @@ -1,415 +0,0 @@ -package net.jacobpeterson.polygon.websocket.client; - -import com.google.common.base.Preconditions; -import com.google.gson.JsonArray; -import com.google.gson.JsonElement; -import com.google.gson.JsonObject; -import com.google.gson.JsonPrimitive; -import com.google.gson.JsonSyntaxException; -import net.jacobpeterson.abstracts.websocket.client.WebsocketClient; -import net.jacobpeterson.abstracts.websocket.exception.WebsocketException; -import net.jacobpeterson.abstracts.websocket.listener.StreamListener; -import net.jacobpeterson.abstracts.websocket.message.StreamMessage; -import net.jacobpeterson.abstracts.websocket.message.StreamMessageType; -import net.jacobpeterson.domain.polygon.websocket.PolygonStreamMessage; -import net.jacobpeterson.domain.polygon.websocket.aggregate.AggregatePerMinuteMessage; -import net.jacobpeterson.domain.polygon.websocket.aggregate.AggregatePerSecondMessage; -import net.jacobpeterson.domain.polygon.websocket.quote.QuoteMessage; -import net.jacobpeterson.domain.polygon.websocket.status.StatusMessage; -import net.jacobpeterson.domain.polygon.websocket.trade.TradeMessage; -import net.jacobpeterson.polygon.websocket.listener.PolygonStreamListener; -import net.jacobpeterson.polygon.websocket.message.PolygonStreamMessageType; -import net.jacobpeterson.util.gson.GsonUtil; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import javax.websocket.DeploymentException; -import java.io.IOException; -import java.net.URI; -import java.net.URISyntaxException; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.HashSet; -import java.util.List; -import java.util.Map; -import java.util.Set; -import java.util.StringJoiner; -import java.util.stream.Collectors; - -/** - * The Class {@link PolygonWebsocketClient}. - */ -public class PolygonWebsocketClient implements WebsocketClient { - - /** The logger. */ - private static final Logger LOGGER = LoggerFactory.getLogger(PolygonWebsocketClient.class); - - /** The all tickers. */ - private static final String ALL_TICKERS = "*"; - - /** The constant EVENT_TYPE_KEY. */ - private static final String EVENT_TYPE_KEY = "ev"; - - /** The key id. */ - private final String keyId; - - /** The Websocket url. */ - private final String websocketURL; - - /** The listeners. */ - private final List listeners; - - /** The client end point. */ - private PolygonWebsocketClientEndpoint polygonWebsocketClientEndpoint; - - /** The Authenticated. */ - private boolean authenticated; - - /** - * Instantiates a new {@link PolygonWebsocketClient}. - * - * @param keyId the key id - * @param websocketURL the websocket url - */ - public PolygonWebsocketClient(String keyId, String websocketURL) { - this.keyId = keyId; - this.websocketURL = websocketURL; - - this.listeners = new ArrayList<>(); - } - - @Override - public void addListener(StreamListener listener) throws WebsocketException { - Preconditions.checkState(listener instanceof PolygonStreamListener); - - if (listeners.isEmpty()) { - try { - connect(); - } catch (IOException | URISyntaxException | DeploymentException exception) { - throw new WebsocketException(exception); - } - } - - listeners.add((PolygonStreamListener) listener); - - submitStreamRequest(PolygonStreamAction.SUBSCRIBE, (PolygonStreamListener) listener); - } - - @Override - public void removeListener(StreamListener listener) throws WebsocketException { - Preconditions.checkState(listener instanceof PolygonStreamListener); - - listeners.remove(listener); - - if (listeners.isEmpty()) { - try { - disconnect(); - } catch (Exception exception) { - throw new WebsocketException(exception); - } - } else { - submitStreamRequest(PolygonStreamAction.UNSUBSCRIBE, (PolygonStreamListener) listener); - } - } - - @Override - public void connect() throws URISyntaxException, IOException, DeploymentException { - LOGGER.info("Connecting..."); - - polygonWebsocketClientEndpoint = new PolygonWebsocketClientEndpoint(this, new URI(websocketURL)); - polygonWebsocketClientEndpoint.setAutomaticallyReconnect(true); - polygonWebsocketClientEndpoint.connect(); - - LOGGER.info("Connected."); - } - - @Override - public void disconnect() throws Exception { - LOGGER.info("Disconnecting..."); - - polygonWebsocketClientEndpoint.disconnect(); - - LOGGER.info("Disconnected."); - } - - @Override - public void sendAuthenticationMessage() { - // Format of message is: {"action":"auth","params":"{API_KEY}"} - - JsonObject authRequest = new JsonObject(); - authRequest.addProperty("action", "auth"); - authRequest.addProperty("params", keyId); - - polygonWebsocketClientEndpoint.sendMessage(authRequest.toString()); - } - - @Override - public void handleResubscribing() { - listeners.forEach(polygonStreamListener -> - submitStreamRequest(PolygonStreamAction.SUBSCRIBE, polygonStreamListener)); - } - - @Override - public void handleWebsocketMessage(String message) { - JsonElement messageJsonElement = GsonUtil.JSON_PARSER.parse(message); - - Preconditions.checkState(messageJsonElement instanceof JsonArray); - - for (JsonElement jsonElement : messageJsonElement.getAsJsonArray()) { - JsonObject messageJsonObject = jsonElement.getAsJsonObject(); - JsonElement eventTypeJsonElement = messageJsonObject.get(EVENT_TYPE_KEY); - - if (eventTypeJsonElement instanceof JsonPrimitive) { - try { - PolygonStreamMessageType polygonStreamMessageType = GsonUtil.GSON.fromJson(eventTypeJsonElement, - PolygonStreamMessageType.class); - - switch (polygonStreamMessageType) { - case STATUS: - StatusMessage statusMessage = GsonUtil.GSON.fromJson(messageJsonObject, - StatusMessage.class); - sendStreamMessageToListeners(polygonStreamMessageType, statusMessage); - - authenticated = isAuthenticatedStatusMessage(statusMessage); - - LOGGER.debug("{}", statusMessage.toString()); - break; - case TRADE: - sendStreamMessageToListeners(polygonStreamMessageType, - GsonUtil.GSON.fromJson(messageJsonObject, TradeMessage.class)); - break; - case QUOTE: - sendStreamMessageToListeners(polygonStreamMessageType, - GsonUtil.GSON.fromJson(messageJsonObject, QuoteMessage.class)); - break; - case AGGREGATE_PER_SECOND: - sendStreamMessageToListeners(polygonStreamMessageType, - GsonUtil.GSON.fromJson(messageJsonObject, AggregatePerSecondMessage.class)); - break; - case AGGREGATE_PER_MINUTE: - sendStreamMessageToListeners(polygonStreamMessageType, - GsonUtil.GSON.fromJson(messageJsonObject, AggregatePerMinuteMessage.class)); - break; - default: - LOGGER.error("Unknown stream object: {}", messageJsonObject); - } - } catch (JsonSyntaxException exception) { - LOGGER.error("Could not parse Websocket message!", exception); - } - } else { - LOGGER.error("Unknown stream message: {}", messageJsonObject); - } - } - } - - @Override - public void sendStreamMessageToListeners(StreamMessageType streamMessageType, StreamMessage streamMessage) { - Preconditions.checkState(streamMessageType instanceof PolygonStreamMessageType); - Preconditions.checkState(streamMessage instanceof PolygonStreamMessage); - - PolygonStreamMessageType polygonStreamMessageType = (PolygonStreamMessageType) streamMessageType; - PolygonStreamMessage polygonStreamMessage = (PolygonStreamMessage) streamMessage; - - for (PolygonStreamListener streamListener : new ArrayList<>(listeners)) { - boolean sendToStreamListener = false; - - if (streamListener.getStockChannels().containsKey(polygonStreamMessage.getSym())) { - if (streamListener.getStockChannels().get(polygonStreamMessage.getSym()) - .contains(polygonStreamMessageType)) { - sendToStreamListener = true; - } - } else if (streamListener.getStockChannels().containsKey(ALL_TICKERS)) { - if (streamListener.getStockChannels().get(ALL_TICKERS).contains(polygonStreamMessageType)) { - sendToStreamListener = true; - } - } - - if (sendToStreamListener) { - streamListener.onStreamUpdate(polygonStreamMessageType, polygonStreamMessage); - } - } - } - - @Override - public boolean isConnected() { - return polygonWebsocketClientEndpoint.getUserSession().isOpen(); - } - - @Override - public boolean isAuthenticated() { - return authenticated; - } - - /** - * Is authenticated status message boolean. - * - * @param statusMessage the status message - * - * @return the boolean - */ - private boolean isAuthenticatedStatusMessage(StatusMessage statusMessage) { - return statusMessage.getStatus().equalsIgnoreCase("success") && - statusMessage.getMessage().equalsIgnoreCase("authenticated"); - } - - /** - * Submit stream request. - * - * @param polygonStreamAction the polygon stream action - * @param newListener the listener - */ - private void submitStreamRequest(PolygonStreamAction polygonStreamAction, PolygonStreamListener newListener) { - Preconditions.checkNotNull(polygonStreamAction); - Preconditions.checkNotNull(newListener); - - ArrayList actionTickerList = new ArrayList<>(); - - Map> registeredTickerChannels = getRegisteredTickerChannels(newListener); - Map> newListenerTickerChannels = newListener.getStockChannels(); - - for (String ticker : newListenerTickerChannels.keySet()) { - Set registeredChannels = registeredTickerChannels.get(ticker); - Set newListenerChannelTypes = newListenerTickerChannels.get(ticker); - - if (registeredTickerChannels.containsKey(ticker)) { - for (PolygonStreamMessageType listenerChannelType : newListenerChannelTypes) { - if (!listenerChannelType.isAPISubscribable()) { - continue; - } - - boolean isTickerChannelRegistered = registeredChannels.contains(listenerChannelType); - String formattedWebsocketTicker = formatWebsocketTicker(listenerChannelType, ticker); - - if (isTickerChannelRegistered) { - if (polygonStreamAction == PolygonStreamAction.UNSUBSCRIBE) { - LOGGER.warn("Cannot unsubscribe from {} for channel {} because it is being used by " + - "another stream listener!", ticker, listenerChannelType.name()); - } else if (polygonStreamAction == PolygonStreamAction.SUBSCRIBE) { - LOGGER.warn("Already subscribed to {} for channel {}", ticker, listenerChannelType.name()); - } - } else { // Not a registered channel by other stream listeners - actionTickerList.add(formattedWebsocketTicker); - - if (polygonStreamAction == PolygonStreamAction.UNSUBSCRIBE) { - LOGGER.info("Unsubscribing from {}", formattedWebsocketTicker); - } else if (polygonStreamAction == PolygonStreamAction.SUBSCRIBE) { - LOGGER.info("Subscribing to {}", formattedWebsocketTicker); - } - } - } - } else { // Not a registered ticker by other stream listeners - for (PolygonStreamMessageType listenerChannelType : newListenerChannelTypes) { - if (!listenerChannelType.isAPISubscribable()) { - continue; - } - - String formattedWebsocketTicker = formatWebsocketTicker(listenerChannelType, ticker); - - actionTickerList.add(formattedWebsocketTicker); - - if (polygonStreamAction == PolygonStreamAction.UNSUBSCRIBE) { - LOGGER.info("Unsubscribing from: {}", formattedWebsocketTicker); - } else if (polygonStreamAction == PolygonStreamAction.SUBSCRIBE) { - LOGGER.info("Subscribing to: {}", formattedWebsocketTicker); - } - } - } - } - - if (actionTickerList.isEmpty()) { - LOGGER.info("Did not change any channel listeners"); - } else { - // Format: {"action":"(un)subscribe","params":"T.AAPL,T.MSFT,T.TSLA"} - - StringJoiner commaActionTickers = new StringJoiner(","); - actionTickerList.forEach(commaActionTickers::add); - - JsonObject actionJsonObject = new JsonObject(); - actionJsonObject.addProperty("action", polygonStreamAction.getAPIName()); - actionJsonObject.addProperty("params", commaActionTickers.toString()); - - polygonWebsocketClientEndpoint.sendMessage(actionJsonObject.toString()); - - LOGGER.info("Requested subscriptions to update for: {}", getRegisteredTickerChannels(null)); - } - } - - /** - * Format websocket ticker string. - * - * @param polygonStreamMessageType the polygon stream message type - * @param ticker the ticker - * - * @return the string - */ - private String formatWebsocketTicker(PolygonStreamMessageType polygonStreamMessageType, String ticker) { - return polygonStreamMessageType.getAPIName() + "." + ticker; - } - - /** - * Gets registered ticker channels. - * - * @param exclude listener to exclude from the final map - * - * @return the registered ticker channels - */ - private Map> getRegisteredTickerChannels(PolygonStreamListener exclude) { - HashMap> registeredTickerChannels = new HashMap<>(); - - for (PolygonStreamListener streamListener : new ArrayList<>(listeners)) { - if (streamListener.equals(exclude)) { - continue; - } - - Map> stockChannelTypes = streamListener.getStockChannels(); - - for (String ticker : stockChannelTypes.keySet()) { - Set streamMessageTypes = stockChannelTypes.get(ticker); - Set subscribableStreamMessageTypes = streamMessageTypes == null ? - new HashSet<>() : streamMessageTypes.stream() - .filter(PolygonStreamMessageType::isAPISubscribable) - .collect(Collectors.toSet()); - - if (!registeredTickerChannels.containsKey(ticker)) { - registeredTickerChannels.put(ticker, subscribableStreamMessageTypes); - } else { - registeredTickerChannels.get(ticker).addAll(subscribableStreamMessageTypes); - } - } - } - - return registeredTickerChannels; - } - - /** - * The enum {@link PolygonStreamAction}. - */ - private enum PolygonStreamAction { - /** The subscribe. */ - SUBSCRIBE("subscribe"), - - /** The unsubscribe. */ - UNSUBSCRIBE("unsubscribe"); - - /** The API name. */ - String apiName; - - /** - * Instantiates a new {@link PolygonStreamAction}. - * - * @param apiName the api name - */ - PolygonStreamAction(String apiName) { - this.apiName = apiName; - } - - /** - * Gets API name. - * - * @return the API name - */ - public String getAPIName() { - return apiName; - } - } -} diff --git a/src/main/java/net/jacobpeterson/polygon/websocket/client/PolygonWebsocketClientEndpoint.java b/src/main/java/net/jacobpeterson/polygon/websocket/client/PolygonWebsocketClientEndpoint.java deleted file mode 100644 index 631a40a6..00000000 --- a/src/main/java/net/jacobpeterson/polygon/websocket/client/PolygonWebsocketClientEndpoint.java +++ /dev/null @@ -1,50 +0,0 @@ -package net.jacobpeterson.polygon.websocket.client; - -import net.jacobpeterson.abstracts.websocket.client.AbstractWebsocketClientEndpoint; -import net.jacobpeterson.abstracts.websocket.client.WebsocketClient; - -import javax.websocket.ClientEndpoint; -import javax.websocket.CloseReason; -import javax.websocket.OnClose; -import javax.websocket.OnError; -import javax.websocket.OnMessage; -import javax.websocket.OnOpen; -import javax.websocket.Session; -import java.net.URI; - -/** - * The type {@link PolygonWebsocketClientEndpoint}. - */ -@ClientEndpoint(subprotocols = "TEXT") -public class PolygonWebsocketClientEndpoint extends AbstractWebsocketClientEndpoint { - - /** - * Instantiates a new {@link PolygonWebsocketClientEndpoint}. - * - * @param websocketClient the websocket client - * @param endpointURI the endpoint uri - */ - public PolygonWebsocketClientEndpoint(WebsocketClient websocketClient, URI endpointURI) { - super(websocketClient, endpointURI, "PolygonWebsocketThread"); - } - - @OnOpen - public void onOpenAnnotated(Session userSession) { - super.onOpen(userSession); - } - - @OnClose - public void onCloseAnnotated(Session userSession, CloseReason reason) { - super.onClose(userSession, reason); - } - - @OnMessage - public void onMessageAnnotated(String message) { - super.onMessage(message); - } - - @OnError - public void onError(Throwable throwable) { - super.onError(throwable); - } -} diff --git a/src/main/java/net/jacobpeterson/polygon/websocket/listener/PolygonStreamListener.java b/src/main/java/net/jacobpeterson/polygon/websocket/listener/PolygonStreamListener.java deleted file mode 100644 index 1bca9c56..00000000 --- a/src/main/java/net/jacobpeterson/polygon/websocket/listener/PolygonStreamListener.java +++ /dev/null @@ -1,24 +0,0 @@ -package net.jacobpeterson.polygon.websocket.listener; - -import net.jacobpeterson.abstracts.websocket.listener.StreamListener; -import net.jacobpeterson.domain.polygon.websocket.PolygonStreamMessage; -import net.jacobpeterson.polygon.websocket.message.PolygonStreamMessageType; - -import java.util.Map; -import java.util.Set; - -/** - * The listener interface for receiving polygonStream events. The class that is interested in processing a - * ChannelMessage event implements this interface, and the object created with that class is registered with a component - * using the component's addPolygonStreamListener method. When the ChannelMessage event occurs, that - * object's appropriate method is invoked. - */ -public interface PolygonStreamListener extends StreamListener { - - /** - * Gets the stock channels for this listener. Null or empty to listen to all other stream messages. - * - * @return the stock channels - */ - Map> getStockChannels(); -} diff --git a/src/main/java/net/jacobpeterson/polygon/websocket/listener/PolygonStreamListenerAdapter.java b/src/main/java/net/jacobpeterson/polygon/websocket/listener/PolygonStreamListenerAdapter.java deleted file mode 100644 index 6d970abb..00000000 --- a/src/main/java/net/jacobpeterson/polygon/websocket/listener/PolygonStreamListenerAdapter.java +++ /dev/null @@ -1,70 +0,0 @@ -package net.jacobpeterson.polygon.websocket.listener; - -import net.jacobpeterson.domain.polygon.websocket.PolygonStreamMessage; -import net.jacobpeterson.polygon.websocket.message.PolygonStreamMessageType; - -import java.util.Arrays; -import java.util.HashMap; -import java.util.HashSet; -import java.util.Map; -import java.util.Map.Entry; -import java.util.Set; - -/** - * The Class {@link PolygonStreamListenerAdapter}. - */ -public class PolygonStreamListenerAdapter implements PolygonStreamListener { - - /** The stock channel types. */ - private final Map> stockChannels = new HashMap<>(); - - /** - * Instantiates a new {@link PolygonStreamListenerAdapter}. - * - * @param ticker the ticker - * @param channelTypes the channel types - */ - public PolygonStreamListenerAdapter(String ticker, PolygonStreamMessageType... channelTypes) { - if (channelTypes != null && channelTypes.length > 0) { - this.stockChannels.put(ticker, new HashSet<>(Arrays.asList(channelTypes))); - } else { - this.stockChannels.put(ticker, new HashSet<>(Arrays.asList(PolygonStreamMessageType.values()))); - } - } - - /** - * Instantiates a new {@link PolygonStreamListenerAdapter}. - * - * @param tickers the tickers - * @param channelTypes the channel types - */ - public PolygonStreamListenerAdapter(Set tickers, PolygonStreamMessageType... channelTypes) { - for (String ticker : tickers) { - if (channelTypes != null && channelTypes.length > 0) { - this.stockChannels.put(ticker, new HashSet<>(Arrays.asList(channelTypes))); - } else { - this.stockChannels.put(ticker, new HashSet<>(Arrays.asList(PolygonStreamMessageType.values()))); - } - } - } - - /** - * Instantiates a new {@link PolygonStreamListenerAdapter}. - * - * @param stockChannels the stock channel types - */ - public PolygonStreamListenerAdapter(Map> stockChannels) { - for (Entry> entry : stockChannels.entrySet()) { - this.stockChannels.put(entry.getKey(), new HashSet<>(entry.getValue())); - } - } - - @Override - public Map> getStockChannels() { - return stockChannels; - } - - @Override - public void onStreamUpdate(PolygonStreamMessageType streamMessageType, PolygonStreamMessage streamMessage) { - } -} diff --git a/src/main/java/net/jacobpeterson/polygon/websocket/message/PolygonStreamMessageType.java b/src/main/java/net/jacobpeterson/polygon/websocket/message/PolygonStreamMessageType.java deleted file mode 100644 index 64d651bb..00000000 --- a/src/main/java/net/jacobpeterson/polygon/websocket/message/PolygonStreamMessageType.java +++ /dev/null @@ -1,59 +0,0 @@ -package net.jacobpeterson.polygon.websocket.message; - -import com.google.gson.annotations.SerializedName; -import net.jacobpeterson.abstracts.enums.APIName; -import net.jacobpeterson.abstracts.websocket.message.StreamMessageType; -import net.jacobpeterson.util.gson.GsonUtil; - -/** - * The enum {@link PolygonStreamMessageType}. - */ -public enum PolygonStreamMessageType implements StreamMessageType, APIName { - - /** Status {@link PolygonStreamMessageType}. */ - @SerializedName("status") - STATUS(false), - - /** Trade {@link PolygonStreamMessageType}. */ - @SerializedName("T") - TRADE(true), - - /** Quote {@link PolygonStreamMessageType}. */ - @SerializedName("Q") - QUOTE(true), - - /** Aggregate per second {@link PolygonStreamMessageType}. */ - @SerializedName("A") - AGGREGATE_PER_SECOND(true), - - /** Aggregate per minute {@link PolygonStreamMessageType}. */ - @SerializedName("AM") - AGGREGATE_PER_MINUTE(true); - - /** Is API subscribable. */ - private final boolean isAPISubscribable; - - /** - * Instantiates a new {@link PolygonStreamMessageType}. - * - * @param isAPISubscribable the is api subscribable - */ - PolygonStreamMessageType(boolean isAPISubscribable) { - this.isAPISubscribable = isAPISubscribable; - } - - @Override - public String getAPIName() { - return GsonUtil.GSON.toJsonTree(this).getAsString(); - } - - /** - * Is API subscribable boolean. - * - * @return the boolean - */ - public boolean isAPISubscribable() { - return isAPISubscribable; - } -} - diff --git a/src/main/java/net/jacobpeterson/util/doc/Documentation2JSONSchema.java b/src/main/java/net/jacobpeterson/util/doc/Documentation2JSONSchema.java index 2fc23c8f..b0a68075 100644 --- a/src/main/java/net/jacobpeterson/util/doc/Documentation2JSONSchema.java +++ b/src/main/java/net/jacobpeterson/util/doc/Documentation2JSONSchema.java @@ -49,35 +49,7 @@ public static void printAlpacaHTMLWebDocAsJSONSchema(String alpacaDocXML) } /** - * This is a standalone helper method that converts response schema HTML copied from the Polygon docs page into a - * json schema property list that includes the type and the description. It's kinda hacky, but it helps in the - * generation of JSON POJOs with Javadocs. Must be in the format of a list of: - *
-     * <div class="description">
-     *     <div>
-     *         <span class="propLabels">
-     *             <span class="propName propOpt">name</span>
-     *             <span class="propType" title="string">string</span>
-     *         </span>
-     *         <span class="propDesc">Name of the item.</span>
-     *     </div>
-     *     ...
-     * </div>
-     * 
- * - * @param polygonDocXML the polygon doc xml - */ - public static void printPolygonHTMLWebDocAsJSONSchema(String polygonDocXML) - throws ParserConfigurationException, SAXException, IOException, XPathExpressionException { - String nameXPath = "//span[" + xPathClassName("propName") + "]"; - String typeXPath = "//span[" + xPathClassName("propType") + "]"; - String descriptionXPath = "//span[" + xPathClassName("propDesc") + "]"; - - System.out.println(getSchemaFromDoc(polygonDocXML, nameXPath, typeXPath, descriptionXPath)); - } - - /** - * Converts a generic HTML doc for Alpaca or Polygon with JSON property fields for "name", "type", and "description" + * Converts a generic HTML doc for Alpaca with JSON property fields for "name", "type", and "description" * into schema JSON used for * jsonSchema2POJO. * diff --git a/src/main/resources/polygon.properties.default b/src/main/resources/polygon.properties.default deleted file mode 100644 index a6ce6ec6..00000000 --- a/src/main/resources/polygon.properties.default +++ /dev/null @@ -1,10 +0,0 @@ -#Example: -#key_id = (Note that this will default to what is set in alpaca.properties) -#base_api_url = https://api.polygon.io -#web_socket_server_url = wss://alpaca.socket.polygon.io/stocks -#user_agent = Mozilla/5.0 AppleWebKit/537.36 (KHTML, like Gecko) Chrome/67.0.3396.87 Safari/537.36 - -#Defauls: -base_api_url = https://api.polygon.io -web_socket_server_url = wss://alpaca.socket.polygon.io/stocks -user_agent = Java/ Unirest/1.4.9 diff --git a/src/test/resources/polygon.properties.default b/src/test/resources/polygon.properties.default deleted file mode 100644 index 73cac850..00000000 --- a/src/test/resources/polygon.properties.default +++ /dev/null @@ -1,4 +0,0 @@ -key_id = ABCDEFGHIJKLMNOPQURSTUVWXYZ -base_api_url = https://api.polygon.io -web_socket_server_url = wss://alpaca.socket.polygon.io/stocks -user_agent = Java/ Unirest/1.4.9 From 2a70de6ae85eb0c91c001925e35094c8721fac66 Mon Sep 17 00:00:00 2001 From: Jacob Peterson <14127217+Petersoj@users.noreply.github.com> Date: Sat, 20 Feb 2021 15:16:32 -0700 Subject: [PATCH 087/271] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 2a3ee31d..e9b4a93a 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -

Logo

+

Logo

Maven Central Javadocs From 0a45e5b39dff689450354ae81c4d2bad852911cd Mon Sep 17 00:00:00 2001 From: Jacob Peterson Date: Sat, 27 Feb 2021 11:56:20 -0700 Subject: [PATCH 088/271] Optimize logging statements --- README.md | 2 ++ .../net/jacobpeterson/alpaca/AlpacaAPI.java | 19 ++++++++++++++++--- .../broker/client/AlpacaWebsocketClient.java | 12 ++++++------ .../client/MarketDataWebsocketClient.java | 16 ++++++++-------- .../util/doc/Documentation2JSONSchema.java | 4 ++-- .../net/jacobpeterson/util/gson/GsonUtil.java | 2 +- .../util/properties/PropertyUtil.java | 18 +++++++++--------- src/main/resources/alpaca.properties.default | 4 ++-- .../alpaca/AlpacaAPIEndpointTest.java | 8 ++++---- 9 files changed, 50 insertions(+), 35 deletions(-) diff --git a/README.md b/README.md index e9b4a93a..febd5994 100644 --- a/README.md +++ b/README.md @@ -484,10 +484,12 @@ To run mocked tests using Mockito, run: ``` ./gradlew test ``` +Note that mocked tests never send real API requests to Alpaca. Mocked tests are meant to test the basic integrity of this API locally. To run live endpoint tests with Alpaca Paper credentials, create the `alpaca.properties` file in `src/test/resources` with the corresponding credentials. Then run: ``` ./gradlew test -PtestPackage=live ``` +Note that the live tests will modify your account minimally. It's meant to test live endpoints on a real paper account during market hours to confirm that API methods are working properly. Please read through the live endpoint tests [here](https://github.com/Petersoj/alpaca-java/tree/master/src/test/java/live/net/jacobpeterson/alpaca) before running this testing suite on your own paper account. Contributions are welcome! diff --git a/src/main/java/net/jacobpeterson/alpaca/AlpacaAPI.java b/src/main/java/net/jacobpeterson/alpaca/AlpacaAPI.java index fd12e287..3d5eae19 100644 --- a/src/main/java/net/jacobpeterson/alpaca/AlpacaAPI.java +++ b/src/main/java/net/jacobpeterson/alpaca/AlpacaAPI.java @@ -11,7 +11,16 @@ import net.jacobpeterson.abstracts.websocket.exception.WebsocketException; import net.jacobpeterson.alpaca.AlpacaConstants.Endpoints; import net.jacobpeterson.alpaca.AlpacaConstants.Parameters; -import net.jacobpeterson.alpaca.enums.*; +import net.jacobpeterson.alpaca.enums.ActivityType; +import net.jacobpeterson.alpaca.enums.AssetStatus; +import net.jacobpeterson.alpaca.enums.BarsTimeFrame; +import net.jacobpeterson.alpaca.enums.OrderClass; +import net.jacobpeterson.alpaca.enums.OrderSide; +import net.jacobpeterson.alpaca.enums.OrderStatus; +import net.jacobpeterson.alpaca.enums.OrderTimeInForce; +import net.jacobpeterson.alpaca.enums.OrderType; +import net.jacobpeterson.alpaca.enums.PortfolioPeriodUnit; +import net.jacobpeterson.alpaca.enums.PortfolioTimeFrame; import net.jacobpeterson.alpaca.properties.AlpacaProperties; import net.jacobpeterson.alpaca.rest.AlpacaRequest; import net.jacobpeterson.alpaca.rest.AlpacaRequestBuilder; @@ -47,7 +56,11 @@ import java.time.LocalDate; import java.time.ZonedDateTime; import java.time.format.DateTimeFormatter; -import java.util.*; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.Map; +import java.util.StringJoiner; import java.util.stream.Collectors; /** @@ -137,7 +150,7 @@ public AlpacaAPI(String keyID, String secret, String oAuthToken, String baseAPIU marketDataWebSocketClient = isOAuth ? new MarketDataWebsocketClient(oAuthToken, baseDataURL) : new MarketDataWebsocketClient(keyID, secret, baseDataURL); - LOGGER.debug(this.toString()); + LOGGER.debug("{}", this); } /** diff --git a/src/main/java/net/jacobpeterson/alpaca/websocket/broker/client/AlpacaWebsocketClient.java b/src/main/java/net/jacobpeterson/alpaca/websocket/broker/client/AlpacaWebsocketClient.java index b76f299e..d579f0ce 100644 --- a/src/main/java/net/jacobpeterson/alpaca/websocket/broker/client/AlpacaWebsocketClient.java +++ b/src/main/java/net/jacobpeterson/alpaca/websocket/broker/client/AlpacaWebsocketClient.java @@ -198,14 +198,14 @@ public void handleWebsocketMessage(String message) { authenticated = isAuthorizationMessageSuccess(authorizationMessage); - LOGGER.debug("{}", authorizationMessage.toString()); + LOGGER.debug("{}", authorizationMessage); break; case LISTENING: ListeningMessage listeningMessage = GsonUtil.GSON.fromJson(messageJsonObject, ListeningMessage.class); sendStreamMessageToListeners(alpacaStreamMessageType, listeningMessage); - LOGGER.debug("{}", listeningMessage.toString()); + LOGGER.debug("{}", listeningMessage); break; case TRADE_UPDATES: sendStreamMessageToListeners(alpacaStreamMessageType, GsonUtil.GSON.fromJson(messageJsonObject, @@ -216,13 +216,13 @@ public void handleWebsocketMessage(String message) { AccountUpdateMessage.class)); break; default: - LOGGER.error("Unhandled stream type: " + alpacaStreamMessageType); + LOGGER.error("Unhandled stream type: {}", alpacaStreamMessageType); } } catch (JsonSyntaxException exception) { - LOGGER.error("Could not parse message: " + messageJsonObject, exception); + LOGGER.error("Could not parse message: {}\n{}", messageJsonObject, exception); } } else { - LOGGER.error("Unknown stream message: " + messageJsonObject); + LOGGER.error("Unknown stream message: {}", messageJsonObject); } } @@ -291,7 +291,7 @@ private void submitStreamRequestUpdate() { alpacaWebsocketClientEndpoint.sendMessage(streamRequestJsonObject.toString()); - LOGGER.info("Requested subscriptions to update to " + streamsJsonArray); + LOGGER.info("Requested subscriptions to update to {}", streamsJsonArray); } /** diff --git a/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/client/MarketDataWebsocketClient.java b/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/client/MarketDataWebsocketClient.java index b4388946..838ef118 100644 --- a/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/client/MarketDataWebsocketClient.java +++ b/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/client/MarketDataWebsocketClient.java @@ -246,37 +246,37 @@ public void handleWebsocketMessage(String message) { ListeningMessage.class); sendStreamMessageToListeners(marketDataStreamMessageType, listeningMessage); - LOGGER.debug("{}", listeningMessage.toString()); + LOGGER.debug("{}", listeningMessage); break; case TRADES: TradeMessage tradeMessage = GsonUtil.GSON.fromJson(messageJsonObject.get(DATA_KEY), TradeMessage.class); sendStreamMessageToListeners(marketDataStreamMessageType, tradeMessage); - LOGGER.debug("{}", tradeMessage.toString()); + LOGGER.debug("{}", tradeMessage); break; case QUOTES: QuoteMessage quoteMessage = GsonUtil.GSON.fromJson(messageJsonObject.get(DATA_KEY), QuoteMessage.class); sendStreamMessageToListeners(marketDataStreamMessageType, quoteMessage); - LOGGER.debug("{}", quoteMessage.toString()); + LOGGER.debug("{}", quoteMessage); break; case AGGREGATE_MINUTE: AggregateMinuteMessage aggregateMinuteMessage = GsonUtil.GSON.fromJson( messageJsonObject.get(DATA_KEY), AggregateMinuteMessage.class); sendStreamMessageToListeners(marketDataStreamMessageType, aggregateMinuteMessage); - LOGGER.debug("{}", aggregateMinuteMessage.toString()); + LOGGER.debug("{}", aggregateMinuteMessage); break; default: - LOGGER.error("Unhandled stream type: " + marketDataStreamMessageType); + LOGGER.error("Unhandled stream type: {}", marketDataStreamMessageType); } } catch (JsonParseException exception) { - LOGGER.error("Could not parse message: " + messageJsonObject, exception); + LOGGER.error("Could not parse message: {}\n{}", messageJsonObject, exception); } } else { - LOGGER.error("Unknown stream message: " + messageJsonObject); + LOGGER.error("Unknown stream message: {}", messageJsonObject); } } } @@ -376,7 +376,7 @@ private void submitStreamRequest() { marketDataWebsocketClientEndpoint.sendMessage(streamRequestJsonObject.toString()); - LOGGER.info("Requested subscriptions to update to " + streamsJsonArray); + LOGGER.info("Requested subscriptions to update to {}", streamsJsonArray); } /** diff --git a/src/main/java/net/jacobpeterson/util/doc/Documentation2JSONSchema.java b/src/main/java/net/jacobpeterson/util/doc/Documentation2JSONSchema.java index b0a68075..32e77970 100644 --- a/src/main/java/net/jacobpeterson/util/doc/Documentation2JSONSchema.java +++ b/src/main/java/net/jacobpeterson/util/doc/Documentation2JSONSchema.java @@ -49,8 +49,8 @@ public static void printAlpacaHTMLWebDocAsJSONSchema(String alpacaDocXML) } /** - * Converts a generic HTML doc for Alpaca with JSON property fields for "name", "type", and "description" - * into schema JSON used for + * Converts a generic HTML doc for Alpaca with JSON property fields for "name", "type", and "description" into + * schema JSON used for * jsonSchema2POJO. * * @param docHTML the doc html diff --git a/src/main/java/net/jacobpeterson/util/gson/GsonUtil.java b/src/main/java/net/jacobpeterson/util/gson/GsonUtil.java index f6f49236..f274e6e7 100644 --- a/src/main/java/net/jacobpeterson/util/gson/GsonUtil.java +++ b/src/main/java/net/jacobpeterson/util/gson/GsonUtil.java @@ -69,7 +69,7 @@ private static synchronized ArrayList getGsonSerializedNameAnnot return CLASS_ANNOTATION_CACHE.get(theClass); } - LOGGER.debug("Caching Gson @SerializedName annotations for " + theClass.getName()); + LOGGER.debug("Caching Gson @SerializedName annotations for {}", theClass.getName()); // Below is expensive, but it only has to be done once per class. diff --git a/src/main/java/net/jacobpeterson/util/properties/PropertyUtil.java b/src/main/java/net/jacobpeterson/util/properties/PropertyUtil.java index 78ac1c3a..d00e382c 100644 --- a/src/main/java/net/jacobpeterson/util/properties/PropertyUtil.java +++ b/src/main/java/net/jacobpeterson/util/properties/PropertyUtil.java @@ -75,20 +75,20 @@ public synchronized static Properties loadPropertyFile(String propertyFile) { // Load the properties try { defaultProperties.load(defaultPropertyStream); - LOGGER.debug("Loaded default properties file: " + defaultPropertyFile); + LOGGER.debug("Loaded default properties file: {}", defaultPropertyFile); } catch (IOException exception) { - LOGGER.error("Could not load default property file: " + defaultPropertyFile, exception); + LOGGER.error("Could not load default property file: {}\n{}", defaultPropertyFile, exception); } // Close the InputStream try { defaultPropertyStream.close(); } catch (IOException exception) { - LOGGER.error("Could not close default property file stream: " + defaultPropertyFile, exception); + LOGGER.error("Could not close default property file stream: {}\n{}", defaultPropertyFile, exception); } } else { - LOGGER.warn("No default property file found for: " + propertyFile); + LOGGER.warn("No default property file found for: {}", propertyFile); } // Load the property file @@ -101,22 +101,22 @@ public synchronized static Properties loadPropertyFile(String propertyFile) { // Load the properties try { properties.load(propertyStream); - LOGGER.info("Loaded properties file: " + propertyFile); + LOGGER.info("Loaded properties file: {}", propertyFile); } catch (IOException exception) { - LOGGER.error("Could not load property file: " + propertyFile, exception); + LOGGER.error("Could not load property file: {}\n{}", propertyFile, exception); } // Close the InputStream try { propertyStream.close(); } catch (IOException exception) { - LOGGER.error("Could not close property file stream: " + propertyFile, exception); + LOGGER.error("Could not close property file stream: {}\n{}", propertyFile, exception); } } else { - LOGGER.error("Could not find property file: " + propertyFile); + LOGGER.error("Could not find property file: {}", propertyFile); if (defaultProperties != null) { - LOGGER.info("Using default properties for: " + propertyFile); + LOGGER.info("Using default properties for: {}", propertyFile); properties = defaultProperties; } } diff --git a/src/main/resources/alpaca.properties.default b/src/main/resources/alpaca.properties.default index 6743623f..15ecc4f5 100644 --- a/src/main/resources/alpaca.properties.default +++ b/src/main/resources/alpaca.properties.default @@ -3,9 +3,9 @@ #secret = #base_api_url = https://paper-api.alpaca.markets #base_data_url = https://data.alpaca.markets -#user_agent=Mozilla/5.0 AppleWebKit/537.36 (KHTML, like Gecko) Chrome/67.0.3396.87 Safari/537.36 +#user_agent = JavaRuntimeEnvironment/ Unirest/1.4.9 #Defaults: base_api_url = https://paper-api.alpaca.markets base_data_url = https://data.alpaca.markets -user_agent = Java/ Unirest/1.4.9 +user_agent = JavaRuntimeEnvironment/ Unirest/1.4.9 diff --git a/src/test/java/live/net/jacobpeterson/alpaca/AlpacaAPIEndpointTest.java b/src/test/java/live/net/jacobpeterson/alpaca/AlpacaAPIEndpointTest.java index 3927633a..6e8ca15e 100644 --- a/src/test/java/live/net/jacobpeterson/alpaca/AlpacaAPIEndpointTest.java +++ b/src/test/java/live/net/jacobpeterson/alpaca/AlpacaAPIEndpointTest.java @@ -82,7 +82,7 @@ public void test_getClock() throws AlpacaAPIRequestException { Clock clock = alpacaAPI.getClock(); assertNotNull(clock); - LOGGER.debug(clock.toString()); + LOGGER.debug("{}", clock); assertNotNull(clock.getTimestamp()); assertNotNull(clock.getIsOpen()); @@ -109,7 +109,7 @@ public void test_getAccount() throws AlpacaAPIRequestException, NumberFormatExce Account account = alpacaAPI.getAccount(); assertNotNull(account); - LOGGER.debug(account.toString()); + LOGGER.debug("{}", account); // Assert basic data integrity and not null Double.parseDouble(account.getCash()); @@ -201,7 +201,7 @@ public void test_getAccountConfiguration() throws AlpacaAPIRequestException { AccountConfiguration accountConfiguration = alpacaAPI.getAccountConfiguration(); assertNotNull(accountConfiguration); - LOGGER.debug(accountConfiguration.toString()); + LOGGER.debug("{}", accountConfiguration); assertNotNull(accountConfiguration.getDtbpCheck()); assertNotNull(accountConfiguration.getTradeConfirmEmail()); @@ -252,7 +252,7 @@ public void test_getOrders_One_Order_Exists_Until_now() throws AlpacaAPIRequestE assertNotNull(orders); assertFalse(orders.isEmpty()); - orders.forEach(order -> LOGGER.debug(order.toString())); + orders.forEach(order -> LOGGER.debug("{}", order)); // Assert required fields are present Order order = orders.get(0); From 9097471c901b2225679419f10b82aec15bb641a6 Mon Sep 17 00:00:00 2001 From: Jacob Peterson Date: Sat, 27 Feb 2021 13:29:07 -0700 Subject: [PATCH 089/271] Modify properties and refactor --- schema_json/alpaca/account/account.json | 2 +- .../portfolio_history/portfolio_history.json | 2 +- .../net/jacobpeterson/alpaca/AlpacaAPI.java | 125 ++++++++---------- .../jacobpeterson/alpaca/AlpacaConstants.java | 8 ++ .../alpaca/enums/BarsTimeFrame.java | 38 ------ .../enums/{ => account}/AccountStatus.java | 4 +- .../enums/{ => activity}/ActivityType.java | 4 +- .../alpaca/enums/api/EndpointAPIType.java | 65 +++++++++ .../alpaca/enums/{ => asset}/AssetStatus.java | 2 +- .../alpaca/enums/{ => order}/OrderClass.java | 2 +- .../alpaca/enums/{ => order}/OrderSide.java | 2 +- .../alpaca/enums/{ => order}/OrderStatus.java | 2 +- .../enums/{ => order}/OrderTimeInForce.java | 2 +- .../alpaca/enums/{ => order}/OrderType.java | 2 +- .../{ => portfolio}/PortfolioPeriodUnit.java | 2 +- .../{ => portfolio}/PortfolioTimeFrame.java | 2 +- .../alpaca/properties/AlpacaProperties.java | 50 ++++--- .../util/properties/PropertyUtil.java | 64 +++++---- .../util/reflection/ReflectionUtil.java | 68 ++++++++++ src/main/resources/alpaca.default.properties | 9 ++ src/main/resources/alpaca.properties.default | 11 -- .../alpaca/AlpacaAPIEndpointTest.java | 4 +- .../jacobpeterson/alpaca/AlpacaAPITest.java | 5 +- src/test/resources/alpaca.default.properties | 5 + src/test/resources/alpaca.properties.default | 5 - 25 files changed, 298 insertions(+), 187 deletions(-) delete mode 100644 src/main/java/net/jacobpeterson/alpaca/enums/BarsTimeFrame.java rename src/main/java/net/jacobpeterson/alpaca/enums/{ => account}/AccountStatus.java (93%) rename src/main/java/net/jacobpeterson/alpaca/enums/{ => activity}/ActivityType.java (94%) create mode 100644 src/main/java/net/jacobpeterson/alpaca/enums/api/EndpointAPIType.java rename src/main/java/net/jacobpeterson/alpaca/enums/{ => asset}/AssetStatus.java (92%) rename src/main/java/net/jacobpeterson/alpaca/enums/{ => order}/OrderClass.java (94%) rename src/main/java/net/jacobpeterson/alpaca/enums/{ => order}/OrderSide.java (93%) rename src/main/java/net/jacobpeterson/alpaca/enums/{ => order}/OrderStatus.java (93%) rename src/main/java/net/jacobpeterson/alpaca/enums/{ => order}/OrderTimeInForce.java (98%) rename src/main/java/net/jacobpeterson/alpaca/enums/{ => order}/OrderType.java (98%) rename src/main/java/net/jacobpeterson/alpaca/enums/{ => portfolio}/PortfolioPeriodUnit.java (94%) rename src/main/java/net/jacobpeterson/alpaca/enums/{ => portfolio}/PortfolioTimeFrame.java (94%) create mode 100644 src/main/java/net/jacobpeterson/util/reflection/ReflectionUtil.java create mode 100644 src/main/resources/alpaca.default.properties delete mode 100644 src/main/resources/alpaca.properties.default create mode 100644 src/test/resources/alpaca.default.properties delete mode 100644 src/test/resources/alpaca.properties.default diff --git a/schema_json/alpaca/account/account.json b/schema_json/alpaca/account/account.json index fb93106b..34ea99e6 100644 --- a/schema_json/alpaca/account/account.json +++ b/schema_json/alpaca/account/account.json @@ -11,7 +11,7 @@ "title": "Account number." }, "status": { - "existingJavaType": "net.jacobpeterson.alpaca.enums.AccountStatus", + "existingJavaType": "net.jacobpeterson.alpaca.enums.account.AccountStatus", "title": "See Account Status" }, "currency": { diff --git a/schema_json/alpaca/portfolio_history/portfolio_history.json b/schema_json/alpaca/portfolio_history/portfolio_history.json index 254eacdf..b5b10e50 100644 --- a/schema_json/alpaca/portfolio_history/portfolio_history.json +++ b/schema_json/alpaca/portfolio_history/portfolio_history.json @@ -23,7 +23,7 @@ "title": "Basis in dollar of the profit loss calculation" }, "timeframe": { - "existingJavaType": "net.jacobpeterson.alpaca.enums.PortfolioTimeFrame", + "existingJavaType": "net.jacobpeterson.alpaca.enums.portfolio.PortfolioTimeFrame", "title": "Time window size of each data element" } } diff --git a/src/main/java/net/jacobpeterson/alpaca/AlpacaAPI.java b/src/main/java/net/jacobpeterson/alpaca/AlpacaAPI.java index 3d5eae19..63c73dfa 100644 --- a/src/main/java/net/jacobpeterson/alpaca/AlpacaAPI.java +++ b/src/main/java/net/jacobpeterson/alpaca/AlpacaAPI.java @@ -11,16 +11,12 @@ import net.jacobpeterson.abstracts.websocket.exception.WebsocketException; import net.jacobpeterson.alpaca.AlpacaConstants.Endpoints; import net.jacobpeterson.alpaca.AlpacaConstants.Parameters; -import net.jacobpeterson.alpaca.enums.ActivityType; -import net.jacobpeterson.alpaca.enums.AssetStatus; -import net.jacobpeterson.alpaca.enums.BarsTimeFrame; -import net.jacobpeterson.alpaca.enums.OrderClass; -import net.jacobpeterson.alpaca.enums.OrderSide; -import net.jacobpeterson.alpaca.enums.OrderStatus; -import net.jacobpeterson.alpaca.enums.OrderTimeInForce; -import net.jacobpeterson.alpaca.enums.OrderType; -import net.jacobpeterson.alpaca.enums.PortfolioPeriodUnit; -import net.jacobpeterson.alpaca.enums.PortfolioTimeFrame; +import net.jacobpeterson.alpaca.enums.activity.ActivityType; +import net.jacobpeterson.alpaca.enums.api.EndpointAPIType; +import net.jacobpeterson.alpaca.enums.asset.AssetStatus; +import net.jacobpeterson.alpaca.enums.order.*; +import net.jacobpeterson.alpaca.enums.portfolio.PortfolioPeriodUnit; +import net.jacobpeterson.alpaca.enums.portfolio.PortfolioTimeFrame; import net.jacobpeterson.alpaca.properties.AlpacaProperties; import net.jacobpeterson.alpaca.rest.AlpacaRequest; import net.jacobpeterson.alpaca.rest.AlpacaRequestBuilder; @@ -56,11 +52,7 @@ import java.time.LocalDate; import java.time.ZonedDateTime; import java.time.format.DateTimeFormatter; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; -import java.util.Map; -import java.util.StringJoiner; +import java.util.*; import java.util.stream.Collectors; /** @@ -77,19 +69,17 @@ public class AlpacaAPI { private static final Logger LOGGER = LoggerFactory.getLogger(AlpacaAPI.class); private final String keyID; - private final String baseAPIURL; + private final String apiURL; private final AlpacaRequest alpacaRequest; - private final String baseDataUrl; private final AlpacaWebsocketClient alpacaWebSocketClient; private final MarketDataWebsocketClient marketDataWebSocketClient; /** * Instantiates a new {@link AlpacaAPI} using properties specified in alpaca.properties file (or their - * associated defaults) + * associated defaults). */ public AlpacaAPI() { - this(AlpacaProperties.KEY_ID_VALUE, AlpacaProperties.SECRET_VALUE, null, AlpacaProperties.BASE_API_URL_VALUE, - AlpacaProperties.BASE_DATA_URL_VALUE); + this(AlpacaProperties.KEY_ID_VALUE, AlpacaProperties.SECRET_VALUE, null); LOGGER.debug(AlpacaProperties.staticToString()); } @@ -101,18 +91,18 @@ public AlpacaAPI() { * @param secret the secret */ public AlpacaAPI(String keyID, String secret) { - this(keyID, secret, null, AlpacaProperties.BASE_API_URL_VALUE, AlpacaProperties.BASE_DATA_URL_VALUE); + this(keyID, secret, null, EndpointAPIType.fromPropertyName(AlpacaProperties.API_ENDPOINT_TYPE_VALUE)); } /** * Instantiates a new {@link AlpacaAPI}. * - * @param keyID the key ID - * @param secret the secret - * @param baseAPIURL the API URL + * @param keyID the key ID + * @param secret the secret + * @param endpointAPIType the {@link EndpointAPIType} */ - public AlpacaAPI(String keyID, String secret, String baseAPIURL) { - this(keyID, secret, null, baseAPIURL, AlpacaProperties.BASE_DATA_URL_VALUE); + public AlpacaAPI(String keyID, String secret, EndpointAPIType endpointAPIType) { + this(keyID, secret, null, endpointAPIType); } /** @@ -121,34 +111,32 @@ public AlpacaAPI(String keyID, String secret, String baseAPIURL) { * @param oAuthToken the OAuth token */ public AlpacaAPI(String oAuthToken) { - this(null, null, oAuthToken, AlpacaProperties.BASE_API_URL_VALUE, AlpacaProperties.BASE_DATA_URL_VALUE); + this(null, null, oAuthToken, EndpointAPIType.fromPropertyName(AlpacaProperties.API_ENDPOINT_TYPE_VALUE)); } /** * Instantiates a new {@link AlpacaAPI}. * - * @param keyID the key ID - * @param secret the secret - * @param oAuthToken the OAuth token - * @param baseAPIURL the base API URL - * @param baseDataURL the base data URL + * @param keyID the key ID + * @param secret the secret + * @param oAuthToken the OAuth token + * @param endpointAPIType the {@link EndpointAPIType} */ - public AlpacaAPI(String keyID, String secret, String oAuthToken, String baseAPIURL, - String baseDataURL) { + public AlpacaAPI(String keyID, String secret, String oAuthToken, EndpointAPIType endpointAPIType) { Preconditions.checkState((keyID != null && secret != null) || oAuthToken != null); + Preconditions.checkNotNull(endpointAPIType); this.keyID = keyID; - this.baseAPIURL = baseAPIURL; - this.baseDataUrl = baseDataURL; + this.apiURL = endpointAPIType.getURL(); boolean isOAuth = oAuthToken != null; alpacaRequest = isOAuth ? new AlpacaRequest(oAuthToken) : new AlpacaRequest(keyID, secret); - alpacaWebSocketClient = isOAuth ? new AlpacaWebsocketClient(oAuthToken, baseAPIURL) : - new AlpacaWebsocketClient(keyID, secret, baseAPIURL); - marketDataWebSocketClient = isOAuth ? new MarketDataWebsocketClient(oAuthToken, baseDataURL) : - new MarketDataWebsocketClient(keyID, secret, baseDataURL); + alpacaWebSocketClient = isOAuth ? new AlpacaWebsocketClient(oAuthToken) : + new AlpacaWebsocketClient(keyID, secret); + marketDataWebSocketClient = isOAuth ? new MarketDataWebsocketClient(oAuthToken) : + new MarketDataWebsocketClient(keyID, secret); LOGGER.debug("{}", this); } @@ -162,7 +150,7 @@ public AlpacaAPI(String keyID, String secret, String oAuthToken, String baseAPIU * @see Get the Account */ public Account getAccount() throws AlpacaAPIRequestException { - AlpacaRequestBuilder urlBuilder = new AlpacaRequestBuilder(baseAPIURL, Endpoints.VERSION_2, + AlpacaRequestBuilder urlBuilder = new AlpacaRequestBuilder(apiURL, Endpoints.VERSION_2, Endpoints.ACCOUNT); HttpResponse response = alpacaRequest.invokeGet(urlBuilder); @@ -196,7 +184,7 @@ public Account getAccount() throws AlpacaAPIRequestException { public List getAccountActivities(ZonedDateTime date, ZonedDateTime until, ZonedDateTime after, SortDirection sortDirection, Integer pageSize, String pageToken, ActivityType... activityTypes) throws AlpacaAPIRequestException { - AlpacaRequestBuilder urlBuilder = new AlpacaRequestBuilder(baseAPIURL, Endpoints.VERSION_2, + AlpacaRequestBuilder urlBuilder = new AlpacaRequestBuilder(apiURL, Endpoints.VERSION_2, Endpoints.ACCOUNT, Endpoints.ACTIVITIES); @@ -279,7 +267,7 @@ public List getAccountActivities(ZonedDateTime date, ZonedDateT * Configuration */ public AccountConfiguration getAccountConfiguration() throws AlpacaAPIRequestException { - AlpacaRequestBuilder urlBuilder = new AlpacaRequestBuilder(baseAPIURL, Endpoints.VERSION_2, + AlpacaRequestBuilder urlBuilder = new AlpacaRequestBuilder(apiURL, Endpoints.VERSION_2, Endpoints.ACCOUNT, Endpoints.CONFIGURATIONS); @@ -307,7 +295,7 @@ public AccountConfiguration setAccountConfiguration(AccountConfiguration account throws AlpacaAPIRequestException { Preconditions.checkNotNull(accountConfiguration); - AlpacaRequestBuilder urlBuilder = new AlpacaRequestBuilder(baseAPIURL, Endpoints.VERSION_2, + AlpacaRequestBuilder urlBuilder = new AlpacaRequestBuilder(apiURL, Endpoints.VERSION_2, Endpoints.ACCOUNT, Endpoints.CONFIGURATIONS); urlBuilder.setCustomBody(GsonUtil.GSON.toJson(accountConfiguration)); @@ -340,7 +328,7 @@ public AccountConfiguration setAccountConfiguration(AccountConfiguration account */ public List getOrders(OrderStatus status, Integer limit, ZonedDateTime after, ZonedDateTime until, SortDirection sortDirection, Boolean nested, List symbols) throws AlpacaAPIRequestException { - AlpacaRequestBuilder urlBuilder = new AlpacaRequestBuilder(baseAPIURL, Endpoints.VERSION_2, + AlpacaRequestBuilder urlBuilder = new AlpacaRequestBuilder(apiURL, Endpoints.VERSION_2, Endpoints.ORDERS); if (status != null) { @@ -426,7 +414,7 @@ public Order requestNewOrder(String symbol, Integer quantity, OrderSide side, Or Preconditions.checkNotNull(type); Preconditions.checkNotNull(timeInForce); - AlpacaRequestBuilder urlBuilder = new AlpacaRequestBuilder(baseAPIURL, Endpoints.VERSION_2, + AlpacaRequestBuilder urlBuilder = new AlpacaRequestBuilder(apiURL, Endpoints.VERSION_2, Endpoints.ORDERS); urlBuilder.appendJSONBodyProperty(Parameters.SYMBOL, symbol); @@ -880,7 +868,7 @@ public Order requestNewTrailingStopPercentOrder(String symbol, Integer quantity, public Order getOrder(String orderID, Boolean nested) throws AlpacaAPIRequestException { Preconditions.checkNotNull(orderID); - AlpacaRequestBuilder urlBuilder = new AlpacaRequestBuilder(baseAPIURL, Endpoints.VERSION_2, + AlpacaRequestBuilder urlBuilder = new AlpacaRequestBuilder(apiURL, Endpoints.VERSION_2, Endpoints.ORDERS, orderID); @@ -910,7 +898,7 @@ public Order getOrder(String orderID, Boolean nested) throws AlpacaAPIRequestExc public Order getOrderByClientID(String clientOrderId) throws AlpacaAPIRequestException { Preconditions.checkNotNull(clientOrderId); - AlpacaRequestBuilder urlBuilder = new AlpacaRequestBuilder(baseAPIURL, Endpoints.VERSION_2, + AlpacaRequestBuilder urlBuilder = new AlpacaRequestBuilder(apiURL, Endpoints.VERSION_2, Endpoints.ORDERS_BY_CLIENT_ORDER_ID); urlBuilder.appendURLParameter(Parameters.CLIENT_ORDER_ID, clientOrderId); @@ -944,7 +932,7 @@ public Order replaceOrder(String orderID, Integer quantity, OrderTimeInForce tim Double stopPrice, Double trail, String clientOrderId) throws AlpacaAPIRequestException { Preconditions.checkNotNull(orderID); - AlpacaRequestBuilder urlBuilder = new AlpacaRequestBuilder(baseAPIURL, Endpoints.VERSION_2, + AlpacaRequestBuilder urlBuilder = new AlpacaRequestBuilder(apiURL, Endpoints.VERSION_2, Endpoints.ORDERS, orderID); @@ -994,7 +982,7 @@ public Order replaceOrder(String orderID, Integer quantity, OrderTimeInForce tim * @see Orders */ public ArrayList cancelAllOrders() throws AlpacaAPIRequestException { - AlpacaRequestBuilder urlBuilder = new AlpacaRequestBuilder(baseAPIURL, Endpoints.VERSION_2, + AlpacaRequestBuilder urlBuilder = new AlpacaRequestBuilder(apiURL, Endpoints.VERSION_2, Endpoints.ORDERS); HttpResponse response = alpacaRequest.invokeDelete(urlBuilder); @@ -1022,7 +1010,7 @@ public ArrayList cancelAllOrders() throws AlpacaAPIRequestExcept public boolean cancelOrder(String orderId) throws AlpacaAPIRequestException { Preconditions.checkNotNull(orderId); - AlpacaRequestBuilder urlBuilder = new AlpacaRequestBuilder(baseAPIURL, Endpoints.VERSION_2, + AlpacaRequestBuilder urlBuilder = new AlpacaRequestBuilder(apiURL, Endpoints.VERSION_2, Endpoints.ORDERS, orderId); @@ -1044,7 +1032,7 @@ public boolean cancelOrder(String orderId) throws AlpacaAPIRequestException { * @see Positions */ public ArrayList getOpenPositions() throws AlpacaAPIRequestException { - AlpacaRequestBuilder urlBuilder = new AlpacaRequestBuilder(baseAPIURL, Endpoints.VERSION_2, + AlpacaRequestBuilder urlBuilder = new AlpacaRequestBuilder(apiURL, Endpoints.VERSION_2, Endpoints.POSITIONS); HttpResponse response = alpacaRequest.invokeGet(urlBuilder); @@ -1071,7 +1059,7 @@ public ArrayList getOpenPositions() throws AlpacaAPIRequestException { public Position getOpenPositionBySymbol(String symbol) throws AlpacaAPIRequestException { Preconditions.checkNotNull(symbol); - AlpacaRequestBuilder urlBuilder = new AlpacaRequestBuilder(baseAPIURL, Endpoints.VERSION_2, + AlpacaRequestBuilder urlBuilder = new AlpacaRequestBuilder(apiURL, Endpoints.VERSION_2, Endpoints.POSITIONS, symbol.trim()); @@ -1095,7 +1083,7 @@ public Position getOpenPositionBySymbol(String symbol) throws AlpacaAPIRequestEx * @see Positions */ public ArrayList closeAllPositions() throws AlpacaAPIRequestException { - AlpacaRequestBuilder urlBuilder = new AlpacaRequestBuilder(baseAPIURL, Endpoints.VERSION_2, + AlpacaRequestBuilder urlBuilder = new AlpacaRequestBuilder(apiURL, Endpoints.VERSION_2, Endpoints.POSITIONS); HttpResponse response = alpacaRequest.invokeDelete(urlBuilder); @@ -1122,7 +1110,7 @@ public ArrayList closeAllPositions() throws AlpacaAPIRequest public Order closePosition(String symbol) throws AlpacaAPIRequestException { Preconditions.checkNotNull(symbol); - AlpacaRequestBuilder urlBuilder = new AlpacaRequestBuilder(baseAPIURL, Endpoints.VERSION_2, + AlpacaRequestBuilder urlBuilder = new AlpacaRequestBuilder(apiURL, Endpoints.VERSION_2, Endpoints.POSITIONS, symbol); @@ -1147,7 +1135,7 @@ public Order closePosition(String symbol) throws AlpacaAPIRequestException { * @see Assets */ public ArrayList getAssets(AssetStatus assetStatus, String assetClass) throws AlpacaAPIRequestException { - AlpacaRequestBuilder urlBuilder = new AlpacaRequestBuilder(baseAPIURL, Endpoints.VERSION_2, + AlpacaRequestBuilder urlBuilder = new AlpacaRequestBuilder(apiURL, Endpoints.VERSION_2, Endpoints.ASSETS); if (assetStatus != null) { @@ -1182,7 +1170,7 @@ public ArrayList getAssets(AssetStatus assetStatus, String assetClass) th public Asset getAssetBySymbol(String symbolOrAssetID) throws AlpacaAPIRequestException { Preconditions.checkNotNull(symbolOrAssetID); - AlpacaRequestBuilder urlBuilder = new AlpacaRequestBuilder(baseAPIURL, Endpoints.VERSION_2, + AlpacaRequestBuilder urlBuilder = new AlpacaRequestBuilder(apiURL, Endpoints.VERSION_2, Endpoints.ASSETS, symbolOrAssetID.trim()); @@ -1204,7 +1192,7 @@ public Asset getAssetBySymbol(String symbolOrAssetID) throws AlpacaAPIRequestExc * @see Watchlists */ public ArrayList getWatchlists() throws AlpacaAPIRequestException { - AlpacaRequestBuilder urlBuilder = new AlpacaRequestBuilder(baseAPIURL, Endpoints.VERSION_2, + AlpacaRequestBuilder urlBuilder = new AlpacaRequestBuilder(apiURL, Endpoints.VERSION_2, Endpoints.WATCHLISTS); HttpResponse response = alpacaRequest.invokeGet(urlBuilder); @@ -1232,7 +1220,7 @@ public Watchlist createWatchlist(String name, String... symbols) throws AlpacaAP Preconditions.checkNotNull(name); Preconditions.checkState(name.length() <= 64); - AlpacaRequestBuilder urlBuilder = new AlpacaRequestBuilder(baseAPIURL, Endpoints.VERSION_2, + AlpacaRequestBuilder urlBuilder = new AlpacaRequestBuilder(apiURL, Endpoints.VERSION_2, Endpoints.WATCHLISTS); urlBuilder.appendJSONBodyProperty("name", name); @@ -1266,7 +1254,7 @@ public Watchlist createWatchlist(String name, String... symbols) throws AlpacaAP public Watchlist getWatchlist(String watchlistID) throws AlpacaAPIRequestException { Preconditions.checkNotNull(watchlistID); - AlpacaRequestBuilder urlBuilder = new AlpacaRequestBuilder(baseAPIURL, Endpoints.VERSION_2, + AlpacaRequestBuilder urlBuilder = new AlpacaRequestBuilder(apiURL, Endpoints.VERSION_2, Endpoints.WATCHLISTS, watchlistID); @@ -1295,7 +1283,7 @@ public Watchlist updateWatchlist(String watchlistID, String name, String... symb throws AlpacaAPIRequestException { Preconditions.checkNotNull(watchlistID); - AlpacaRequestBuilder urlBuilder = new AlpacaRequestBuilder(baseAPIURL, Endpoints.VERSION_2, + AlpacaRequestBuilder urlBuilder = new AlpacaRequestBuilder(apiURL, Endpoints.VERSION_2, Endpoints.WATCHLISTS, watchlistID); @@ -1334,7 +1322,7 @@ public Watchlist addWatchlistAsset(String watchlistID, String symbol) throws Alp Preconditions.checkNotNull(watchlistID); Preconditions.checkNotNull(symbol); - AlpacaRequestBuilder urlBuilder = new AlpacaRequestBuilder(baseAPIURL, Endpoints.VERSION_2, + AlpacaRequestBuilder urlBuilder = new AlpacaRequestBuilder(apiURL, Endpoints.VERSION_2, Endpoints.WATCHLISTS, watchlistID); @@ -1362,7 +1350,7 @@ public Watchlist addWatchlistAsset(String watchlistID, String symbol) throws Alp public boolean deleteWatchlist(String watchlistID) throws AlpacaAPIRequestException { Preconditions.checkNotNull(watchlistID); - AlpacaRequestBuilder urlBuilder = new AlpacaRequestBuilder(baseAPIURL, Endpoints.VERSION_2, + AlpacaRequestBuilder urlBuilder = new AlpacaRequestBuilder(apiURL, Endpoints.VERSION_2, Endpoints.WATCHLISTS, watchlistID); @@ -1390,7 +1378,7 @@ public Watchlist removeSymbolFromWatchlist(String watchlistID, String symbol) th Preconditions.checkNotNull(watchlistID); Preconditions.checkNotNull(symbol); - AlpacaRequestBuilder urlBuilder = new AlpacaRequestBuilder(baseAPIURL, Endpoints.VERSION_2, + AlpacaRequestBuilder urlBuilder = new AlpacaRequestBuilder(apiURL, Endpoints.VERSION_2, Endpoints.WATCHLISTS, watchlistID, symbol); @@ -1424,7 +1412,7 @@ public Watchlist removeSymbolFromWatchlist(String watchlistID, String symbol) th */ public PortfolioHistory getPortfolioHistory(Integer periodLength, PortfolioPeriodUnit periodUnit, PortfolioTimeFrame timeFrame, LocalDate dateEnd, Boolean extendedHours) throws AlpacaAPIRequestException { - AlpacaRequestBuilder urlBuilder = new AlpacaRequestBuilder(baseAPIURL, Endpoints.VERSION_2, + AlpacaRequestBuilder urlBuilder = new AlpacaRequestBuilder(apiURL, Endpoints.VERSION_2, Endpoints.ACCOUNT, Endpoints.PORTFOLIO, Endpoints.HISTORY); @@ -1478,7 +1466,7 @@ public List getCalendar() throws AlpacaAPIRequestException { * @see Calendar */ public List getCalendar(LocalDate start, LocalDate end) throws AlpacaAPIRequestException { - AlpacaRequestBuilder urlBuilder = new AlpacaRequestBuilder(baseAPIURL, Endpoints.VERSION_2, Endpoints.CALENDAR); + AlpacaRequestBuilder urlBuilder = new AlpacaRequestBuilder(apiURL, Endpoints.VERSION_2, Endpoints.CALENDAR); if (start != null) { urlBuilder.appendURLParameter(Parameters.START, FormatUtil.toDateString(start)); @@ -1508,7 +1496,7 @@ public List getCalendar(LocalDate start, LocalDate end) throws AlpacaA * @see Clock */ public Clock getClock() throws AlpacaAPIRequestException { - AlpacaRequestBuilder urlBuilder = new AlpacaRequestBuilder(baseAPIURL, Endpoints.VERSION_2, + AlpacaRequestBuilder urlBuilder = new AlpacaRequestBuilder(apiURL, Endpoints.VERSION_2, Endpoints.CLOCK); HttpResponse response = alpacaRequest.invokeGet(urlBuilder); @@ -1724,8 +1712,7 @@ public void removeMarketDataStreamListener(MarketDataStreamListener streamListen @Override public String toString() { return new StringJoiner(", ", this.getClass().getSimpleName() + "[", "]") - .add("baseAPIURL = " + baseAPIURL) - .add("baseDataUrl = " + baseDataUrl) + .add("apiURL = " + apiURL) .add("keyID = " + keyID) .toString(); } diff --git a/src/main/java/net/jacobpeterson/alpaca/AlpacaConstants.java b/src/main/java/net/jacobpeterson/alpaca/AlpacaConstants.java index 00c24185..740e48ae 100644 --- a/src/main/java/net/jacobpeterson/alpaca/AlpacaConstants.java +++ b/src/main/java/net/jacobpeterson/alpaca/AlpacaConstants.java @@ -5,6 +5,14 @@ */ public class AlpacaConstants { + /** + * Defines URLs of {@link AlpacaAPI} + */ + public static class URLs { + + public static final String DATA_V2 = "https://data.alpaca.markets/v2"; + } + /** * Defines endpoints of {@link AlpacaAPI}. */ diff --git a/src/main/java/net/jacobpeterson/alpaca/enums/BarsTimeFrame.java b/src/main/java/net/jacobpeterson/alpaca/enums/BarsTimeFrame.java deleted file mode 100644 index 1ac45f35..00000000 --- a/src/main/java/net/jacobpeterson/alpaca/enums/BarsTimeFrame.java +++ /dev/null @@ -1,38 +0,0 @@ -package net.jacobpeterson.alpaca.enums; - -import net.jacobpeterson.abstracts.enums.APIName; - -/** - * {@link BarsTimeFrame} defines enums for time frames of a {@link net.jacobpeterson.domain.alpaca.marketdata.Bar}. - */ -public enum BarsTimeFrame implements APIName { - - /** 1 minute {@link BarsTimeFrame}. */ - ONE_MIN("1Min"), - - /** 5 minute {@link BarsTimeFrame}. */ - FIVE_MINUTE("5Min"), - - /** 15 minute {@link BarsTimeFrame}. */ - FIFTEEN_MINUTE("15Min"), - - /** 1 day {@link BarsTimeFrame}. */ - ONE_DAY("1D"); - - /** The API name. */ - String apiName; - - /** - * Instantiates a new {@link BarsTimeFrame}. - * - * @param apiName the API name - */ - BarsTimeFrame(String apiName) { - this.apiName = apiName; - } - - @Override - public String getAPIName() { - return apiName; - } -} diff --git a/src/main/java/net/jacobpeterson/alpaca/enums/AccountStatus.java b/src/main/java/net/jacobpeterson/alpaca/enums/account/AccountStatus.java similarity index 93% rename from src/main/java/net/jacobpeterson/alpaca/enums/AccountStatus.java rename to src/main/java/net/jacobpeterson/alpaca/enums/account/AccountStatus.java index 25f53982..123a0225 100644 --- a/src/main/java/net/jacobpeterson/alpaca/enums/AccountStatus.java +++ b/src/main/java/net/jacobpeterson/alpaca/enums/account/AccountStatus.java @@ -1,4 +1,4 @@ -package net.jacobpeterson.alpaca.enums; +package net.jacobpeterson.alpaca.enums.account; import net.jacobpeterson.abstracts.enums.APIName; @@ -17,7 +17,7 @@ public enum AccountStatus implements APIName { /** * The account application submission failed for some reason. */ - SUBMISSION_FAILED(""), + SUBMISSION_FAILED("SUBMISSION_FAILED"), /** * The account application has been submitted for review. diff --git a/src/main/java/net/jacobpeterson/alpaca/enums/ActivityType.java b/src/main/java/net/jacobpeterson/alpaca/enums/activity/ActivityType.java similarity index 94% rename from src/main/java/net/jacobpeterson/alpaca/enums/ActivityType.java rename to src/main/java/net/jacobpeterson/alpaca/enums/activity/ActivityType.java index f87a8960..649e1546 100644 --- a/src/main/java/net/jacobpeterson/alpaca/enums/ActivityType.java +++ b/src/main/java/net/jacobpeterson/alpaca/enums/activity/ActivityType.java @@ -1,9 +1,9 @@ -package net.jacobpeterson.alpaca.enums; +package net.jacobpeterson.alpaca.enums.activity; import net.jacobpeterson.abstracts.enums.APIName; /** - * {@link AccountStatus} defines enums for various account activity types. + * {@link ActivityType} defines enums for various account activity types. * * @see * https://docs.alpaca.markets/api-documentation/api-v2/account-activities/ diff --git a/src/main/java/net/jacobpeterson/alpaca/enums/api/EndpointAPIType.java b/src/main/java/net/jacobpeterson/alpaca/enums/api/EndpointAPIType.java new file mode 100644 index 00000000..90f54907 --- /dev/null +++ b/src/main/java/net/jacobpeterson/alpaca/enums/api/EndpointAPIType.java @@ -0,0 +1,65 @@ +package net.jacobpeterson.alpaca.enums.api; + +/** + * {@link EndpointAPIType} defines the type of API endpoint to use with Alpaca. + */ +public enum EndpointAPIType { + + /** The live {@link EndpointAPIType}. */ + LIVE("https://api.alpaca.markets/", "live"), + + /** The paper {@link EndpointAPIType}. */ + PAPER("https://paper-api.alpaca.markets/", "paper"); + + /** The URL. */ + String url; + + /** The property name. */ + String propertyName; + + /** + * Instantiates a new {@link EndpointAPIType}. + * + * @param url the URL + * @param propertyName the property name + */ + EndpointAPIType(String url, String propertyName) { + this.url = url; + this.propertyName = propertyName; + } + + /** + * Gets an {@link EndpointAPIType} from its {@link EndpointAPIType#getPropertyName()}. + * + * @param propertyName the property name + * + * @return the {@link EndpointAPIType} + */ + public static EndpointAPIType fromPropertyName(String propertyName) { + for (EndpointAPIType endpointAPIType : values()) { + if (endpointAPIType.getPropertyName().equals(propertyName)) { + return endpointAPIType; + } + } + + return null; + } + + /** + * Gets URL. + * + * @return the URL + */ + public String getURL() { + return url; + } + + /** + * Gets property name. + * + * @return the property name + */ + public String getPropertyName() { + return propertyName; + } +} diff --git a/src/main/java/net/jacobpeterson/alpaca/enums/AssetStatus.java b/src/main/java/net/jacobpeterson/alpaca/enums/asset/AssetStatus.java similarity index 92% rename from src/main/java/net/jacobpeterson/alpaca/enums/AssetStatus.java rename to src/main/java/net/jacobpeterson/alpaca/enums/asset/AssetStatus.java index a1a03ef8..08447bfe 100644 --- a/src/main/java/net/jacobpeterson/alpaca/enums/AssetStatus.java +++ b/src/main/java/net/jacobpeterson/alpaca/enums/asset/AssetStatus.java @@ -1,4 +1,4 @@ -package net.jacobpeterson.alpaca.enums; +package net.jacobpeterson.alpaca.enums.asset; import net.jacobpeterson.abstracts.enums.APIName; diff --git a/src/main/java/net/jacobpeterson/alpaca/enums/OrderClass.java b/src/main/java/net/jacobpeterson/alpaca/enums/order/OrderClass.java similarity index 94% rename from src/main/java/net/jacobpeterson/alpaca/enums/OrderClass.java rename to src/main/java/net/jacobpeterson/alpaca/enums/order/OrderClass.java index 3c1d6530..c678ce2b 100644 --- a/src/main/java/net/jacobpeterson/alpaca/enums/OrderClass.java +++ b/src/main/java/net/jacobpeterson/alpaca/enums/order/OrderClass.java @@ -1,4 +1,4 @@ -package net.jacobpeterson.alpaca.enums; +package net.jacobpeterson.alpaca.enums.order; import net.jacobpeterson.abstracts.enums.APIName; diff --git a/src/main/java/net/jacobpeterson/alpaca/enums/OrderSide.java b/src/main/java/net/jacobpeterson/alpaca/enums/order/OrderSide.java similarity index 93% rename from src/main/java/net/jacobpeterson/alpaca/enums/OrderSide.java rename to src/main/java/net/jacobpeterson/alpaca/enums/order/OrderSide.java index 717e9d92..ce054d75 100644 --- a/src/main/java/net/jacobpeterson/alpaca/enums/OrderSide.java +++ b/src/main/java/net/jacobpeterson/alpaca/enums/order/OrderSide.java @@ -1,4 +1,4 @@ -package net.jacobpeterson.alpaca.enums; +package net.jacobpeterson.alpaca.enums.order; import net.jacobpeterson.abstracts.enums.APIName; diff --git a/src/main/java/net/jacobpeterson/alpaca/enums/OrderStatus.java b/src/main/java/net/jacobpeterson/alpaca/enums/order/OrderStatus.java similarity index 93% rename from src/main/java/net/jacobpeterson/alpaca/enums/OrderStatus.java rename to src/main/java/net/jacobpeterson/alpaca/enums/order/OrderStatus.java index 0f8b9b0e..464c078a 100644 --- a/src/main/java/net/jacobpeterson/alpaca/enums/OrderStatus.java +++ b/src/main/java/net/jacobpeterson/alpaca/enums/order/OrderStatus.java @@ -1,4 +1,4 @@ -package net.jacobpeterson.alpaca.enums; +package net.jacobpeterson.alpaca.enums.order; import net.jacobpeterson.abstracts.enums.APIName; diff --git a/src/main/java/net/jacobpeterson/alpaca/enums/OrderTimeInForce.java b/src/main/java/net/jacobpeterson/alpaca/enums/order/OrderTimeInForce.java similarity index 98% rename from src/main/java/net/jacobpeterson/alpaca/enums/OrderTimeInForce.java rename to src/main/java/net/jacobpeterson/alpaca/enums/order/OrderTimeInForce.java index 88723625..2fbcf5b9 100644 --- a/src/main/java/net/jacobpeterson/alpaca/enums/OrderTimeInForce.java +++ b/src/main/java/net/jacobpeterson/alpaca/enums/order/OrderTimeInForce.java @@ -1,4 +1,4 @@ -package net.jacobpeterson.alpaca.enums; +package net.jacobpeterson.alpaca.enums.order; import net.jacobpeterson.abstracts.enums.APIName; diff --git a/src/main/java/net/jacobpeterson/alpaca/enums/OrderType.java b/src/main/java/net/jacobpeterson/alpaca/enums/order/OrderType.java similarity index 98% rename from src/main/java/net/jacobpeterson/alpaca/enums/OrderType.java rename to src/main/java/net/jacobpeterson/alpaca/enums/order/OrderType.java index 0923a8d2..fbfd0676 100644 --- a/src/main/java/net/jacobpeterson/alpaca/enums/OrderType.java +++ b/src/main/java/net/jacobpeterson/alpaca/enums/order/OrderType.java @@ -1,4 +1,4 @@ -package net.jacobpeterson.alpaca.enums; +package net.jacobpeterson.alpaca.enums.order; import net.jacobpeterson.abstracts.enums.APIName; diff --git a/src/main/java/net/jacobpeterson/alpaca/enums/PortfolioPeriodUnit.java b/src/main/java/net/jacobpeterson/alpaca/enums/portfolio/PortfolioPeriodUnit.java similarity index 94% rename from src/main/java/net/jacobpeterson/alpaca/enums/PortfolioPeriodUnit.java rename to src/main/java/net/jacobpeterson/alpaca/enums/portfolio/PortfolioPeriodUnit.java index e14c1881..47abcc14 100644 --- a/src/main/java/net/jacobpeterson/alpaca/enums/PortfolioPeriodUnit.java +++ b/src/main/java/net/jacobpeterson/alpaca/enums/portfolio/PortfolioPeriodUnit.java @@ -1,4 +1,4 @@ -package net.jacobpeterson.alpaca.enums; +package net.jacobpeterson.alpaca.enums.portfolio; import net.jacobpeterson.abstracts.enums.APIName; diff --git a/src/main/java/net/jacobpeterson/alpaca/enums/PortfolioTimeFrame.java b/src/main/java/net/jacobpeterson/alpaca/enums/portfolio/PortfolioTimeFrame.java similarity index 94% rename from src/main/java/net/jacobpeterson/alpaca/enums/PortfolioTimeFrame.java rename to src/main/java/net/jacobpeterson/alpaca/enums/portfolio/PortfolioTimeFrame.java index 52c22fd4..d0dd86c3 100644 --- a/src/main/java/net/jacobpeterson/alpaca/enums/PortfolioTimeFrame.java +++ b/src/main/java/net/jacobpeterson/alpaca/enums/portfolio/PortfolioTimeFrame.java @@ -1,4 +1,4 @@ -package net.jacobpeterson.alpaca.enums; +package net.jacobpeterson.alpaca.enums.portfolio; import com.google.gson.annotations.SerializedName; import net.jacobpeterson.abstracts.enums.APIName; diff --git a/src/main/java/net/jacobpeterson/alpaca/properties/AlpacaProperties.java b/src/main/java/net/jacobpeterson/alpaca/properties/AlpacaProperties.java index 0a768fb0..961de525 100644 --- a/src/main/java/net/jacobpeterson/alpaca/properties/AlpacaProperties.java +++ b/src/main/java/net/jacobpeterson/alpaca/properties/AlpacaProperties.java @@ -1,6 +1,10 @@ package net.jacobpeterson.alpaca.properties; -import java.util.StringJoiner; +import net.jacobpeterson.util.reflection.ReflectionUtil; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.stream.Collectors; import static net.jacobpeterson.util.properties.PropertyUtil.getProperty; @@ -9,22 +13,26 @@ */ public class AlpacaProperties { + private static final Logger LOGGER = LoggerFactory.getLogger(AlpacaProperties.class); + private static final String ALPACA_PROPERTIES_FILE = "alpaca.properties"; + private static final String ALPACA_DEFAULT_PROPERTIES_FILE = "alpaca.default.properties"; private static final String KEY_ID_KEY = "key_id"; - public static final String KEY_ID_VALUE = getProperty(ALPACA_PROPERTIES_FILE, KEY_ID_KEY); + public static final String KEY_ID_VALUE = getProperty(ALPACA_PROPERTIES_FILE, ALPACA_DEFAULT_PROPERTIES_FILE, + KEY_ID_KEY); private static final String SECRET_KEY = "secret"; - public static final String SECRET_VALUE = getProperty(ALPACA_PROPERTIES_FILE, SECRET_KEY); - - private static final String BASE_API_URL_KEY = "base_api_url"; - public static final String BASE_API_URL_VALUE = getProperty(ALPACA_PROPERTIES_FILE, BASE_API_URL_KEY); + public static final String SECRET_VALUE = getProperty(ALPACA_PROPERTIES_FILE, ALPACA_DEFAULT_PROPERTIES_FILE, + SECRET_KEY); - private static final String BASE_DATA_URL_KEY = "base_data_url"; - public static final String BASE_DATA_URL_VALUE = getProperty(ALPACA_PROPERTIES_FILE, BASE_DATA_URL_KEY); + private static final String API_ENDPOINT_TYPE_KEY = "endpoint_api_type"; + public static final String API_ENDPOINT_TYPE_VALUE = getProperty(ALPACA_PROPERTIES_FILE, + ALPACA_DEFAULT_PROPERTIES_FILE, API_ENDPOINT_TYPE_KEY); private static final String USER_AGENT_KEY = "user_agent"; - public static final String USER_AGENT_VALUE = getProperty(ALPACA_PROPERTIES_FILE, USER_AGENT_KEY); + public static final String USER_AGENT_VALUE = getProperty(ALPACA_PROPERTIES_FILE, ALPACA_DEFAULT_PROPERTIES_FILE, + USER_AGENT_KEY); /** * Static {@link #toString()}. @@ -32,18 +40,16 @@ public class AlpacaProperties { * @return the static {@link #toString()} */ public static String staticToString() { - return new StringJoiner(", ", AlpacaProperties.class.getSimpleName() + "[", "]") - .add("ALPACA_PROPERTIES_FILE = " + ALPACA_PROPERTIES_FILE) - .add("BASE_API_URL_KEY = " + BASE_API_URL_KEY) - .add("BASE_API_URL_VALUE = " + BASE_API_URL_VALUE) - .add("BASE_DATA_URL_KEY = " + BASE_DATA_URL_KEY) - .add("BASE_DATA_URL_VALUE = " + BASE_DATA_URL_VALUE) - .add("KEY_ID_KEY = " + KEY_ID_KEY) - .add("KEY_ID_VALUE = " + KEY_ID_VALUE) - .add("SECRET_KEY = " + SECRET_KEY) - .add("SECRET_VALUE = " + SECRET_VALUE) - .add("USER_AGENT_KEY = " + USER_AGENT_KEY) - .add("USER_AGENT_VALUE = " + USER_AGENT_VALUE) - .toString(); + return ReflectionUtil.getAllDeclaredFields(AlpacaProperties.class).stream() + .filter(field -> !field.getName().contains("LOGGER")) + .map(field -> { + try { + return field.getName() + " = " + field.get(null); + } catch (IllegalAccessException exception) { + LOGGER.error("Could not get field!", exception); + } + + return field.getName() + " = ?"; + }).collect(Collectors.joining()); } } diff --git a/src/main/java/net/jacobpeterson/util/properties/PropertyUtil.java b/src/main/java/net/jacobpeterson/util/properties/PropertyUtil.java index d00e382c..721e9412 100644 --- a/src/main/java/net/jacobpeterson/util/properties/PropertyUtil.java +++ b/src/main/java/net/jacobpeterson/util/properties/PropertyUtil.java @@ -11,63 +11,81 @@ import java.util.Properties; /** - * The type {@link PropertyUtil}. + * {@link PropertyUtil} is a util class for all things {@link Properties}. */ public class PropertyUtil { - /** The logger. */ private static final Logger LOGGER = LoggerFactory.getLogger(PropertyUtil.class); - /** The property file. */ public static final Map CACHED_PROPERTIES = Collections.synchronizedMap(new HashMap<>()); /** - * Get property string. Will try to IO load the properties in the property file if not cached already. + * Gets a string property from a property file. Will try to IO load the properties in the property file if not + * cached already. * - * @param propertyFile the property file (.default properties files are also cached if they exist) + * @param propertyFile the property file * @param key the key * * @return the string */ public static String getProperty(String propertyFile, String key) { - return getProperty(propertyFile, key, null); + return getProperty(propertyFile, null, key, null); } /** - * Get property string. Will try to IO load the properties in the property file if not cached already. + * Gets a string property from a property file. Will try to IO load the properties in the property file if not + * cached already. If the desired property is not found in the "propertyFile", then the "defaultPropertyFile" is + * searched. * - * @param propertyFile the property file (.default properties files are also cached if they exist) - * @param key the key - * @param defaultValue the default value (if not .default properties were found, then this is returned) + * @param propertyFile the property file + * @param defaultPropertyFile the default property file + * @param key the key + * + * @return the string + */ + public static String getProperty(String propertyFile, String defaultPropertyFile, String key) { + return getProperty(propertyFile, defaultPropertyFile, key, null); + } + + /** + * Gets a string property from a property file. Will try to IO load the properties in the property file if not + * cached already. If the desired property is not found in the "propertyFile", then the "defaultPropertyFile" is + * searched, and if it's not there, then "defaultValue" is returned. + * + * @param propertyFile the property file + * @param defaultPropertyFile the default property file + * @param key the key + * @param defaultValue the default value (if the desired property wasn't found, then this is returned) * * @return the string */ - public static String getProperty(String propertyFile, String key, String defaultValue) { + public static String getProperty(String propertyFile, String defaultPropertyFile, String key, String defaultValue) { + Properties properties; + if (!CACHED_PROPERTIES.containsKey(propertyFile)) { - CACHED_PROPERTIES.put(propertyFile, loadPropertyFile(propertyFile)); - } - Properties properties = CACHED_PROPERTIES.get(propertyFile); - if (properties != null) { - return properties.getProperty(key, defaultValue); + properties = loadPropertyFile(propertyFile, defaultPropertyFile); + CACHED_PROPERTIES.put(propertyFile, properties); + } else { + properties = CACHED_PROPERTIES.get(propertyFile); } - return null; + + return properties == null ? defaultValue : properties.getProperty(key, defaultValue); } /** - * Load property file properties. + * Loads property file {@link Properties}. * - * @param propertyFile the property file name (.default properties files are also searched) + * @param propertyFile the property file name + * @param defaultPropertyFile the default property file name * * @return the properties */ - public synchronized static Properties loadPropertyFile(String propertyFile) { + public synchronized static Properties loadPropertyFile(String propertyFile, String defaultPropertyFile) { Properties properties = null; // Load the default property file if exists Properties defaultProperties = null; - String defaultPropertyFile = propertyFile + ".default"; - InputStream defaultPropertyStream = ClassLoader.getSystemClassLoader() - .getResourceAsStream(defaultPropertyFile); + InputStream defaultPropertyStream = ClassLoader.getSystemClassLoader().getResourceAsStream(defaultPropertyFile); if (defaultPropertyStream != null) { defaultProperties = new Properties(); diff --git a/src/main/java/net/jacobpeterson/util/reflection/ReflectionUtil.java b/src/main/java/net/jacobpeterson/util/reflection/ReflectionUtil.java new file mode 100644 index 00000000..566a2857 --- /dev/null +++ b/src/main/java/net/jacobpeterson/util/reflection/ReflectionUtil.java @@ -0,0 +1,68 @@ +package net.jacobpeterson.util.reflection; + +import java.lang.reflect.Field; +import java.util.ArrayList; +import java.util.HashMap; + +/** + * {@link ReflectionUtil} is a utility class for Java Reflection. + */ +public class ReflectionUtil { + + private static final HashMap, ArrayList> ALL_DECLARED_FIELDS_CACHE = new HashMap<>(); + + /** + * Gets all declared {@link Field}s in the inheritance tree. This method is very inefficient so caching of results + * has been implemented. + * + * @param clazz the {@link Class} + * + * @return all the declared {@link Field}s + */ + public static ArrayList getAllDeclaredFields(Class clazz) { + // Check cache + ArrayList cachedFields = ALL_DECLARED_FIELDS_CACHE.get(clazz); + if (cachedFields != null) { + return cachedFields; + } + + ArrayList declaredFields = new ArrayList<>(); + + // Loop while currentClass has a superclass and add declared fields + Class currentClass = clazz; + do { + for (Field currentDeclaredField : currentClass.getDeclaredFields()) { + // Check if the field name already exists in the list + if (declaredFields.stream() + .map(Field::getName) + .noneMatch(fieldName -> fieldName.equals(currentDeclaredField.getName()))) { + declaredFields.add(currentDeclaredField); + } + } + + currentClass = currentClass.getSuperclass(); + } while (currentClass != null); + + ALL_DECLARED_FIELDS_CACHE.put(clazz, declaredFields); + + return declaredFields; + } + + /** + * Gets a {@link Field}s in the inheritance tree. + * + * @param clazz the {@link Class} + * @param fieldName the {@link Field} name + * + * @return the declared {@link Field} (null if not found) + */ + public static Field getDeclaredField(Class clazz, String fieldName) { + for (Field field : getAllDeclaredFields(clazz)) { + if (field.getName().equals(fieldName)) { + return field; + } + } + + return null; + } +} diff --git a/src/main/resources/alpaca.default.properties b/src/main/resources/alpaca.default.properties new file mode 100644 index 00000000..1baf1967 --- /dev/null +++ b/src/main/resources/alpaca.default.properties @@ -0,0 +1,9 @@ +#Example: +#key_id = +#secret = +#endpoint_api_type = +#user_agent = JavaRuntimeEnvironment/ Unirest/1.4.9 + +#Defaults: +endpoint_api_type=paper +user_agent=JavaRuntimeEnvironment/ Unirest/1.4.9 diff --git a/src/main/resources/alpaca.properties.default b/src/main/resources/alpaca.properties.default deleted file mode 100644 index 15ecc4f5..00000000 --- a/src/main/resources/alpaca.properties.default +++ /dev/null @@ -1,11 +0,0 @@ -#Example: -#key_id = -#secret = -#base_api_url = https://paper-api.alpaca.markets -#base_data_url = https://data.alpaca.markets -#user_agent = JavaRuntimeEnvironment/ Unirest/1.4.9 - -#Defaults: -base_api_url = https://paper-api.alpaca.markets -base_data_url = https://data.alpaca.markets -user_agent = JavaRuntimeEnvironment/ Unirest/1.4.9 diff --git a/src/test/java/live/net/jacobpeterson/alpaca/AlpacaAPIEndpointTest.java b/src/test/java/live/net/jacobpeterson/alpaca/AlpacaAPIEndpointTest.java index 6e8ca15e..532dedaf 100644 --- a/src/test/java/live/net/jacobpeterson/alpaca/AlpacaAPIEndpointTest.java +++ b/src/test/java/live/net/jacobpeterson/alpaca/AlpacaAPIEndpointTest.java @@ -2,8 +2,8 @@ import net.jacobpeterson.abstracts.enums.SortDirection; import net.jacobpeterson.alpaca.AlpacaAPI; -import net.jacobpeterson.alpaca.enums.ActivityType; -import net.jacobpeterson.alpaca.enums.OrderStatus; +import net.jacobpeterson.alpaca.enums.activity.ActivityType; +import net.jacobpeterson.alpaca.enums.order.OrderStatus; import net.jacobpeterson.alpaca.rest.exception.AlpacaAPIRequestException; import net.jacobpeterson.domain.alpaca.account.Account; import net.jacobpeterson.domain.alpaca.accountactivities.AccountActivity; diff --git a/src/test/java/mock/net/jacobpeterson/alpaca/AlpacaAPITest.java b/src/test/java/mock/net/jacobpeterson/alpaca/AlpacaAPITest.java index f49abdc7..d6783d62 100644 --- a/src/test/java/mock/net/jacobpeterson/alpaca/AlpacaAPITest.java +++ b/src/test/java/mock/net/jacobpeterson/alpaca/AlpacaAPITest.java @@ -50,7 +50,7 @@ public void testAlpacaAPIConstructor_oAuthToken() { } /** - * Tests {@link AlpacaAPI#AlpacaAPI(String, String, String, String, String)}. + * Tests {@link AlpacaAPI#AlpacaAPI(String, String, String, String)}. */ @Test public void testAlpacaAPIConstructor_keyID_secret_oAuthToken_baseAPIURL_baseDataURL() { @@ -58,8 +58,7 @@ public void testAlpacaAPIConstructor_keyID_secret_oAuthToken_baseAPIURL_baseData String secret = "NOPQURSTUVWXYZ"; String oAuthToken = "ABCDEFGHIJKLMNOPQURSTUVWXYZ"; String baseAPIURL = "https://paper-api.foo.bar"; - String baseDataURL = "https://data.foo.bar"; - new AlpacaAPI(keyID, secret, oAuthToken, baseAPIURL, baseDataURL); + new AlpacaAPI(keyID, secret, oAuthToken, baseAPIURL); } } diff --git a/src/test/resources/alpaca.default.properties b/src/test/resources/alpaca.default.properties new file mode 100644 index 00000000..6d116dd0 --- /dev/null +++ b/src/test/resources/alpaca.default.properties @@ -0,0 +1,5 @@ +key_id=ABCDEFGHIJKLM +secret=NOPQURSTUVWXYZ +endpoint_api_type=paper +data_api_version=v2 +user_agent=JavaRuntimeEnvironment/ Unirest/1.4.9 diff --git a/src/test/resources/alpaca.properties.default b/src/test/resources/alpaca.properties.default deleted file mode 100644 index 4c0253e9..00000000 --- a/src/test/resources/alpaca.properties.default +++ /dev/null @@ -1,5 +0,0 @@ -key_id = ABCDEFGHIJKLM -secret = NOPQURSTUVWXYZ -base_api_url = https://paper-api.alpaca.markets -base_data_url = https://data.alpaca.markets -user_agent = Java/ Unirest/1.4.9 From cbcd541fb0884ac9c0d30fcc5b120d30a691e109 Mon Sep 17 00:00:00 2001 From: Jacob Peterson Date: Sat, 27 Feb 2021 13:47:05 -0700 Subject: [PATCH 090/271] Fix tests and compile errors --- .../net/jacobpeterson/alpaca/AlpacaAPI.java | 168 +----------------- .../alpaca/properties/AlpacaProperties.java | 24 +-- .../broker/client/AlpacaWebsocketClient.java | 30 ++-- .../client/MarketDataWebsocketClient.java | 26 ++- .../jacobpeterson/alpaca/AlpacaAPITest.java | 23 ++- 5 files changed, 61 insertions(+), 210 deletions(-) diff --git a/src/main/java/net/jacobpeterson/alpaca/AlpacaAPI.java b/src/main/java/net/jacobpeterson/alpaca/AlpacaAPI.java index 63c73dfa..85e26492 100644 --- a/src/main/java/net/jacobpeterson/alpaca/AlpacaAPI.java +++ b/src/main/java/net/jacobpeterson/alpaca/AlpacaAPI.java @@ -33,9 +33,6 @@ import net.jacobpeterson.domain.alpaca.asset.Asset; import net.jacobpeterson.domain.alpaca.calendar.Calendar; import net.jacobpeterson.domain.alpaca.clock.Clock; -import net.jacobpeterson.domain.alpaca.marketdata.Bar; -import net.jacobpeterson.domain.alpaca.marketdata.LastQuoteResponse; -import net.jacobpeterson.domain.alpaca.marketdata.LastTradeResponse; import net.jacobpeterson.domain.alpaca.order.CancelledOrder; import net.jacobpeterson.domain.alpaca.order.Order; import net.jacobpeterson.domain.alpaca.portfoliohistory.PortfolioHistory; @@ -52,7 +49,10 @@ import java.time.LocalDate; import java.time.ZonedDateTime; import java.time.format.DateTimeFormatter; -import java.util.*; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.StringJoiner; import java.util.stream.Collectors; /** @@ -79,7 +79,8 @@ public class AlpacaAPI { * associated defaults). */ public AlpacaAPI() { - this(AlpacaProperties.KEY_ID_VALUE, AlpacaProperties.SECRET_VALUE, null); + this(AlpacaProperties.KEY_ID_VALUE, AlpacaProperties.SECRET_VALUE, + EndpointAPIType.fromPropertyName(AlpacaProperties.API_ENDPOINT_TYPE_VALUE)); LOGGER.debug(AlpacaProperties.staticToString()); } @@ -133,8 +134,8 @@ public AlpacaAPI(String keyID, String secret, String oAuthToken, EndpointAPIType alpacaRequest = isOAuth ? new AlpacaRequest(oAuthToken) : new AlpacaRequest(keyID, secret); - alpacaWebSocketClient = isOAuth ? new AlpacaWebsocketClient(oAuthToken) : - new AlpacaWebsocketClient(keyID, secret); + alpacaWebSocketClient = isOAuth ? new AlpacaWebsocketClient(oAuthToken, endpointAPIType) : + new AlpacaWebsocketClient(keyID, secret, endpointAPIType); marketDataWebSocketClient = isOAuth ? new MarketDataWebsocketClient(oAuthToken) : new MarketDataWebsocketClient(keyID, secret); @@ -1508,159 +1509,6 @@ public Clock getClock() throws AlpacaAPIRequestException { return alpacaRequest.getResponseObject(response, Clock.class); } - /** - * Retrieves a list of bars for each requested symbol. It is guaranteed all bars are in ascending order by time. - *

- * Currently, no “incomplete” bars are returned. For example, a 1 minute bar for 09:30 will not be returned until - * 09:31. - * - * @param timeframe One of minute, 1Min, 5Min, 15Min, day or 1D. minute is an alias of 1Min. Similarly, day is of - * 1D. - * @param symbol One symbol name. - * @param limit The maximum number of bars to be returned for each symbol. It can be between 1 and 1000. Default - * is 100 if parameter is unspecified or 0. - * @param start Filter bars equal to or after this time. Cannot be used with after. - * @param end Filter bars equal to or before this time. Cannot be used with until. - * @param after Filter bars after this time. Cannot be used with start. - * @param until Filter bars before this time. Cannot be used with end. - * - * @return An object with a key for each symbol and the Bars object as the values. Note that it returns status 200 - * with an empty object if no requested symbol is found. - * - * @throws AlpacaAPIRequestException thrown for {@link AlpacaAPIRequestException}s - * @see Bars - */ - public Map> getBars(BarsTimeFrame timeframe, String symbol, Integer limit, ZonedDateTime start, - ZonedDateTime end, ZonedDateTime after, ZonedDateTime until) - throws AlpacaAPIRequestException { - return this.getBars(timeframe, new String[]{symbol}, limit, start, end, after, until); - } - - /** - * Retrieves a list of bars for each requested symbol. It is guaranteed all bars are in ascending order by time. - *

- * Currently, no “incomplete” bars are returned. For example, a 1 minute bar for 09:30 will not be returned until - * 09:31. - * - * @param timeframe One of minute, 1Min, 5Min, 15Min, day or 1D. minute is an alias of 1Min. Similarly, day is of - * 1D. - * @param symbols One or more (max 200) symbol names split by commas (“,”). - * @param limit The maximum number of bars to be returned for each symbol. It can be between 1 and 1000. Default - * is 100 if parameter is unspecified or 0. - * @param start Filter bars equal to or after this time. Cannot be used with after. - * @param end Filter bars equal to or before this time. Cannot be used with until. - * @param after Filter bars after this time. Cannot be used with start. - * @param until Filter bars before this time. Cannot be used with end. - * - * @return @return An object with a key for each symbol and the Bars object as the values. Note that it returns - * status 200 with an empty object if no requested symbol is found. - * - * @throws AlpacaAPIRequestException thrown for {@link AlpacaAPIRequestException}s - * @see Bars - */ - public Map> getBars(BarsTimeFrame timeframe, String[] symbols, Integer limit, ZonedDateTime start, - ZonedDateTime end, ZonedDateTime after, ZonedDateTime until) - throws AlpacaAPIRequestException { - AlpacaRequestBuilder urlBuilder = new AlpacaRequestBuilder(baseDataUrl, Endpoints.VERSION_1, - Endpoints.BARS); - - if (timeframe != null) { - urlBuilder.appendEndpoint(timeframe.getAPIName()); - } - - if (symbols != null) { - urlBuilder.appendURLParameter(Parameters.SYMBOLS, String.join(",", symbols)); - } - - if (limit != null) { - urlBuilder.appendURLParameter(Parameters.LIMIT, limit.toString()); - } - - if (start != null) { - urlBuilder.appendURLParameter(Parameters.START, - start.format(DateTimeFormatter.ISO_OFFSET_DATE_TIME)); - } - - if (end != null) { - urlBuilder.appendURLParameter(Parameters.END, - end.format(DateTimeFormatter.ISO_OFFSET_DATE_TIME)); - } - - if (after != null) { - urlBuilder.appendURLParameter(Parameters.AFTER, - after.format(DateTimeFormatter.ISO_OFFSET_DATE_TIME)); - } - - if (until != null) { - urlBuilder.appendURLParameter(Parameters.UNTIL, - until.format(DateTimeFormatter.ISO_OFFSET_DATE_TIME)); - } - - HttpResponse response = alpacaRequest.invokeGet(urlBuilder); - - if (response.getStatus() != 200) { - throw new AlpacaAPIRequestException(response); - } - - Type mapType = new TypeToken>>() {}.getType(); - - return alpacaRequest.getResponseObject(response, mapType); - } - - /** - * Retrieves the last trade for the requested symbol. - * - * @param symbol A stock ticker symbol to retrieve the last trade of - * - * @return the last trade response object - * - * @throws AlpacaAPIRequestException thrown for {@link AlpacaAPIRequestException}s - * @see Last Trade - */ - public LastTradeResponse getLastTrade(String symbol) throws AlpacaAPIRequestException { - Preconditions.checkNotNull(symbol); - - AlpacaRequestBuilder urlBuilder = new AlpacaRequestBuilder(baseDataUrl, Endpoints.VERSION_1, - Endpoints.LAST, - Endpoints.STOCKS, - symbol); - - HttpResponse response = alpacaRequest.invokeGet(urlBuilder); - - if (response.getStatus() != 200) { - throw new AlpacaAPIRequestException(response); - } - - return alpacaRequest.getResponseObject(response, LastTradeResponse.class); - } - - /** - * Retrieves the last quote for the requested symbol. - * - * @param symbol A stock ticker symbol to retrieve the last trade of - * - * @return the last quote response object - * - * @throws AlpacaAPIRequestException thrown for {@link AlpacaAPIRequestException}s - * @see Last Quote - */ - public LastQuoteResponse getLastQuote(String symbol) throws AlpacaAPIRequestException { - Preconditions.checkNotNull(symbol); - - AlpacaRequestBuilder urlBuilder = new AlpacaRequestBuilder(baseDataUrl, Endpoints.VERSION_1, - Endpoints.LAST_QUOTE, - Endpoints.STOCKS, - symbol); - - HttpResponse response = alpacaRequest.invokeGet(urlBuilder); - - if (response.getStatus() != 200) { - throw new AlpacaAPIRequestException(response); - } - - return alpacaRequest.getResponseObject(response, LastQuoteResponse.class); - } - /** * Adds the {@link AlpacaStreamListener}. Note that when the first {@link AlpacaStreamListener} is added, the * Websocket connection is created. diff --git a/src/main/java/net/jacobpeterson/alpaca/properties/AlpacaProperties.java b/src/main/java/net/jacobpeterson/alpaca/properties/AlpacaProperties.java index 961de525..c30f08de 100644 --- a/src/main/java/net/jacobpeterson/alpaca/properties/AlpacaProperties.java +++ b/src/main/java/net/jacobpeterson/alpaca/properties/AlpacaProperties.java @@ -40,16 +40,18 @@ public class AlpacaProperties { * @return the static {@link #toString()} */ public static String staticToString() { - return ReflectionUtil.getAllDeclaredFields(AlpacaProperties.class).stream() - .filter(field -> !field.getName().contains("LOGGER")) - .map(field -> { - try { - return field.getName() + " = " + field.get(null); - } catch (IllegalAccessException exception) { - LOGGER.error("Could not get field!", exception); - } - - return field.getName() + " = ?"; - }).collect(Collectors.joining()); + return AlpacaProperties.class.getName() + "[" + + ReflectionUtil.getAllDeclaredFields(AlpacaProperties.class).stream() + .filter(field -> !field.getName().contains("LOGGER")) + .map(field -> { + try { + return field.getName() + " = " + field.get(null); + } catch (IllegalAccessException exception) { + LOGGER.error("Could not get field!", exception); + } + + return field.getName() + " = ?"; + }).collect(Collectors.joining(", ")) + + "]"; } } diff --git a/src/main/java/net/jacobpeterson/alpaca/websocket/broker/client/AlpacaWebsocketClient.java b/src/main/java/net/jacobpeterson/alpaca/websocket/broker/client/AlpacaWebsocketClient.java index d579f0ce..e99b07d0 100644 --- a/src/main/java/net/jacobpeterson/alpaca/websocket/broker/client/AlpacaWebsocketClient.java +++ b/src/main/java/net/jacobpeterson/alpaca/websocket/broker/client/AlpacaWebsocketClient.java @@ -1,16 +1,13 @@ package net.jacobpeterson.alpaca.websocket.broker.client; import com.google.common.base.Preconditions; -import com.google.gson.JsonArray; -import com.google.gson.JsonElement; -import com.google.gson.JsonObject; -import com.google.gson.JsonPrimitive; -import com.google.gson.JsonSyntaxException; +import com.google.gson.*; import net.jacobpeterson.abstracts.websocket.client.WebsocketClient; import net.jacobpeterson.abstracts.websocket.exception.WebsocketException; import net.jacobpeterson.abstracts.websocket.listener.StreamListener; import net.jacobpeterson.abstracts.websocket.message.StreamMessage; import net.jacobpeterson.abstracts.websocket.message.StreamMessageType; +import net.jacobpeterson.alpaca.enums.api.EndpointAPIType; import net.jacobpeterson.alpaca.websocket.broker.listener.AlpacaStreamListener; import net.jacobpeterson.alpaca.websocket.broker.message.AlpacaStreamMessageType; import net.jacobpeterson.domain.alpaca.streaming.AlpacaStreamMessage; @@ -53,22 +50,22 @@ public class AlpacaWebsocketClient implements WebsocketClient { /** * Instantiates a new {@link AlpacaWebsocketClient}. * - * @param keyID the key ID - * @param secret the secret - * @param baseAPIURL the base API URL + * @param keyID the key ID + * @param secret the secret + * @param endpointAPIType the {@link EndpointAPIType} */ - public AlpacaWebsocketClient(String keyID, String secret, String baseAPIURL) { - this(keyID, secret, null, baseAPIURL); + public AlpacaWebsocketClient(String keyID, String secret, EndpointAPIType endpointAPIType) { + this(keyID, secret, null, endpointAPIType); } /** * Instantiates a new {@link AlpacaWebsocketClient}. * - * @param oAuthToken the OAuth token - * @param baseAPIURL the base API URL + * @param oAuthToken the OAuth token + * @param endpointAPIType the {@link EndpointAPIType} */ - public AlpacaWebsocketClient(String oAuthToken, String baseAPIURL) { - this(null, null, oAuthToken, baseAPIURL); + public AlpacaWebsocketClient(String oAuthToken, EndpointAPIType endpointAPIType) { + this(null, null, oAuthToken, endpointAPIType); } /** @@ -77,13 +74,12 @@ public AlpacaWebsocketClient(String oAuthToken, String baseAPIURL) { * @param keyID the key ID * @param secret the secret * @param oAuthToken the OAuth token - * @param baseAPIURL the base API URL */ - private AlpacaWebsocketClient(String keyID, String secret, String oAuthToken, String baseAPIURL) { + private AlpacaWebsocketClient(String keyID, String secret, String oAuthToken, EndpointAPIType endpointAPIType) { this.keyID = keyID; this.secret = secret; this.oAuthToken = oAuthToken; - this.streamAPIURL = baseAPIURL.replace("https", "wss") + "/stream"; + this.streamAPIURL = endpointAPIType.getURL().replace("https", "wss") + "/stream"; listeners = new ArrayList<>(); } diff --git a/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/client/MarketDataWebsocketClient.java b/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/client/MarketDataWebsocketClient.java index 838ef118..9a509b16 100644 --- a/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/client/MarketDataWebsocketClient.java +++ b/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/client/MarketDataWebsocketClient.java @@ -11,6 +11,7 @@ import net.jacobpeterson.abstracts.websocket.listener.StreamListener; import net.jacobpeterson.abstracts.websocket.message.StreamMessage; import net.jacobpeterson.abstracts.websocket.message.StreamMessageType; +import net.jacobpeterson.alpaca.AlpacaConstants; import net.jacobpeterson.alpaca.websocket.broker.client.AlpacaWebsocketClient; import net.jacobpeterson.alpaca.websocket.marketdata.listener.MarketDataStreamListener; import net.jacobpeterson.alpaca.websocket.marketdata.message.MarketDataStreamMessageType; @@ -68,37 +69,34 @@ public class MarketDataWebsocketClient implements WebsocketClient { /** * Instantiates a new {@link AlpacaWebsocketClient}. * - * @param keyID the key ID - * @param secret the secret - * @param baseDataUrl the base data URL + * @param keyID the key ID + * @param secret the secret */ - public MarketDataWebsocketClient(String keyID, String secret, String baseDataUrl) { - this(keyID, secret, null, baseDataUrl); + public MarketDataWebsocketClient(String keyID, String secret) { + this(keyID, secret, null); } /** * Instantiates a new {@link AlpacaWebsocketClient}. * * @param oAuthToken the OAuth token - * @param baseAPIURL the base API URL */ - public MarketDataWebsocketClient(String oAuthToken, String baseAPIURL) { - this(null, null, oAuthToken, baseAPIURL); + public MarketDataWebsocketClient(String oAuthToken) { + this(null, null, oAuthToken); } /** * Instantiates a new {@link AlpacaWebsocketClient}. * - * @param keyID the key ID - * @param secret the secret - * @param oAuthToken the OAuth token - * @param baseDataUrl the base data URL + * @param keyID the key ID + * @param secret the secret + * @param oAuthToken the OAuth token */ - private MarketDataWebsocketClient(String keyID, String secret, String oAuthToken, String baseDataUrl) { + private MarketDataWebsocketClient(String keyID, String secret, String oAuthToken) { this.keyID = keyID; this.secret = secret; this.oAuthToken = oAuthToken; - this.streamAPIURL = baseDataUrl.replace("https", "wss") + "/stream"; + this.streamAPIURL = AlpacaConstants.URLs.DATA_V2.replace("https", "wss") + "/stream"; listeners = new ArrayList<>(); } diff --git a/src/test/java/mock/net/jacobpeterson/alpaca/AlpacaAPITest.java b/src/test/java/mock/net/jacobpeterson/alpaca/AlpacaAPITest.java index d6783d62..602a1f2c 100644 --- a/src/test/java/mock/net/jacobpeterson/alpaca/AlpacaAPITest.java +++ b/src/test/java/mock/net/jacobpeterson/alpaca/AlpacaAPITest.java @@ -1,6 +1,7 @@ package mock.net.jacobpeterson.alpaca; import net.jacobpeterson.alpaca.AlpacaAPI; +import net.jacobpeterson.alpaca.enums.api.EndpointAPIType; import org.junit.jupiter.api.Test; /** @@ -8,6 +9,12 @@ */ public class AlpacaAPITest { + static { + // Log trace-level + System.setProperty("org.slf4j.simpleLogger.log.net.jacobpeterson", "trace"); + System.setProperty("org.slf4j.simpleLogger.log.mock.net.jacobpeterson", "trace"); + } + /** * Tests {@link AlpacaAPI#AlpacaAPI()}. */ @@ -28,15 +35,15 @@ public void testAlpacaAPIConstructor_keyID_secret() { } /** - * Tests {@link AlpacaAPI#AlpacaAPI(String, String, String)}. + * Tests {@link AlpacaAPI#AlpacaAPI(String, String, EndpointAPIType)}. */ @Test - public void testAlpacaAPIConstructor_keyID_secret_baseAPIURL() { + public void testAlpacaAPIConstructor_keyID_secret_endpointAPIType() { String keyID = "ABCDEFGHIJKLM"; String secret = "NOPQURSTUVWXYZ"; - String baseAPIURL = "https://paper-api.foo.bar"; - new AlpacaAPI(keyID, secret, baseAPIURL); + new AlpacaAPI(keyID, secret, EndpointAPIType.LIVE); + new AlpacaAPI(keyID, secret, EndpointAPIType.PAPER); } /** @@ -50,15 +57,15 @@ public void testAlpacaAPIConstructor_oAuthToken() { } /** - * Tests {@link AlpacaAPI#AlpacaAPI(String, String, String, String)}. + * Tests {@link AlpacaAPI#AlpacaAPI(String, String, String, EndpointAPIType)}. */ @Test - public void testAlpacaAPIConstructor_keyID_secret_oAuthToken_baseAPIURL_baseDataURL() { + public void testAlpacaAPIConstructor_keyID_secret_oAuthToken_endpointAPIType() { String keyID = "ABCDEFGHIJKLM"; String secret = "NOPQURSTUVWXYZ"; String oAuthToken = "ABCDEFGHIJKLMNOPQURSTUVWXYZ"; - String baseAPIURL = "https://paper-api.foo.bar"; - new AlpacaAPI(keyID, secret, oAuthToken, baseAPIURL); + new AlpacaAPI(keyID, secret, oAuthToken, EndpointAPIType.LIVE); + new AlpacaAPI(keyID, secret, oAuthToken, EndpointAPIType.PAPER); } } From 6c5be49d2d241ade1b622294c10626a271c3b66f Mon Sep 17 00:00:00 2001 From: Jacob Peterson Date: Sat, 27 Feb 2021 16:39:51 -0700 Subject: [PATCH 091/271] Implement historical market mata v2 --- .../market_data/historical/bar/bar.json | 30 +++ .../historical/bar/bars_response.json | 18 ++ .../market_data/historical/quote/quote.json | 38 ++++ .../historical/quote/quotes_response.json | 18 ++ .../market_data/historical/trade/trade.json | 34 ++++ .../historical/trade/trades_response.json | 18 ++ .../net/jacobpeterson/alpaca/AlpacaAPI.java | 185 ++++++++++++++++-- .../jacobpeterson/alpaca/AlpacaConstants.java | 9 +- .../enums/marketdata/BarsTimeFrame.java | 35 ++++ .../alpaca/rest/AlpacaRequestBuilder.java | 2 +- .../client/MarketDataWebsocketClient.java | 14 +- .../jacobpeterson/util/format/FormatUtil.java | 89 +-------- .../util/properties/PropertyUtil.java | 6 +- 13 files changed, 381 insertions(+), 115 deletions(-) create mode 100644 schema_json/alpaca/market_data/historical/bar/bar.json create mode 100644 schema_json/alpaca/market_data/historical/bar/bars_response.json create mode 100644 schema_json/alpaca/market_data/historical/quote/quote.json create mode 100644 schema_json/alpaca/market_data/historical/quote/quotes_response.json create mode 100644 schema_json/alpaca/market_data/historical/trade/trade.json create mode 100644 schema_json/alpaca/market_data/historical/trade/trades_response.json create mode 100644 src/main/java/net/jacobpeterson/alpaca/enums/marketdata/BarsTimeFrame.java diff --git a/schema_json/alpaca/market_data/historical/bar/bar.json b/schema_json/alpaca/market_data/historical/bar/bar.json new file mode 100644 index 00000000..d57dd011 --- /dev/null +++ b/schema_json/alpaca/market_data/historical/bar/bar.json @@ -0,0 +1,30 @@ +{ + "type": "object", + "title": "See https://alpaca.markets/docs/api-documentation/api-v2/market-data/alpaca-data-api-v2/historical/", + "properties": { + "t": { + "existingJavaType": "java.time.ZonedDateTime", + "title": "Timestamp with nanosecond precision." + }, + "o": { + "existingJavaType": "java.lang.Double", + "title": "Open price." + }, + "h": { + "existingJavaType": "java.lang.Double", + "title": "High price." + }, + "l": { + "existingJavaType": "java.lang.Double", + "title": "Low price." + }, + "c": { + "existingJavaType": "java.lang.Double", + "title": "Close price." + }, + "v": { + "existingJavaType": "java.lang.Double", + "title": "Volume." + } + } +} diff --git a/schema_json/alpaca/market_data/historical/bar/bars_response.json b/schema_json/alpaca/market_data/historical/bar/bars_response.json new file mode 100644 index 00000000..958fcd17 --- /dev/null +++ b/schema_json/alpaca/market_data/historical/bar/bars_response.json @@ -0,0 +1,18 @@ +{ + "type": "object", + "title": "See https://alpaca.markets/docs/api-documentation/api-v2/market-data/alpaca-data-api-v2/historical/", + "properties": { + "bars": { + "existingJavaType": "java.util.ArrayList", + "title": "Array of bars." + }, + "symbol": { + "existingJavaType": "java.lang.String", + "title": "Symbol that was queried." + }, + "next_page_token": { + "existingJavaType": "java.lang.String", + "title": "Token that can be used to query the next page." + } + } +} diff --git a/schema_json/alpaca/market_data/historical/quote/quote.json b/schema_json/alpaca/market_data/historical/quote/quote.json new file mode 100644 index 00000000..a9f4c124 --- /dev/null +++ b/schema_json/alpaca/market_data/historical/quote/quote.json @@ -0,0 +1,38 @@ +{ + "type": "object", + "title": "See https://alpaca.markets/docs/api-documentation/api-v2/market-data/alpaca-data-api-v2/historical/", + "properties": { + "t": { + "existingJavaType": "java.time.ZonedDateTime", + "title": "Timestamp with nanosecond precision." + }, + "ax": { + "existingJavaType": "java.lang.String", + "title": "Ask exchange." + }, + "ap": { + "existingJavaType": "java.lang.Double", + "title": "Ask price." + }, + "as": { + "existingJavaType": "java.lang.Integer", + "title": "Ask size." + }, + "bx": { + "existingJavaType": "java.lang.String", + "title": "Bid exchange." + }, + "bp": { + "existingJavaType": "java.lang.Double", + "title": "Bid price." + }, + "bs": { + "existingJavaType": "java.lang.Integer", + "title": "Bid size." + }, + "c": { + "existingJavaType": "java.util.ArrayList", + "title": "Quote conditions." + } + } +} diff --git a/schema_json/alpaca/market_data/historical/quote/quotes_response.json b/schema_json/alpaca/market_data/historical/quote/quotes_response.json new file mode 100644 index 00000000..55de6702 --- /dev/null +++ b/schema_json/alpaca/market_data/historical/quote/quotes_response.json @@ -0,0 +1,18 @@ +{ + "type": "object", + "title": "See https://alpaca.markets/docs/api-documentation/api-v2/market-data/alpaca-data-api-v2/historical/", + "properties": { + "quotes": { + "existingJavaType": "java.util.ArrayList", + "title": "Array of quotes." + }, + "symbol": { + "existingJavaType": "java.lang.String", + "title": "Symbol that was queried." + }, + "next_page_token": { + "existingJavaType": "java.lang.String", + "title": "Token that can be used to query the next page." + } + } +} diff --git a/schema_json/alpaca/market_data/historical/trade/trade.json b/schema_json/alpaca/market_data/historical/trade/trade.json new file mode 100644 index 00000000..ddb6c378 --- /dev/null +++ b/schema_json/alpaca/market_data/historical/trade/trade.json @@ -0,0 +1,34 @@ +{ + "type": "object", + "title": "See https://alpaca.markets/docs/api-documentation/api-v2/market-data/alpaca-data-api-v2/historical/", + "properties": { + "t": { + "existingJavaType": "java.time.ZonedDateTime", + "title": "Timestamp with nanosecond precision." + }, + "x": { + "existingJavaType": "java.lang.String", + "title": "Exchange where the trade happened." + }, + "p": { + "existingJavaType": "java.lang.Double", + "title": "Trade price." + }, + "s": { + "existingJavaType": "java.lang.Integer", + "title": "Trade size." + }, + "c": { + "existingJavaType": "java.util.ArrayList", + "title": "Trade conditions." + }, + "i": { + "existingJavaType": "java.lang.Long", + "title": "Trade ID." + }, + "z": { + "existingJavaType": "java.lang.String", + "title": "Tape." + } + } +} diff --git a/schema_json/alpaca/market_data/historical/trade/trades_response.json b/schema_json/alpaca/market_data/historical/trade/trades_response.json new file mode 100644 index 00000000..3225389c --- /dev/null +++ b/schema_json/alpaca/market_data/historical/trade/trades_response.json @@ -0,0 +1,18 @@ +{ + "type": "object", + "title": "See https://alpaca.markets/docs/api-documentation/api-v2/market-data/alpaca-data-api-v2/historical/", + "properties": { + "trades": { + "existingJavaType": "java.util.ArrayList", + "title": "Array of trades." + }, + "symbol": { + "existingJavaType": "java.lang.String", + "title": "Symbol that was queried." + }, + "next_page_token": { + "existingJavaType": "java.lang.String", + "title": "Token that can be used to query the next page." + } + } +} diff --git a/src/main/java/net/jacobpeterson/alpaca/AlpacaAPI.java b/src/main/java/net/jacobpeterson/alpaca/AlpacaAPI.java index 85e26492..9bfbce74 100644 --- a/src/main/java/net/jacobpeterson/alpaca/AlpacaAPI.java +++ b/src/main/java/net/jacobpeterson/alpaca/AlpacaAPI.java @@ -11,10 +11,16 @@ import net.jacobpeterson.abstracts.websocket.exception.WebsocketException; import net.jacobpeterson.alpaca.AlpacaConstants.Endpoints; import net.jacobpeterson.alpaca.AlpacaConstants.Parameters; +import net.jacobpeterson.alpaca.AlpacaConstants.URLs; import net.jacobpeterson.alpaca.enums.activity.ActivityType; import net.jacobpeterson.alpaca.enums.api.EndpointAPIType; import net.jacobpeterson.alpaca.enums.asset.AssetStatus; -import net.jacobpeterson.alpaca.enums.order.*; +import net.jacobpeterson.alpaca.enums.marketdata.BarsTimeFrame; +import net.jacobpeterson.alpaca.enums.order.OrderClass; +import net.jacobpeterson.alpaca.enums.order.OrderSide; +import net.jacobpeterson.alpaca.enums.order.OrderStatus; +import net.jacobpeterson.alpaca.enums.order.OrderTimeInForce; +import net.jacobpeterson.alpaca.enums.order.OrderType; import net.jacobpeterson.alpaca.enums.portfolio.PortfolioPeriodUnit; import net.jacobpeterson.alpaca.enums.portfolio.PortfolioTimeFrame; import net.jacobpeterson.alpaca.properties.AlpacaProperties; @@ -33,6 +39,12 @@ import net.jacobpeterson.domain.alpaca.asset.Asset; import net.jacobpeterson.domain.alpaca.calendar.Calendar; import net.jacobpeterson.domain.alpaca.clock.Clock; +import net.jacobpeterson.domain.alpaca.marketdata.historical.bar.Bar; +import net.jacobpeterson.domain.alpaca.marketdata.historical.bar.BarsResponse; +import net.jacobpeterson.domain.alpaca.marketdata.historical.quote.Quote; +import net.jacobpeterson.domain.alpaca.marketdata.historical.quote.QuotesResponse; +import net.jacobpeterson.domain.alpaca.marketdata.historical.trade.Trade; +import net.jacobpeterson.domain.alpaca.marketdata.historical.trade.TradesResponse; import net.jacobpeterson.domain.alpaca.order.CancelledOrder; import net.jacobpeterson.domain.alpaca.order.Order; import net.jacobpeterson.domain.alpaca.portfoliohistory.PortfolioHistory; @@ -426,22 +438,22 @@ public Order requestNewOrder(String symbol, Integer quantity, OrderSide side, Or if (limitPrice != null) { urlBuilder.appendJSONBodyProperty(Parameters.LIMIT_PRICE, - FormatUtil.toDecimalFormat(limitPrice)); + FormatUtil.toCurrencyFormat(limitPrice)); } if (stopPrice != null) { urlBuilder.appendJSONBodyProperty(Parameters.STOP_PRICE, - FormatUtil.toDecimalFormat(stopPrice)); + FormatUtil.toCurrencyFormat(stopPrice)); } if (trailPrice != null) { urlBuilder.appendJSONBodyProperty(Parameters.TRAIL_PRICE, - FormatUtil.toDecimalFormat(trailPrice)); + FormatUtil.toCurrencyFormat(trailPrice)); } if (trailPercent != null) { urlBuilder.appendJSONBodyProperty(Parameters.TRAIL_PERCENT, - FormatUtil.toDecimalFormat(trailPercent)); + FormatUtil.toCurrencyFormat(trailPercent)); } if (extendedHours != null) { @@ -947,17 +959,17 @@ public Order replaceOrder(String orderID, Integer quantity, OrderTimeInForce tim if (limitPrice != null) { urlBuilder.appendJSONBodyProperty(Parameters.LIMIT_PRICE, - FormatUtil.toDecimalFormat(limitPrice)); + FormatUtil.toCurrencyFormat(limitPrice)); } if (stopPrice != null) { urlBuilder.appendJSONBodyProperty(Parameters.STOP_PRICE, - FormatUtil.toDecimalFormat(stopPrice)); + FormatUtil.toCurrencyFormat(stopPrice)); } if (trail != null) { urlBuilder.appendJSONBodyProperty(Parameters.TRAIL, - FormatUtil.toDecimalFormat(trail)); + FormatUtil.toCurrencyFormat(trail)); } if (clientOrderId != null) { @@ -1427,7 +1439,7 @@ public PortfolioHistory getPortfolioHistory(Integer periodLength, PortfolioPerio } if (dateEnd != null) { - urlBuilder.appendURLParameter(Parameters.DATE_END, FormatUtil.toDateString(dateEnd)); + urlBuilder.appendURLParameter(Parameters.DATE_END, dateEnd.format(DateTimeFormatter.ISO_DATE)); } if (extendedHours != null) { @@ -1458,8 +1470,8 @@ public List getCalendar() throws AlpacaAPIRequestException { /** * Returns the market {@link Calendar}. * - * @param start The first date to retrieve data for (inclusive) - * @param end The last date to retrieve data for (inclusive) + * @param start the first date to retrieve data for (inclusive) + * @param end the last date to retrieve data for (inclusive) * * @return the {@link Calendar} {@link List} * @@ -1470,11 +1482,11 @@ public List getCalendar(LocalDate start, LocalDate end) throws AlpacaA AlpacaRequestBuilder urlBuilder = new AlpacaRequestBuilder(apiURL, Endpoints.VERSION_2, Endpoints.CALENDAR); if (start != null) { - urlBuilder.appendURLParameter(Parameters.START, FormatUtil.toDateString(start)); + urlBuilder.appendURLParameter(Parameters.START, start.format(DateTimeFormatter.ISO_DATE)); } if (end != null) { - urlBuilder.appendURLParameter(Parameters.END, FormatUtil.toDateString(end)); + urlBuilder.appendURLParameter(Parameters.END, end.format(DateTimeFormatter.ISO_DATE)); } HttpResponse response = alpacaRequest.invokeGet(urlBuilder); @@ -1509,6 +1521,153 @@ public Clock getClock() throws AlpacaAPIRequestException { return alpacaRequest.getResponseObject(response, Clock.class); } + /** + * Gets {@link Trade} historical data for the requested security. + * + * @param symbol the symbol to query for + * @param start filter data equal to or after this {@link ZonedDateTime}. Fractions of a second are not + * accepted + * @param end filter data equal to or before this {@link ZonedDateTime}. Fractions of a second are not + * accepted + * @param limit number of data points to return. Must be in range 1-10000, defaults to 1000 if null is given + * @param pageToken pagination token to continue from + * + * @return the {@link TradesResponse} + * + * @throws AlpacaAPIRequestException thrown for {@link AlpacaAPIRequestException}s + * @see + * Historical Market Data + */ + public TradesResponse getTrades(String symbol, ZonedDateTime start, ZonedDateTime end, Integer limit, + String pageToken) throws AlpacaAPIRequestException { + Preconditions.checkNotNull(symbol); + Preconditions.checkNotNull(start); + Preconditions.checkNotNull(end); + + AlpacaRequestBuilder urlBuilder = new AlpacaRequestBuilder(URLs.DATA, Endpoints.VERSION_2, + Endpoints.STOCKS, + symbol, + Endpoints.TRADES); + + urlBuilder.appendURLParameter(Parameters.START, start.format(DateTimeFormatter.ISO_OFFSET_DATE_TIME)); + urlBuilder.appendURLParameter(Parameters.END, end.format(DateTimeFormatter.ISO_OFFSET_DATE_TIME)); + + if (limit != null) { + urlBuilder.appendURLParameter(Parameters.LIMIT, limit.toString()); + } + + if (pageToken != null) { + urlBuilder.appendURLParameter(Parameters.PAGE_TOKEN, pageToken); + } + + HttpResponse response = alpacaRequest.invokeGet(urlBuilder); + + if (response.getStatus() != 200) { + throw new AlpacaAPIRequestException(response); + } + + return alpacaRequest.getResponseObject(response, TradesResponse.class); + } + + /** + * Gets {@link Quote} (NBBO or National Best Bid and Offer) historical data for the requested security. + * + * @param symbol the symbol to query for + * @param start filter data equal to or after this {@link ZonedDateTime}. Fractions of a second are not + * accepted + * @param end filter data equal to or before this {@link ZonedDateTime}. Fractions of a second are not + * accepted + * @param limit number of data points to return. Must be in range 1-10000, defaults to 1000 if null is given + * @param pageToken pagination token to continue from + * + * @return the {@link QuotesResponse} + * + * @throws AlpacaAPIRequestException thrown for {@link AlpacaAPIRequestException}s + * @see + * Historical Market Data + */ + public QuotesResponse getQuotes(String symbol, ZonedDateTime start, ZonedDateTime end, Integer limit, + String pageToken) throws AlpacaAPIRequestException { + Preconditions.checkNotNull(symbol); + Preconditions.checkNotNull(start); + Preconditions.checkNotNull(end); + + AlpacaRequestBuilder urlBuilder = new AlpacaRequestBuilder(URLs.DATA, Endpoints.VERSION_2, + Endpoints.STOCKS, + symbol, + Endpoints.QUOTES); + + urlBuilder.appendURLParameter(Parameters.START, start.format(DateTimeFormatter.ISO_OFFSET_DATE_TIME)); + urlBuilder.appendURLParameter(Parameters.END, end.format(DateTimeFormatter.ISO_OFFSET_DATE_TIME)); + + if (limit != null) { + urlBuilder.appendURLParameter(Parameters.LIMIT, limit.toString()); + } + + if (pageToken != null) { + urlBuilder.appendURLParameter(Parameters.PAGE_TOKEN, pageToken); + } + + HttpResponse response = alpacaRequest.invokeGet(urlBuilder); + + if (response.getStatus() != 200) { + throw new AlpacaAPIRequestException(response); + } + + return alpacaRequest.getResponseObject(response, QuotesResponse.class); + } + + /** + * Gets {@link Bar} aggregate historical data for the requested security. + * + * @param symbol the symbol to query for + * @param start filter data equal to or after this {@link ZonedDateTime}. Fractions of a second are not + * accepted + * @param end filter data equal to or before this {@link ZonedDateTime}. Fractions of a second are not + * accepted + * @param limit number of data points to return. Must be in range 1-10000, defaults to 1000 if null is given + * @param pageToken pagination token to continue from + * @param timeFrame the {@link BarsTimeFrame} for the aggregation + * + * @return the {@link BarsResponse} + * + * @throws AlpacaAPIRequestException thrown for {@link AlpacaAPIRequestException}s + * @see + * Historical Market Data + */ + public BarsResponse getBars(String symbol, ZonedDateTime start, ZonedDateTime end, Integer limit, + String pageToken, BarsTimeFrame timeFrame) throws AlpacaAPIRequestException { + Preconditions.checkNotNull(symbol); + Preconditions.checkNotNull(start); + Preconditions.checkNotNull(end); + Preconditions.checkNotNull(timeFrame); + + AlpacaRequestBuilder urlBuilder = new AlpacaRequestBuilder(URLs.DATA, Endpoints.VERSION_2, + Endpoints.STOCKS, + symbol, + Endpoints.BARS); + + urlBuilder.appendURLParameter(Parameters.START, start.format(DateTimeFormatter.ISO_OFFSET_DATE_TIME)); + urlBuilder.appendURLParameter(Parameters.END, end.format(DateTimeFormatter.ISO_OFFSET_DATE_TIME)); + urlBuilder.appendURLParameter(Parameters.TIMEFRAME, timeFrame.getAPIName()); + + if (limit != null) { + urlBuilder.appendURLParameter(Parameters.LIMIT, limit.toString()); + } + + if (pageToken != null) { + urlBuilder.appendURLParameter(Parameters.PAGE_TOKEN, pageToken); + } + + HttpResponse response = alpacaRequest.invokeGet(urlBuilder); + + if (response.getStatus() != 200) { + throw new AlpacaAPIRequestException(response); + } + + return alpacaRequest.getResponseObject(response, BarsResponse.class); + } + /** * Adds the {@link AlpacaStreamListener}. Note that when the first {@link AlpacaStreamListener} is added, the * Websocket connection is created. diff --git a/src/main/java/net/jacobpeterson/alpaca/AlpacaConstants.java b/src/main/java/net/jacobpeterson/alpaca/AlpacaConstants.java index 740e48ae..388df19d 100644 --- a/src/main/java/net/jacobpeterson/alpaca/AlpacaConstants.java +++ b/src/main/java/net/jacobpeterson/alpaca/AlpacaConstants.java @@ -10,7 +10,7 @@ public class AlpacaConstants { */ public static class URLs { - public static final String DATA_V2 = "https://data.alpaca.markets/v2"; + public static final String DATA = "https://data.alpaca.markets/"; } /** @@ -18,11 +18,7 @@ public static class URLs { */ public static class Endpoints { - public static final String VERSION_1 = "v1"; public static final String VERSION_2 = "v2"; - public static final String BARS = "bars"; - public static final String LAST = "last"; - public static final String LAST_QUOTE = "last_quote"; public static final String STOCKS = "stocks"; public static final String CLOCK = "clock"; public static final String CALENDAR = "calendar"; @@ -36,6 +32,9 @@ public static class Endpoints { public static final String WATCHLISTS = "watchlists"; public static final String PORTFOLIO = "portfolio"; public static final String HISTORY = "history"; + public static final String TRADES = "trades"; + public static final String QUOTES = "quotes"; + public static final String BARS = "bars"; } /** diff --git a/src/main/java/net/jacobpeterson/alpaca/enums/marketdata/BarsTimeFrame.java b/src/main/java/net/jacobpeterson/alpaca/enums/marketdata/BarsTimeFrame.java new file mode 100644 index 00000000..45c22641 --- /dev/null +++ b/src/main/java/net/jacobpeterson/alpaca/enums/marketdata/BarsTimeFrame.java @@ -0,0 +1,35 @@ +package net.jacobpeterson.alpaca.enums.marketdata; + +import net.jacobpeterson.abstracts.enums.APIName; + +/** + * The {@link BarsTimeFrame} enum defines time frame enums for the Bars endpoint. + */ +public enum BarsTimeFrame implements APIName { + + /** 1 Minute {@link BarsTimeFrame}. */ + MINUTE("1Min"), + + /** 1 Hour {@link BarsTimeFrame}. */ + HOUR("1Hour"), + + /** 1 Day {@link BarsTimeFrame}. */ + DAY("1Day"); + + /** The API name. */ + String apiName; + + /** + * Instantiates a new {@link BarsTimeFrame}. + * + * @param apiName the API name + */ + BarsTimeFrame(String apiName) { + this.apiName = apiName; + } + + @Override + public String getAPIName() { + return apiName; + } +} diff --git a/src/main/java/net/jacobpeterson/alpaca/rest/AlpacaRequestBuilder.java b/src/main/java/net/jacobpeterson/alpaca/rest/AlpacaRequestBuilder.java index 6d4b7a12..a8cb8017 100644 --- a/src/main/java/net/jacobpeterson/alpaca/rest/AlpacaRequestBuilder.java +++ b/src/main/java/net/jacobpeterson/alpaca/rest/AlpacaRequestBuilder.java @@ -12,7 +12,7 @@ public class AlpacaRequestBuilder extends AbstractRequestBuilder { /** * Instantiates a new {@link AlpacaRequestBuilder}. * - * @param baseUrl the base url + * @param baseUrl the base URL * @param apiVersion the API version * @param endpoints the endpoints */ diff --git a/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/client/MarketDataWebsocketClient.java b/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/client/MarketDataWebsocketClient.java index 9a509b16..1c1d0ef4 100644 --- a/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/client/MarketDataWebsocketClient.java +++ b/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/client/MarketDataWebsocketClient.java @@ -1,11 +1,7 @@ package net.jacobpeterson.alpaca.websocket.marketdata.client; import com.google.common.base.Preconditions; -import com.google.gson.JsonArray; -import com.google.gson.JsonElement; -import com.google.gson.JsonObject; -import com.google.gson.JsonParseException; -import com.google.gson.JsonPrimitive; +import com.google.gson.*; import net.jacobpeterson.abstracts.websocket.client.WebsocketClient; import net.jacobpeterson.abstracts.websocket.exception.WebsocketException; import net.jacobpeterson.abstracts.websocket.listener.StreamListener; @@ -31,11 +27,7 @@ import java.io.IOException; import java.net.URI; import java.net.URISyntaxException; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.HashSet; -import java.util.List; -import java.util.Set; +import java.util.*; import java.util.stream.Collectors; /** @@ -96,7 +88,7 @@ private MarketDataWebsocketClient(String keyID, String secret, String oAuthToken this.keyID = keyID; this.secret = secret; this.oAuthToken = oAuthToken; - this.streamAPIURL = AlpacaConstants.URLs.DATA_V2.replace("https", "wss") + "/stream"; + this.streamAPIURL = AlpacaConstants.URLs.DATA.replace("https", "wss") + "/stream"; listeners = new ArrayList<>(); } diff --git a/src/main/java/net/jacobpeterson/util/format/FormatUtil.java b/src/main/java/net/jacobpeterson/util/format/FormatUtil.java index 0febc589..e3e9d59b 100644 --- a/src/main/java/net/jacobpeterson/util/format/FormatUtil.java +++ b/src/main/java/net/jacobpeterson/util/format/FormatUtil.java @@ -2,100 +2,25 @@ import java.text.DecimalFormat; import java.text.NumberFormat; -import java.time.LocalDate; -import java.time.LocalDateTime; -import java.time.ZoneId; -import java.time.ZonedDateTime; -import java.time.format.DateTimeFormatter; /** - * The Class {@link FormatUtil}. + * {@link FormatUtil} is a utility class for various formatting. */ public class FormatUtil { - /** The constant PREV_UNIX_EPOCH_NANO_TIME. */ + private static final NumberFormat CURRENCY_FORMATTER = new DecimalFormat("#0.00"); private static final long PREV_UNIX_EPOCH_NANO_TIME = System.currentTimeMillis() * 1000000L; - /** The constant PREV_UNIX_EPOCH_NANO_TIME. */ private static final long PREV_UNIX_EPOCH_MICRO_TIME = PREV_UNIX_EPOCH_NANO_TIME / 1000L; - /** The date time formatter. */ - private static final DateTimeFormatter INPUT_DATE_TIME_FORMATTER = DateTimeFormatter.ofPattern( - "[yyyyMMdd][yyyy-MM-dd][yyyy-DDD]['T'[HHmmss][HHmm][HH:mm:ss][HH:mm][.SSSSSSSSS][.SSSSSSSS][.SSSSSSS][" + - ".SSSSSS][.SSSSS][.SSS][.SS][.S]][OOOO][O][z][XXXXX][XXXX]['['VV']']"); - /** The output date time formatter. */ - private static final DateTimeFormatter OUTPUT_DATE_TIME_FORMATTER = - DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss'Z'").withZone(ZoneId.of("UTC")); - /** The number formatter. */ - private static final NumberFormat NUMBER_FORMATTER = new DecimalFormat("#0.00"); /** - * To date time string. + * Formats an arbitrary number to a currency format. e.g. $123.45 * - * @param ldt the ldt + * @param numberToFormat the {@link Number} to format * - * @return the string + * @return the formatted string */ - public static String toDateTimeString(LocalDateTime ldt) { - ZonedDateTime ldtZoned = ldt.atZone(ZoneId.systemDefault()); - - ZonedDateTime localTimeZoned = ldtZoned.withZoneSameInstant(ZoneId.of("UTC")); - - return OUTPUT_DATE_TIME_FORMATTER.format(localTimeZoned); - } - - /** - * To decimal format. - * - * @param numberToFormat the number to format - * - * @return the string - */ - public static String toDecimalFormat(Number numberToFormat) { - return NUMBER_FORMATTER.format(numberToFormat); - } - - /** - * To date string. - * - * @param ld the ld - * - * @return the string - */ - public static String toDateString(LocalDate ld) { - return ld.format(DateTimeFormatter.ISO_DATE); - } - - /** - * From date time string. - * - * @param dateTimeString the date time string - * - * @return the local date time using the system time zone - */ - public static LocalDateTime fromDateTimeString(String dateTimeString) { - LocalDateTime ldt = LocalDateTime.parse(dateTimeString, INPUT_DATE_TIME_FORMATTER); - - ZonedDateTime ldtZoned; - - if (dateTimeString.endsWith("Z")) { - ldtZoned = ldt.atZone(ZoneId.of("UTC")); - } else { - ldtZoned = ldt.atZone(ZoneId.of("America/New_York")); - } - - ZonedDateTime localTimeZoned = ldtZoned.withZoneSameInstant(ZoneId.systemDefault()); - - return localTimeZoned.toLocalDateTime(); - } - - /** - * From date string. - * - * @param dateString the date string - * - * @return the local date - */ - public static LocalDate fromDateString(String dateString) { - return LocalDate.parse(dateString, DateTimeFormatter.ISO_DATE); + public static String toCurrencyFormat(Number numberToFormat) { + return CURRENCY_FORMATTER.format(numberToFormat); } /** diff --git a/src/main/java/net/jacobpeterson/util/properties/PropertyUtil.java b/src/main/java/net/jacobpeterson/util/properties/PropertyUtil.java index 721e9412..c80caa6f 100644 --- a/src/main/java/net/jacobpeterson/util/properties/PropertyUtil.java +++ b/src/main/java/net/jacobpeterson/util/properties/PropertyUtil.java @@ -34,7 +34,7 @@ public static String getProperty(String propertyFile, String key) { /** * Gets a string property from a property file. Will try to IO load the properties in the property file if not - * cached already. If the desired property is not found in the "propertyFile", then the "defaultPropertyFile" is + * cached already. If the desired property is not found in the 'propertyFile', then the 'defaultPropertyFile' is * searched. * * @param propertyFile the property file @@ -49,8 +49,8 @@ public static String getProperty(String propertyFile, String defaultPropertyFile /** * Gets a string property from a property file. Will try to IO load the properties in the property file if not - * cached already. If the desired property is not found in the "propertyFile", then the "defaultPropertyFile" is - * searched, and if it's not there, then "defaultValue" is returned. + * cached already. If the desired property is not found in the 'propertyFile', then the 'defaultPropertyFile' is + * searched, and if it's not there, then 'defaultValue' is returned. * * @param propertyFile the property file * @param defaultPropertyFile the default property file From 19ad54855a384f40ac6ee2fd8edff7a805c6da65 Mon Sep 17 00:00:00 2001 From: Jacob Peterson Date: Sat, 27 Feb 2021 18:26:55 -0700 Subject: [PATCH 092/271] Add realtime market data v2 schemas and refactor --- schema_json/alpaca/market_data/bar.json | 30 ---- .../alpaca/market_data/last_quote.json | 34 ----- .../market_data/last_quote_response.json | 18 --- .../alpaca/market_data/last_trade.json | 38 ----- .../market_data/last_trade_response.json | 18 --- .../market_data/realtime/bar/bar_message.json | 44 ++++++ .../realtime/market_data_message.json | 14 ++ .../realtime/quote/quote_message.json | 59 ++++++++ .../trade/trade_message.json | 30 ++-- .../market_data_stream_data_message.json | 18 --- .../market_data_stream_status_message.json | 13 -- .../aggregate/aggregate_minute_message.json | 53 ------- .../authorization/authorization_data.json | 14 -- .../authorization/authorization_message.json | 13 -- .../streaming/listening/listening_data.json | 14 -- .../listening/listening_message.json | 13 -- .../streaming/market_data_stream_message.json | 7 - .../streaming/quote/quote_message.json | 47 ------- .../AbstractWebsocketClientEndpoint.java | 7 +- .../net/jacobpeterson/alpaca/AlpacaAPI.java | 40 +++--- .../alpaca/enums/account/AccountStatus.java | 3 +- .../alpaca/enums/api/DataAPIType.java | 62 ++++++++ .../alpaca/enums/api/EndpointAPIType.java | 9 +- .../alpaca/enums/asset/AssetStatus.java | 3 +- .../enums/marketdata/BarsTimeFrame.java | 3 +- .../alpaca/enums/order/OrderClass.java | 3 +- .../alpaca/enums/order/OrderSide.java | 3 +- .../alpaca/enums/order/OrderStatus.java | 3 +- .../alpaca/enums/order/OrderTimeInForce.java | 3 +- .../alpaca/enums/order/OrderType.java | 3 +- .../enums/portfolio/PortfolioPeriodUnit.java | 3 +- .../alpaca/properties/AlpacaProperties.java | 10 +- .../message/AlpacaStreamMessageType.java | 6 +- .../client/MarketDataWebsocketClient.java | 133 +++++++++--------- .../MarketDataWebsocketClientEndpoint.java | 8 +- .../listener/MarketDataListener.java | 23 +++ .../listener/MarketDataListenerAdapter.java | 65 +++++++++ .../listener/MarketDataStreamListener.java | 23 --- .../MarketDataStreamListenerAdapter.java | 65 --------- .../message/MarketDataMessageType.java | 59 ++++++++ .../message/MarketDataStreamMessageType.java | 58 -------- .../util/gson/LocalDateAdapter.java | 8 +- .../util/gson/ZonedDateTimeAdapter.java | 8 +- src/main/resources/alpaca.default.properties | 2 + .../jacobpeterson/alpaca/AlpacaAPITest.java | 17 +-- src/test/resources/alpaca.default.properties | 2 +- 46 files changed, 478 insertions(+), 631 deletions(-) delete mode 100644 schema_json/alpaca/market_data/bar.json delete mode 100644 schema_json/alpaca/market_data/last_quote.json delete mode 100644 schema_json/alpaca/market_data/last_quote_response.json delete mode 100644 schema_json/alpaca/market_data/last_trade.json delete mode 100644 schema_json/alpaca/market_data/last_trade_response.json create mode 100644 schema_json/alpaca/market_data/realtime/bar/bar_message.json create mode 100644 schema_json/alpaca/market_data/realtime/market_data_message.json create mode 100644 schema_json/alpaca/market_data/realtime/quote/quote_message.json rename schema_json/alpaca/market_data/{streaming => realtime}/trade/trade_message.json (53%) delete mode 100644 schema_json/alpaca/market_data/streaming/abstracts/market_data_stream_data_message.json delete mode 100644 schema_json/alpaca/market_data/streaming/abstracts/market_data_stream_status_message.json delete mode 100644 schema_json/alpaca/market_data/streaming/aggregate/aggregate_minute_message.json delete mode 100644 schema_json/alpaca/market_data/streaming/authorization/authorization_data.json delete mode 100644 schema_json/alpaca/market_data/streaming/authorization/authorization_message.json delete mode 100644 schema_json/alpaca/market_data/streaming/listening/listening_data.json delete mode 100644 schema_json/alpaca/market_data/streaming/listening/listening_message.json delete mode 100644 schema_json/alpaca/market_data/streaming/market_data_stream_message.json delete mode 100644 schema_json/alpaca/market_data/streaming/quote/quote_message.json create mode 100644 src/main/java/net/jacobpeterson/alpaca/enums/api/DataAPIType.java create mode 100644 src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/listener/MarketDataListener.java create mode 100644 src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/listener/MarketDataListenerAdapter.java delete mode 100644 src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/listener/MarketDataStreamListener.java delete mode 100644 src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/listener/MarketDataStreamListenerAdapter.java create mode 100644 src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/message/MarketDataMessageType.java delete mode 100644 src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/message/MarketDataStreamMessageType.java diff --git a/schema_json/alpaca/market_data/bar.json b/schema_json/alpaca/market_data/bar.json deleted file mode 100644 index 36cfcf3d..00000000 --- a/schema_json/alpaca/market_data/bar.json +++ /dev/null @@ -1,30 +0,0 @@ -{ - "type": "object", - "title": "See https://alpaca.markets/docs/api-documentation/api-v2/market-data/bars/", - "properties": { - "t": { - "existingJavaType": "java.lang.Long", - "title": "The beginning time of this bar as a Unix epoch in seconds" - }, - "o": { - "existingJavaType": "java.lang.Double", - "title": "Open price" - }, - "h": { - "existingJavaType": "java.lang.Double", - "title": "High price" - }, - "l": { - "existingJavaType": "java.lang.Double", - "title": "Low price" - }, - "c": { - "existingJavaType": "java.lang.Double", - "title": "Close price" - }, - "v": { - "existingJavaType": "java.lang.Double", - "title": "Volume" - } - } -} diff --git a/schema_json/alpaca/market_data/last_quote.json b/schema_json/alpaca/market_data/last_quote.json deleted file mode 100644 index 4276182c..00000000 --- a/schema_json/alpaca/market_data/last_quote.json +++ /dev/null @@ -1,34 +0,0 @@ -{ - "type": "object", - "title": "See https://alpaca.markets/docs/api-documentation/api-v2/market-data/last-quote/", - "properties": { - "askprice": { - "existingJavaType": "java.lang.Double", - "title": "The current ask price" - }, - "asksize": { - "existingJavaType": "java.lang.Long", - "title": "The current ask size" - }, - "askexchange": { - "existingJavaType": "java.lang.Integer", - "title": "The exchange code of the ask quote" - }, - "bidprice": { - "existingJavaType": "java.lang.Double", - "title": "The current bid price" - }, - "bidsize": { - "existingJavaType": "java.lang.Long", - "title": "The current bid size" - }, - "bidexchange": { - "existingJavaType": "java.lang.Integer", - "title": "The exchange code of the bid quote" - }, - "timestamp": { - "existingJavaType": "java.lang.Long", - "title": "Epoch timestamp in nanoseconds" - } - } -} diff --git a/schema_json/alpaca/market_data/last_quote_response.json b/schema_json/alpaca/market_data/last_quote_response.json deleted file mode 100644 index 68de463e..00000000 --- a/schema_json/alpaca/market_data/last_quote_response.json +++ /dev/null @@ -1,18 +0,0 @@ -{ - "type": "object", - "title": "See https://alpaca.markets/docs/api-documentation/api-v2/market-data/last-quote/", - "properties": { - "status": { - "existingJavaType": "java.lang.String", - "title": "Status of this requests response" - }, - "symbol": { - "existingJavaType": "java.lang.String", - "title": "Symbol that was evaluated from the request" - }, - "last": { - "existingJavaType": "net.jacobpeterson.domain.alpaca.marketdata.LastQuote", - "title": "The Last Quote object" - } - } -} diff --git a/schema_json/alpaca/market_data/last_trade.json b/schema_json/alpaca/market_data/last_trade.json deleted file mode 100644 index 6f98da79..00000000 --- a/schema_json/alpaca/market_data/last_trade.json +++ /dev/null @@ -1,38 +0,0 @@ -{ - "type": "object", - "title": "See https://alpaca.markets/docs/api-documentation/api-v2/market-data/last-trade/", - "properties": { - "price": { - "existingJavaType": "java.lang.Double", - "title": "Price of the trade" - }, - "size": { - "existingJavaType": "java.lang.Long", - "title": "Last trade volume size" - }, - "exchange": { - "existingJavaType": "java.lang.Integer", - "title": "Exchange code where the last trade was made" - }, - "cond1": { - "existingJavaType": "java.lang.Integer", - "title": "Condition 1 of the trade" - }, - "cond2": { - "existingJavaType": "java.lang.Integer", - "title": "Condition 2 of the trade" - }, - "cond3": { - "existingJavaType": "java.lang.Integer", - "title": "Condition 3 of the trade" - }, - "cond4": { - "existingJavaType": "java.lang.Integer", - "title": "Condition 4 of the trade" - }, - "timestamp": { - "existingJavaType": "java.lang.Long", - "title": "Epoch timestamp in nanoseconds" - } - } -} diff --git a/schema_json/alpaca/market_data/last_trade_response.json b/schema_json/alpaca/market_data/last_trade_response.json deleted file mode 100644 index e11f6547..00000000 --- a/schema_json/alpaca/market_data/last_trade_response.json +++ /dev/null @@ -1,18 +0,0 @@ -{ - "type": "object", - "title": "See https://alpaca.markets/docs/api-documentation/api-v2/market-data/last-trade/", - "properties": { - "status": { - "existingJavaType": "java.lang.String", - "title": "Status of this requests response" - }, - "symbol": { - "existingJavaType": "java.lang.String", - "title": "Symbol that was evaluated from the request" - }, - "last": { - "existingJavaType": "net.jacobpeterson.domain.alpaca.marketdata.LastTrade", - "title": "The Last Trade object" - } - } -} diff --git a/schema_json/alpaca/market_data/realtime/bar/bar_message.json b/schema_json/alpaca/market_data/realtime/bar/bar_message.json new file mode 100644 index 00000000..f03983a0 --- /dev/null +++ b/schema_json/alpaca/market_data/realtime/bar/bar_message.json @@ -0,0 +1,44 @@ +{ + "type": "object", + "extends": { + "existingJavaType": "net.jacobpeterson.domain.alpaca.marketdata.realtime.MarketDataMessage" + }, + "title": "See https://alpaca.markets/docs/api-documentation/api-v2/market-data/streaming/", + "properties": { + "S": { + "existingJavaType": "java.lang.String", + "javaName": "symbol", + "title": "Symbol" + }, + "o": { + "existingJavaType": "java.lang.Double", + "javaName": "open", + "title": "Open price" + }, + "h": { + "existingJavaType": "java.lang.Double", + "javaName": "high", + "title": "High price" + }, + "l": { + "existingJavaType": "java.lang.Double", + "javaName": "low", + "title": "Low price" + }, + "c": { + "existingJavaType": "java.lang.Double", + "javaName": "close", + "title": "Close price" + }, + "v": { + "existingJavaType": "java.lang.Integer", + "javaName": "volume", + "title": "Volume" + }, + "t": { + "existingJavaType": "java.time.ZonedDateTime", + "javaName": "timestamp", + "title": "Timestamp" + } + } +} diff --git a/schema_json/alpaca/market_data/realtime/market_data_message.json b/schema_json/alpaca/market_data/realtime/market_data_message.json new file mode 100644 index 00000000..c63f6432 --- /dev/null +++ b/schema_json/alpaca/market_data/realtime/market_data_message.json @@ -0,0 +1,14 @@ +{ + "type": "object", + "javaInterfaces": [ + "net.jacobpeterson.abstracts.websocket.message.StreamMessage" + ], + "title": "See https://alpaca.markets/docs/api-documentation/api-v2/market-data/alpaca-data-api-v2/real-time/", + "properties": { + "T": { + "existingJavaType": "java.lang.String", + "javaName": "messageType", + "title": "The message type." + } + } +} diff --git a/schema_json/alpaca/market_data/realtime/quote/quote_message.json b/schema_json/alpaca/market_data/realtime/quote/quote_message.json new file mode 100644 index 00000000..c59aaab9 --- /dev/null +++ b/schema_json/alpaca/market_data/realtime/quote/quote_message.json @@ -0,0 +1,59 @@ +{ + "type": "object", + "extends": { + "existingJavaType": "net.jacobpeterson.domain.alpaca.marketdata.realtime.MarketDataMessage" + }, + "title": "See https://alpaca.markets/docs/api-documentation/api-v2/market-data/streaming/", + "properties": { + "S": { + "existingJavaType": "java.lang.String", + "javaName": "symbol", + "title": "Symbol" + }, + "ax": { + "existingJavaType": "java.lang.String", + "javaName": "askExchangeCode", + "title": "Ask exchange code" + }, + "ap": { + "existingJavaType": "java.lang.Double", + "javaName": "askPrice", + "title": "Ask price" + }, + "as": { + "existingJavaType": "java.lang.Integer", + "javaName": "askSize", + "title": "Ask size" + }, + "bx": { + "existingJavaType": "java.lang.String", + "javaName": "bidExchangeCode", + "title": "Bid exchange code" + }, + "bp": { + "existingJavaType": "java.lang.Double", + "javaName": "bidPrice", + "title": "Bid price" + }, + "bs": { + "existingJavaType": "java.lang.Integer", + "javaName": "bidSize", + "title": "Bid size" + }, + "t": { + "existingJavaType": "java.time.ZonedDateTime", + "javaName": "timestamp", + "title": "Timestamp with nanosecond precision" + }, + "c": { + "existingJavaType": "java.util.ArrayList", + "javaName": "conditions", + "title": "Quote conditions" + }, + "z": { + "existingJavaType": "java.lang.String", + "javaName": "tape", + "title": "Tape" + } + } +} diff --git a/schema_json/alpaca/market_data/streaming/trade/trade_message.json b/schema_json/alpaca/market_data/realtime/trade/trade_message.json similarity index 53% rename from schema_json/alpaca/market_data/streaming/trade/trade_message.json rename to schema_json/alpaca/market_data/realtime/trade/trade_message.json index 59a4735b..e61bee82 100644 --- a/schema_json/alpaca/market_data/streaming/trade/trade_message.json +++ b/schema_json/alpaca/market_data/realtime/trade/trade_message.json @@ -1,37 +1,49 @@ { "type": "object", "extends": { - "existingJavaType": "net.jacobpeterson.domain.alpaca.marketdata.streaming.abstracts.MarketDataStreamDataMessage" + "existingJavaType": "net.jacobpeterson.domain.alpaca.marketdata.realtime.MarketDataMessage" }, "title": "See https://alpaca.markets/docs/api-documentation/api-v2/market-data/streaming/", "properties": { + "S": { + "existingJavaType": "java.lang.String", + "javaName": "symbol", + "title": "Symbol" + }, "i": { - "existingJavaType": "java.lang.Long", + "existingJavaType": "java.lang.Integer", + "javaName": "tradeID", "title": "Trade ID" }, "x": { "existingJavaType": "java.lang.String", + "javaName": "exchange", "title": "Exchange code where the trade occurred" }, "p": { "existingJavaType": "java.lang.Double", + "javaName": "price", "title": "Trade price" }, "s": { "existingJavaType": "java.lang.Integer", - "title": "Trade size (shares)" + "javaName": "size", + "title": "Trade size" }, "t": { - "existingJavaType": "java.lang.Long", - "title": "Epoch timestamp in milliseconds" + "existingJavaType": "java.time.ZonedDateTime", + "javaName": "timestamp", + "title": "Timestamp with nanosecond precision" }, "c": { - "existingJavaType": "java.util.ArrayList", - "title": "Trade condition flags" + "existingJavaType": "java.util.ArrayList", + "javaName": "conditions", + "title": "Trade conditions" }, "z": { - "existingJavaType": "java.lang.Integer", - "title": "Tape ID ( 1=A 2=B 3=C)" + "existingJavaType": "java.lang.String", + "javaName": "tape", + "title": "Tape" } } } diff --git a/schema_json/alpaca/market_data/streaming/abstracts/market_data_stream_data_message.json b/schema_json/alpaca/market_data/streaming/abstracts/market_data_stream_data_message.json deleted file mode 100644 index 2e9632cd..00000000 --- a/schema_json/alpaca/market_data/streaming/abstracts/market_data_stream_data_message.json +++ /dev/null @@ -1,18 +0,0 @@ -{ - "type": "object", - "extends": { - "existingJavaType": "net.jacobpeterson.domain.alpaca.marketdata.streaming.MarketDataStreamMessage" - }, - "title": "See https://alpaca.markets/docs/api-documentation/api-v2/market-data/streaming/", - "properties": { - "ev": { - "existingJavaType": "net.jacobpeterson.alpaca.websocket.marketdata.message.MarketDataStreamMessageType", - "title": "The event type" - }, - "T": { - "existingJavaType": "java.lang.String", - "javaName": "ticker", - "title": "The ticker" - } - } -} diff --git a/schema_json/alpaca/market_data/streaming/abstracts/market_data_stream_status_message.json b/schema_json/alpaca/market_data/streaming/abstracts/market_data_stream_status_message.json deleted file mode 100644 index 3be488cd..00000000 --- a/schema_json/alpaca/market_data/streaming/abstracts/market_data_stream_status_message.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "type": "object", - "extends": { - "existingJavaType": "net.jacobpeterson.domain.alpaca.marketdata.streaming.MarketDataStreamMessage" - }, - "title": "See https://alpaca.markets/docs/api-documentation/api-v2/market-data/streaming/", - "properties": { - "stream": { - "existingJavaType": "net.jacobpeterson.alpaca.websocket.marketdata.message.MarketDataStreamMessageType", - "title": "The stream type" - } - } -} diff --git a/schema_json/alpaca/market_data/streaming/aggregate/aggregate_minute_message.json b/schema_json/alpaca/market_data/streaming/aggregate/aggregate_minute_message.json deleted file mode 100644 index 7180cfd6..00000000 --- a/schema_json/alpaca/market_data/streaming/aggregate/aggregate_minute_message.json +++ /dev/null @@ -1,53 +0,0 @@ -{ - "type": "object", - "extends": { - "existingJavaType": "net.jacobpeterson.domain.alpaca.marketdata.streaming.abstracts.MarketDataStreamDataMessage" - }, - "title": "See https://alpaca.markets/docs/api-documentation/api-v2/market-data/streaming/", - "properties": { - "v": { - "existingJavaType": "java.lang.Long", - "title": "Tick volume (shares)" - }, - "av": { - "existingJavaType": "java.lang.Long", - "title": "Accumulated volume (Today)" - }, - "op": { - "existingJavaType": "java.lang.Double", - "title": "Official open price of the bar" - }, - "vw": { - "existingJavaType": "java.lang.Double", - "title": "VWAP (Volume Weighted Average Price)" - }, - "o": { - "existingJavaType": "java.lang.Double", - "title": "Bar open price" - }, - "h": { - "existingJavaType": "java.lang.Double", - "title": "Bar high price" - }, - "l": { - "existingJavaType": "java.lang.Double", - "title": "Bar low price" - }, - "c": { - "existingJavaType": "java.lang.Double", - "title": "Bar close price" - }, - "a": { - "existingJavaType": "java.lang.Double", - "title": "Average price of the bar (Tick Average / VWAP Price)" - }, - "s": { - "existingJavaType": "java.lang.Long", - "title": "Epoch time at the beginning of the window in milliseconds" - }, - "e": { - "existingJavaType": "java.lang.Long", - "title": "Epoch time at the ending of the window in milliseconds" - } - } -} diff --git a/schema_json/alpaca/market_data/streaming/authorization/authorization_data.json b/schema_json/alpaca/market_data/streaming/authorization/authorization_data.json deleted file mode 100644 index 444fe929..00000000 --- a/schema_json/alpaca/market_data/streaming/authorization/authorization_data.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "type": "object", - "title": "See https://alpaca.markets/docs/api-documentation/api-v2/market-data/streaming/", - "properties": { - "status": { - "existingJavaType": "java.lang.String", - "title": "The status." - }, - "action": { - "existingJavaType": "java.lang.String", - "title": "The desired action." - } - } -} diff --git a/schema_json/alpaca/market_data/streaming/authorization/authorization_message.json b/schema_json/alpaca/market_data/streaming/authorization/authorization_message.json deleted file mode 100644 index 024be680..00000000 --- a/schema_json/alpaca/market_data/streaming/authorization/authorization_message.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "type": "object", - "title": "See https://alpaca.markets/docs/api-documentation/api-v2/market-data/streaming/", - "extends": { - "existingJavaType": "net.jacobpeterson.domain.alpaca.marketdata.streaming.abstracts.MarketDataStreamStatusMessage" - }, - "properties": { - "data": { - "existingJavaType": "net.jacobpeterson.domain.alpaca.marketdata.streaming.authorization.AuthorizationData", - "title": "The data" - } - } -} diff --git a/schema_json/alpaca/market_data/streaming/listening/listening_data.json b/schema_json/alpaca/market_data/streaming/listening/listening_data.json deleted file mode 100644 index a04bdd08..00000000 --- a/schema_json/alpaca/market_data/streaming/listening/listening_data.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "type": "object", - "title": "See https://alpaca.markets/docs/api-documentation/api-v2/market-data/streaming/", - "properties": { - "streams": { - "existingJavaType": "java.util.ArrayList", - "title": "The streams listened to" - }, - "error": { - "existingJavaType": "java.util.ArrayList", - "title": "The error, if one exists" - } - } -} diff --git a/schema_json/alpaca/market_data/streaming/listening/listening_message.json b/schema_json/alpaca/market_data/streaming/listening/listening_message.json deleted file mode 100644 index 40ca046c..00000000 --- a/schema_json/alpaca/market_data/streaming/listening/listening_message.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "type": "object", - "title": "See https://docs.alpaca.markets/api-documentation/api-v2/streaming/", - "extends": { - "existingJavaType": "net.jacobpeterson.domain.alpaca.marketdata.streaming.abstracts.MarketDataStreamStatusMessage" - }, - "properties": { - "data": { - "existingJavaType": "net.jacobpeterson.domain.alpaca.marketdata.streaming.listening.ListeningData", - "title": "The data" - } - } -} diff --git a/schema_json/alpaca/market_data/streaming/market_data_stream_message.json b/schema_json/alpaca/market_data/streaming/market_data_stream_message.json deleted file mode 100644 index b4461616..00000000 --- a/schema_json/alpaca/market_data/streaming/market_data_stream_message.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "type": "object", - "javaInterfaces": [ - "net.jacobpeterson.abstracts.websocket.message.StreamMessage" - ], - "title": "See https://alpaca.markets/docs/api-documentation/api-v2/market-data/streaming/" -} diff --git a/schema_json/alpaca/market_data/streaming/quote/quote_message.json b/schema_json/alpaca/market_data/streaming/quote/quote_message.json deleted file mode 100644 index a583ea19..00000000 --- a/schema_json/alpaca/market_data/streaming/quote/quote_message.json +++ /dev/null @@ -1,47 +0,0 @@ -{ - "type": "object", - "extends": { - "existingJavaType": "net.jacobpeterson.domain.alpaca.marketdata.streaming.abstracts.MarketDataStreamDataMessage" - }, - "title": "See https://alpaca.markets/docs/api-documentation/api-v2/market-data/streaming/", - "properties": { - "x": { - "existingJavaType": "java.lang.String", - "javaName": "bx", - "title": "Bix Exchange ID" - }, - "p": { - "existingJavaType": "java.lang.Double", - "javaName": "bp", - "title": "Bid Price" - }, - "s": { - "existingJavaType": "java.lang.Integer", - "javaName": "bs", - "title": "Bid Size" - }, - "X": { - "existingJavaType": "java.lang.String", - "javaName": "ax", - "title": "Ask Exchange ID" - }, - "P": { - "existingJavaType": "java.lang.Double", - "javaName": "ap", - "title": "Ask Price" - }, - "S": { - "existingJavaType": "java.lang.Integer", - "javaName": "as", - "title": "Ask Size" - }, - "c": { - "existingJavaType": "java.util.ArrayList", - "title": "Quote condition flags" - }, - "t": { - "existingJavaType": "java.lang.Long", - "title": "Quote Timestamp ( Unix MS )" - } - } -} diff --git a/src/main/java/net/jacobpeterson/abstracts/websocket/client/AbstractWebsocketClientEndpoint.java b/src/main/java/net/jacobpeterson/abstracts/websocket/client/AbstractWebsocketClientEndpoint.java index 9d5fa3c8..131ed87c 100644 --- a/src/main/java/net/jacobpeterson/abstracts/websocket/client/AbstractWebsocketClientEndpoint.java +++ b/src/main/java/net/jacobpeterson/abstracts/websocket/client/AbstractWebsocketClientEndpoint.java @@ -4,12 +4,8 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import javax.websocket.CloseReason; +import javax.websocket.*; import javax.websocket.CloseReason.CloseCodes; -import javax.websocket.ContainerProvider; -import javax.websocket.DeploymentException; -import javax.websocket.Session; -import javax.websocket.WebSocketContainer; import java.io.IOException; import java.net.URI; import java.util.concurrent.ExecutorService; @@ -155,6 +151,7 @@ protected void onError(Throwable throwable) { */ public void sendMessage(String message) { LOGGER.debug("sendMessage {}", message); + userSession.getAsyncRemote().sendText(message); } diff --git a/src/main/java/net/jacobpeterson/alpaca/AlpacaAPI.java b/src/main/java/net/jacobpeterson/alpaca/AlpacaAPI.java index 9bfbce74..dca2e4ef 100644 --- a/src/main/java/net/jacobpeterson/alpaca/AlpacaAPI.java +++ b/src/main/java/net/jacobpeterson/alpaca/AlpacaAPI.java @@ -13,6 +13,7 @@ import net.jacobpeterson.alpaca.AlpacaConstants.Parameters; import net.jacobpeterson.alpaca.AlpacaConstants.URLs; import net.jacobpeterson.alpaca.enums.activity.ActivityType; +import net.jacobpeterson.alpaca.enums.api.DataAPIType; import net.jacobpeterson.alpaca.enums.api.EndpointAPIType; import net.jacobpeterson.alpaca.enums.asset.AssetStatus; import net.jacobpeterson.alpaca.enums.marketdata.BarsTimeFrame; @@ -30,7 +31,7 @@ import net.jacobpeterson.alpaca.websocket.broker.client.AlpacaWebsocketClient; import net.jacobpeterson.alpaca.websocket.broker.listener.AlpacaStreamListener; import net.jacobpeterson.alpaca.websocket.marketdata.client.MarketDataWebsocketClient; -import net.jacobpeterson.alpaca.websocket.marketdata.listener.MarketDataStreamListener; +import net.jacobpeterson.alpaca.websocket.marketdata.listener.MarketDataListener; import net.jacobpeterson.domain.alpaca.account.Account; import net.jacobpeterson.domain.alpaca.accountactivities.AccountActivity; import net.jacobpeterson.domain.alpaca.accountactivities.NonTradeActivity; @@ -92,7 +93,8 @@ public class AlpacaAPI { */ public AlpacaAPI() { this(AlpacaProperties.KEY_ID_VALUE, AlpacaProperties.SECRET_VALUE, - EndpointAPIType.fromPropertyName(AlpacaProperties.API_ENDPOINT_TYPE_VALUE)); + EndpointAPIType.fromPropertyName(AlpacaProperties.ENDPOINT_API_TYPE_VALUE), + DataAPIType.fromPropertyName(AlpacaProperties.DATA_API_TYPE_VALUE)); LOGGER.debug(AlpacaProperties.staticToString()); } @@ -104,7 +106,8 @@ public AlpacaAPI() { * @param secret the secret */ public AlpacaAPI(String keyID, String secret) { - this(keyID, secret, null, EndpointAPIType.fromPropertyName(AlpacaProperties.API_ENDPOINT_TYPE_VALUE)); + this(keyID, secret, null, EndpointAPIType.fromPropertyName(AlpacaProperties.ENDPOINT_API_TYPE_VALUE), + DataAPIType.fromPropertyName(AlpacaProperties.DATA_API_TYPE_VALUE)); } /** @@ -113,9 +116,10 @@ public AlpacaAPI(String keyID, String secret) { * @param keyID the key ID * @param secret the secret * @param endpointAPIType the {@link EndpointAPIType} + * @param dataAPIType the {@link DataAPIType} */ - public AlpacaAPI(String keyID, String secret, EndpointAPIType endpointAPIType) { - this(keyID, secret, null, endpointAPIType); + public AlpacaAPI(String keyID, String secret, EndpointAPIType endpointAPIType, DataAPIType dataAPIType) { + this(keyID, secret, null, endpointAPIType, dataAPIType); } /** @@ -124,7 +128,8 @@ public AlpacaAPI(String keyID, String secret, EndpointAPIType endpointAPIType) { * @param oAuthToken the OAuth token */ public AlpacaAPI(String oAuthToken) { - this(null, null, oAuthToken, EndpointAPIType.fromPropertyName(AlpacaProperties.API_ENDPOINT_TYPE_VALUE)); + this(null, null, oAuthToken, EndpointAPIType.fromPropertyName(AlpacaProperties.ENDPOINT_API_TYPE_VALUE), + DataAPIType.fromPropertyName(AlpacaProperties.DATA_API_TYPE_VALUE)); } /** @@ -134,10 +139,13 @@ public AlpacaAPI(String oAuthToken) { * @param secret the secret * @param oAuthToken the OAuth token * @param endpointAPIType the {@link EndpointAPIType} + * @param dataAPIType the {@link DataAPIType} */ - public AlpacaAPI(String keyID, String secret, String oAuthToken, EndpointAPIType endpointAPIType) { + public AlpacaAPI(String keyID, String secret, String oAuthToken, EndpointAPIType endpointAPIType, + DataAPIType dataAPIType) { Preconditions.checkState((keyID != null && secret != null) || oAuthToken != null); Preconditions.checkNotNull(endpointAPIType); + Preconditions.checkNotNull(dataAPIType); this.keyID = keyID; this.apiURL = endpointAPIType.getURL(); @@ -148,8 +156,8 @@ public AlpacaAPI(String keyID, String secret, String oAuthToken, EndpointAPIType new AlpacaRequest(keyID, secret); alpacaWebSocketClient = isOAuth ? new AlpacaWebsocketClient(oAuthToken, endpointAPIType) : new AlpacaWebsocketClient(keyID, secret, endpointAPIType); - marketDataWebSocketClient = isOAuth ? new MarketDataWebsocketClient(oAuthToken) : - new MarketDataWebsocketClient(keyID, secret); + marketDataWebSocketClient = isOAuth ? new MarketDataWebsocketClient(oAuthToken, dataAPIType) : + new MarketDataWebsocketClient(keyID, secret, dataAPIType); LOGGER.debug("{}", this); } @@ -1693,26 +1701,26 @@ public void removeAlpacaStreamListener(AlpacaStreamListener streamListener) thro } /** - * Adds the {@link MarketDataStreamListener}. Note that when the first {@link AlpacaStreamListener} is added, the + * Adds the {@link MarketDataListener}. Note that when the first {@link AlpacaStreamListener} is added, the * Websocket connection is created. * - * @param streamListener the {@link MarketDataStreamListener} + * @param streamListener the {@link MarketDataListener} * * @throws WebsocketException thrown for {@link WebsocketException}s */ - public void addMarketDataStreamListener(MarketDataStreamListener streamListener) throws WebsocketException { + public void addMarketDataStreamListener(MarketDataListener streamListener) throws WebsocketException { marketDataWebSocketClient.addListener(streamListener); } /** - * Removes the {@link MarketDataStreamListener}. Note that when the last {@link MarketDataStreamListener} is - * removed, the Websocket connection is closed. + * Removes the {@link MarketDataListener}. Note that when the last {@link MarketDataListener} is removed, the + * Websocket connection is closed. * - * @param streamListener the {@link MarketDataStreamListener} + * @param streamListener the {@link MarketDataListener} * * @throws WebsocketException thrown for {@link WebsocketException}s */ - public void removeMarketDataStreamListener(MarketDataStreamListener streamListener) throws WebsocketException { + public void removeMarketDataStreamListener(MarketDataListener streamListener) throws WebsocketException { marketDataWebSocketClient.removeListener(streamListener); } diff --git a/src/main/java/net/jacobpeterson/alpaca/enums/account/AccountStatus.java b/src/main/java/net/jacobpeterson/alpaca/enums/account/AccountStatus.java index 123a0225..d4995559 100644 --- a/src/main/java/net/jacobpeterson/alpaca/enums/account/AccountStatus.java +++ b/src/main/java/net/jacobpeterson/alpaca/enums/account/AccountStatus.java @@ -44,8 +44,7 @@ public enum AccountStatus implements APIName { */ REJECTED("REJECTED"); - /** The API name. */ - String apiName; + private final String apiName; /** * Instantiates a new {@link AccountStatus}. diff --git a/src/main/java/net/jacobpeterson/alpaca/enums/api/DataAPIType.java b/src/main/java/net/jacobpeterson/alpaca/enums/api/DataAPIType.java new file mode 100644 index 00000000..721af52b --- /dev/null +++ b/src/main/java/net/jacobpeterson/alpaca/enums/api/DataAPIType.java @@ -0,0 +1,62 @@ +package net.jacobpeterson.alpaca.enums.api; + +/** + * {@link DataAPIType} defines the types of Data APIs to use with Alpaca. + */ +public enum DataAPIType { + + /** The IEX {@link DataAPIType}. */ + IEX("wss://stream.data.alpaca.markets/v2/iex", "iex"), + + /** The SIP {@link DataAPIType}. */ + SIP("wss://stream.data.alpaca.markets/v2/sip", "sip"); + + private final String url; + private final String propertyName; + + /** + * Instantiates a new {@link DataAPIType}. + * + * @param url the URL + * @param propertyName the property name + */ + DataAPIType(String url, String propertyName) { + this.url = url; + this.propertyName = propertyName; + } + + /** + * Gets a {@link DataAPIType} from its {@link DataAPIType#getPropertyName()}. + * + * @param propertyName the property name + * + * @return the {@link DataAPIType} + */ + public static DataAPIType fromPropertyName(String propertyName) { + for (DataAPIType dataAPIType : values()) { + if (dataAPIType.getPropertyName().equals(propertyName)) { + return dataAPIType; + } + } + + return null; + } + + /** + * Gets URL. + * + * @return the URL + */ + public String getURL() { + return url; + } + + /** + * Gets property name. + * + * @return the property name + */ + public String getPropertyName() { + return propertyName; + } +} diff --git a/src/main/java/net/jacobpeterson/alpaca/enums/api/EndpointAPIType.java b/src/main/java/net/jacobpeterson/alpaca/enums/api/EndpointAPIType.java index 90f54907..3c9f1d49 100644 --- a/src/main/java/net/jacobpeterson/alpaca/enums/api/EndpointAPIType.java +++ b/src/main/java/net/jacobpeterson/alpaca/enums/api/EndpointAPIType.java @@ -1,7 +1,7 @@ package net.jacobpeterson.alpaca.enums.api; /** - * {@link EndpointAPIType} defines the type of API endpoint to use with Alpaca. + * {@link EndpointAPIType} defines the types of API endpoint to use with Alpaca. */ public enum EndpointAPIType { @@ -11,11 +11,8 @@ public enum EndpointAPIType { /** The paper {@link EndpointAPIType}. */ PAPER("https://paper-api.alpaca.markets/", "paper"); - /** The URL. */ - String url; - - /** The property name. */ - String propertyName; + private final String url; + private final String propertyName; /** * Instantiates a new {@link EndpointAPIType}. diff --git a/src/main/java/net/jacobpeterson/alpaca/enums/asset/AssetStatus.java b/src/main/java/net/jacobpeterson/alpaca/enums/asset/AssetStatus.java index 08447bfe..261cf8a3 100644 --- a/src/main/java/net/jacobpeterson/alpaca/enums/asset/AssetStatus.java +++ b/src/main/java/net/jacobpeterson/alpaca/enums/asset/AssetStatus.java @@ -13,8 +13,7 @@ public enum AssetStatus implements APIName { /** The inactive {@link AssetStatus}. */ INACTIVE("inactive"); - /** The API name. */ - String apiName; + private final String apiName; /** * Instantiates a new {@link AssetStatus}. diff --git a/src/main/java/net/jacobpeterson/alpaca/enums/marketdata/BarsTimeFrame.java b/src/main/java/net/jacobpeterson/alpaca/enums/marketdata/BarsTimeFrame.java index 45c22641..fd56cdf8 100644 --- a/src/main/java/net/jacobpeterson/alpaca/enums/marketdata/BarsTimeFrame.java +++ b/src/main/java/net/jacobpeterson/alpaca/enums/marketdata/BarsTimeFrame.java @@ -16,8 +16,7 @@ public enum BarsTimeFrame implements APIName { /** 1 Day {@link BarsTimeFrame}. */ DAY("1Day"); - /** The API name. */ - String apiName; + private final String apiName; /** * Instantiates a new {@link BarsTimeFrame}. diff --git a/src/main/java/net/jacobpeterson/alpaca/enums/order/OrderClass.java b/src/main/java/net/jacobpeterson/alpaca/enums/order/OrderClass.java index c678ce2b..8850e28e 100644 --- a/src/main/java/net/jacobpeterson/alpaca/enums/order/OrderClass.java +++ b/src/main/java/net/jacobpeterson/alpaca/enums/order/OrderClass.java @@ -19,8 +19,7 @@ public enum OrderClass implements APIName { /** One-Triggers-Other {@link OrderClass}. */ OTO("oto"); - /** The API name. */ - String apiName; + private final String apiName; /** * Instantiates a new {@link OrderClass}. diff --git a/src/main/java/net/jacobpeterson/alpaca/enums/order/OrderSide.java b/src/main/java/net/jacobpeterson/alpaca/enums/order/OrderSide.java index ce054d75..aa28c6a1 100644 --- a/src/main/java/net/jacobpeterson/alpaca/enums/order/OrderSide.java +++ b/src/main/java/net/jacobpeterson/alpaca/enums/order/OrderSide.java @@ -13,8 +13,7 @@ public enum OrderSide implements APIName { /** The sell {@link OrderSide}. */ SELL("sell"); - /** The API name. */ - String apiName; + private final String apiName; /** * Instantiates a new {@link OrderSide}. diff --git a/src/main/java/net/jacobpeterson/alpaca/enums/order/OrderStatus.java b/src/main/java/net/jacobpeterson/alpaca/enums/order/OrderStatus.java index 464c078a..76476041 100644 --- a/src/main/java/net/jacobpeterson/alpaca/enums/order/OrderStatus.java +++ b/src/main/java/net/jacobpeterson/alpaca/enums/order/OrderStatus.java @@ -16,8 +16,7 @@ public enum OrderStatus implements APIName { /** All {@link OrderStatus}es. */ ALL("all"); - /** The API name. */ - String apiName; + private final String apiName; /** * Instantiates a new {@link OrderStatus}. diff --git a/src/main/java/net/jacobpeterson/alpaca/enums/order/OrderTimeInForce.java b/src/main/java/net/jacobpeterson/alpaca/enums/order/OrderTimeInForce.java index 2fbcf5b9..39e5752f 100644 --- a/src/main/java/net/jacobpeterson/alpaca/enums/order/OrderTimeInForce.java +++ b/src/main/java/net/jacobpeterson/alpaca/enums/order/OrderTimeInForce.java @@ -54,8 +54,7 @@ public enum OrderTimeInForce implements APIName { */ FOK("fok"); - /** The API name. */ - String apiName; + private final String apiName; /** * Instantiates a new {@link OrderTimeInForce}. diff --git a/src/main/java/net/jacobpeterson/alpaca/enums/order/OrderType.java b/src/main/java/net/jacobpeterson/alpaca/enums/order/OrderType.java index fbfd0676..32b2316d 100644 --- a/src/main/java/net/jacobpeterson/alpaca/enums/order/OrderType.java +++ b/src/main/java/net/jacobpeterson/alpaca/enums/order/OrderType.java @@ -49,8 +49,7 @@ public enum OrderType implements APIName { */ TRAILING_STOP("trailing_stop"); - /** The API name. */ - String apiName; + private final String apiName; /** * Instantiates a new {@link OrderType}. diff --git a/src/main/java/net/jacobpeterson/alpaca/enums/portfolio/PortfolioPeriodUnit.java b/src/main/java/net/jacobpeterson/alpaca/enums/portfolio/PortfolioPeriodUnit.java index 47abcc14..fb301047 100644 --- a/src/main/java/net/jacobpeterson/alpaca/enums/portfolio/PortfolioPeriodUnit.java +++ b/src/main/java/net/jacobpeterson/alpaca/enums/portfolio/PortfolioPeriodUnit.java @@ -21,8 +21,7 @@ public enum PortfolioPeriodUnit implements APIName { /** Year {@link PortfolioPeriodUnit}. */ YEAR("A"); - /** The API name. */ - String apiName; + private final String apiName; /** * Instantiates a new {@link PortfolioPeriodUnit}. diff --git a/src/main/java/net/jacobpeterson/alpaca/properties/AlpacaProperties.java b/src/main/java/net/jacobpeterson/alpaca/properties/AlpacaProperties.java index c30f08de..8b42cb3a 100644 --- a/src/main/java/net/jacobpeterson/alpaca/properties/AlpacaProperties.java +++ b/src/main/java/net/jacobpeterson/alpaca/properties/AlpacaProperties.java @@ -26,9 +26,13 @@ public class AlpacaProperties { public static final String SECRET_VALUE = getProperty(ALPACA_PROPERTIES_FILE, ALPACA_DEFAULT_PROPERTIES_FILE, SECRET_KEY); - private static final String API_ENDPOINT_TYPE_KEY = "endpoint_api_type"; - public static final String API_ENDPOINT_TYPE_VALUE = getProperty(ALPACA_PROPERTIES_FILE, - ALPACA_DEFAULT_PROPERTIES_FILE, API_ENDPOINT_TYPE_KEY); + private static final String ENDPOINT_API_TYPE_KEY = "endpoint_api_type"; + public static final String ENDPOINT_API_TYPE_VALUE = getProperty(ALPACA_PROPERTIES_FILE, + ALPACA_DEFAULT_PROPERTIES_FILE, ENDPOINT_API_TYPE_KEY); + + private static final String DATA_API_TYPE_KEY = "data_api_type"; + public static final String DATA_API_TYPE_VALUE = getProperty(ALPACA_PROPERTIES_FILE, + ALPACA_DEFAULT_PROPERTIES_FILE, DATA_API_TYPE_KEY); private static final String USER_AGENT_KEY = "user_agent"; public static final String USER_AGENT_VALUE = getProperty(ALPACA_PROPERTIES_FILE, ALPACA_DEFAULT_PROPERTIES_FILE, diff --git a/src/main/java/net/jacobpeterson/alpaca/websocket/broker/message/AlpacaStreamMessageType.java b/src/main/java/net/jacobpeterson/alpaca/websocket/broker/message/AlpacaStreamMessageType.java index 0a68e4d9..4a51eae2 100644 --- a/src/main/java/net/jacobpeterson/alpaca/websocket/broker/message/AlpacaStreamMessageType.java +++ b/src/main/java/net/jacobpeterson/alpaca/websocket/broker/message/AlpacaStreamMessageType.java @@ -32,7 +32,7 @@ public enum AlpacaStreamMessageType implements StreamMessageType, APIName { /** * Instantiates a new {@link AlpacaStreamMessageType}. * - * @param isAPISubscribable true if API subscribable + * @param isAPISubscribable true if API is subscribable */ AlpacaStreamMessageType(boolean isAPISubscribable) { this.isAPISubscribable = isAPISubscribable; @@ -44,9 +44,9 @@ public String getAPIName() { } /** - * Returns true if API subscribable. + * Returns true if API is subscribable. * - * @return true if API subscribable + * @return true if API is subscribable */ public boolean isAPISubscribable() { return isAPISubscribable; diff --git a/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/client/MarketDataWebsocketClient.java b/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/client/MarketDataWebsocketClient.java index 1c1d0ef4..c99b2816 100644 --- a/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/client/MarketDataWebsocketClient.java +++ b/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/client/MarketDataWebsocketClient.java @@ -1,24 +1,20 @@ package net.jacobpeterson.alpaca.websocket.marketdata.client; import com.google.common.base.Preconditions; -import com.google.gson.*; +import com.google.gson.JsonArray; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import com.google.gson.JsonParseException; +import com.google.gson.JsonPrimitive; import net.jacobpeterson.abstracts.websocket.client.WebsocketClient; import net.jacobpeterson.abstracts.websocket.exception.WebsocketException; import net.jacobpeterson.abstracts.websocket.listener.StreamListener; import net.jacobpeterson.abstracts.websocket.message.StreamMessage; import net.jacobpeterson.abstracts.websocket.message.StreamMessageType; -import net.jacobpeterson.alpaca.AlpacaConstants; +import net.jacobpeterson.alpaca.enums.api.DataAPIType; import net.jacobpeterson.alpaca.websocket.broker.client.AlpacaWebsocketClient; -import net.jacobpeterson.alpaca.websocket.marketdata.listener.MarketDataStreamListener; -import net.jacobpeterson.alpaca.websocket.marketdata.message.MarketDataStreamMessageType; -import net.jacobpeterson.domain.alpaca.marketdata.streaming.MarketDataStreamMessage; -import net.jacobpeterson.domain.alpaca.marketdata.streaming.abstracts.MarketDataStreamDataMessage; -import net.jacobpeterson.domain.alpaca.marketdata.streaming.abstracts.MarketDataStreamStatusMessage; -import net.jacobpeterson.domain.alpaca.marketdata.streaming.aggregate.AggregateMinuteMessage; -import net.jacobpeterson.domain.alpaca.marketdata.streaming.authorization.AuthorizationMessage; -import net.jacobpeterson.domain.alpaca.marketdata.streaming.listening.ListeningMessage; -import net.jacobpeterson.domain.alpaca.marketdata.streaming.quote.QuoteMessage; -import net.jacobpeterson.domain.alpaca.marketdata.streaming.trade.TradeMessage; +import net.jacobpeterson.alpaca.websocket.marketdata.listener.MarketDataListener; +import net.jacobpeterson.alpaca.websocket.marketdata.message.MarketDataMessageType; import net.jacobpeterson.util.gson.GsonUtil; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -27,7 +23,11 @@ import java.io.IOException; import java.net.URI; import java.net.URISyntaxException; -import java.util.*; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Set; import java.util.stream.Collectors; /** @@ -43,17 +43,17 @@ public class MarketDataWebsocketClient implements WebsocketClient { private static final String DATA_KEY = "data"; private static final String ALL_TICKERS = "*"; private static final String TRADES_STREAM_PREFIX = - GsonUtil.GSON.toJson(MarketDataStreamMessageType.TRADES).replace("\"", "") + "."; + GsonUtil.GSON.toJson(MarketDataMessageType.TRADES).replace("\"", "") + "."; private static final String QUOTES_STREAM_PREFIX = - GsonUtil.GSON.toJson(MarketDataStreamMessageType.QUOTES).replace("\"", "") + "."; + GsonUtil.GSON.toJson(MarketDataMessageType.QUOTES).replace("\"", "") + "."; private static final String AGGREGATE_MINUTE_STREAM_PREFIX = - GsonUtil.GSON.toJson(MarketDataStreamMessageType.AGGREGATE_MINUTE).replace("\"", "") + "."; + GsonUtil.GSON.toJson(MarketDataMessageType.AGGREGATE_MINUTE).replace("\"", "") + "."; private final String keyID; private final String secret; private final String oAuthToken; - private final String streamAPIURL; - private final List listeners; + private final String websocketAPIURL; + private final List listeners; private MarketDataWebsocketClientEndpoint marketDataWebsocketClientEndpoint; private boolean authenticated; @@ -61,58 +61,61 @@ public class MarketDataWebsocketClient implements WebsocketClient { /** * Instantiates a new {@link AlpacaWebsocketClient}. * - * @param keyID the key ID - * @param secret the secret + * @param keyID the key ID + * @param secret the secret + * @param dataAPIType the {@link DataAPIType} */ - public MarketDataWebsocketClient(String keyID, String secret) { - this(keyID, secret, null); + public MarketDataWebsocketClient(String keyID, String secret, DataAPIType dataAPIType) { + this(keyID, secret, null, dataAPIType); } /** * Instantiates a new {@link AlpacaWebsocketClient}. * - * @param oAuthToken the OAuth token + * @param oAuthToken the OAuth token + * @param dataAPIType the {@link DataAPIType} */ - public MarketDataWebsocketClient(String oAuthToken) { - this(null, null, oAuthToken); + public MarketDataWebsocketClient(String oAuthToken, DataAPIType dataAPIType) { + this(null, null, oAuthToken, dataAPIType); } /** * Instantiates a new {@link AlpacaWebsocketClient}. * - * @param keyID the key ID - * @param secret the secret - * @param oAuthToken the OAuth token + * @param keyID the key ID + * @param secret the secret + * @param oAuthToken the OAuth token + * @param dataAPIType the {@link DataAPIType} */ - private MarketDataWebsocketClient(String keyID, String secret, String oAuthToken) { + private MarketDataWebsocketClient(String keyID, String secret, String oAuthToken, DataAPIType dataAPIType) { this.keyID = keyID; this.secret = secret; this.oAuthToken = oAuthToken; - this.streamAPIURL = AlpacaConstants.URLs.DATA.replace("https", "wss") + "/stream"; + this.websocketAPIURL = dataAPIType.getURL(); listeners = new ArrayList<>(); } @Override public void addListener(StreamListener streamListener) throws WebsocketException { - Preconditions.checkState(streamListener instanceof MarketDataStreamListener); + Preconditions.checkState(streamListener instanceof MarketDataListener); - if (listeners.isEmpty()) { + if (!isConnected()) { try { connect(); - } catch (IOException | URISyntaxException | DeploymentException exception) { + } catch (Exception exception) { throw new WebsocketException(exception); } } - listeners.add((MarketDataStreamListener) streamListener); + listeners.add((MarketDataListener) streamListener); submitStreamRequest(); } @Override public void removeListener(StreamListener streamListener) throws WebsocketException { - Preconditions.checkState(streamListener instanceof MarketDataStreamListener); + Preconditions.checkState(streamListener instanceof MarketDataListener); listeners.remove(streamListener); @@ -131,7 +134,7 @@ public void removeListener(StreamListener streamListener) throws Websocket public void connect() throws URISyntaxException, IOException, DeploymentException { LOGGER.info("Connecting..."); - marketDataWebsocketClientEndpoint = new MarketDataWebsocketClientEndpoint(this, new URI(streamAPIURL)); + marketDataWebsocketClientEndpoint = new MarketDataWebsocketClientEndpoint(this, new URI(websocketAPIURL)); marketDataWebsocketClientEndpoint.setAutomaticallyReconnect(true); marketDataWebsocketClientEndpoint.connect(); @@ -205,7 +208,7 @@ public void handleWebsocketMessage(String message) { try { String streamString = streamJsonElement.getAsString(); - MarketDataStreamMessageType marketDataStreamMessageType; + MarketDataMessageType marketDataMessageType; // Check if the "stream" is a data stream, that is, Q, T, or AM if (streamString.startsWith(TRADES_STREAM_PREFIX) || @@ -213,19 +216,19 @@ public void handleWebsocketMessage(String message) { streamString.startsWith(AGGREGATE_MINUTE_STREAM_PREFIX)) { // Set the stream message type to the parsed EVENT_TYPE_KEY - marketDataStreamMessageType = GsonUtil.GSON.fromJson( + marketDataMessageType = GsonUtil.GSON.fromJson( messageJsonObject.get(DATA_KEY).getAsJsonObject().get(EVENT_TYPE_KEY), - MarketDataStreamMessageType.class); + MarketDataMessageType.class); } else { - marketDataStreamMessageType = GsonUtil.GSON.fromJson(streamJsonElement, - MarketDataStreamMessageType.class); + marketDataMessageType = GsonUtil.GSON.fromJson(streamJsonElement, + MarketDataMessageType.class); } - switch (marketDataStreamMessageType) { + switch (marketDataMessageType) { case AUTHORIZATION: AuthorizationMessage authorizationMessage = GsonUtil.GSON.fromJson(messageJsonObject, AuthorizationMessage.class); - sendStreamMessageToListeners(marketDataStreamMessageType, authorizationMessage); + sendStreamMessageToListeners(marketDataMessageType, authorizationMessage); authenticated = isAuthorizationMessageSuccess(authorizationMessage); @@ -234,33 +237,33 @@ public void handleWebsocketMessage(String message) { case LISTENING: ListeningMessage listeningMessage = GsonUtil.GSON.fromJson(messageJsonObject, ListeningMessage.class); - sendStreamMessageToListeners(marketDataStreamMessageType, listeningMessage); + sendStreamMessageToListeners(marketDataMessageType, listeningMessage); LOGGER.debug("{}", listeningMessage); break; case TRADES: TradeMessage tradeMessage = GsonUtil.GSON.fromJson(messageJsonObject.get(DATA_KEY), TradeMessage.class); - sendStreamMessageToListeners(marketDataStreamMessageType, tradeMessage); + sendStreamMessageToListeners(marketDataMessageType, tradeMessage); LOGGER.debug("{}", tradeMessage); break; case QUOTES: QuoteMessage quoteMessage = GsonUtil.GSON.fromJson(messageJsonObject.get(DATA_KEY), QuoteMessage.class); - sendStreamMessageToListeners(marketDataStreamMessageType, quoteMessage); + sendStreamMessageToListeners(marketDataMessageType, quoteMessage); LOGGER.debug("{}", quoteMessage); break; case AGGREGATE_MINUTE: AggregateMinuteMessage aggregateMinuteMessage = GsonUtil.GSON.fromJson( messageJsonObject.get(DATA_KEY), AggregateMinuteMessage.class); - sendStreamMessageToListeners(marketDataStreamMessageType, aggregateMinuteMessage); + sendStreamMessageToListeners(marketDataMessageType, aggregateMinuteMessage); LOGGER.debug("{}", aggregateMinuteMessage); break; default: - LOGGER.error("Unhandled stream type: {}", marketDataStreamMessageType); + LOGGER.error("Unhandled stream type: {}", marketDataMessageType); } } catch (JsonParseException exception) { LOGGER.error("Could not parse message: {}\n{}", messageJsonObject, exception); @@ -273,13 +276,13 @@ public void handleWebsocketMessage(String message) { @Override public void sendStreamMessageToListeners(StreamMessageType streamMessageType, StreamMessage streamMessage) { - Preconditions.checkState(streamMessageType instanceof MarketDataStreamMessageType); + Preconditions.checkState(streamMessageType instanceof MarketDataMessageType); Preconditions.checkState(streamMessage instanceof MarketDataStreamMessage); - MarketDataStreamMessageType marketDataStreamMessageType = (MarketDataStreamMessageType) streamMessageType; + MarketDataMessageType marketDataMessageType = (MarketDataMessageType) streamMessageType; MarketDataStreamMessage marketDataStreamMessage = (MarketDataStreamMessage) streamMessage; - for (MarketDataStreamListener streamListener : new ArrayList<>(listeners)) { + for (MarketDataListener streamListener : new ArrayList<>(listeners)) { boolean sendToStreamListener = false; if (marketDataStreamMessage instanceof MarketDataStreamStatusMessage) { @@ -287,8 +290,8 @@ public void sendStreamMessageToListeners(StreamMessageType streamMessageType, St // Send the message if any message type registered in the listener for // any ticker is a status message sendToStreamListener = streamListener.getDataStreams().values().stream().anyMatch(types -> - types.contains(MarketDataStreamMessageType.LISTENING) || - types.contains(MarketDataStreamMessageType.AUTHORIZATION)); + types.contains(MarketDataMessageType.LISTENING) || + types.contains(MarketDataMessageType.AUTHORIZATION)); } else { // If the data streams list is empty or null, send the message sendToStreamListener = true; } @@ -297,15 +300,15 @@ public void sendStreamMessageToListeners(StreamMessageType streamMessageType, St marketDataStreamMessage; if (streamListener.getDataStreams() != null && !streamListener.getDataStreams().isEmpty()) { - Set tickerMessageTypes = + Set tickerMessageTypes = streamListener.getDataStreams().getOrDefault(marketDataStreamDataMessage.getTicker(), null); - Set allTickerMessageTypes = + Set allTickerMessageTypes = streamListener.getDataStreams().getOrDefault(ALL_TICKERS, null); if (tickerMessageTypes != null) { - sendToStreamListener = tickerMessageTypes.contains(marketDataStreamMessageType); + sendToStreamListener = tickerMessageTypes.contains(marketDataMessageType); } else if (allTickerMessageTypes != null) { - sendToStreamListener = allTickerMessageTypes.contains(marketDataStreamMessageType); + sendToStreamListener = allTickerMessageTypes.contains(marketDataMessageType); } } else { // If the data streams list is empty or null, send the message sendToStreamListener = true; @@ -313,7 +316,7 @@ public void sendStreamMessageToListeners(StreamMessageType streamMessageType, St } if (sendToStreamListener) { - streamListener.onStreamUpdate(marketDataStreamMessageType, marketDataStreamMessage); + streamListener.onStreamUpdate(marketDataMessageType, marketDataStreamMessage); } } } @@ -370,22 +373,22 @@ private void submitStreamRequest() { } /** - * Gets the registered {@link MarketDataStreamMessageType}s of tickers. May contain duplicates. + * Gets the registered {@link MarketDataMessageType}s of tickers. May contain duplicates. * - * @return the registered {@link MarketDataStreamMessageType}s of tickers + * @return the registered {@link MarketDataMessageType}s of tickers */ - public HashMap> getRegisteredMessageTypes() { - HashMap> allMarketDataStreamMessageTypes = new HashMap<>(); + public HashMap> getRegisteredMessageTypes() { + HashMap> allMarketDataStreamMessageTypes = new HashMap<>(); - for (MarketDataStreamListener streamListener : new ArrayList<>(listeners)) { + for (MarketDataListener streamListener : new ArrayList<>(listeners)) { if (streamListener.getDataStreams() == null || streamListener.getDataStreams().isEmpty()) { continue; // Skip over empty/null data stream listeners } streamListener.getDataStreams().forEach((dataStreamTicker, marketDataStreamMessageTypes) -> { // Filter to only subscribable message types and use HashSet to prevent duplicates - HashSet subscribableMessageTypes = marketDataStreamMessageTypes.stream() - .filter(MarketDataStreamMessageType::isAPISubscribable) + HashSet subscribableMessageTypes = marketDataStreamMessageTypes.stream() + .filter(MarketDataMessageType::isAPISubscribable) .collect(Collectors.toCollection(HashSet::new)); // Add the subscribableMessageTypes if the ticker already exists, otherwise insert a new pair diff --git a/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/client/MarketDataWebsocketClientEndpoint.java b/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/client/MarketDataWebsocketClientEndpoint.java index c2c7cab2..79851bcb 100644 --- a/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/client/MarketDataWebsocketClientEndpoint.java +++ b/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/client/MarketDataWebsocketClientEndpoint.java @@ -3,13 +3,7 @@ import net.jacobpeterson.abstracts.websocket.client.AbstractWebsocketClientEndpoint; import net.jacobpeterson.abstracts.websocket.client.WebsocketClient; -import javax.websocket.ClientEndpoint; -import javax.websocket.CloseReason; -import javax.websocket.OnClose; -import javax.websocket.OnError; -import javax.websocket.OnMessage; -import javax.websocket.OnOpen; -import javax.websocket.Session; +import javax.websocket.*; import java.net.URI; /** diff --git a/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/listener/MarketDataListener.java b/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/listener/MarketDataListener.java new file mode 100644 index 00000000..212aa7e7 --- /dev/null +++ b/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/listener/MarketDataListener.java @@ -0,0 +1,23 @@ +package net.jacobpeterson.alpaca.websocket.marketdata.listener; + +import net.jacobpeterson.abstracts.websocket.listener.StreamListener; +import net.jacobpeterson.alpaca.websocket.marketdata.message.MarketDataMessageType; +import net.jacobpeterson.domain.alpaca.marketdata.realtime.MarketDataMessage; + +import java.util.Map; +import java.util.Set; + +/** + * An asynchronous update interface for receiving websocket messages from + * {@link net.jacobpeterson.alpaca.websocket.marketdata.client.MarketDataWebsocketClient} + */ +public interface MarketDataListener extends StreamListener { + + /** + * Gets the {@link MarketDataMessageType} of tickers {@link Map} for this {@link MarketDataListener}. Null or empty + * to listen to all stream messages. + * + * @return the {@link MarketDataMessageType} of tickers {@link Map} + */ + Map> getDataStreams(); +} diff --git a/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/listener/MarketDataListenerAdapter.java b/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/listener/MarketDataListenerAdapter.java new file mode 100644 index 00000000..aa485524 --- /dev/null +++ b/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/listener/MarketDataListenerAdapter.java @@ -0,0 +1,65 @@ +package net.jacobpeterson.alpaca.websocket.marketdata.listener; + +import net.jacobpeterson.alpaca.websocket.marketdata.message.MarketDataMessageType; +import net.jacobpeterson.domain.alpaca.marketdata.realtime.MarketDataMessage; + +import java.util.Arrays; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; + +/** + * {@link MarketDataListenerAdapter} is an adapter for {@link MarketDataListener}. + */ +public class MarketDataListenerAdapter implements MarketDataListener { + + private final Map> dataStreams = new HashMap<>(); + + /** + * Instantiates a new {@link MarketDataListenerAdapter}. + * + * @param symbol the symbol + * @param dataMessageTypes the {@link MarketDataMessageType}s + */ + public MarketDataListenerAdapter(String symbol, MarketDataMessageType... dataMessageTypes) { + if (dataMessageTypes != null && dataMessageTypes.length > 0) { + dataStreams.put(symbol, new HashSet<>(Arrays.asList(dataMessageTypes))); + } else { + dataStreams.put(symbol, new HashSet<>(Arrays.asList(MarketDataMessageType.values()))); + } + } + + /** + * Instantiates a new {@link MarketDataListenerAdapter}. + * + * @param symbols the symbols + * @param dataMessageTypes the {@link MarketDataMessageType}s + */ + public MarketDataListenerAdapter(Set symbols, MarketDataMessageType... dataMessageTypes) { + for (String symbol : symbols) { + if (dataMessageTypes != null && dataMessageTypes.length > 0) { + dataStreams.put(symbol, new HashSet<>(Arrays.asList(dataMessageTypes))); + } else { + dataStreams.put(symbol, new HashSet<>(Arrays.asList(MarketDataMessageType.values()))); + } + } + } + + /** + * Instantiates a new {@link MarketDataListenerAdapter}. + * + * @param dataStreams the {@link MarketDataMessageType} {@link Set} of symbols {@link Map} + */ + public MarketDataListenerAdapter(Map> dataStreams) { + this.dataStreams.putAll(dataStreams); + } + + @Override + public Map> getDataStreams() { + return dataStreams; + } + + @Override + public void onStreamUpdate(MarketDataMessageType streamMessageType, MarketDataMessage streamMessage) { } +} diff --git a/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/listener/MarketDataStreamListener.java b/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/listener/MarketDataStreamListener.java deleted file mode 100644 index 03bd0f40..00000000 --- a/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/listener/MarketDataStreamListener.java +++ /dev/null @@ -1,23 +0,0 @@ -package net.jacobpeterson.alpaca.websocket.marketdata.listener; - -import net.jacobpeterson.abstracts.websocket.listener.StreamListener; -import net.jacobpeterson.alpaca.websocket.marketdata.message.MarketDataStreamMessageType; -import net.jacobpeterson.domain.alpaca.marketdata.streaming.MarketDataStreamMessage; - -import java.util.Map; -import java.util.Set; - -/** - * An asynchronous update interface for receiving websocket messages for - * {@link net.jacobpeterson.alpaca.websocket.marketdata.client.MarketDataWebsocketClient} - */ -public interface MarketDataStreamListener extends StreamListener { - - /** - * Gets the {@link MarketDataStreamMessageType} of tickers {@link Map} for this {@link MarketDataStreamListener}. - * Null or empty to listen to all stream messages. - * - * @return the {@link MarketDataStreamMessageType} of tickers {@link Map} - */ - Map> getDataStreams(); -} diff --git a/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/listener/MarketDataStreamListenerAdapter.java b/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/listener/MarketDataStreamListenerAdapter.java deleted file mode 100644 index 34167cf1..00000000 --- a/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/listener/MarketDataStreamListenerAdapter.java +++ /dev/null @@ -1,65 +0,0 @@ -package net.jacobpeterson.alpaca.websocket.marketdata.listener; - -import net.jacobpeterson.alpaca.websocket.marketdata.message.MarketDataStreamMessageType; -import net.jacobpeterson.domain.alpaca.marketdata.streaming.MarketDataStreamMessage; - -import java.util.Arrays; -import java.util.HashMap; -import java.util.HashSet; -import java.util.Map; -import java.util.Set; - -/** - * {@link MarketDataStreamListenerAdapter} is an adapter for {@link MarketDataStreamListener}. - */ -public class MarketDataStreamListenerAdapter implements MarketDataStreamListener { - - private final Map> dataStreams = new HashMap<>(); - - /** - * Instantiates a new {@link MarketDataStreamListenerAdapter}. - * - * @param ticker the ticker - * @param dataMessageTypes the {@link MarketDataStreamMessageType}s - */ - public MarketDataStreamListenerAdapter(String ticker, MarketDataStreamMessageType... dataMessageTypes) { - if (dataMessageTypes != null && dataMessageTypes.length > 0) { - dataStreams.put(ticker, new HashSet<>(Arrays.asList(dataMessageTypes))); - } else { - dataStreams.put(ticker, new HashSet<>(Arrays.asList(MarketDataStreamMessageType.values()))); - } - } - - /** - * Instantiates a new {@link MarketDataStreamListenerAdapter}. - * - * @param tickers the tickers - * @param dataMessageTypes the {@link MarketDataStreamMessageType}s - */ - public MarketDataStreamListenerAdapter(Set tickers, MarketDataStreamMessageType... dataMessageTypes) { - for (String ticker : tickers) { - if (dataMessageTypes != null && dataMessageTypes.length > 0) { - dataStreams.put(ticker, new HashSet<>(Arrays.asList(dataMessageTypes))); - } else { - dataStreams.put(ticker, new HashSet<>(Arrays.asList(MarketDataStreamMessageType.values()))); - } - } - } - - /** - * Instantiates a new {@link MarketDataStreamListenerAdapter}. - * - * @param dataStreams the {@link MarketDataStreamMessageType}s of the ticker {@link Map} - */ - public MarketDataStreamListenerAdapter(Map> dataStreams) { - dataStreams.replaceAll((k, v) -> new HashSet<>(v)); - } - - @Override - public Map> getDataStreams() { - return dataStreams; - } - - @Override - public void onStreamUpdate(MarketDataStreamMessageType streamMessageType, MarketDataStreamMessage streamMessage) { } -} diff --git a/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/message/MarketDataMessageType.java b/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/message/MarketDataMessageType.java new file mode 100644 index 00000000..df9283b5 --- /dev/null +++ b/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/message/MarketDataMessageType.java @@ -0,0 +1,59 @@ +package net.jacobpeterson.alpaca.websocket.marketdata.message; + +import com.google.gson.annotations.SerializedName; +import net.jacobpeterson.abstracts.enums.APIName; +import net.jacobpeterson.abstracts.websocket.message.StreamMessageType; + +/** + * {@link MarketDataMessageType} defines enums for various market data stream message types. + */ +public enum MarketDataMessageType implements StreamMessageType, APIName { + + /** The success {@link MarketDataMessageType}. */ + @SerializedName("success") + SUCCESS("success", false), + + /** The error {@link MarketDataMessageType}. */ + @SerializedName("error") + ERROR("error", false), + + /** The trade {@link MarketDataMessageType}. */ + @SerializedName("t") + TRADE("t", true), + + /** The quote {@link MarketDataMessageType}. */ + @SerializedName("q") + QUOTE("q", true), + + /** The bar {@link MarketDataMessageType}. */ + @SerializedName("b") + BAR("b", true); + + private final String apiName; + private final boolean isAPISubscribable; + + /** + * Instantiates a new {@link MarketDataMessageType}. + * + * @param apiName the API name + * @param isAPISubscribable true if API is subscribable + */ + MarketDataMessageType(String apiName, boolean isAPISubscribable) { + this.apiName = apiName; + this.isAPISubscribable = isAPISubscribable; + } + + @Override + public String getAPIName() { + return apiName; + } + + /** + * Returns true if API is subscribable. + * + * @return true if API is subscribable + */ + public boolean isAPISubscribable() { + return isAPISubscribable; + } +} diff --git a/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/message/MarketDataStreamMessageType.java b/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/message/MarketDataStreamMessageType.java deleted file mode 100644 index ff90d09d..00000000 --- a/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/message/MarketDataStreamMessageType.java +++ /dev/null @@ -1,58 +0,0 @@ -package net.jacobpeterson.alpaca.websocket.marketdata.message; - -import com.google.gson.annotations.SerializedName; -import net.jacobpeterson.abstracts.enums.APIName; -import net.jacobpeterson.abstracts.websocket.message.StreamMessageType; -import net.jacobpeterson.util.gson.GsonUtil; - -/** - * {@link MarketDataStreamMessageType} defines enums for various message types for {@link - * net.jacobpeterson.alpaca.websocket.marketdata.listener.MarketDataStreamListener}. - */ -public enum MarketDataStreamMessageType implements StreamMessageType, APIName { - - /** The listening status {@link MarketDataStreamMessageType}. */ - @SerializedName("listening") - LISTENING(false), - - /** The authorization status {@link MarketDataStreamMessageType}. */ - @SerializedName("authorization") - AUTHORIZATION(false), - - /** The trades {@link MarketDataStreamMessageType}. */ - @SerializedName("T") - TRADES(true), - - /** The quotes {@link MarketDataStreamMessageType}. */ - @SerializedName("Q") - QUOTES(true), - - /** The aggregate minute {@link MarketDataStreamMessageType}. */ - @SerializedName("AM") - AGGREGATE_MINUTE(true); - - private final boolean isAPISubscribable; - - /** - * Instantiates a new {@link MarketDataStreamMessageType}. - * - * @param isAPISubscribable true if API subscribable - */ - MarketDataStreamMessageType(boolean isAPISubscribable) { - this.isAPISubscribable = isAPISubscribable; - } - - @Override - public String getAPIName() { - return GsonUtil.GSON.toJsonTree(this).getAsString(); - } - - /** - * Returns true if API subscribable. - * - * @return true if API subscribable - */ - public boolean isAPISubscribable() { - return isAPISubscribable; - } -} diff --git a/src/main/java/net/jacobpeterson/util/gson/LocalDateAdapter.java b/src/main/java/net/jacobpeterson/util/gson/LocalDateAdapter.java index 2799c1f2..fa320691 100644 --- a/src/main/java/net/jacobpeterson/util/gson/LocalDateAdapter.java +++ b/src/main/java/net/jacobpeterson/util/gson/LocalDateAdapter.java @@ -1,12 +1,6 @@ package net.jacobpeterson.util.gson; -import com.google.gson.JsonDeserializationContext; -import com.google.gson.JsonDeserializer; -import com.google.gson.JsonElement; -import com.google.gson.JsonParseException; -import com.google.gson.JsonPrimitive; -import com.google.gson.JsonSerializationContext; -import com.google.gson.JsonSerializer; +import com.google.gson.*; import java.lang.reflect.Type; import java.time.LocalDate; diff --git a/src/main/java/net/jacobpeterson/util/gson/ZonedDateTimeAdapter.java b/src/main/java/net/jacobpeterson/util/gson/ZonedDateTimeAdapter.java index b4b8a5e6..46657f89 100644 --- a/src/main/java/net/jacobpeterson/util/gson/ZonedDateTimeAdapter.java +++ b/src/main/java/net/jacobpeterson/util/gson/ZonedDateTimeAdapter.java @@ -1,12 +1,6 @@ package net.jacobpeterson.util.gson; -import com.google.gson.JsonDeserializationContext; -import com.google.gson.JsonDeserializer; -import com.google.gson.JsonElement; -import com.google.gson.JsonParseException; -import com.google.gson.JsonPrimitive; -import com.google.gson.JsonSerializationContext; -import com.google.gson.JsonSerializer; +import com.google.gson.*; import java.lang.reflect.Type; import java.time.ZonedDateTime; diff --git a/src/main/resources/alpaca.default.properties b/src/main/resources/alpaca.default.properties index 1baf1967..9bbef30b 100644 --- a/src/main/resources/alpaca.default.properties +++ b/src/main/resources/alpaca.default.properties @@ -2,8 +2,10 @@ #key_id = #secret = #endpoint_api_type = +#data_api_type = #user_agent = JavaRuntimeEnvironment/ Unirest/1.4.9 #Defaults: endpoint_api_type=paper +data_api_type=iex user_agent=JavaRuntimeEnvironment/ Unirest/1.4.9 diff --git a/src/test/java/mock/net/jacobpeterson/alpaca/AlpacaAPITest.java b/src/test/java/mock/net/jacobpeterson/alpaca/AlpacaAPITest.java index 602a1f2c..55fbff90 100644 --- a/src/test/java/mock/net/jacobpeterson/alpaca/AlpacaAPITest.java +++ b/src/test/java/mock/net/jacobpeterson/alpaca/AlpacaAPITest.java @@ -1,6 +1,7 @@ package mock.net.jacobpeterson.alpaca; import net.jacobpeterson.alpaca.AlpacaAPI; +import net.jacobpeterson.alpaca.enums.api.DataAPIType; import net.jacobpeterson.alpaca.enums.api.EndpointAPIType; import org.junit.jupiter.api.Test; @@ -35,15 +36,15 @@ public void testAlpacaAPIConstructor_keyID_secret() { } /** - * Tests {@link AlpacaAPI#AlpacaAPI(String, String, EndpointAPIType)}. + * Tests {@link AlpacaAPI#AlpacaAPI(String, String, EndpointAPIType, DataAPIType)}. */ @Test - public void testAlpacaAPIConstructor_keyID_secret_endpointAPIType() { + public void testAlpacaAPIConstructor_keyID_secret_endpointAPIType_dataAPIType() { String keyID = "ABCDEFGHIJKLM"; String secret = "NOPQURSTUVWXYZ"; - new AlpacaAPI(keyID, secret, EndpointAPIType.LIVE); - new AlpacaAPI(keyID, secret, EndpointAPIType.PAPER); + new AlpacaAPI(keyID, secret, EndpointAPIType.PAPER, DataAPIType.IEX); + new AlpacaAPI(keyID, secret, EndpointAPIType.LIVE, DataAPIType.SIP); } /** @@ -57,15 +58,15 @@ public void testAlpacaAPIConstructor_oAuthToken() { } /** - * Tests {@link AlpacaAPI#AlpacaAPI(String, String, String, EndpointAPIType)}. + * Tests {@link AlpacaAPI#AlpacaAPI(String, String, String, EndpointAPIType, DataAPIType)}. */ @Test - public void testAlpacaAPIConstructor_keyID_secret_oAuthToken_endpointAPIType() { + public void testAlpacaAPIConstructor_keyID_secret_oAuthToken_endpointAPIType_dataAPIType() { String keyID = "ABCDEFGHIJKLM"; String secret = "NOPQURSTUVWXYZ"; String oAuthToken = "ABCDEFGHIJKLMNOPQURSTUVWXYZ"; - new AlpacaAPI(keyID, secret, oAuthToken, EndpointAPIType.LIVE); - new AlpacaAPI(keyID, secret, oAuthToken, EndpointAPIType.PAPER); + new AlpacaAPI(keyID, secret, oAuthToken, EndpointAPIType.PAPER, DataAPIType.IEX); + new AlpacaAPI(keyID, secret, oAuthToken, EndpointAPIType.LIVE, DataAPIType.SIP); } } diff --git a/src/test/resources/alpaca.default.properties b/src/test/resources/alpaca.default.properties index 6d116dd0..08092cab 100644 --- a/src/test/resources/alpaca.default.properties +++ b/src/test/resources/alpaca.default.properties @@ -1,5 +1,5 @@ key_id=ABCDEFGHIJKLM secret=NOPQURSTUVWXYZ endpoint_api_type=paper -data_api_version=v2 +data_api_type=iex user_agent=JavaRuntimeEnvironment/ Unirest/1.4.9 From ec6604cc9f07f26ca5ce2c7d4f7f932ba4afba49 Mon Sep 17 00:00:00 2001 From: Jacob Peterson Date: Sat, 27 Feb 2021 23:13:25 -0700 Subject: [PATCH 093/271] Implement market data websockets v2 --- .../market_data/realtime/bar/bar_message.json | 7 +- .../realtime/control/error_message.json | 18 + .../control/subscriptions_message.json | 21 ++ .../realtime/control/success_message.json | 14 + .../realtime/quote/quote_message.json | 7 +- .../market_data/realtime/symbol_message.json | 14 + .../realtime/trade/trade_message.json | 7 +- .../AbstractWebsocketClientEndpoint.java | 20 +- .../websocket/client/WebsocketClient.java | 12 +- .../broker/client/AlpacaWebsocketClient.java | 31 +- .../client/AlpacaWebsocketClientEndpoint.java | 7 +- .../client/MarketDataWebsocketClient.java | 338 +++++++----------- .../MarketDataWebsocketClientEndpoint.java | 15 +- .../message/MarketDataMessageType.java | 4 + .../util/gson/LocalDateAdapter.java | 8 +- .../util/gson/ZonedDateTimeAdapter.java | 8 +- 16 files changed, 254 insertions(+), 277 deletions(-) create mode 100644 schema_json/alpaca/market_data/realtime/control/error_message.json create mode 100644 schema_json/alpaca/market_data/realtime/control/subscriptions_message.json create mode 100644 schema_json/alpaca/market_data/realtime/control/success_message.json create mode 100644 schema_json/alpaca/market_data/realtime/symbol_message.json diff --git a/schema_json/alpaca/market_data/realtime/bar/bar_message.json b/schema_json/alpaca/market_data/realtime/bar/bar_message.json index f03983a0..0fdad40d 100644 --- a/schema_json/alpaca/market_data/realtime/bar/bar_message.json +++ b/schema_json/alpaca/market_data/realtime/bar/bar_message.json @@ -1,15 +1,10 @@ { "type": "object", "extends": { - "existingJavaType": "net.jacobpeterson.domain.alpaca.marketdata.realtime.MarketDataMessage" + "existingJavaType": "net.jacobpeterson.domain.alpaca.marketdata.realtime.SymbolMessage" }, "title": "See https://alpaca.markets/docs/api-documentation/api-v2/market-data/streaming/", "properties": { - "S": { - "existingJavaType": "java.lang.String", - "javaName": "symbol", - "title": "Symbol" - }, "o": { "existingJavaType": "java.lang.Double", "javaName": "open", diff --git a/schema_json/alpaca/market_data/realtime/control/error_message.json b/schema_json/alpaca/market_data/realtime/control/error_message.json new file mode 100644 index 00000000..6d25bc9b --- /dev/null +++ b/schema_json/alpaca/market_data/realtime/control/error_message.json @@ -0,0 +1,18 @@ +{ + "type": "object", + "extends": { + "existingJavaType": "net.jacobpeterson.domain.alpaca.marketdata.realtime.MarketDataMessage" + }, + "title": "See https://alpaca.markets/docs/api-documentation/api-v2/market-data/streaming/", + "properties": { + "code": { + "existingJavaType": "java.lang.Integer", + "title": "The error code." + }, + "msg": { + "existingJavaType": "java.lang.String", + "javaName": "message", + "title": "The error message." + } + } +} diff --git a/schema_json/alpaca/market_data/realtime/control/subscriptions_message.json b/schema_json/alpaca/market_data/realtime/control/subscriptions_message.json new file mode 100644 index 00000000..29cf1c22 --- /dev/null +++ b/schema_json/alpaca/market_data/realtime/control/subscriptions_message.json @@ -0,0 +1,21 @@ +{ + "type": "object", + "extends": { + "existingJavaType": "net.jacobpeterson.domain.alpaca.marketdata.realtime.MarketDataMessage" + }, + "title": "See https://alpaca.markets/docs/api-documentation/api-v2/market-data/streaming/", + "properties": { + "trades": { + "existingJavaType": "java.util.ArrayList", + "title": "The list of subscribed trades." + }, + "quotes": { + "existingJavaType": "java.util.ArrayList", + "title": "The list of subscribed quotes." + }, + "bars": { + "existingJavaType": "java.util.ArrayList", + "title": "The list of subscribed bars." + } + } +} diff --git a/schema_json/alpaca/market_data/realtime/control/success_message.json b/schema_json/alpaca/market_data/realtime/control/success_message.json new file mode 100644 index 00000000..5478d3ef --- /dev/null +++ b/schema_json/alpaca/market_data/realtime/control/success_message.json @@ -0,0 +1,14 @@ +{ + "type": "object", + "extends": { + "existingJavaType": "net.jacobpeterson.domain.alpaca.marketdata.realtime.MarketDataMessage" + }, + "title": "See https://alpaca.markets/docs/api-documentation/api-v2/market-data/streaming/", + "properties": { + "msg": { + "existingJavaType": "java.lang.String", + "javaName": "message", + "title": "The success message." + } + } +} diff --git a/schema_json/alpaca/market_data/realtime/quote/quote_message.json b/schema_json/alpaca/market_data/realtime/quote/quote_message.json index c59aaab9..a7f6233c 100644 --- a/schema_json/alpaca/market_data/realtime/quote/quote_message.json +++ b/schema_json/alpaca/market_data/realtime/quote/quote_message.json @@ -1,15 +1,10 @@ { "type": "object", "extends": { - "existingJavaType": "net.jacobpeterson.domain.alpaca.marketdata.realtime.MarketDataMessage" + "existingJavaType": "net.jacobpeterson.domain.alpaca.marketdata.realtime.SymbolMessage" }, "title": "See https://alpaca.markets/docs/api-documentation/api-v2/market-data/streaming/", "properties": { - "S": { - "existingJavaType": "java.lang.String", - "javaName": "symbol", - "title": "Symbol" - }, "ax": { "existingJavaType": "java.lang.String", "javaName": "askExchangeCode", diff --git a/schema_json/alpaca/market_data/realtime/symbol_message.json b/schema_json/alpaca/market_data/realtime/symbol_message.json new file mode 100644 index 00000000..22c6aaa8 --- /dev/null +++ b/schema_json/alpaca/market_data/realtime/symbol_message.json @@ -0,0 +1,14 @@ +{ + "type": "object", + "extends": { + "existingJavaType": "net.jacobpeterson.domain.alpaca.marketdata.realtime.MarketDataMessage" + }, + "title": "See https://alpaca.markets/docs/api-documentation/api-v2/market-data/streaming/", + "properties": { + "S": { + "existingJavaType": "java.lang.String", + "javaName": "symbol", + "title": "Symbol" + } + } +} diff --git a/schema_json/alpaca/market_data/realtime/trade/trade_message.json b/schema_json/alpaca/market_data/realtime/trade/trade_message.json index e61bee82..7aa28731 100644 --- a/schema_json/alpaca/market_data/realtime/trade/trade_message.json +++ b/schema_json/alpaca/market_data/realtime/trade/trade_message.json @@ -1,15 +1,10 @@ { "type": "object", "extends": { - "existingJavaType": "net.jacobpeterson.domain.alpaca.marketdata.realtime.MarketDataMessage" + "existingJavaType": "net.jacobpeterson.domain.alpaca.marketdata.realtime.SymbolMessage" }, "title": "See https://alpaca.markets/docs/api-documentation/api-v2/market-data/streaming/", "properties": { - "S": { - "existingJavaType": "java.lang.String", - "javaName": "symbol", - "title": "Symbol" - }, "i": { "existingJavaType": "java.lang.Integer", "javaName": "tradeID", diff --git a/src/main/java/net/jacobpeterson/abstracts/websocket/client/AbstractWebsocketClientEndpoint.java b/src/main/java/net/jacobpeterson/abstracts/websocket/client/AbstractWebsocketClientEndpoint.java index 131ed87c..38bdc619 100644 --- a/src/main/java/net/jacobpeterson/abstracts/websocket/client/AbstractWebsocketClientEndpoint.java +++ b/src/main/java/net/jacobpeterson/abstracts/websocket/client/AbstractWebsocketClientEndpoint.java @@ -4,8 +4,12 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import javax.websocket.*; +import javax.websocket.CloseReason; import javax.websocket.CloseReason.CloseCodes; +import javax.websocket.ContainerProvider; +import javax.websocket.DeploymentException; +import javax.websocket.Session; +import javax.websocket.WebSocketContainer; import java.io.IOException; import java.net.URI; import java.util.concurrent.ExecutorService; @@ -15,15 +19,17 @@ * {@link AbstractWebsocketClientEndpoint} is used for handling a Websocket directly. *
* NOTES: You MUST annotate a subclass's {@link #onOpen(Session)}, {@link #onClose(Session, CloseReason)}, {@link - * #onMessage(String)}, and {@link #onError(Throwable)} with {@link javax.websocket.OnOpen}, {@link - * javax.websocket.OnClose}, {@link javax.websocket.OnMessage}, and {@link javax.websocket.OnError} respectively and + * #onMessage(String)}*, and {@link #onError(Throwable)} with {@link javax.websocket.OnOpen}, {@link + * javax.websocket.OnClose}*, {@link javax.websocket.OnMessage}, and {@link javax.websocket.OnError} respectively and * with the appropriate websocket sub-protocols because Websocket annotations don't work with inheritance. + * + * @param the {@link WebsocketClient} type parameter */ -public abstract class AbstractWebsocketClientEndpoint { +public abstract class AbstractWebsocketClientEndpoint> { private static final Logger LOGGER = LoggerFactory.getLogger(AbstractWebsocketClientEndpoint.class); - private final WebsocketClient websocketClient; + private final T websocketClient; private final URI endpointURI; private final String messageThreadName; @@ -38,11 +44,11 @@ public abstract class AbstractWebsocketClientEndpoint { /** * Instantiates a new {@link AbstractWebsocketClientEndpoint}. * - * @param websocketClient the {@link WebsocketClient} + * @param websocketClient the {@link WebsocketClient} type * @param endpointURI the endpoint {@link URI} * @param messageThreadName the message thread name */ - public AbstractWebsocketClientEndpoint(WebsocketClient websocketClient, URI endpointURI, String messageThreadName) { + public AbstractWebsocketClientEndpoint(T websocketClient, URI endpointURI, String messageThreadName) { this.websocketClient = websocketClient; this.endpointURI = endpointURI; this.messageThreadName = messageThreadName; diff --git a/src/main/java/net/jacobpeterson/abstracts/websocket/client/WebsocketClient.java b/src/main/java/net/jacobpeterson/abstracts/websocket/client/WebsocketClient.java index b42267dc..200076a8 100644 --- a/src/main/java/net/jacobpeterson/abstracts/websocket/client/WebsocketClient.java +++ b/src/main/java/net/jacobpeterson/abstracts/websocket/client/WebsocketClient.java @@ -7,8 +7,12 @@ /** * {@link WebsocketClient} represents a client for a Websocket. + * + * @param the {@link StreamListener} type parameter + * @param the {@link StreamMessageType} type parameter + * @param the {@link StreamMessage} type parameter */ -public interface WebsocketClient { +public interface WebsocketClient, T extends StreamMessageType, M extends StreamMessage> { /** * Adds a {@link StreamListener}. Note that this method is blocking. @@ -17,7 +21,7 @@ public interface WebsocketClient { * * @throws WebsocketException thrown for {@link WebsocketException}s */ - void addListener(StreamListener streamListener) throws WebsocketException; + void addListener(L streamListener) throws WebsocketException; /** * Removes a {@link StreamListener}. Note that this method is blocking. @@ -26,7 +30,7 @@ public interface WebsocketClient { * * @throws WebsocketException thrown for {@link WebsocketException}s */ - void removeListener(StreamListener streamListener) throws WebsocketException; + void removeListener(L streamListener) throws WebsocketException; /** * Connects the Websocket. Note that this method is blocking. @@ -65,7 +69,7 @@ public interface WebsocketClient { * @param streamMessageType the {@link StreamMessageType} * @param streamMessage the {@link StreamMessage} */ - void sendStreamMessageToListeners(StreamMessageType streamMessageType, StreamMessage streamMessage); + void sendStreamMessageToListeners(T streamMessageType, M streamMessage); /** * Returns true if the Websocket is connected. diff --git a/src/main/java/net/jacobpeterson/alpaca/websocket/broker/client/AlpacaWebsocketClient.java b/src/main/java/net/jacobpeterson/alpaca/websocket/broker/client/AlpacaWebsocketClient.java index e99b07d0..2a1e2fb7 100644 --- a/src/main/java/net/jacobpeterson/alpaca/websocket/broker/client/AlpacaWebsocketClient.java +++ b/src/main/java/net/jacobpeterson/alpaca/websocket/broker/client/AlpacaWebsocketClient.java @@ -1,12 +1,13 @@ package net.jacobpeterson.alpaca.websocket.broker.client; import com.google.common.base.Preconditions; -import com.google.gson.*; +import com.google.gson.JsonArray; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import com.google.gson.JsonPrimitive; +import com.google.gson.JsonSyntaxException; import net.jacobpeterson.abstracts.websocket.client.WebsocketClient; import net.jacobpeterson.abstracts.websocket.exception.WebsocketException; -import net.jacobpeterson.abstracts.websocket.listener.StreamListener; -import net.jacobpeterson.abstracts.websocket.message.StreamMessage; -import net.jacobpeterson.abstracts.websocket.message.StreamMessageType; import net.jacobpeterson.alpaca.enums.api.EndpointAPIType; import net.jacobpeterson.alpaca.websocket.broker.listener.AlpacaStreamListener; import net.jacobpeterson.alpaca.websocket.broker.message.AlpacaStreamMessageType; @@ -33,7 +34,8 @@ * {@link AlpacaWebsocketClient} represents a client for the {@link net.jacobpeterson.alpaca.AlpacaAPI} Websocket * stream. */ -public class AlpacaWebsocketClient implements WebsocketClient { +public class AlpacaWebsocketClient implements WebsocketClient { private static final Logger LOGGER = LoggerFactory.getLogger(AlpacaWebsocketClient.class); private static final String STREAM_KEY = "stream"; @@ -85,9 +87,7 @@ private AlpacaWebsocketClient(String keyID, String secret, String oAuthToken, En } @Override - public void addListener(StreamListener streamListener) throws WebsocketException { - Preconditions.checkState(streamListener instanceof AlpacaStreamListener); - + public void addListener(AlpacaStreamListener streamListener) throws WebsocketException { if (listeners.isEmpty()) { try { connect(); @@ -96,15 +96,13 @@ public void addListener(StreamListener streamListener) throws WebsocketExc } } - listeners.add((AlpacaStreamListener) streamListener); + listeners.add(streamListener); submitStreamRequestUpdate(); } @Override - public void removeListener(StreamListener streamListener) throws WebsocketException { - Preconditions.checkState(streamListener instanceof AlpacaStreamListener); - + public void removeListener(AlpacaStreamListener streamListener) throws WebsocketException { listeners.remove(streamListener); if (listeners.isEmpty()) { @@ -223,13 +221,8 @@ public void handleWebsocketMessage(String message) { } @Override - public void sendStreamMessageToListeners(StreamMessageType streamMessageType, StreamMessage streamMessage) { - Preconditions.checkState(streamMessageType instanceof AlpacaStreamMessageType); - Preconditions.checkState(streamMessage instanceof AlpacaStreamMessage); - - AlpacaStreamMessageType alpacaStreamMessageType = (AlpacaStreamMessageType) streamMessageType; - AlpacaStreamMessage alpacaStreamMessage = (AlpacaStreamMessage) streamMessage; - + public void sendStreamMessageToListeners(AlpacaStreamMessageType alpacaStreamMessageType, + AlpacaStreamMessage alpacaStreamMessage) { for (AlpacaStreamListener alpacaStreamListener : new ArrayList<>(listeners)) { if (alpacaStreamListener.getStreamMessageTypes() == null || alpacaStreamListener.getStreamMessageTypes().isEmpty() || diff --git a/src/main/java/net/jacobpeterson/alpaca/websocket/broker/client/AlpacaWebsocketClientEndpoint.java b/src/main/java/net/jacobpeterson/alpaca/websocket/broker/client/AlpacaWebsocketClientEndpoint.java index b72d21ae..76485be2 100644 --- a/src/main/java/net/jacobpeterson/alpaca/websocket/broker/client/AlpacaWebsocketClientEndpoint.java +++ b/src/main/java/net/jacobpeterson/alpaca/websocket/broker/client/AlpacaWebsocketClientEndpoint.java @@ -1,7 +1,6 @@ package net.jacobpeterson.alpaca.websocket.broker.client; import net.jacobpeterson.abstracts.websocket.client.AbstractWebsocketClientEndpoint; -import net.jacobpeterson.abstracts.websocket.client.WebsocketClient; import javax.websocket.ClientEndpoint; import javax.websocket.CloseReason; @@ -18,15 +17,15 @@ * net.jacobpeterson.alpaca.AlpacaAPI}. */ @ClientEndpoint(subprotocols = "BINARY") -public class AlpacaWebsocketClientEndpoint extends AbstractWebsocketClientEndpoint { +public class AlpacaWebsocketClientEndpoint extends AbstractWebsocketClientEndpoint { /** * Instantiates a new {@link AlpacaWebsocketClientEndpoint}. * - * @param websocketClient the {@link WebsocketClient} + * @param websocketClient the {@link AlpacaWebsocketClient} * @param endpointURI the endpoint {@link URI} */ - public AlpacaWebsocketClientEndpoint(WebsocketClient websocketClient, URI endpointURI) { + public AlpacaWebsocketClientEndpoint(AlpacaWebsocketClient websocketClient, URI endpointURI) { super(websocketClient, endpointURI, "AlpacaWebsocketThread"); } diff --git a/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/client/MarketDataWebsocketClient.java b/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/client/MarketDataWebsocketClient.java index c99b2816..f0dee71d 100644 --- a/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/client/MarketDataWebsocketClient.java +++ b/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/client/MarketDataWebsocketClient.java @@ -4,17 +4,20 @@ import com.google.gson.JsonArray; import com.google.gson.JsonElement; import com.google.gson.JsonObject; -import com.google.gson.JsonParseException; -import com.google.gson.JsonPrimitive; import net.jacobpeterson.abstracts.websocket.client.WebsocketClient; import net.jacobpeterson.abstracts.websocket.exception.WebsocketException; -import net.jacobpeterson.abstracts.websocket.listener.StreamListener; -import net.jacobpeterson.abstracts.websocket.message.StreamMessage; -import net.jacobpeterson.abstracts.websocket.message.StreamMessageType; import net.jacobpeterson.alpaca.enums.api.DataAPIType; import net.jacobpeterson.alpaca.websocket.broker.client.AlpacaWebsocketClient; import net.jacobpeterson.alpaca.websocket.marketdata.listener.MarketDataListener; import net.jacobpeterson.alpaca.websocket.marketdata.message.MarketDataMessageType; +import net.jacobpeterson.domain.alpaca.marketdata.realtime.MarketDataMessage; +import net.jacobpeterson.domain.alpaca.marketdata.realtime.SymbolMessage; +import net.jacobpeterson.domain.alpaca.marketdata.realtime.bar.BarMessage; +import net.jacobpeterson.domain.alpaca.marketdata.realtime.control.ErrorMessage; +import net.jacobpeterson.domain.alpaca.marketdata.realtime.control.SubscriptionsMessage; +import net.jacobpeterson.domain.alpaca.marketdata.realtime.control.SuccessMessage; +import net.jacobpeterson.domain.alpaca.marketdata.realtime.quote.QuoteMessage; +import net.jacobpeterson.domain.alpaca.marketdata.realtime.trade.TradeMessage; import net.jacobpeterson.util.gson.GsonUtil; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -24,30 +27,20 @@ import java.net.URI; import java.net.URISyntaxException; import java.util.ArrayList; -import java.util.HashMap; -import java.util.HashSet; import java.util.List; +import java.util.Map; import java.util.Set; -import java.util.stream.Collectors; /** * {@link MarketDataWebsocketClient} represents a client for the {@link net.jacobpeterson.alpaca.AlpacaAPI} market data * Websocket stream. */ -public class MarketDataWebsocketClient implements WebsocketClient { +public class MarketDataWebsocketClient implements WebsocketClient { private static final Logger LOGGER = LoggerFactory.getLogger(MarketDataWebsocketClient.class); - - private static final String STREAM_KEY = "stream"; - private static final String EVENT_TYPE_KEY = "ev"; - private static final String DATA_KEY = "data"; private static final String ALL_TICKERS = "*"; - private static final String TRADES_STREAM_PREFIX = - GsonUtil.GSON.toJson(MarketDataMessageType.TRADES).replace("\"", "") + "."; - private static final String QUOTES_STREAM_PREFIX = - GsonUtil.GSON.toJson(MarketDataMessageType.QUOTES).replace("\"", "") + "."; - private static final String AGGREGATE_MINUTE_STREAM_PREFIX = - GsonUtil.GSON.toJson(MarketDataMessageType.AGGREGATE_MINUTE).replace("\"", "") + "."; + private static final String MESSAGE_TYPE_KEY = "T"; private final String keyID; private final String secret; @@ -97,9 +90,7 @@ private MarketDataWebsocketClient(String keyID, String secret, String oAuthToken } @Override - public void addListener(StreamListener streamListener) throws WebsocketException { - Preconditions.checkState(streamListener instanceof MarketDataListener); - + public void addListener(MarketDataListener streamListener) throws WebsocketException { if (!isConnected()) { try { connect(); @@ -108,15 +99,13 @@ public void addListener(StreamListener streamListener) throws WebsocketExc } } - listeners.add((MarketDataListener) streamListener); + listeners.add(streamListener); - submitStreamRequest(); + submitRequest(streamListener, false); } @Override - public void removeListener(StreamListener streamListener) throws WebsocketException { - Preconditions.checkState(streamListener instanceof MarketDataListener); - + public void removeListener(MarketDataListener streamListener) throws WebsocketException { listeners.remove(streamListener); if (listeners.isEmpty()) { @@ -126,7 +115,7 @@ public void removeListener(StreamListener streamListener) throws Websocket throw new WebsocketException(exception); } } else { - submitStreamRequest(); + submitRequest(streamListener, true); } } @@ -153,170 +142,108 @@ public void disconnect() throws Exception { @Override public void sendAuthenticationMessage() { /* Format of message is: - * { - * "action": "authenticate", - * "data": { - * "key_id": "{YOUR_API_KEY_ID}", - * "secret_key": "{YOUR_API_SECRET_KEY}" - * } - * } + * {"action": "auth", "key": "{APCA-API-KEY-ID}", "secret": "{APCA-API-SECRET-KEY}"} */ JsonObject authRequest = new JsonObject(); - authRequest.addProperty("action", "authenticate"); + authRequest.addProperty("action", "auth"); - JsonObject dataJson = new JsonObject(); if (oAuthToken != null) { - dataJson.addProperty("oauth_token", oAuthToken); + throw new UnsupportedOperationException("OAuth isn't currently implemented!"); } else { - dataJson.addProperty("key_id", keyID); - dataJson.addProperty("secret_key", secret); + authRequest.addProperty("key", keyID); + authRequest.addProperty("secret", secret); } - authRequest.add("data", dataJson); - marketDataWebsocketClientEndpoint.sendMessage(authRequest.toString()); } @Override public void handleResubscribing() { - submitStreamRequest(); + for (MarketDataListener marketDataListener : listeners) { + submitRequest(marketDataListener, false); + } } @Override public void handleWebsocketMessage(String message) { - /* Format of authorization message: - * {"stream":"authorization","data":{"action":"authenticate","status":"authorized"}} - * - * Format of listened streams message: - * {"stream":"listening","data":{"streams":["Q.AAPL"]}} - * - * Format of stream data message: - * {"stream":"Q.AAPL","data":{"ev":"Q","T":"AAPL","x":17,"p":112.12,"s":1,"X":3,"P":112.98,"S":1, - * "c":[0],"t":1601645053026000000}} - */ - JsonElement messageJsonElement = GsonUtil.JSON_PARSER.parse(message); - Preconditions.checkState(messageJsonElement instanceof JsonObject); - - JsonObject messageJsonObject = messageJsonElement.getAsJsonObject(); - - if (messageJsonObject.has(STREAM_KEY)) { - JsonElement streamJsonElement = messageJsonObject.get(STREAM_KEY); - - if (streamJsonElement instanceof JsonPrimitive) { - try { - String streamString = streamJsonElement.getAsString(); - - MarketDataMessageType marketDataMessageType; - - // Check if the "stream" is a data stream, that is, Q, T, or AM - if (streamString.startsWith(TRADES_STREAM_PREFIX) || - streamString.startsWith(QUOTES_STREAM_PREFIX) || - streamString.startsWith(AGGREGATE_MINUTE_STREAM_PREFIX)) { - - // Set the stream message type to the parsed EVENT_TYPE_KEY - marketDataMessageType = GsonUtil.GSON.fromJson( - messageJsonObject.get(DATA_KEY).getAsJsonObject().get(EVENT_TYPE_KEY), - MarketDataMessageType.class); - } else { - marketDataMessageType = GsonUtil.GSON.fromJson(streamJsonElement, - MarketDataMessageType.class); - } - - switch (marketDataMessageType) { - case AUTHORIZATION: - AuthorizationMessage authorizationMessage = GsonUtil.GSON.fromJson(messageJsonObject, - AuthorizationMessage.class); - sendStreamMessageToListeners(marketDataMessageType, authorizationMessage); - - authenticated = isAuthorizationMessageSuccess(authorizationMessage); - - LOGGER.debug("{}", authorizationMessage); - break; - case LISTENING: - ListeningMessage listeningMessage = GsonUtil.GSON.fromJson(messageJsonObject, - ListeningMessage.class); - sendStreamMessageToListeners(marketDataMessageType, listeningMessage); - - LOGGER.debug("{}", listeningMessage); - break; - case TRADES: - TradeMessage tradeMessage = GsonUtil.GSON.fromJson(messageJsonObject.get(DATA_KEY), - TradeMessage.class); - sendStreamMessageToListeners(marketDataMessageType, tradeMessage); - - LOGGER.debug("{}", tradeMessage); - break; - case QUOTES: - QuoteMessage quoteMessage = GsonUtil.GSON.fromJson(messageJsonObject.get(DATA_KEY), - QuoteMessage.class); - sendStreamMessageToListeners(marketDataMessageType, quoteMessage); - - LOGGER.debug("{}", quoteMessage); - break; - case AGGREGATE_MINUTE: - AggregateMinuteMessage aggregateMinuteMessage = GsonUtil.GSON.fromJson( - messageJsonObject.get(DATA_KEY), AggregateMinuteMessage.class); - sendStreamMessageToListeners(marketDataMessageType, aggregateMinuteMessage); - - LOGGER.debug("{}", aggregateMinuteMessage); - break; - default: - LOGGER.error("Unhandled stream type: {}", marketDataMessageType); - } - } catch (JsonParseException exception) { - LOGGER.error("Could not parse message: {}\n{}", messageJsonObject, exception); - } - } else { - LOGGER.error("Unknown stream message: {}", messageJsonObject); + Preconditions.checkState(messageJsonElement instanceof JsonArray); + + JsonArray messageJsonArray = messageJsonElement.getAsJsonArray(); + + for (JsonElement arrayJsonElement : messageJsonArray) { + if (!(arrayJsonElement instanceof JsonObject)) { + continue; + } + + JsonObject messageJsonObject = arrayJsonElement.getAsJsonObject(); + MarketDataMessageType marketDataMessageType = GsonUtil.GSON.fromJson( + messageJsonObject.get(MESSAGE_TYPE_KEY), MarketDataMessageType.class); + if (marketDataMessageType == null) { + LOGGER.error("Message type not found in message: {}", messageJsonObject); + continue; + } + + MarketDataMessage marketDataMessage; + switch (marketDataMessageType) { + case SUCCESS: + marketDataMessage = GsonUtil.GSON.fromJson(messageJsonObject, SuccessMessage.class); + authenticated = isAuthorizationMessageSuccess((SuccessMessage) marketDataMessage); + break; + case ERROR: + marketDataMessage = GsonUtil.GSON.fromJson(messageJsonObject, ErrorMessage.class); + LOGGER.error("{}", marketDataMessage); + break; + case SUBSCRIPTION: + marketDataMessage = GsonUtil.GSON.fromJson(messageJsonObject, SubscriptionsMessage.class); + break; + case TRADE: + marketDataMessage = GsonUtil.GSON.fromJson(messageJsonObject, TradeMessage.class); + break; + case QUOTE: + marketDataMessage = GsonUtil.GSON.fromJson(messageJsonObject, QuoteMessage.class); + break; + case BAR: + marketDataMessage = GsonUtil.GSON.fromJson(messageJsonObject, BarMessage.class); + break; + default: + LOGGER.error("Message type {} not implemented!", marketDataMessageType); + continue; } + + LOGGER.debug("{}", marketDataMessage); + sendStreamMessageToListeners(marketDataMessageType, marketDataMessage); } } @Override - public void sendStreamMessageToListeners(StreamMessageType streamMessageType, StreamMessage streamMessage) { - Preconditions.checkState(streamMessageType instanceof MarketDataMessageType); - Preconditions.checkState(streamMessage instanceof MarketDataStreamMessage); - - MarketDataMessageType marketDataMessageType = (MarketDataMessageType) streamMessageType; - MarketDataStreamMessage marketDataStreamMessage = (MarketDataStreamMessage) streamMessage; - - for (MarketDataListener streamListener : new ArrayList<>(listeners)) { + public void sendStreamMessageToListeners(MarketDataMessageType marketDataMessageType, + MarketDataMessage marketDataMessage) { + for (MarketDataListener marketDataListener : new ArrayList<>(listeners)) { boolean sendToStreamListener = false; - if (marketDataStreamMessage instanceof MarketDataStreamStatusMessage) { - if (streamListener.getDataStreams() != null && !streamListener.getDataStreams().isEmpty()) { - // Send the message if any message type registered in the listener for - // any ticker is a status message - sendToStreamListener = streamListener.getDataStreams().values().stream().anyMatch(types -> - types.contains(MarketDataMessageType.LISTENING) || - types.contains(MarketDataMessageType.AUTHORIZATION)); - } else { // If the data streams list is empty or null, send the message - sendToStreamListener = true; - } - } else if (marketDataStreamMessage instanceof MarketDataStreamDataMessage) { - MarketDataStreamDataMessage marketDataStreamDataMessage = (MarketDataStreamDataMessage) - marketDataStreamMessage; - - if (streamListener.getDataStreams() != null && !streamListener.getDataStreams().isEmpty()) { - Set tickerMessageTypes = - streamListener.getDataStreams().getOrDefault(marketDataStreamDataMessage.getTicker(), null); - Set allTickerMessageTypes = - streamListener.getDataStreams().getOrDefault(ALL_TICKERS, null); - - if (tickerMessageTypes != null) { - sendToStreamListener = tickerMessageTypes.contains(marketDataMessageType); - } else if (allTickerMessageTypes != null) { - sendToStreamListener = allTickerMessageTypes.contains(marketDataMessageType); - } - } else { // If the data streams list is empty or null, send the message - sendToStreamListener = true; + if (marketDataListener.getDataStreams() == null || marketDataListener.getDataStreams().isEmpty()) { + sendToStreamListener = true; + } else if (!(marketDataMessage instanceof SymbolMessage) && marketDataListener.getDataStreams() + .values().stream().anyMatch(types -> types.contains(marketDataMessageType))) { + sendToStreamListener = true; + } else if (marketDataMessage instanceof SymbolMessage) { + SymbolMessage symbolMessage = (SymbolMessage) marketDataMessage; + Set symbolMessageTypes = marketDataListener.getDataStreams() + .getOrDefault(symbolMessage.getMessageType(), null); + Set allMessageTypes = marketDataListener.getDataStreams() + .getOrDefault(ALL_TICKERS, null); + + if (symbolMessageTypes != null) { + sendToStreamListener = symbolMessageTypes.contains(marketDataMessageType); + } else if (allMessageTypes != null) { + sendToStreamListener = allMessageTypes.contains(marketDataMessageType); } } if (sendToStreamListener) { - streamListener.onStreamUpdate(marketDataMessageType, marketDataStreamMessage); + marketDataListener.onStreamUpdate(marketDataMessageType, marketDataMessage); } } } @@ -332,74 +259,49 @@ public boolean isAuthenticated() { } /** - * Returns true if {@link AuthorizationMessage} states success. + * Returns true if {@link SuccessMessage} states authentication. * - * @param authorizationMessage the {@link AuthorizationMessage} + * @param successMessage the {@link SuccessMessage} * - * @return true if {@link AuthorizationMessage} states success + * @return true if {@link SuccessMessage} states authentication */ - private boolean isAuthorizationMessageSuccess(AuthorizationMessage authorizationMessage) { - return authorizationMessage.getData().getStatus().equalsIgnoreCase("authorized") && - authorizationMessage.getData().getAction().equalsIgnoreCase("authenticate"); + private boolean isAuthorizationMessageSuccess(SuccessMessage successMessage) { + return successMessage.getMessage().equals("authenticated"); } /** - * Submits a stream request. + * Submits a (un)subscribe request. + * + * @param marketDataListener the {@link MarketDataListener}s + * @param unsubscribe true to unsubscribe, false to subscribe */ - private void submitStreamRequest() { - /* Stream request example: - * { - * "action": "listen", - * "data": { - * "streams": ["T.SPY", "Q.SPY", "AM.SPY"] - * } - * } - */ - + private void submitRequest(MarketDataListener marketDataListener, boolean unsubscribe) { JsonObject streamRequestJsonObject = new JsonObject(); - streamRequestJsonObject.addProperty("action", "listen"); + streamRequestJsonObject.addProperty("action", unsubscribe ? "unsubscribe" : "subscribe"); - JsonArray streamsJsonArray = new JsonArray(); - getRegisteredMessageTypes().forEach((s, m) -> m.forEach(i -> streamsJsonArray.add(i.getAPIName() + "." + s))); + JsonArray tradesJsonArray = new JsonArray(); + JsonArray quotesJsonArray = new JsonArray(); + JsonArray barsJsonArray = new JsonArray(); - JsonObject dataJsonObject = new JsonObject(); - dataJsonObject.add("streams", streamsJsonArray); + for (Map.Entry> listenerEntry : marketDataListener + .getDataStreams().entrySet()) { + if (listenerEntry.getValue().contains(MarketDataMessageType.TRADE)) { + tradesJsonArray.add(listenerEntry.getKey()); + } + if (listenerEntry.getValue().contains(MarketDataMessageType.QUOTE)) { + quotesJsonArray.add(listenerEntry.getKey()); + } + if (listenerEntry.getValue().contains(MarketDataMessageType.BAR)) { + barsJsonArray.add(listenerEntry.getKey()); + } + } - streamRequestJsonObject.add("data", dataJsonObject); + streamRequestJsonObject.add("trades", tradesJsonArray); + streamRequestJsonObject.add("quotes", quotesJsonArray); + streamRequestJsonObject.add("bars", barsJsonArray); marketDataWebsocketClientEndpoint.sendMessage(streamRequestJsonObject.toString()); - LOGGER.info("Requested subscriptions to update to {}", streamsJsonArray); - } - - /** - * Gets the registered {@link MarketDataMessageType}s of tickers. May contain duplicates. - * - * @return the registered {@link MarketDataMessageType}s of tickers - */ - public HashMap> getRegisteredMessageTypes() { - HashMap> allMarketDataStreamMessageTypes = new HashMap<>(); - - for (MarketDataListener streamListener : new ArrayList<>(listeners)) { - if (streamListener.getDataStreams() == null || streamListener.getDataStreams().isEmpty()) { - continue; // Skip over empty/null data stream listeners - } - - streamListener.getDataStreams().forEach((dataStreamTicker, marketDataStreamMessageTypes) -> { - // Filter to only subscribable message types and use HashSet to prevent duplicates - HashSet subscribableMessageTypes = marketDataStreamMessageTypes.stream() - .filter(MarketDataMessageType::isAPISubscribable) - .collect(Collectors.toCollection(HashSet::new)); - - // Add the subscribableMessageTypes if the ticker already exists, otherwise insert a new pair - if (allMarketDataStreamMessageTypes.containsKey(dataStreamTicker)) { - allMarketDataStreamMessageTypes.get(dataStreamTicker).addAll(subscribableMessageTypes); - } else { - allMarketDataStreamMessageTypes.put(dataStreamTicker, subscribableMessageTypes); - } - }); - } - - return allMarketDataStreamMessageTypes; + LOGGER.info("Requested subscriptions to update to {}", streamRequestJsonObject); } } diff --git a/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/client/MarketDataWebsocketClientEndpoint.java b/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/client/MarketDataWebsocketClientEndpoint.java index 79851bcb..8eeff8bf 100644 --- a/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/client/MarketDataWebsocketClientEndpoint.java +++ b/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/client/MarketDataWebsocketClientEndpoint.java @@ -1,9 +1,14 @@ package net.jacobpeterson.alpaca.websocket.marketdata.client; import net.jacobpeterson.abstracts.websocket.client.AbstractWebsocketClientEndpoint; -import net.jacobpeterson.abstracts.websocket.client.WebsocketClient; -import javax.websocket.*; +import javax.websocket.ClientEndpoint; +import javax.websocket.CloseReason; +import javax.websocket.OnClose; +import javax.websocket.OnError; +import javax.websocket.OnMessage; +import javax.websocket.OnOpen; +import javax.websocket.Session; import java.net.URI; /** @@ -11,15 +16,15 @@ * net.jacobpeterson.alpaca.AlpacaAPI} market data. */ @ClientEndpoint(subprotocols = "STRING") -public class MarketDataWebsocketClientEndpoint extends AbstractWebsocketClientEndpoint { +public class MarketDataWebsocketClientEndpoint extends AbstractWebsocketClientEndpoint { /** * Instantiates a new {@link MarketDataWebsocketClientEndpoint}. * - * @param websocketClient the {@link WebsocketClient} + * @param websocketClient the {@link MarketDataWebsocketClient} * @param endpointURI the endpoint {@link URI} */ - public MarketDataWebsocketClientEndpoint(WebsocketClient websocketClient, URI endpointURI) { + public MarketDataWebsocketClientEndpoint(MarketDataWebsocketClient websocketClient, URI endpointURI) { super(websocketClient, endpointURI, "MarketDataWebsocketThread"); } diff --git a/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/message/MarketDataMessageType.java b/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/message/MarketDataMessageType.java index df9283b5..702d5dc0 100644 --- a/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/message/MarketDataMessageType.java +++ b/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/message/MarketDataMessageType.java @@ -17,6 +17,10 @@ public enum MarketDataMessageType implements StreamMessageType, APIName { @SerializedName("error") ERROR("error", false), + /** The subscription {@link MarketDataMessageType}. */ + @SerializedName("subscription") + SUBSCRIPTION("subscription", false), + /** The trade {@link MarketDataMessageType}. */ @SerializedName("t") TRADE("t", true), diff --git a/src/main/java/net/jacobpeterson/util/gson/LocalDateAdapter.java b/src/main/java/net/jacobpeterson/util/gson/LocalDateAdapter.java index fa320691..2799c1f2 100644 --- a/src/main/java/net/jacobpeterson/util/gson/LocalDateAdapter.java +++ b/src/main/java/net/jacobpeterson/util/gson/LocalDateAdapter.java @@ -1,6 +1,12 @@ package net.jacobpeterson.util.gson; -import com.google.gson.*; +import com.google.gson.JsonDeserializationContext; +import com.google.gson.JsonDeserializer; +import com.google.gson.JsonElement; +import com.google.gson.JsonParseException; +import com.google.gson.JsonPrimitive; +import com.google.gson.JsonSerializationContext; +import com.google.gson.JsonSerializer; import java.lang.reflect.Type; import java.time.LocalDate; diff --git a/src/main/java/net/jacobpeterson/util/gson/ZonedDateTimeAdapter.java b/src/main/java/net/jacobpeterson/util/gson/ZonedDateTimeAdapter.java index 46657f89..b4b8a5e6 100644 --- a/src/main/java/net/jacobpeterson/util/gson/ZonedDateTimeAdapter.java +++ b/src/main/java/net/jacobpeterson/util/gson/ZonedDateTimeAdapter.java @@ -1,6 +1,12 @@ package net.jacobpeterson.util.gson; -import com.google.gson.*; +import com.google.gson.JsonDeserializationContext; +import com.google.gson.JsonDeserializer; +import com.google.gson.JsonElement; +import com.google.gson.JsonParseException; +import com.google.gson.JsonPrimitive; +import com.google.gson.JsonSerializationContext; +import com.google.gson.JsonSerializer; import java.lang.reflect.Type; import java.time.ZonedDateTime; From 680d175035e743bb236e6250d104c7919b4cbfd8 Mon Sep 17 00:00:00 2001 From: Jacob Peterson Date: Mon, 1 Mar 2021 13:14:45 -0700 Subject: [PATCH 094/271] Fix data v2 websocket bugs --- .../broker/client/AlpacaWebsocketClient.java | 14 ++++++++------ .../client/MarketDataWebsocketClient.java | 10 ++++++++-- 2 files changed, 16 insertions(+), 8 deletions(-) diff --git a/src/main/java/net/jacobpeterson/alpaca/websocket/broker/client/AlpacaWebsocketClient.java b/src/main/java/net/jacobpeterson/alpaca/websocket/broker/client/AlpacaWebsocketClient.java index 2a1e2fb7..c539b65d 100644 --- a/src/main/java/net/jacobpeterson/alpaca/websocket/broker/client/AlpacaWebsocketClient.java +++ b/src/main/java/net/jacobpeterson/alpaca/websocket/broker/client/AlpacaWebsocketClient.java @@ -1,11 +1,7 @@ package net.jacobpeterson.alpaca.websocket.broker.client; import com.google.common.base.Preconditions; -import com.google.gson.JsonArray; -import com.google.gson.JsonElement; -import com.google.gson.JsonObject; -import com.google.gson.JsonPrimitive; -import com.google.gson.JsonSyntaxException; +import com.google.gson.*; import net.jacobpeterson.abstracts.websocket.client.WebsocketClient; import net.jacobpeterson.abstracts.websocket.exception.WebsocketException; import net.jacobpeterson.alpaca.enums.api.EndpointAPIType; @@ -234,7 +230,13 @@ public void sendStreamMessageToListeners(AlpacaStreamMessageType alpacaStreamMes @Override public boolean isConnected() { - return alpacaWebsocketClientEndpoint.getUserSession().isOpen(); + if (alpacaWebsocketClientEndpoint == null) { + return false; + } else if (alpacaWebsocketClientEndpoint.getUserSession() != null) { + return alpacaWebsocketClientEndpoint.getUserSession().isOpen(); + } else { + return false; + } } @Override diff --git a/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/client/MarketDataWebsocketClient.java b/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/client/MarketDataWebsocketClient.java index f0dee71d..b89dee37 100644 --- a/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/client/MarketDataWebsocketClient.java +++ b/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/client/MarketDataWebsocketClient.java @@ -231,7 +231,7 @@ public void sendStreamMessageToListeners(MarketDataMessageType marketDataMessage } else if (marketDataMessage instanceof SymbolMessage) { SymbolMessage symbolMessage = (SymbolMessage) marketDataMessage; Set symbolMessageTypes = marketDataListener.getDataStreams() - .getOrDefault(symbolMessage.getMessageType(), null); + .getOrDefault(symbolMessage.getSymbol(), null); Set allMessageTypes = marketDataListener.getDataStreams() .getOrDefault(ALL_TICKERS, null); @@ -250,7 +250,13 @@ public void sendStreamMessageToListeners(MarketDataMessageType marketDataMessage @Override public boolean isConnected() { - return marketDataWebsocketClientEndpoint.getUserSession().isOpen(); + if (marketDataWebsocketClientEndpoint == null) { + return false; + } else if (marketDataWebsocketClientEndpoint.getUserSession() != null) { + return marketDataWebsocketClientEndpoint.getUserSession().isOpen(); + } else { + return false; + } } @Override From 124eeda6f0a205fd662d016a0a2b626ebbc3d4b9 Mon Sep 17 00:00:00 2001 From: Jacob Peterson Date: Mon, 1 Mar 2021 19:40:03 -0700 Subject: [PATCH 095/271] Fix market data websockets (un)subscribe logic --- build.gradle | 6 +-- .../broker/client/AlpacaWebsocketClient.java | 22 +++++----- .../client/MarketDataWebsocketClient.java | 44 ++++++++++++++----- 3 files changed, 46 insertions(+), 26 deletions(-) diff --git a/build.gradle b/build.gradle index 69db8f0e..2099a396 100644 --- a/build.gradle +++ b/build.gradle @@ -79,10 +79,8 @@ javadoc { options.addStringOption('Xdoclint:none', '-quiet') // Explicitly define Charset for Javadocs options.addStringOption("charset", "UTF-8") - - options { - links 'https://docs.oracle.com/javase/8/docs/api/' - } + // Add Java SE 8 link + options.addStringOption("link", "https://docs.oracle.com/javase/8/docs/api/") } test { diff --git a/src/main/java/net/jacobpeterson/alpaca/websocket/broker/client/AlpacaWebsocketClient.java b/src/main/java/net/jacobpeterson/alpaca/websocket/broker/client/AlpacaWebsocketClient.java index c539b65d..e900ad7f 100644 --- a/src/main/java/net/jacobpeterson/alpaca/websocket/broker/client/AlpacaWebsocketClient.java +++ b/src/main/java/net/jacobpeterson/alpaca/websocket/broker/client/AlpacaWebsocketClient.java @@ -1,7 +1,11 @@ package net.jacobpeterson.alpaca.websocket.broker.client; import com.google.common.base.Preconditions; -import com.google.gson.*; +import com.google.gson.JsonArray; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import com.google.gson.JsonPrimitive; +import com.google.gson.JsonSyntaxException; import net.jacobpeterson.abstracts.websocket.client.WebsocketClient; import net.jacobpeterson.abstracts.websocket.exception.WebsocketException; import net.jacobpeterson.alpaca.enums.api.EndpointAPIType; @@ -101,7 +105,7 @@ public void addListener(AlpacaStreamListener streamListener) throws WebsocketExc public void removeListener(AlpacaStreamListener streamListener) throws WebsocketException { listeners.remove(streamListener); - if (listeners.isEmpty()) { + if (listeners.isEmpty() && isConnected()) { try { disconnect(); } catch (Exception exception) { @@ -127,7 +131,9 @@ public void connect() throws URISyntaxException, IOException, DeploymentExceptio public void disconnect() throws Exception { LOGGER.info("Disconnecting..."); - alpacaWebsocketClientEndpoint.disconnect(); + if (alpacaWebsocketClientEndpoint != null) { + alpacaWebsocketClientEndpoint.disconnect(); + } LOGGER.info("Disconnected."); } @@ -230,13 +236,9 @@ public void sendStreamMessageToListeners(AlpacaStreamMessageType alpacaStreamMes @Override public boolean isConnected() { - if (alpacaWebsocketClientEndpoint == null) { - return false; - } else if (alpacaWebsocketClientEndpoint.getUserSession() != null) { - return alpacaWebsocketClientEndpoint.getUserSession().isOpen(); - } else { - return false; - } + return alpacaWebsocketClientEndpoint != null && + alpacaWebsocketClientEndpoint.getUserSession() != null && + alpacaWebsocketClientEndpoint.getUserSession().isOpen(); } @Override diff --git a/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/client/MarketDataWebsocketClient.java b/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/client/MarketDataWebsocketClient.java index b89dee37..54bda499 100644 --- a/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/client/MarketDataWebsocketClient.java +++ b/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/client/MarketDataWebsocketClient.java @@ -27,6 +27,7 @@ import java.net.URI; import java.net.URISyntaxException; import java.util.ArrayList; +import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Set; @@ -108,7 +109,7 @@ public void addListener(MarketDataListener streamListener) throws WebsocketExcep public void removeListener(MarketDataListener streamListener) throws WebsocketException { listeners.remove(streamListener); - if (listeners.isEmpty()) { + if (listeners.isEmpty() && isConnected()) { try { disconnect(); } catch (Exception exception) { @@ -134,7 +135,9 @@ public void connect() throws URISyntaxException, IOException, DeploymentExceptio public void disconnect() throws Exception { LOGGER.info("Disconnecting..."); - marketDataWebsocketClientEndpoint.disconnect(); + if (marketDataWebsocketClientEndpoint != null) { + marketDataWebsocketClientEndpoint.disconnect(); + } LOGGER.info("Disconnected."); } @@ -250,13 +253,9 @@ public void sendStreamMessageToListeners(MarketDataMessageType marketDataMessage @Override public boolean isConnected() { - if (marketDataWebsocketClientEndpoint == null) { - return false; - } else if (marketDataWebsocketClientEndpoint.getUserSession() != null) { - return marketDataWebsocketClientEndpoint.getUserSession().isOpen(); - } else { - return false; - } + return marketDataWebsocketClientEndpoint != null && + marketDataWebsocketClientEndpoint.getUserSession() != null && + marketDataWebsocketClientEndpoint.getUserSession().isOpen(); } @Override @@ -289,15 +288,36 @@ private void submitRequest(MarketDataListener marketDataListener, boolean unsubs JsonArray quotesJsonArray = new JsonArray(); JsonArray barsJsonArray = new JsonArray(); + Map> flattenedDataStreams = listeners.stream() + .filter(listener -> listener != marketDataListener) // Filter out listener being added + .flatMap(listener -> listener.getDataStreams().entrySet().stream()) // Flat map all data streams + .collect(HashMap::new, // Collect all data streams into one map + (cumulativeMap, listenerEntry) -> { + Set listenerMessageTypes = + cumulativeMap.getOrDefault(listenerEntry.getKey(), null); + if (listenerMessageTypes != null) { + cumulativeMap.get(listenerEntry.getKey()).addAll(listenerEntry.getValue()); + } else { + cumulativeMap.put(listenerEntry.getKey(), listenerEntry.getValue()); + } + }, + HashMap::putAll); + for (Map.Entry> listenerEntry : marketDataListener .getDataStreams().entrySet()) { - if (listenerEntry.getValue().contains(MarketDataMessageType.TRADE)) { + Set currentListenedTypes = flattenedDataStreams + .getOrDefault(listenerEntry.getKey(), null); + + if (listenerEntry.getValue().contains(MarketDataMessageType.TRADE) && + (currentListenedTypes == null || !currentListenedTypes.contains(MarketDataMessageType.TRADE))) { tradesJsonArray.add(listenerEntry.getKey()); } - if (listenerEntry.getValue().contains(MarketDataMessageType.QUOTE)) { + if (listenerEntry.getValue().contains(MarketDataMessageType.QUOTE) && + (currentListenedTypes == null || !currentListenedTypes.contains(MarketDataMessageType.QUOTE))) { quotesJsonArray.add(listenerEntry.getKey()); } - if (listenerEntry.getValue().contains(MarketDataMessageType.BAR)) { + if (listenerEntry.getValue().contains(MarketDataMessageType.BAR) && + (currentListenedTypes == null || !currentListenedTypes.contains(MarketDataMessageType.BAR))) { barsJsonArray.add(listenerEntry.getKey()); } } From c20970667f6e534151793fd5dd82c14ba1143996 Mon Sep 17 00:00:00 2001 From: Jacob Peterson Date: Tue, 2 Mar 2021 10:42:28 -0700 Subject: [PATCH 096/271] Update README and fix market data endpoints --- README.md | 159 ++++++++++-------- .../net/jacobpeterson/alpaca/AlpacaAPI.java | 12 +- .../jacobpeterson/alpaca/AlpacaConstants.java | 2 +- .../client/MarketDataWebsocketClient.java | 2 +- .../jacobpeterson/util/format/FormatUtil.java | 17 ++ 5 files changed, 116 insertions(+), 76 deletions(-) diff --git a/README.md b/README.md index febd5994..9904bb77 100644 --- a/README.md +++ b/README.md @@ -332,54 +332,6 @@ try { } ``` -### Bars -The bars API provides time-aggregated price and volume data from the IEX exchange. - -Example usage: -```java -try { - // Get 15min bars of AAPL and TSLA from 12/23/2020 at 9:30 AM to - // 12/24/2020 at 4 PM and print them out - Map> bars = alpacaAPI.getBars( - BarsTimeFrame.FIFTEEN_MINUTE, - new String[]{"AAPL", "TSLA"}, - null, - ZonedDateTime.of(2020, 12, 23, 9, 30, 0, 0, ZoneId.of("America/New_York")), - ZonedDateTime.of(2020, 12, 24, 12 + 4, 0, 0, 0, ZoneId.of("America/New_York")), - null, - null); - bars.entrySet().forEach(System.out::println); -} catch (AlpacaAPIRequestException e) { - e.printStackTrace(); -} -``` - -### LastTrade -The Last Trade API provides last trade details for a symbol. - -Example usage: -```java -try { - // Print out the last trade of AAPL - System.out.println(alpacaAPI.getLastTrade("AAPL")); -} catch (AlpacaAPIRequestException e) { - e.printStackTrace(); -} -``` - -### LastQuote -The Last Quote API provides last quote details for a symbol. - -Example usage: -```java -try { - // Print out the last quote of AAPL - System.out.println(alpacaAPI.getLastQuote("AAPL")); -} catch (AlpacaAPIRequestException e) { - e.printStackTrace(); -} -``` - ### Alpaca Streaming Alpaca offers WebSocket streaming for account and order updates. @@ -421,47 +373,118 @@ try { } ``` -### Alpaca Market Data Streaming +### Trades +The Trades API provides historical trade data for a given ticker symbol in a specified date range. + +Example usage: +```java +try { + // Get first 100 trades of AAPL of first minute on 3/1/2021 and print them out + TradesResponse appleTradesResponse = alpacaAPI.getTrades( + "AAPL", + ZonedDateTime.of(2021, 3, 1, 9, 30, 0, 0, ZoneId.of("America/New_York")), + ZonedDateTime.of(2021, 3, 1, 9, 31, 0, 0, ZoneId.of("America/New_York")), + 100, + null); + appleTradesResponse.getTrades().forEach(System.out::println); +} catch (AlpacaAPIRequestException e) { + e.printStackTrace(); +} +``` + +### Quotes +The Quotes API provides NBBO quotes for a given ticker symbol in a specified date range. + +Example usage: +```java +try { + // Get first 100 quotes of AAPL of first minute on 3/1/2021 and print them out + QuotesResponse appleQuotesResponse = alpacaAPI.getQuotes( + "AAPL", + ZonedDateTime.of(2021, 3, 1, 9, 30, 0, 0, ZoneId.of("America/New_York")), + ZonedDateTime.of(2021, 3, 1, 9, 31, 0, 0, ZoneId.of("America/New_York")), + 100, + null); + appleQuotesResponse.getQuotes().forEach(System.out::println); +} catch (AlpacaAPIRequestException e) { + e.printStackTrace(); +} +``` + +### Bars +The bars API returns aggregate historical data for the requested securities. + +Example usage: +```java +try { + // Get hour bars of AAPL from 2/22/2021 at 9:30 AM to 2/24/2021 at 4 PM and print them out + BarsResponse appleBarsResponse = alpacaAPI.getBars( + "AAPL", + ZonedDateTime.of(2021, 2, 22, 9, 30, 0, 0, ZoneId.of("America/New_York")), + ZonedDateTime.of(2021, 2, 24, 12 + 4, 0, 0, 0, ZoneId.of("America/New_York")), + null, + null, + BarsTimeFrame.HOUR); + appleBarsResponse.getBars().forEach(System.out::println); +} catch (AlpacaAPIRequestException e) { + e.printStackTrace(); +} +``` + +### Realtime Market Data Alpaca's Data API provides websocket streaming for trades, quotes and minute bars. This helps receive the most up to date market information that could help your trading strategy to act upon certain market movement. Example usage: ```java try { - // Listen to TSLA quotes, trades, and minute aggregates and print their messages out - MarketDataStreamListener streamListenerTSLA = new MarketDataStreamListenerAdapter( + // Listen to TSLA quotes, trades, and minute bars and print their messages out + MarketDataListener listenerTSLA = new MarketDataListenerAdapter( "TSLA", - MarketDataStreamMessageType.QUOTES, - MarketDataStreamMessageType.TRADES, - MarketDataStreamMessageType.AGGREGATE_MINUTE) { + MarketDataMessageType.TRADE, + MarketDataMessageType.QUOTE, + MarketDataMessageType.BAR) { @Override - public void onStreamUpdate(MarketDataStreamMessageType streamMessageType, - MarketDataStreamMessage streamMessage) { + public void onStreamUpdate(MarketDataMessageType streamMessageType, MarketDataMessage streamMessage) { switch (streamMessageType) { - case QUOTES: - System.out.println((QuoteMessage) streamMessage); + case TRADE: + TradeMessage tradeMessage = (TradeMessage) streamMessage; + System.out.printf("Trade: Price=%.2f Size=%d Time=%s\n", + tradeMessage.getPrice(), tradeMessage.getSize(), tradeMessage.getTimestamp()); break; - case TRADES: - System.out.println((TradeMessage) streamMessage); + case QUOTE: + QuoteMessage quoteMessage = (QuoteMessage) streamMessage; + System.out.printf("Quote: Ask Price=%.2f Ask Size=%d Bid Price=%.2f Bid Size=%d Time=%s\n", + quoteMessage.getAskPrice(), + quoteMessage.getAskSize(), + quoteMessage.getBidPrice(), + quoteMessage.getBidSize(), + quoteMessage.getTimestamp()); break; - case AGGREGATE_MINUTE: - System.out.println((AggregateMinuteMessage) streamMessage); + case BAR: + BarMessage barMessage = (BarMessage) streamMessage; + System.out.printf("Bar: O=%.2f H=%.2f L=%.2f C=%.2f Time=%s \n", + barMessage.getOpen(), + barMessage.getHigh(), + barMessage.getLow(), + barMessage.getClose(), + barMessage.getTimestamp()); break; } } }; - // Add the 'MarketDataStreamListener' - // Note that when the first 'MarketDataStreamListener' is added, the Websocket + // Add the 'MarketDataListener' + // Note that when the first 'MarketDataListener' is added, the Websocket // connection is created. - alpacaAPI.addMarketDataStreamListener(streamListenerTSLA); + alpacaAPI.addMarketDataStreamListener(listenerTSLA); // Wait for 5 seconds Thread.sleep(5000); - // Remove the 'MarketDataStreamListener' - // Note that when the last 'MarketDataStreamListener' is removed, the Websocket + // Remove the 'MarketDataListener' + // Note that when the last 'MarketDataListener' is removed, the Websocket // connection is closed. - alpacaAPI.removeMarketDataStreamListener(streamListenerTSLA); + alpacaAPI.removeMarketDataStreamListener(listenerTSLA); } catch (WebsocketException exception) { exception.printStackTrace(); } diff --git a/src/main/java/net/jacobpeterson/alpaca/AlpacaAPI.java b/src/main/java/net/jacobpeterson/alpaca/AlpacaAPI.java index dca2e4ef..2c267e31 100644 --- a/src/main/java/net/jacobpeterson/alpaca/AlpacaAPI.java +++ b/src/main/java/net/jacobpeterson/alpaca/AlpacaAPI.java @@ -1557,8 +1557,8 @@ public TradesResponse getTrades(String symbol, ZonedDateTime start, ZonedDateTim symbol, Endpoints.TRADES); - urlBuilder.appendURLParameter(Parameters.START, start.format(DateTimeFormatter.ISO_OFFSET_DATE_TIME)); - urlBuilder.appendURLParameter(Parameters.END, end.format(DateTimeFormatter.ISO_OFFSET_DATE_TIME)); + urlBuilder.appendURLParameter(Parameters.START, FormatUtil.toRFC3339Format(start)); + urlBuilder.appendURLParameter(Parameters.END, FormatUtil.toRFC3339Format(end)); if (limit != null) { urlBuilder.appendURLParameter(Parameters.LIMIT, limit.toString()); @@ -1605,8 +1605,8 @@ public QuotesResponse getQuotes(String symbol, ZonedDateTime start, ZonedDateTim symbol, Endpoints.QUOTES); - urlBuilder.appendURLParameter(Parameters.START, start.format(DateTimeFormatter.ISO_OFFSET_DATE_TIME)); - urlBuilder.appendURLParameter(Parameters.END, end.format(DateTimeFormatter.ISO_OFFSET_DATE_TIME)); + urlBuilder.appendURLParameter(Parameters.START, FormatUtil.toRFC3339Format(start)); + urlBuilder.appendURLParameter(Parameters.END, FormatUtil.toRFC3339Format(end)); if (limit != null) { urlBuilder.appendURLParameter(Parameters.LIMIT, limit.toString()); @@ -1655,8 +1655,8 @@ public BarsResponse getBars(String symbol, ZonedDateTime start, ZonedDateTime en symbol, Endpoints.BARS); - urlBuilder.appendURLParameter(Parameters.START, start.format(DateTimeFormatter.ISO_OFFSET_DATE_TIME)); - urlBuilder.appendURLParameter(Parameters.END, end.format(DateTimeFormatter.ISO_OFFSET_DATE_TIME)); + urlBuilder.appendURLParameter(Parameters.START, FormatUtil.toRFC3339Format(start)); + urlBuilder.appendURLParameter(Parameters.END, FormatUtil.toRFC3339Format(end)); urlBuilder.appendURLParameter(Parameters.TIMEFRAME, timeFrame.getAPIName()); if (limit != null) { diff --git a/src/main/java/net/jacobpeterson/alpaca/AlpacaConstants.java b/src/main/java/net/jacobpeterson/alpaca/AlpacaConstants.java index 388df19d..ae66ebbf 100644 --- a/src/main/java/net/jacobpeterson/alpaca/AlpacaConstants.java +++ b/src/main/java/net/jacobpeterson/alpaca/AlpacaConstants.java @@ -10,7 +10,7 @@ public class AlpacaConstants { */ public static class URLs { - public static final String DATA = "https://data.alpaca.markets/"; + public static final String DATA = "https://data.alpaca.markets"; } /** diff --git a/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/client/MarketDataWebsocketClient.java b/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/client/MarketDataWebsocketClient.java index 54bda499..33442ba5 100644 --- a/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/client/MarketDataWebsocketClient.java +++ b/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/client/MarketDataWebsocketClient.java @@ -296,7 +296,7 @@ private void submitRequest(MarketDataListener marketDataListener, boolean unsubs Set listenerMessageTypes = cumulativeMap.getOrDefault(listenerEntry.getKey(), null); if (listenerMessageTypes != null) { - cumulativeMap.get(listenerEntry.getKey()).addAll(listenerEntry.getValue()); + listenerMessageTypes.addAll(listenerEntry.getValue()); } else { cumulativeMap.put(listenerEntry.getKey(), listenerEntry.getValue()); } diff --git a/src/main/java/net/jacobpeterson/util/format/FormatUtil.java b/src/main/java/net/jacobpeterson/util/format/FormatUtil.java index e3e9d59b..02beef6e 100644 --- a/src/main/java/net/jacobpeterson/util/format/FormatUtil.java +++ b/src/main/java/net/jacobpeterson/util/format/FormatUtil.java @@ -2,6 +2,10 @@ import java.text.DecimalFormat; import java.text.NumberFormat; +import java.time.ZoneId; +import java.time.ZonedDateTime; +import java.time.format.DateTimeFormatter; +import java.time.temporal.TemporalAccessor; /** * {@link FormatUtil} is a utility class for various formatting. @@ -9,6 +13,8 @@ public class FormatUtil { private static final NumberFormat CURRENCY_FORMATTER = new DecimalFormat("#0.00"); + private static final DateTimeFormatter RFC_3339_FORMATTER = + DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss'Z'").withZone(ZoneId.of("America/New_York")); private static final long PREV_UNIX_EPOCH_NANO_TIME = System.currentTimeMillis() * 1000000L; private static final long PREV_UNIX_EPOCH_MICRO_TIME = PREV_UNIX_EPOCH_NANO_TIME / 1000L; @@ -23,6 +29,17 @@ public static String toCurrencyFormat(Number numberToFormat) { return CURRENCY_FORMATTER.format(numberToFormat); } + /** + * Formats a {@link TemporalAccessor} using {@link #RFC_3339_FORMATTER} {@link DateTimeFormatter}. + * + * @param zonedDateTime the {@link ZonedDateTime} + * + * @return the formatted string + */ + public static String toRFC3339Format(ZonedDateTime zonedDateTime) { + return RFC_3339_FORMATTER.format(zonedDateTime); + } + /** * Converts a given unix epoch time in nanoseconds or microseconds to milliseconds if necessary. * From 30b47aab492fc4daf61579055557a0f372629d56 Mon Sep 17 00:00:00 2001 From: Jacob Peterson Date: Tue, 2 Mar 2021 12:49:52 -0700 Subject: [PATCH 097/271] Update to 7.0 --- README.md | 14 ++++++++------ build.gradle | 2 +- .../alpaca/enums/api/EndpointAPIType.java | 4 ++-- 3 files changed, 11 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index 9904bb77..28b55b13 100644 --- a/README.md +++ b/README.md @@ -26,11 +26,11 @@ This is a Java implementation for the Alpaca net.jacobpeterson alpaca-java - 6.1 + 7.0 compile ``` @@ -61,6 +61,8 @@ Creating an `alpaca.properties` file on the classpath with the following format ``` key_id = secret = +endpoint_api_type = +data_api_type = ``` The default values for `alpaca.properties` can be found [here](https://github.com/Petersoj/alpaca-java/tree/master/src/main/resources). diff --git a/build.gradle b/build.gradle index 2099a396..53fb059a 100644 --- a/build.gradle +++ b/build.gradle @@ -30,7 +30,7 @@ tasks.getByName("generateJsonSchema2Pojo").enabled(false) // Disable default 'js archivesBaseName = 'alpaca-java' group = 'net.jacobpeterson' -version = '6.1' +version = '7.0' repositories { mavenLocal() diff --git a/src/main/java/net/jacobpeterson/alpaca/enums/api/EndpointAPIType.java b/src/main/java/net/jacobpeterson/alpaca/enums/api/EndpointAPIType.java index 3c9f1d49..d92f7753 100644 --- a/src/main/java/net/jacobpeterson/alpaca/enums/api/EndpointAPIType.java +++ b/src/main/java/net/jacobpeterson/alpaca/enums/api/EndpointAPIType.java @@ -6,10 +6,10 @@ public enum EndpointAPIType { /** The live {@link EndpointAPIType}. */ - LIVE("https://api.alpaca.markets/", "live"), + LIVE("https://api.alpaca.markets", "live"), /** The paper {@link EndpointAPIType}. */ - PAPER("https://paper-api.alpaca.markets/", "paper"); + PAPER("https://paper-api.alpaca.markets", "paper"); private final String url; private final String propertyName; From 32c0866b84da0f83eaab55fb93d83cc0f2fed9e8 Mon Sep 17 00:00:00 2001 From: Jacob Peterson Date: Thu, 4 Mar 2021 13:32:14 -0700 Subject: [PATCH 098/271] Add fractional and notional orders --- .../AbstractWebsocketClientEndpoint.java | 40 +++-- .../net/jacobpeterson/alpaca/AlpacaAPI.java | 165 ++++++++++++------ .../jacobpeterson/alpaca/AlpacaConstants.java | 1 + 3 files changed, 139 insertions(+), 67 deletions(-) diff --git a/src/main/java/net/jacobpeterson/abstracts/websocket/client/AbstractWebsocketClientEndpoint.java b/src/main/java/net/jacobpeterson/abstracts/websocket/client/AbstractWebsocketClientEndpoint.java index 38bdc619..3f0e9a16 100644 --- a/src/main/java/net/jacobpeterson/abstracts/websocket/client/AbstractWebsocketClientEndpoint.java +++ b/src/main/java/net/jacobpeterson/abstracts/websocket/client/AbstractWebsocketClientEndpoint.java @@ -19,8 +19,8 @@ * {@link AbstractWebsocketClientEndpoint} is used for handling a Websocket directly. *
* NOTES: You MUST annotate a subclass's {@link #onOpen(Session)}, {@link #onClose(Session, CloseReason)}, {@link - * #onMessage(String)}*, and {@link #onError(Throwable)} with {@link javax.websocket.OnOpen}, {@link - * javax.websocket.OnClose}*, {@link javax.websocket.OnMessage}, and {@link javax.websocket.OnError} respectively and + * #onMessage(String)}, and {@link #onError(Throwable)} with {@link javax.websocket.OnOpen}, {@link + * javax.websocket.OnClose}, {@link javax.websocket.OnMessage}, and {@link javax.websocket.OnError} respectively and * with the appropriate websocket sub-protocols because Websocket annotations don't work with inheritance. * * @param the {@link WebsocketClient} type parameter @@ -74,23 +74,16 @@ public void connect() throws DeploymentException, IOException { /** * Disconnects this Websocket. * - * @throws IOException throw for {@link IOException}s + * @throws Exception throw for {@link Exception}s */ public void disconnect() throws Exception { automaticallyReconnect = false; if (userSession != null) { userSession.close(); - - WebSocketContainer webSocketContainer = userSession.getContainer(); - if (webSocketContainer instanceof LifeCycle) { - ((LifeCycle) webSocketContainer).stop(); // Closes all websocket-related threads - } } - if (executorService != null) { - executorService.shutdown(); - } + cleanUp(); } /** @@ -127,11 +120,34 @@ protected void onClose(Session userSession, CloseReason reason) { LOGGER.error("Could not reconnect!", exception); } } else { - this.userSession = null; + try { + cleanUp(); + } catch (Exception exception) { + LOGGER.error("Could not clean up websocket client endpoint!", exception); + } + LOGGER.info("Websocket closed"); } } + /** + * Cleans up websocket-related threads. + * + * @throws Exception thrown for {@link Exception}s + */ + private void cleanUp() throws Exception { + if (userSession != null) { + WebSocketContainer webSocketContainer = userSession.getContainer(); + if (webSocketContainer instanceof LifeCycle) { + ((LifeCycle) webSocketContainer).stop(); // Closes all websocket-related threads + } + } + + if (executorService != null) { + executorService.shutdown(); + } + } + /** * Called when the Websocket message is received. * diff --git a/src/main/java/net/jacobpeterson/alpaca/AlpacaAPI.java b/src/main/java/net/jacobpeterson/alpaca/AlpacaAPI.java index 2c267e31..ff0c702d 100644 --- a/src/main/java/net/jacobpeterson/alpaca/AlpacaAPI.java +++ b/src/main/java/net/jacobpeterson/alpaca/AlpacaAPI.java @@ -399,7 +399,9 @@ public List getOrders(OrderStatus status, Integer limit, ZonedDateTime af * method can be set to null if they aren't required for your order type. * * @param symbol symbol or asset ID to identify the asset to trade - * @param quantity number of shares to trade + * @param quantity number of shares to trade. Can be fractional. + * @param notional dollar amount to trade. Cannot work with qty. Can only work for market order types + * and day for time in force. * @param side buy or sell * @param type market, limit, stop, or stop_limit * @param timeInForce day, gtc, opg, cls, ioc, fok. Please see Understand Orders for more info. @@ -424,26 +426,30 @@ public List getOrders(OrderStatus status, Integer limit, ZonedDateTime af * @throws AlpacaAPIRequestException thrown for {@link AlpacaAPIRequestException}s * @see
Orders */ - public Order requestNewOrder(String symbol, Integer quantity, OrderSide side, OrderType type, + public Order requestNewOrder(String symbol, Double quantity, Double notional, OrderSide side, OrderType type, OrderTimeInForce timeInForce, Double limitPrice, Double stopPrice, Double trailPrice, Double trailPercent, Boolean extendedHours, String clientOrderId, OrderClass orderClass, Double takeProfitLimitPrice, Double stopLossStopPrice, Double stopLossLimitPrice) throws AlpacaAPIRequestException { Preconditions.checkNotNull(symbol); - Preconditions.checkNotNull(quantity); + Preconditions.checkState(quantity != null ^ notional != null, "Either 'quantity' or 'notional' are required."); Preconditions.checkNotNull(side); Preconditions.checkNotNull(type); Preconditions.checkNotNull(timeInForce); - AlpacaRequestBuilder urlBuilder = new AlpacaRequestBuilder(apiURL, Endpoints.VERSION_2, - Endpoints.ORDERS); + AlpacaRequestBuilder urlBuilder = new AlpacaRequestBuilder(apiURL, Endpoints.VERSION_2, Endpoints.ORDERS); urlBuilder.appendJSONBodyProperty(Parameters.SYMBOL, symbol); - urlBuilder.appendJSONBodyProperty(Parameters.QTY, quantity.toString()); urlBuilder.appendJSONBodyProperty(Parameters.SIDE, side.getAPIName()); urlBuilder.appendJSONBodyProperty(Parameters.TYPE, type.getAPIName()); urlBuilder.appendJSONBodyProperty(Parameters.TIME_IN_FORCE, timeInForce.getAPIName()); + if (quantity != null) { + urlBuilder.appendJSONBodyProperty(Parameters.QTY, quantity.toString()); + } else { + urlBuilder.appendJSONBodyProperty(Parameters.NOTIONAL, notional.toString()); + } + if (limitPrice != null) { urlBuilder.appendJSONBodyProperty(Parameters.LIMIT_PRICE, FormatUtil.toCurrencyFormat(limitPrice)); @@ -509,7 +515,7 @@ public Order requestNewOrder(String symbol, Integer quantity, OrderSide side, Or /** * A market order is a request to buy or sell a security at the currently available market price. It provides the * most likely method of filling an order. Market orders fill nearly instantaneously. This method calls {@link - * #requestNewOrder(String, Integer, OrderSide, OrderType, OrderTimeInForce, Double, Double, Double, Double, + * #requestNewOrder(String, Double, Double, OrderSide, OrderType, OrderTimeInForce, Double, Double, Double, Double, * Boolean, String, OrderClass, Double, Double, Double)} with {@link OrderType#MARKET}. * * @param symbol symbol or asset ID to identify the asset to trade @@ -524,17 +530,61 @@ public Order requestNewOrder(String symbol, Integer quantity, OrderSide side, Or */ public Order requestNewMarketOrder(String symbol, Integer quantity, OrderSide side, OrderTimeInForce timeInForce) throws AlpacaAPIRequestException { - return requestNewOrder(symbol, quantity, side, OrderType.MARKET, timeInForce, null, null, null, null, null, - null, OrderClass.SIMPLE, null, null, null); + return requestNewOrder(symbol, quantity.doubleValue(), null, side, OrderType.MARKET, timeInForce, null, null, + null, null, null, null, OrderClass.SIMPLE, null, null, null); + } + + /** + * A market order is a request to buy or sell a security at the currently available market price. It provides the + * most likely method of filling an order. Market orders fill nearly instantaneously. This method calls {@link + * #requestNewOrder(String, Double, Double, OrderSide, OrderType, OrderTimeInForce, Double, Double, Double, Double, + * Boolean, String, OrderClass, Double, Double, Double)} with {@link OrderType#MARKET} and {@link + * OrderTimeInForce#DAY} with a fractional quantity. + * + * @param symbol symbol or asset ID to identify the asset to trade + * @param quantity number of shares to trade. Can be fractional. + * @param side buy or sell + * + * @return the order + * + * @throws AlpacaAPIRequestException thrown for {@link AlpacaAPIRequestException}s + * @see Order Types + */ + public Order requestNewFractionalMarketOrder(String symbol, Double quantity, OrderSide side) + throws AlpacaAPIRequestException { + return requestNewOrder(symbol, quantity, null, side, OrderType.MARKET, OrderTimeInForce.DAY, null, null, null, + null, null, null, null, null, null, null); + } + + /** + * A market order is a request to buy or sell a security at the currently available market price. It provides the + * most likely method of filling an order. Market orders fill nearly instantaneously. This method calls {@link + * #requestNewOrder(String, Double, Double, OrderSide, OrderType, OrderTimeInForce, Double, Double, Double, Double, + * Boolean, String, OrderClass, Double, Double, Double)} with {@link OrderType#MARKET} and {@link + * OrderTimeInForce#DAY} with a notional dollar amount. + * + * @param symbol symbol or asset ID to identify the asset to trade + * @param notional dollar amount to trade + * @param side buy or sell + * + * @return the order + * + * @throws AlpacaAPIRequestException thrown for {@link AlpacaAPIRequestException}s + * @see Order Types + */ + public Order requestNewNotionalMarketOrder(String symbol, Double notional, OrderSide side) + throws AlpacaAPIRequestException { + return requestNewOrder(symbol, null, notional, side, OrderType.MARKET, OrderTimeInForce.DAY, null, null, null, + null, null, null, null, null, null, null); } /** * A limit order is an order to buy or sell at a specified price or better. A buy limit order (a limit order to buy) * is executed at the specified limit price or lower (i.e., better). Conversely, a sell limit order (a limit order * to sell) is executed at the specified limit price or higher (better). Unlike a market order, you have to specify - * the limit price parameter when submitting your order. This method calls {@link #requestNewOrder(String, Integer, - * OrderSide, OrderType, OrderTimeInForce, Double, Double, Double, Double, Boolean, String, OrderClass, Double, - * Double, Double)} with {@link OrderType#LIMIT}. + * the limit price parameter when submitting your order. This method calls {@link #requestNewOrder(String, Double, + * Double, OrderSide, OrderType, OrderTimeInForce, Double, Double, Double, Double, Boolean, String, OrderClass, + * Double, Double, Double)} with {@link OrderType#LIMIT}. * * @param symbol symbol or asset ID to identify the asset to trade * @param quantity number of shares to trade @@ -551,8 +601,8 @@ public Order requestNewMarketOrder(String symbol, Integer quantity, OrderSide si */ public Order requestNewLimitOrder(String symbol, Integer quantity, OrderSide side, OrderTimeInForce timeInForce, Double limitPrice, Boolean extendedHours) throws AlpacaAPIRequestException { - return requestNewOrder(symbol, quantity, side, OrderType.LIMIT, timeInForce, limitPrice, null, null, null, - extendedHours, null, OrderClass.SIMPLE, null, null, null); + return requestNewOrder(symbol, quantity.doubleValue(), null, side, OrderType.LIMIT, timeInForce, limitPrice, + null, null, null, extendedHours, null, OrderClass.SIMPLE, null, null, null); } /** @@ -561,7 +611,7 @@ public Order requestNewLimitOrder(String symbol, Integer quantity, OrderSide sid * specified stop price, the stop order becomes a market order. Alpaca converts buy stop orders into stop limit * orders with a limit price that is 4% higher than a stop price < $50 (or 2.5% higher than a stop price >= * $50). Sell stop orders are not converted into stop limit orders. This method calls {@link - * #requestNewOrder(String, Integer, OrderSide, OrderType, OrderTimeInForce, Double, Double, Double, Double, + * #requestNewOrder(String, Double, Double, OrderSide, OrderType, OrderTimeInForce, Double, Double, Double, Double, * Boolean, String, OrderClass, Double, Double, Double)} with {@link OrderType#STOP}. * * @param symbol symbol or asset ID to identify the asset to trade @@ -579,8 +629,8 @@ public Order requestNewLimitOrder(String symbol, Integer quantity, OrderSide sid */ public Order requestNewStopOrder(String symbol, Integer quantity, OrderSide side, OrderTimeInForce timeInForce, Double stopPrice, Boolean extendedHours) throws AlpacaAPIRequestException { - return requestNewOrder(symbol, quantity, side, OrderType.STOP, timeInForce, null, stopPrice, null, null, - extendedHours, null, OrderClass.SIMPLE, null, null, null); + return requestNewOrder(symbol, quantity.doubleValue(), null, side, OrderType.STOP, timeInForce, null, stopPrice, + null, null, extendedHours, null, OrderClass.SIMPLE, null, null, null); } /** @@ -588,7 +638,7 @@ public Order requestNewStopOrder(String symbol, Integer quantity, OrderSide side * those of a limit order and is used to mitigate risk. The stop-limit order will be executed at a specified limit * price, or better, after a given stop price has been reached. Once the stop price is reached, the stop-limit order * becomes a limit order to buy or sell at the limit price or better. This method calls {@link - * #requestNewOrder(String, Integer, OrderSide, OrderType, OrderTimeInForce, Double, Double, Double, Double, + * #requestNewOrder(String, Double, Double, OrderSide, OrderType, OrderTimeInForce, Double, Double, Double, Double, * Boolean, String, OrderClass, Double, Double, Double)} with {@link OrderType#STOP_LIMIT}. * * @param symbol symbol or asset ID to identify the asset to trade @@ -607,14 +657,14 @@ public Order requestNewStopOrder(String symbol, Integer quantity, OrderSide side */ public Order requestNewStopLimitOrder(String symbol, Integer quantity, OrderSide side, OrderTimeInForce timeInForce, Double limitPrice, Double stopPrice, Boolean extendedHours) throws AlpacaAPIRequestException { - return requestNewOrder(symbol, quantity, side, OrderType.STOP_LIMIT, timeInForce, limitPrice, stopPrice, - null, null, extendedHours, null, OrderClass.SIMPLE, null, null, null); + return requestNewOrder(symbol, quantity.doubleValue(), null, side, OrderType.STOP_LIMIT, timeInForce, + limitPrice, stopPrice, null, null, extendedHours, null, OrderClass.SIMPLE, null, null, null); } /** * A bracket order is a chain of three orders that can be used to manage your position entry and exit. It is a * common use case of an OTOCO (One Triggers OCO {One Cancels Other}) order. This method calls {@link - * #requestNewOrder(String, Integer, OrderSide, OrderType, OrderTimeInForce, Double, Double, Double, Double, + * #requestNewOrder(String, Double, Double, OrderSide, OrderType, OrderTimeInForce, Double, Double, Double, Double, * Boolean, String, OrderClass, Double, Double, Double)} with {@link OrderType#MARKET} and with parameters for a * bracket order. * @@ -637,14 +687,15 @@ public Order requestNewStopLimitOrder(String symbol, Integer quantity, OrderSide public Order requestNewMarketBracketOrder(String symbol, Integer quantity, OrderSide side, OrderTimeInForce timeInForce, Double takeProfitLimitPrice, Double stopLossStopPrice, Double stopLossLimitPrice) throws AlpacaAPIRequestException { - return requestNewOrder(symbol, quantity, side, OrderType.MARKET, timeInForce, null, null, null, null, null, - null, OrderClass.BRACKET, takeProfitLimitPrice, stopLossStopPrice, stopLossLimitPrice); + return requestNewOrder(symbol, quantity.doubleValue(), null, side, OrderType.MARKET, timeInForce, null, null, + null, null, null, null, OrderClass.BRACKET, takeProfitLimitPrice, stopLossStopPrice, + stopLossLimitPrice); } /** * A bracket order is a chain of three orders that can be used to manage your position entry and exit. It is a * common use case of an OTOCO (One Triggers OCO {One Cancels Other}) order. This method calls {@link - * #requestNewOrder(String, Integer, OrderSide, OrderType, OrderTimeInForce, Double, Double, Double, Double, + * #requestNewOrder(String, Double, Double, OrderSide, OrderType, OrderTimeInForce, Double, Double, Double, Double, * Boolean, String, OrderClass, Double, Double, Double)} with {@link OrderType#LIMIT} and with parameters for a * bracket order. * @@ -670,15 +721,16 @@ public Order requestNewMarketBracketOrder(String symbol, Integer quantity, Order public Order requestNewLimitBracketOrder(String symbol, Integer quantity, OrderSide side, OrderTimeInForce timeInForce, Double limitPrice, Boolean extendedHours, Double takeProfitLimitPrice, Double stopLossStopPrice, Double stopLossLimitPrice) throws AlpacaAPIRequestException { - return requestNewOrder(symbol, quantity, side, OrderType.LIMIT, timeInForce, limitPrice, null, null, null, - extendedHours, null, OrderClass.BRACKET, takeProfitLimitPrice, stopLossStopPrice, stopLossLimitPrice); + return requestNewOrder(symbol, quantity.doubleValue(), null, side, OrderType.LIMIT, timeInForce, limitPrice, + null, null, null, extendedHours, null, OrderClass.BRACKET, takeProfitLimitPrice, stopLossStopPrice, + stopLossLimitPrice); } /** * OCO (One-Cancels-Other) is another type of advanced order type. This is a set of two orders with the same side * (buy/buy or sell/sell) and currently only exit order is supported. In other words, this is the second part of the * bracket orders where the entry order is already filled, and you can submit the take-profit and stop-loss in one - * order submission. This method calls {@link #requestNewOrder(String, Integer, OrderSide, OrderType, + * order submission. This method calls {@link #requestNewOrder(String, Double, Double, OrderSide, OrderType, * OrderTimeInForce, Double, Double, Double, Double, Boolean, String, OrderClass, Double, Double, Double)} with * parameters for an OCO order type. * @@ -701,15 +753,16 @@ public Order requestNewLimitBracketOrder(String symbol, Integer quantity, OrderS public Order requestNewOCOOrder(String symbol, Integer quantity, OrderSide side, OrderTimeInForce timeInForce, Boolean extendedHours, Double takeProfitLimitPrice, Double stopLossStopPrice, Double stopLossLimitPrice) throws AlpacaAPIRequestException { - return requestNewOrder(symbol, quantity, side, OrderType.LIMIT, timeInForce, null, null, null, null, - extendedHours, null, OrderClass.OCO, takeProfitLimitPrice, stopLossStopPrice, stopLossLimitPrice); + return requestNewOrder(symbol, quantity.doubleValue(), null, side, OrderType.LIMIT, timeInForce, null, null, + null, null, extendedHours, null, OrderClass.OCO, takeProfitLimitPrice, stopLossStopPrice, + stopLossLimitPrice); } /** * OTO (One-Triggers-Other) is a variant of bracket order. It takes one of the take-profit or stop-loss order in - * addition to the entry order. This method calls {@link #requestNewOrder(String, Integer, OrderSide, OrderType, - * OrderTimeInForce, Double, Double, Double, Double, Boolean, String, OrderClass, Double, Double, Double)} with - * {@link OrderType#LIMIT} and with parameters for an OTO order type. + * addition to the entry order. This method calls {@link #requestNewOrder(String, Double, Double, OrderSide, + * OrderType, OrderTimeInForce, Double, Double, Double, Double, Boolean, String, OrderClass, Double, Double, + * Double)} with {@link OrderType#LIMIT} and with parameters for an OTO order type. * * @param symbol symbol or asset ID to identify the asset to trade * @param quantity number of shares to trade @@ -728,15 +781,15 @@ public Order requestNewOCOOrder(String symbol, Integer quantity, OrderSide side, public Order requestNewOTOMarketOrder(String symbol, Integer quantity, OrderSide side, OrderTimeInForce timeInForce, Double takeProfitLimitPrice, Double stopLossStopPrice, Double stopLossLimitPrice) throws AlpacaAPIRequestException { - return requestNewOrder(symbol, quantity, side, OrderType.MARKET, timeInForce, null, null, null, null, null, - null, OrderClass.OTO, takeProfitLimitPrice, stopLossStopPrice, stopLossLimitPrice); + return requestNewOrder(symbol, quantity.doubleValue(), null, side, OrderType.MARKET, timeInForce, null, null, + null, null, null, null, OrderClass.OTO, takeProfitLimitPrice, stopLossStopPrice, stopLossLimitPrice); } /** * OTO (One-Triggers-Other) is a variant of bracket order. It takes one of the take-profit or stop-loss order in - * addition to the entry order. This method calls {@link #requestNewOrder(String, Integer, OrderSide, OrderType, - * OrderTimeInForce, Double, Double, Double, Double, Boolean, String, OrderClass, Double, Double, Double)} with - * {@link OrderType#LIMIT} and with parameters for an OTO order type. + * addition to the entry order. This method calls {@link #requestNewOrder(String, Double, Double, OrderSide, + * OrderType, OrderTimeInForce, Double, Double, Double, Double, Boolean, String, OrderClass, Double, Double, + * Double)} with {@link OrderType#LIMIT} and with parameters for an OTO order type. * * @param symbol symbol or asset ID to identify the asset to trade * @param quantity number of shares to trade @@ -758,15 +811,16 @@ public Order requestNewOTOMarketOrder(String symbol, Integer quantity, OrderSide public Order requestNewOTOLimitOrder(String symbol, Integer quantity, OrderSide side, OrderTimeInForce timeInForce, Double limitPrice, Boolean extendedHours, Double takeProfitLimitPrice, Double stopLossStopPrice, Double stopLossLimitPrice) throws AlpacaAPIRequestException { - return requestNewOrder(symbol, quantity, side, OrderType.LIMIT, timeInForce, limitPrice, null, null, null, - extendedHours, null, OrderClass.OTO, takeProfitLimitPrice, stopLossStopPrice, stopLossLimitPrice); + return requestNewOrder(symbol, quantity.doubleValue(), null, side, OrderType.LIMIT, timeInForce, limitPrice, + null, null, null, extendedHours, null, OrderClass.OTO, takeProfitLimitPrice, stopLossStopPrice, + stopLossLimitPrice); } /** * OTO (One-Triggers-Other) is a variant of bracket order. It takes one of the take-profit or stop-loss order in - * addition to the entry order. This method calls {@link #requestNewOrder(String, Integer, OrderSide, OrderType, - * OrderTimeInForce, Double, Double, Double, Double, Boolean, String, OrderClass, Double, Double, Double)} with - * {@link OrderType#STOP} and with parameters for an OTO order type. + * addition to the entry order. This method calls {@link #requestNewOrder(String, Double, Double, OrderSide, + * OrderType, OrderTimeInForce, Double, Double, Double, Double, Boolean, String, OrderClass, Double, Double, + * Double)} with {@link OrderType#STOP} and with parameters for an OTO order type. * * @param symbol symbol or asset ID to identify the asset to trade * @param quantity number of shares to trade @@ -788,15 +842,16 @@ public Order requestNewOTOLimitOrder(String symbol, Integer quantity, OrderSide public Order requestNewOTOStopOrder(String symbol, Integer quantity, OrderSide side, OrderTimeInForce timeInForce, Double stopPrice, Boolean extendedHours, Double takeProfitLimitPrice, Double stopLossStopPrice, Double stopLossLimitPrice) throws AlpacaAPIRequestException { - return requestNewOrder(symbol, quantity, side, OrderType.STOP, timeInForce, null, stopPrice, null, null, - extendedHours, null, OrderClass.OTO, takeProfitLimitPrice, stopLossStopPrice, stopLossLimitPrice); + return requestNewOrder(symbol, quantity.doubleValue(), null, side, OrderType.STOP, timeInForce, null, stopPrice, + null, null, extendedHours, null, OrderClass.OTO, takeProfitLimitPrice, stopLossStopPrice, + stopLossLimitPrice); } /** * OTO (One-Triggers-Other) is a variant of bracket order. It takes one of the take-profit or stop-loss order in - * addition to the entry order. This method calls {@link #requestNewOrder(String, Integer, OrderSide, OrderType, - * OrderTimeInForce, Double, Double, Double, Double, Boolean, String, OrderClass, Double, Double, Double)} with - * {@link OrderType#STOP_LIMIT} and with parameters for an OTO order type. + * addition to the entry order. This method calls {@link #requestNewOrder(String, Double, Double, OrderSide, + * OrderType, OrderTimeInForce, Double, Double, Double, Double, Boolean, String, OrderClass, Double, Double, + * Double)} with {@link OrderType#STOP_LIMIT} and with parameters for an OTO order type. * * @param symbol symbol or asset ID to identify the asset to trade * @param quantity number of shares to trade @@ -820,14 +875,14 @@ public Order requestNewOTOStopLimitOrder(String symbol, Integer quantity, OrderS OrderTimeInForce timeInForce, Double limitPrice, Double stopPrice, Boolean extendedHours, Double takeProfitLimitPrice, Double stopLossStopPrice, Double stopLossLimitPrice) throws AlpacaAPIRequestException { - return requestNewOrder(symbol, quantity, side, OrderType.STOP_LIMIT, timeInForce, limitPrice, stopPrice, - null, null, extendedHours, null, OrderClass.OTO, takeProfitLimitPrice, + return requestNewOrder(symbol, quantity.doubleValue(), null, side, OrderType.STOP_LIMIT, timeInForce, + limitPrice, stopPrice, null, null, extendedHours, null, OrderClass.OTO, takeProfitLimitPrice, stopLossStopPrice, stopLossLimitPrice); } /** * Trailing stop orders allow you to continuously and automatically keep updating the stop price threshold based on - * the stock price movement. This method calls {@link #requestNewOrder(String, Integer, OrderSide, OrderType, + * the stock price movement. This method calls {@link #requestNewOrder(String, Double, Double, OrderSide, OrderType, * OrderTimeInForce, Double, Double, Double, Double, Boolean, String, OrderClass, Double, Double, Double)} with * {@link OrderType#TRAILING_STOP} and with parameters for a Trailing Stop Price order type. * @@ -846,13 +901,13 @@ public Order requestNewOTOStopLimitOrder(String symbol, Integer quantity, OrderS */ public Order requestNewTrailingStopPriceOrder(String symbol, Integer quantity, OrderSide side, OrderTimeInForce timeInForce, Double trailPrice, Boolean extendedHours) throws AlpacaAPIRequestException { - return requestNewOrder(symbol, quantity, side, OrderType.TRAILING_STOP, timeInForce, null, null, - trailPrice, null, extendedHours, null, null, null, null, null); + return requestNewOrder(symbol, quantity.doubleValue(), null, side, OrderType.TRAILING_STOP, timeInForce, null, + null, trailPrice, null, extendedHours, null, null, null, null, null); } /** * Trailing stop orders allow you to continuously and automatically keep updating the stop price threshold based on - * the stock price movement. This method calls {@link #requestNewOrder(String, Integer, OrderSide, OrderType, + * the stock price movement. This method calls {@link #requestNewOrder(String, Double, Double, OrderSide, OrderType, * OrderTimeInForce, Double, Double, Double, Double, Boolean, String, OrderClass, Double, Double, Double)} with * {@link OrderType#TRAILING_STOP} and with parameters for a Trailing Stop Percent order type. * @@ -871,8 +926,8 @@ public Order requestNewTrailingStopPriceOrder(String symbol, Integer quantity, O */ public Order requestNewTrailingStopPercentOrder(String symbol, Integer quantity, OrderSide side, OrderTimeInForce timeInForce, Double trailPercent, Boolean extendedHours) throws AlpacaAPIRequestException { - return requestNewOrder(symbol, quantity, side, OrderType.TRAILING_STOP, timeInForce, null, null, - null, trailPercent, extendedHours, null, null, null, null, null); + return requestNewOrder(symbol, quantity.doubleValue(), null, side, OrderType.TRAILING_STOP, timeInForce, null, + null, null, trailPercent, extendedHours, null, null, null, null, null); } /** diff --git a/src/main/java/net/jacobpeterson/alpaca/AlpacaConstants.java b/src/main/java/net/jacobpeterson/alpaca/AlpacaConstants.java index ae66ebbf..c5fe2865 100644 --- a/src/main/java/net/jacobpeterson/alpaca/AlpacaConstants.java +++ b/src/main/java/net/jacobpeterson/alpaca/AlpacaConstants.java @@ -61,6 +61,7 @@ public static class Parameters { public static final String TYPE = "type"; public static final String SIDE = "side"; public static final String QTY = "qty"; + public static final String NOTIONAL = "notional"; public static final String SYMBOL = "symbol"; public static final String DIRECTION = "direction"; public static final String NESTED = "nested"; From 904a6908c97926af3d23373b01433b384d6a53c6 Mon Sep 17 00:00:00 2001 From: Jacob Peterson Date: Tue, 16 Mar 2021 19:47:28 -0600 Subject: [PATCH 099/271] Add 'fractionable' field to Asset schema --- schema_json/alpaca/asset/asset.json | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/schema_json/alpaca/asset/asset.json b/schema_json/alpaca/asset/asset.json index c1e4b05d..4742fa29 100644 --- a/schema_json/alpaca/asset/asset.json +++ b/schema_json/alpaca/asset/asset.json @@ -37,6 +37,10 @@ "easy_to_borrow": { "existingJavaType": "java.lang.Boolean", "title": "Asset is easy-to-borrow or not (filtering for easy_to_borrow = True is the best way to check whether the name is currently available to short at Alpaca)." + }, + "fractionable": { + "existingJavaType": "java.lang.Boolean", + "title": "Asset is fractionable or not." } } } From fb42b72c0bd3833b29d478c0e92b932ca1984a52 Mon Sep 17 00:00:00 2001 From: Jacob Peterson Date: Fri, 26 Mar 2021 12:06:16 -0600 Subject: [PATCH 100/271] Pass UnirestExceptions through AlpacaAPIRequestException --- .../abstracts/rest/AbstractRequest.java | 80 ++---- .../AbstractAPIRequestException.java | 77 ++--- .../net/jacobpeterson/alpaca/AlpacaAPI.java | 272 ++++-------------- .../alpaca/rest/AlpacaRequest.java | 181 ++++++++++++ .../exception/AlpacaAPIRequestException.java | 41 ++- 5 files changed, 340 insertions(+), 311 deletions(-) diff --git a/src/main/java/net/jacobpeterson/abstracts/rest/AbstractRequest.java b/src/main/java/net/jacobpeterson/abstracts/rest/AbstractRequest.java index 3bad064d..96c23a46 100644 --- a/src/main/java/net/jacobpeterson/abstracts/rest/AbstractRequest.java +++ b/src/main/java/net/jacobpeterson/abstracts/rest/AbstractRequest.java @@ -40,8 +40,10 @@ public AbstractRequest() { * @param abstractRequestBuilder the {@link AbstractRequestBuilder} * * @return the {@link HttpResponse} + * + * @throws UnirestException thrown for {@link UnirestException}s */ - public HttpResponse invokeGet(AbstractRequestBuilder abstractRequestBuilder) { + public HttpResponse invokeGet(AbstractRequestBuilder abstractRequestBuilder) throws UnirestException { String url = abstractRequestBuilder.buildURL(); LOGGER.debug("GET URL {}", url); @@ -54,12 +56,7 @@ public HttpResponse invokeGet(AbstractRequestBuilder abstractReques LOGGER.debug("GET Headers: {}", headers); } - try { - return request.asBinary(); - } catch (UnirestException exception) { - LOGGER.error("Could not get request as binary!", exception); - return null; - } + return request.asBinary(); } /** @@ -68,8 +65,10 @@ public HttpResponse invokeGet(AbstractRequestBuilder abstractReques * @param abstractRequestBuilder the {@link AbstractRequestBuilder} * * @return the {@link HttpResponse} + * + * @throws UnirestException thrown for {@link UnirestException}s */ - public HttpResponse invokeHead(AbstractRequestBuilder abstractRequestBuilder) { + public HttpResponse invokeHead(AbstractRequestBuilder abstractRequestBuilder) throws UnirestException { String url = abstractRequestBuilder.buildURL(); LOGGER.debug("HEAD URL {}", url); @@ -82,12 +81,7 @@ public HttpResponse invokeHead(AbstractRequestBuilder abstractReque LOGGER.debug("HEAD Headers: {}", headers); } - try { - return request.asBinary(); - } catch (UnirestException exception) { - LOGGER.error("Could not get request as binary!", exception); - return null; - } + return request.asBinary(); } /** @@ -96,8 +90,10 @@ public HttpResponse invokeHead(AbstractRequestBuilder abstractReque * @param abstractRequestBuilder the {@link AbstractRequestBuilder} * * @return the {@link HttpResponse} + * + * @throws UnirestException thrown for {@link UnirestException}s */ - public HttpResponse invokePost(AbstractRequestBuilder abstractRequestBuilder) { + public HttpResponse invokePost(AbstractRequestBuilder abstractRequestBuilder) throws UnirestException { String url = abstractRequestBuilder.buildURL(); LOGGER.debug("POST URL: {}", url); @@ -117,12 +113,7 @@ public HttpResponse invokePost(AbstractRequestBuilder abstractReque LOGGER.debug("POST Body: {}", body); } - try { - return request.asBinary(); - } catch (UnirestException exception) { - LOGGER.error("Could not get request as binary!", exception); - return null; - } + return request.asBinary(); } /** @@ -131,8 +122,11 @@ public HttpResponse invokePost(AbstractRequestBuilder abstractReque * @param abstractRequestBuilder the {@link AbstractRequestBuilder} * * @return the {@link HttpResponse} + * + * @throws UnirestException thrown for {@link UnirestException}s */ - public HttpResponse invokePatch(AbstractRequestBuilder abstractRequestBuilder) { + public HttpResponse invokePatch(AbstractRequestBuilder abstractRequestBuilder) + throws UnirestException { String url = abstractRequestBuilder.buildURL(); LOGGER.debug("PATCH URL {}", url); @@ -152,12 +146,7 @@ public HttpResponse invokePatch(AbstractRequestBuilder abstractRequ LOGGER.debug("PATCH Body: {}", body); } - try { - return request.asBinary(); - } catch (UnirestException exception) { - LOGGER.error("Could not get request as binary!", exception); - return null; - } + return request.asBinary(); } /** @@ -166,8 +155,10 @@ public HttpResponse invokePatch(AbstractRequestBuilder abstractRequ * @param abstractRequestBuilder the {@link AbstractRequestBuilder} * * @return the {@link HttpResponse} + * + * @throws UnirestException thrown for {@link UnirestException}s */ - public HttpResponse invokePut(AbstractRequestBuilder abstractRequestBuilder) { + public HttpResponse invokePut(AbstractRequestBuilder abstractRequestBuilder) throws UnirestException { String url = abstractRequestBuilder.buildURL(); LOGGER.debug("PUT URL {}", url); @@ -187,12 +178,7 @@ public HttpResponse invokePut(AbstractRequestBuilder abstractReques LOGGER.debug("PUT Body: " + body); } - try { - return request.asBinary(); - } catch (UnirestException exception) { - LOGGER.error("Could not get request as binary!", exception); - return null; - } + return request.asBinary(); } /** @@ -201,8 +187,11 @@ public HttpResponse invokePut(AbstractRequestBuilder abstractReques * @param abstractRequestBuilder the {@link AbstractRequestBuilder} * * @return the {@link HttpResponse} + * + * @throws UnirestException thrown for {@link UnirestException}s */ - public HttpResponse invokeDelete(AbstractRequestBuilder abstractRequestBuilder) { + public HttpResponse invokeDelete(AbstractRequestBuilder abstractRequestBuilder) + throws UnirestException { String url = abstractRequestBuilder.buildURL(); LOGGER.debug("DELETE URL {}", url); @@ -222,12 +211,7 @@ public HttpResponse invokeDelete(AbstractRequestBuilder abstractReq LOGGER.debug("DELETE Body: {}", body); } - try { - return request.asBinary(); - } catch (UnirestException exception) { - LOGGER.error("Could not get request as binary!", exception); - return null; - } + return request.asBinary(); } /** @@ -236,8 +220,11 @@ public HttpResponse invokeDelete(AbstractRequestBuilder abstractReq * @param abstractRequestBuilder the {@link AbstractRequestBuilder} * * @return the {@link HttpResponse} + * + * @throws UnirestException thrown for {@link UnirestException}s */ - public HttpResponse invokeOptions(AbstractRequestBuilder abstractRequestBuilder) { + public HttpResponse invokeOptions(AbstractRequestBuilder abstractRequestBuilder) + throws UnirestException { String url = abstractRequestBuilder.buildURL(); LOGGER.debug("OPTIONS URL {}", url); @@ -257,12 +244,7 @@ public HttpResponse invokeOptions(AbstractRequestBuilder abstractRe LOGGER.debug("OPTIONS Body: {}", body); } - try { - return request.asBinary(); - } catch (UnirestException exception) { - LOGGER.error("Could not get request as binary!", exception); - return null; - } + return request.asBinary(); } /** diff --git a/src/main/java/net/jacobpeterson/abstracts/rest/exception/AbstractAPIRequestException.java b/src/main/java/net/jacobpeterson/abstracts/rest/exception/AbstractAPIRequestException.java index 2fd621b1..dde2d55c 100644 --- a/src/main/java/net/jacobpeterson/abstracts/rest/exception/AbstractAPIRequestException.java +++ b/src/main/java/net/jacobpeterson/abstracts/rest/exception/AbstractAPIRequestException.java @@ -1,12 +1,8 @@ package net.jacobpeterson.abstracts.rest.exception; -import com.google.gson.JsonElement; -import com.google.gson.JsonObject; import com.mashape.unirest.http.HttpResponse; -import net.jacobpeterson.util.gson.GsonUtil; import java.io.InputStream; -import java.io.InputStreamReader; /** * {@link AbstractAPIRequestException} represents {@link HttpResponse} request exceptions. @@ -16,13 +12,22 @@ public abstract class AbstractAPIRequestException extends Exception { protected static final String CODE_KEY = "code"; protected static final String MESSAGE_KEY = "message"; - protected final String apiName; + protected String apiName; protected HttpResponse httpResponse; - protected int requestStatusCode; + protected Integer requestStatusCode; protected String requestStatusText; protected Integer apiResponseCode; protected String apiResponseMessage; + /** + * Instantiates a new {@link AbstractAPIRequestException}. + * + * @param cause the cause + */ + public AbstractAPIRequestException(Exception cause) { + super(cause); + } + /** * Instantiates a new {@link AbstractAPIRequestException}. * @@ -47,56 +52,30 @@ public AbstractAPIRequestException(String apiName, HttpResponse htt */ protected abstract void parseAPIExceptionMessage(); - /** - * Parse a standard API exception response of the following format: - *

-     *  {
-     *     "code": 40010001,
-     *     "message": "Error message"
-     *  }
-     * 
- */ - protected void parseStandardAPIExceptionResponse() { - JsonElement responseJsonElement = GsonUtil.JSON_PARSER.parse(new InputStreamReader(httpResponse.getBody())); - - if (responseJsonElement instanceof JsonObject) { - JsonObject responseJsonObject = (JsonObject) responseJsonElement; - - if (responseJsonObject.has(CODE_KEY)) { - apiResponseCode = responseJsonObject.get(CODE_KEY).getAsInt(); - } - - if (responseJsonObject.has(MESSAGE_KEY)) { - apiResponseMessage = responseJsonObject.get(MESSAGE_KEY).getAsString(); - } - } - - // Just use the response JSON if the message could not be parsed. - if (apiResponseMessage == null) { - apiResponseMessage = responseJsonElement.toString(); - } - } - /** * {@inheritDoc} This will call {@link #parseAPIExceptionMessage()} and return a formatted message. */ @Override public String getMessage() { - this.parseAPIExceptionMessage(); + if (getCause() == null) { + this.parseAPIExceptionMessage(); - StringBuilder messageBuilder = new StringBuilder(apiName).append(" API Request Exception! : "); - messageBuilder.append("Status Code = ").append(requestStatusCode); - messageBuilder.append(", Status Text = \"").append(requestStatusText).append("\""); + StringBuilder messageBuilder = new StringBuilder(apiName).append(" API Request Exception! : "); + messageBuilder.append("Status Code = ").append(requestStatusCode); + messageBuilder.append(", Status Text = \"").append(requestStatusText).append("\""); - if (apiResponseCode != null) { - messageBuilder.append(", API Response Code = ").append(apiResponseCode); - } + if (apiResponseCode != null) { + messageBuilder.append(", API Response Code = ").append(apiResponseCode); + } - if (apiResponseMessage != null) { - messageBuilder.append(", API Response Message = \"").append(apiResponseMessage).append("\""); - } + if (apiResponseMessage != null) { + messageBuilder.append(", API Response Message = \"").append(apiResponseMessage).append("\""); + } - return messageBuilder.toString(); + return messageBuilder.toString(); + } else { + return super.getMessage(); + } } /** @@ -104,7 +83,7 @@ public String getMessage() { * * @return the request status code */ - public int getRequestStatusCode() { + public Integer getRequestStatusCode() { return requestStatusCode; } @@ -122,7 +101,7 @@ public String getRequestStatusText() { * * @return the API response code */ - public int getAPIResponseCode() { + public Integer getAPIResponseCode() { return apiResponseCode; } diff --git a/src/main/java/net/jacobpeterson/alpaca/AlpacaAPI.java b/src/main/java/net/jacobpeterson/alpaca/AlpacaAPI.java index ff0c702d..365ca45a 100644 --- a/src/main/java/net/jacobpeterson/alpaca/AlpacaAPI.java +++ b/src/main/java/net/jacobpeterson/alpaca/AlpacaAPI.java @@ -7,6 +7,7 @@ import com.google.gson.JsonPrimitive; import com.google.gson.reflect.TypeToken; import com.mashape.unirest.http.HttpResponse; +import com.mashape.unirest.http.exceptions.UnirestException; import net.jacobpeterson.abstracts.enums.SortDirection; import net.jacobpeterson.abstracts.websocket.exception.WebsocketException; import net.jacobpeterson.alpaca.AlpacaConstants.Endpoints; @@ -174,13 +175,7 @@ public Account getAccount() throws AlpacaAPIRequestException { AlpacaRequestBuilder urlBuilder = new AlpacaRequestBuilder(apiURL, Endpoints.VERSION_2, Endpoints.ACCOUNT); - HttpResponse response = alpacaRequest.invokeGet(urlBuilder); - - if (response.getStatus() != 200) { - throw new AlpacaAPIRequestException(response); - } - - return alpacaRequest.getResponseObject(response, Account.class); + return alpacaRequest.get(urlBuilder, Account.class); } /** @@ -245,7 +240,12 @@ public List getAccountActivities(ZonedDateTime date, ZonedDateT urlBuilder.appendURLParameter(Parameters.PAGE_TOKEN, pageToken); } - HttpResponse response = alpacaRequest.invokeGet(urlBuilder); + HttpResponse response; + try { + response = alpacaRequest.invokeGet(urlBuilder); + } catch (UnirestException exception) { + throw new AlpacaAPIRequestException(exception); + } if (response.getStatus() != 200) { throw new AlpacaAPIRequestException(response); @@ -292,13 +292,7 @@ public AccountConfiguration getAccountConfiguration() throws AlpacaAPIRequestExc Endpoints.ACCOUNT, Endpoints.CONFIGURATIONS); - HttpResponse response = alpacaRequest.invokeGet(urlBuilder); - - if (response.getStatus() != 200) { - throw new AlpacaAPIRequestException(response); - } - - return alpacaRequest.getResponseObject(response, AccountConfiguration.class); + return alpacaRequest.get(urlBuilder, AccountConfiguration.class); } /** @@ -321,13 +315,7 @@ public AccountConfiguration setAccountConfiguration(AccountConfiguration account Endpoints.CONFIGURATIONS); urlBuilder.setCustomBody(GsonUtil.GSON.toJson(accountConfiguration)); - HttpResponse response = alpacaRequest.invokePatch(urlBuilder); - - if (response.getStatus() != 200) { - throw new AlpacaAPIRequestException(response); - } - - return alpacaRequest.getResponseObject(response, AccountConfiguration.class); + return alpacaRequest.patch(urlBuilder, AccountConfiguration.class); } /** @@ -382,15 +370,7 @@ public List getOrders(OrderStatus status, Integer limit, ZonedDateTime af urlBuilder.appendURLParameter(Parameters.SYMBOLS, String.join(",", symbols)); } - HttpResponse response = alpacaRequest.invokeGet(urlBuilder); - - if (response.getStatus() != 200) { - throw new AlpacaAPIRequestException(response); - } - - Type arrayListType = new TypeToken>() {}.getType(); - - return alpacaRequest.getResponseObject(response, arrayListType); + return alpacaRequest.get(urlBuilder, new TypeToken>() {}.getType()); } /** @@ -503,13 +483,7 @@ public Order requestNewOrder(String symbol, Double quantity, Double notional, Or urlBuilder.appendJSONBodyJSONProperty(Parameters.STOP_LOSS, stopLoss); } - HttpResponse response = alpacaRequest.invokePost(urlBuilder); - - if (response.getStatus() != 200) { - throw new AlpacaAPIRequestException(response); - } - - return alpacaRequest.getResponseObject(response, Order.class); + return alpacaRequest.post(urlBuilder, Order.class); } /** @@ -952,13 +926,7 @@ public Order getOrder(String orderID, Boolean nested) throws AlpacaAPIRequestExc urlBuilder.appendURLParameter(Parameters.NESTED, nested.toString()); } - HttpResponse response = alpacaRequest.invokeGet(urlBuilder); - - if (response.getStatus() != 200) { - throw new AlpacaAPIRequestException(response); - } - - return alpacaRequest.getResponseObject(response, Order.class); + return alpacaRequest.get(urlBuilder, Order.class); } /** @@ -978,13 +946,7 @@ public Order getOrderByClientID(String clientOrderId) throws AlpacaAPIRequestExc Endpoints.ORDERS_BY_CLIENT_ORDER_ID); urlBuilder.appendURLParameter(Parameters.CLIENT_ORDER_ID, clientOrderId); - HttpResponse response = alpacaRequest.invokeGet(urlBuilder); - - if (response.getStatus() != 200) { - throw new AlpacaAPIRequestException(response); - } - - return alpacaRequest.getResponseObject(response, Order.class); + return alpacaRequest.get(urlBuilder, Order.class); } /** @@ -1038,14 +1000,7 @@ public Order replaceOrder(String orderID, Integer quantity, OrderTimeInForce tim if (clientOrderId != null) { urlBuilder.appendJSONBodyProperty(Parameters.CLIENT_ORDER_ID, clientOrderId); } - - HttpResponse response = alpacaRequest.invokePatch(urlBuilder); - - if (response.getStatus() != 200) { - throw new AlpacaAPIRequestException(response); - } - - return alpacaRequest.getResponseObject(response, Order.class); + return alpacaRequest.patch(urlBuilder, Order.class); } /** @@ -1061,15 +1016,17 @@ public ArrayList cancelAllOrders() throws AlpacaAPIRequestExcept AlpacaRequestBuilder urlBuilder = new AlpacaRequestBuilder(apiURL, Endpoints.VERSION_2, Endpoints.ORDERS); - HttpResponse response = alpacaRequest.invokeDelete(urlBuilder); + try { + HttpResponse response = alpacaRequest.invokeDelete(urlBuilder); - if (response.getStatus() != 207) { // This returns a 207 multi-status message - throw new AlpacaAPIRequestException(response); - } - - Type arrayListType = new TypeToken>() {}.getType(); + if (response.getStatus() != 207) { // This returns a 207 multi-status message + throw new AlpacaAPIRequestException(response); + } - return alpacaRequest.getResponseObject(response, arrayListType); + return alpacaRequest.getResponseObject(response, new TypeToken>() {}.getType()); + } catch (UnirestException exception) { + throw new AlpacaAPIRequestException(exception); + } } /** @@ -1090,13 +1047,17 @@ public boolean cancelOrder(String orderId) throws AlpacaAPIRequestException { Endpoints.ORDERS, orderId); - HttpResponse response = alpacaRequest.invokeDelete(urlBuilder); + try { + HttpResponse response = alpacaRequest.invokeDelete(urlBuilder); - if ((response.getStatus() != 200 && response.getStatus() != 204)) { - throw new AlpacaAPIRequestException(response); - } + if (!(response.getStatus() == 200 || response.getStatus() == 204)) { + throw new AlpacaAPIRequestException(response); + } - return response.getStatus() == 200 || response.getStatus() == 204; + return true; + } catch (UnirestException exception) { + throw new AlpacaAPIRequestException(exception); + } } /** @@ -1111,15 +1072,7 @@ public ArrayList getOpenPositions() throws AlpacaAPIRequestException { AlpacaRequestBuilder urlBuilder = new AlpacaRequestBuilder(apiURL, Endpoints.VERSION_2, Endpoints.POSITIONS); - HttpResponse response = alpacaRequest.invokeGet(urlBuilder); - - if (response.getStatus() != 200) { - throw new AlpacaAPIRequestException(response); - } - - Type arrayListType = new TypeToken>() {}.getType(); - - return alpacaRequest.getResponseObject(response, arrayListType); + return alpacaRequest.get(urlBuilder, new TypeToken>() {}.getType()); } /** @@ -1139,13 +1092,7 @@ public Position getOpenPositionBySymbol(String symbol) throws AlpacaAPIRequestEx Endpoints.POSITIONS, symbol.trim()); - HttpResponse response = alpacaRequest.invokeGet(urlBuilder); - - if (response.getStatus() != 200) { - throw new AlpacaAPIRequestException(response); - } - - return alpacaRequest.getResponseObject(response, Position.class); + return alpacaRequest.get(urlBuilder, Position.class); } /** @@ -1162,15 +1109,7 @@ public ArrayList closeAllPositions() throws AlpacaAPIRequest AlpacaRequestBuilder urlBuilder = new AlpacaRequestBuilder(apiURL, Endpoints.VERSION_2, Endpoints.POSITIONS); - HttpResponse response = alpacaRequest.invokeDelete(urlBuilder); - - if (response.getStatus() != 207) { // This returns a 207 multi-status message - throw new AlpacaAPIRequestException(response); - } - - Type arrayListType = new TypeToken>() {}.getType(); - - return alpacaRequest.getResponseObject(response, arrayListType); + return alpacaRequest.delete(urlBuilder, new TypeToken>() {}.getType()); } /** @@ -1190,13 +1129,7 @@ public Order closePosition(String symbol) throws AlpacaAPIRequestException { Endpoints.POSITIONS, symbol); - HttpResponse response = alpacaRequest.invokeDelete(urlBuilder); - - if (response.getStatus() != 200) { - throw new AlpacaAPIRequestException(response); - } - - return alpacaRequest.getResponseObject(response, Order.class); + return alpacaRequest.delete(urlBuilder, Order.class); } /** @@ -1222,15 +1155,7 @@ public ArrayList getAssets(AssetStatus assetStatus, String assetClass) th urlBuilder.appendURLParameter(Parameters.ASSET_CLASS, assetClass.trim()); } - HttpResponse response = alpacaRequest.invokeGet(urlBuilder); - - if (response.getStatus() != 200) { - throw new AlpacaAPIRequestException(response); - } - - Type arrayListType = new TypeToken>() {}.getType(); - - return alpacaRequest.getResponseObject(response, arrayListType); + return alpacaRequest.get(urlBuilder, new TypeToken>() {}.getType()); } /** @@ -1250,13 +1175,7 @@ public Asset getAssetBySymbol(String symbolOrAssetID) throws AlpacaAPIRequestExc Endpoints.ASSETS, symbolOrAssetID.trim()); - HttpResponse response = alpacaRequest.invokeGet(urlBuilder); - - if (response.getStatus() != 200) { - throw new AlpacaAPIRequestException(response); - } - - return alpacaRequest.getResponseObject(response, Asset.class); + return alpacaRequest.get(urlBuilder, Asset.class); } /** @@ -1271,14 +1190,7 @@ public ArrayList getWatchlists() throws AlpacaAPIRequestException { AlpacaRequestBuilder urlBuilder = new AlpacaRequestBuilder(apiURL, Endpoints.VERSION_2, Endpoints.WATCHLISTS); - HttpResponse response = alpacaRequest.invokeGet(urlBuilder); - - if (response.getStatus() != 200) { - throw new AlpacaAPIRequestException(response); - } - - Type arrayListType = new TypeToken>() {}.getType(); - return alpacaRequest.getResponseObject(response, arrayListType); + return alpacaRequest.get(urlBuilder, new TypeToken>() {}.getType()); } /** @@ -1308,13 +1220,7 @@ public Watchlist createWatchlist(String name, String... symbols) throws AlpacaAP urlBuilder.appendJSONBodyJSONProperty("symbols", symbolsArray); } - HttpResponse response = alpacaRequest.invokePost(urlBuilder); - - if (response.getStatus() != 200) { - throw new AlpacaAPIRequestException(response); - } - - return alpacaRequest.getResponseObject(response, Watchlist.class); + return alpacaRequest.post(urlBuilder, Watchlist.class); } /** @@ -1334,13 +1240,7 @@ public Watchlist getWatchlist(String watchlistID) throws AlpacaAPIRequestExcepti Endpoints.WATCHLISTS, watchlistID); - HttpResponse response = alpacaRequest.invokeGet(urlBuilder); - - if (response.getStatus() != 200) { - throw new AlpacaAPIRequestException(response); - } - - return alpacaRequest.getResponseObject(response, Watchlist.class); + return alpacaRequest.get(urlBuilder, Watchlist.class); } /** @@ -1374,13 +1274,7 @@ public Watchlist updateWatchlist(String watchlistID, String name, String... symb urlBuilder.appendJSONBodyJSONProperty("symbols", symbolsArray); } - HttpResponse response = alpacaRequest.invokePut(urlBuilder); - - if (response.getStatus() != 200) { - throw new AlpacaAPIRequestException(response); - } - - return alpacaRequest.getResponseObject(response, Watchlist.class); + return alpacaRequest.put(urlBuilder, Watchlist.class); } /** @@ -1404,13 +1298,7 @@ public Watchlist addWatchlistAsset(String watchlistID, String symbol) throws Alp urlBuilder.appendJSONBodyProperty("symbol", symbol); - HttpResponse response = alpacaRequest.invokePost(urlBuilder); - - if (response.getStatus() != 200) { - throw new AlpacaAPIRequestException(response); - } - - return alpacaRequest.getResponseObject(response, Watchlist.class); + return alpacaRequest.post(urlBuilder, Watchlist.class); } /** @@ -1430,13 +1318,17 @@ public boolean deleteWatchlist(String watchlistID) throws AlpacaAPIRequestExcept Endpoints.WATCHLISTS, watchlistID); - HttpResponse response = alpacaRequest.invokeDelete(urlBuilder); + try { + HttpResponse response = alpacaRequest.invokeDelete(urlBuilder); - if ((response.getStatus() != 200 && response.getStatus() != 204)) { - throw new AlpacaAPIRequestException(response); - } + if (!(response.getStatus() == 200 || response.getStatus() == 204)) { + throw new AlpacaAPIRequestException(response); + } - return response.getStatus() == 200 || response.getStatus() == 204; + return true; + } catch (UnirestException exception) { + throw new AlpacaAPIRequestException(exception); + } } /** @@ -1459,13 +1351,7 @@ public Watchlist removeSymbolFromWatchlist(String watchlistID, String symbol) th watchlistID, symbol); - HttpResponse response = alpacaRequest.invokeDelete(urlBuilder); - - if (response.getStatus() != 200) { - throw new AlpacaAPIRequestException(response); - } - - return alpacaRequest.getResponseObject(response, Watchlist.class); + return alpacaRequest.delete(urlBuilder, Watchlist.class); } /** @@ -1509,13 +1395,7 @@ public PortfolioHistory getPortfolioHistory(Integer periodLength, PortfolioPerio urlBuilder.appendURLParameter(Parameters.EXTENDED_HOURS, extendedHours.toString()); } - HttpResponse response = alpacaRequest.invokeGet(urlBuilder); - - if (response.getStatus() != 200) { - throw new AlpacaAPIRequestException(response); - } - - return alpacaRequest.getResponseObject(response, PortfolioHistory.class); + return alpacaRequest.get(urlBuilder, PortfolioHistory.class); } /** @@ -1552,15 +1432,7 @@ public List getCalendar(LocalDate start, LocalDate end) throws AlpacaA urlBuilder.appendURLParameter(Parameters.END, end.format(DateTimeFormatter.ISO_DATE)); } - HttpResponse response = alpacaRequest.invokeGet(urlBuilder); - - if (response.getStatus() != 200) { - throw new AlpacaAPIRequestException(response); - } - - Type arrayListType = new TypeToken>() {}.getType(); - - return alpacaRequest.getResponseObject(response, arrayListType); + return alpacaRequest.get(urlBuilder, new TypeToken>() {}.getType()); } /** @@ -1575,13 +1447,7 @@ public Clock getClock() throws AlpacaAPIRequestException { AlpacaRequestBuilder urlBuilder = new AlpacaRequestBuilder(apiURL, Endpoints.VERSION_2, Endpoints.CLOCK); - HttpResponse response = alpacaRequest.invokeGet(urlBuilder); - - if (response.getStatus() != 200) { - throw new AlpacaAPIRequestException(response); - } - - return alpacaRequest.getResponseObject(response, Clock.class); + return alpacaRequest.get(urlBuilder, Clock.class); } /** @@ -1623,13 +1489,7 @@ public TradesResponse getTrades(String symbol, ZonedDateTime start, ZonedDateTim urlBuilder.appendURLParameter(Parameters.PAGE_TOKEN, pageToken); } - HttpResponse response = alpacaRequest.invokeGet(urlBuilder); - - if (response.getStatus() != 200) { - throw new AlpacaAPIRequestException(response); - } - - return alpacaRequest.getResponseObject(response, TradesResponse.class); + return alpacaRequest.get(urlBuilder, TradesResponse.class); } /** @@ -1671,13 +1531,7 @@ public QuotesResponse getQuotes(String symbol, ZonedDateTime start, ZonedDateTim urlBuilder.appendURLParameter(Parameters.PAGE_TOKEN, pageToken); } - HttpResponse response = alpacaRequest.invokeGet(urlBuilder); - - if (response.getStatus() != 200) { - throw new AlpacaAPIRequestException(response); - } - - return alpacaRequest.getResponseObject(response, QuotesResponse.class); + return alpacaRequest.get(urlBuilder, QuotesResponse.class); } /** @@ -1722,13 +1576,7 @@ public BarsResponse getBars(String symbol, ZonedDateTime start, ZonedDateTime en urlBuilder.appendURLParameter(Parameters.PAGE_TOKEN, pageToken); } - HttpResponse response = alpacaRequest.invokeGet(urlBuilder); - - if (response.getStatus() != 200) { - throw new AlpacaAPIRequestException(response); - } - - return alpacaRequest.getResponseObject(response, BarsResponse.class); + return alpacaRequest.get(urlBuilder, BarsResponse.class); } /** diff --git a/src/main/java/net/jacobpeterson/alpaca/rest/AlpacaRequest.java b/src/main/java/net/jacobpeterson/alpaca/rest/AlpacaRequest.java index 29fa29e8..5e0032dc 100644 --- a/src/main/java/net/jacobpeterson/alpaca/rest/AlpacaRequest.java +++ b/src/main/java/net/jacobpeterson/alpaca/rest/AlpacaRequest.java @@ -1,8 +1,14 @@ package net.jacobpeterson.alpaca.rest; import com.google.common.base.Preconditions; +import com.mashape.unirest.http.HttpResponse; +import com.mashape.unirest.http.exceptions.UnirestException; import net.jacobpeterson.abstracts.rest.AbstractRequest; import net.jacobpeterson.alpaca.properties.AlpacaProperties; +import net.jacobpeterson.alpaca.rest.exception.AlpacaAPIRequestException; + +import java.io.InputStream; +import java.lang.reflect.Type; /** * {@link AlpacaRequest} contains methods for HTTP requests for {@link net.jacobpeterson.alpaca.AlpacaAPI}. @@ -42,4 +48,179 @@ public AlpacaRequest(String oAuthToken) { headers.put(USER_AGENT_KEY, AlpacaProperties.USER_AGENT_VALUE); headers.put(AUTH_TOKEN, "Bearer " + oAuthToken); } + + /** + * Invokes a GET request and handles/returns the response object. + * + * @param the type + * @param urlBuilder the {@link AlpacaRequestBuilder} + * @param type the type to return + * + * @return the response object type + * + * @throws AlpacaAPIRequestException thrown for {@link AlpacaAPIRequestException}s + */ + public T get(AlpacaRequestBuilder urlBuilder, Type type) throws AlpacaAPIRequestException { + try { + HttpResponse response = invokeGet(urlBuilder); + + if (response.getStatus() != 200) { + throw new AlpacaAPIRequestException(response); + } + + return getResponseObject(response, type); + } catch (UnirestException exception) { + throw new AlpacaAPIRequestException(exception); + } + } + + /** + * Invokes a HEAD request and handles/returns the response object. + * + * @param the type + * @param urlBuilder the {@link AlpacaRequestBuilder} + * @param type the type to return + * + * @return the response object type + * + * @throws AlpacaAPIRequestException thrown for {@link AlpacaAPIRequestException}s + */ + public T head(AlpacaRequestBuilder urlBuilder, Type type) throws AlpacaAPIRequestException { + try { + HttpResponse response = invokeHead(urlBuilder); + + if (response.getStatus() != 200) { + throw new AlpacaAPIRequestException(response); + } + + return getResponseObject(response, type); + } catch (UnirestException exception) { + throw new AlpacaAPIRequestException(exception); + } + } + + /** + * Invokes a POST request and handles/returns the response object. + * + * @param the type + * @param urlBuilder the {@link AlpacaRequestBuilder} + * @param type the type to return + * + * @return the response object type + * + * @throws AlpacaAPIRequestException thrown for {@link AlpacaAPIRequestException}s + */ + public T post(AlpacaRequestBuilder urlBuilder, Type type) throws AlpacaAPIRequestException { + try { + HttpResponse response = invokePost(urlBuilder); + + if (response.getStatus() != 200) { + throw new AlpacaAPIRequestException(response); + } + + return getResponseObject(response, type); + } catch (UnirestException exception) { + throw new AlpacaAPIRequestException(exception); + } + } + + /** + * Invokes a PATCH request and handles/returns the response object. + * + * @param the type + * @param urlBuilder the {@link AlpacaRequestBuilder} + * @param type the type to return + * + * @return the response object type + * + * @throws AlpacaAPIRequestException thrown for {@link AlpacaAPIRequestException}s + */ + public T patch(AlpacaRequestBuilder urlBuilder, Type type) throws AlpacaAPIRequestException { + try { + HttpResponse response = invokePatch(urlBuilder); + + if (response.getStatus() != 200) { + throw new AlpacaAPIRequestException(response); + } + + return getResponseObject(response, type); + } catch (UnirestException exception) { + throw new AlpacaAPIRequestException(exception); + } + } + + /** + * Invokes a POST request and handles/returns the response object. + * + * @param the type + * @param urlBuilder the {@link AlpacaRequestBuilder} + * @param type the type to return + * + * @return the response object type + * + * @throws AlpacaAPIRequestException thrown for {@link AlpacaAPIRequestException}s + */ + public T put(AlpacaRequestBuilder urlBuilder, Type type) throws AlpacaAPIRequestException { + try { + HttpResponse response = invokePut(urlBuilder); + + if (response.getStatus() != 200) { + throw new AlpacaAPIRequestException(response); + } + + return getResponseObject(response, type); + } catch (UnirestException exception) { + throw new AlpacaAPIRequestException(exception); + } + } + + /** + * Invokes a DELETE request and handles/returns the response object. + * + * @param the type + * @param urlBuilder the {@link AlpacaRequestBuilder} + * @param type the type to return + * + * @return the response object type + * + * @throws AlpacaAPIRequestException thrown for {@link AlpacaAPIRequestException}s + */ + public T delete(AlpacaRequestBuilder urlBuilder, Type type) throws AlpacaAPIRequestException { + try { + HttpResponse response = invokeDelete(urlBuilder); + + if (response.getStatus() != 200) { + throw new AlpacaAPIRequestException(response); + } + + return getResponseObject(response, type); + } catch (UnirestException exception) { + throw new AlpacaAPIRequestException(exception); + } + } + + /** + * Invokes a OPTIONS request and handles/returns the response object. + * + * @param the type + * @param urlBuilder the {@link AlpacaRequestBuilder} + * @param type the type to return + * + * @return the response object type + * + * @throws AlpacaAPIRequestException thrown for {@link AlpacaAPIRequestException}s + */ + public T options(AlpacaRequestBuilder urlBuilder, Type type) throws AlpacaAPIRequestException { + try { + HttpResponse response = invokeOptions(urlBuilder); + + if (response.getStatus() != 200) { + throw new AlpacaAPIRequestException(response); + } + + return getResponseObject(response, type); + } catch (UnirestException exception) { + throw new AlpacaAPIRequestException(exception); + } + } } diff --git a/src/main/java/net/jacobpeterson/alpaca/rest/exception/AlpacaAPIRequestException.java b/src/main/java/net/jacobpeterson/alpaca/rest/exception/AlpacaAPIRequestException.java index f52d1e8b..70eeb03b 100644 --- a/src/main/java/net/jacobpeterson/alpaca/rest/exception/AlpacaAPIRequestException.java +++ b/src/main/java/net/jacobpeterson/alpaca/rest/exception/AlpacaAPIRequestException.java @@ -1,9 +1,13 @@ package net.jacobpeterson.alpaca.rest.exception; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; import com.mashape.unirest.http.HttpResponse; import net.jacobpeterson.abstracts.rest.exception.AbstractAPIRequestException; +import net.jacobpeterson.util.gson.GsonUtil; import java.io.InputStream; +import java.io.InputStreamReader; /** * {@link AlpacaAPIRequestException} represents {@link HttpResponse} request exceptions for {@link @@ -11,6 +15,15 @@ */ public class AlpacaAPIRequestException extends AbstractAPIRequestException { + /** + * Instantiates a new {@link AlpacaAPIRequestException}. + * + * @param cause the cause + */ + public AlpacaAPIRequestException(Exception cause) { + super(cause); + } + /** * Instantiates a new {@link AlpacaAPIRequestException}. * @@ -20,8 +33,34 @@ public AlpacaAPIRequestException(HttpResponse httpResponse) { super("Alpaca", httpResponse); } + /** + * Parse a standard API exception response of the following format: + *
+     *  {
+     *     "code": 40010001,
+     *     "message": "Error message"
+     *  }
+     * 
+ */ @Override protected void parseAPIExceptionMessage() { - super.parseStandardAPIExceptionResponse(); + JsonElement responseJsonElement = GsonUtil.JSON_PARSER.parse(new InputStreamReader(httpResponse.getBody())); + + if (responseJsonElement instanceof JsonObject) { + JsonObject responseJsonObject = (JsonObject) responseJsonElement; + + if (responseJsonObject.has(CODE_KEY)) { + apiResponseCode = responseJsonObject.get(CODE_KEY).getAsInt(); + } + + if (responseJsonObject.has(MESSAGE_KEY)) { + apiResponseMessage = responseJsonObject.get(MESSAGE_KEY).getAsString(); + } + } + + // Just use the response JSON if the message could not be parsed. + if (apiResponseMessage == null) { + apiResponseMessage = responseJsonElement.toString(); + } } } From d91b7dab2c7b1c46e13002aaf8c4398ad44138d2 Mon Sep 17 00:00:00 2001 From: Jacob Peterson Date: Fri, 2 Apr 2021 10:13:22 -0600 Subject: [PATCH 101/271] Fix getAccountActivities --- README.md | 2 +- .../non_trade_activity.json | 4 ++++ .../net/jacobpeterson/alpaca/AlpacaAPI.java | 20 ++++++++++++------- .../jacobpeterson/alpaca/AlpacaConstants.java | 8 ++++++++ .../net/jacobpeterson/util/gson/GsonUtil.java | 2 ++ 5 files changed, 28 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index 28b55b13..658f6b36 100644 --- a/README.md +++ b/README.md @@ -116,7 +116,7 @@ try { null, null, ZonedDateTime.of(2020, 12, 23, 0, 0, 0, 0, ZoneId.of("America/New_York")), - Direction.ASCENDING, + SortDirection.ASCENDING, null, null, (ActivityType[]) null); diff --git a/schema_json/alpaca/account_activities/non_trade_activity.json b/schema_json/alpaca/account_activities/non_trade_activity.json index d5f18724..b28630a1 100644 --- a/schema_json/alpaca/account_activities/non_trade_activity.json +++ b/schema_json/alpaca/account_activities/non_trade_activity.json @@ -36,6 +36,10 @@ "description": { "existingJavaType": "java.lang.String", "title": "For Transaction records (TRANS,CSR,CSD) a description field is present (even if the docs don't mention it!" + }, + "status": { + "existingJavaType": "java.lang.String", + "title": "The status." } } } diff --git a/src/main/java/net/jacobpeterson/alpaca/AlpacaAPI.java b/src/main/java/net/jacobpeterson/alpaca/AlpacaAPI.java index 365ca45a..7d301528 100644 --- a/src/main/java/net/jacobpeterson/alpaca/AlpacaAPI.java +++ b/src/main/java/net/jacobpeterson/alpaca/AlpacaAPI.java @@ -59,7 +59,6 @@ import org.slf4j.LoggerFactory; import java.io.InputStream; -import java.lang.reflect.Type; import java.time.LocalDate; import java.time.ZonedDateTime; import java.time.format.DateTimeFormatter; @@ -69,6 +68,8 @@ import java.util.StringJoiner; import java.util.stream.Collectors; +import static net.jacobpeterson.alpaca.AlpacaConstants.Fields.ACTIVITY_TYPE; + /** * {@link AlpacaAPI} contains several methods to interface with Alpaca. You will generally only need one instance of it * in your application. Note that many methods allow null to be passed in as a parameter if it is @@ -259,14 +260,19 @@ public List getAccountActivities(ZonedDateTime date, ZonedDateT for (JsonElement arrayJsonElement : responseJsonArray) { // Loop through response array if (arrayJsonElement instanceof JsonObject) { - JsonObject arrayJsonObject = (JsonObject) arrayJsonElement; + JsonObject arrayJsonObject = arrayJsonElement.getAsJsonObject(); + + if (arrayJsonObject.has(ACTIVITY_TYPE)) { + String activityType = arrayJsonObject.get(ACTIVITY_TYPE).getAsString(); - if (GsonUtil.doesGsonPOJOMatch(TradeActivity.class, arrayJsonObject)) { - accountActivities.add(GsonUtil.GSON.fromJson(arrayJsonObject, TradeActivity.class)); - } else if (GsonUtil.doesGsonPOJOMatch(NonTradeActivity.class, arrayJsonObject)) { - accountActivities.add(GsonUtil.GSON.fromJson(arrayJsonObject, NonTradeActivity.class)); + // A 'TradeActivity' always has 'activity_type' field as 'FILL' + if (activityType.equals(ActivityType.FILL.getAPIName())) { + accountActivities.add(GsonUtil.GSON.fromJson(arrayJsonObject, TradeActivity.class)); + } else { + accountActivities.add(GsonUtil.GSON.fromJson(arrayJsonObject, NonTradeActivity.class)); + } } else { - LOGGER.warn("Received unknown JSON Object in response!"); + LOGGER.warn("JSON Object does not have 'activity_type' in response! {}", arrayJsonObject); } } else { throw new IllegalStateException("All array elements must be objects!"); diff --git a/src/main/java/net/jacobpeterson/alpaca/AlpacaConstants.java b/src/main/java/net/jacobpeterson/alpaca/AlpacaConstants.java index c5fe2865..4c60e5dd 100644 --- a/src/main/java/net/jacobpeterson/alpaca/AlpacaConstants.java +++ b/src/main/java/net/jacobpeterson/alpaca/AlpacaConstants.java @@ -75,4 +75,12 @@ public static class Parameters { public static final String PAGE_SIZE = "page_size"; public static final String PAGE_TOKEN = "page_token"; } + + /** + * Defines miscellaneous fields of {@link AlpacaAPI}. + */ + public static class Fields { + + public static final String ACTIVITY_TYPE = "activity_type"; + } } diff --git a/src/main/java/net/jacobpeterson/util/gson/GsonUtil.java b/src/main/java/net/jacobpeterson/util/gson/GsonUtil.java index f274e6e7..751b98b4 100644 --- a/src/main/java/net/jacobpeterson/util/gson/GsonUtil.java +++ b/src/main/java/net/jacobpeterson/util/gson/GsonUtil.java @@ -50,9 +50,11 @@ public static boolean doesGsonPOJOMatch(Class jsonPOJOClass, JsonObject jsonO for (String key : jsonObjectKeys) { if (!classKeys.contains(key)) { + LOGGER.trace("Class '{}' did not contain key '{}'", jsonPOJOClass, key); return false; } } + return true; } From d46e3c05640b53f8d2b3c30a05128f4cd70d7642 Mon Sep 17 00:00:00 2001 From: Jacob Peterson Date: Fri, 2 Apr 2021 10:26:09 -0600 Subject: [PATCH 102/271] Update Order section in README --- README.md | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 658f6b36..181cd24e 100644 --- a/README.md +++ b/README.md @@ -166,7 +166,7 @@ try { Arrays.asList("AAPL", "TSLA")); orders.forEach(System.out::println); - // Request a new buy limit order for TSLA for 100 shares at a limit price + // Request a new limit order for TSLA for 100 shares at a limit price // of 600.00 and TIF of DAY. Order limitOrderTSLA = alpacaAPI.requestNewLimitOrder( "TSLA", @@ -196,6 +196,11 @@ try { for (CancelledOrder cancelledOrder : cancelledOrders) { System.out.println("Cancelled Order: " + cancelledOrder.getOrder()); } + + // Request a new fractional market order for 0.5 shares of GME + alpacaAPI.requestNewFractionalMarketOrder("GME", 0.5, OrderSide.BUY); + // Request a new notional market order for $25 worth of GME shares + alpacaAPI.requestNewNotionalMarketOrder("GME", 25d, OrderSide.BUY); } catch (AlpacaAPIRequestException e) { e.printStackTrace(); } From 79eac323d12e03683559a0f057351acc885ddd68 Mon Sep 17 00:00:00 2001 From: Jacob Peterson Date: Fri, 2 Apr 2021 10:42:26 -0600 Subject: [PATCH 103/271] Update Order JSON schema --- README.md | 2 +- schema_json/alpaca/order/order.json | 34 +++++++++++++++++++---------- 2 files changed, 24 insertions(+), 12 deletions(-) diff --git a/README.md b/README.md index 181cd24e..67d83aac 100644 --- a/README.md +++ b/README.md @@ -161,7 +161,7 @@ try { null, ZonedDateTime.of(2020, 12, 23, 0, 0, 0, 0, ZoneId.of("America/New_York")), null, - Direction.ASCENDING, + SortDirection.ASCENDING, true, Arrays.asList("AAPL", "TSLA")); orders.forEach(System.out::println); diff --git a/schema_json/alpaca/order/order.json b/schema_json/alpaca/order/order.json index ac9db9c4..22f4619e 100644 --- a/schema_json/alpaca/order/order.json +++ b/schema_json/alpaca/order/order.json @@ -62,17 +62,33 @@ "existingJavaType": "java.lang.String", "title": "Asset class" }, + "notional": { + "existingJavaType": "java.lang.String", + "title": "Ordered notional amount. If entered, qty will be null. Can take up to 9 decimal points." + }, "qty": { "existingJavaType": "java.lang.String", - "title": "Ordered quantity" + "title": "Ordered quantity. If entered, notional will be null. Can take up to 9 decimal points." }, "filled_qty": { "existingJavaType": "java.lang.String", "title": "Filled quantity" }, + "filled_avg_price": { + "existingJavaType": "java.lang.String", + "title": "Filled average price" + }, + "order_class": { + "existingJavaType": "java.lang.String", + "title": "simple, bracket, oco or oto. For details of non-simple order classes, please see Bracket Order Overview" + }, + "order_type": { + "existingJavaType": "java.lang.String", + "title": "(Deprecated with just type field below.)" + }, "type": { "existingJavaType": "java.lang.String", - "title": "Valid values: market, limit, stop, stop_limit" + "title": "Valid values: market, limit, stop, stop_limit, trailing_stop" }, "side": { "existingJavaType": "java.lang.String", @@ -90,10 +106,6 @@ "existingJavaType": "java.lang.String", "title": "Stop price" }, - "filled_avg_price": { - "existingJavaType": "java.lang.String", - "title": "Stop price" - }, "status": { "existingJavaType": "java.lang.String", "title": "See Orders page" @@ -104,16 +116,16 @@ }, "legs": { "existingJavaType": "java.util.ArrayList", - "title": "When querying non-simple order_class orders in a nested style, an array of Order entities associated with this order is returned. Otherwise, null." - }, - "trail_price": { - "existingJavaType": "java.lang.String", - "title": "The dollar value away from the high water mark for trailing stop orders." + "title": "When querying non-simple order_class orders in a nested style, an array of Order entities associated with this order. Otherwise, null." }, "trail_percent": { "existingJavaType": "java.lang.String", "title": "The percent value away from the high water mark for trailing stop orders." }, + "trail_price": { + "existingJavaType": "java.lang.String", + "title": "The dollar value away from the high water mark for trailing stop orders." + }, "hwm": { "existingJavaType": "java.lang.String", "title": "High Water Mark - The highest (lowest) market price seen since the trailing stop order was submitted." From 4e70b6004c92698e694241c24fbf0b7165e6fbd4 Mon Sep 17 00:00:00 2001 From: Jacob Peterson Date: Fri, 2 Apr 2021 10:47:31 -0600 Subject: [PATCH 104/271] Update to 7.1 --- README.md | 4 ++-- build.gradle | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 67d83aac..c733b319 100644 --- a/README.md +++ b/README.md @@ -39,7 +39,7 @@ If you are using Gradle as your build tool, add the following dependency to your ``` dependencies { - implementation group: 'net.jacobpeterson', name: 'alpaca-java', version: '7.0' + implementation group: 'net.jacobpeterson', name: 'alpaca-java', version: '7.1' } ``` @@ -49,7 +49,7 @@ If you are using Maven as your build tool, add the following dependency to your net.jacobpeterson alpaca-java - 7.0 + 7.1 compile ``` diff --git a/build.gradle b/build.gradle index 53fb059a..044a7f0b 100644 --- a/build.gradle +++ b/build.gradle @@ -30,7 +30,7 @@ tasks.getByName("generateJsonSchema2Pojo").enabled(false) // Disable default 'js archivesBaseName = 'alpaca-java' group = 'net.jacobpeterson' -version = '7.0' +version = '7.1' repositories { mavenLocal() From f9f64edd5c4843780bb69d07bfc861d3601c2a6b Mon Sep 17 00:00:00 2001 From: Salvador Montiel Date: Sun, 25 Apr 2021 16:02:51 -0500 Subject: [PATCH 105/271] Add 'name' field to Asset schema --- schema_json/alpaca/asset/asset.json | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/schema_json/alpaca/asset/asset.json b/schema_json/alpaca/asset/asset.json index 4742fa29..9e9cb088 100644 --- a/schema_json/alpaca/asset/asset.json +++ b/schema_json/alpaca/asset/asset.json @@ -18,6 +18,10 @@ "existingJavaType": "java.lang.String", "title": "symbol" }, + "name": { + "existingJavaType": "java.lang.String", + "title": "The official name of the asset" + }, "status": { "existingJavaType": "java.lang.String", "title": "active or inactive" From 019c617a0a2ed58c0b9e27a7933698e5b3b58f2e Mon Sep 17 00:00:00 2001 From: Jacob Peterson Date: Mon, 26 Apr 2021 16:53:03 -0600 Subject: [PATCH 106/271] Fix locality and sub-penny currency formatting. --- codecov.yml | 1 + .../java/net/jacobpeterson/util/format/FormatUtil.java | 8 +++++++- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/codecov.yml b/codecov.yml index 7a0562f8..c749985f 100644 --- a/codecov.yml +++ b/codecov.yml @@ -3,3 +3,4 @@ coverage: # Turn off Codecov status for now project: off patch: off +comment: false \ No newline at end of file diff --git a/src/main/java/net/jacobpeterson/util/format/FormatUtil.java b/src/main/java/net/jacobpeterson/util/format/FormatUtil.java index 02beef6e..2a563f7c 100644 --- a/src/main/java/net/jacobpeterson/util/format/FormatUtil.java +++ b/src/main/java/net/jacobpeterson/util/format/FormatUtil.java @@ -1,18 +1,24 @@ package net.jacobpeterson.util.format; import java.text.DecimalFormat; +import java.text.DecimalFormatSymbols; import java.text.NumberFormat; import java.time.ZoneId; import java.time.ZonedDateTime; import java.time.format.DateTimeFormatter; import java.time.temporal.TemporalAccessor; +import java.util.Locale; /** * {@link FormatUtil} is a utility class for various formatting. */ public class FormatUtil { - private static final NumberFormat CURRENCY_FORMATTER = new DecimalFormat("#0.00"); + // Alpaca uses the following rounding mechanics with respect to buy orders: (1) rounded down to two decimal + // places if the last trade price is over $1.00; otherwise, rounded down to four decimal places, hence the '#' in + // the 3rd and 4th least significant decimal digits. + private static final NumberFormat CURRENCY_FORMATTER = new DecimalFormat("#0.00##", + DecimalFormatSymbols.getInstance(Locale.US)); private static final DateTimeFormatter RFC_3339_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss'Z'").withZone(ZoneId.of("America/New_York")); private static final long PREV_UNIX_EPOCH_NANO_TIME = System.currentTimeMillis() * 1000000L; From 8c112e33a87837f1be2711ceac7885d257e2b143 Mon Sep 17 00:00:00 2001 From: Jacob Peterson Date: Tue, 27 Apr 2021 14:07:49 -0600 Subject: [PATCH 107/271] Fix test_getOrders_One_Order_Exists_Until_now --- .../alpaca/AlpacaAPIEndpointTest.java | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/src/test/java/live/net/jacobpeterson/alpaca/AlpacaAPIEndpointTest.java b/src/test/java/live/net/jacobpeterson/alpaca/AlpacaAPIEndpointTest.java index 532dedaf..2f07dc6d 100644 --- a/src/test/java/live/net/jacobpeterson/alpaca/AlpacaAPIEndpointTest.java +++ b/src/test/java/live/net/jacobpeterson/alpaca/AlpacaAPIEndpointTest.java @@ -12,7 +12,12 @@ import net.jacobpeterson.domain.alpaca.accountconfiguration.AccountConfiguration; import net.jacobpeterson.domain.alpaca.clock.Clock; import net.jacobpeterson.domain.alpaca.order.Order; -import org.junit.jupiter.api.*; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.MethodOrderer; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestMethodOrder; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -20,7 +25,6 @@ import java.util.List; import static org.junit.jupiter.api.Assertions.*; -import static org.junit.jupiter.api.Assumptions.assumeTrue; /** * {@link AlpacaAPIEndpointTest} tests live endpoints using Alpaca Paper credentials given in the @@ -261,7 +265,6 @@ public void test_getOrders_One_Order_Exists_Until_now() throws AlpacaAPIRequestE assertNotNull(order.getCreatedAt()); assertNotNull(order.getUpdatedAt()); assertNotNull(order.getSubmittedAt()); - assertNotNull(order.getFilledAt()); assertNotNull(order.getAssetId()); assertNotNull(order.getSymbol()); assertNotNull(order.getAssetClass()); @@ -270,13 +273,7 @@ public void test_getOrders_One_Order_Exists_Until_now() throws AlpacaAPIRequestE assertNotNull(order.getType()); assertNotNull(order.getSide()); assertNotNull(order.getTimeInForce()); - assertNotNull(order.getFilledAvgPrice()); assertNotNull(order.getStatus()); assertNotNull(order.getExtendedHours()); } - - @Test - public void test() { - assumeTrue(marketOpen, "Market is not open. Skipping!"); - } } From d4e5ac830767a6b69259192c60645433272435a6 Mon Sep 17 00:00:00 2001 From: Jacob Peterson Date: Tue, 27 Apr 2021 14:09:57 -0600 Subject: [PATCH 108/271] Update to 7.1.1 --- README.md | 4 ++-- build.gradle | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index c733b319..ad2164cd 100644 --- a/README.md +++ b/README.md @@ -39,7 +39,7 @@ If you are using Gradle as your build tool, add the following dependency to your ``` dependencies { - implementation group: 'net.jacobpeterson', name: 'alpaca-java', version: '7.1' + implementation group: 'net.jacobpeterson', name: 'alpaca-java', version: '7.1.1' } ``` @@ -49,7 +49,7 @@ If you are using Maven as your build tool, add the following dependency to your net.jacobpeterson alpaca-java - 7.1 + 7.1.1 compile ``` diff --git a/build.gradle b/build.gradle index 044a7f0b..da159f0a 100644 --- a/build.gradle +++ b/build.gradle @@ -30,7 +30,7 @@ tasks.getByName("generateJsonSchema2Pojo").enabled(false) // Disable default 'js archivesBaseName = 'alpaca-java' group = 'net.jacobpeterson' -version = '7.1' +version = '7.1.1' repositories { mavenLocal() From 1ae82dc774f2c3dd2beae2603fcaf182b2b14433 Mon Sep 17 00:00:00 2001 From: Jacob Peterson Date: Fri, 7 May 2021 15:06:00 -0600 Subject: [PATCH 109/271] Add TradeUpdateEvent enum --- LICENSE | 2 +- .../alpaca/streaming/trade/trade_update.json | 2 +- .../enums/streaming/TradeUpdateEvent.java | 117 ++++++++++++++++++ 3 files changed, 119 insertions(+), 2 deletions(-) create mode 100644 src/main/java/net/jacobpeterson/alpaca/enums/streaming/TradeUpdateEvent.java diff --git a/LICENSE b/LICENSE index 81f990d6..706b1675 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2020 +Copyright (c) Jacob Peterson 2021 Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/schema_json/alpaca/streaming/trade/trade_update.json b/schema_json/alpaca/streaming/trade/trade_update.json index a94b47f6..da0c0085 100644 --- a/schema_json/alpaca/streaming/trade/trade_update.json +++ b/schema_json/alpaca/streaming/trade/trade_update.json @@ -3,7 +3,7 @@ "title": "See https://docs.alpaca.markets/api-documentation/api-v2/streaming/", "properties": { "event": { - "existingJavaType": "java.lang.String", + "existingJavaType": "net.jacobpeterson.alpaca.enums.streaming.TradeUpdateEvent", "title": "The event type" }, "price": { diff --git a/src/main/java/net/jacobpeterson/alpaca/enums/streaming/TradeUpdateEvent.java b/src/main/java/net/jacobpeterson/alpaca/enums/streaming/TradeUpdateEvent.java new file mode 100644 index 00000000..30641ca9 --- /dev/null +++ b/src/main/java/net/jacobpeterson/alpaca/enums/streaming/TradeUpdateEvent.java @@ -0,0 +1,117 @@ +package net.jacobpeterson.alpaca.enums.streaming; + +import com.google.gson.annotations.SerializedName; +import net.jacobpeterson.abstracts.enums.APIName; +import net.jacobpeterson.util.gson.GsonUtil; + +/** + * {@link TradeUpdateEvent} defines enums for various trade update events. + */ +public enum TradeUpdateEvent implements APIName { + + /** + * Sent when an order has been routed to exchanges for execution. + */ + @SerializedName("new") + NEW, + + /** + * Sent when your order has been completely filled. + */ + @SerializedName("fill") + FILL, + + /** + * Sent when a number of shares less than the total remaining quantity on your order has been filled. + */ + @SerializedName("partial_fill") + PARTIAL_FILL, + + /** + * Sent when your requested cancellation of an order is processed. + */ + @SerializedName("canceled") + CANCELED, + + /** + * Sent when an order has reached the end of its lifespan, as determined by the order’s time in force value. + **/ + @SerializedName("expired") + EXPIRED, + + /** + * Sent when the order is done executing for the day, and will not receive further updates until the next trading + * day. + */ + @SerializedName("done_for_day") + DONE_FOR_DAY, + + /** + * Sent when your requested replacement of an order is processed. + */ + @SerializedName("replaced") + REPLACED, + + /** + * Sent when your order has been rejected. + */ + @SerializedName("rejected") + REJECTED, + + /** + * Sent when the order has been received by Alpaca and routed to the exchanges, but has not yet been accepted for + * execution. + */ + @SerializedName("pending_new") + PENDING_NEW, + + /** + * Sent when your order has been stopped, and a trade is guaranteed for the order, usually at a stated price or + * better, but has not yet occurred. + */ + @SerializedName("stopped") + STOPPED, + + /** + * Sent when the order is awaiting cancellation. Most cancellations will occur without the order entering this + * state. + */ + @SerializedName("pending_cancel") + PENDING_CANCEL, + + /** + * Sent when the order is awaiting replacement. + */ + @SerializedName("pending_replace") + PENDING_REPLACE, + + /** + * Sent when the order has been completed for the day - it is either “filled” or “done_for_day” - but remaining + * settlement calculations are still pending. + */ + @SerializedName("calculated") + CALCULATED, + + /** + * Sent when the order has been suspended and is not eligible for trading. + */ + @SerializedName("suspended") + SUSPENDED, + + /** + * Sent when the order replace has been rejected. + */ + @SerializedName("order_replace_rejected") + ORDER_REPLACE_REJECTED, + + /** + * Sent when the order cancel has been rejected. + */ + @SerializedName("order_cancel_rejected") + ORDER_CANCEL_REJECTED; + + @Override + public String getAPIName() { + return GsonUtil.GSON.toJsonTree(this).getAsString(); + } +} From 6c9884d8fedf4bfc0e0eb1fda05056d8d8f097bd Mon Sep 17 00:00:00 2001 From: Jacob Peterson Date: Fri, 7 May 2021 15:26:52 -0600 Subject: [PATCH 110/271] Add ActivityType enum to account activity schemas --- .../non_trade_activity.json | 4 +- .../account_activities/trade_activity.json | 2 +- .../alpaca/enums/activity/ActivityType.java | 41 ++++++++++++++++++- 3 files changed, 42 insertions(+), 5 deletions(-) diff --git a/schema_json/alpaca/account_activities/non_trade_activity.json b/schema_json/alpaca/account_activities/non_trade_activity.json index b28630a1..0e961e4c 100644 --- a/schema_json/alpaca/account_activities/non_trade_activity.json +++ b/schema_json/alpaca/account_activities/non_trade_activity.json @@ -6,7 +6,7 @@ }, "properties": { "activity_type": { - "existingJavaType": "java.lang.String", + "existingJavaType": "net.jacobpeterson.alpaca.enums.activity.ActivityType", "title": "See https://docs.alpaca.markets/api-documentation/api-v2/account-activities/#activity-types for a list of possible values." }, "id": { @@ -35,7 +35,7 @@ }, "description": { "existingJavaType": "java.lang.String", - "title": "For Transaction records (TRANS,CSR,CSD) a description field is present (even if the docs don't mention it!" + "title": "Description field for transaction records (TRANS,CSR,CSD)." }, "status": { "existingJavaType": "java.lang.String", diff --git a/schema_json/alpaca/account_activities/trade_activity.json b/schema_json/alpaca/account_activities/trade_activity.json index 4bc34b68..579268d4 100644 --- a/schema_json/alpaca/account_activities/trade_activity.json +++ b/schema_json/alpaca/account_activities/trade_activity.json @@ -6,7 +6,7 @@ }, "properties": { "activity_type": { - "existingJavaType": "java.lang.String", + "existingJavaType": "net.jacobpeterson.alpaca.enums.activity.ActivityType", "title": "FILL" }, "id": { diff --git a/src/main/java/net/jacobpeterson/alpaca/enums/activity/ActivityType.java b/src/main/java/net/jacobpeterson/alpaca/enums/activity/ActivityType.java index 649e1546..eff31187 100644 --- a/src/main/java/net/jacobpeterson/alpaca/enums/activity/ActivityType.java +++ b/src/main/java/net/jacobpeterson/alpaca/enums/activity/ActivityType.java @@ -1,5 +1,6 @@ package net.jacobpeterson.alpaca.enums.activity; +import com.google.gson.annotations.SerializedName; import net.jacobpeterson.abstracts.enums.APIName; /** @@ -14,99 +15,135 @@ public enum ActivityType implements APIName { // @see ActivityType#getAPIName() /** Order fills (both partial and full fills) */ + @SerializedName("FILL") FILL, - /** Cash transactions (both CSD and CSR) */ + /** Cash transactions (both CSD and CSW) */ + @SerializedName("TRANS") TRANS, /** Miscellaneous or rarely used activity types (All types except those in TRANS, DIV, or FILL) */ + @SerializedName("MISC") MISC, /** ACATS IN/OUT (Cash) */ + @SerializedName("ACATC") ACATC, /** ACATS IN/OUT (Securities) */ + @SerializedName("ACATS") ACATS, - /** Cash disbursement(+) */ + /** Cash deposit(+) */ + @SerializedName("CSD") CSD, /** Cash receipt(-) */ + @SerializedName("CSR") CSR, + /** Cash withdrawal(-) */ + @SerializedName("CSW") + CSW, + /** Dividends */ + @SerializedName("DIV") DIV, /** Dividend (capital gain long term) */ + @SerializedName("DIVCGL") DIVCGL, /** Dividend (capital gain short term) */ + @SerializedName("DIVCGS") DIVCGS, /** Dividend fee */ + @SerializedName("DIVFEE") DIVFEE, /** Dividend adjusted (Foreign Tax Withheld) */ + @SerializedName("DIVFT") DIVFT, /** Dividend adjusted (NRA Withheld) */ + @SerializedName("DIVNRA") DIVNRA, /** Dividend return of capital */ + @SerializedName("DIVROC") DIVROC, /** Dividend adjusted (Tefra Withheld) */ + @SerializedName("DIVTW") DIVTW, /** Dividend (tax exempt) */ + @SerializedName("DIVTXEX") DIVTXEX, /** Interest (credit/margin) */ + @SerializedName("INT") INT, /** Interest adjusted (NRA Withheld) */ + @SerializedName("INTNRA") INTNRA, /** Interest adjusted (Tefra Withheld) */ + @SerializedName("INTTW") INTTW, /** Journal entry */ + @SerializedName("JNL") JNL, /** Journal entry (cash) */ + @SerializedName("JNLC") JNLC, /** Journal entry (stock) */ + @SerializedName("JNLS") JNLS, /** Merger/Acquisition */ + @SerializedName("MA") MA, /** Name change */ + @SerializedName("NC") NC, /** Option assignment */ + @SerializedName("OPASN") OPASN, /** Option expiration */ + @SerializedName("OPEXP") OPEXP, /** Option exercise */ + @SerializedName("OPXRC") OPXRC, /** Pass Thru Charge */ + @SerializedName("PTC") PTC, /** Pass Thru Rebate */ + @SerializedName("PTR") PTR, /** Reorg CA */ + @SerializedName("REORG") REORG, /** Symbol change */ + @SerializedName("SC") SC, /** Stock spinoff */ + @SerializedName("SSO") SSO, /** Stock split */ From 8163d301ed9c74374a131f827bc6272ee856daac Mon Sep 17 00:00:00 2001 From: Jacob Peterson Date: Tue, 18 May 2021 20:57:01 -0600 Subject: [PATCH 111/271] Add latest historical endpoints --- .../net/jacobpeterson/alpaca/AlpacaAPI.java | 46 +++++++++++++++++++ .../jacobpeterson/alpaca/AlpacaConstants.java | 1 + 2 files changed, 47 insertions(+) diff --git a/src/main/java/net/jacobpeterson/alpaca/AlpacaAPI.java b/src/main/java/net/jacobpeterson/alpaca/AlpacaAPI.java index 7d301528..e3eca27b 100644 --- a/src/main/java/net/jacobpeterson/alpaca/AlpacaAPI.java +++ b/src/main/java/net/jacobpeterson/alpaca/AlpacaAPI.java @@ -1498,6 +1498,29 @@ public TradesResponse getTrades(String symbol, ZonedDateTime start, ZonedDateTim return alpacaRequest.get(urlBuilder, TradesResponse.class); } + /** + * The Latest trade API provides the latest {@link Trade} data for a given ticker symbol. + * + * @param symbol the symbol to query for + * + * @return the {@link Trade} + * + * @throws AlpacaAPIRequestException thrown for {@link AlpacaAPIRequestException}s + * @see + * Historical Market Data + */ + public Trade getLatestTrade(String symbol) throws AlpacaAPIRequestException { + Preconditions.checkNotNull(symbol); + + AlpacaRequestBuilder urlBuilder = new AlpacaRequestBuilder(URLs.DATA, Endpoints.VERSION_2, + Endpoints.STOCKS, + symbol, + Endpoints.TRADES, + Endpoints.LATEST); + + return alpacaRequest.get(urlBuilder, Trade.class); + } + /** * Gets {@link Quote} (NBBO or National Best Bid and Offer) historical data for the requested security. * @@ -1540,6 +1563,29 @@ public QuotesResponse getQuotes(String symbol, ZonedDateTime start, ZonedDateTim return alpacaRequest.get(urlBuilder, QuotesResponse.class); } + /** + * The Latest quote API provides the latest quote data for a given ticker symbol. + * + * @param symbol the symbol to query for + * + * @return the {@link Quote} + * + * @throws AlpacaAPIRequestException thrown for {@link AlpacaAPIRequestException}s + * @see + * Historical Market Data + */ + public Quote getLatestQuote(String symbol) throws AlpacaAPIRequestException { + Preconditions.checkNotNull(symbol); + + AlpacaRequestBuilder urlBuilder = new AlpacaRequestBuilder(URLs.DATA, Endpoints.VERSION_2, + Endpoints.STOCKS, + symbol, + Endpoints.QUOTES, + Endpoints.LATEST); + + return alpacaRequest.get(urlBuilder, Quote.class); + } + /** * Gets {@link Bar} aggregate historical data for the requested security. * diff --git a/src/main/java/net/jacobpeterson/alpaca/AlpacaConstants.java b/src/main/java/net/jacobpeterson/alpaca/AlpacaConstants.java index 4c60e5dd..74d39557 100644 --- a/src/main/java/net/jacobpeterson/alpaca/AlpacaConstants.java +++ b/src/main/java/net/jacobpeterson/alpaca/AlpacaConstants.java @@ -35,6 +35,7 @@ public static class Endpoints { public static final String TRADES = "trades"; public static final String QUOTES = "quotes"; public static final String BARS = "bars"; + public static final String LATEST = "trades"; } /** From 08253294cba1d677676d52a4a12652bf3baf8835 Mon Sep 17 00:00:00 2001 From: Jacob Peterson Date: Tue, 18 May 2021 22:23:36 -0600 Subject: [PATCH 112/271] Add snapshot endpoints, update README, fix latest endpoints --- README.md | 42 +++++++++ build.gradle | 2 +- .../quote/latest_quote_response.json | 14 +++ .../historical/snapshot/snapshot.json | 26 +++++ .../trade/latest_trade_response.json | 14 +++ .../net/jacobpeterson/alpaca/AlpacaAPI.java | 94 +++++++++++++++++-- .../jacobpeterson/alpaca/AlpacaConstants.java | 4 +- 7 files changed, 188 insertions(+), 8 deletions(-) create mode 100644 schema_json/alpaca/market_data/historical/quote/latest_quote_response.json create mode 100644 schema_json/alpaca/market_data/historical/snapshot/snapshot.json create mode 100644 schema_json/alpaca/market_data/historical/trade/latest_trade_response.json diff --git a/README.md b/README.md index ad2164cd..5b900b19 100644 --- a/README.md +++ b/README.md @@ -399,6 +399,19 @@ try { } ``` +### Latest Trade +The Latest trade API provides the latest trade data for a given ticker symbol. + +Example usage: +```java +try { + LatestTradeResponse latestAAPLTrade = alpacaAPI.getLatestTrade("AAPL"); + System.out.println(latestAAPLTrade); +} catch (AlpacaAPIRequestException e) { + e.printStackTrace(); +} +``` + ### Quotes The Quotes API provides NBBO quotes for a given ticker symbol in a specified date range. @@ -418,6 +431,19 @@ try { } ``` +### Latest Quote +The Latest quote API provides the latest quote data for a given ticker symbol. + +Example usage: +```java +try { + LatestQuoteResponse latestAAPLQuote = alpacaAPI.getLatestQuote("AAPL"); + System.out.println(latestAAPLQuote); +} catch (AlpacaAPIRequestException e) { + e.printStackTrace(); +} +``` + ### Bars The bars API returns aggregate historical data for the requested securities. @@ -438,6 +464,22 @@ try { } ``` +### Snapshot +The Snapshot API for multiple tickers provides the latest trade, latest quote, minute bar daily bar and previous daily bar data for the given ticker symbols. + +Example usage: +```java +try { + Map snapshotsOfSymbols = alpacaAPI.getSnapshots(Arrays.asList("AAPL", "TSLA", "GME")); + snapshotsOfSymbols.entrySet().forEach(System.out::println); + + Snapshot msftSnapshot = alpacaAPI.getSnapshot("MSFT"); + System.out.println(msftSnapshot); +} catch (AlpacaAPIRequestException e) { + e.printStackTrace(); +} +``` + ### Realtime Market Data Alpaca's Data API provides websocket streaming for trades, quotes and minute bars. This helps receive the most up to date market information that could help your trading strategy to act upon certain market movement. diff --git a/build.gradle b/build.gradle index da159f0a..6fb85e6d 100644 --- a/build.gradle +++ b/build.gradle @@ -30,7 +30,7 @@ tasks.getByName("generateJsonSchema2Pojo").enabled(false) // Disable default 'js archivesBaseName = 'alpaca-java' group = 'net.jacobpeterson' -version = '7.1.1' +version = '7.1.1-SNAPSHOT' repositories { mavenLocal() diff --git a/schema_json/alpaca/market_data/historical/quote/latest_quote_response.json b/schema_json/alpaca/market_data/historical/quote/latest_quote_response.json new file mode 100644 index 00000000..84c129ad --- /dev/null +++ b/schema_json/alpaca/market_data/historical/quote/latest_quote_response.json @@ -0,0 +1,14 @@ +{ + "type": "object", + "title": "See https://alpaca.markets/docs/api-documentation/api-v2/market-data/alpaca-data-api-v2/historical/", + "properties": { + "symbol": { + "existingJavaType": "java.lang.String", + "title": "Symbol that was queried." + }, + "quote": { + "existingJavaType": "net.jacobpeterson.domain.alpaca.marketdata.historical.quote.Quote", + "title": "The latest quote." + } + } +} diff --git a/schema_json/alpaca/market_data/historical/snapshot/snapshot.json b/schema_json/alpaca/market_data/historical/snapshot/snapshot.json new file mode 100644 index 00000000..408014b7 --- /dev/null +++ b/schema_json/alpaca/market_data/historical/snapshot/snapshot.json @@ -0,0 +1,26 @@ +{ + "type": "object", + "title": "See https://alpaca.markets/docs/api-documentation/api-v2/market-data/alpaca-data-api-v2/historical/", + "properties": { + "latestTrade": { + "existingJavaType": "net.jacobpeterson.domain.alpaca.marketdata.historical.trade.Trade", + "title": "Latest trade." + }, + "latestQuote": { + "existingJavaType": "net.jacobpeterson.domain.alpaca.marketdata.historical.quote.Quote", + "title": "Latest quote." + }, + "minuteBar": { + "existingJavaType": "net.jacobpeterson.domain.alpaca.marketdata.historical.bar.Bar", + "title": "Minute bar." + }, + "dailyBar": { + "existingJavaType": "net.jacobpeterson.domain.alpaca.marketdata.historical.bar.Bar", + "title": "Daily bar." + }, + "prevDailyBar": { + "existingJavaType": "net.jacobpeterson.domain.alpaca.marketdata.historical.bar.Bar", + "title": "Previous daily close bar." + } + } +} diff --git a/schema_json/alpaca/market_data/historical/trade/latest_trade_response.json b/schema_json/alpaca/market_data/historical/trade/latest_trade_response.json new file mode 100644 index 00000000..e951fbf2 --- /dev/null +++ b/schema_json/alpaca/market_data/historical/trade/latest_trade_response.json @@ -0,0 +1,14 @@ +{ + "type": "object", + "title": "See https://alpaca.markets/docs/api-documentation/api-v2/market-data/alpaca-data-api-v2/historical/", + "properties": { + "symbol": { + "existingJavaType": "java.lang.String", + "title": "Symbol that was queried." + }, + "trade": { + "existingJavaType": "net.jacobpeterson.domain.alpaca.marketdata.historical.trade.Trade", + "title": "The latest trade." + } + } +} diff --git a/src/main/java/net/jacobpeterson/alpaca/AlpacaAPI.java b/src/main/java/net/jacobpeterson/alpaca/AlpacaAPI.java index e3eca27b..f48ebf26 100644 --- a/src/main/java/net/jacobpeterson/alpaca/AlpacaAPI.java +++ b/src/main/java/net/jacobpeterson/alpaca/AlpacaAPI.java @@ -43,8 +43,11 @@ import net.jacobpeterson.domain.alpaca.clock.Clock; import net.jacobpeterson.domain.alpaca.marketdata.historical.bar.Bar; import net.jacobpeterson.domain.alpaca.marketdata.historical.bar.BarsResponse; +import net.jacobpeterson.domain.alpaca.marketdata.historical.quote.LatestQuoteResponse; import net.jacobpeterson.domain.alpaca.marketdata.historical.quote.Quote; import net.jacobpeterson.domain.alpaca.marketdata.historical.quote.QuotesResponse; +import net.jacobpeterson.domain.alpaca.marketdata.historical.snapshot.Snapshot; +import net.jacobpeterson.domain.alpaca.marketdata.historical.trade.LatestTradeResponse; import net.jacobpeterson.domain.alpaca.marketdata.historical.trade.Trade; import net.jacobpeterson.domain.alpaca.marketdata.historical.trade.TradesResponse; import net.jacobpeterson.domain.alpaca.order.CancelledOrder; @@ -64,7 +67,9 @@ import java.time.format.DateTimeFormatter; import java.util.ArrayList; import java.util.Arrays; +import java.util.HashMap; import java.util.List; +import java.util.Map; import java.util.StringJoiner; import java.util.stream.Collectors; @@ -1503,13 +1508,13 @@ public TradesResponse getTrades(String symbol, ZonedDateTime start, ZonedDateTim * * @param symbol the symbol to query for * - * @return the {@link Trade} + * @return the {@link LatestTradeResponse} * * @throws AlpacaAPIRequestException thrown for {@link AlpacaAPIRequestException}s * @see * Historical Market Data */ - public Trade getLatestTrade(String symbol) throws AlpacaAPIRequestException { + public LatestTradeResponse getLatestTrade(String symbol) throws AlpacaAPIRequestException { Preconditions.checkNotNull(symbol); AlpacaRequestBuilder urlBuilder = new AlpacaRequestBuilder(URLs.DATA, Endpoints.VERSION_2, @@ -1518,7 +1523,7 @@ public Trade getLatestTrade(String symbol) throws AlpacaAPIRequestException { Endpoints.TRADES, Endpoints.LATEST); - return alpacaRequest.get(urlBuilder, Trade.class); + return alpacaRequest.get(urlBuilder, LatestTradeResponse.class); } /** @@ -1568,13 +1573,13 @@ public QuotesResponse getQuotes(String symbol, ZonedDateTime start, ZonedDateTim * * @param symbol the symbol to query for * - * @return the {@link Quote} + * @return the {@link LatestQuoteResponse} * * @throws AlpacaAPIRequestException thrown for {@link AlpacaAPIRequestException}s * @see * Historical Market Data */ - public Quote getLatestQuote(String symbol) throws AlpacaAPIRequestException { + public LatestQuoteResponse getLatestQuote(String symbol) throws AlpacaAPIRequestException { Preconditions.checkNotNull(symbol); AlpacaRequestBuilder urlBuilder = new AlpacaRequestBuilder(URLs.DATA, Endpoints.VERSION_2, @@ -1583,7 +1588,7 @@ public Quote getLatestQuote(String symbol) throws AlpacaAPIRequestException { Endpoints.QUOTES, Endpoints.LATEST); - return alpacaRequest.get(urlBuilder, Quote.class); + return alpacaRequest.get(urlBuilder, LatestQuoteResponse.class); } /** @@ -1631,6 +1636,83 @@ public BarsResponse getBars(String symbol, ZonedDateTime start, ZonedDateTime en return alpacaRequest.get(urlBuilder, BarsResponse.class); } + /** + * The Snapshot API for one ticker provides the latest trade, latest quote, minute bar daily bar and previous daily + * bar data for a given ticker symbol + * + * @param symbols the symbols to query for + * + * @return a {@link Map} with they keys being the symbol and the value being the {@link Snapshot} + * + * @throws AlpacaAPIRequestException thrown for {@link AlpacaAPIRequestException}s + * @see + * Historical Market Data + */ + public Map getSnapshots(List symbols) throws AlpacaAPIRequestException { + Preconditions.checkNotNull(symbols); + Preconditions.checkState(!symbols.isEmpty()); + + AlpacaRequestBuilder urlBuilder = new AlpacaRequestBuilder(URLs.DATA, Endpoints.VERSION_2, + Endpoints.STOCKS, + Endpoints.SNAPSHOTS); + + urlBuilder.appendURLParameter(Parameters.SYMBOLS, String.join(",", symbols)); + + HttpResponse response; + try { + response = alpacaRequest.invokeGet(urlBuilder); + } catch (UnirestException exception) { + throw new AlpacaAPIRequestException(exception); + } + + if (response.getStatus() != 200) { + throw new AlpacaAPIRequestException(response); + } + + JsonElement responseJsonElement = alpacaRequest.getResponseJSON(response); + HashMap snapshotsOfSymbols = new HashMap<>(); + + if (responseJsonElement instanceof JsonObject) { + JsonObject responseJsonObject = (JsonObject) responseJsonElement; + + for (String symbol : responseJsonObject.keySet()) { // Loop through response object keys + JsonElement jsonObjectElement = responseJsonObject.get(symbol); + + if (jsonObjectElement instanceof JsonObject) { + snapshotsOfSymbols.put(symbol, GsonUtil.GSON.fromJson(jsonObjectElement, Snapshot.class)); + } else { + throw new IllegalStateException("The response object must only contain objects!"); + } + } + return snapshotsOfSymbols; + } else { + throw new IllegalStateException("The response must be an object!"); + } + } + + /** + * The Snapshot API for one ticker provides the latest trade, latest quote, minute bar daily bar and previous daily + * bar data for a given ticker symbol + * + * @param symbol the symbol to query for + * + * @return the {@link Snapshot} + * + * @throws AlpacaAPIRequestException thrown for {@link AlpacaAPIRequestException}s + * @see + * Historical Market Data + */ + public Snapshot getSnapshot(String symbol) throws AlpacaAPIRequestException { + Preconditions.checkNotNull(symbol); + + AlpacaRequestBuilder urlBuilder = new AlpacaRequestBuilder(URLs.DATA, Endpoints.VERSION_2, + Endpoints.STOCKS, + symbol, + Endpoints.SNAPSHOT); + + return alpacaRequest.get(urlBuilder, Snapshot.class); + } + /** * Adds the {@link AlpacaStreamListener}. Note that when the first {@link AlpacaStreamListener} is added, the * Websocket connection is created. diff --git a/src/main/java/net/jacobpeterson/alpaca/AlpacaConstants.java b/src/main/java/net/jacobpeterson/alpaca/AlpacaConstants.java index 74d39557..efda583b 100644 --- a/src/main/java/net/jacobpeterson/alpaca/AlpacaConstants.java +++ b/src/main/java/net/jacobpeterson/alpaca/AlpacaConstants.java @@ -35,7 +35,9 @@ public static class Endpoints { public static final String TRADES = "trades"; public static final String QUOTES = "quotes"; public static final String BARS = "bars"; - public static final String LATEST = "trades"; + public static final String SNAPSHOT = "snapshot"; + public static final String SNAPSHOTS = "snapshots"; + public static final String LATEST = "latest"; } /** From f279284daa6c5829a1da2617e190d0c2c30cf260 Mon Sep 17 00:00:00 2001 From: Jacob Peterson Date: Tue, 18 May 2021 22:55:51 -0600 Subject: [PATCH 113/271] Add WebsocketStateListener for Alpaca and MarketData streams --- .../AbstractWebsocketClientEndpoint.java | 28 ++++++++++++++++--- .../websocket/client/WebsocketClient.java | 7 +++++ .../client/WebsocketStateListener.java | 24 ++++++++++++++++ .../net/jacobpeterson/alpaca/AlpacaAPI.java | 19 +++++++++++++ .../broker/client/AlpacaWebsocketClient.java | 8 ++++++ .../client/MarketDataWebsocketClient.java | 8 ++++++ 6 files changed, 90 insertions(+), 4 deletions(-) create mode 100644 src/main/java/net/jacobpeterson/abstracts/websocket/client/WebsocketStateListener.java diff --git a/src/main/java/net/jacobpeterson/abstracts/websocket/client/AbstractWebsocketClientEndpoint.java b/src/main/java/net/jacobpeterson/abstracts/websocket/client/AbstractWebsocketClientEndpoint.java index 3f0e9a16..218dc960 100644 --- a/src/main/java/net/jacobpeterson/abstracts/websocket/client/AbstractWebsocketClientEndpoint.java +++ b/src/main/java/net/jacobpeterson/abstracts/websocket/client/AbstractWebsocketClientEndpoint.java @@ -38,6 +38,7 @@ public abstract class AbstractWebsocketClientEndpoint, T extends Strea * @return true if the Websocket is authenticated */ boolean isAuthenticated(); + + /** + * Sets the {@link WebsocketStateListener} for this {@link WebsocketClient}. + * + * @param websocketStateListener the {@link WebsocketStateListener} + */ + void setWebsocketStateListener(WebsocketStateListener websocketStateListener); } diff --git a/src/main/java/net/jacobpeterson/abstracts/websocket/client/WebsocketStateListener.java b/src/main/java/net/jacobpeterson/abstracts/websocket/client/WebsocketStateListener.java new file mode 100644 index 00000000..12b6df8d --- /dev/null +++ b/src/main/java/net/jacobpeterson/abstracts/websocket/client/WebsocketStateListener.java @@ -0,0 +1,24 @@ +package net.jacobpeterson.abstracts.websocket.client; + +/** + * {@link WebsocketStateListener} listens for various state changes in a {@link WebsocketClient}. + */ +public interface WebsocketStateListener { + + /** + * Called when the {@link WebsocketClient} is connected. + */ + void onConnected(); + + /** + * Called when the {@link WebsocketClient} is disconnected. + */ + void onDisconnected(); + + /** + * Called when a {@link WebsocketClient} error has occurred. + * + * @param cause the cause {@link Throwable} + */ + void onError(Throwable cause); +} diff --git a/src/main/java/net/jacobpeterson/alpaca/AlpacaAPI.java b/src/main/java/net/jacobpeterson/alpaca/AlpacaAPI.java index f48ebf26..acd18acb 100644 --- a/src/main/java/net/jacobpeterson/alpaca/AlpacaAPI.java +++ b/src/main/java/net/jacobpeterson/alpaca/AlpacaAPI.java @@ -9,6 +9,7 @@ import com.mashape.unirest.http.HttpResponse; import com.mashape.unirest.http.exceptions.UnirestException; import net.jacobpeterson.abstracts.enums.SortDirection; +import net.jacobpeterson.abstracts.websocket.client.WebsocketStateListener; import net.jacobpeterson.abstracts.websocket.exception.WebsocketException; import net.jacobpeterson.alpaca.AlpacaConstants.Endpoints; import net.jacobpeterson.alpaca.AlpacaConstants.Parameters; @@ -1737,6 +1738,15 @@ public void removeAlpacaStreamListener(AlpacaStreamListener streamListener) thro alpacaWebSocketClient.removeListener(streamListener); } + /** + * Sets the {@link AlpacaWebsocketClient} {@link WebsocketStateListener} to listen to websocket state changes. + * + * @param websocketStateListener the {@link WebsocketStateListener} + */ + public void setAlpacaStreamWebsocketStateListener(WebsocketStateListener websocketStateListener) { + alpacaWebSocketClient.setWebsocketStateListener(websocketStateListener); + } + /** * Adds the {@link MarketDataListener}. Note that when the first {@link AlpacaStreamListener} is added, the * Websocket connection is created. @@ -1761,6 +1771,15 @@ public void removeMarketDataStreamListener(MarketDataListener streamListener) th marketDataWebSocketClient.removeListener(streamListener); } + /** + * Sets the {@link MarketDataWebsocketClient} {@link WebsocketStateListener} to listen to websocket state changes. + * + * @param websocketStateListener the {@link WebsocketStateListener} + */ + public void setMarketDataStreamWebsocketStateListener(WebsocketStateListener websocketStateListener) { + marketDataWebSocketClient.setWebsocketStateListener(websocketStateListener); + } + @Override public String toString() { return new StringJoiner(", ", this.getClass().getSimpleName() + "[", "]") diff --git a/src/main/java/net/jacobpeterson/alpaca/websocket/broker/client/AlpacaWebsocketClient.java b/src/main/java/net/jacobpeterson/alpaca/websocket/broker/client/AlpacaWebsocketClient.java index e900ad7f..03d4fc70 100644 --- a/src/main/java/net/jacobpeterson/alpaca/websocket/broker/client/AlpacaWebsocketClient.java +++ b/src/main/java/net/jacobpeterson/alpaca/websocket/broker/client/AlpacaWebsocketClient.java @@ -7,6 +7,7 @@ import com.google.gson.JsonPrimitive; import com.google.gson.JsonSyntaxException; import net.jacobpeterson.abstracts.websocket.client.WebsocketClient; +import net.jacobpeterson.abstracts.websocket.client.WebsocketStateListener; import net.jacobpeterson.abstracts.websocket.exception.WebsocketException; import net.jacobpeterson.alpaca.enums.api.EndpointAPIType; import net.jacobpeterson.alpaca.websocket.broker.listener.AlpacaStreamListener; @@ -47,6 +48,7 @@ public class AlpacaWebsocketClient implements WebsocketClient listeners; private AlpacaWebsocketClientEndpoint alpacaWebsocketClientEndpoint; + private WebsocketStateListener websocketStateListener; private boolean authenticated; /** @@ -121,6 +123,7 @@ public void connect() throws URISyntaxException, IOException, DeploymentExceptio LOGGER.info("Connecting..."); alpacaWebsocketClientEndpoint = new AlpacaWebsocketClientEndpoint(this, new URI(streamAPIURL)); + alpacaWebsocketClientEndpoint.setWebsocketStateListener(websocketStateListener); alpacaWebsocketClientEndpoint.setAutomaticallyReconnect(true); alpacaWebsocketClientEndpoint.connect(); @@ -246,6 +249,11 @@ public boolean isAuthenticated() { return authenticated; } + @Override + public void setWebsocketStateListener(WebsocketStateListener websocketStateListener) { + this.websocketStateListener = websocketStateListener; + } + /** * Returns true if {@link AuthorizationMessage} states success. * diff --git a/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/client/MarketDataWebsocketClient.java b/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/client/MarketDataWebsocketClient.java index 33442ba5..54e1d98b 100644 --- a/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/client/MarketDataWebsocketClient.java +++ b/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/client/MarketDataWebsocketClient.java @@ -5,6 +5,7 @@ import com.google.gson.JsonElement; import com.google.gson.JsonObject; import net.jacobpeterson.abstracts.websocket.client.WebsocketClient; +import net.jacobpeterson.abstracts.websocket.client.WebsocketStateListener; import net.jacobpeterson.abstracts.websocket.exception.WebsocketException; import net.jacobpeterson.alpaca.enums.api.DataAPIType; import net.jacobpeterson.alpaca.websocket.broker.client.AlpacaWebsocketClient; @@ -50,6 +51,7 @@ public class MarketDataWebsocketClient implements WebsocketClient listeners; private MarketDataWebsocketClientEndpoint marketDataWebsocketClientEndpoint; + private WebsocketStateListener websocketStateListener; private boolean authenticated; /** @@ -125,6 +127,7 @@ public void connect() throws URISyntaxException, IOException, DeploymentExceptio LOGGER.info("Connecting..."); marketDataWebsocketClientEndpoint = new MarketDataWebsocketClientEndpoint(this, new URI(websocketAPIURL)); + marketDataWebsocketClientEndpoint.setWebsocketStateListener(websocketStateListener); marketDataWebsocketClientEndpoint.setAutomaticallyReconnect(true); marketDataWebsocketClientEndpoint.connect(); @@ -263,6 +266,11 @@ public boolean isAuthenticated() { return authenticated; } + @Override + public void setWebsocketStateListener(WebsocketStateListener websocketStateListener) { + this.websocketStateListener = websocketStateListener; + } + /** * Returns true if {@link SuccessMessage} states authentication. * From 462a2c4a07e83deb5d3ac820e752a1a3de3be735 Mon Sep 17 00:00:00 2001 From: Jacob Peterson Date: Tue, 18 May 2021 23:00:28 -0600 Subject: [PATCH 114/271] Update README Table of Contents --- README.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/README.md b/README.md index 5b900b19..b6081add 100644 --- a/README.md +++ b/README.md @@ -28,8 +28,11 @@ This is a Java implementation for the Alpaca Date: Tue, 18 May 2021 23:01:57 -0600 Subject: [PATCH 115/271] Update to 7.2 --- README.md | 4 ++-- build.gradle | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index b6081add..0d21f1da 100644 --- a/README.md +++ b/README.md @@ -42,7 +42,7 @@ If you are using Gradle as your build tool, add the following dependency to your ``` dependencies { - implementation group: 'net.jacobpeterson', name: 'alpaca-java', version: '7.1.1' + implementation group: 'net.jacobpeterson', name: 'alpaca-java', version: '7.2' } ``` @@ -52,7 +52,7 @@ If you are using Maven as your build tool, add the following dependency to your net.jacobpeterson alpaca-java - 7.1.1 + 7.2 compile ``` diff --git a/build.gradle b/build.gradle index 6fb85e6d..77e109f9 100644 --- a/build.gradle +++ b/build.gradle @@ -30,7 +30,7 @@ tasks.getByName("generateJsonSchema2Pojo").enabled(false) // Disable default 'js archivesBaseName = 'alpaca-java' group = 'net.jacobpeterson' -version = '7.1.1-SNAPSHOT' +version = '7.2-SNAPSHOT' repositories { mavenLocal() From 32cec3e2d3461c6307f8f56be8e7c0c3df7de2d3 Mon Sep 17 00:00:00 2001 From: Jacob Peterson Date: Thu, 27 May 2021 12:05:13 -0600 Subject: [PATCH 116/271] Add FEE ActivityType and remove redundant enum annotations --- .../account_activities/account_activity.json | 8 +++- .../non_trade_activity.json | 4 -- .../account_activities/trade_activity.json | 4 -- .../net/jacobpeterson/alpaca/AlpacaAPI.java | 18 ++++++--- .../jacobpeterson/alpaca/AlpacaConstants.java | 1 + .../alpaca/enums/activity/ActivityType.java | 37 ++----------------- 6 files changed, 24 insertions(+), 48 deletions(-) diff --git a/schema_json/alpaca/account_activities/account_activity.json b/schema_json/alpaca/account_activities/account_activity.json index 1a180f27..35537847 100644 --- a/schema_json/alpaca/account_activities/account_activity.json +++ b/schema_json/alpaca/account_activities/account_activity.json @@ -1,4 +1,10 @@ { "type": "object", - "title": "See https://docs.alpaca.markets/api-documentation/api-v2/account-activities/" + "title": "See https://docs.alpaca.markets/api-documentation/api-v2/account-activities/", + "properties": { + "activity_type": { + "existingJavaType": "net.jacobpeterson.alpaca.enums.activity.ActivityType", + "title": "See https://docs.alpaca.markets/api-documentation/api-v2/account-activities/#activity-types for a list of possible values." + } + } } diff --git a/schema_json/alpaca/account_activities/non_trade_activity.json b/schema_json/alpaca/account_activities/non_trade_activity.json index 0e961e4c..1edd8243 100644 --- a/schema_json/alpaca/account_activities/non_trade_activity.json +++ b/schema_json/alpaca/account_activities/non_trade_activity.json @@ -5,10 +5,6 @@ "existingJavaType": "net.jacobpeterson.domain.alpaca.accountactivities.AccountActivity" }, "properties": { - "activity_type": { - "existingJavaType": "net.jacobpeterson.alpaca.enums.activity.ActivityType", - "title": "See https://docs.alpaca.markets/api-documentation/api-v2/account-activities/#activity-types for a list of possible values." - }, "id": { "existingJavaType": "java.lang.String", "title": "An ID for the activity, always in “::” format. Can be sent as page_token in requests to facilitate the paging of results." diff --git a/schema_json/alpaca/account_activities/trade_activity.json b/schema_json/alpaca/account_activities/trade_activity.json index 579268d4..52b7abe2 100644 --- a/schema_json/alpaca/account_activities/trade_activity.json +++ b/schema_json/alpaca/account_activities/trade_activity.json @@ -5,10 +5,6 @@ "existingJavaType": "net.jacobpeterson.domain.alpaca.accountactivities.AccountActivity" }, "properties": { - "activity_type": { - "existingJavaType": "net.jacobpeterson.alpaca.enums.activity.ActivityType", - "title": "FILL" - }, "id": { "existingJavaType": "java.lang.String", "title": "An id for the activity. Always in “::” format. Can be sent as page_token in requests to facilitate the paging of results." diff --git a/src/main/java/net/jacobpeterson/alpaca/AlpacaAPI.java b/src/main/java/net/jacobpeterson/alpaca/AlpacaAPI.java index acd18acb..a4877704 100644 --- a/src/main/java/net/jacobpeterson/alpaca/AlpacaAPI.java +++ b/src/main/java/net/jacobpeterson/alpaca/AlpacaAPI.java @@ -151,7 +151,8 @@ public AlpacaAPI(String oAuthToken) { */ public AlpacaAPI(String keyID, String secret, String oAuthToken, EndpointAPIType endpointAPIType, DataAPIType dataAPIType) { - Preconditions.checkState((keyID != null && secret != null) || oAuthToken != null); + Preconditions.checkState((keyID != null && secret != null) || oAuthToken != null, + "You must specify a KeyID and Secret or an OAuthToken!"); Preconditions.checkNotNull(endpointAPIType); Preconditions.checkNotNull(dataAPIType); @@ -215,7 +216,7 @@ public List getAccountActivities(ZonedDateTime date, ZonedDateT if (activityTypes.length == 1) { // Get one activity urlBuilder.appendEndpoint(activityTypes[0].getAPIName()); } else { // Get list of activities - urlBuilder.appendURLParameter("activity_types", Arrays.stream(activityTypes) + urlBuilder.appendURLParameter(Parameters.ACTIVITY_TYPES, Arrays.stream(activityTypes) .map(ActivityType::getAPIName).collect(Collectors.joining(","))); // Makes comma-separated list } } @@ -270,15 +271,22 @@ public List getAccountActivities(ZonedDateTime date, ZonedDateT if (arrayJsonObject.has(ACTIVITY_TYPE)) { String activityType = arrayJsonObject.get(ACTIVITY_TYPE).getAsString(); + AccountActivity accountActivity; // A 'TradeActivity' always has 'activity_type' field as 'FILL' if (activityType.equals(ActivityType.FILL.getAPIName())) { - accountActivities.add(GsonUtil.GSON.fromJson(arrayJsonObject, TradeActivity.class)); + accountActivity = GsonUtil.GSON.fromJson(arrayJsonObject, TradeActivity.class); } else { - accountActivities.add(GsonUtil.GSON.fromJson(arrayJsonObject, NonTradeActivity.class)); + accountActivity = GsonUtil.GSON.fromJson(arrayJsonObject, NonTradeActivity.class); } + + if (accountActivity.getActivityType() == null) { + LOGGER.error("No Activity Type enum exists for {}\nReport this!", activityType); + } + + accountActivities.add(accountActivity); } else { - LOGGER.warn("JSON Object does not have 'activity_type' in response! {}", arrayJsonObject); + LOGGER.error("JSON Object does not have 'activity_type' in response! {}", arrayJsonObject); } } else { throw new IllegalStateException("All array elements must be objects!"); diff --git a/src/main/java/net/jacobpeterson/alpaca/AlpacaConstants.java b/src/main/java/net/jacobpeterson/alpaca/AlpacaConstants.java index efda583b..78cbd0d1 100644 --- a/src/main/java/net/jacobpeterson/alpaca/AlpacaConstants.java +++ b/src/main/java/net/jacobpeterson/alpaca/AlpacaConstants.java @@ -71,6 +71,7 @@ public static class Parameters { public static final String UNTIL = "until"; public static final String AFTER = "after"; public static final String LIMIT = "limit"; + public static final String ACTIVITY_TYPES = "activity_types"; public static final String STATUS = "status"; public static final String PERIOD = "period"; public static final String TIMEFRAME = "timeframe"; diff --git a/src/main/java/net/jacobpeterson/alpaca/enums/activity/ActivityType.java b/src/main/java/net/jacobpeterson/alpaca/enums/activity/ActivityType.java index eff31187..2d4c104d 100644 --- a/src/main/java/net/jacobpeterson/alpaca/enums/activity/ActivityType.java +++ b/src/main/java/net/jacobpeterson/alpaca/enums/activity/ActivityType.java @@ -1,6 +1,5 @@ package net.jacobpeterson.alpaca.enums.activity; -import com.google.gson.annotations.SerializedName; import net.jacobpeterson.abstracts.enums.APIName; /** @@ -15,135 +14,105 @@ public enum ActivityType implements APIName { // @see ActivityType#getAPIName() /** Order fills (both partial and full fills) */ - @SerializedName("FILL") FILL, /** Cash transactions (both CSD and CSW) */ - @SerializedName("TRANS") TRANS, /** Miscellaneous or rarely used activity types (All types except those in TRANS, DIV, or FILL) */ - @SerializedName("MISC") MISC, /** ACATS IN/OUT (Cash) */ - @SerializedName("ACATC") ACATC, /** ACATS IN/OUT (Securities) */ - @SerializedName("ACATS") ACATS, /** Cash deposit(+) */ - @SerializedName("CSD") CSD, /** Cash receipt(-) */ - @SerializedName("CSR") CSR, /** Cash withdrawal(-) */ - @SerializedName("CSW") CSW, /** Dividends */ - @SerializedName("DIV") DIV, /** Dividend (capital gain long term) */ - @SerializedName("DIVCGL") DIVCGL, /** Dividend (capital gain short term) */ - @SerializedName("DIVCGS") DIVCGS, /** Dividend fee */ - @SerializedName("DIVFEE") DIVFEE, /** Dividend adjusted (Foreign Tax Withheld) */ - @SerializedName("DIVFT") DIVFT, /** Dividend adjusted (NRA Withheld) */ - @SerializedName("DIVNRA") DIVNRA, /** Dividend return of capital */ - @SerializedName("DIVROC") DIVROC, /** Dividend adjusted (Tefra Withheld) */ - @SerializedName("DIVTW") DIVTW, /** Dividend (tax exempt) */ - @SerializedName("DIVTXEX") DIVTXEX, /** Interest (credit/margin) */ - @SerializedName("INT") INT, /** Interest adjusted (NRA Withheld) */ - @SerializedName("INTNRA") INTNRA, /** Interest adjusted (Tefra Withheld) */ - @SerializedName("INTTW") INTTW, /** Journal entry */ - @SerializedName("JNL") JNL, /** Journal entry (cash) */ - @SerializedName("JNLC") JNLC, /** Journal entry (stock) */ - @SerializedName("JNLS") JNLS, /** Merger/Acquisition */ - @SerializedName("MA") MA, /** Name change */ - @SerializedName("NC") NC, /** Option assignment */ - @SerializedName("OPASN") OPASN, /** Option expiration */ - @SerializedName("OPEXP") OPEXP, /** Option exercise */ - @SerializedName("OPXRC") OPXRC, /** Pass Thru Charge */ - @SerializedName("PTC") PTC, /** Pass Thru Rebate */ - @SerializedName("PTR") PTR, + /** REG/TAF fees */ + FEE, + /** Reorg CA */ - @SerializedName("REORG") REORG, /** Symbol change */ - @SerializedName("SC") SC, /** Stock spinoff */ - @SerializedName("SSO") SSO, /** Stock split */ From 6a223bb7e41549fede707d8221c5bcc28892a026 Mon Sep 17 00:00:00 2001 From: Jacob Peterson Date: Thu, 27 May 2021 12:06:07 -0600 Subject: [PATCH 117/271] Update to 7.2.1 --- README.md | 4 ++-- build.gradle | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 0d21f1da..84f2de01 100644 --- a/README.md +++ b/README.md @@ -42,7 +42,7 @@ If you are using Gradle as your build tool, add the following dependency to your ``` dependencies { - implementation group: 'net.jacobpeterson', name: 'alpaca-java', version: '7.2' + implementation group: 'net.jacobpeterson', name: 'alpaca-java', version: '7.2.1' } ``` @@ -52,7 +52,7 @@ If you are using Maven as your build tool, add the following dependency to your net.jacobpeterson alpaca-java - 7.2 + 7.2.1 compile ``` diff --git a/build.gradle b/build.gradle index 77e109f9..b009e2f2 100644 --- a/build.gradle +++ b/build.gradle @@ -30,7 +30,7 @@ tasks.getByName("generateJsonSchema2Pojo").enabled(false) // Disable default 'js archivesBaseName = 'alpaca-java' group = 'net.jacobpeterson' -version = '7.2-SNAPSHOT' +version = '7.2.1-SNAPSHOT' repositories { mavenLocal() From 11f4aa751d2f82259c0c8d0042f6cf6fc44974a0 Mon Sep 17 00:00:00 2001 From: Jacob Peterson Date: Thu, 24 Jun 2021 19:39:41 -0600 Subject: [PATCH 118/271] Quick refactoring in preparation for rewrite --- README.md | 27 +-------- build.gradle | 8 +-- schema_json/{alpaca => }/account/account.json | 0 .../account_activities/account_activity.json | 0 .../non_trade_activity.json | 2 +- .../account_activities/trade_activity.json | 2 +- .../account_configuration.json | 0 schema_json/{alpaca => }/asset/asset.json | 0 .../{alpaca => }/calendar/calendar.json | 0 schema_json/{alpaca => }/clock/clock.json | 0 .../market_data/historical/bar/bar.json | 0 .../historical/bar/bars_response.json | 2 +- .../quote/latest_quote_response.json | 2 +- .../market_data/historical/quote/quote.json | 0 .../historical/quote/quotes_response.json | 2 +- .../historical/snapshot/snapshot.json | 10 ++-- .../trade/latest_trade_response.json | 2 +- .../market_data/historical/trade/trade.json | 0 .../historical/trade/trades_response.json | 2 +- .../market_data/realtime/bar/bar_message.json | 2 +- .../realtime/control/error_message.json | 2 +- .../control/subscriptions_message.json | 2 +- .../realtime/control/success_message.json | 2 +- .../realtime/market_data_message.json | 2 +- .../realtime/quote/quote_message.json | 2 +- .../market_data/realtime/symbol_message.json | 2 +- .../realtime/trade/trade_message.json | 2 +- .../{alpaca => }/order/cancelled_order.json | 2 +- schema_json/{alpaca => }/order/order.json | 2 +- .../portfolio_history/portfolio_history.json | 0 .../position/close_position_order.json | 2 +- .../{alpaca => }/position/position.json | 0 .../streaming/account/account_update.json | 2 +- .../account/account_update_message.json | 4 +- .../streaming/alpaca_stream_message.json | 2 +- .../authorization/authorization_data.json | 0 .../authorization/authorization_message.json | 4 +- .../streaming/listening/listening_data.json | 0 .../listening/listening_message.json | 4 +- .../streaming/trade/trade_update.json | 2 +- .../streaming/trade/trade_update_message.json | 4 +- .../{alpaca => }/watchlist/watchlist.json | 2 +- .../net/jacobpeterson/alpaca/AlpacaAPI.java | 56 +++++++++---------- .../jacobpeterson/alpaca/AlpacaConstants.java | 8 +-- .../{ => alpaca}/abstracts/enums/APIName.java | 2 +- .../abstracts/enums/SortDirection.java | 2 +- .../abstracts/rest/AbstractRequest.java | 4 +- .../rest/AbstractRequestBuilder.java | 2 +- .../AbstractAPIRequestException.java | 2 +- .../AbstractWebsocketClientEndpoint.java | 2 +- .../websocket/client/WebsocketClient.java | 10 ++-- .../client/WebsocketStateListener.java | 2 +- .../exception/WebsocketException.java | 2 +- .../websocket/listener/StreamListener.java | 6 +- .../websocket/message/StreamMessage.java | 2 +- .../websocket/message/StreamMessageType.java | 2 +- .../alpaca/enums/account/AccountStatus.java | 2 +- .../alpaca/enums/activity/ActivityType.java | 2 +- .../alpaca/enums/asset/AssetStatus.java | 2 +- .../enums/marketdata/BarsTimeFrame.java | 2 +- .../alpaca/enums/order/OrderClass.java | 4 +- .../alpaca/enums/order/OrderSide.java | 4 +- .../alpaca/enums/order/OrderStatus.java | 4 +- .../alpaca/enums/order/OrderTimeInForce.java | 4 +- .../alpaca/enums/order/OrderType.java | 4 +- .../enums/portfolio/PortfolioPeriodUnit.java | 4 +- .../enums/portfolio/PortfolioTimeFrame.java | 6 +- .../enums/streaming/TradeUpdateEvent.java | 4 +- .../alpaca/properties/AlpacaProperties.java | 12 +++- .../alpaca/rest/AlpacaRequest.java | 5 +- .../alpaca/rest/AlpacaRequestBuilder.java | 5 +- .../exception/AlpacaAPIRequestException.java | 8 +-- .../util/doc/Documentation2JSONSchema.java | 2 +- .../{ => alpaca}/util/format/FormatUtil.java | 20 +------ .../{ => alpaca}/util/gson/GsonUtil.java | 2 +- .../util/gson/LocalDateAdapter.java | 2 +- .../util/gson/ZonedDateTimeAdapter.java | 2 +- .../util/properties/PropertyUtil.java | 4 +- .../util/reflection/ReflectionUtil.java | 2 +- .../broker/client/AlpacaWebsocketClient.java | 22 ++++---- .../client/AlpacaWebsocketClientEndpoint.java | 6 +- .../broker/listener/AlpacaStreamListener.java | 4 +- .../listener/AlpacaStreamListenerAdapter.java | 2 +- .../message/AlpacaStreamMessageType.java | 6 +- .../client/MarketDataWebsocketClient.java | 28 +++++----- .../MarketDataWebsocketClientEndpoint.java | 7 ++- .../listener/MarketDataListener.java | 4 +- .../listener/MarketDataListenerAdapter.java | 2 +- .../message/MarketDataMessageType.java | 4 +- .../alpaca/AlpacaAPIEndpointTest.java | 27 ++++----- 90 files changed, 194 insertions(+), 231 deletions(-) rename schema_json/{alpaca => }/account/account.json (100%) rename schema_json/{alpaca => }/account_activities/account_activity.json (100%) rename schema_json/{alpaca => }/account_activities/non_trade_activity.json (96%) rename schema_json/{alpaca => }/account_activities/trade_activity.json (96%) rename schema_json/{alpaca => }/account_configuration/account_configuration.json (100%) rename schema_json/{alpaca => }/asset/asset.json (100%) rename schema_json/{alpaca => }/calendar/calendar.json (100%) rename schema_json/{alpaca => }/clock/clock.json (100%) rename schema_json/{alpaca => }/market_data/historical/bar/bar.json (100%) rename schema_json/{alpaca => }/market_data/historical/bar/bars_response.json (93%) rename schema_json/{alpaca => }/market_data/historical/quote/latest_quote_response.json (88%) rename schema_json/{alpaca => }/market_data/historical/quote/quote.json (100%) rename schema_json/{alpaca => }/market_data/historical/quote/quotes_response.json (92%) rename schema_json/{alpaca => }/market_data/historical/snapshot/snapshot.json (68%) rename schema_json/{alpaca => }/market_data/historical/trade/latest_trade_response.json (88%) rename schema_json/{alpaca => }/market_data/historical/trade/trade.json (100%) rename schema_json/{alpaca => }/market_data/historical/trade/trades_response.json (92%) rename schema_json/{alpaca => }/market_data/realtime/bar/bar_message.json (93%) rename schema_json/{alpaca => }/market_data/realtime/control/error_message.json (88%) rename schema_json/{alpaca => }/market_data/realtime/control/subscriptions_message.json (91%) rename schema_json/{alpaca => }/market_data/realtime/control/success_message.json (86%) rename schema_json/{alpaca => }/market_data/realtime/market_data_message.json (85%) rename schema_json/{alpaca => }/market_data/realtime/quote/quote_message.json (95%) rename schema_json/{alpaca => }/market_data/realtime/symbol_message.json (86%) rename schema_json/{alpaca => }/market_data/realtime/trade/trade_message.json (94%) rename schema_json/{alpaca => }/order/cancelled_order.json (88%) rename schema_json/{alpaca => }/order/order.json (99%) rename schema_json/{alpaca => }/portfolio_history/portfolio_history.json (100%) rename schema_json/{alpaca => }/position/close_position_order.json (89%) rename schema_json/{alpaca => }/position/position.json (100%) rename schema_json/{alpaca => }/streaming/account/account_update.json (77%) rename schema_json/{alpaca => }/streaming/account/account_update_message.json (70%) rename schema_json/{alpaca => }/streaming/alpaca_stream_message.json (84%) rename schema_json/{alpaca => }/streaming/authorization/authorization_data.json (100%) rename schema_json/{alpaca => }/streaming/authorization/authorization_message.json (72%) rename schema_json/{alpaca => }/streaming/listening/listening_data.json (100%) rename schema_json/{alpaca => }/streaming/listening/listening_message.json (71%) rename schema_json/{alpaca => }/streaming/trade/trade_update.json (91%) rename schema_json/{alpaca => }/streaming/trade/trade_update_message.json (69%) rename schema_json/{alpaca => }/watchlist/watchlist.json (96%) rename src/main/java/net/jacobpeterson/{ => alpaca}/abstracts/enums/APIName.java (82%) rename src/main/java/net/jacobpeterson/{ => alpaca}/abstracts/enums/SortDirection.java (91%) rename src/main/java/net/jacobpeterson/{ => alpaca}/abstracts/rest/AbstractRequest.java (98%) rename src/main/java/net/jacobpeterson/{ => alpaca}/abstracts/rest/AbstractRequestBuilder.java (98%) rename src/main/java/net/jacobpeterson/{ => alpaca}/abstracts/rest/exception/AbstractAPIRequestException.java (98%) rename src/main/java/net/jacobpeterson/{ => alpaca}/abstracts/websocket/client/AbstractWebsocketClientEndpoint.java (99%) rename src/main/java/net/jacobpeterson/{ => alpaca}/abstracts/websocket/client/WebsocketClient.java (87%) rename src/main/java/net/jacobpeterson/{ => alpaca}/abstracts/websocket/client/WebsocketStateListener.java (89%) rename src/main/java/net/jacobpeterson/{ => alpaca}/abstracts/websocket/exception/WebsocketException.java (92%) rename src/main/java/net/jacobpeterson/{ => alpaca}/abstracts/websocket/listener/StreamListener.java (71%) rename src/main/java/net/jacobpeterson/{ => alpaca}/abstracts/websocket/message/StreamMessage.java (64%) rename src/main/java/net/jacobpeterson/{ => alpaca}/abstracts/websocket/message/StreamMessageType.java (67%) rename src/main/java/net/jacobpeterson/{ => alpaca}/util/doc/Documentation2JSONSchema.java (99%) rename src/main/java/net/jacobpeterson/{ => alpaca}/util/format/FormatUtil.java (71%) rename src/main/java/net/jacobpeterson/{ => alpaca}/util/gson/GsonUtil.java (98%) rename src/main/java/net/jacobpeterson/{ => alpaca}/util/gson/LocalDateAdapter.java (95%) rename src/main/java/net/jacobpeterson/{ => alpaca}/util/gson/ZonedDateTimeAdapter.java (96%) rename src/main/java/net/jacobpeterson/{ => alpaca}/util/properties/PropertyUtil.java (97%) rename src/main/java/net/jacobpeterson/{ => alpaca}/util/reflection/ReflectionUtil.java (97%) diff --git a/README.md b/README.md index 84f2de01..d7fd846b 100644 --- a/README.md +++ b/README.md @@ -10,32 +10,7 @@ ## Overview This is a Java implementation for the Alpaca API. Alpaca lets you trade with algorithms, connect with apps, and build services all with a commission-free stock trading API. This library is community developed and if you have any questions, please ask them on [Github Discussions](https://github.com/Petersoj/alpaca-java/discussions), the [Alpaca Slack #dev-alpaca-java channel](https://alpaca.markets/slack), or on the [Alpaca Forums](https://forum.alpaca.markets/). -## Table of Contents -1. [Gradle and Maven Integration](#Gradle-and-Maven-Integration) -1. [Configuration](#Configuration) -1. [Logger](#Logger) -1. [AlpacaAPI](#AlpacaAPI) - - [AlpacaAPIRequestException](#AlpacaAPIRequestException) - - [Account](#Account) - - [AccountActivity](#AccountActivity) - - [AccountConfiguration](#AccountConfiguration) - - [Orders](#Orders) - - [Positions](#Positions) - - [Assets](#Assets) - - [Watchlist](#Watchlist) - - [PortfolioHistory](#PortfolioHistory) - - [Calendar](#Calendar) - - [Clock](#Clock) - - [Alpaca Streaming](#Alpaca-Streaming) - - [Trades](#Trades) - - [Latest Trade](#Latest-Trade) - - [Quotes](#Quotes) - - [Latest Quote](#Latest-Quote) - - [Bars](#Bars) - - [Snapshot](#Snapshot) - - [Realtime Market Data](#Realtime-Market-Data) -1. [Building](#Building) -1. [Testing](#Testing) +Give this repository a star ⭐ if it helped you build an awesome trading algorithm in Java! ## Gradle and Maven Integration If you are using Gradle as your build tool, add the following dependency to your `build.gradle` file: diff --git a/build.gradle b/build.gradle index b009e2f2..17afefa7 100644 --- a/build.gradle +++ b/build.gradle @@ -3,7 +3,6 @@ import org.jsonschema2pojo.gradle.GenerateJsonSchemaJavaTask buildscript { repositories { mavenCentral() - jcenter() } dependencies { @@ -33,9 +32,7 @@ group = 'net.jacobpeterson' version = '7.2.1-SNAPSHOT' repositories { - mavenLocal() mavenCentral() - jcenter() } dependencies { @@ -163,6 +160,7 @@ nexusStaging { processResources { exclude('alpaca.properties') } + // // END publishing // @@ -170,6 +168,7 @@ processResources { // // START POJO generation // + // The generated POJOs will be in a package structure analogous to the path in the 'schema_json/' directory // See: https://github.com/joelittlejohn/jsonschema2pojo/wiki/Reference @@ -177,7 +176,7 @@ final def targetDirectoryPath = file("${project.buildDir}/generated-sources/sche task generatePOJOs() { final def jsonSourceDirectory = file("${project.projectDir}/schema_json").getPath() - final def jsonPackageNameStart = "net.jacobpeterson.domain" + final def jsonPackageNameStart = "net.jacobpeterson.alpaca.domain" // Loop through all files in schema JSON file tree file(jsonSourceDirectory).eachFileRecurse { jsonFile -> @@ -232,6 +231,7 @@ jsonSchema2Pojo { includeGetters = true includeSetters = true } + // // END POJO generation // diff --git a/schema_json/alpaca/account/account.json b/schema_json/account/account.json similarity index 100% rename from schema_json/alpaca/account/account.json rename to schema_json/account/account.json diff --git a/schema_json/alpaca/account_activities/account_activity.json b/schema_json/account_activities/account_activity.json similarity index 100% rename from schema_json/alpaca/account_activities/account_activity.json rename to schema_json/account_activities/account_activity.json diff --git a/schema_json/alpaca/account_activities/non_trade_activity.json b/schema_json/account_activities/non_trade_activity.json similarity index 96% rename from schema_json/alpaca/account_activities/non_trade_activity.json rename to schema_json/account_activities/non_trade_activity.json index 1edd8243..e559fe07 100644 --- a/schema_json/alpaca/account_activities/non_trade_activity.json +++ b/schema_json/account_activities/non_trade_activity.json @@ -2,7 +2,7 @@ "type": "object", "title": "See https://docs.alpaca.markets/api-documentation/api-v2/account-activities/", "extends": { - "existingJavaType": "net.jacobpeterson.domain.alpaca.accountactivities.AccountActivity" + "existingJavaType": "net.jacobpeterson.alpaca.domain.accountactivities.AccountActivity" }, "properties": { "id": { diff --git a/schema_json/alpaca/account_activities/trade_activity.json b/schema_json/account_activities/trade_activity.json similarity index 96% rename from schema_json/alpaca/account_activities/trade_activity.json rename to schema_json/account_activities/trade_activity.json index 52b7abe2..55b2c59d 100644 --- a/schema_json/alpaca/account_activities/trade_activity.json +++ b/schema_json/account_activities/trade_activity.json @@ -2,7 +2,7 @@ "type": "object", "title": "See https://docs.alpaca.markets/api-documentation/api-v2/account-activities/", "extends": { - "existingJavaType": "net.jacobpeterson.domain.alpaca.accountactivities.AccountActivity" + "existingJavaType": "net.jacobpeterson.alpaca.domain.accountactivities.AccountActivity" }, "properties": { "id": { diff --git a/schema_json/alpaca/account_configuration/account_configuration.json b/schema_json/account_configuration/account_configuration.json similarity index 100% rename from schema_json/alpaca/account_configuration/account_configuration.json rename to schema_json/account_configuration/account_configuration.json diff --git a/schema_json/alpaca/asset/asset.json b/schema_json/asset/asset.json similarity index 100% rename from schema_json/alpaca/asset/asset.json rename to schema_json/asset/asset.json diff --git a/schema_json/alpaca/calendar/calendar.json b/schema_json/calendar/calendar.json similarity index 100% rename from schema_json/alpaca/calendar/calendar.json rename to schema_json/calendar/calendar.json diff --git a/schema_json/alpaca/clock/clock.json b/schema_json/clock/clock.json similarity index 100% rename from schema_json/alpaca/clock/clock.json rename to schema_json/clock/clock.json diff --git a/schema_json/alpaca/market_data/historical/bar/bar.json b/schema_json/market_data/historical/bar/bar.json similarity index 100% rename from schema_json/alpaca/market_data/historical/bar/bar.json rename to schema_json/market_data/historical/bar/bar.json diff --git a/schema_json/alpaca/market_data/historical/bar/bars_response.json b/schema_json/market_data/historical/bar/bars_response.json similarity index 93% rename from schema_json/alpaca/market_data/historical/bar/bars_response.json rename to schema_json/market_data/historical/bar/bars_response.json index 958fcd17..65b3baa4 100644 --- a/schema_json/alpaca/market_data/historical/bar/bars_response.json +++ b/schema_json/market_data/historical/bar/bars_response.json @@ -3,7 +3,7 @@ "title": "See https://alpaca.markets/docs/api-documentation/api-v2/market-data/alpaca-data-api-v2/historical/", "properties": { "bars": { - "existingJavaType": "java.util.ArrayList", + "existingJavaType": "java.util.ArrayList", "title": "Array of bars." }, "symbol": { diff --git a/schema_json/alpaca/market_data/historical/quote/latest_quote_response.json b/schema_json/market_data/historical/quote/latest_quote_response.json similarity index 88% rename from schema_json/alpaca/market_data/historical/quote/latest_quote_response.json rename to schema_json/market_data/historical/quote/latest_quote_response.json index 84c129ad..071e43ed 100644 --- a/schema_json/alpaca/market_data/historical/quote/latest_quote_response.json +++ b/schema_json/market_data/historical/quote/latest_quote_response.json @@ -7,7 +7,7 @@ "title": "Symbol that was queried." }, "quote": { - "existingJavaType": "net.jacobpeterson.domain.alpaca.marketdata.historical.quote.Quote", + "existingJavaType": "net.jacobpeterson.alpaca.domain.marketdata.historical.quote.Quote", "title": "The latest quote." } } diff --git a/schema_json/alpaca/market_data/historical/quote/quote.json b/schema_json/market_data/historical/quote/quote.json similarity index 100% rename from schema_json/alpaca/market_data/historical/quote/quote.json rename to schema_json/market_data/historical/quote/quote.json diff --git a/schema_json/alpaca/market_data/historical/quote/quotes_response.json b/schema_json/market_data/historical/quote/quotes_response.json similarity index 92% rename from schema_json/alpaca/market_data/historical/quote/quotes_response.json rename to schema_json/market_data/historical/quote/quotes_response.json index 55de6702..73ab9628 100644 --- a/schema_json/alpaca/market_data/historical/quote/quotes_response.json +++ b/schema_json/market_data/historical/quote/quotes_response.json @@ -3,7 +3,7 @@ "title": "See https://alpaca.markets/docs/api-documentation/api-v2/market-data/alpaca-data-api-v2/historical/", "properties": { "quotes": { - "existingJavaType": "java.util.ArrayList", + "existingJavaType": "java.util.ArrayList", "title": "Array of quotes." }, "symbol": { diff --git a/schema_json/alpaca/market_data/historical/snapshot/snapshot.json b/schema_json/market_data/historical/snapshot/snapshot.json similarity index 68% rename from schema_json/alpaca/market_data/historical/snapshot/snapshot.json rename to schema_json/market_data/historical/snapshot/snapshot.json index 408014b7..2226ffa0 100644 --- a/schema_json/alpaca/market_data/historical/snapshot/snapshot.json +++ b/schema_json/market_data/historical/snapshot/snapshot.json @@ -3,23 +3,23 @@ "title": "See https://alpaca.markets/docs/api-documentation/api-v2/market-data/alpaca-data-api-v2/historical/", "properties": { "latestTrade": { - "existingJavaType": "net.jacobpeterson.domain.alpaca.marketdata.historical.trade.Trade", + "existingJavaType": "net.jacobpeterson.alpaca.domain.marketdata.historical.trade.Trade", "title": "Latest trade." }, "latestQuote": { - "existingJavaType": "net.jacobpeterson.domain.alpaca.marketdata.historical.quote.Quote", + "existingJavaType": "net.jacobpeterson.alpaca.domain.marketdata.historical.quote.Quote", "title": "Latest quote." }, "minuteBar": { - "existingJavaType": "net.jacobpeterson.domain.alpaca.marketdata.historical.bar.Bar", + "existingJavaType": "net.jacobpeterson.alpaca.domain.marketdata.historical.bar.Bar", "title": "Minute bar." }, "dailyBar": { - "existingJavaType": "net.jacobpeterson.domain.alpaca.marketdata.historical.bar.Bar", + "existingJavaType": "net.jacobpeterson.alpaca.domain.marketdata.historical.bar.Bar", "title": "Daily bar." }, "prevDailyBar": { - "existingJavaType": "net.jacobpeterson.domain.alpaca.marketdata.historical.bar.Bar", + "existingJavaType": "net.jacobpeterson.alpaca.domain.marketdata.historical.bar.Bar", "title": "Previous daily close bar." } } diff --git a/schema_json/alpaca/market_data/historical/trade/latest_trade_response.json b/schema_json/market_data/historical/trade/latest_trade_response.json similarity index 88% rename from schema_json/alpaca/market_data/historical/trade/latest_trade_response.json rename to schema_json/market_data/historical/trade/latest_trade_response.json index e951fbf2..46010258 100644 --- a/schema_json/alpaca/market_data/historical/trade/latest_trade_response.json +++ b/schema_json/market_data/historical/trade/latest_trade_response.json @@ -7,7 +7,7 @@ "title": "Symbol that was queried." }, "trade": { - "existingJavaType": "net.jacobpeterson.domain.alpaca.marketdata.historical.trade.Trade", + "existingJavaType": "net.jacobpeterson.alpaca.domain.marketdata.historical.trade.Trade", "title": "The latest trade." } } diff --git a/schema_json/alpaca/market_data/historical/trade/trade.json b/schema_json/market_data/historical/trade/trade.json similarity index 100% rename from schema_json/alpaca/market_data/historical/trade/trade.json rename to schema_json/market_data/historical/trade/trade.json diff --git a/schema_json/alpaca/market_data/historical/trade/trades_response.json b/schema_json/market_data/historical/trade/trades_response.json similarity index 92% rename from schema_json/alpaca/market_data/historical/trade/trades_response.json rename to schema_json/market_data/historical/trade/trades_response.json index 3225389c..6134d448 100644 --- a/schema_json/alpaca/market_data/historical/trade/trades_response.json +++ b/schema_json/market_data/historical/trade/trades_response.json @@ -3,7 +3,7 @@ "title": "See https://alpaca.markets/docs/api-documentation/api-v2/market-data/alpaca-data-api-v2/historical/", "properties": { "trades": { - "existingJavaType": "java.util.ArrayList", + "existingJavaType": "java.util.ArrayList", "title": "Array of trades." }, "symbol": { diff --git a/schema_json/alpaca/market_data/realtime/bar/bar_message.json b/schema_json/market_data/realtime/bar/bar_message.json similarity index 93% rename from schema_json/alpaca/market_data/realtime/bar/bar_message.json rename to schema_json/market_data/realtime/bar/bar_message.json index 0fdad40d..5f882bcb 100644 --- a/schema_json/alpaca/market_data/realtime/bar/bar_message.json +++ b/schema_json/market_data/realtime/bar/bar_message.json @@ -1,7 +1,7 @@ { "type": "object", "extends": { - "existingJavaType": "net.jacobpeterson.domain.alpaca.marketdata.realtime.SymbolMessage" + "existingJavaType": "net.jacobpeterson.alpaca.domain.marketdata.realtime.SymbolMessage" }, "title": "See https://alpaca.markets/docs/api-documentation/api-v2/market-data/streaming/", "properties": { diff --git a/schema_json/alpaca/market_data/realtime/control/error_message.json b/schema_json/market_data/realtime/control/error_message.json similarity index 88% rename from schema_json/alpaca/market_data/realtime/control/error_message.json rename to schema_json/market_data/realtime/control/error_message.json index 6d25bc9b..54107768 100644 --- a/schema_json/alpaca/market_data/realtime/control/error_message.json +++ b/schema_json/market_data/realtime/control/error_message.json @@ -1,7 +1,7 @@ { "type": "object", "extends": { - "existingJavaType": "net.jacobpeterson.domain.alpaca.marketdata.realtime.MarketDataMessage" + "existingJavaType": "net.jacobpeterson.alpaca.domain.marketdata.realtime.MarketDataMessage" }, "title": "See https://alpaca.markets/docs/api-documentation/api-v2/market-data/streaming/", "properties": { diff --git a/schema_json/alpaca/market_data/realtime/control/subscriptions_message.json b/schema_json/market_data/realtime/control/subscriptions_message.json similarity index 91% rename from schema_json/alpaca/market_data/realtime/control/subscriptions_message.json rename to schema_json/market_data/realtime/control/subscriptions_message.json index 29cf1c22..0f2134ab 100644 --- a/schema_json/alpaca/market_data/realtime/control/subscriptions_message.json +++ b/schema_json/market_data/realtime/control/subscriptions_message.json @@ -1,7 +1,7 @@ { "type": "object", "extends": { - "existingJavaType": "net.jacobpeterson.domain.alpaca.marketdata.realtime.MarketDataMessage" + "existingJavaType": "net.jacobpeterson.alpaca.domain.marketdata.realtime.MarketDataMessage" }, "title": "See https://alpaca.markets/docs/api-documentation/api-v2/market-data/streaming/", "properties": { diff --git a/schema_json/alpaca/market_data/realtime/control/success_message.json b/schema_json/market_data/realtime/control/success_message.json similarity index 86% rename from schema_json/alpaca/market_data/realtime/control/success_message.json rename to schema_json/market_data/realtime/control/success_message.json index 5478d3ef..0b161ae2 100644 --- a/schema_json/alpaca/market_data/realtime/control/success_message.json +++ b/schema_json/market_data/realtime/control/success_message.json @@ -1,7 +1,7 @@ { "type": "object", "extends": { - "existingJavaType": "net.jacobpeterson.domain.alpaca.marketdata.realtime.MarketDataMessage" + "existingJavaType": "net.jacobpeterson.alpaca.domain.marketdata.realtime.MarketDataMessage" }, "title": "See https://alpaca.markets/docs/api-documentation/api-v2/market-data/streaming/", "properties": { diff --git a/schema_json/alpaca/market_data/realtime/market_data_message.json b/schema_json/market_data/realtime/market_data_message.json similarity index 85% rename from schema_json/alpaca/market_data/realtime/market_data_message.json rename to schema_json/market_data/realtime/market_data_message.json index c63f6432..0643f9bf 100644 --- a/schema_json/alpaca/market_data/realtime/market_data_message.json +++ b/schema_json/market_data/realtime/market_data_message.json @@ -1,7 +1,7 @@ { "type": "object", "javaInterfaces": [ - "net.jacobpeterson.abstracts.websocket.message.StreamMessage" + "net.jacobpeterson.alpaca.abstracts.websocket.message.StreamMessage" ], "title": "See https://alpaca.markets/docs/api-documentation/api-v2/market-data/alpaca-data-api-v2/real-time/", "properties": { diff --git a/schema_json/alpaca/market_data/realtime/quote/quote_message.json b/schema_json/market_data/realtime/quote/quote_message.json similarity index 95% rename from schema_json/alpaca/market_data/realtime/quote/quote_message.json rename to schema_json/market_data/realtime/quote/quote_message.json index a7f6233c..c34e289b 100644 --- a/schema_json/alpaca/market_data/realtime/quote/quote_message.json +++ b/schema_json/market_data/realtime/quote/quote_message.json @@ -1,7 +1,7 @@ { "type": "object", "extends": { - "existingJavaType": "net.jacobpeterson.domain.alpaca.marketdata.realtime.SymbolMessage" + "existingJavaType": "net.jacobpeterson.alpaca.domain.marketdata.realtime.SymbolMessage" }, "title": "See https://alpaca.markets/docs/api-documentation/api-v2/market-data/streaming/", "properties": { diff --git a/schema_json/alpaca/market_data/realtime/symbol_message.json b/schema_json/market_data/realtime/symbol_message.json similarity index 86% rename from schema_json/alpaca/market_data/realtime/symbol_message.json rename to schema_json/market_data/realtime/symbol_message.json index 22c6aaa8..d430d031 100644 --- a/schema_json/alpaca/market_data/realtime/symbol_message.json +++ b/schema_json/market_data/realtime/symbol_message.json @@ -1,7 +1,7 @@ { "type": "object", "extends": { - "existingJavaType": "net.jacobpeterson.domain.alpaca.marketdata.realtime.MarketDataMessage" + "existingJavaType": "net.jacobpeterson.alpaca.domain.marketdata.realtime.MarketDataMessage" }, "title": "See https://alpaca.markets/docs/api-documentation/api-v2/market-data/streaming/", "properties": { diff --git a/schema_json/alpaca/market_data/realtime/trade/trade_message.json b/schema_json/market_data/realtime/trade/trade_message.json similarity index 94% rename from schema_json/alpaca/market_data/realtime/trade/trade_message.json rename to schema_json/market_data/realtime/trade/trade_message.json index 7aa28731..4b05a13b 100644 --- a/schema_json/alpaca/market_data/realtime/trade/trade_message.json +++ b/schema_json/market_data/realtime/trade/trade_message.json @@ -1,7 +1,7 @@ { "type": "object", "extends": { - "existingJavaType": "net.jacobpeterson.domain.alpaca.marketdata.realtime.SymbolMessage" + "existingJavaType": "net.jacobpeterson.alpaca.domain.marketdata.realtime.SymbolMessage" }, "title": "See https://alpaca.markets/docs/api-documentation/api-v2/market-data/streaming/", "properties": { diff --git a/schema_json/alpaca/order/cancelled_order.json b/schema_json/order/cancelled_order.json similarity index 88% rename from schema_json/alpaca/order/cancelled_order.json rename to schema_json/order/cancelled_order.json index 31266216..a4b7464d 100644 --- a/schema_json/alpaca/order/cancelled_order.json +++ b/schema_json/order/cancelled_order.json @@ -11,7 +11,7 @@ "title": "The HTTP status code of the cancel status" }, "body": { - "existingJavaType": "net.jacobpeterson.domain.alpaca.order.Order", + "existingJavaType": "net.jacobpeterson.alpaca.domain.order.Order", "javaName": "order", "title": "The cancelled order" } diff --git a/schema_json/alpaca/order/order.json b/schema_json/order/order.json similarity index 99% rename from schema_json/alpaca/order/order.json rename to schema_json/order/order.json index 22f4619e..c02647f0 100644 --- a/schema_json/alpaca/order/order.json +++ b/schema_json/order/order.json @@ -115,7 +115,7 @@ "title": "If true, eligible for execution outside regular trading hours." }, "legs": { - "existingJavaType": "java.util.ArrayList", + "existingJavaType": "java.util.ArrayList", "title": "When querying non-simple order_class orders in a nested style, an array of Order entities associated with this order. Otherwise, null." }, "trail_percent": { diff --git a/schema_json/alpaca/portfolio_history/portfolio_history.json b/schema_json/portfolio_history/portfolio_history.json similarity index 100% rename from schema_json/alpaca/portfolio_history/portfolio_history.json rename to schema_json/portfolio_history/portfolio_history.json diff --git a/schema_json/alpaca/position/close_position_order.json b/schema_json/position/close_position_order.json similarity index 89% rename from schema_json/alpaca/position/close_position_order.json rename to schema_json/position/close_position_order.json index 31f14ae3..9dc71a06 100644 --- a/schema_json/alpaca/position/close_position_order.json +++ b/schema_json/position/close_position_order.json @@ -11,7 +11,7 @@ "title": "The HTTP status code of the position-closing order status" }, "body": { - "existingJavaType": "net.jacobpeterson.domain.alpaca.order.Order", + "existingJavaType": "net.jacobpeterson.alpaca.domain.order.Order", "title": "The position-closing order" } } diff --git a/schema_json/alpaca/position/position.json b/schema_json/position/position.json similarity index 100% rename from schema_json/alpaca/position/position.json rename to schema_json/position/position.json diff --git a/schema_json/alpaca/streaming/account/account_update.json b/schema_json/streaming/account/account_update.json similarity index 77% rename from schema_json/alpaca/streaming/account/account_update.json rename to schema_json/streaming/account/account_update.json index 90c1b561..a7f81849 100644 --- a/schema_json/alpaca/streaming/account/account_update.json +++ b/schema_json/streaming/account/account_update.json @@ -1,7 +1,7 @@ { "type": "object", "extends": { - "existingJavaType": "net.jacobpeterson.domain.alpaca.account.Account" + "existingJavaType": "net.jacobpeterson.alpaca.domain.account.Account" }, "title": "See https://docs.alpaca.markets/api-documentation/api-v2/streaming/" } diff --git a/schema_json/alpaca/streaming/account/account_update_message.json b/schema_json/streaming/account/account_update_message.json similarity index 70% rename from schema_json/alpaca/streaming/account/account_update_message.json rename to schema_json/streaming/account/account_update_message.json index 93028221..6b548955 100644 --- a/schema_json/alpaca/streaming/account/account_update_message.json +++ b/schema_json/streaming/account/account_update_message.json @@ -1,12 +1,12 @@ { "type": "object", "extends": { - "existingJavaType": "net.jacobpeterson.domain.alpaca.streaming.AlpacaStreamMessage" + "existingJavaType": "net.jacobpeterson.alpaca.domain.streaming.AlpacaStreamMessage" }, "title": "See https://docs.alpaca.markets/api-documentation/api-v2/streaming/", "properties": { "data": { - "existingJavaType": "net.jacobpeterson.domain.alpaca.streaming.account.AccountUpdate" + "existingJavaType": "net.jacobpeterson.alpaca.domain.streaming.account.AccountUpdate" } } } diff --git a/schema_json/alpaca/streaming/alpaca_stream_message.json b/schema_json/streaming/alpaca_stream_message.json similarity index 84% rename from schema_json/alpaca/streaming/alpaca_stream_message.json rename to schema_json/streaming/alpaca_stream_message.json index 50a446fa..a64cf72b 100644 --- a/schema_json/alpaca/streaming/alpaca_stream_message.json +++ b/schema_json/streaming/alpaca_stream_message.json @@ -1,7 +1,7 @@ { "type": "object", "javaInterfaces": [ - "net.jacobpeterson.abstracts.websocket.message.StreamMessage" + "net.jacobpeterson.alpaca.abstracts.websocket.message.StreamMessage" ], "title": "See https://docs.alpaca.markets/api-documentation/api-v2/streaming/", "properties": { diff --git a/schema_json/alpaca/streaming/authorization/authorization_data.json b/schema_json/streaming/authorization/authorization_data.json similarity index 100% rename from schema_json/alpaca/streaming/authorization/authorization_data.json rename to schema_json/streaming/authorization/authorization_data.json diff --git a/schema_json/alpaca/streaming/authorization/authorization_message.json b/schema_json/streaming/authorization/authorization_message.json similarity index 72% rename from schema_json/alpaca/streaming/authorization/authorization_message.json rename to schema_json/streaming/authorization/authorization_message.json index e067a5d7..16a72db5 100644 --- a/schema_json/alpaca/streaming/authorization/authorization_message.json +++ b/schema_json/streaming/authorization/authorization_message.json @@ -2,11 +2,11 @@ "type": "object", "title": "See https://docs.alpaca.markets/api-documentation/api-v2/streaming/", "extends": { - "existingJavaType": "net.jacobpeterson.domain.alpaca.streaming.AlpacaStreamMessage" + "existingJavaType": "net.jacobpeterson.alpaca.domain.streaming.AlpacaStreamMessage" }, "properties": { "data": { - "existingJavaType": "net.jacobpeterson.domain.alpaca.streaming.authorization.AuthorizationData", + "existingJavaType": "net.jacobpeterson.alpaca.domain.streaming.authorization.AuthorizationData", "title": "The data" } } diff --git a/schema_json/alpaca/streaming/listening/listening_data.json b/schema_json/streaming/listening/listening_data.json similarity index 100% rename from schema_json/alpaca/streaming/listening/listening_data.json rename to schema_json/streaming/listening/listening_data.json diff --git a/schema_json/alpaca/streaming/listening/listening_message.json b/schema_json/streaming/listening/listening_message.json similarity index 71% rename from schema_json/alpaca/streaming/listening/listening_message.json rename to schema_json/streaming/listening/listening_message.json index 6c99cfcf..796378b6 100644 --- a/schema_json/alpaca/streaming/listening/listening_message.json +++ b/schema_json/streaming/listening/listening_message.json @@ -2,11 +2,11 @@ "type": "object", "title": "See https://docs.alpaca.markets/api-documentation/api-v2/streaming/", "extends": { - "existingJavaType": "net.jacobpeterson.domain.alpaca.streaming.AlpacaStreamMessage" + "existingJavaType": "net.jacobpeterson.alpaca.domain.streaming.AlpacaStreamMessage" }, "properties": { "data": { - "existingJavaType": "net.jacobpeterson.domain.alpaca.streaming.listening.ListeningData", + "existingJavaType": "net.jacobpeterson.alpaca.domain.streaming.listening.ListeningData", "title": "The data" } } diff --git a/schema_json/alpaca/streaming/trade/trade_update.json b/schema_json/streaming/trade/trade_update.json similarity index 91% rename from schema_json/alpaca/streaming/trade/trade_update.json rename to schema_json/streaming/trade/trade_update.json index da0c0085..eb197257 100644 --- a/schema_json/alpaca/streaming/trade/trade_update.json +++ b/schema_json/streaming/trade/trade_update.json @@ -19,7 +19,7 @@ "title": "The position quantity" }, "order": { - "existingJavaType": "net.jacobpeterson.domain.alpaca.order.Order", + "existingJavaType": "net.jacobpeterson.alpaca.domain.order.Order", "title": "The Order" } } diff --git a/schema_json/alpaca/streaming/trade/trade_update_message.json b/schema_json/streaming/trade/trade_update_message.json similarity index 69% rename from schema_json/alpaca/streaming/trade/trade_update_message.json rename to schema_json/streaming/trade/trade_update_message.json index 4077af01..e99eb6c3 100644 --- a/schema_json/alpaca/streaming/trade/trade_update_message.json +++ b/schema_json/streaming/trade/trade_update_message.json @@ -1,12 +1,12 @@ { "type": "object", "extends": { - "existingJavaType": "net.jacobpeterson.domain.alpaca.streaming.AlpacaStreamMessage" + "existingJavaType": "net.jacobpeterson.alpaca.domain.streaming.AlpacaStreamMessage" }, "title": "See https://docs.alpaca.markets/api-documentation/api-v2/streaming/", "properties": { "data": { - "existingJavaType": "net.jacobpeterson.domain.alpaca.streaming.trade.TradeUpdate" + "existingJavaType": "net.jacobpeterson.alpaca.domain.streaming.trade.TradeUpdate" } } } diff --git a/schema_json/alpaca/watchlist/watchlist.json b/schema_json/watchlist/watchlist.json similarity index 96% rename from schema_json/alpaca/watchlist/watchlist.json rename to schema_json/watchlist/watchlist.json index 6f924558..61ea25cb 100644 --- a/schema_json/alpaca/watchlist/watchlist.json +++ b/schema_json/watchlist/watchlist.json @@ -23,7 +23,7 @@ "title": "Account ID" }, "assets": { - "existingJavaType": "java.util.ArrayList", + "existingJavaType": "java.util.ArrayList", "title": "The content of this watchlist, in the order as registered by the client" } } diff --git a/src/main/java/net/jacobpeterson/alpaca/AlpacaAPI.java b/src/main/java/net/jacobpeterson/alpaca/AlpacaAPI.java index a4877704..966f95ff 100644 --- a/src/main/java/net/jacobpeterson/alpaca/AlpacaAPI.java +++ b/src/main/java/net/jacobpeterson/alpaca/AlpacaAPI.java @@ -8,12 +8,35 @@ import com.google.gson.reflect.TypeToken; import com.mashape.unirest.http.HttpResponse; import com.mashape.unirest.http.exceptions.UnirestException; -import net.jacobpeterson.abstracts.enums.SortDirection; -import net.jacobpeterson.abstracts.websocket.client.WebsocketStateListener; -import net.jacobpeterson.abstracts.websocket.exception.WebsocketException; import net.jacobpeterson.alpaca.AlpacaConstants.Endpoints; import net.jacobpeterson.alpaca.AlpacaConstants.Parameters; import net.jacobpeterson.alpaca.AlpacaConstants.URLs; +import net.jacobpeterson.alpaca.abstracts.enums.SortDirection; +import net.jacobpeterson.alpaca.abstracts.websocket.client.WebsocketStateListener; +import net.jacobpeterson.alpaca.abstracts.websocket.exception.WebsocketException; +import net.jacobpeterson.alpaca.domain.account.Account; +import net.jacobpeterson.alpaca.domain.accountactivities.AccountActivity; +import net.jacobpeterson.alpaca.domain.accountactivities.NonTradeActivity; +import net.jacobpeterson.alpaca.domain.accountactivities.TradeActivity; +import net.jacobpeterson.alpaca.domain.accountconfiguration.AccountConfiguration; +import net.jacobpeterson.alpaca.domain.asset.Asset; +import net.jacobpeterson.alpaca.domain.calendar.Calendar; +import net.jacobpeterson.alpaca.domain.clock.Clock; +import net.jacobpeterson.alpaca.domain.marketdata.historical.bar.Bar; +import net.jacobpeterson.alpaca.domain.marketdata.historical.bar.BarsResponse; +import net.jacobpeterson.alpaca.domain.marketdata.historical.quote.LatestQuoteResponse; +import net.jacobpeterson.alpaca.domain.marketdata.historical.quote.Quote; +import net.jacobpeterson.alpaca.domain.marketdata.historical.quote.QuotesResponse; +import net.jacobpeterson.alpaca.domain.marketdata.historical.snapshot.Snapshot; +import net.jacobpeterson.alpaca.domain.marketdata.historical.trade.LatestTradeResponse; +import net.jacobpeterson.alpaca.domain.marketdata.historical.trade.Trade; +import net.jacobpeterson.alpaca.domain.marketdata.historical.trade.TradesResponse; +import net.jacobpeterson.alpaca.domain.order.CancelledOrder; +import net.jacobpeterson.alpaca.domain.order.Order; +import net.jacobpeterson.alpaca.domain.portfoliohistory.PortfolioHistory; +import net.jacobpeterson.alpaca.domain.position.ClosePositionOrder; +import net.jacobpeterson.alpaca.domain.position.Position; +import net.jacobpeterson.alpaca.domain.watchlist.Watchlist; import net.jacobpeterson.alpaca.enums.activity.ActivityType; import net.jacobpeterson.alpaca.enums.api.DataAPIType; import net.jacobpeterson.alpaca.enums.api.EndpointAPIType; @@ -30,35 +53,12 @@ import net.jacobpeterson.alpaca.rest.AlpacaRequest; import net.jacobpeterson.alpaca.rest.AlpacaRequestBuilder; import net.jacobpeterson.alpaca.rest.exception.AlpacaAPIRequestException; +import net.jacobpeterson.alpaca.util.format.FormatUtil; +import net.jacobpeterson.alpaca.util.gson.GsonUtil; import net.jacobpeterson.alpaca.websocket.broker.client.AlpacaWebsocketClient; import net.jacobpeterson.alpaca.websocket.broker.listener.AlpacaStreamListener; import net.jacobpeterson.alpaca.websocket.marketdata.client.MarketDataWebsocketClient; import net.jacobpeterson.alpaca.websocket.marketdata.listener.MarketDataListener; -import net.jacobpeterson.domain.alpaca.account.Account; -import net.jacobpeterson.domain.alpaca.accountactivities.AccountActivity; -import net.jacobpeterson.domain.alpaca.accountactivities.NonTradeActivity; -import net.jacobpeterson.domain.alpaca.accountactivities.TradeActivity; -import net.jacobpeterson.domain.alpaca.accountconfiguration.AccountConfiguration; -import net.jacobpeterson.domain.alpaca.asset.Asset; -import net.jacobpeterson.domain.alpaca.calendar.Calendar; -import net.jacobpeterson.domain.alpaca.clock.Clock; -import net.jacobpeterson.domain.alpaca.marketdata.historical.bar.Bar; -import net.jacobpeterson.domain.alpaca.marketdata.historical.bar.BarsResponse; -import net.jacobpeterson.domain.alpaca.marketdata.historical.quote.LatestQuoteResponse; -import net.jacobpeterson.domain.alpaca.marketdata.historical.quote.Quote; -import net.jacobpeterson.domain.alpaca.marketdata.historical.quote.QuotesResponse; -import net.jacobpeterson.domain.alpaca.marketdata.historical.snapshot.Snapshot; -import net.jacobpeterson.domain.alpaca.marketdata.historical.trade.LatestTradeResponse; -import net.jacobpeterson.domain.alpaca.marketdata.historical.trade.Trade; -import net.jacobpeterson.domain.alpaca.marketdata.historical.trade.TradesResponse; -import net.jacobpeterson.domain.alpaca.order.CancelledOrder; -import net.jacobpeterson.domain.alpaca.order.Order; -import net.jacobpeterson.domain.alpaca.portfoliohistory.PortfolioHistory; -import net.jacobpeterson.domain.alpaca.position.ClosePositionOrder; -import net.jacobpeterson.domain.alpaca.position.Position; -import net.jacobpeterson.domain.alpaca.watchlist.Watchlist; -import net.jacobpeterson.util.format.FormatUtil; -import net.jacobpeterson.util.gson.GsonUtil; import org.slf4j.Logger; import org.slf4j.LoggerFactory; diff --git a/src/main/java/net/jacobpeterson/alpaca/AlpacaConstants.java b/src/main/java/net/jacobpeterson/alpaca/AlpacaConstants.java index 78cbd0d1..4c8642d9 100644 --- a/src/main/java/net/jacobpeterson/alpaca/AlpacaConstants.java +++ b/src/main/java/net/jacobpeterson/alpaca/AlpacaConstants.java @@ -6,7 +6,7 @@ public class AlpacaConstants { /** - * Defines URLs of {@link AlpacaAPI} + * Defines URLs for {@link AlpacaAPI} */ public static class URLs { @@ -14,7 +14,7 @@ public static class URLs { } /** - * Defines endpoints of {@link AlpacaAPI}. + * Defines endpoints for {@link AlpacaAPI}. */ public static class Endpoints { @@ -41,7 +41,7 @@ public static class Endpoints { } /** - * Defines parameters of {@link AlpacaAPI}. + * Defines parameters for {@link AlpacaAPI}. */ public static class Parameters { @@ -81,7 +81,7 @@ public static class Parameters { } /** - * Defines miscellaneous fields of {@link AlpacaAPI}. + * Defines miscellaneous fields for {@link AlpacaAPI}. */ public static class Fields { diff --git a/src/main/java/net/jacobpeterson/abstracts/enums/APIName.java b/src/main/java/net/jacobpeterson/alpaca/abstracts/enums/APIName.java similarity index 82% rename from src/main/java/net/jacobpeterson/abstracts/enums/APIName.java rename to src/main/java/net/jacobpeterson/alpaca/abstracts/enums/APIName.java index b921793d..1f8ff1e1 100644 --- a/src/main/java/net/jacobpeterson/abstracts/enums/APIName.java +++ b/src/main/java/net/jacobpeterson/alpaca/abstracts/enums/APIName.java @@ -1,4 +1,4 @@ -package net.jacobpeterson.abstracts.enums; +package net.jacobpeterson.alpaca.abstracts.enums; /** * The {@link APIName} interface is for enums classes to define enum members with API names. diff --git a/src/main/java/net/jacobpeterson/abstracts/enums/SortDirection.java b/src/main/java/net/jacobpeterson/alpaca/abstracts/enums/SortDirection.java similarity index 91% rename from src/main/java/net/jacobpeterson/abstracts/enums/SortDirection.java rename to src/main/java/net/jacobpeterson/alpaca/abstracts/enums/SortDirection.java index 6b1b92ab..dde6620f 100644 --- a/src/main/java/net/jacobpeterson/abstracts/enums/SortDirection.java +++ b/src/main/java/net/jacobpeterson/alpaca/abstracts/enums/SortDirection.java @@ -1,4 +1,4 @@ -package net.jacobpeterson.abstracts.enums; +package net.jacobpeterson.alpaca.abstracts.enums; /** * {@link SortDirection} defines enums for directional sorting. diff --git a/src/main/java/net/jacobpeterson/abstracts/rest/AbstractRequest.java b/src/main/java/net/jacobpeterson/alpaca/abstracts/rest/AbstractRequest.java similarity index 98% rename from src/main/java/net/jacobpeterson/abstracts/rest/AbstractRequest.java rename to src/main/java/net/jacobpeterson/alpaca/abstracts/rest/AbstractRequest.java index 96c23a46..12f7d88c 100644 --- a/src/main/java/net/jacobpeterson/abstracts/rest/AbstractRequest.java +++ b/src/main/java/net/jacobpeterson/alpaca/abstracts/rest/AbstractRequest.java @@ -1,4 +1,4 @@ -package net.jacobpeterson.abstracts.rest; +package net.jacobpeterson.alpaca.abstracts.rest; import com.google.gson.JsonElement; import com.google.gson.stream.JsonReader; @@ -7,7 +7,7 @@ import com.mashape.unirest.http.exceptions.UnirestException; import com.mashape.unirest.request.GetRequest; import com.mashape.unirest.request.HttpRequestWithBody; -import net.jacobpeterson.util.gson.GsonUtil; +import net.jacobpeterson.alpaca.util.gson.GsonUtil; import org.slf4j.Logger; import org.slf4j.LoggerFactory; diff --git a/src/main/java/net/jacobpeterson/abstracts/rest/AbstractRequestBuilder.java b/src/main/java/net/jacobpeterson/alpaca/abstracts/rest/AbstractRequestBuilder.java similarity index 98% rename from src/main/java/net/jacobpeterson/abstracts/rest/AbstractRequestBuilder.java rename to src/main/java/net/jacobpeterson/alpaca/abstracts/rest/AbstractRequestBuilder.java index b0863e30..e829ea32 100644 --- a/src/main/java/net/jacobpeterson/abstracts/rest/AbstractRequestBuilder.java +++ b/src/main/java/net/jacobpeterson/alpaca/abstracts/rest/AbstractRequestBuilder.java @@ -1,4 +1,4 @@ -package net.jacobpeterson.abstracts.rest; +package net.jacobpeterson.alpaca.abstracts.rest; import com.google.gson.JsonElement; import com.google.gson.JsonObject; diff --git a/src/main/java/net/jacobpeterson/abstracts/rest/exception/AbstractAPIRequestException.java b/src/main/java/net/jacobpeterson/alpaca/abstracts/rest/exception/AbstractAPIRequestException.java similarity index 98% rename from src/main/java/net/jacobpeterson/abstracts/rest/exception/AbstractAPIRequestException.java rename to src/main/java/net/jacobpeterson/alpaca/abstracts/rest/exception/AbstractAPIRequestException.java index dde2d55c..5fbae9ac 100644 --- a/src/main/java/net/jacobpeterson/abstracts/rest/exception/AbstractAPIRequestException.java +++ b/src/main/java/net/jacobpeterson/alpaca/abstracts/rest/exception/AbstractAPIRequestException.java @@ -1,4 +1,4 @@ -package net.jacobpeterson.abstracts.rest.exception; +package net.jacobpeterson.alpaca.abstracts.rest.exception; import com.mashape.unirest.http.HttpResponse; diff --git a/src/main/java/net/jacobpeterson/abstracts/websocket/client/AbstractWebsocketClientEndpoint.java b/src/main/java/net/jacobpeterson/alpaca/abstracts/websocket/client/AbstractWebsocketClientEndpoint.java similarity index 99% rename from src/main/java/net/jacobpeterson/abstracts/websocket/client/AbstractWebsocketClientEndpoint.java rename to src/main/java/net/jacobpeterson/alpaca/abstracts/websocket/client/AbstractWebsocketClientEndpoint.java index 218dc960..72a8ed37 100644 --- a/src/main/java/net/jacobpeterson/abstracts/websocket/client/AbstractWebsocketClientEndpoint.java +++ b/src/main/java/net/jacobpeterson/alpaca/abstracts/websocket/client/AbstractWebsocketClientEndpoint.java @@ -1,4 +1,4 @@ -package net.jacobpeterson.abstracts.websocket.client; +package net.jacobpeterson.alpaca.abstracts.websocket.client; import org.eclipse.jetty.util.component.LifeCycle; import org.slf4j.Logger; diff --git a/src/main/java/net/jacobpeterson/abstracts/websocket/client/WebsocketClient.java b/src/main/java/net/jacobpeterson/alpaca/abstracts/websocket/client/WebsocketClient.java similarity index 87% rename from src/main/java/net/jacobpeterson/abstracts/websocket/client/WebsocketClient.java rename to src/main/java/net/jacobpeterson/alpaca/abstracts/websocket/client/WebsocketClient.java index af3ab671..7aa4ff7a 100644 --- a/src/main/java/net/jacobpeterson/abstracts/websocket/client/WebsocketClient.java +++ b/src/main/java/net/jacobpeterson/alpaca/abstracts/websocket/client/WebsocketClient.java @@ -1,9 +1,9 @@ -package net.jacobpeterson.abstracts.websocket.client; +package net.jacobpeterson.alpaca.abstracts.websocket.client; -import net.jacobpeterson.abstracts.websocket.exception.WebsocketException; -import net.jacobpeterson.abstracts.websocket.listener.StreamListener; -import net.jacobpeterson.abstracts.websocket.message.StreamMessage; -import net.jacobpeterson.abstracts.websocket.message.StreamMessageType; +import net.jacobpeterson.alpaca.abstracts.websocket.exception.WebsocketException; +import net.jacobpeterson.alpaca.abstracts.websocket.listener.StreamListener; +import net.jacobpeterson.alpaca.abstracts.websocket.message.StreamMessage; +import net.jacobpeterson.alpaca.abstracts.websocket.message.StreamMessageType; /** * {@link WebsocketClient} represents a client for a Websocket. diff --git a/src/main/java/net/jacobpeterson/abstracts/websocket/client/WebsocketStateListener.java b/src/main/java/net/jacobpeterson/alpaca/abstracts/websocket/client/WebsocketStateListener.java similarity index 89% rename from src/main/java/net/jacobpeterson/abstracts/websocket/client/WebsocketStateListener.java rename to src/main/java/net/jacobpeterson/alpaca/abstracts/websocket/client/WebsocketStateListener.java index 12b6df8d..36c76ebd 100644 --- a/src/main/java/net/jacobpeterson/abstracts/websocket/client/WebsocketStateListener.java +++ b/src/main/java/net/jacobpeterson/alpaca/abstracts/websocket/client/WebsocketStateListener.java @@ -1,4 +1,4 @@ -package net.jacobpeterson.abstracts.websocket.client; +package net.jacobpeterson.alpaca.abstracts.websocket.client; /** * {@link WebsocketStateListener} listens for various state changes in a {@link WebsocketClient}. diff --git a/src/main/java/net/jacobpeterson/abstracts/websocket/exception/WebsocketException.java b/src/main/java/net/jacobpeterson/alpaca/abstracts/websocket/exception/WebsocketException.java similarity index 92% rename from src/main/java/net/jacobpeterson/abstracts/websocket/exception/WebsocketException.java rename to src/main/java/net/jacobpeterson/alpaca/abstracts/websocket/exception/WebsocketException.java index 0c7d57ff..6d15d207 100644 --- a/src/main/java/net/jacobpeterson/abstracts/websocket/exception/WebsocketException.java +++ b/src/main/java/net/jacobpeterson/alpaca/abstracts/websocket/exception/WebsocketException.java @@ -1,4 +1,4 @@ -package net.jacobpeterson.abstracts.websocket.exception; +package net.jacobpeterson.alpaca.abstracts.websocket.exception; import java.io.IOException; diff --git a/src/main/java/net/jacobpeterson/abstracts/websocket/listener/StreamListener.java b/src/main/java/net/jacobpeterson/alpaca/abstracts/websocket/listener/StreamListener.java similarity index 71% rename from src/main/java/net/jacobpeterson/abstracts/websocket/listener/StreamListener.java rename to src/main/java/net/jacobpeterson/alpaca/abstracts/websocket/listener/StreamListener.java index 558a0dce..9b7cb113 100644 --- a/src/main/java/net/jacobpeterson/abstracts/websocket/listener/StreamListener.java +++ b/src/main/java/net/jacobpeterson/alpaca/abstracts/websocket/listener/StreamListener.java @@ -1,7 +1,7 @@ -package net.jacobpeterson.abstracts.websocket.listener; +package net.jacobpeterson.alpaca.abstracts.websocket.listener; -import net.jacobpeterson.abstracts.websocket.message.StreamMessage; -import net.jacobpeterson.abstracts.websocket.message.StreamMessageType; +import net.jacobpeterson.alpaca.abstracts.websocket.message.StreamMessage; +import net.jacobpeterson.alpaca.abstracts.websocket.message.StreamMessageType; /** * {@link StreamListener} is an interface for listening to stream updates. diff --git a/src/main/java/net/jacobpeterson/abstracts/websocket/message/StreamMessage.java b/src/main/java/net/jacobpeterson/alpaca/abstracts/websocket/message/StreamMessage.java similarity index 64% rename from src/main/java/net/jacobpeterson/abstracts/websocket/message/StreamMessage.java rename to src/main/java/net/jacobpeterson/alpaca/abstracts/websocket/message/StreamMessage.java index ab14452b..b0822851 100644 --- a/src/main/java/net/jacobpeterson/abstracts/websocket/message/StreamMessage.java +++ b/src/main/java/net/jacobpeterson/alpaca/abstracts/websocket/message/StreamMessage.java @@ -1,4 +1,4 @@ -package net.jacobpeterson.abstracts.websocket.message; +package net.jacobpeterson.alpaca.abstracts.websocket.message; /** * {@link StreamMessage} is an interface to define stream messages. diff --git a/src/main/java/net/jacobpeterson/abstracts/websocket/message/StreamMessageType.java b/src/main/java/net/jacobpeterson/alpaca/abstracts/websocket/message/StreamMessageType.java similarity index 67% rename from src/main/java/net/jacobpeterson/abstracts/websocket/message/StreamMessageType.java rename to src/main/java/net/jacobpeterson/alpaca/abstracts/websocket/message/StreamMessageType.java index 14409939..4275202b 100644 --- a/src/main/java/net/jacobpeterson/abstracts/websocket/message/StreamMessageType.java +++ b/src/main/java/net/jacobpeterson/alpaca/abstracts/websocket/message/StreamMessageType.java @@ -1,4 +1,4 @@ -package net.jacobpeterson.abstracts.websocket.message; +package net.jacobpeterson.alpaca.abstracts.websocket.message; /** * {@link StreamMessageType} is an interface to define a stream message type. diff --git a/src/main/java/net/jacobpeterson/alpaca/enums/account/AccountStatus.java b/src/main/java/net/jacobpeterson/alpaca/enums/account/AccountStatus.java index d4995559..ed2b8112 100644 --- a/src/main/java/net/jacobpeterson/alpaca/enums/account/AccountStatus.java +++ b/src/main/java/net/jacobpeterson/alpaca/enums/account/AccountStatus.java @@ -1,6 +1,6 @@ package net.jacobpeterson.alpaca.enums.account; -import net.jacobpeterson.abstracts.enums.APIName; +import net.jacobpeterson.alpaca.abstracts.enums.APIName; /** * {@link AccountStatus} defines enums for various account statuses. diff --git a/src/main/java/net/jacobpeterson/alpaca/enums/activity/ActivityType.java b/src/main/java/net/jacobpeterson/alpaca/enums/activity/ActivityType.java index 2d4c104d..97be6d6d 100644 --- a/src/main/java/net/jacobpeterson/alpaca/enums/activity/ActivityType.java +++ b/src/main/java/net/jacobpeterson/alpaca/enums/activity/ActivityType.java @@ -1,6 +1,6 @@ package net.jacobpeterson.alpaca.enums.activity; -import net.jacobpeterson.abstracts.enums.APIName; +import net.jacobpeterson.alpaca.abstracts.enums.APIName; /** * {@link ActivityType} defines enums for various account activity types. diff --git a/src/main/java/net/jacobpeterson/alpaca/enums/asset/AssetStatus.java b/src/main/java/net/jacobpeterson/alpaca/enums/asset/AssetStatus.java index 261cf8a3..984fad0f 100644 --- a/src/main/java/net/jacobpeterson/alpaca/enums/asset/AssetStatus.java +++ b/src/main/java/net/jacobpeterson/alpaca/enums/asset/AssetStatus.java @@ -1,6 +1,6 @@ package net.jacobpeterson.alpaca.enums.asset; -import net.jacobpeterson.abstracts.enums.APIName; +import net.jacobpeterson.alpaca.abstracts.enums.APIName; /** * {@link AssetStatus} defines enums of various asset statuses. diff --git a/src/main/java/net/jacobpeterson/alpaca/enums/marketdata/BarsTimeFrame.java b/src/main/java/net/jacobpeterson/alpaca/enums/marketdata/BarsTimeFrame.java index fd56cdf8..fade0d84 100644 --- a/src/main/java/net/jacobpeterson/alpaca/enums/marketdata/BarsTimeFrame.java +++ b/src/main/java/net/jacobpeterson/alpaca/enums/marketdata/BarsTimeFrame.java @@ -1,6 +1,6 @@ package net.jacobpeterson.alpaca.enums.marketdata; -import net.jacobpeterson.abstracts.enums.APIName; +import net.jacobpeterson.alpaca.abstracts.enums.APIName; /** * The {@link BarsTimeFrame} enum defines time frame enums for the Bars endpoint. diff --git a/src/main/java/net/jacobpeterson/alpaca/enums/order/OrderClass.java b/src/main/java/net/jacobpeterson/alpaca/enums/order/OrderClass.java index 8850e28e..ce94ea2b 100644 --- a/src/main/java/net/jacobpeterson/alpaca/enums/order/OrderClass.java +++ b/src/main/java/net/jacobpeterson/alpaca/enums/order/OrderClass.java @@ -1,9 +1,9 @@ package net.jacobpeterson.alpaca.enums.order; -import net.jacobpeterson.abstracts.enums.APIName; +import net.jacobpeterson.alpaca.abstracts.enums.APIName; /** - * {@link OrderClass} defines enums of various {@link net.jacobpeterson.domain.alpaca.order.Order} classes. + * {@link OrderClass} defines enums of various {@link net.jacobpeterson.alpaca.domain.order.Order} classes. */ public enum OrderClass implements APIName { diff --git a/src/main/java/net/jacobpeterson/alpaca/enums/order/OrderSide.java b/src/main/java/net/jacobpeterson/alpaca/enums/order/OrderSide.java index aa28c6a1..b976215b 100644 --- a/src/main/java/net/jacobpeterson/alpaca/enums/order/OrderSide.java +++ b/src/main/java/net/jacobpeterson/alpaca/enums/order/OrderSide.java @@ -1,9 +1,9 @@ package net.jacobpeterson.alpaca.enums.order; -import net.jacobpeterson.abstracts.enums.APIName; +import net.jacobpeterson.alpaca.abstracts.enums.APIName; /** - * {@link OrderSide} defines enums for various {@link net.jacobpeterson.domain.alpaca.order.Order} sides. + * {@link OrderSide} defines enums for various {@link net.jacobpeterson.alpaca.domain.order.Order} sides. */ public enum OrderSide implements APIName { diff --git a/src/main/java/net/jacobpeterson/alpaca/enums/order/OrderStatus.java b/src/main/java/net/jacobpeterson/alpaca/enums/order/OrderStatus.java index 76476041..efcca514 100644 --- a/src/main/java/net/jacobpeterson/alpaca/enums/order/OrderStatus.java +++ b/src/main/java/net/jacobpeterson/alpaca/enums/order/OrderStatus.java @@ -1,9 +1,9 @@ package net.jacobpeterson.alpaca.enums.order; -import net.jacobpeterson.abstracts.enums.APIName; +import net.jacobpeterson.alpaca.abstracts.enums.APIName; /** - * {@link OrderStatus} defines enums for various {@link net.jacobpeterson.domain.alpaca.order.Order} statuses. + * {@link OrderStatus} defines enums for various {@link net.jacobpeterson.alpaca.domain.order.Order} statuses. */ public enum OrderStatus implements APIName { diff --git a/src/main/java/net/jacobpeterson/alpaca/enums/order/OrderTimeInForce.java b/src/main/java/net/jacobpeterson/alpaca/enums/order/OrderTimeInForce.java index 39e5752f..3f941377 100644 --- a/src/main/java/net/jacobpeterson/alpaca/enums/order/OrderTimeInForce.java +++ b/src/main/java/net/jacobpeterson/alpaca/enums/order/OrderTimeInForce.java @@ -1,9 +1,9 @@ package net.jacobpeterson.alpaca.enums.order; -import net.jacobpeterson.abstracts.enums.APIName; +import net.jacobpeterson.alpaca.abstracts.enums.APIName; /** - * {@link OrderTimeInForce} defines enums for various {@link net.jacobpeterson.domain.alpaca.order.Order} TIFs. + * {@link OrderTimeInForce} defines enums for various {@link net.jacobpeterson.alpaca.domain.order.Order} TIFs. * * @see Time in Force */ diff --git a/src/main/java/net/jacobpeterson/alpaca/enums/order/OrderType.java b/src/main/java/net/jacobpeterson/alpaca/enums/order/OrderType.java index 32b2316d..ea557311 100644 --- a/src/main/java/net/jacobpeterson/alpaca/enums/order/OrderType.java +++ b/src/main/java/net/jacobpeterson/alpaca/enums/order/OrderType.java @@ -1,9 +1,9 @@ package net.jacobpeterson.alpaca.enums.order; -import net.jacobpeterson.abstracts.enums.APIName; +import net.jacobpeterson.alpaca.abstracts.enums.APIName; /** - * {@link OrderType} defines enums for various {@link net.jacobpeterson.domain.alpaca.order.Order} types. + * {@link OrderType} defines enums for various {@link net.jacobpeterson.alpaca.domain.order.Order} types. * * @see Order Types */ diff --git a/src/main/java/net/jacobpeterson/alpaca/enums/portfolio/PortfolioPeriodUnit.java b/src/main/java/net/jacobpeterson/alpaca/enums/portfolio/PortfolioPeriodUnit.java index fb301047..7818f0bf 100644 --- a/src/main/java/net/jacobpeterson/alpaca/enums/portfolio/PortfolioPeriodUnit.java +++ b/src/main/java/net/jacobpeterson/alpaca/enums/portfolio/PortfolioPeriodUnit.java @@ -1,10 +1,10 @@ package net.jacobpeterson.alpaca.enums.portfolio; -import net.jacobpeterson.abstracts.enums.APIName; +import net.jacobpeterson.alpaca.abstracts.enums.APIName; /** * {@link PortfolioPeriodUnit} defines enums for various - * {@link net.jacobpeterson.domain.alpaca.portfoliohistory.PortfolioHistory} + * {@link net.jacobpeterson.alpaca.domain.portfoliohistory.PortfolioHistory} * period units. */ public enum PortfolioPeriodUnit implements APIName { diff --git a/src/main/java/net/jacobpeterson/alpaca/enums/portfolio/PortfolioTimeFrame.java b/src/main/java/net/jacobpeterson/alpaca/enums/portfolio/PortfolioTimeFrame.java index d0dd86c3..55cf11a7 100644 --- a/src/main/java/net/jacobpeterson/alpaca/enums/portfolio/PortfolioTimeFrame.java +++ b/src/main/java/net/jacobpeterson/alpaca/enums/portfolio/PortfolioTimeFrame.java @@ -1,12 +1,12 @@ package net.jacobpeterson.alpaca.enums.portfolio; import com.google.gson.annotations.SerializedName; -import net.jacobpeterson.abstracts.enums.APIName; -import net.jacobpeterson.util.gson.GsonUtil; +import net.jacobpeterson.alpaca.abstracts.enums.APIName; +import net.jacobpeterson.alpaca.util.gson.GsonUtil; /** * {@link PortfolioTimeFrame} defines enums for various - * {@link net.jacobpeterson.domain.alpaca.portfoliohistory.PortfolioHistory} + * {@link net.jacobpeterson.alpaca.domain.portfoliohistory.PortfolioHistory} * time frames. */ public enum PortfolioTimeFrame implements APIName { diff --git a/src/main/java/net/jacobpeterson/alpaca/enums/streaming/TradeUpdateEvent.java b/src/main/java/net/jacobpeterson/alpaca/enums/streaming/TradeUpdateEvent.java index 30641ca9..2c1a0d63 100644 --- a/src/main/java/net/jacobpeterson/alpaca/enums/streaming/TradeUpdateEvent.java +++ b/src/main/java/net/jacobpeterson/alpaca/enums/streaming/TradeUpdateEvent.java @@ -1,8 +1,8 @@ package net.jacobpeterson.alpaca.enums.streaming; import com.google.gson.annotations.SerializedName; -import net.jacobpeterson.abstracts.enums.APIName; -import net.jacobpeterson.util.gson.GsonUtil; +import net.jacobpeterson.alpaca.abstracts.enums.APIName; +import net.jacobpeterson.alpaca.util.gson.GsonUtil; /** * {@link TradeUpdateEvent} defines enums for various trade update events. diff --git a/src/main/java/net/jacobpeterson/alpaca/properties/AlpacaProperties.java b/src/main/java/net/jacobpeterson/alpaca/properties/AlpacaProperties.java index 8b42cb3a..3ae083d0 100644 --- a/src/main/java/net/jacobpeterson/alpaca/properties/AlpacaProperties.java +++ b/src/main/java/net/jacobpeterson/alpaca/properties/AlpacaProperties.java @@ -1,15 +1,16 @@ package net.jacobpeterson.alpaca.properties; -import net.jacobpeterson.util.reflection.ReflectionUtil; +import net.jacobpeterson.alpaca.AlpacaAPI; +import net.jacobpeterson.alpaca.util.reflection.ReflectionUtil; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.util.stream.Collectors; -import static net.jacobpeterson.util.properties.PropertyUtil.getProperty; +import static net.jacobpeterson.alpaca.util.properties.PropertyUtil.getProperty; /** - * {@link AlpacaProperties} defines properties for {@link net.jacobpeterson.alpaca.AlpacaAPI}. + * {@link AlpacaProperties} defines properties for {@link AlpacaAPI}. */ public class AlpacaProperties { @@ -19,22 +20,27 @@ public class AlpacaProperties { private static final String ALPACA_DEFAULT_PROPERTIES_FILE = "alpaca.default.properties"; private static final String KEY_ID_KEY = "key_id"; + /** The value of {@link #KEY_ID_KEY} in {@link #ALPACA_PROPERTIES_FILE}. */ public static final String KEY_ID_VALUE = getProperty(ALPACA_PROPERTIES_FILE, ALPACA_DEFAULT_PROPERTIES_FILE, KEY_ID_KEY); private static final String SECRET_KEY = "secret"; + /** The value of {@link #SECRET_KEY} in {@link #ALPACA_PROPERTIES_FILE}. */ public static final String SECRET_VALUE = getProperty(ALPACA_PROPERTIES_FILE, ALPACA_DEFAULT_PROPERTIES_FILE, SECRET_KEY); private static final String ENDPOINT_API_TYPE_KEY = "endpoint_api_type"; + /** The value of {@link #ENDPOINT_API_TYPE_KEY} in {@link #ALPACA_PROPERTIES_FILE}. */ public static final String ENDPOINT_API_TYPE_VALUE = getProperty(ALPACA_PROPERTIES_FILE, ALPACA_DEFAULT_PROPERTIES_FILE, ENDPOINT_API_TYPE_KEY); private static final String DATA_API_TYPE_KEY = "data_api_type"; + /** The value of {@link #DATA_API_TYPE_KEY} in {@link #ALPACA_PROPERTIES_FILE}. */ public static final String DATA_API_TYPE_VALUE = getProperty(ALPACA_PROPERTIES_FILE, ALPACA_DEFAULT_PROPERTIES_FILE, DATA_API_TYPE_KEY); private static final String USER_AGENT_KEY = "user_agent"; + /** The value of {@link #USER_AGENT_KEY} in {@link #ALPACA_PROPERTIES_FILE}. */ public static final String USER_AGENT_VALUE = getProperty(ALPACA_PROPERTIES_FILE, ALPACA_DEFAULT_PROPERTIES_FILE, USER_AGENT_KEY); diff --git a/src/main/java/net/jacobpeterson/alpaca/rest/AlpacaRequest.java b/src/main/java/net/jacobpeterson/alpaca/rest/AlpacaRequest.java index 5e0032dc..88d2c5cc 100644 --- a/src/main/java/net/jacobpeterson/alpaca/rest/AlpacaRequest.java +++ b/src/main/java/net/jacobpeterson/alpaca/rest/AlpacaRequest.java @@ -3,7 +3,8 @@ import com.google.common.base.Preconditions; import com.mashape.unirest.http.HttpResponse; import com.mashape.unirest.http.exceptions.UnirestException; -import net.jacobpeterson.abstracts.rest.AbstractRequest; +import net.jacobpeterson.alpaca.AlpacaAPI; +import net.jacobpeterson.alpaca.abstracts.rest.AbstractRequest; import net.jacobpeterson.alpaca.properties.AlpacaProperties; import net.jacobpeterson.alpaca.rest.exception.AlpacaAPIRequestException; @@ -11,7 +12,7 @@ import java.lang.reflect.Type; /** - * {@link AlpacaRequest} contains methods for HTTP requests for {@link net.jacobpeterson.alpaca.AlpacaAPI}. + * {@link AlpacaRequest} contains methods for HTTP requests for {@link AlpacaAPI}. */ public class AlpacaRequest extends AbstractRequest { diff --git a/src/main/java/net/jacobpeterson/alpaca/rest/AlpacaRequestBuilder.java b/src/main/java/net/jacobpeterson/alpaca/rest/AlpacaRequestBuilder.java index a8cb8017..a7c67d78 100644 --- a/src/main/java/net/jacobpeterson/alpaca/rest/AlpacaRequestBuilder.java +++ b/src/main/java/net/jacobpeterson/alpaca/rest/AlpacaRequestBuilder.java @@ -1,11 +1,12 @@ package net.jacobpeterson.alpaca.rest; -import net.jacobpeterson.abstracts.rest.AbstractRequestBuilder; +import net.jacobpeterson.alpaca.AlpacaAPI; +import net.jacobpeterson.alpaca.abstracts.rest.AbstractRequestBuilder; import java.util.Arrays; /** - * {@link AlpacaRequestBuilder} is a builder for URL Strings for {@link net.jacobpeterson.alpaca.AlpacaAPI}. + * {@link AlpacaRequestBuilder} is a builder for URL Strings for {@link AlpacaAPI}. */ public class AlpacaRequestBuilder extends AbstractRequestBuilder { diff --git a/src/main/java/net/jacobpeterson/alpaca/rest/exception/AlpacaAPIRequestException.java b/src/main/java/net/jacobpeterson/alpaca/rest/exception/AlpacaAPIRequestException.java index 70eeb03b..18703fe0 100644 --- a/src/main/java/net/jacobpeterson/alpaca/rest/exception/AlpacaAPIRequestException.java +++ b/src/main/java/net/jacobpeterson/alpaca/rest/exception/AlpacaAPIRequestException.java @@ -3,15 +3,15 @@ import com.google.gson.JsonElement; import com.google.gson.JsonObject; import com.mashape.unirest.http.HttpResponse; -import net.jacobpeterson.abstracts.rest.exception.AbstractAPIRequestException; -import net.jacobpeterson.util.gson.GsonUtil; +import net.jacobpeterson.alpaca.AlpacaAPI; +import net.jacobpeterson.alpaca.abstracts.rest.exception.AbstractAPIRequestException; +import net.jacobpeterson.alpaca.util.gson.GsonUtil; import java.io.InputStream; import java.io.InputStreamReader; /** - * {@link AlpacaAPIRequestException} represents {@link HttpResponse} request exceptions for {@link - * net.jacobpeterson.alpaca.AlpacaAPI}. + * {@link AlpacaAPIRequestException} represents {@link HttpResponse} {@link Exception}s for {@link AlpacaAPI}. */ public class AlpacaAPIRequestException extends AbstractAPIRequestException { diff --git a/src/main/java/net/jacobpeterson/util/doc/Documentation2JSONSchema.java b/src/main/java/net/jacobpeterson/alpaca/util/doc/Documentation2JSONSchema.java similarity index 99% rename from src/main/java/net/jacobpeterson/util/doc/Documentation2JSONSchema.java rename to src/main/java/net/jacobpeterson/alpaca/util/doc/Documentation2JSONSchema.java index 32e77970..29fbf597 100644 --- a/src/main/java/net/jacobpeterson/util/doc/Documentation2JSONSchema.java +++ b/src/main/java/net/jacobpeterson/alpaca/util/doc/Documentation2JSONSchema.java @@ -1,4 +1,4 @@ -package net.jacobpeterson.util.doc; +package net.jacobpeterson.alpaca.util.doc; import org.w3c.dom.Document; import org.w3c.dom.NodeList; diff --git a/src/main/java/net/jacobpeterson/util/format/FormatUtil.java b/src/main/java/net/jacobpeterson/alpaca/util/format/FormatUtil.java similarity index 71% rename from src/main/java/net/jacobpeterson/util/format/FormatUtil.java rename to src/main/java/net/jacobpeterson/alpaca/util/format/FormatUtil.java index 2a563f7c..39567d24 100644 --- a/src/main/java/net/jacobpeterson/util/format/FormatUtil.java +++ b/src/main/java/net/jacobpeterson/alpaca/util/format/FormatUtil.java @@ -1,4 +1,4 @@ -package net.jacobpeterson.util.format; +package net.jacobpeterson.alpaca.util.format; import java.text.DecimalFormat; import java.text.DecimalFormatSymbols; @@ -21,8 +21,6 @@ public class FormatUtil { DecimalFormatSymbols.getInstance(Locale.US)); private static final DateTimeFormatter RFC_3339_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss'Z'").withZone(ZoneId.of("America/New_York")); - private static final long PREV_UNIX_EPOCH_NANO_TIME = System.currentTimeMillis() * 1000000L; - private static final long PREV_UNIX_EPOCH_MICRO_TIME = PREV_UNIX_EPOCH_NANO_TIME / 1000L; /** * Formats an arbitrary number to a currency format. e.g. $123.45 @@ -45,20 +43,4 @@ public static String toCurrencyFormat(Number numberToFormat) { public static String toRFC3339Format(ZonedDateTime zonedDateTime) { return RFC_3339_FORMATTER.format(zonedDateTime); } - - /** - * Converts a given unix epoch time in nanoseconds or microseconds to milliseconds if necessary. - * - * @param time the time - * - * @return the long - */ - public static long convertToMilli(long time) { - if (time > PREV_UNIX_EPOCH_NANO_TIME) { - time /= 1000000L; - } else if (time > PREV_UNIX_EPOCH_MICRO_TIME) { - time /= 1000L; - } - return time; - } } diff --git a/src/main/java/net/jacobpeterson/util/gson/GsonUtil.java b/src/main/java/net/jacobpeterson/alpaca/util/gson/GsonUtil.java similarity index 98% rename from src/main/java/net/jacobpeterson/util/gson/GsonUtil.java rename to src/main/java/net/jacobpeterson/alpaca/util/gson/GsonUtil.java index 751b98b4..98923560 100644 --- a/src/main/java/net/jacobpeterson/util/gson/GsonUtil.java +++ b/src/main/java/net/jacobpeterson/alpaca/util/gson/GsonUtil.java @@ -1,4 +1,4 @@ -package net.jacobpeterson.util.gson; +package net.jacobpeterson.alpaca.util.gson; import com.google.gson.Gson; import com.google.gson.GsonBuilder; diff --git a/src/main/java/net/jacobpeterson/util/gson/LocalDateAdapter.java b/src/main/java/net/jacobpeterson/alpaca/util/gson/LocalDateAdapter.java similarity index 95% rename from src/main/java/net/jacobpeterson/util/gson/LocalDateAdapter.java rename to src/main/java/net/jacobpeterson/alpaca/util/gson/LocalDateAdapter.java index 2799c1f2..5a09b3cb 100644 --- a/src/main/java/net/jacobpeterson/util/gson/LocalDateAdapter.java +++ b/src/main/java/net/jacobpeterson/alpaca/util/gson/LocalDateAdapter.java @@ -1,4 +1,4 @@ -package net.jacobpeterson.util.gson; +package net.jacobpeterson.alpaca.util.gson; import com.google.gson.JsonDeserializationContext; import com.google.gson.JsonDeserializer; diff --git a/src/main/java/net/jacobpeterson/util/gson/ZonedDateTimeAdapter.java b/src/main/java/net/jacobpeterson/alpaca/util/gson/ZonedDateTimeAdapter.java similarity index 96% rename from src/main/java/net/jacobpeterson/util/gson/ZonedDateTimeAdapter.java rename to src/main/java/net/jacobpeterson/alpaca/util/gson/ZonedDateTimeAdapter.java index b4b8a5e6..728161fd 100644 --- a/src/main/java/net/jacobpeterson/util/gson/ZonedDateTimeAdapter.java +++ b/src/main/java/net/jacobpeterson/alpaca/util/gson/ZonedDateTimeAdapter.java @@ -1,4 +1,4 @@ -package net.jacobpeterson.util.gson; +package net.jacobpeterson.alpaca.util.gson; import com.google.gson.JsonDeserializationContext; import com.google.gson.JsonDeserializer; diff --git a/src/main/java/net/jacobpeterson/util/properties/PropertyUtil.java b/src/main/java/net/jacobpeterson/alpaca/util/properties/PropertyUtil.java similarity index 97% rename from src/main/java/net/jacobpeterson/util/properties/PropertyUtil.java rename to src/main/java/net/jacobpeterson/alpaca/util/properties/PropertyUtil.java index c80caa6f..e8e27bc2 100644 --- a/src/main/java/net/jacobpeterson/util/properties/PropertyUtil.java +++ b/src/main/java/net/jacobpeterson/alpaca/util/properties/PropertyUtil.java @@ -1,4 +1,4 @@ -package net.jacobpeterson.util.properties; +package net.jacobpeterson.alpaca.util.properties; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -131,7 +131,7 @@ public synchronized static Properties loadPropertyFile(String propertyFile, Stri LOGGER.error("Could not close property file stream: {}\n{}", propertyFile, exception); } } else { - LOGGER.error("Could not find property file: {}", propertyFile); + LOGGER.debug("Could not find property file: {}", propertyFile); if (defaultProperties != null) { LOGGER.info("Using default properties for: {}", propertyFile); diff --git a/src/main/java/net/jacobpeterson/util/reflection/ReflectionUtil.java b/src/main/java/net/jacobpeterson/alpaca/util/reflection/ReflectionUtil.java similarity index 97% rename from src/main/java/net/jacobpeterson/util/reflection/ReflectionUtil.java rename to src/main/java/net/jacobpeterson/alpaca/util/reflection/ReflectionUtil.java index 566a2857..c2138871 100644 --- a/src/main/java/net/jacobpeterson/util/reflection/ReflectionUtil.java +++ b/src/main/java/net/jacobpeterson/alpaca/util/reflection/ReflectionUtil.java @@ -1,4 +1,4 @@ -package net.jacobpeterson.util.reflection; +package net.jacobpeterson.alpaca.util.reflection; import java.lang.reflect.Field; import java.util.ArrayList; diff --git a/src/main/java/net/jacobpeterson/alpaca/websocket/broker/client/AlpacaWebsocketClient.java b/src/main/java/net/jacobpeterson/alpaca/websocket/broker/client/AlpacaWebsocketClient.java index 03d4fc70..db41bef2 100644 --- a/src/main/java/net/jacobpeterson/alpaca/websocket/broker/client/AlpacaWebsocketClient.java +++ b/src/main/java/net/jacobpeterson/alpaca/websocket/broker/client/AlpacaWebsocketClient.java @@ -6,18 +6,19 @@ import com.google.gson.JsonObject; import com.google.gson.JsonPrimitive; import com.google.gson.JsonSyntaxException; -import net.jacobpeterson.abstracts.websocket.client.WebsocketClient; -import net.jacobpeterson.abstracts.websocket.client.WebsocketStateListener; -import net.jacobpeterson.abstracts.websocket.exception.WebsocketException; +import net.jacobpeterson.alpaca.AlpacaAPI; +import net.jacobpeterson.alpaca.abstracts.websocket.client.WebsocketClient; +import net.jacobpeterson.alpaca.abstracts.websocket.client.WebsocketStateListener; +import net.jacobpeterson.alpaca.abstracts.websocket.exception.WebsocketException; +import net.jacobpeterson.alpaca.domain.streaming.AlpacaStreamMessage; +import net.jacobpeterson.alpaca.domain.streaming.account.AccountUpdateMessage; +import net.jacobpeterson.alpaca.domain.streaming.authorization.AuthorizationMessage; +import net.jacobpeterson.alpaca.domain.streaming.listening.ListeningMessage; +import net.jacobpeterson.alpaca.domain.streaming.trade.TradeUpdateMessage; import net.jacobpeterson.alpaca.enums.api.EndpointAPIType; +import net.jacobpeterson.alpaca.util.gson.GsonUtil; import net.jacobpeterson.alpaca.websocket.broker.listener.AlpacaStreamListener; import net.jacobpeterson.alpaca.websocket.broker.message.AlpacaStreamMessageType; -import net.jacobpeterson.domain.alpaca.streaming.AlpacaStreamMessage; -import net.jacobpeterson.domain.alpaca.streaming.account.AccountUpdateMessage; -import net.jacobpeterson.domain.alpaca.streaming.authorization.AuthorizationMessage; -import net.jacobpeterson.domain.alpaca.streaming.listening.ListeningMessage; -import net.jacobpeterson.domain.alpaca.streaming.trade.TradeUpdateMessage; -import net.jacobpeterson.util.gson.GsonUtil; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -32,8 +33,7 @@ import java.util.stream.Collectors; /** - * {@link AlpacaWebsocketClient} represents a client for the {@link net.jacobpeterson.alpaca.AlpacaAPI} Websocket - * stream. + * {@link AlpacaWebsocketClient} represents a client for the {@link AlpacaAPI} Websocket stream. */ public class AlpacaWebsocketClient implements WebsocketClient { diff --git a/src/main/java/net/jacobpeterson/alpaca/websocket/broker/client/AlpacaWebsocketClientEndpoint.java b/src/main/java/net/jacobpeterson/alpaca/websocket/broker/client/AlpacaWebsocketClientEndpoint.java index 76485be2..a9c215ac 100644 --- a/src/main/java/net/jacobpeterson/alpaca/websocket/broker/client/AlpacaWebsocketClientEndpoint.java +++ b/src/main/java/net/jacobpeterson/alpaca/websocket/broker/client/AlpacaWebsocketClientEndpoint.java @@ -1,6 +1,7 @@ package net.jacobpeterson.alpaca.websocket.broker.client; -import net.jacobpeterson.abstracts.websocket.client.AbstractWebsocketClientEndpoint; +import net.jacobpeterson.alpaca.AlpacaAPI; +import net.jacobpeterson.alpaca.abstracts.websocket.client.AbstractWebsocketClientEndpoint; import javax.websocket.ClientEndpoint; import javax.websocket.CloseReason; @@ -13,8 +14,7 @@ import java.nio.charset.StandardCharsets; /** - * {@link AlpacaWebsocketClientEndpoint} is used for handling a Websocket directly for {@link - * net.jacobpeterson.alpaca.AlpacaAPI}. + * {@link AlpacaWebsocketClientEndpoint} is used for handling a Websocket directly for {@link AlpacaAPI}. */ @ClientEndpoint(subprotocols = "BINARY") public class AlpacaWebsocketClientEndpoint extends AbstractWebsocketClientEndpoint { diff --git a/src/main/java/net/jacobpeterson/alpaca/websocket/broker/listener/AlpacaStreamListener.java b/src/main/java/net/jacobpeterson/alpaca/websocket/broker/listener/AlpacaStreamListener.java index ef55d49e..50b8863a 100644 --- a/src/main/java/net/jacobpeterson/alpaca/websocket/broker/listener/AlpacaStreamListener.java +++ b/src/main/java/net/jacobpeterson/alpaca/websocket/broker/listener/AlpacaStreamListener.java @@ -1,8 +1,8 @@ package net.jacobpeterson.alpaca.websocket.broker.listener; -import net.jacobpeterson.abstracts.websocket.listener.StreamListener; +import net.jacobpeterson.alpaca.abstracts.websocket.listener.StreamListener; +import net.jacobpeterson.alpaca.domain.streaming.AlpacaStreamMessage; import net.jacobpeterson.alpaca.websocket.broker.message.AlpacaStreamMessageType; -import net.jacobpeterson.domain.alpaca.streaming.AlpacaStreamMessage; import java.util.Set; diff --git a/src/main/java/net/jacobpeterson/alpaca/websocket/broker/listener/AlpacaStreamListenerAdapter.java b/src/main/java/net/jacobpeterson/alpaca/websocket/broker/listener/AlpacaStreamListenerAdapter.java index facd5458..5785b074 100644 --- a/src/main/java/net/jacobpeterson/alpaca/websocket/broker/listener/AlpacaStreamListenerAdapter.java +++ b/src/main/java/net/jacobpeterson/alpaca/websocket/broker/listener/AlpacaStreamListenerAdapter.java @@ -1,7 +1,7 @@ package net.jacobpeterson.alpaca.websocket.broker.listener; +import net.jacobpeterson.alpaca.domain.streaming.AlpacaStreamMessage; import net.jacobpeterson.alpaca.websocket.broker.message.AlpacaStreamMessageType; -import net.jacobpeterson.domain.alpaca.streaming.AlpacaStreamMessage; import java.util.Arrays; import java.util.HashSet; diff --git a/src/main/java/net/jacobpeterson/alpaca/websocket/broker/message/AlpacaStreamMessageType.java b/src/main/java/net/jacobpeterson/alpaca/websocket/broker/message/AlpacaStreamMessageType.java index 4a51eae2..4922b598 100644 --- a/src/main/java/net/jacobpeterson/alpaca/websocket/broker/message/AlpacaStreamMessageType.java +++ b/src/main/java/net/jacobpeterson/alpaca/websocket/broker/message/AlpacaStreamMessageType.java @@ -1,9 +1,9 @@ package net.jacobpeterson.alpaca.websocket.broker.message; import com.google.gson.annotations.SerializedName; -import net.jacobpeterson.abstracts.enums.APIName; -import net.jacobpeterson.abstracts.websocket.message.StreamMessageType; -import net.jacobpeterson.util.gson.GsonUtil; +import net.jacobpeterson.alpaca.abstracts.enums.APIName; +import net.jacobpeterson.alpaca.abstracts.websocket.message.StreamMessageType; +import net.jacobpeterson.alpaca.util.gson.GsonUtil; /** * {@link AlpacaStreamMessageType} defines enums for various message types for {@link diff --git a/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/client/MarketDataWebsocketClient.java b/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/client/MarketDataWebsocketClient.java index 54e1d98b..a730d552 100644 --- a/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/client/MarketDataWebsocketClient.java +++ b/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/client/MarketDataWebsocketClient.java @@ -4,22 +4,23 @@ import com.google.gson.JsonArray; import com.google.gson.JsonElement; import com.google.gson.JsonObject; -import net.jacobpeterson.abstracts.websocket.client.WebsocketClient; -import net.jacobpeterson.abstracts.websocket.client.WebsocketStateListener; -import net.jacobpeterson.abstracts.websocket.exception.WebsocketException; +import net.jacobpeterson.alpaca.AlpacaAPI; +import net.jacobpeterson.alpaca.abstracts.websocket.client.WebsocketClient; +import net.jacobpeterson.alpaca.abstracts.websocket.client.WebsocketStateListener; +import net.jacobpeterson.alpaca.abstracts.websocket.exception.WebsocketException; +import net.jacobpeterson.alpaca.domain.marketdata.realtime.MarketDataMessage; +import net.jacobpeterson.alpaca.domain.marketdata.realtime.SymbolMessage; +import net.jacobpeterson.alpaca.domain.marketdata.realtime.bar.BarMessage; +import net.jacobpeterson.alpaca.domain.marketdata.realtime.control.ErrorMessage; +import net.jacobpeterson.alpaca.domain.marketdata.realtime.control.SubscriptionsMessage; +import net.jacobpeterson.alpaca.domain.marketdata.realtime.control.SuccessMessage; +import net.jacobpeterson.alpaca.domain.marketdata.realtime.quote.QuoteMessage; +import net.jacobpeterson.alpaca.domain.marketdata.realtime.trade.TradeMessage; import net.jacobpeterson.alpaca.enums.api.DataAPIType; +import net.jacobpeterson.alpaca.util.gson.GsonUtil; import net.jacobpeterson.alpaca.websocket.broker.client.AlpacaWebsocketClient; import net.jacobpeterson.alpaca.websocket.marketdata.listener.MarketDataListener; import net.jacobpeterson.alpaca.websocket.marketdata.message.MarketDataMessageType; -import net.jacobpeterson.domain.alpaca.marketdata.realtime.MarketDataMessage; -import net.jacobpeterson.domain.alpaca.marketdata.realtime.SymbolMessage; -import net.jacobpeterson.domain.alpaca.marketdata.realtime.bar.BarMessage; -import net.jacobpeterson.domain.alpaca.marketdata.realtime.control.ErrorMessage; -import net.jacobpeterson.domain.alpaca.marketdata.realtime.control.SubscriptionsMessage; -import net.jacobpeterson.domain.alpaca.marketdata.realtime.control.SuccessMessage; -import net.jacobpeterson.domain.alpaca.marketdata.realtime.quote.QuoteMessage; -import net.jacobpeterson.domain.alpaca.marketdata.realtime.trade.TradeMessage; -import net.jacobpeterson.util.gson.GsonUtil; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -34,8 +35,7 @@ import java.util.Set; /** - * {@link MarketDataWebsocketClient} represents a client for the {@link net.jacobpeterson.alpaca.AlpacaAPI} market data - * Websocket stream. + * {@link MarketDataWebsocketClient} represents a client for the {@link AlpacaAPI} market data Websocket stream. */ public class MarketDataWebsocketClient implements WebsocketClient { diff --git a/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/client/MarketDataWebsocketClientEndpoint.java b/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/client/MarketDataWebsocketClientEndpoint.java index 8eeff8bf..f82d0be0 100644 --- a/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/client/MarketDataWebsocketClientEndpoint.java +++ b/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/client/MarketDataWebsocketClientEndpoint.java @@ -1,6 +1,7 @@ package net.jacobpeterson.alpaca.websocket.marketdata.client; -import net.jacobpeterson.abstracts.websocket.client.AbstractWebsocketClientEndpoint; +import net.jacobpeterson.alpaca.AlpacaAPI; +import net.jacobpeterson.alpaca.abstracts.websocket.client.AbstractWebsocketClientEndpoint; import javax.websocket.ClientEndpoint; import javax.websocket.CloseReason; @@ -12,8 +13,8 @@ import java.net.URI; /** - * {@link MarketDataWebsocketClientEndpoint} is used for handling a Websocket directly for {@link - * net.jacobpeterson.alpaca.AlpacaAPI} market data. + * {@link MarketDataWebsocketClientEndpoint} is used for handling a Websocket directly for {@link AlpacaAPI} market + * data. */ @ClientEndpoint(subprotocols = "STRING") public class MarketDataWebsocketClientEndpoint extends AbstractWebsocketClientEndpoint { diff --git a/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/listener/MarketDataListener.java b/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/listener/MarketDataListener.java index 212aa7e7..71ad002f 100644 --- a/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/listener/MarketDataListener.java +++ b/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/listener/MarketDataListener.java @@ -1,8 +1,8 @@ package net.jacobpeterson.alpaca.websocket.marketdata.listener; -import net.jacobpeterson.abstracts.websocket.listener.StreamListener; +import net.jacobpeterson.alpaca.abstracts.websocket.listener.StreamListener; +import net.jacobpeterson.alpaca.domain.marketdata.realtime.MarketDataMessage; import net.jacobpeterson.alpaca.websocket.marketdata.message.MarketDataMessageType; -import net.jacobpeterson.domain.alpaca.marketdata.realtime.MarketDataMessage; import java.util.Map; import java.util.Set; diff --git a/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/listener/MarketDataListenerAdapter.java b/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/listener/MarketDataListenerAdapter.java index aa485524..f38712cd 100644 --- a/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/listener/MarketDataListenerAdapter.java +++ b/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/listener/MarketDataListenerAdapter.java @@ -1,7 +1,7 @@ package net.jacobpeterson.alpaca.websocket.marketdata.listener; +import net.jacobpeterson.alpaca.domain.marketdata.realtime.MarketDataMessage; import net.jacobpeterson.alpaca.websocket.marketdata.message.MarketDataMessageType; -import net.jacobpeterson.domain.alpaca.marketdata.realtime.MarketDataMessage; import java.util.Arrays; import java.util.HashMap; diff --git a/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/message/MarketDataMessageType.java b/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/message/MarketDataMessageType.java index 702d5dc0..8835670d 100644 --- a/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/message/MarketDataMessageType.java +++ b/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/message/MarketDataMessageType.java @@ -1,8 +1,8 @@ package net.jacobpeterson.alpaca.websocket.marketdata.message; import com.google.gson.annotations.SerializedName; -import net.jacobpeterson.abstracts.enums.APIName; -import net.jacobpeterson.abstracts.websocket.message.StreamMessageType; +import net.jacobpeterson.alpaca.abstracts.enums.APIName; +import net.jacobpeterson.alpaca.abstracts.websocket.message.StreamMessageType; /** * {@link MarketDataMessageType} defines enums for various market data stream message types. diff --git a/src/test/java/live/net/jacobpeterson/alpaca/AlpacaAPIEndpointTest.java b/src/test/java/live/net/jacobpeterson/alpaca/AlpacaAPIEndpointTest.java index 2f07dc6d..11975c9c 100644 --- a/src/test/java/live/net/jacobpeterson/alpaca/AlpacaAPIEndpointTest.java +++ b/src/test/java/live/net/jacobpeterson/alpaca/AlpacaAPIEndpointTest.java @@ -1,30 +1,27 @@ package live.net.jacobpeterson.alpaca; -import net.jacobpeterson.abstracts.enums.SortDirection; import net.jacobpeterson.alpaca.AlpacaAPI; +import net.jacobpeterson.alpaca.abstracts.enums.SortDirection; +import net.jacobpeterson.alpaca.domain.account.Account; +import net.jacobpeterson.alpaca.domain.accountactivities.AccountActivity; +import net.jacobpeterson.alpaca.domain.accountactivities.NonTradeActivity; +import net.jacobpeterson.alpaca.domain.accountactivities.TradeActivity; +import net.jacobpeterson.alpaca.domain.accountconfiguration.AccountConfiguration; +import net.jacobpeterson.alpaca.domain.clock.Clock; +import net.jacobpeterson.alpaca.domain.order.Order; import net.jacobpeterson.alpaca.enums.activity.ActivityType; import net.jacobpeterson.alpaca.enums.order.OrderStatus; import net.jacobpeterson.alpaca.rest.exception.AlpacaAPIRequestException; -import net.jacobpeterson.domain.alpaca.account.Account; -import net.jacobpeterson.domain.alpaca.accountactivities.AccountActivity; -import net.jacobpeterson.domain.alpaca.accountactivities.NonTradeActivity; -import net.jacobpeterson.domain.alpaca.accountactivities.TradeActivity; -import net.jacobpeterson.domain.alpaca.accountconfiguration.AccountConfiguration; -import net.jacobpeterson.domain.alpaca.clock.Clock; -import net.jacobpeterson.domain.alpaca.order.Order; -import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.BeforeAll; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.MethodOrderer; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.TestMethodOrder; +import org.junit.jupiter.api.*; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.time.ZonedDateTime; import java.util.List; -import static org.junit.jupiter.api.Assertions.*; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.fail; /** * {@link AlpacaAPIEndpointTest} tests live endpoints using Alpaca Paper credentials given in the From e37a69ec2dbccafe39f93b4934f707e368cf31b4 Mon Sep 17 00:00:00 2001 From: Jacob Peterson Date: Thu, 1 Jul 2021 22:52:46 -0600 Subject: [PATCH 119/271] Refactor, clean up, and add enums all to schema_json --- README.md | 58 +++---- build.gradle | 37 +++-- schema_json/account/account.json | 44 +++--- schema_json/account/enums/account_status.json | 36 +++++ .../account_activities/account_activity.json | 10 +- .../enums/activity_type.json | 144 ++++++++++++++++++ .../enums/trade_activity_side.json | 8 + .../enums/trade_activity_type.json | 8 + .../non_trade_activity.json | 10 +- .../account_activities/trade_activity.json | 14 +- .../account_configuration.json | 10 +- .../enums/dtbp_check.json | 10 ++ .../enums/trade_confirm_email.json | 8 + schema_json/asset/asset.json | 14 +- schema_json/asset/enums/asset_status.json | 8 + schema_json/calendar/calendar.json | 14 +- schema_json/clock/clock.json | 10 +- .../market_data/historical/bar/bar.json | 4 +- .../historical/bar/bars_response.json | 6 +- .../quote/latest_quote_response.json | 6 +- .../market_data/historical/quote/quote.json | 4 +- .../historical/quote/quotes_response.json | 6 +- .../historical/snapshot/snapshot.json | 22 +-- .../trade/latest_trade_response.json | 6 +- .../market_data/historical/trade/trade.json | 4 +- .../historical/trade/trades_response.json | 6 +- .../market_data/realtime/bar/bar_message.json | 16 +- .../realtime/control/error_message.json | 2 +- .../control/subscriptions_message.json | 8 +- .../realtime/control/success_message.json | 2 +- .../enums/market_data_message_type.json | 32 ++++ .../realtime/market_data_message.json | 6 +- .../realtime/quote/quote_message.json | 20 +-- .../market_data/realtime/symbol_message.json | 4 +- .../realtime/trade/trade_message.json | 16 +- schema_json/order/cancelled_order.json | 10 +- schema_json/order/enums/order_class.json | 24 +++ schema_json/order/enums/order_side.json | 8 + schema_json/order/enums/order_status.json | 72 +++++++++ .../order/enums/order_time_in_force.json | 38 +++++ schema_json/order/enums/order_type.json | 28 ++++ schema_json/order/order.json | 66 ++++---- .../enums/portfolio_period_unit.json | 24 +++ .../enums/portfolio_time_frame.json | 28 ++++ .../portfolio_history/portfolio_history.json | 16 +- .../position/close_position_order.json | 9 +- schema_json/position/position.json | 34 ++--- .../streaming/account/account_update.json | 4 +- .../account/account_update_message.json | 7 +- .../streaming/alpaca_stream_message.json | 6 +- .../authorization/authorization_data.json | 2 +- .../authorization/authorization_message.json | 6 +- .../enums/alpaca_stream_message_type.json | 10 ++ .../streaming/listening/listening_data.json | 5 +- .../listening/listening_message.json | 6 +- .../trade/enums/trade_update_event.json | 102 +++++++++++++ schema_json/streaming/trade/trade_update.json | 16 +- .../streaming/trade/trade_update_message.json | 5 +- schema_json/watchlist/watchlist.json | 18 +-- .../net/jacobpeterson/alpaca/AlpacaAPI.java | 4 +- .../abstracts/rest/AbstractRequest.java | 3 +- .../alpaca/enums/account/AccountStatus.java | 63 -------- .../alpaca/enums/activity/ActivityType.java | 125 --------------- .../alpaca/enums/asset/AssetStatus.java | 31 ---- .../enums/marketdata/BarsTimeFrame.java | 34 ----- .../alpaca/enums/order/OrderClass.java | 38 ----- .../alpaca/enums/order/OrderSide.java | 31 ---- .../alpaca/enums/order/OrderStatus.java | 34 ----- .../alpaca/enums/order/OrderTimeInForce.java | 72 --------- .../alpaca/enums/order/OrderType.java | 67 -------- .../enums/portfolio/PortfolioPeriodUnit.java | 39 ----- .../enums/portfolio/PortfolioTimeFrame.java | 38 ----- .../enums/streaming/TradeUpdateEvent.java | 117 -------------- .../api => properties/enums}/DataAPIType.java | 2 +- .../enums}/EndpointAPIType.java | 2 +- .../exception/AlpacaAPIRequestException.java | 3 +- .../alpaca/util/gson/GsonUtil.java | 34 ++--- .../broker/client/AlpacaWebsocketClient.java | 5 +- .../client/MarketDataWebsocketClient.java | 5 +- src/main/resources/alpaca.default.properties | 4 +- .../jacobpeterson/alpaca/AlpacaAPITest.java | 4 +- 81 files changed, 913 insertions(+), 999 deletions(-) create mode 100644 schema_json/account/enums/account_status.json create mode 100644 schema_json/account_activities/enums/activity_type.json create mode 100644 schema_json/account_activities/enums/trade_activity_side.json create mode 100644 schema_json/account_activities/enums/trade_activity_type.json create mode 100644 schema_json/account_configuration/enums/dtbp_check.json create mode 100644 schema_json/account_configuration/enums/trade_confirm_email.json create mode 100644 schema_json/asset/enums/asset_status.json create mode 100644 schema_json/market_data/realtime/enums/market_data_message_type.json create mode 100644 schema_json/order/enums/order_class.json create mode 100644 schema_json/order/enums/order_side.json create mode 100644 schema_json/order/enums/order_status.json create mode 100644 schema_json/order/enums/order_time_in_force.json create mode 100644 schema_json/order/enums/order_type.json create mode 100644 schema_json/portfolio_history/enums/portfolio_period_unit.json create mode 100644 schema_json/portfolio_history/enums/portfolio_time_frame.json create mode 100644 schema_json/streaming/enums/alpaca_stream_message_type.json create mode 100644 schema_json/streaming/trade/enums/trade_update_event.json delete mode 100644 src/main/java/net/jacobpeterson/alpaca/enums/account/AccountStatus.java delete mode 100644 src/main/java/net/jacobpeterson/alpaca/enums/activity/ActivityType.java delete mode 100644 src/main/java/net/jacobpeterson/alpaca/enums/asset/AssetStatus.java delete mode 100644 src/main/java/net/jacobpeterson/alpaca/enums/marketdata/BarsTimeFrame.java delete mode 100644 src/main/java/net/jacobpeterson/alpaca/enums/order/OrderClass.java delete mode 100644 src/main/java/net/jacobpeterson/alpaca/enums/order/OrderSide.java delete mode 100644 src/main/java/net/jacobpeterson/alpaca/enums/order/OrderStatus.java delete mode 100644 src/main/java/net/jacobpeterson/alpaca/enums/order/OrderTimeInForce.java delete mode 100644 src/main/java/net/jacobpeterson/alpaca/enums/order/OrderType.java delete mode 100644 src/main/java/net/jacobpeterson/alpaca/enums/portfolio/PortfolioPeriodUnit.java delete mode 100644 src/main/java/net/jacobpeterson/alpaca/enums/portfolio/PortfolioTimeFrame.java delete mode 100644 src/main/java/net/jacobpeterson/alpaca/enums/streaming/TradeUpdateEvent.java rename src/main/java/net/jacobpeterson/alpaca/{enums/api => properties/enums}/DataAPIType.java (96%) rename src/main/java/net/jacobpeterson/alpaca/{enums/api => properties/enums}/EndpointAPIType.java (96%) diff --git a/README.md b/README.md index d7fd846b..a1880534 100644 --- a/README.md +++ b/README.md @@ -7,12 +7,12 @@ GitHub

-## Overview +# Overview This is a Java implementation for the Alpaca API. Alpaca lets you trade with algorithms, connect with apps, and build services all with a commission-free stock trading API. This library is community developed and if you have any questions, please ask them on [Github Discussions](https://github.com/Petersoj/alpaca-java/discussions), the [Alpaca Slack #dev-alpaca-java channel](https://alpaca.markets/slack), or on the [Alpaca Forums](https://forum.alpaca.markets/). Give this repository a star ⭐ if it helped you build an awesome trading algorithm in Java! -## Gradle and Maven Integration +# Gradle and Maven Integration If you are using Gradle as your build tool, add the following dependency to your `build.gradle` file: ``` @@ -34,8 +34,8 @@ If you are using Maven as your build tool, add the following dependency to your Note that you don't have to use the Maven Central artifacts and instead can just install a clone of this project to your local Maven repository as shown in the [Building](#building) section. -## Configuration -Creating an `alpaca.properties` file on the classpath with the following format allows you to easily load properties using the `AlpacaAPI()` default constructor: +# Configuration +Creating an `alpaca.properties` file on the classpath with the following format allows you to easily load properties using the `AlpacaAPI` default constructor: ``` key_id = secret = @@ -44,10 +44,10 @@ data_api_type = ``` The default values for `alpaca.properties` can be found [here](https://github.com/Petersoj/alpaca-java/tree/master/src/main/resources). -## Logger +# Logger For logging, this library uses [SLF4j](http://www.slf4j.org/) which serves as an interface for various logging frameworks. This enables you to use whatever logging framework you would like. However, if you do not add a logging framework as a dependency in your project, the console will output a message stating that SLF4j is defaulting to a no-operation (NOP) logger implementation. To enable logging, add a logging framework of your choice as a dependency to your project such as [Log4j 2](http://logging.apache.org/log4j/2.x/index.html), [SLF4j-simple](http://www.slf4j.org/manual.html), or [Apache Commons Logging](https://commons.apache.org/proper/commons-logging/). -## AlpacaAPI +# AlpacaAPI The `AlpacaAPI` class contains several methods to interface with Alpaca. You will generally only need one instance of it in your application. Note that many methods allow `null` to be passed in as a parameter if it is optional. The Alpaca API specification is located [here](https://docs.alpaca.markets/api-documentation/api-v2/) and the `AlpacaAPI` Javadoc is located [here](https://javadoc.io/doc/net.jacobpeterson/alpaca-java/latest/net/jacobpeterson/alpaca/AlpacaAPI.html). Example usage: @@ -67,10 +67,10 @@ AlpacaAPI alpacaAPI = new AlpacaAPI(oAuthToken); Note that the example uses of the `AlpacaAPI` class methods below are not exhaustive. Refer to the `AlpacaAPI` [Javadoc](https://javadoc.io/doc/net.jacobpeterson/alpaca-java/latest/net/jacobpeterson/alpaca/AlpacaAPI.html) for all method signatures. -### AlpacaAPIRequestException +## AlpacaAPIRequestException The `AlpacaAPIRequestException` is thrown anytime an exception occurs when using various `AlpacaAPI` methods. It should be caught and handled within your trading algorithm application accordingly. -### Account +## Account The Account API serves important information related to an account, including account status, funds available for trade, funds available for withdrawal, and various flags relevant to an account's ability to trade. Example usage: @@ -83,7 +83,7 @@ try { } ``` -### AccountActivity +## AccountActivity The Account Activities API provides access to a historical record of transaction activities that have impacted your account. Example usage: @@ -110,7 +110,7 @@ try { } ``` -### AccountConfiguration +## AccountConfiguration The Account Configuration API provides custom configurations about your trading account settings. These configurations control various allow you to modify settings to suit your trading needs. Example usage: @@ -126,7 +126,7 @@ try { } ``` -### Orders +## Orders The Orders API allows a user to monitor, place and cancel their orders with Alpaca. Each order has a unique identifier provided by the client. This client-side unique order ID will be automatically generated by the system if not provided by the client, and will be returned as part of the order object along with the rest of the fields described below. Once an order is placed, it can be queried using the client-side order ID to check the status. Updates on open orders at Alpaca will also be sent over the streaming interface, which is the recommended method of maintaining order state. Example usage: @@ -184,7 +184,7 @@ try { } ``` -### Positions +## Positions The Positions API provides information about an account's current open positions. The response will include information such as cost basis, shares traded, and market value, which will be updated live as price information is updated. Once a position is closed, it will no longer be queryable through this API. Example usage: @@ -204,7 +204,7 @@ try { } ``` -### Assets +## Assets The Assets API serves as the master list of assets available for trade and data consumption from Alpaca. Assets are sorted by asset class, exchange and symbol. Example usage: @@ -224,7 +224,7 @@ try { } ``` -### Watchlist +## Watchlist The Watchlist API provides CRUD operation for the account's watchlist. An account can have multiple watchlists and each is uniquely identified by ID but can also be addressed by user-defined name. Each watchlist is an ordered list of assets. Example usage: @@ -256,7 +256,7 @@ try { } ``` -### PortfolioHistory +## PortfolioHistory The portfolio history API returns the timeseries data for equity and profit loss information of the account. Example usage: @@ -287,7 +287,7 @@ try { } ``` -### Calendar +## Calendar The calendar API serves the full list of market days from 1970 to 2029. It can also be queried by specifying a start and/or end time to narrow down the results. In addition to the dates, the response also contains the specific open and close times for the market days, taking into account early closures. Example usage: @@ -303,7 +303,7 @@ try { } ``` -### Clock +## Clock The clock API serves the current market timestamp, whether the market is currently open, as well as the times of the next market open and close. Example usage: @@ -317,7 +317,7 @@ try { } ``` -### Alpaca Streaming +## Alpaca Streaming Alpaca offers WebSocket streaming for account and order updates. Example usage: @@ -358,7 +358,7 @@ try { } ``` -### Trades +## Trades The Trades API provides historical trade data for a given ticker symbol in a specified date range. Example usage: @@ -377,7 +377,7 @@ try { } ``` -### Latest Trade +## Latest Trade The Latest trade API provides the latest trade data for a given ticker symbol. Example usage: @@ -390,7 +390,7 @@ try { } ``` -### Quotes +## Quotes The Quotes API provides NBBO quotes for a given ticker symbol in a specified date range. Example usage: @@ -409,7 +409,7 @@ try { } ``` -### Latest Quote +## Latest Quote The Latest quote API provides the latest quote data for a given ticker symbol. Example usage: @@ -422,7 +422,7 @@ try { } ``` -### Bars +## Bars The bars API returns aggregate historical data for the requested securities. Example usage: @@ -442,7 +442,7 @@ try { } ``` -### Snapshot +## Snapshot The Snapshot API for multiple tickers provides the latest trade, latest quote, minute bar daily bar and previous daily bar data for the given ticker symbols. Example usage: @@ -458,7 +458,7 @@ try { } ``` -### Realtime Market Data +## Realtime Market Data Alpaca's Data API provides websocket streaming for trades, quotes and minute bars. This helps receive the most up to date market information that could help your trading strategy to act upon certain market movement. Example usage: @@ -517,7 +517,7 @@ try { } ``` -## Building +# Building To build this project yourself, clone this repository and run: ``` ./gradlew build @@ -528,7 +528,7 @@ To install built artifacts to your local maven repo, run: ./gradlew install -x test ``` -## Testing +# Testing To run mocked tests using Mockito, run: ``` @@ -542,4 +542,8 @@ To run live endpoint tests with Alpaca Paper credentials, create the `alpaca.pro ``` Note that the live tests will modify your account minimally. It's meant to test live endpoints on a real paper account during market hours to confirm that API methods are working properly. Please read through the live endpoint tests [here](https://github.com/Petersoj/alpaca-java/tree/master/src/test/java/live/net/jacobpeterson/alpaca) before running this testing suite on your own paper account. +# TODO +- Unit Testing (both live and mocked) +- Add [TimeSeriesDataStore](https://github.com/Petersoj/TimeSeriesDataStore) using Alpaca Data API + Contributions are welcome! diff --git a/build.gradle b/build.gradle index 17afefa7..9608f1b0 100644 --- a/build.gradle +++ b/build.gradle @@ -6,15 +6,17 @@ buildscript { } dependencies { + // For Maven and Maven Central support classpath group: 'com.bmuschko', name: 'gradle-nexus-plugin', version: '2.3.1' - classpath group: 'org.jsonschema2pojo', name: 'jsonschema2pojo-gradle-plugin', version: '1.0.2' + // For JSONSchema2POJO + classpath group: 'org.jsonschema2pojo', name: 'jsonschema2pojo-gradle-plugin', version: '1.1.1' } } plugins { id 'java' - id 'eclipse' + // For Maven and Maven Central support id 'maven-publish' id 'com.bmuschko.nexus' version '2.3.1' id 'io.codearte.nexus-staging' version '0.11.0' @@ -23,7 +25,6 @@ plugins { id 'jacoco' } -// Calling 'apply' here because 'jsonschema2pojo' doesn't exist in Gradle Central Plugin Repository apply plugin: 'jsonschema2pojo' tasks.getByName("generateJsonSchema2Pojo").enabled(false) // Disable default 'jsonSchema2Pojo' task @@ -36,14 +37,20 @@ repositories { } dependencies { - implementation group: 'org.slf4j', name: 'slf4j-api', version: '1.7.30' - // This is a bridge for the Jakarta Commons Logging library used in dependencies to actually use SLF4j instead - implementation group: 'org.slf4j', name: 'jcl-over-slf4j', version: '1.7.30' - implementation group: 'com.google.guava', name: 'guava', version: '20.0' - implementation group: 'com.mashape.unirest', name: 'unirest-java', version: '1.4.9' - implementation group: 'com.google.code.gson', name: 'gson', version: '2.8.5' - implementation group: 'org.eclipse.jetty.websocket', name: 'javax-websocket-client-impl', version: '9.4.28.v20200408' + // Logging framework + implementation group: 'org.slf4j', name: 'slf4j-api', version: '1.7.31' + // This is a bridge for the Apache Jakarta Commons Logging library used in dependencies to actually use SLF4j instead + implementation group: 'org.slf4j', name: 'jcl-over-slf4j', version: '1.7.31' + // Guava and GSON + implementation group: 'com.google.guava', name: 'guava', version: '30.1.1-jre' + implementation group: 'com.google.code.gson', name: 'gson', version: '2.8.7' + + // OkHttp + implementation group: 'com.squareup.okhttp3', name: 'okhttp', version: '4.9.1' + implementation group: 'com.github.devcsrj', name: 'slf4j-okhttp3-logging-interceptor', version: '1.0.1' + + // Unit test dependencies testImplementation group: 'org.slf4j', name: 'slf4j-simple', version: '1.7.30' testImplementation group: 'org.junit.jupiter', name: 'junit-jupiter', version: '5.7.0' testImplementation group: 'org.mockito', name: 'mockito-core', version: '3.7.0' @@ -72,12 +79,14 @@ targetCompatibility = 1.8 [compileJava, compileTestJava]*.options*.encoding = 'UTF-8' javadoc { + // Explicitly define source version + options.addStringOption('source', '8') // Used to suppress Javadoc linting warnings options.addStringOption('Xdoclint:none', '-quiet') // Explicitly define Charset for Javadocs - options.addStringOption("charset", "UTF-8") + options.addStringOption('charset', 'UTF-8') // Add Java SE 8 link - options.addStringOption("link", "https://docs.oracle.com/javase/8/docs/api/") + options.addStringOption('link', 'https://docs.oracle.com/javase/8/docs/api/') } test { @@ -176,13 +185,13 @@ final def targetDirectoryPath = file("${project.buildDir}/generated-sources/sche task generatePOJOs() { final def jsonSourceDirectory = file("${project.projectDir}/schema_json").getPath() - final def jsonPackageNameStart = "net.jacobpeterson.alpaca.domain" + final def jsonPackageNamePrefix = "net.jacobpeterson.alpaca.model" // Loop through all files in schema JSON file tree file(jsonSourceDirectory).eachFileRecurse { jsonFile -> if (jsonFile.getName().endsWith(".json")) { def startPackageIndex = jsonFile.getAbsolutePath().indexOf(jsonSourceDirectory) + jsonSourceDirectory.length() - def targetPackage = jsonPackageNameStart + jsonFile.getParentFile().getAbsolutePath() + def targetPackage = jsonPackageNamePrefix + jsonFile.getParentFile().getAbsolutePath() .substring(startPackageIndex) .replace(File.separator, '.').replace('-', '').replace('_', '') .toLowerCase() diff --git a/schema_json/account/account.json b/schema_json/account/account.json index 34ea99e6..7bbf8633 100644 --- a/schema_json/account/account.json +++ b/schema_json/account/account.json @@ -1,6 +1,6 @@ { "type": "object", - "title": "See https://docs.alpaca.markets/api-documentation/api-v2/account/", + "title": "See Account.", "properties": { "id": { "existingJavaType": "java.lang.String", @@ -11,24 +11,24 @@ "title": "Account number." }, "status": { - "existingJavaType": "net.jacobpeterson.alpaca.enums.account.AccountStatus", - "title": "See Account Status" + "existingJavaType": "net.jacobpeterson.alpaca.model.account.enums.AccountStatus", + "title": "The {@link net.jacobpeterson.alpaca.model.account.enums.AccountStatus}." }, "currency": { "existingJavaType": "java.lang.String", - "title": "“USD”" + "title": "\"USD\"" }, "cash": { "existingJavaType": "java.lang.String", - "title": "Cash balance" + "title": "Cash balance." }, "portfolio_value": { "existingJavaType": "java.lang.String", - "title": "Total value of cash + holding positions (This field is deprecated. It is equivalent to the equity field.)" + "title": "Total value of cash + holding positions. (This field is deprecated. It is equivalent to the equity field.)" }, "pattern_day_trader": { "existingJavaType": "java.lang.Boolean", - "title": "Whether or not the account has been flagged as a pattern day trader" + "title": "Whether or not the account has been flagged as a pattern day trader." }, "trade_suspended_by_user": { "existingJavaType": "java.lang.Boolean", @@ -48,63 +48,63 @@ }, "created_at": { "existingJavaType": "java.time.ZonedDateTime", - "title": "Timestamp this account was created at" + "title": "Timestamp this account was created at." }, "shorting_enabled": { "existingJavaType": "java.lang.Boolean", - "title": "Flag to denote whether or not the account is permitted to short" + "title": "Flag to denote whether or not the account is permitted to short." }, "long_market_value": { "existingJavaType": "java.lang.String", - "title": "Real-time MtM value of all long positions held in the account" + "title": "Real-time MtM value of all long positions held in the account." }, "short_market_value": { "existingJavaType": "java.lang.String", - "title": "Real-time MtM value of all short positions held in the account" + "title": "Real-time MtM value of all short positions held in the account." }, "equity": { "existingJavaType": "java.lang.String", - "title": "Cash + long_market_value + short_market_value" + "title": "Cash + long_market_value + short_market_value." }, "last_equity": { "existingJavaType": "java.lang.String", - "title": "Equity as of previous trading day at 16:00:00 ET" + "title": "Equity as of previous trading day at 16:00:00 ET." }, "multiplier": { "existingJavaType": "java.lang.String", - "title": "Buying power multiplier that represents account margin classification; valid values 1 (standard limited margin account with 1x buying power), 2 (reg T margin account with 2x intraday and overnight buying power; this is the default for all non-PDT accounts with $2,000 or more equity), 4 (PDT account with 4x intraday buying power and 2x reg T overnight buying power)" + "title": "Buying power multiplier that represents account margin classification; valid values 1 (standard limited margin account with 1x buying power), 2 (reg T margin account with 2x intraday and overnight buying power; this is the default for all non-PDT accounts with $2,000 or more equity), 4 (PDT account with 4x intraday buying power and 2x reg T overnight buying power)." }, "buying_power": { "existingJavaType": "java.lang.String", - "title": "Current available $ buying power; If multiplier = 4, this is your daytrade buying power which is calculated as (last_equity - (last) maintenance_margin) * 4; If multiplier = 2, buying_power = max(equity – initial_margin,0) * 2; If multiplier = 1, buying_power = cash" + "title": "Current available $ buying power; If multiplier = 4, this is your daytrade buying power which is calculated as (last_equity - (last) maintenance_margin) * 4; If multiplier = 2, buying_power = max(equity – initial_margin,0) * 2; If multiplier = 1, buying_power = cash." }, "initial_margin": { "existingJavaType": "java.lang.String", - "title": "Reg T initial margin requirement (continuously updated value)" + "title": "Reg T initial margin requirement (continuously updated value)." }, "maintenance_margin": { "existingJavaType": "java.lang.String", - "title": "Maintenance margin requirement (continuously updated value)" + "title": "Maintenance margin requirement (continuously updated value)." }, "sma": { "existingJavaType": "java.lang.String", - "title": "Value of special memorandum account (will be used at a later date to provide additional buying_power)" + "title": "Value of special memorandum account (will be used at a later date to provide additional buying_power)." }, "daytrade_count": { "existingJavaType": "java.lang.Integer", - "title": "The current number of daytrades that have been made in the last 5 trading days (inclusive of today)" + "title": "The current number of daytrades that have been made in the last 5 trading days (inclusive of today)." }, "last_maintenance_margin": { "existingJavaType": "java.lang.String", - "title": "Your maintenance margin requirement on the previous trading day" + "title": "Your maintenance margin requirement on the previous trading day." }, "daytrading_buying_power": { "existingJavaType": "java.lang.String", - "title": "Your buying power for day trades (continuously updated value)" + "title": "Your buying power for day trades (continuously updated value)." }, "regt_buying_power": { "existingJavaType": "java.lang.String", - "title": "Your buying power under Regulation T (your excess equity - equity minus margin value - times your margin multiplier)" + "title": "Your buying power under Regulation T (your excess equity - equity minus margin value - times your margin multiplier)." } } } diff --git a/schema_json/account/enums/account_status.json b/schema_json/account/enums/account_status.json new file mode 100644 index 00000000..7cb842bf --- /dev/null +++ b/schema_json/account/enums/account_status.json @@ -0,0 +1,36 @@ +{ + "type": "string", + "title": "See Account.", + "enum": [ + "ONBOARDING", + "SUBMISSION_FAILED", + "SUBMITTED", + "ACCOUNT_UPDATED", + "APPROVAL_PENDING", + "ACTIVE", + "REJECTED" + ], + "javaEnums": [ + { + "description": "The account is onboarding." + }, + { + "description": "The account application submission failed for some reason." + }, + { + "description": "The account application has been submitted for review." + }, + { + "description": "The account information is being updated." + }, + { + "description": "The final account approval is pending." + }, + { + "description": "The account is active for trading." + }, + { + "description": "The account application has been rejected." + } + ] +} diff --git a/schema_json/account_activities/account_activity.json b/schema_json/account_activities/account_activity.json index 35537847..2e931066 100644 --- a/schema_json/account_activities/account_activity.json +++ b/schema_json/account_activities/account_activity.json @@ -1,10 +1,14 @@ { "type": "object", - "title": "See https://docs.alpaca.markets/api-documentation/api-v2/account-activities/", + "title": "See Account Activities.", "properties": { "activity_type": { - "existingJavaType": "net.jacobpeterson.alpaca.enums.activity.ActivityType", - "title": "See https://docs.alpaca.markets/api-documentation/api-v2/account-activities/#activity-types for a list of possible values." + "existingJavaType": "net.jacobpeterson.alpaca.model.accountactivities.enums.ActivityType", + "title": "The {@link net.jacobpeterson.alpaca.model.accountactivities.enums.ActivityType}." + }, + "id": { + "existingJavaType": "java.lang.String", + "title": "An ID for the activity, always in \"::\" format. Can be sent as page_token in requests to facilitate the paging of results." } } } diff --git a/schema_json/account_activities/enums/activity_type.json b/schema_json/account_activities/enums/activity_type.json new file mode 100644 index 00000000..03c44af1 --- /dev/null +++ b/schema_json/account_activities/enums/activity_type.json @@ -0,0 +1,144 @@ +{ + "type": "string", + "title": "See Account Activities.", + "enum": [ + "FILL", + "TRANS", + "MISC", + "ACATC", + "ACATS", + "CSD", + "CSR", + "CSW", + "DIV", + "DIVCGL", + "DIVCGS", + "DIVFEE", + "DIVFT", + "DIVNRA", + "DIVROC", + "DIVTW", + "DIVTXEX", + "INT", + "INTNRA", + "INTTW", + "JNL", + "JNLC", + "JNLS", + "MA", + "NC", + "OPASN", + "OPEXP", + "OPXRC", + "PTC", + "PTR", + "FEE", + "REORG", + "SC", + "SSO" + ], + "javaEnums": [ + { + "description": "Order fills (both partial and full fills)." + }, + { + "description": "Cash transactions (both CSD and CSW)." + }, + { + "description": "Miscellaneous or rarely used activity types (All types except those in TRANS, DIV, or FILL)." + }, + { + "description": "ACATS IN/OUT (Cash)." + }, + { + "description": "ACATS IN/OUT (Securities)." + }, + { + "description": "Cash deposit(+)." + }, + { + "description": "Cash receipt(-)." + }, + { + "description": "Cash withdrawal(-)." + }, + { + "description": "Dividends." + }, + { + "description": "Dividend (capital gain long term)." + }, + { + "description": "Dividend (capital gain short term)." + }, + { + "description": "Dividend fee." + }, + { + "description": "Dividend adjusted (Foreign Tax Withheld)." + }, + { + "description": "Dividend adjusted (NRA Withheld)." + }, + { + "description": "Dividend return of capital." + }, + { + "description": "Dividend adjusted (Tefra Withheld)." + }, + { + "description": "Dividend (tax exempt)." + }, + { + "description": "Interest (credit/margin)." + }, + { + "description": "Interest adjusted (NRA Withheld)." + }, + { + "description": "Interest adjusted (Tefra Withheld)." + }, + { + "description": "Journal entry." + }, + { + "description": "Journal entry (cash)." + }, + { + "description": "Journal entry (stock)." + }, + { + "description": "Merger/Acquisition." + }, + { + "description": "Name change." + }, + { + "description": "Option assignment." + }, + { + "description": "Option expiration." + }, + { + "description": "Option exercise." + }, + { + "description": "Pass Thru Charge." + }, + { + "description": "Pass Thru Rebate." + }, + { + "description": "REG/TAF fees." + }, + { + "description": "Reorg CA." + }, + { + "description": "Symbol change." + }, + { + "description": "Stock spinoff." + } + ] +} diff --git a/schema_json/account_activities/enums/trade_activity_side.json b/schema_json/account_activities/enums/trade_activity_side.json new file mode 100644 index 00000000..ddcd4869 --- /dev/null +++ b/schema_json/account_activities/enums/trade_activity_side.json @@ -0,0 +1,8 @@ +{ + "type": "string", + "title": "See Account Activities.", + "enum": [ + "buy", + "sell" + ] +} diff --git a/schema_json/account_activities/enums/trade_activity_type.json b/schema_json/account_activities/enums/trade_activity_type.json new file mode 100644 index 00000000..8b92e2b9 --- /dev/null +++ b/schema_json/account_activities/enums/trade_activity_type.json @@ -0,0 +1,8 @@ +{ + "type": "string", + "title": "See Account Activities.", + "enum": [ + "fill", + "partial_fill" + ] +} diff --git a/schema_json/account_activities/non_trade_activity.json b/schema_json/account_activities/non_trade_activity.json index e559fe07..b4ee73b2 100644 --- a/schema_json/account_activities/non_trade_activity.json +++ b/schema_json/account_activities/non_trade_activity.json @@ -1,14 +1,10 @@ { "type": "object", - "title": "See https://docs.alpaca.markets/api-documentation/api-v2/account-activities/", + "title": "See Account Activities.", "extends": { - "existingJavaType": "net.jacobpeterson.alpaca.domain.accountactivities.AccountActivity" + "existingJavaType": "net.jacobpeterson.alpaca.model.accountactivities.AccountActivity" }, "properties": { - "id": { - "existingJavaType": "java.lang.String", - "title": "An ID for the activity, always in “::” format. Can be sent as page_token in requests to facilitate the paging of results." - }, "date": { "existingJavaType": "java.lang.String", "title": "The date on which the activity occurred or on which the transaction associated with the activity settled." @@ -31,7 +27,7 @@ }, "description": { "existingJavaType": "java.lang.String", - "title": "Description field for transaction records (TRANS,CSR,CSD)." + "title": "Description field for transaction records (TRANS, CSR, CSD)." }, "status": { "existingJavaType": "java.lang.String", diff --git a/schema_json/account_activities/trade_activity.json b/schema_json/account_activities/trade_activity.json index 55b2c59d..7561047d 100644 --- a/schema_json/account_activities/trade_activity.json +++ b/schema_json/account_activities/trade_activity.json @@ -1,14 +1,10 @@ { "type": "object", - "title": "See https://docs.alpaca.markets/api-documentation/api-v2/account-activities/", + "title": "See Account Activities.", "extends": { "existingJavaType": "net.jacobpeterson.alpaca.domain.accountactivities.AccountActivity" }, "properties": { - "id": { - "existingJavaType": "java.lang.String", - "title": "An id for the activity. Always in “::” format. Can be sent as page_token in requests to facilitate the paging of results." - }, "cum_qty": { "existingJavaType": "java.lang.String", "title": "The cumulative quantity of shares involved in the execution." @@ -26,8 +22,8 @@ "title": "The number of shares involved in the trade execution." }, "side": { - "existingJavaType": "java.lang.String", - "title": "Buy or sell" + "existingJavaType": "net.jacobpeterson.alpaca.model.accountactivities.enums.TradeActivitySide", + "title": "The {@link net.jacobpeterson.alpaca.model.accountactivities.enums.TradeActivitySide}." }, "symbol": { "existingJavaType": "java.lang.String", @@ -42,8 +38,8 @@ "title": "The id for the order that filled. Always in GUID format." }, "type": { - "existingJavaType": "java.lang.String", - "title": "fill or partial_fill" + "existingJavaType": "net.jacobpeterson.alpaca.model.accountactivities.enums.TradeActivityType", + "title": "The {@link net.jacobpeterson.alpaca.model.accountactivities.enums.TradeActivityType}." } } } diff --git a/schema_json/account_configuration/account_configuration.json b/schema_json/account_configuration/account_configuration.json index 1f80585b..9bf8fc35 100644 --- a/schema_json/account_configuration/account_configuration.json +++ b/schema_json/account_configuration/account_configuration.json @@ -1,14 +1,14 @@ { "type": "object", - "title": "See https://docs.alpaca.markets/api-documentation/api-v2/account-configuration/", + "title": "See Account Configuration.", "properties": { "dtbp_check": { - "existingJavaType": "java.lang.String", - "title": "Both, entry, or exit. Controls Day Trading Margin Call (DTMC) checks." + "existingJavaType": "net.jacobpeterson.alpaca.model.accountconfiguration.enums.DTBPCheck", + "title": "The {@link net.jacobpeterson.alpaca.model.accountconfiguration.enums.DTBPCheck}. Controls Day Trading Margin Call (DTMC) checks." }, "trade_confirm_email": { - "existingJavaType": "java.lang.String", - "title": "all or none. If none, emails for order fills are not sent." + "existingJavaType": "net.jacobpeterson.alpaca.model.accountconfiguration.enums.TradeConfirmEmail", + "title": "The {@link net.jacobpeterson.alpaca.model.accountconfiguration.enums.TradeConfirmEmail}. If {@link net.jacobpeterson.alpaca.model.accountconfiguration.enums.TradeConfirmEmail#NONE}, emails for order fills are not sent." }, "suspend_trade": { "existingJavaType": "java.lang.Boolean", diff --git a/schema_json/account_configuration/enums/dtbp_check.json b/schema_json/account_configuration/enums/dtbp_check.json new file mode 100644 index 00000000..75940d81 --- /dev/null +++ b/schema_json/account_configuration/enums/dtbp_check.json @@ -0,0 +1,10 @@ +{ + "type": "string", + "javaName": "DTBPCheck", + "title": "See Account Configuration.", + "enum": [ + "both", + "entry", + "exit" + ] +} diff --git a/schema_json/account_configuration/enums/trade_confirm_email.json b/schema_json/account_configuration/enums/trade_confirm_email.json new file mode 100644 index 00000000..8f28197c --- /dev/null +++ b/schema_json/account_configuration/enums/trade_confirm_email.json @@ -0,0 +1,8 @@ +{ + "type": "string", + "title": "See Account Configuration.", + "enum": [ + "all", + "none" + ] +} diff --git a/schema_json/asset/asset.json b/schema_json/asset/asset.json index 9e9cb088..49cd8218 100644 --- a/schema_json/asset/asset.json +++ b/schema_json/asset/asset.json @@ -1,6 +1,6 @@ { "type": "object", - "title": "See https://docs.alpaca.markets/api-documentation/api-v2/assets/", + "title": "See Assets.", "properties": { "id": { "existingJavaType": "java.lang.String", @@ -8,23 +8,23 @@ }, "class": { "existingJavaType": "java.lang.String", - "title": "“us_equity”" + "title": "\"us_equity\"" }, "exchange": { "existingJavaType": "java.lang.String", - "title": "AMEX, ARCA, BATS, NYSE, NASDAQ or NYSEARCA" + "title": "AMEX, ARCA, BATS, NYSE, NASDAQ or NYSEARCA." }, "symbol": { "existingJavaType": "java.lang.String", - "title": "symbol" + "title": "The symbol." }, "name": { "existingJavaType": "java.lang.String", - "title": "The official name of the asset" + "title": "The official name of the asset." }, "status": { - "existingJavaType": "java.lang.String", - "title": "active or inactive" + "existingJavaType": "net.jacobpeterson.alpaca.model.asset.enums.AssetStatus", + "title": "The {@link net.jacobpeterson.alpaca.model.asset.enums.AssetStatus}." }, "tradable": { "existingJavaType": "java.lang.Boolean", diff --git a/schema_json/asset/enums/asset_status.json b/schema_json/asset/enums/asset_status.json new file mode 100644 index 00000000..b77af48e --- /dev/null +++ b/schema_json/asset/enums/asset_status.json @@ -0,0 +1,8 @@ +{ + "type": "string", + "title": "See Assets.", + "enum": [ + "active", + "inactive" + ] +} diff --git a/schema_json/calendar/calendar.json b/schema_json/calendar/calendar.json index df9098a6..96207ded 100644 --- a/schema_json/calendar/calendar.json +++ b/schema_json/calendar/calendar.json @@ -1,18 +1,18 @@ { "type": "object", - "title": "See https://docs.alpaca.markets/api-documentation/api-v2/calendar/", + "title": "See Calendar.", "properties": { "date": { - "existingJavaType": "java.lang.String", - "title": "Date string in “%Y-%m-%d” format" + "existingJavaType": "java.time.LocalDate", + "title": "Date string in \"%Y-%m-%d\" format." }, "open": { - "existingJavaType": "java.lang.String", - "title": "The time the market opens at on this date in “%H:%M” format" + "existingJavaType": "java.time.LocalTime", + "title": "The time the market opens at on this date in \"%H:%M\" format." }, "close": { - "existingJavaType": "java.lang.String", - "title": "The time the market closes at on this date in “%H:%M” format" + "existingJavaType": "java.time.LocalTime", + "title": "The time the market closes at on this date in \"%H:%M\" format." } } } diff --git a/schema_json/clock/clock.json b/schema_json/clock/clock.json index 88579382..671d4a19 100644 --- a/schema_json/clock/clock.json +++ b/schema_json/clock/clock.json @@ -1,22 +1,22 @@ { "type": "object", - "title": "See https://docs.alpaca.markets/api-documentation/api-v2/clock/", + "title": "See Clock.", "properties": { "timestamp": { "existingJavaType": "java.time.ZonedDateTime", - "title": "Current timestamp" + "title": "Current timestamp." }, "is_open": { "existingJavaType": "java.lang.Boolean", - "title": "Whether or not the market is open" + "title": "Whether or not the market is open." }, "next_open": { "existingJavaType": "java.time.ZonedDateTime", - "title": "Next market open timestamp" + "title": "Next market open timestamp." }, "next_close": { "existingJavaType": "java.time.ZonedDateTime", - "title": "Next market close timestamp" + "title": "Next market close timestamp." } } } diff --git a/schema_json/market_data/historical/bar/bar.json b/schema_json/market_data/historical/bar/bar.json index d57dd011..b39bff53 100644 --- a/schema_json/market_data/historical/bar/bar.json +++ b/schema_json/market_data/historical/bar/bar.json @@ -1,6 +1,6 @@ { "type": "object", - "title": "See https://alpaca.markets/docs/api-documentation/api-v2/market-data/alpaca-data-api-v2/historical/", + "title": "See Historical Data.", "properties": { "t": { "existingJavaType": "java.time.ZonedDateTime", @@ -23,7 +23,7 @@ "title": "Close price." }, "v": { - "existingJavaType": "java.lang.Double", + "existingJavaType": "java.lang.Long", "title": "Volume." } } diff --git a/schema_json/market_data/historical/bar/bars_response.json b/schema_json/market_data/historical/bar/bars_response.json index 65b3baa4..28692460 100644 --- a/schema_json/market_data/historical/bar/bars_response.json +++ b/schema_json/market_data/historical/bar/bars_response.json @@ -1,10 +1,10 @@ { "type": "object", - "title": "See https://alpaca.markets/docs/api-documentation/api-v2/market-data/alpaca-data-api-v2/historical/", + "title": "See Historical Data.", "properties": { "bars": { - "existingJavaType": "java.util.ArrayList", - "title": "Array of bars." + "existingJavaType": "java.util.ArrayList", + "title": "The {@link java.util.ArrayList} of {@link net.jacobpeterson.alpaca.model.marketdata.historical.bar.Bar}s." }, "symbol": { "existingJavaType": "java.lang.String", diff --git a/schema_json/market_data/historical/quote/latest_quote_response.json b/schema_json/market_data/historical/quote/latest_quote_response.json index 071e43ed..e7fda37a 100644 --- a/schema_json/market_data/historical/quote/latest_quote_response.json +++ b/schema_json/market_data/historical/quote/latest_quote_response.json @@ -1,14 +1,14 @@ { "type": "object", - "title": "See https://alpaca.markets/docs/api-documentation/api-v2/market-data/alpaca-data-api-v2/historical/", + "title": "See Historical Data.", "properties": { "symbol": { "existingJavaType": "java.lang.String", "title": "Symbol that was queried." }, "quote": { - "existingJavaType": "net.jacobpeterson.alpaca.domain.marketdata.historical.quote.Quote", - "title": "The latest quote." + "existingJavaType": "net.jacobpeterson.alpaca.model.marketdata.historical.quote.Quote", + "title": "The latest {@link net.jacobpeterson.alpaca.model.marketdata.historical.quote.Quote}." } } } diff --git a/schema_json/market_data/historical/quote/quote.json b/schema_json/market_data/historical/quote/quote.json index a9f4c124..7a238f6e 100644 --- a/schema_json/market_data/historical/quote/quote.json +++ b/schema_json/market_data/historical/quote/quote.json @@ -1,6 +1,6 @@ { "type": "object", - "title": "See https://alpaca.markets/docs/api-documentation/api-v2/market-data/alpaca-data-api-v2/historical/", + "title": "See Historical Data.", "properties": { "t": { "existingJavaType": "java.time.ZonedDateTime", @@ -32,7 +32,7 @@ }, "c": { "existingJavaType": "java.util.ArrayList", - "title": "Quote conditions." + "title": "The {@link java.util.ArrayList} of quote conditions." } } } diff --git a/schema_json/market_data/historical/quote/quotes_response.json b/schema_json/market_data/historical/quote/quotes_response.json index 73ab9628..15810872 100644 --- a/schema_json/market_data/historical/quote/quotes_response.json +++ b/schema_json/market_data/historical/quote/quotes_response.json @@ -1,10 +1,10 @@ { "type": "object", - "title": "See https://alpaca.markets/docs/api-documentation/api-v2/market-data/alpaca-data-api-v2/historical/", + "title": "See Historical Data.", "properties": { "quotes": { - "existingJavaType": "java.util.ArrayList", - "title": "Array of quotes." + "existingJavaType": "java.util.ArrayList", + "title": "The {@link java.util.ArrayList} of {@link net.jacobpeterson.alpaca.model.marketdata.historical.quote.Quote}s." }, "symbol": { "existingJavaType": "java.lang.String", diff --git a/schema_json/market_data/historical/snapshot/snapshot.json b/schema_json/market_data/historical/snapshot/snapshot.json index 2226ffa0..97661731 100644 --- a/schema_json/market_data/historical/snapshot/snapshot.json +++ b/schema_json/market_data/historical/snapshot/snapshot.json @@ -1,26 +1,26 @@ { "type": "object", - "title": "See https://alpaca.markets/docs/api-documentation/api-v2/market-data/alpaca-data-api-v2/historical/", + "title": "See Historical Data.", "properties": { "latestTrade": { - "existingJavaType": "net.jacobpeterson.alpaca.domain.marketdata.historical.trade.Trade", - "title": "Latest trade." + "existingJavaType": "net.jacobpeterson.alpaca.model.marketdata.historical.trade.Trade", + "title": "The latest {@link net.jacobpeterson.alpaca.model.marketdata.historical.trade.Trade}." }, "latestQuote": { - "existingJavaType": "net.jacobpeterson.alpaca.domain.marketdata.historical.quote.Quote", - "title": "Latest quote." + "existingJavaType": "net.jacobpeterson.alpaca.model.marketdata.historical.quote.Quote", + "title": "The latest {@link net.jacobpeterson.alpaca.model.marketdata.historical.quote.Quote}." }, "minuteBar": { - "existingJavaType": "net.jacobpeterson.alpaca.domain.marketdata.historical.bar.Bar", - "title": "Minute bar." + "existingJavaType": "net.jacobpeterson.alpaca.model.marketdata.historical.bar.Bar", + "title": "The minute {@link net.jacobpeterson.alpaca.model.marketdata.historical.bar.Bar}." }, "dailyBar": { - "existingJavaType": "net.jacobpeterson.alpaca.domain.marketdata.historical.bar.Bar", - "title": "Daily bar." + "existingJavaType": "net.jacobpeterson.alpaca.model.marketdata.historical.bar.Bar", + "title": "The daily {@link net.jacobpeterson.alpaca.model.marketdata.historical.bar.Bar}." }, "prevDailyBar": { - "existingJavaType": "net.jacobpeterson.alpaca.domain.marketdata.historical.bar.Bar", - "title": "Previous daily close bar." + "existingJavaType": "net.jacobpeterson.alpaca.model.marketdata.historical.bar.Bar", + "title": "The previous daily {@link net.jacobpeterson.alpaca.model.marketdata.historical.bar.Bar}." } } } diff --git a/schema_json/market_data/historical/trade/latest_trade_response.json b/schema_json/market_data/historical/trade/latest_trade_response.json index 46010258..d71856aa 100644 --- a/schema_json/market_data/historical/trade/latest_trade_response.json +++ b/schema_json/market_data/historical/trade/latest_trade_response.json @@ -1,14 +1,14 @@ { "type": "object", - "title": "See https://alpaca.markets/docs/api-documentation/api-v2/market-data/alpaca-data-api-v2/historical/", + "title": "See Historical Data.", "properties": { "symbol": { "existingJavaType": "java.lang.String", "title": "Symbol that was queried." }, "trade": { - "existingJavaType": "net.jacobpeterson.alpaca.domain.marketdata.historical.trade.Trade", - "title": "The latest trade." + "existingJavaType": "net.jacobpeterson.alpaca.model.marketdata.historical.trade.Trade", + "title": "The latest {@link net.jacobpeterson.alpaca.model.marketdata.historical.trade.Trade}." } } } diff --git a/schema_json/market_data/historical/trade/trade.json b/schema_json/market_data/historical/trade/trade.json index ddb6c378..c9303e7b 100644 --- a/schema_json/market_data/historical/trade/trade.json +++ b/schema_json/market_data/historical/trade/trade.json @@ -1,6 +1,6 @@ { "type": "object", - "title": "See https://alpaca.markets/docs/api-documentation/api-v2/market-data/alpaca-data-api-v2/historical/", + "title": "See Historical Data.", "properties": { "t": { "existingJavaType": "java.time.ZonedDateTime", @@ -20,7 +20,7 @@ }, "c": { "existingJavaType": "java.util.ArrayList", - "title": "Trade conditions." + "title": "The {@link java.util.ArrayList} of trade conditions." }, "i": { "existingJavaType": "java.lang.Long", diff --git a/schema_json/market_data/historical/trade/trades_response.json b/schema_json/market_data/historical/trade/trades_response.json index 6134d448..810bc260 100644 --- a/schema_json/market_data/historical/trade/trades_response.json +++ b/schema_json/market_data/historical/trade/trades_response.json @@ -1,10 +1,10 @@ { "type": "object", - "title": "See https://alpaca.markets/docs/api-documentation/api-v2/market-data/alpaca-data-api-v2/historical/", + "title": "See Historical Data.", "properties": { "trades": { - "existingJavaType": "java.util.ArrayList", - "title": "Array of trades." + "existingJavaType": "java.util.ArrayList", + "title": "The {@link java.util.ArrayList} of {@link net.jacobpeterson.alpaca.model.marketdata.historical.trade.Trade}s." }, "symbol": { "existingJavaType": "java.lang.String", diff --git a/schema_json/market_data/realtime/bar/bar_message.json b/schema_json/market_data/realtime/bar/bar_message.json index 5f882bcb..9688c494 100644 --- a/schema_json/market_data/realtime/bar/bar_message.json +++ b/schema_json/market_data/realtime/bar/bar_message.json @@ -3,37 +3,37 @@ "extends": { "existingJavaType": "net.jacobpeterson.alpaca.domain.marketdata.realtime.SymbolMessage" }, - "title": "See https://alpaca.markets/docs/api-documentation/api-v2/market-data/streaming/", + "title": "See Real-time Data.", "properties": { "o": { "existingJavaType": "java.lang.Double", "javaName": "open", - "title": "Open price" + "title": "Open price." }, "h": { "existingJavaType": "java.lang.Double", "javaName": "high", - "title": "High price" + "title": "High price." }, "l": { "existingJavaType": "java.lang.Double", "javaName": "low", - "title": "Low price" + "title": "Low price." }, "c": { "existingJavaType": "java.lang.Double", "javaName": "close", - "title": "Close price" + "title": "Close price." }, "v": { - "existingJavaType": "java.lang.Integer", + "existingJavaType": "java.lang.Long", "javaName": "volume", - "title": "Volume" + "title": "Volume." }, "t": { "existingJavaType": "java.time.ZonedDateTime", "javaName": "timestamp", - "title": "Timestamp" + "title": "The timestamp." } } } diff --git a/schema_json/market_data/realtime/control/error_message.json b/schema_json/market_data/realtime/control/error_message.json index 54107768..5453dce0 100644 --- a/schema_json/market_data/realtime/control/error_message.json +++ b/schema_json/market_data/realtime/control/error_message.json @@ -3,7 +3,7 @@ "extends": { "existingJavaType": "net.jacobpeterson.alpaca.domain.marketdata.realtime.MarketDataMessage" }, - "title": "See https://alpaca.markets/docs/api-documentation/api-v2/market-data/streaming/", + "title": "See Real-time Data.", "properties": { "code": { "existingJavaType": "java.lang.Integer", diff --git a/schema_json/market_data/realtime/control/subscriptions_message.json b/schema_json/market_data/realtime/control/subscriptions_message.json index 0f2134ab..41e0dec8 100644 --- a/schema_json/market_data/realtime/control/subscriptions_message.json +++ b/schema_json/market_data/realtime/control/subscriptions_message.json @@ -3,19 +3,19 @@ "extends": { "existingJavaType": "net.jacobpeterson.alpaca.domain.marketdata.realtime.MarketDataMessage" }, - "title": "See https://alpaca.markets/docs/api-documentation/api-v2/market-data/streaming/", + "title": "See Real-time Data.", "properties": { "trades": { "existingJavaType": "java.util.ArrayList", - "title": "The list of subscribed trades." + "title": "The {@link java.util.Arraylist} of subscribed trades." }, "quotes": { "existingJavaType": "java.util.ArrayList", - "title": "The list of subscribed quotes." + "title": "The {@link java.util.Arraylist} of subscribed quotes." }, "bars": { "existingJavaType": "java.util.ArrayList", - "title": "The list of subscribed bars." + "title": "The {@link java.util.Arraylist} of subscribed bars." } } } diff --git a/schema_json/market_data/realtime/control/success_message.json b/schema_json/market_data/realtime/control/success_message.json index 0b161ae2..1103e67c 100644 --- a/schema_json/market_data/realtime/control/success_message.json +++ b/schema_json/market_data/realtime/control/success_message.json @@ -3,7 +3,7 @@ "extends": { "existingJavaType": "net.jacobpeterson.alpaca.domain.marketdata.realtime.MarketDataMessage" }, - "title": "See https://alpaca.markets/docs/api-documentation/api-v2/market-data/streaming/", + "title": "See Real-time Data.", "properties": { "msg": { "existingJavaType": "java.lang.String", diff --git a/schema_json/market_data/realtime/enums/market_data_message_type.json b/schema_json/market_data/realtime/enums/market_data_message_type.json new file mode 100644 index 00000000..40368ec9 --- /dev/null +++ b/schema_json/market_data/realtime/enums/market_data_message_type.json @@ -0,0 +1,32 @@ +{ + "type": "string", + "title": "See Real-time Data.", + "enum": [ + "success", + "error", + "subscription", + "t", + "q", + "b" + ], + "javaEnums": [ + { + "name": "SUCCESS" + }, + { + "name": "ERROR" + }, + { + "name": "SUBSCRIPTION" + }, + { + "name": "TRADE" + }, + { + "name": "QUOTE" + }, + { + "name": "BAR" + } + ] +} diff --git a/schema_json/market_data/realtime/market_data_message.json b/schema_json/market_data/realtime/market_data_message.json index 0643f9bf..5a96da74 100644 --- a/schema_json/market_data/realtime/market_data_message.json +++ b/schema_json/market_data/realtime/market_data_message.json @@ -3,12 +3,12 @@ "javaInterfaces": [ "net.jacobpeterson.alpaca.abstracts.websocket.message.StreamMessage" ], - "title": "See https://alpaca.markets/docs/api-documentation/api-v2/market-data/alpaca-data-api-v2/real-time/", + "title": "See Real-time Data.", "properties": { "T": { - "existingJavaType": "java.lang.String", + "existingJavaType": "net.jacobpeterson.alpaca.model.marketdata.realtime.enums.MarketDataMessageType", "javaName": "messageType", - "title": "The message type." + "title": "The {@link net.jacobpeterson.alpaca.model.marketdata.realtime.enums.MarketDataMessageType}." } } } diff --git a/schema_json/market_data/realtime/quote/quote_message.json b/schema_json/market_data/realtime/quote/quote_message.json index c34e289b..2e3179f3 100644 --- a/schema_json/market_data/realtime/quote/quote_message.json +++ b/schema_json/market_data/realtime/quote/quote_message.json @@ -3,52 +3,52 @@ "extends": { "existingJavaType": "net.jacobpeterson.alpaca.domain.marketdata.realtime.SymbolMessage" }, - "title": "See https://alpaca.markets/docs/api-documentation/api-v2/market-data/streaming/", + "title": "See Real-time Data.", "properties": { "ax": { "existingJavaType": "java.lang.String", "javaName": "askExchangeCode", - "title": "Ask exchange code" + "title": "Ask exchange code." }, "ap": { "existingJavaType": "java.lang.Double", "javaName": "askPrice", - "title": "Ask price" + "title": "Ask price." }, "as": { "existingJavaType": "java.lang.Integer", "javaName": "askSize", - "title": "Ask size" + "title": "Ask size." }, "bx": { "existingJavaType": "java.lang.String", "javaName": "bidExchangeCode", - "title": "Bid exchange code" + "title": "Bid exchange code." }, "bp": { "existingJavaType": "java.lang.Double", "javaName": "bidPrice", - "title": "Bid price" + "title": "Bid price." }, "bs": { "existingJavaType": "java.lang.Integer", "javaName": "bidSize", - "title": "Bid size" + "title": "Bid size." }, "t": { "existingJavaType": "java.time.ZonedDateTime", "javaName": "timestamp", - "title": "Timestamp with nanosecond precision" + "title": "Timestamp with nanosecond precision." }, "c": { "existingJavaType": "java.util.ArrayList", "javaName": "conditions", - "title": "Quote conditions" + "title": "The {@link java.util.ArrayList} of quote conditions." }, "z": { "existingJavaType": "java.lang.String", "javaName": "tape", - "title": "Tape" + "title": "Tape." } } } diff --git a/schema_json/market_data/realtime/symbol_message.json b/schema_json/market_data/realtime/symbol_message.json index d430d031..e69ef37c 100644 --- a/schema_json/market_data/realtime/symbol_message.json +++ b/schema_json/market_data/realtime/symbol_message.json @@ -3,12 +3,12 @@ "extends": { "existingJavaType": "net.jacobpeterson.alpaca.domain.marketdata.realtime.MarketDataMessage" }, - "title": "See https://alpaca.markets/docs/api-documentation/api-v2/market-data/streaming/", + "title": "See Real-time Data.", "properties": { "S": { "existingJavaType": "java.lang.String", "javaName": "symbol", - "title": "Symbol" + "title": "The symbol." } } } diff --git a/schema_json/market_data/realtime/trade/trade_message.json b/schema_json/market_data/realtime/trade/trade_message.json index 4b05a13b..f2bcca33 100644 --- a/schema_json/market_data/realtime/trade/trade_message.json +++ b/schema_json/market_data/realtime/trade/trade_message.json @@ -3,42 +3,42 @@ "extends": { "existingJavaType": "net.jacobpeterson.alpaca.domain.marketdata.realtime.SymbolMessage" }, - "title": "See https://alpaca.markets/docs/api-documentation/api-v2/market-data/streaming/", + "title": "See Real-time Data.", "properties": { "i": { "existingJavaType": "java.lang.Integer", "javaName": "tradeID", - "title": "Trade ID" + "title": "Trade ID." }, "x": { "existingJavaType": "java.lang.String", "javaName": "exchange", - "title": "Exchange code where the trade occurred" + "title": "Exchange code where the trade occurred." }, "p": { "existingJavaType": "java.lang.Double", "javaName": "price", - "title": "Trade price" + "title": "Trade price." }, "s": { "existingJavaType": "java.lang.Integer", "javaName": "size", - "title": "Trade size" + "title": "Trade size." }, "t": { "existingJavaType": "java.time.ZonedDateTime", "javaName": "timestamp", - "title": "Timestamp with nanosecond precision" + "title": "Timestamp with nanosecond precision." }, "c": { "existingJavaType": "java.util.ArrayList", "javaName": "conditions", - "title": "Trade conditions" + "title": "The {@link java.util.ArrayList} of trade conditions." }, "z": { "existingJavaType": "java.lang.String", "javaName": "tape", - "title": "Tape" + "title": "Tape." } } } diff --git a/schema_json/order/cancelled_order.json b/schema_json/order/cancelled_order.json index a4b7464d..96cd3967 100644 --- a/schema_json/order/cancelled_order.json +++ b/schema_json/order/cancelled_order.json @@ -1,19 +1,19 @@ { "type": "object", - "title": "See https://docs.alpaca.markets/api-documentation/api-v2/orders/", + "title": "See Orders.", "properties": { "id": { "existingJavaType": "java.lang.String", - "title": "Order ID" + "title": "Order ID." }, "status": { "existingJavaType": "java.lang.Integer", - "title": "The HTTP status code of the cancel status" + "title": "The HTTP status code of the cancelled order." }, "body": { - "existingJavaType": "net.jacobpeterson.alpaca.domain.order.Order", + "existingJavaType": "net.jacobpeterson.alpaca.model.order.Order", "javaName": "order", - "title": "The cancelled order" + "title": "The cancelled {@link net.jacobpeterson.alpaca.model.order.Order}." } } } diff --git a/schema_json/order/enums/order_class.json b/schema_json/order/enums/order_class.json new file mode 100644 index 00000000..6baaf86c --- /dev/null +++ b/schema_json/order/enums/order_class.json @@ -0,0 +1,24 @@ +{ + "type": "string", + "title": "See Orders.", + "enum": [ + "simple", + "bracket", + "oco", + "oto" + ], + "javaEnums": [ + { + "name": "SIMPLE" + }, + { + "name": "BRACKET" + }, + { + "name": "ONE_CANCELS_OTHER" + }, + { + "name": "ONE_TRIGGERS_OTHER" + } + ] +} diff --git a/schema_json/order/enums/order_side.json b/schema_json/order/enums/order_side.json new file mode 100644 index 00000000..e8b6178f --- /dev/null +++ b/schema_json/order/enums/order_side.json @@ -0,0 +1,8 @@ +{ + "type": "string", + "title": "See Orders.", + "enum": [ + "buy", + "sell" + ] +} diff --git a/schema_json/order/enums/order_status.json b/schema_json/order/enums/order_status.json new file mode 100644 index 00000000..135b39b7 --- /dev/null +++ b/schema_json/order/enums/order_status.json @@ -0,0 +1,72 @@ +{ + "type": "string", + "title": "See Orders.", + "enum": [ + "new", + "partially_filled", + "filled", + "done_for_day", + "canceled", + "expired", + "replaced", + "pending_cancel", + "pending_replace", + "accepted", + "pending_new", + "accepted_for_bidding", + "stopped", + "rejected", + "suspended", + "calculated" + ], + "javaEnums": [ + { + "description": "The order has been received by Alpaca, and routed to exchanges for execution. This is the usual initial state of an order." + }, + { + "description": "The order has been partially filled." + }, + { + "description": "The order has been filled, and no further updates will occur for the order." + }, + { + "description": "The order is done executing for the day, and will not receive further updates until the next trading day." + }, + { + "description": "The order has been canceled, and no further updates will occur for the order. This can be either due to a cancel request by the user, or the order has been canceled by the exchanges due to its time-in-force." + }, + { + "description": "The order has expired, and no further updates will occur for the order." + }, + { + "description": "The order was replaced by another order, or was updated due to a market event such as corporate action." + }, + { + "description": "The order is waiting to be canceled." + }, + { + "description": "The order is waiting to be replaced by another order. The order will reject cancel request while in this state." + }, + { + "description": "The order has been received by Alpaca, but hasn’t yet been routed to the execution venue. This could be seen often out side of trading session hours." + }, + { + "description": "The order has been received by Alpaca, and routed to the exchanges, but has not yet been accepted for execution. This state only occurs on rare occasions." + }, + { + "description": "The order has been received by exchanges, and is evaluated for pricing. This state only occurs on rare occasions." + }, + { + "description": "The order has been stopped, and a trade is guaranteed for the order, usually at a stated price or better, but has not yet occurred. This state only occurs on rare occasions." + }, + { + "description": "The order has been rejected, and no further updates will occur for the order. This state occurs on rare occasions and may occur based on various conditions decided by the exchanges." + }, + { + "description": "The order has been suspended, and is not eligible for trading. This state only occurs on rare occasions." + }, + { + "description": "The order has been completed for the day (either filled or done for day), but remaining settlement calculations are still pending. This state only occurs on rare occasions." + } + ] +} diff --git a/schema_json/order/enums/order_time_in_force.json b/schema_json/order/enums/order_time_in_force.json new file mode 100644 index 00000000..fe468cd2 --- /dev/null +++ b/schema_json/order/enums/order_time_in_force.json @@ -0,0 +1,38 @@ +{ + "type": "string", + "title": "See Orders.", + "enum": [ + "day", + "gtc", + "opg", + "cls", + "ioc", + "fok" + ], + "javaEnums": [ + { + "name": "DAY", + "description": "A day order is eligible for execution only on the day it is live. By default, the order is only valid during Regular Trading Hours (9:30am - 4:00pm ET). If unfilled after the closing auction, it is automatically canceled. If submitted after the close, it is queued and submitted the following trading day. However, if marked as eligible for extended hours, the order can also execute during supported extended hours." + }, + { + "name": "GOOD_UNTIL_CANCELLED", + "description": "The order is good until canceled. Non-marketable GTC limit orders are subject to price adjustments to offset corporate actions affecting the issue. We do not currently support Do Not Reduce(DNR) orders to opt out of such price adjustments." + }, + { + "name": "OPG", + "description": "Use this TIF with a market/limit order type to submit \"market on open\" (MOO) and \"limit on open\" (LOO) orders. This order is eligible to execute only in the market opening auction. Any unfilled orders after the open will be cancelled. OPG orders submitted after 9:28am but before 7:00pm ET will be rejected. OPG orders submitted after 7:00pm will be queued and routed to the following day’s opening auction. On open/on close orders are routed to the primary exchange. Such orders do not necessarily execute exactly at 9:30am / 4:00pm ET but execute per the exchange’s auction rules." + }, + { + "name": "CLS", + "description": "Use this TIF with a market/limit order type to submit \"market on close\" (MOC) and \"limit on close\" (LOC) orders. This order is eligible to execute only in the market closing auction. Any unfilled orders after the close will be cancelled. CLS orders submitted after 3:50pm but before 7:00pm ET will be rejected. CLS orders submitted after 7:00pm will be queued and routed to the following day’s closing auction. Only available with API v2." + }, + { + "name": "IMMEDIATE_OR_CANCEL", + "description": "An Immediate Or Cancel (IOC) order requires all or part of the order to be executed immediately. Any unfilled portion of the order is canceled. Only available with API v2. Most market makers who receive IOC orders will attempt to fill the order on a principal basis only, and cancel any unfilled balance. On occasion, this can result in the entire order being cancelled if the market maker does not have any existing inventory of the security in question." + }, + { + "name": "FILL_OR_KILL", + "description": "A Fill or Kill (FOK) order is only executed if the entire order quantity can be filled, otherwise the order is canceled. Only available with API v2." + } + ] +} diff --git a/schema_json/order/enums/order_type.json b/schema_json/order/enums/order_type.json new file mode 100644 index 00000000..32d93d2b --- /dev/null +++ b/schema_json/order/enums/order_type.json @@ -0,0 +1,28 @@ +{ + "type": "string", + "title": "See Orders.", + "enum": [ + "market", + "limit", + "stop", + "stop_limit", + "trailing_stop" + ], + "javaEnums": [ + { + "description": "A market order is a request to buy or sell a security at the currently available market price. It provides the most likely method of filling an order. Market orders fill nearly instantaneously." + }, + { + "description": "A limit order is an order to buy or sell at a specified price or better. A buy limit order (a limit order to buy) is executed at the specified limit price or lower (i.e., better). Conversely, a sell limit order (a limit order to sell) is executed at the specified limit price or higher (better). Unlike a market order, you have to specify the limit price parameter when submitting your order." + }, + { + "description": "A stop (market) order is an order to buy or sell a security when its price moves past a particular point, ensuring a higher probability of achieving a predetermined entry or exit price. Once the market price crosses the specified stop price, the stop order becomes a market order. Alpaca converts buy stop orders into stop limit orders with a limit price that is 4% higher than a stop price < $50 (or 2.5% higher than a stop price >= $50). Sell stop orders are not converted into stop limit orders." + }, + { + "description": "A stop-limit order is a conditional trade over a set time frame that combines the features of a stop order with those of a limit order and is used to mitigate risk. The stop-limit order will be executed at a specified limit price, or better, after a given stop price has been reached. Once the stop price is reached, the stop-limit order becomes a limit order to buy or sell at the limit price or better" + }, + { + "description": "Trailing stop orders allow you to continuously and automatically keep updating the stop price threshold based on the stock price movement. You request a single order with a dollar offset value or percentage value as the trail and the actual stop price for this order changes as the stock price moves in your favorable way, or stay at the last level otherwise. This way, you don’t need to monitor the price movement and keep sending replace requests to update the stop price close to the latest market movement." + } + ] +} diff --git a/schema_json/order/order.json b/schema_json/order/order.json index c02647f0..d9b5c0d6 100644 --- a/schema_json/order/order.json +++ b/schema_json/order/order.json @@ -1,66 +1,66 @@ { "type": "object", - "title": "See https://docs.alpaca.markets/api-documentation/api-v2/orders/", + "title": "See Orders.", "properties": { "id": { "existingJavaType": "java.lang.String", - "title": "Order ID" + "title": "Order ID." }, "client_order_id": { "existingJavaType": "java.lang.String", - "title": "Client unique order ID" + "title": "Client unique order ID." }, "created_at": { "existingJavaType": "java.time.ZonedDateTime", - "title": "Created at" + "title": "Created at." }, "updated_at": { "existingJavaType": "java.time.ZonedDateTime", - "title": "Updated at" + "title": "Updated at." }, "submitted_at": { "existingJavaType": "java.time.ZonedDateTime", - "title": "Submitted at" + "title": "Submitted at." }, "filled_at": { "existingJavaType": "java.time.ZonedDateTime", - "title": "Filled at" + "title": "Filled at." }, "expired_at": { "existingJavaType": "java.time.ZonedDateTime", - "title": "Expired at" + "title": "Expired at." }, "canceled_at": { "existingJavaType": "java.time.ZonedDateTime", - "title": "Canceled at" + "title": "Canceled at." }, "failed_at": { "existingJavaType": "java.time.ZonedDateTime", - "title": "Failed at" + "title": "Failed at." }, "replaced_at": { "existingJavaType": "java.time.ZonedDateTime", - "title": "Replaced at" + "title": "Replaced at." }, "replaced_by": { "existingJavaType": "java.lang.String", - "title": "The order ID that this order was replaced by" + "title": "The order ID that this order was replaced by." }, "replaces": { "existingJavaType": "java.lang.String", - "title": "The order ID that this order replaces" + "title": "The order ID that this order replaces." }, "asset_id": { "existingJavaType": "java.lang.String", - "title": "Asset ID" + "title": "Asset ID." }, "symbol": { "existingJavaType": "java.lang.String", - "title": "Asset symbol" + "title": "Asset symbol." }, "asset_class": { "existingJavaType": "java.lang.String", - "title": "Asset class" + "title": "Asset class." }, "notional": { "existingJavaType": "java.lang.String", @@ -72,51 +72,51 @@ }, "filled_qty": { "existingJavaType": "java.lang.String", - "title": "Filled quantity" + "title": "Filled quantity." }, "filled_avg_price": { "existingJavaType": "java.lang.String", - "title": "Filled average price" + "title": "Filled average price." }, "order_class": { - "existingJavaType": "java.lang.String", - "title": "simple, bracket, oco or oto. For details of non-simple order classes, please see Bracket Order Overview" + "existingJavaType": "net.jacobpeterson.alpaca.model.order.enums.OrderClass", + "title": "The {@link net.jacobpeterson.alpaca.model.order.enums.OrderClass}. For details of non-simple order classes, please see Bracket Order Overview." }, "order_type": { "existingJavaType": "java.lang.String", - "title": "(Deprecated with just type field below.)" + "title": "(Deprecated with just type field below.)." }, "type": { - "existingJavaType": "java.lang.String", - "title": "Valid values: market, limit, stop, stop_limit, trailing_stop" + "existingJavaType": "net.jacobpeterson.alpaca.model.order.enums.OrderType", + "title": "The {@link net.jacobpeterson.alpaca.model.order.enums.OrderType}." }, "side": { - "existingJavaType": "java.lang.String", - "title": "Valid values: buy, sell" + "existingJavaType": "net.jacobpeterson.alpaca.model.order.enums.OrderSide", + "title": "The {@link net.jacobpeterson.alpaca.model.order.enums.OrderSide}." }, "time_in_force": { - "existingJavaType": "java.lang.String", - "title": "See Orders page" + "existingJavaType": "net.jacobpeterson.alpaca.model.order.enums.OrderTimeInForce", + "title": "The {@link net.jacobpeterson.alpaca.model.order.enums.OrderTimeInForce}." }, "limit_price": { "existingJavaType": "java.lang.String", - "title": "Limit price" + "title": "Limit price." }, "stop_price": { "existingJavaType": "java.lang.String", - "title": "Stop price" + "title": "Stop price." }, "status": { - "existingJavaType": "java.lang.String", - "title": "See Orders page" + "existingJavaType": "net.jacobpeterson.alpaca.model.order.enums.OrderStatus", + "title": "The {@link net.jacobpeterson.alpaca.model.order.enums.OrderStatus}." }, "extended_hours": { "existingJavaType": "java.lang.Boolean", "title": "If true, eligible for execution outside regular trading hours." }, "legs": { - "existingJavaType": "java.util.ArrayList", - "title": "When querying non-simple order_class orders in a nested style, an array of Order entities associated with this order. Otherwise, null." + "existingJavaType": "java.util.ArrayList", + "title": "When querying non-simple order_class orders in a nested style, an {@link java.util.ArrayList} of {@link net.jacobpeterson.alpaca.model.order.Order} entities associated with this order. Otherwise, null." }, "trail_percent": { "existingJavaType": "java.lang.String", diff --git a/schema_json/portfolio_history/enums/portfolio_period_unit.json b/schema_json/portfolio_history/enums/portfolio_period_unit.json new file mode 100644 index 00000000..b55c083d --- /dev/null +++ b/schema_json/portfolio_history/enums/portfolio_period_unit.json @@ -0,0 +1,24 @@ +{ + "type": "string", + "title": "See Portfolio History.", + "enum": [ + "D", + "W", + "M", + "A" + ], + "javaEnums": [ + { + "name": "DAY" + }, + { + "name": "WEEK" + }, + { + "name": "MONTH" + }, + { + "name": "YEAR" + } + ] +} diff --git a/schema_json/portfolio_history/enums/portfolio_time_frame.json b/schema_json/portfolio_history/enums/portfolio_time_frame.json new file mode 100644 index 00000000..2cd977e2 --- /dev/null +++ b/schema_json/portfolio_history/enums/portfolio_time_frame.json @@ -0,0 +1,28 @@ +{ + "type": "string", + "title": "See Portfolio History.", + "enum": [ + "1Min", + "5Min", + "15Min", + "1H", + "1D" + ], + "javaEnums": [ + { + "description": "ONE_MIN" + }, + { + "description": "FIVE_MINUTE" + }, + { + "description": "FIFTEEN_MINUTE" + }, + { + "description": "ONE_HOUR" + }, + { + "description": "ONE_DAY" + } + ] +} diff --git a/schema_json/portfolio_history/portfolio_history.json b/schema_json/portfolio_history/portfolio_history.json index b5b10e50..c24275de 100644 --- a/schema_json/portfolio_history/portfolio_history.json +++ b/schema_json/portfolio_history/portfolio_history.json @@ -1,30 +1,30 @@ { "type": "object", - "title": "See https://docs.alpaca.markets/api-documentation/api-v2/portfolio-history/", + "title": "See Portfolio History.", "properties": { "timestamp": { "existingJavaType": "java.util.ArrayList", - "title": "Time of each data element, left-labeled (the beginning of time window)" + "title": "Time of each data element, left-labeled (the beginning of time window)." }, "equity": { "existingJavaType": "java.util.ArrayList", - "title": "Equity value of the account in dollar amount as of the end of each time window" + "title": "Equity value of the account in dollar amount as of the end of each time window." }, "profit_loss": { "existingJavaType": "java.util.ArrayList", - "title": "Profit/loss in dollar from the base value" + "title": "Profit/loss in dollar from the base value." }, "profit_loss_pct": { "existingJavaType": "java.util.ArrayList", - "title": "Profit/loss in percentage from the base value" + "title": "Profit/loss in percentage from the base value." }, "base_value": { "existingJavaType": "java.lang.Double", - "title": "Basis in dollar of the profit loss calculation" + "title": "Basis in dollar of the profit loss calculation." }, "timeframe": { - "existingJavaType": "net.jacobpeterson.alpaca.enums.portfolio.PortfolioTimeFrame", - "title": "Time window size of each data element" + "existingJavaType": "net.jacobpeterson.alpaca.model.portfolio.enums.PortfolioTimeFrame", + "title": "Time window size of each data element." } } } diff --git a/schema_json/position/close_position_order.json b/schema_json/position/close_position_order.json index 9dc71a06..b1aacddd 100644 --- a/schema_json/position/close_position_order.json +++ b/schema_json/position/close_position_order.json @@ -4,15 +4,16 @@ "properties": { "symbol": { "existingJavaType": "java.lang.String", - "title": "The symbol" + "title": "The symbol." }, "status": { "existingJavaType": "java.lang.Integer", - "title": "The HTTP status code of the position-closing order status" + "title": "The HTTP status code of the position-closing order." }, "body": { - "existingJavaType": "net.jacobpeterson.alpaca.domain.order.Order", - "title": "The position-closing order" + "existingJavaType": "net.jacobpeterson.alpaca.model.order.Order", + "javaName": "order", + "title": "The position-closing {@link net.jacobpeterson.alpaca.model.order.Order}." } } } diff --git a/schema_json/position/position.json b/schema_json/position/position.json index 254678d3..bee7e824 100644 --- a/schema_json/position/position.json +++ b/schema_json/position/position.json @@ -1,70 +1,70 @@ { "type": "object", - "title": "See https://alpaca.markets/docs/api-documentation/api-v2/positions/", + "title": "See Positions.", "properties": { "asset_id": { "existingJavaType": "java.lang.String", - "title": "Asset ID" + "title": "Asset ID." }, "symbol": { "existingJavaType": "java.lang.String", - "title": "Symbol name of the asset" + "title": "Symbol name of the asset." }, "exchange": { "existingJavaType": "java.lang.String", - "title": "Exchange name of the asset" + "title": "Exchange name of the asset." }, "asset_class": { "existingJavaType": "java.lang.String", - "title": "Asset class name" + "title": "Asset class name." }, "avg_entry_price": { "existingJavaType": "java.lang.String", - "title": "Average entry price of the position" + "title": "Average entry price of the position." }, "qty": { "existingJavaType": "java.lang.String", - "title": "The number of shares" + "title": "The number of shares." }, "side": { "existingJavaType": "java.lang.String", - "title": "“long”" + "title": "\"long\"." }, "market_value": { "existingJavaType": "java.lang.String", - "title": "Total dollar amount of the position" + "title": "Total dollar amount of the position." }, "cost_basis": { "existingJavaType": "java.lang.String", - "title": "Total cost basis in dollar" + "title": "Total cost basis in dollar." }, "unrealized_pl": { "existingJavaType": "java.lang.String", - "title": "Unrealized profit/loss in dollars" + "title": "Unrealized profit/loss in dollars." }, "unrealized_plpc": { "existingJavaType": "java.lang.String", - "title": "Unrealized profit/loss percent (by a factor of 1)" + "title": "Unrealized profit/loss percent (by a factor of 1)." }, "unrealized_intraday_pl": { "existingJavaType": "java.lang.String", - "title": "Unrealized profit/loss in dollars for the day" + "title": "Unrealized profit/loss in dollars for the day." }, "unrealized_intraday_plpc": { "existingJavaType": "java.lang.String", - "title": "Unrealized profit/loss percent (by a factor of 1)" + "title": "Unrealized profit/loss percent (by a factor of 1)." }, "current_price": { "existingJavaType": "java.lang.String", - "title": "Current asset price per share" + "title": "Current asset price per share." }, "lastday_price": { "existingJavaType": "java.lang.String", - "title": "Last day’s asset price per share based on the closing value of the last trading day" + "title": "Last day’s asset price per share based on the closing value of the last trading day." }, "change_today": { "existingJavaType": "java.lang.String", - "title": "Percent change from last day price (by a factor of 1)" + "title": "Percent change from last day price (by a factor of 1)." } } } diff --git a/schema_json/streaming/account/account_update.json b/schema_json/streaming/account/account_update.json index a7f81849..887b1acc 100644 --- a/schema_json/streaming/account/account_update.json +++ b/schema_json/streaming/account/account_update.json @@ -1,7 +1,7 @@ { "type": "object", "extends": { - "existingJavaType": "net.jacobpeterson.alpaca.domain.account.Account" + "existingJavaType": "net.jacobpeterson.alpaca.model.account.Account" }, - "title": "See https://docs.alpaca.markets/api-documentation/api-v2/streaming/" + "title": "See Streaming." } diff --git a/schema_json/streaming/account/account_update_message.json b/schema_json/streaming/account/account_update_message.json index 6b548955..410c5d9a 100644 --- a/schema_json/streaming/account/account_update_message.json +++ b/schema_json/streaming/account/account_update_message.json @@ -1,12 +1,13 @@ { "type": "object", "extends": { - "existingJavaType": "net.jacobpeterson.alpaca.domain.streaming.AlpacaStreamMessage" + "existingJavaType": "net.jacobpeterson.alpaca.model.streaming.AlpacaStreamMessage" }, - "title": "See https://docs.alpaca.markets/api-documentation/api-v2/streaming/", + "title": "See Streaming.", "properties": { "data": { - "existingJavaType": "net.jacobpeterson.alpaca.domain.streaming.account.AccountUpdate" + "existingJavaType": "net.jacobpeterson.alpaca.model.streaming.account.AccountUpdate", + "title": "The {@link net.jacobpeterson.alpaca.model.streaming.account.AccountUpdate}." } } } diff --git a/schema_json/streaming/alpaca_stream_message.json b/schema_json/streaming/alpaca_stream_message.json index a64cf72b..6e7f4833 100644 --- a/schema_json/streaming/alpaca_stream_message.json +++ b/schema_json/streaming/alpaca_stream_message.json @@ -3,11 +3,11 @@ "javaInterfaces": [ "net.jacobpeterson.alpaca.abstracts.websocket.message.StreamMessage" ], - "title": "See https://docs.alpaca.markets/api-documentation/api-v2/streaming/", + "title": "See Streaming.", "properties": { "stream": { - "existingJavaType": "net.jacobpeterson.alpaca.websocket.broker.message.AlpacaStreamMessageType", - "title": "The stream type" + "existingJavaType": "net.jacobpeterson.alpaca.model.streaming.enums.AlpacaStreamMessageType", + "title": "The {@link net.jacobpeterson.alpaca.model.streaming.enums.AlpacaStreamMessageType}." } } } diff --git a/schema_json/streaming/authorization/authorization_data.json b/schema_json/streaming/authorization/authorization_data.json index f91b1e08..e88df4d5 100644 --- a/schema_json/streaming/authorization/authorization_data.json +++ b/schema_json/streaming/authorization/authorization_data.json @@ -1,6 +1,6 @@ { "type": "object", - "title": "See https://docs.alpaca.markets/api-documentation/api-v2/streaming/", + "title": "See Streaming.", "properties": { "status": { "existingJavaType": "java.lang.String", diff --git a/schema_json/streaming/authorization/authorization_message.json b/schema_json/streaming/authorization/authorization_message.json index 16a72db5..c7a8c9cf 100644 --- a/schema_json/streaming/authorization/authorization_message.json +++ b/schema_json/streaming/authorization/authorization_message.json @@ -1,13 +1,13 @@ { "type": "object", - "title": "See https://docs.alpaca.markets/api-documentation/api-v2/streaming/", + "title": "See Streaming.", "extends": { "existingJavaType": "net.jacobpeterson.alpaca.domain.streaming.AlpacaStreamMessage" }, "properties": { "data": { - "existingJavaType": "net.jacobpeterson.alpaca.domain.streaming.authorization.AuthorizationData", - "title": "The data" + "existingJavaType": "net.jacobpeterson.alpaca.model.streaming.authorization.AuthorizationData", + "title": "The {@link net.jacobpeterson.alpaca.model.streaming.authorization.AuthorizationData}." } } } diff --git a/schema_json/streaming/enums/alpaca_stream_message_type.json b/schema_json/streaming/enums/alpaca_stream_message_type.json new file mode 100644 index 00000000..315f94be --- /dev/null +++ b/schema_json/streaming/enums/alpaca_stream_message_type.json @@ -0,0 +1,10 @@ +{ + "type": "string", + "title": "See Streaming.", + "enum": [ + "listening", + "authorization", + "trade_updates", + "account_updates" + ] +} diff --git a/schema_json/streaming/listening/listening_data.json b/schema_json/streaming/listening/listening_data.json index 2a8a2594..a5a3db2f 100644 --- a/schema_json/streaming/listening/listening_data.json +++ b/schema_json/streaming/listening/listening_data.json @@ -1,9 +1,10 @@ { "type": "object", - "title": "See https://docs.alpaca.markets/api-documentation/api-v2/streaming/", + "title": "See Streaming.", "properties": { "streams": { - "existingJavaType": "java.util.ArrayList" + "existingJavaType": "java.util.ArrayList", + "title": "The {@link java.util.ArrayList} of {@link net.jacobpeterson.alpaca.model.streaming.enums.AlpacaStreamMessageType}s." } } } diff --git a/schema_json/streaming/listening/listening_message.json b/schema_json/streaming/listening/listening_message.json index 796378b6..8a230446 100644 --- a/schema_json/streaming/listening/listening_message.json +++ b/schema_json/streaming/listening/listening_message.json @@ -1,13 +1,13 @@ { "type": "object", - "title": "See https://docs.alpaca.markets/api-documentation/api-v2/streaming/", + "title": "See Streaming.", "extends": { "existingJavaType": "net.jacobpeterson.alpaca.domain.streaming.AlpacaStreamMessage" }, "properties": { "data": { - "existingJavaType": "net.jacobpeterson.alpaca.domain.streaming.listening.ListeningData", - "title": "The data" + "existingJavaType": "net.jacobpeterson.alpaca.model.streaming.listening.ListeningData", + "title": "The {@link net.jacobpeterson.alpaca.model.streaming.listening.ListeningData}." } } } diff --git a/schema_json/streaming/trade/enums/trade_update_event.json b/schema_json/streaming/trade/enums/trade_update_event.json new file mode 100644 index 00000000..f66d089a --- /dev/null +++ b/schema_json/streaming/trade/enums/trade_update_event.json @@ -0,0 +1,102 @@ +{ + "type": "string", + "title": "See Orders.", + "enum": [ + "new", + "fill", + "partial_fill", + "canceled", + "expired", + "done_for_day", + "replaced", + "rejected", + "pending_new", + "stopped", + "pending_cancel", + "pending_replace", + "calculated", + "suspended", + "order_replace_rejected", + "order_cancel_rejected" + ], + "javaEnums": [ + { + "description": "Sent when an order has been routed to exchanges for execution." + }, + { + "description": "Sent when your order has been completely filled." + }, + { + "description": "The time at which the order was filled." + }, + { + "description": "The average price per share at which the order was filled." + }, + { + "description": "The size of your total position, after this fill event, in shares. Positive for long positions, negative for short positions." + }, + { + "description": "Sent when a number of shares less than the total remaining quantity on your order has been filled." + }, + { + "description": "The time at which the shares were filled." + }, + { + "description": "The average price per share at which the shares were filled." + }, + { + "description": "The size of your total position, after this fill event, in shares. Positive for long positions, negative for short positions." + }, + { + "description": "Sent when your requested cancelation of an order is processed." + }, + { + "description": "The time at which the order was canceled." + }, + { + "description": "Sent when an order has reached the end of its lifespan, as determined by the order’s time in force value." + }, + { + "description": "The time at which the order expired." + }, + { + "description": "Sent when the order is done executing for the day, and will not receive further updates until the next trading day." + }, + { + "description": "Sent when your requested replacement of an order is processed." + }, + { + "description": "The time at which the order was replaced." + }, + { + "description": "Sent when your order has been rejected." + }, + { + "description": "The time at which the rejection occurred." + }, + { + "description": "Sent when the order has been received by Alpaca and routed to the exchanges, but has not yet been accepted for execution." + }, + { + "description": "Sent when your order has been stopped, and a trade is guaranteed for the order, usually at a stated price or better, but has not yet occurred." + }, + { + "description": "Sent when the order is awaiting cancelation. Most cancelations will occur without the order entering this state." + }, + { + "description": "Sent when the order is awaiting replacement." + }, + { + "description": "Sent when the order has been completed for the day - it is either “filled” or “done_for_day” - but remaining settlement calculations are still pending." + }, + { + "description": "Sent when the order has been suspended and is not eligible for trading." + }, + { + "description": "Sent when the order replace has been rejected." + }, + { + "description": "Sent when the order cancel has been rejected." + } + ] +} diff --git a/schema_json/streaming/trade/trade_update.json b/schema_json/streaming/trade/trade_update.json index eb197257..ffa7558e 100644 --- a/schema_json/streaming/trade/trade_update.json +++ b/schema_json/streaming/trade/trade_update.json @@ -1,26 +1,26 @@ { "type": "object", - "title": "See https://docs.alpaca.markets/api-documentation/api-v2/streaming/", + "title": "See Streaming.", "properties": { "event": { - "existingJavaType": "net.jacobpeterson.alpaca.enums.streaming.TradeUpdateEvent", - "title": "The event type" + "existingJavaType": "net.jacobpeterson.alpaca.model.streaming.trade.enums.TradeUpdateEvent", + "title": "The {@link net.jacobpeterson.alpaca.model.streaming.trade.enums.TradeUpdateEvent}." }, "price": { "existingJavaType": "java.lang.String", - "title": "The Price" + "title": "The price." }, "timestamp": { "existingJavaType": "java.time.ZonedDateTime", - "title": "The timestamp" + "title": "The timestamp." }, "position_qty": { "existingJavaType": "java.lang.String", - "title": "The position quantity" + "title": "The position quantity." }, "order": { - "existingJavaType": "net.jacobpeterson.alpaca.domain.order.Order", - "title": "The Order" + "existingJavaType": "net.jacobpeterson.alpaca.model.order.Order", + "title": "The {@link net.jacobpeterson.alpaca.model.order.Order}." } } } diff --git a/schema_json/streaming/trade/trade_update_message.json b/schema_json/streaming/trade/trade_update_message.json index e99eb6c3..4ab2ec6a 100644 --- a/schema_json/streaming/trade/trade_update_message.json +++ b/schema_json/streaming/trade/trade_update_message.json @@ -3,10 +3,11 @@ "extends": { "existingJavaType": "net.jacobpeterson.alpaca.domain.streaming.AlpacaStreamMessage" }, - "title": "See https://docs.alpaca.markets/api-documentation/api-v2/streaming/", + "title": "See Streaming.", "properties": { "data": { - "existingJavaType": "net.jacobpeterson.alpaca.domain.streaming.trade.TradeUpdate" + "existingJavaType": "net.jacobpeterson.alpaca.model.streaming.trade.TradeUpdate", + "title": "The {@link net.jacobpeterson.alpaca.model.streaming.trade.TradeUpdate}." } } } diff --git a/schema_json/watchlist/watchlist.json b/schema_json/watchlist/watchlist.json index 61ea25cb..9bc2d429 100644 --- a/schema_json/watchlist/watchlist.json +++ b/schema_json/watchlist/watchlist.json @@ -1,30 +1,30 @@ { "type": "object", - "title": "See https://docs.alpaca.markets/api-documentation/api-v2/watchlist/", + "title": "See Watchlist.", "properties": { "id": { "existingJavaType": "java.lang.String", - "title": "Watchlist ID" + "title": "Watchlist ID." }, "created_at": { - "existingJavaType": "java.lang.String", - "title": "Created at" + "existingJavaType": "java.time.ZonedDateTime", + "title": "Created at." }, "updated_at": { - "existingJavaType": "java.lang.String", - "title": "Updated at" + "existingJavaType": "java.time.ZonedDateTime", + "title": "Updated at." }, "name": { "existingJavaType": "java.lang.String", - "title": "User-defined watchlist name (up to 64 characters)" + "title": "User-defined watchlist name (up to 64 characters)." }, "account_id": { "existingJavaType": "java.lang.String", - "title": "Account ID" + "title": "Account ID." }, "assets": { "existingJavaType": "java.util.ArrayList", - "title": "The content of this watchlist, in the order as registered by the client" + "title": "The content of this watchlist, in the order as registered by the client." } } } diff --git a/src/main/java/net/jacobpeterson/alpaca/AlpacaAPI.java b/src/main/java/net/jacobpeterson/alpaca/AlpacaAPI.java index 966f95ff..57ab07eb 100644 --- a/src/main/java/net/jacobpeterson/alpaca/AlpacaAPI.java +++ b/src/main/java/net/jacobpeterson/alpaca/AlpacaAPI.java @@ -38,8 +38,8 @@ import net.jacobpeterson.alpaca.domain.position.Position; import net.jacobpeterson.alpaca.domain.watchlist.Watchlist; import net.jacobpeterson.alpaca.enums.activity.ActivityType; -import net.jacobpeterson.alpaca.enums.api.DataAPIType; -import net.jacobpeterson.alpaca.enums.api.EndpointAPIType; +import net.jacobpeterson.alpaca.properties.enums.DataAPIType; +import net.jacobpeterson.alpaca.properties.enums.EndpointAPIType; import net.jacobpeterson.alpaca.enums.asset.AssetStatus; import net.jacobpeterson.alpaca.enums.marketdata.BarsTimeFrame; import net.jacobpeterson.alpaca.enums.order.OrderClass; diff --git a/src/main/java/net/jacobpeterson/alpaca/abstracts/rest/AbstractRequest.java b/src/main/java/net/jacobpeterson/alpaca/abstracts/rest/AbstractRequest.java index 12f7d88c..2b9dcd4b 100644 --- a/src/main/java/net/jacobpeterson/alpaca/abstracts/rest/AbstractRequest.java +++ b/src/main/java/net/jacobpeterson/alpaca/abstracts/rest/AbstractRequest.java @@ -1,6 +1,7 @@ package net.jacobpeterson.alpaca.abstracts.rest; import com.google.gson.JsonElement; +import com.google.gson.JsonParser; import com.google.gson.stream.JsonReader; import com.mashape.unirest.http.HttpResponse; import com.mashape.unirest.http.Unirest; @@ -274,7 +275,7 @@ public T getResponseObject(HttpResponse httpResponse, Type type */ public JsonElement getResponseJSON(HttpResponse httpResponse) { try (JsonReader jsonReader = new JsonReader(new InputStreamReader(httpResponse.getRawBody()))) { - return GsonUtil.JSON_PARSER.parse(jsonReader); + return JsonParser.parseReader(jsonReader); } catch (IOException ioException) { LOGGER.error("Could not parse response JSON object", ioException); return null; diff --git a/src/main/java/net/jacobpeterson/alpaca/enums/account/AccountStatus.java b/src/main/java/net/jacobpeterson/alpaca/enums/account/AccountStatus.java deleted file mode 100644 index ed2b8112..00000000 --- a/src/main/java/net/jacobpeterson/alpaca/enums/account/AccountStatus.java +++ /dev/null @@ -1,63 +0,0 @@ -package net.jacobpeterson.alpaca.enums.account; - -import net.jacobpeterson.alpaca.abstracts.enums.APIName; - -/** - * {@link AccountStatus} defines enums for various account statuses. - * - * @see Account Status - */ -public enum AccountStatus implements APIName { - - /** - * The account is onboarding. - */ - ONBOARDING("ONBOARDING"), - - /** - * The account application submission failed for some reason. - */ - SUBMISSION_FAILED("SUBMISSION_FAILED"), - - /** - * The account application has been submitted for review. - */ - SUBMITTED("SUBMITTED"), - - /** - * The account information is being updated. - */ - ACCOUNT_UPDATED("ACCOUNT_UPDATED"), - - /** - * The final account approval is pending. - */ - APPROVAL_PENDING("APPROVAL_PENDING"), - - /** - * The account is active for trading. - */ - ACTIVE("ACTIVE"), - - /** - * The account application has been rejected. - */ - REJECTED("REJECTED"); - - private final String apiName; - - /** - * Instantiates a new {@link AccountStatus}. - * - * @param apiName the API name - */ - AccountStatus(String apiName) { - this.apiName = apiName; - } - - @Override - public String getAPIName() { - return apiName; - } -} - diff --git a/src/main/java/net/jacobpeterson/alpaca/enums/activity/ActivityType.java b/src/main/java/net/jacobpeterson/alpaca/enums/activity/ActivityType.java deleted file mode 100644 index 97be6d6d..00000000 --- a/src/main/java/net/jacobpeterson/alpaca/enums/activity/ActivityType.java +++ /dev/null @@ -1,125 +0,0 @@ -package net.jacobpeterson.alpaca.enums.activity; - -import net.jacobpeterson.alpaca.abstracts.enums.APIName; - -/** - * {@link ActivityType} defines enums for various account activity types. - * - * @see - * https://docs.alpaca.markets/api-documentation/api-v2/account-activities/ - */ -public enum ActivityType implements APIName { - - // The names of these enums must be the API name exactly - // @see ActivityType#getAPIName() - - /** Order fills (both partial and full fills) */ - FILL, - - /** Cash transactions (both CSD and CSW) */ - TRANS, - - /** Miscellaneous or rarely used activity types (All types except those in TRANS, DIV, or FILL) */ - MISC, - - /** ACATS IN/OUT (Cash) */ - ACATC, - - /** ACATS IN/OUT (Securities) */ - ACATS, - - /** Cash deposit(+) */ - CSD, - - /** Cash receipt(-) */ - CSR, - - /** Cash withdrawal(-) */ - CSW, - - /** Dividends */ - DIV, - - /** Dividend (capital gain long term) */ - DIVCGL, - - /** Dividend (capital gain short term) */ - DIVCGS, - - /** Dividend fee */ - DIVFEE, - - /** Dividend adjusted (Foreign Tax Withheld) */ - DIVFT, - - /** Dividend adjusted (NRA Withheld) */ - DIVNRA, - - /** Dividend return of capital */ - DIVROC, - - /** Dividend adjusted (Tefra Withheld) */ - DIVTW, - - /** Dividend (tax exempt) */ - DIVTXEX, - - /** Interest (credit/margin) */ - INT, - - /** Interest adjusted (NRA Withheld) */ - INTNRA, - - /** Interest adjusted (Tefra Withheld) */ - INTTW, - - /** Journal entry */ - JNL, - - /** Journal entry (cash) */ - JNLC, - - /** Journal entry (stock) */ - JNLS, - - /** Merger/Acquisition */ - MA, - - /** Name change */ - NC, - - /** Option assignment */ - OPASN, - - /** Option expiration */ - OPEXP, - - /** Option exercise */ - OPXRC, - - /** Pass Thru Charge */ - PTC, - - /** Pass Thru Rebate */ - PTR, - - /** REG/TAF fees */ - FEE, - - /** Reorg CA */ - REORG, - - /** Symbol change */ - SC, - - /** Stock spinoff */ - SSO, - - /** Stock split */ - SSP; - - @Override - public String getAPIName() { - return this.name(); - } -} diff --git a/src/main/java/net/jacobpeterson/alpaca/enums/asset/AssetStatus.java b/src/main/java/net/jacobpeterson/alpaca/enums/asset/AssetStatus.java deleted file mode 100644 index 984fad0f..00000000 --- a/src/main/java/net/jacobpeterson/alpaca/enums/asset/AssetStatus.java +++ /dev/null @@ -1,31 +0,0 @@ -package net.jacobpeterson.alpaca.enums.asset; - -import net.jacobpeterson.alpaca.abstracts.enums.APIName; - -/** - * {@link AssetStatus} defines enums of various asset statuses. - */ -public enum AssetStatus implements APIName { - - /** The active {@link AssetStatus}. */ - ACTIVE("active"), - - /** The inactive {@link AssetStatus}. */ - INACTIVE("inactive"); - - private final String apiName; - - /** - * Instantiates a new {@link AssetStatus}. - * - * @param apiName the API name - */ - AssetStatus(String apiName) { - this.apiName = apiName; - } - - @Override - public String getAPIName() { - return apiName; - } -} diff --git a/src/main/java/net/jacobpeterson/alpaca/enums/marketdata/BarsTimeFrame.java b/src/main/java/net/jacobpeterson/alpaca/enums/marketdata/BarsTimeFrame.java deleted file mode 100644 index fade0d84..00000000 --- a/src/main/java/net/jacobpeterson/alpaca/enums/marketdata/BarsTimeFrame.java +++ /dev/null @@ -1,34 +0,0 @@ -package net.jacobpeterson.alpaca.enums.marketdata; - -import net.jacobpeterson.alpaca.abstracts.enums.APIName; - -/** - * The {@link BarsTimeFrame} enum defines time frame enums for the Bars endpoint. - */ -public enum BarsTimeFrame implements APIName { - - /** 1 Minute {@link BarsTimeFrame}. */ - MINUTE("1Min"), - - /** 1 Hour {@link BarsTimeFrame}. */ - HOUR("1Hour"), - - /** 1 Day {@link BarsTimeFrame}. */ - DAY("1Day"); - - private final String apiName; - - /** - * Instantiates a new {@link BarsTimeFrame}. - * - * @param apiName the API name - */ - BarsTimeFrame(String apiName) { - this.apiName = apiName; - } - - @Override - public String getAPIName() { - return apiName; - } -} diff --git a/src/main/java/net/jacobpeterson/alpaca/enums/order/OrderClass.java b/src/main/java/net/jacobpeterson/alpaca/enums/order/OrderClass.java deleted file mode 100644 index ce94ea2b..00000000 --- a/src/main/java/net/jacobpeterson/alpaca/enums/order/OrderClass.java +++ /dev/null @@ -1,38 +0,0 @@ -package net.jacobpeterson.alpaca.enums.order; - -import net.jacobpeterson.alpaca.abstracts.enums.APIName; - -/** - * {@link OrderClass} defines enums of various {@link net.jacobpeterson.alpaca.domain.order.Order} classes. - */ -public enum OrderClass implements APIName { - - /** Simple {@link OrderClass}. */ - SIMPLE("simple"), - - /** Bracket {@link OrderClass}. */ - BRACKET("bracket"), - - /** One-Cancels-Other {@link OrderClass}. */ - OCO("oco"), - - /** One-Triggers-Other {@link OrderClass}. */ - OTO("oto"); - - private final String apiName; - - /** - * Instantiates a new {@link OrderClass}. - * - * @param apiName the API name - */ - OrderClass(String apiName) { - this.apiName = apiName; - } - - @Override - public String getAPIName() { - return apiName; - } -} - diff --git a/src/main/java/net/jacobpeterson/alpaca/enums/order/OrderSide.java b/src/main/java/net/jacobpeterson/alpaca/enums/order/OrderSide.java deleted file mode 100644 index b976215b..00000000 --- a/src/main/java/net/jacobpeterson/alpaca/enums/order/OrderSide.java +++ /dev/null @@ -1,31 +0,0 @@ -package net.jacobpeterson.alpaca.enums.order; - -import net.jacobpeterson.alpaca.abstracts.enums.APIName; - -/** - * {@link OrderSide} defines enums for various {@link net.jacobpeterson.alpaca.domain.order.Order} sides. - */ -public enum OrderSide implements APIName { - - /** The buy {@link OrderSide}. */ - BUY("buy"), - - /** The sell {@link OrderSide}. */ - SELL("sell"); - - private final String apiName; - - /** - * Instantiates a new {@link OrderSide}. - * - * @param apiName the API name - */ - OrderSide(String apiName) { - this.apiName = apiName; - } - - @Override - public String getAPIName() { - return apiName; - } -} diff --git a/src/main/java/net/jacobpeterson/alpaca/enums/order/OrderStatus.java b/src/main/java/net/jacobpeterson/alpaca/enums/order/OrderStatus.java deleted file mode 100644 index efcca514..00000000 --- a/src/main/java/net/jacobpeterson/alpaca/enums/order/OrderStatus.java +++ /dev/null @@ -1,34 +0,0 @@ -package net.jacobpeterson.alpaca.enums.order; - -import net.jacobpeterson.alpaca.abstracts.enums.APIName; - -/** - * {@link OrderStatus} defines enums for various {@link net.jacobpeterson.alpaca.domain.order.Order} statuses. - */ -public enum OrderStatus implements APIName { - - /** Open {@link OrderStatus}. */ - OPEN("open"), - - /** Closed {@link OrderStatus}. */ - CLOSED("closed"), - - /** All {@link OrderStatus}es. */ - ALL("all"); - - private final String apiName; - - /** - * Instantiates a new {@link OrderStatus}. - * - * @param apiName the API name - */ - OrderStatus(String apiName) { - this.apiName = apiName; - } - - @Override - public String getAPIName() { - return apiName; - } -} diff --git a/src/main/java/net/jacobpeterson/alpaca/enums/order/OrderTimeInForce.java b/src/main/java/net/jacobpeterson/alpaca/enums/order/OrderTimeInForce.java deleted file mode 100644 index 3f941377..00000000 --- a/src/main/java/net/jacobpeterson/alpaca/enums/order/OrderTimeInForce.java +++ /dev/null @@ -1,72 +0,0 @@ -package net.jacobpeterson.alpaca.enums.order; - -import net.jacobpeterson.alpaca.abstracts.enums.APIName; - -/** - * {@link OrderTimeInForce} defines enums for various {@link net.jacobpeterson.alpaca.domain.order.Order} TIFs. - * - * @see Time in Force - */ -public enum OrderTimeInForce implements APIName { - - /** - * A day order is eligible for execution only on the day it is live. By default, the order is only valid during - * Regular Trading Hours (9:30am - 4:00pm ET). If unfilled after the closing auction, it is automatically canceled. - * If submitted after the close, it is queued and submitted the following trading day. However, if marked as - * eligible for extended hours, the order can also execute during supported extended hours. - */ - DAY("day"), - - /** - * The order is good until canceled. Non-marketable GTC limit orders are subject to price adjustments to offset - * corporate actions affecting the issue. We do not currently support Do Not Reduce(DNR) orders to opt out of such - * price adjustments. - */ - GTC("gtc"), - - /** - * Use this TIF with a market/limit order type to submit “market on open” (MOO) and “limit on open” (LOO) orders. - * This order is eligible to execute only in the market opening auction. Any unfilled orders after the open will be - * cancelled. OPG orders submitted after 9:28am but before 7:00pm ET will be rejected. OPG orders submitted after - * 7:00pm will be queued and routed to the following day’s opening auction. On open/on close orders are routed to - * the primary exchange. Such orders do not necessarily execute exactly at 9:30am / 4:00pm ET but execute per the - * exchange’s auction rules. - */ - OPG("opg"), - - /** - * Use this TIF with a market/limit order type to submit “market on close” (MOC) and “limit on close” (LOC) orders. - * This order is eligible to execute only in the market closing auction. Any unfilled orders after the close will be - * cancelled. CLS orders submitted after 3:50pm but before 7:00pm ET will be rejected. CLS orders submitted after - * 7:00pm will be queued and routed to the following day’s closing auction. Only available with API v2. - */ - CLS("cls"), - - /** - * An Immediate Or Cancel (IOC) order requires all or part of the order to be executed immediately. Any unfilled - * portion of the order is canceled. Only available with API v2. - */ - IOC("ioc"), - - /** - * A Fill or Kill (FOK) order is only executed if the entire order quantity can be filled, otherwise the order is - * canceled. Only available with API v2. - */ - FOK("fok"); - - private final String apiName; - - /** - * Instantiates a new {@link OrderTimeInForce}. - * - * @param apiName the API name - */ - OrderTimeInForce(String apiName) { - this.apiName = apiName; - } - - @Override - public String getAPIName() { - return apiName; - } -} diff --git a/src/main/java/net/jacobpeterson/alpaca/enums/order/OrderType.java b/src/main/java/net/jacobpeterson/alpaca/enums/order/OrderType.java deleted file mode 100644 index ea557311..00000000 --- a/src/main/java/net/jacobpeterson/alpaca/enums/order/OrderType.java +++ /dev/null @@ -1,67 +0,0 @@ -package net.jacobpeterson.alpaca.enums.order; - -import net.jacobpeterson.alpaca.abstracts.enums.APIName; - -/** - * {@link OrderType} defines enums for various {@link net.jacobpeterson.alpaca.domain.order.Order} types. - * - * @see Order Types - */ -public enum OrderType implements APIName { - - /** - * A market order is a request to buy or sell a security at the currently available market price. It provides the - * most likely method of filling an order. Market orders fill nearly instantaneously. - */ - MARKET("market"), - - /** - * A limit order is an order to buy or sell at a specified price or better. A buy limit order (a limit order to buy) - * is executed at the specified limit price or lower (i.e., better). Conversely, a sell limit order (a limit order - * to sell) is executed at the specified limit price or higher (better). Unlike a market order, you have to specify - * the limit price parameter when submitting your order. - */ - LIMIT("limit"), - - /** - * A stop (market) order is an order to buy or sell a security when its price moves past a particular point, - * ensuring a higher probability of achieving a predetermined entry or exit price. Once the market price crosses the - * specified stop price, the stop order becomes a market order. Alpaca converts buy stop orders into stop limit - * orders with a limit price that is 4% higher than a stop price < $50 (or 2.5% higher than a stop price >= - * $50). Sell stop orders are not converted into stop limit orders. - */ - STOP("stop"), - - /** - * A stop-limit order is a conditional trade over a set time frame that combines the features of a stop order with - * those of a limit order and is used to mitigate risk. The stop-limit order will be executed at a specified limit - * price, or better, after a given stop price has been reached. Once the stop price is reached, the stop-limit order - * becomes a limit order to buy or sell at the limit price or better - */ - STOP_LIMIT("stop_limit"), - - /** - * Trailing stop orders allow you to continuously and automatically keep updating the stop price threshold based on - * the stock price movement. You request a single order with a dollar offset value or percentage value as the trail - * and the actual stop price for this order changes as the stock price moves in your favorable way, or stay at the - * last level otherwise. This way, you don’t need to monitor the price movement and keep sending replace requests to - * update the stop price close to the latest market movement. - */ - TRAILING_STOP("trailing_stop"); - - private final String apiName; - - /** - * Instantiates a new {@link OrderType}. - * - * @param apiName the API name - */ - OrderType(String apiName) { - this.apiName = apiName; - } - - @Override - public String getAPIName() { - return apiName; - } -} diff --git a/src/main/java/net/jacobpeterson/alpaca/enums/portfolio/PortfolioPeriodUnit.java b/src/main/java/net/jacobpeterson/alpaca/enums/portfolio/PortfolioPeriodUnit.java deleted file mode 100644 index 7818f0bf..00000000 --- a/src/main/java/net/jacobpeterson/alpaca/enums/portfolio/PortfolioPeriodUnit.java +++ /dev/null @@ -1,39 +0,0 @@ -package net.jacobpeterson.alpaca.enums.portfolio; - -import net.jacobpeterson.alpaca.abstracts.enums.APIName; - -/** - * {@link PortfolioPeriodUnit} defines enums for various - * {@link net.jacobpeterson.alpaca.domain.portfoliohistory.PortfolioHistory} - * period units. - */ -public enum PortfolioPeriodUnit implements APIName { - - /** Day {@link PortfolioPeriodUnit}. */ - DAY("D"), - - /** Week {@link PortfolioPeriodUnit}. */ - WEEK("W"), - - /** Month {@link PortfolioPeriodUnit}. */ - MONTH("M"), - - /** Year {@link PortfolioPeriodUnit}. */ - YEAR("A"); - - private final String apiName; - - /** - * Instantiates a new {@link PortfolioPeriodUnit}. - * - * @param apiName the API name - */ - PortfolioPeriodUnit(String apiName) { - this.apiName = apiName; - } - - @Override - public String getAPIName() { - return apiName; - } -} diff --git a/src/main/java/net/jacobpeterson/alpaca/enums/portfolio/PortfolioTimeFrame.java b/src/main/java/net/jacobpeterson/alpaca/enums/portfolio/PortfolioTimeFrame.java deleted file mode 100644 index 55cf11a7..00000000 --- a/src/main/java/net/jacobpeterson/alpaca/enums/portfolio/PortfolioTimeFrame.java +++ /dev/null @@ -1,38 +0,0 @@ -package net.jacobpeterson.alpaca.enums.portfolio; - -import com.google.gson.annotations.SerializedName; -import net.jacobpeterson.alpaca.abstracts.enums.APIName; -import net.jacobpeterson.alpaca.util.gson.GsonUtil; - -/** - * {@link PortfolioTimeFrame} defines enums for various - * {@link net.jacobpeterson.alpaca.domain.portfoliohistory.PortfolioHistory} - * time frames. - */ -public enum PortfolioTimeFrame implements APIName { - - /** 1 min {@link PortfolioTimeFrame}. */ - @SerializedName("1Min") - ONE_MIN, - - /** 5 minute {@link PortfolioTimeFrame}. */ - @SerializedName("5Min") - FIVE_MINUTE, - - /** 15 minute {@link PortfolioTimeFrame}. */ - @SerializedName("15Min") - FIFTEEN_MINUTE, - - /** 1 hour {@link PortfolioTimeFrame}. */ - @SerializedName("1H") - ONE_HOUR, - - /** 1 day {@link PortfolioTimeFrame}. */ - @SerializedName("1D") - ONE_DAY; - - @Override - public String getAPIName() { - return GsonUtil.GSON.toJsonTree(this).getAsString(); - } -} diff --git a/src/main/java/net/jacobpeterson/alpaca/enums/streaming/TradeUpdateEvent.java b/src/main/java/net/jacobpeterson/alpaca/enums/streaming/TradeUpdateEvent.java deleted file mode 100644 index 2c1a0d63..00000000 --- a/src/main/java/net/jacobpeterson/alpaca/enums/streaming/TradeUpdateEvent.java +++ /dev/null @@ -1,117 +0,0 @@ -package net.jacobpeterson.alpaca.enums.streaming; - -import com.google.gson.annotations.SerializedName; -import net.jacobpeterson.alpaca.abstracts.enums.APIName; -import net.jacobpeterson.alpaca.util.gson.GsonUtil; - -/** - * {@link TradeUpdateEvent} defines enums for various trade update events. - */ -public enum TradeUpdateEvent implements APIName { - - /** - * Sent when an order has been routed to exchanges for execution. - */ - @SerializedName("new") - NEW, - - /** - * Sent when your order has been completely filled. - */ - @SerializedName("fill") - FILL, - - /** - * Sent when a number of shares less than the total remaining quantity on your order has been filled. - */ - @SerializedName("partial_fill") - PARTIAL_FILL, - - /** - * Sent when your requested cancellation of an order is processed. - */ - @SerializedName("canceled") - CANCELED, - - /** - * Sent when an order has reached the end of its lifespan, as determined by the order’s time in force value. - **/ - @SerializedName("expired") - EXPIRED, - - /** - * Sent when the order is done executing for the day, and will not receive further updates until the next trading - * day. - */ - @SerializedName("done_for_day") - DONE_FOR_DAY, - - /** - * Sent when your requested replacement of an order is processed. - */ - @SerializedName("replaced") - REPLACED, - - /** - * Sent when your order has been rejected. - */ - @SerializedName("rejected") - REJECTED, - - /** - * Sent when the order has been received by Alpaca and routed to the exchanges, but has not yet been accepted for - * execution. - */ - @SerializedName("pending_new") - PENDING_NEW, - - /** - * Sent when your order has been stopped, and a trade is guaranteed for the order, usually at a stated price or - * better, but has not yet occurred. - */ - @SerializedName("stopped") - STOPPED, - - /** - * Sent when the order is awaiting cancellation. Most cancellations will occur without the order entering this - * state. - */ - @SerializedName("pending_cancel") - PENDING_CANCEL, - - /** - * Sent when the order is awaiting replacement. - */ - @SerializedName("pending_replace") - PENDING_REPLACE, - - /** - * Sent when the order has been completed for the day - it is either “filled” or “done_for_day” - but remaining - * settlement calculations are still pending. - */ - @SerializedName("calculated") - CALCULATED, - - /** - * Sent when the order has been suspended and is not eligible for trading. - */ - @SerializedName("suspended") - SUSPENDED, - - /** - * Sent when the order replace has been rejected. - */ - @SerializedName("order_replace_rejected") - ORDER_REPLACE_REJECTED, - - /** - * Sent when the order cancel has been rejected. - */ - @SerializedName("order_cancel_rejected") - ORDER_CANCEL_REJECTED; - - @Override - public String getAPIName() { - return GsonUtil.GSON.toJsonTree(this).getAsString(); - } -} diff --git a/src/main/java/net/jacobpeterson/alpaca/enums/api/DataAPIType.java b/src/main/java/net/jacobpeterson/alpaca/properties/enums/DataAPIType.java similarity index 96% rename from src/main/java/net/jacobpeterson/alpaca/enums/api/DataAPIType.java rename to src/main/java/net/jacobpeterson/alpaca/properties/enums/DataAPIType.java index 721af52b..552b5c8d 100644 --- a/src/main/java/net/jacobpeterson/alpaca/enums/api/DataAPIType.java +++ b/src/main/java/net/jacobpeterson/alpaca/properties/enums/DataAPIType.java @@ -1,4 +1,4 @@ -package net.jacobpeterson.alpaca.enums.api; +package net.jacobpeterson.alpaca.properties.enums; /** * {@link DataAPIType} defines the types of Data APIs to use with Alpaca. diff --git a/src/main/java/net/jacobpeterson/alpaca/enums/api/EndpointAPIType.java b/src/main/java/net/jacobpeterson/alpaca/properties/enums/EndpointAPIType.java similarity index 96% rename from src/main/java/net/jacobpeterson/alpaca/enums/api/EndpointAPIType.java rename to src/main/java/net/jacobpeterson/alpaca/properties/enums/EndpointAPIType.java index d92f7753..05af1341 100644 --- a/src/main/java/net/jacobpeterson/alpaca/enums/api/EndpointAPIType.java +++ b/src/main/java/net/jacobpeterson/alpaca/properties/enums/EndpointAPIType.java @@ -1,4 +1,4 @@ -package net.jacobpeterson.alpaca.enums.api; +package net.jacobpeterson.alpaca.properties.enums; /** * {@link EndpointAPIType} defines the types of API endpoint to use with Alpaca. diff --git a/src/main/java/net/jacobpeterson/alpaca/rest/exception/AlpacaAPIRequestException.java b/src/main/java/net/jacobpeterson/alpaca/rest/exception/AlpacaAPIRequestException.java index 18703fe0..2103b235 100644 --- a/src/main/java/net/jacobpeterson/alpaca/rest/exception/AlpacaAPIRequestException.java +++ b/src/main/java/net/jacobpeterson/alpaca/rest/exception/AlpacaAPIRequestException.java @@ -2,6 +2,7 @@ import com.google.gson.JsonElement; import com.google.gson.JsonObject; +import com.google.gson.JsonParser; import com.mashape.unirest.http.HttpResponse; import net.jacobpeterson.alpaca.AlpacaAPI; import net.jacobpeterson.alpaca.abstracts.rest.exception.AbstractAPIRequestException; @@ -44,7 +45,7 @@ public AlpacaAPIRequestException(HttpResponse httpResponse) { */ @Override protected void parseAPIExceptionMessage() { - JsonElement responseJsonElement = GsonUtil.JSON_PARSER.parse(new InputStreamReader(httpResponse.getBody())); + JsonElement responseJsonElement = JsonParser.parseString(new InputStreamReader(httpResponse.getBody())); if (responseJsonElement instanceof JsonObject) { JsonObject responseJsonObject = (JsonObject) responseJsonElement; diff --git a/src/main/java/net/jacobpeterson/alpaca/util/gson/GsonUtil.java b/src/main/java/net/jacobpeterson/alpaca/util/gson/GsonUtil.java index 98923560..fdfc31ba 100644 --- a/src/main/java/net/jacobpeterson/alpaca/util/gson/GsonUtil.java +++ b/src/main/java/net/jacobpeterson/alpaca/util/gson/GsonUtil.java @@ -3,7 +3,6 @@ import com.google.gson.Gson; import com.google.gson.GsonBuilder; import com.google.gson.JsonObject; -import com.google.gson.JsonParser; import com.google.gson.annotations.SerializedName; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -30,19 +29,18 @@ public class GsonUtil { .enableComplexMapKeySerialization() .setLenient() .create(); - public static final JsonParser JSON_PARSER = new JsonParser(); /** - * Checks if all Gson {@link SerializedName} annotation values (including inherited ones) in the JSON POJO are - * present in the immediate JSON object. + * Checks if all {@link Gson} {@link SerializedName} annotation values (including inherited ones) in the JSON POJO + * are present in the immediate JSON object. * - * @param jsonPOJOClass the json pojo class - * @param jsonObject the json object + * @param jsonPOJOClass the JSON POJO class + * @param jsonObject the JSON object * - * @return the boolean + * @return a boolean */ public static boolean doesGsonPOJOMatch(Class jsonPOJOClass, JsonObject jsonObject) { - ArrayList gsonSerializedNameAnnotations = getGsonSerializedNameAnnotations(jsonPOJOClass); + List gsonSerializedNameAnnotations = getGsonSerializedNameAnnotations(jsonPOJOClass); Set jsonObjectKeys = jsonObject.keySet(); List classKeys = new ArrayList<>(); @@ -59,25 +57,25 @@ public static boolean doesGsonPOJOMatch(Class jsonPOJOClass, JsonObject jsonO } /** - * Gets gson serialized name annotations. + * Gets {@link Gson} {@link SerializedName} annotations from a given {@link Class} * - * @param theClass the the class + * @param clazz the {@link Class} * - * @return the gson serialized name annotations + * @return a {@link List} of {@link SerializedName}s */ - private static synchronized ArrayList getGsonSerializedNameAnnotations(Class theClass) { + private static synchronized List getGsonSerializedNameAnnotations(Class clazz) { // Use a caching system because Reflection can be quite expensive - if (CLASS_ANNOTATION_CACHE.containsKey(theClass)) { - return CLASS_ANNOTATION_CACHE.get(theClass); + if (CLASS_ANNOTATION_CACHE.containsKey(clazz)) { + return CLASS_ANNOTATION_CACHE.get(clazz); } - LOGGER.debug("Caching Gson @SerializedName annotations for {}", theClass.getName()); + LOGGER.debug("Caching Gson @SerializedName annotations for {}", clazz.getName()); // Below is expensive, but it only has to be done once per class. - ArrayList serializedNameAnnotations = new ArrayList<>(); + List serializedNameAnnotations = new ArrayList<>(); - Class currentClass = theClass; + Class currentClass = clazz; do { for (Field field : currentClass.getDeclaredFields()) { // Loop through all the fields for (Annotation annotation : field.getDeclaredAnnotations()) { // Loop through all the field annotations @@ -90,7 +88,7 @@ private static synchronized ArrayList getGsonSerializedNameAnnot currentClass = currentClass.getSuperclass(); } while (currentClass != null); // Loop through all inherited classes - CLASS_ANNOTATION_CACHE.put(theClass, serializedNameAnnotations); + CLASS_ANNOTATION_CACHE.put(clazz, serializedNameAnnotations); return serializedNameAnnotations; } diff --git a/src/main/java/net/jacobpeterson/alpaca/websocket/broker/client/AlpacaWebsocketClient.java b/src/main/java/net/jacobpeterson/alpaca/websocket/broker/client/AlpacaWebsocketClient.java index db41bef2..d4b053f9 100644 --- a/src/main/java/net/jacobpeterson/alpaca/websocket/broker/client/AlpacaWebsocketClient.java +++ b/src/main/java/net/jacobpeterson/alpaca/websocket/broker/client/AlpacaWebsocketClient.java @@ -4,6 +4,7 @@ import com.google.gson.JsonArray; import com.google.gson.JsonElement; import com.google.gson.JsonObject; +import com.google.gson.JsonParser; import com.google.gson.JsonPrimitive; import com.google.gson.JsonSyntaxException; import net.jacobpeterson.alpaca.AlpacaAPI; @@ -15,7 +16,7 @@ import net.jacobpeterson.alpaca.domain.streaming.authorization.AuthorizationMessage; import net.jacobpeterson.alpaca.domain.streaming.listening.ListeningMessage; import net.jacobpeterson.alpaca.domain.streaming.trade.TradeUpdateMessage; -import net.jacobpeterson.alpaca.enums.api.EndpointAPIType; +import net.jacobpeterson.alpaca.properties.enums.EndpointAPIType; import net.jacobpeterson.alpaca.util.gson.GsonUtil; import net.jacobpeterson.alpaca.websocket.broker.listener.AlpacaStreamListener; import net.jacobpeterson.alpaca.websocket.broker.message.AlpacaStreamMessageType; @@ -177,7 +178,7 @@ public void handleResubscribing() { @Override public void handleWebsocketMessage(String message) { - JsonElement messageJsonElement = GsonUtil.JSON_PARSER.parse(message); + JsonElement messageJsonElement = JsonParser.parseString(message); Preconditions.checkState(messageJsonElement instanceof JsonObject); diff --git a/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/client/MarketDataWebsocketClient.java b/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/client/MarketDataWebsocketClient.java index a730d552..8dd9f932 100644 --- a/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/client/MarketDataWebsocketClient.java +++ b/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/client/MarketDataWebsocketClient.java @@ -4,6 +4,7 @@ import com.google.gson.JsonArray; import com.google.gson.JsonElement; import com.google.gson.JsonObject; +import com.google.gson.JsonParser; import net.jacobpeterson.alpaca.AlpacaAPI; import net.jacobpeterson.alpaca.abstracts.websocket.client.WebsocketClient; import net.jacobpeterson.alpaca.abstracts.websocket.client.WebsocketStateListener; @@ -16,7 +17,7 @@ import net.jacobpeterson.alpaca.domain.marketdata.realtime.control.SuccessMessage; import net.jacobpeterson.alpaca.domain.marketdata.realtime.quote.QuoteMessage; import net.jacobpeterson.alpaca.domain.marketdata.realtime.trade.TradeMessage; -import net.jacobpeterson.alpaca.enums.api.DataAPIType; +import net.jacobpeterson.alpaca.properties.enums.DataAPIType; import net.jacobpeterson.alpaca.util.gson.GsonUtil; import net.jacobpeterson.alpaca.websocket.broker.client.AlpacaWebsocketClient; import net.jacobpeterson.alpaca.websocket.marketdata.listener.MarketDataListener; @@ -173,7 +174,7 @@ public void handleResubscribing() { @Override public void handleWebsocketMessage(String message) { - JsonElement messageJsonElement = GsonUtil.JSON_PARSER.parse(message); + JsonElement messageJsonElement = JsonParser.parseString(message); Preconditions.checkState(messageJsonElement instanceof JsonArray); JsonArray messageJsonArray = messageJsonElement.getAsJsonArray(); diff --git a/src/main/resources/alpaca.default.properties b/src/main/resources/alpaca.default.properties index 9bbef30b..02242abb 100644 --- a/src/main/resources/alpaca.default.properties +++ b/src/main/resources/alpaca.default.properties @@ -3,9 +3,9 @@ #secret = #endpoint_api_type = #data_api_type = -#user_agent = JavaRuntimeEnvironment/ Unirest/1.4.9 +#user_agent = #Defaults: endpoint_api_type=paper data_api_type=iex -user_agent=JavaRuntimeEnvironment/ Unirest/1.4.9 +user_agent=JavaRuntimeEnvironment/ OkHttp/4.9.1 diff --git a/src/test/java/mock/net/jacobpeterson/alpaca/AlpacaAPITest.java b/src/test/java/mock/net/jacobpeterson/alpaca/AlpacaAPITest.java index 55fbff90..324bd9b1 100644 --- a/src/test/java/mock/net/jacobpeterson/alpaca/AlpacaAPITest.java +++ b/src/test/java/mock/net/jacobpeterson/alpaca/AlpacaAPITest.java @@ -1,8 +1,8 @@ package mock.net.jacobpeterson.alpaca; import net.jacobpeterson.alpaca.AlpacaAPI; -import net.jacobpeterson.alpaca.enums.api.DataAPIType; -import net.jacobpeterson.alpaca.enums.api.EndpointAPIType; +import net.jacobpeterson.alpaca.properties.enums.DataAPIType; +import net.jacobpeterson.alpaca.properties.enums.EndpointAPIType; import org.junit.jupiter.api.Test; /** From a28b6d4e688415fb3a4951df84617e395bc9ea38 Mon Sep 17 00:00:00 2001 From: Jacob Peterson Date: Fri, 2 Jul 2021 16:58:33 -0600 Subject: [PATCH 120/271] More refactoring and add sort_direction.json --- schema_json/common/enums/sort_direction.json | 16 ++ .../net/jacobpeterson/alpaca/AlpacaAPI.java | 239 ++++++++++++------ .../jacobpeterson/alpaca/AlpacaConstants.java | 90 ------- .../alpaca/abstracts/enums/APIName.java | 14 - .../alpaca/abstracts/enums/SortDirection.java | 30 --- .../AbstractAPIRequestException.java | 13 +- .../alpaca/properties/AlpacaProperties.java | 28 -- .../alpaca/properties/enums/DataAPIType.java | 8 +- .../properties/enums/EndpointAPIType.java | 8 +- .../util/reflection/ReflectionUtil.java | 68 ----- .../message/AlpacaStreamMessageType.java | 54 ---- .../message/MarketDataMessageType.java | 63 ----- .../test/live}/AlpacaAPIEndpointTest.java | 2 +- .../alpaca/test/mock}/AlpacaAPITest.java | 6 +- src/test/resources/alpaca.default.properties | 2 +- 15 files changed, 187 insertions(+), 454 deletions(-) create mode 100644 schema_json/common/enums/sort_direction.json delete mode 100644 src/main/java/net/jacobpeterson/alpaca/AlpacaConstants.java delete mode 100644 src/main/java/net/jacobpeterson/alpaca/abstracts/enums/APIName.java delete mode 100644 src/main/java/net/jacobpeterson/alpaca/abstracts/enums/SortDirection.java delete mode 100644 src/main/java/net/jacobpeterson/alpaca/util/reflection/ReflectionUtil.java delete mode 100644 src/main/java/net/jacobpeterson/alpaca/websocket/broker/message/AlpacaStreamMessageType.java delete mode 100644 src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/message/MarketDataMessageType.java rename src/test/java/{live/net/jacobpeterson/alpaca => net/jacobpeterson/alpaca/test/live}/AlpacaAPIEndpointTest.java (99%) rename src/test/java/{mock/net/jacobpeterson/alpaca => net/jacobpeterson/alpaca/test/mock}/AlpacaAPITest.java (92%) diff --git a/schema_json/common/enums/sort_direction.json b/schema_json/common/enums/sort_direction.json new file mode 100644 index 00000000..01e5a4eb --- /dev/null +++ b/schema_json/common/enums/sort_direction.json @@ -0,0 +1,16 @@ +{ + "type": "string", + "title": "Defines enums for sort directions.", + "enum": [ + "asc", + "desc" + ], + "javaEnums": [ + { + "name": "ASCENDING" + }, + { + "name": "DESCENDING" + } + ] +} diff --git a/src/main/java/net/jacobpeterson/alpaca/AlpacaAPI.java b/src/main/java/net/jacobpeterson/alpaca/AlpacaAPI.java index 57ab07eb..4f0f146a 100644 --- a/src/main/java/net/jacobpeterson/alpaca/AlpacaAPI.java +++ b/src/main/java/net/jacobpeterson/alpaca/AlpacaAPI.java @@ -6,50 +6,39 @@ import com.google.gson.JsonObject; import com.google.gson.JsonPrimitive; import com.google.gson.reflect.TypeToken; -import com.mashape.unirest.http.HttpResponse; -import com.mashape.unirest.http.exceptions.UnirestException; -import net.jacobpeterson.alpaca.AlpacaConstants.Endpoints; -import net.jacobpeterson.alpaca.AlpacaConstants.Parameters; -import net.jacobpeterson.alpaca.AlpacaConstants.URLs; -import net.jacobpeterson.alpaca.abstracts.enums.SortDirection; import net.jacobpeterson.alpaca.abstracts.websocket.client.WebsocketStateListener; import net.jacobpeterson.alpaca.abstracts.websocket.exception.WebsocketException; -import net.jacobpeterson.alpaca.domain.account.Account; -import net.jacobpeterson.alpaca.domain.accountactivities.AccountActivity; -import net.jacobpeterson.alpaca.domain.accountactivities.NonTradeActivity; -import net.jacobpeterson.alpaca.domain.accountactivities.TradeActivity; -import net.jacobpeterson.alpaca.domain.accountconfiguration.AccountConfiguration; -import net.jacobpeterson.alpaca.domain.asset.Asset; -import net.jacobpeterson.alpaca.domain.calendar.Calendar; -import net.jacobpeterson.alpaca.domain.clock.Clock; -import net.jacobpeterson.alpaca.domain.marketdata.historical.bar.Bar; -import net.jacobpeterson.alpaca.domain.marketdata.historical.bar.BarsResponse; -import net.jacobpeterson.alpaca.domain.marketdata.historical.quote.LatestQuoteResponse; -import net.jacobpeterson.alpaca.domain.marketdata.historical.quote.Quote; -import net.jacobpeterson.alpaca.domain.marketdata.historical.quote.QuotesResponse; -import net.jacobpeterson.alpaca.domain.marketdata.historical.snapshot.Snapshot; -import net.jacobpeterson.alpaca.domain.marketdata.historical.trade.LatestTradeResponse; -import net.jacobpeterson.alpaca.domain.marketdata.historical.trade.Trade; -import net.jacobpeterson.alpaca.domain.marketdata.historical.trade.TradesResponse; -import net.jacobpeterson.alpaca.domain.order.CancelledOrder; -import net.jacobpeterson.alpaca.domain.order.Order; -import net.jacobpeterson.alpaca.domain.portfoliohistory.PortfolioHistory; -import net.jacobpeterson.alpaca.domain.position.ClosePositionOrder; -import net.jacobpeterson.alpaca.domain.position.Position; -import net.jacobpeterson.alpaca.domain.watchlist.Watchlist; -import net.jacobpeterson.alpaca.enums.activity.ActivityType; +import net.jacobpeterson.alpaca.model.account.Account; +import net.jacobpeterson.alpaca.model.accountactivities.AccountActivity; +import net.jacobpeterson.alpaca.model.accountactivities.NonTradeActivity; +import net.jacobpeterson.alpaca.model.accountactivities.TradeActivity; +import net.jacobpeterson.alpaca.model.accountactivities.enums.ActivityType; +import net.jacobpeterson.alpaca.model.accountconfiguration.AccountConfiguration; +import net.jacobpeterson.alpaca.model.asset.Asset; +import net.jacobpeterson.alpaca.model.asset.enums.AssetStatus; +import net.jacobpeterson.alpaca.model.marketdata.historical.bar.Bar; +import net.jacobpeterson.alpaca.model.marketdata.historical.bar.BarsResponse; +import net.jacobpeterson.alpaca.model.marketdata.historical.quote.LatestQuoteResponse; +import net.jacobpeterson.alpaca.model.marketdata.historical.quote.Quote; +import net.jacobpeterson.alpaca.model.marketdata.historical.quote.QuotesResponse; +import net.jacobpeterson.alpaca.model.marketdata.historical.trade.LatestTradeResponse; +import net.jacobpeterson.alpaca.model.marketdata.historical.trade.Trade; +import net.jacobpeterson.alpaca.model.marketdata.historical.trade.TradesResponse; +import net.jacobpeterson.alpaca.model.order.CancelledOrder; +import net.jacobpeterson.alpaca.model.order.Order; +import net.jacobpeterson.alpaca.model.order.enums.OrderClass; +import net.jacobpeterson.alpaca.model.order.enums.OrderSide; +import net.jacobpeterson.alpaca.model.order.enums.OrderStatus; +import net.jacobpeterson.alpaca.model.order.enums.OrderTimeInForce; +import net.jacobpeterson.alpaca.model.order.enums.OrderType; +import net.jacobpeterson.alpaca.model.portfoliohistory.PortfolioHistory; +import net.jacobpeterson.alpaca.model.portfoliohistory.enums.PortfolioPeriodUnit; +import net.jacobpeterson.alpaca.model.portfoliohistory.enums.PortfolioTimeFrame; +import net.jacobpeterson.alpaca.model.position.ClosePositionOrder; +import net.jacobpeterson.alpaca.model.watchlist.Watchlist; +import net.jacobpeterson.alpaca.properties.AlpacaProperties; import net.jacobpeterson.alpaca.properties.enums.DataAPIType; import net.jacobpeterson.alpaca.properties.enums.EndpointAPIType; -import net.jacobpeterson.alpaca.enums.asset.AssetStatus; -import net.jacobpeterson.alpaca.enums.marketdata.BarsTimeFrame; -import net.jacobpeterson.alpaca.enums.order.OrderClass; -import net.jacobpeterson.alpaca.enums.order.OrderSide; -import net.jacobpeterson.alpaca.enums.order.OrderStatus; -import net.jacobpeterson.alpaca.enums.order.OrderTimeInForce; -import net.jacobpeterson.alpaca.enums.order.OrderType; -import net.jacobpeterson.alpaca.enums.portfolio.PortfolioPeriodUnit; -import net.jacobpeterson.alpaca.enums.portfolio.PortfolioTimeFrame; -import net.jacobpeterson.alpaca.properties.AlpacaProperties; import net.jacobpeterson.alpaca.rest.AlpacaRequest; import net.jacobpeterson.alpaca.rest.AlpacaRequestBuilder; import net.jacobpeterson.alpaca.rest.exception.AlpacaAPIRequestException; @@ -74,7 +63,7 @@ import java.util.StringJoiner; import java.util.stream.Collectors; -import static net.jacobpeterson.alpaca.AlpacaConstants.Fields.ACTIVITY_TYPE; +import static com.google.common.base.Preconditions.checkNotNull; /** * {@link AlpacaAPI} contains several methods to interface with Alpaca. You will generally only need one instance of it @@ -87,6 +76,89 @@ */ public class AlpacaAPI { + /** + * Defines URLs constants. + */ + static class URLs { + + public static final String DATA = "https://data.alpaca.markets"; + } + + /** + * Defines endpoint constants. + */ + static class Endpoints { + + public static final String VERSION_2 = "v2"; + public static final String STOCKS = "stocks"; + public static final String CLOCK = "clock"; + public static final String CALENDAR = "calendar"; + public static final String ASSETS = "assets"; + public static final String POSITIONS = "positions"; + public static final String ACCOUNT = "account"; + public static final String ACTIVITIES = "activities"; + public static final String CONFIGURATIONS = "configurations"; + public static final String ORDERS_BY_CLIENT_ORDER_ID = "orders:by_client_order_id"; + public static final String ORDERS = "orders"; + public static final String WATCHLISTS = "watchlists"; + public static final String PORTFOLIO = "portfolio"; + public static final String HISTORY = "history"; + public static final String TRADES = "trades"; + public static final String QUOTES = "quotes"; + public static final String BARS = "bars"; + public static final String SNAPSHOT = "snapshot"; + public static final String SNAPSHOTS = "snapshots"; + public static final String LATEST = "latest"; + } + + /** + * Defines parameters constants. + */ + static class Parameters { + + public static final String SYMBOLS = "symbols"; + public static final String END = "end"; + public static final String START = "start"; + public static final String DATE = "date"; + public static final String ASSET_CLASS = "asset_class"; + public static final String CLIENT_ORDER_ID = "client_order_id"; + public static final String ORDER_CLASS = "order_class"; + public static final String TAKE_PROFIT = "take_profit"; + public static final String STOP_LOSS = "stop_loss"; + public static final String STOP_PRICE = "stop_price"; + public static final String TRAIL_PRICE = "trail_price"; + public static final String TRAIL_PERCENT = "trail_percent"; + public static final String TRAIL = "trail"; + public static final String LIMIT_PRICE = "limit_price"; + public static final String TIME_IN_FORCE = "time_in_force"; + public static final String EXTENDED_HOURS = "extended_hours"; + public static final String TYPE = "type"; + public static final String SIDE = "side"; + public static final String QTY = "qty"; + public static final String NOTIONAL = "notional"; + public static final String SYMBOL = "symbol"; + public static final String DIRECTION = "direction"; + public static final String NESTED = "nested"; + public static final String UNTIL = "until"; + public static final String AFTER = "after"; + public static final String LIMIT = "limit"; + public static final String ACTIVITY_TYPES = "activity_types"; + public static final String STATUS = "status"; + public static final String PERIOD = "period"; + public static final String TIMEFRAME = "timeframe"; + public static final String DATE_END = "date_end"; + public static final String PAGE_SIZE = "page_size"; + public static final String PAGE_TOKEN = "page_token"; + } + + /** + * Defines field constants. + */ + static class Fields { + + public static final String ACTIVITY_TYPE = "activity_type"; + } + private static final Logger LOGGER = LoggerFactory.getLogger(AlpacaAPI.class); private final String keyID; @@ -100,11 +172,10 @@ public class AlpacaAPI { * associated defaults). */ public AlpacaAPI() { - this(AlpacaProperties.KEY_ID_VALUE, AlpacaProperties.SECRET_VALUE, + this(AlpacaProperties.KEY_ID_VALUE, + AlpacaProperties.SECRET_VALUE, EndpointAPIType.fromPropertyName(AlpacaProperties.ENDPOINT_API_TYPE_VALUE), DataAPIType.fromPropertyName(AlpacaProperties.DATA_API_TYPE_VALUE)); - - LOGGER.debug(AlpacaProperties.staticToString()); } /** @@ -114,7 +185,8 @@ public AlpacaAPI() { * @param secret the secret */ public AlpacaAPI(String keyID, String secret) { - this(keyID, secret, null, EndpointAPIType.fromPropertyName(AlpacaProperties.ENDPOINT_API_TYPE_VALUE), + this(keyID, secret, null, + EndpointAPIType.fromPropertyName(AlpacaProperties.ENDPOINT_API_TYPE_VALUE), DataAPIType.fromPropertyName(AlpacaProperties.DATA_API_TYPE_VALUE)); } @@ -136,7 +208,8 @@ public AlpacaAPI(String keyID, String secret, EndpointAPIType endpointAPIType, D * @param oAuthToken the OAuth token */ public AlpacaAPI(String oAuthToken) { - this(null, null, oAuthToken, EndpointAPIType.fromPropertyName(AlpacaProperties.ENDPOINT_API_TYPE_VALUE), + this(null, null, oAuthToken, + EndpointAPIType.fromPropertyName(AlpacaProperties.ENDPOINT_API_TYPE_VALUE), DataAPIType.fromPropertyName(AlpacaProperties.DATA_API_TYPE_VALUE)); } @@ -152,9 +225,9 @@ public AlpacaAPI(String oAuthToken) { public AlpacaAPI(String keyID, String secret, String oAuthToken, EndpointAPIType endpointAPIType, DataAPIType dataAPIType) { Preconditions.checkState((keyID != null && secret != null) || oAuthToken != null, - "You must specify a KeyID and Secret or an OAuthToken!"); - Preconditions.checkNotNull(endpointAPIType); - Preconditions.checkNotNull(dataAPIType); + "You must specify a (KeyID and Secret) or an OAuthToken!"); + checkNotNull(endpointAPIType); + checkNotNull(dataAPIType); this.keyID = keyID; this.apiURL = endpointAPIType.getURL(); @@ -328,7 +401,7 @@ public AccountConfiguration getAccountConfiguration() throws AlpacaAPIRequestExc */ public AccountConfiguration setAccountConfiguration(AccountConfiguration accountConfiguration) throws AlpacaAPIRequestException { - Preconditions.checkNotNull(accountConfiguration); + checkNotNull(accountConfiguration); AlpacaRequestBuilder urlBuilder = new AlpacaRequestBuilder(apiURL, Endpoints.VERSION_2, Endpoints.ACCOUNT, @@ -431,11 +504,11 @@ public Order requestNewOrder(String symbol, Double quantity, Double notional, Or Boolean extendedHours, String clientOrderId, OrderClass orderClass, Double takeProfitLimitPrice, Double stopLossStopPrice, Double stopLossLimitPrice) throws AlpacaAPIRequestException { - Preconditions.checkNotNull(symbol); + checkNotNull(symbol); Preconditions.checkState(quantity != null ^ notional != null, "Either 'quantity' or 'notional' are required."); - Preconditions.checkNotNull(side); - Preconditions.checkNotNull(type); - Preconditions.checkNotNull(timeInForce); + checkNotNull(side); + checkNotNull(type); + checkNotNull(timeInForce); AlpacaRequestBuilder urlBuilder = new AlpacaRequestBuilder(apiURL, Endpoints.VERSION_2, Endpoints.ORDERS); @@ -936,7 +1009,7 @@ public Order requestNewTrailingStopPercentOrder(String symbol, Integer quantity, * @see Orders */ public Order getOrder(String orderID, Boolean nested) throws AlpacaAPIRequestException { - Preconditions.checkNotNull(orderID); + checkNotNull(orderID); AlpacaRequestBuilder urlBuilder = new AlpacaRequestBuilder(apiURL, Endpoints.VERSION_2, Endpoints.ORDERS, @@ -960,7 +1033,7 @@ public Order getOrder(String orderID, Boolean nested) throws AlpacaAPIRequestExc * @see Orders */ public Order getOrderByClientID(String clientOrderId) throws AlpacaAPIRequestException { - Preconditions.checkNotNull(clientOrderId); + checkNotNull(clientOrderId); AlpacaRequestBuilder urlBuilder = new AlpacaRequestBuilder(apiURL, Endpoints.VERSION_2, Endpoints.ORDERS_BY_CLIENT_ORDER_ID); @@ -988,7 +1061,7 @@ public Order getOrderByClientID(String clientOrderId) throws AlpacaAPIRequestExc */ public Order replaceOrder(String orderID, Integer quantity, OrderTimeInForce timeInForce, Double limitPrice, Double stopPrice, Double trail, String clientOrderId) throws AlpacaAPIRequestException { - Preconditions.checkNotNull(orderID); + checkNotNull(orderID); AlpacaRequestBuilder urlBuilder = new AlpacaRequestBuilder(apiURL, Endpoints.VERSION_2, Endpoints.ORDERS, @@ -1061,7 +1134,7 @@ public ArrayList cancelAllOrders() throws AlpacaAPIRequestExcept * @see Orders */ public boolean cancelOrder(String orderId) throws AlpacaAPIRequestException { - Preconditions.checkNotNull(orderId); + checkNotNull(orderId); AlpacaRequestBuilder urlBuilder = new AlpacaRequestBuilder(apiURL, Endpoints.VERSION_2, Endpoints.ORDERS, @@ -1106,7 +1179,7 @@ public ArrayList getOpenPositions() throws AlpacaAPIRequestException { * @see Positions */ public Position getOpenPositionBySymbol(String symbol) throws AlpacaAPIRequestException { - Preconditions.checkNotNull(symbol); + checkNotNull(symbol); AlpacaRequestBuilder urlBuilder = new AlpacaRequestBuilder(apiURL, Endpoints.VERSION_2, Endpoints.POSITIONS, @@ -1143,7 +1216,7 @@ public ArrayList closeAllPositions() throws AlpacaAPIRequest * @see Positions */ public Order closePosition(String symbol) throws AlpacaAPIRequestException { - Preconditions.checkNotNull(symbol); + checkNotNull(symbol); AlpacaRequestBuilder urlBuilder = new AlpacaRequestBuilder(apiURL, Endpoints.VERSION_2, Endpoints.POSITIONS, @@ -1189,7 +1262,7 @@ public ArrayList getAssets(AssetStatus assetStatus, String assetClass) th * @see Assets */ public Asset getAssetBySymbol(String symbolOrAssetID) throws AlpacaAPIRequestException { - Preconditions.checkNotNull(symbolOrAssetID); + checkNotNull(symbolOrAssetID); AlpacaRequestBuilder urlBuilder = new AlpacaRequestBuilder(apiURL, Endpoints.VERSION_2, Endpoints.ASSETS, @@ -1225,7 +1298,7 @@ public ArrayList getWatchlists() throws AlpacaAPIRequestException { * @see Watchlists */ public Watchlist createWatchlist(String name, String... symbols) throws AlpacaAPIRequestException { - Preconditions.checkNotNull(name); + checkNotNull(name); Preconditions.checkState(name.length() <= 64); AlpacaRequestBuilder urlBuilder = new AlpacaRequestBuilder(apiURL, Endpoints.VERSION_2, @@ -1254,7 +1327,7 @@ public Watchlist createWatchlist(String name, String... symbols) throws AlpacaAP * @see Watchlists */ public Watchlist getWatchlist(String watchlistID) throws AlpacaAPIRequestException { - Preconditions.checkNotNull(watchlistID); + checkNotNull(watchlistID); AlpacaRequestBuilder urlBuilder = new AlpacaRequestBuilder(apiURL, Endpoints.VERSION_2, Endpoints.WATCHLISTS, @@ -1277,7 +1350,7 @@ public Watchlist getWatchlist(String watchlistID) throws AlpacaAPIRequestExcepti */ public Watchlist updateWatchlist(String watchlistID, String name, String... symbols) throws AlpacaAPIRequestException { - Preconditions.checkNotNull(watchlistID); + checkNotNull(watchlistID); AlpacaRequestBuilder urlBuilder = new AlpacaRequestBuilder(apiURL, Endpoints.VERSION_2, Endpoints.WATCHLISTS, @@ -1309,8 +1382,8 @@ public Watchlist updateWatchlist(String watchlistID, String name, String... symb * @see Watchlists */ public Watchlist addWatchlistAsset(String watchlistID, String symbol) throws AlpacaAPIRequestException { - Preconditions.checkNotNull(watchlistID); - Preconditions.checkNotNull(symbol); + checkNotNull(watchlistID); + checkNotNull(symbol); AlpacaRequestBuilder urlBuilder = new AlpacaRequestBuilder(apiURL, Endpoints.VERSION_2, Endpoints.WATCHLISTS, @@ -1332,7 +1405,7 @@ public Watchlist addWatchlistAsset(String watchlistID, String symbol) throws Alp * @see Watchlists */ public boolean deleteWatchlist(String watchlistID) throws AlpacaAPIRequestException { - Preconditions.checkNotNull(watchlistID); + checkNotNull(watchlistID); AlpacaRequestBuilder urlBuilder = new AlpacaRequestBuilder(apiURL, Endpoints.VERSION_2, Endpoints.WATCHLISTS, @@ -1363,8 +1436,8 @@ public boolean deleteWatchlist(String watchlistID) throws AlpacaAPIRequestExcept * @see Watchlists */ public Watchlist removeSymbolFromWatchlist(String watchlistID, String symbol) throws AlpacaAPIRequestException { - Preconditions.checkNotNull(watchlistID); - Preconditions.checkNotNull(symbol); + checkNotNull(watchlistID); + checkNotNull(symbol); AlpacaRequestBuilder urlBuilder = new AlpacaRequestBuilder(apiURL, Endpoints.VERSION_2, Endpoints.WATCHLISTS, @@ -1489,9 +1562,9 @@ public Clock getClock() throws AlpacaAPIRequestException { */ public TradesResponse getTrades(String symbol, ZonedDateTime start, ZonedDateTime end, Integer limit, String pageToken) throws AlpacaAPIRequestException { - Preconditions.checkNotNull(symbol); - Preconditions.checkNotNull(start); - Preconditions.checkNotNull(end); + checkNotNull(symbol); + checkNotNull(start); + checkNotNull(end); AlpacaRequestBuilder urlBuilder = new AlpacaRequestBuilder(URLs.DATA, Endpoints.VERSION_2, Endpoints.STOCKS, @@ -1524,7 +1597,7 @@ public TradesResponse getTrades(String symbol, ZonedDateTime start, ZonedDateTim * Historical Market Data */ public LatestTradeResponse getLatestTrade(String symbol) throws AlpacaAPIRequestException { - Preconditions.checkNotNull(symbol); + checkNotNull(symbol); AlpacaRequestBuilder urlBuilder = new AlpacaRequestBuilder(URLs.DATA, Endpoints.VERSION_2, Endpoints.STOCKS, @@ -1554,9 +1627,9 @@ public LatestTradeResponse getLatestTrade(String symbol) throws AlpacaAPIRequest */ public QuotesResponse getQuotes(String symbol, ZonedDateTime start, ZonedDateTime end, Integer limit, String pageToken) throws AlpacaAPIRequestException { - Preconditions.checkNotNull(symbol); - Preconditions.checkNotNull(start); - Preconditions.checkNotNull(end); + checkNotNull(symbol); + checkNotNull(start); + checkNotNull(end); AlpacaRequestBuilder urlBuilder = new AlpacaRequestBuilder(URLs.DATA, Endpoints.VERSION_2, Endpoints.STOCKS, @@ -1589,7 +1662,7 @@ public QuotesResponse getQuotes(String symbol, ZonedDateTime start, ZonedDateTim * Historical Market Data */ public LatestQuoteResponse getLatestQuote(String symbol) throws AlpacaAPIRequestException { - Preconditions.checkNotNull(symbol); + checkNotNull(symbol); AlpacaRequestBuilder urlBuilder = new AlpacaRequestBuilder(URLs.DATA, Endpoints.VERSION_2, Endpoints.STOCKS, @@ -1620,10 +1693,10 @@ public LatestQuoteResponse getLatestQuote(String symbol) throws AlpacaAPIRequest */ public BarsResponse getBars(String symbol, ZonedDateTime start, ZonedDateTime end, Integer limit, String pageToken, BarsTimeFrame timeFrame) throws AlpacaAPIRequestException { - Preconditions.checkNotNull(symbol); - Preconditions.checkNotNull(start); - Preconditions.checkNotNull(end); - Preconditions.checkNotNull(timeFrame); + checkNotNull(symbol); + checkNotNull(start); + checkNotNull(end); + checkNotNull(timeFrame); AlpacaRequestBuilder urlBuilder = new AlpacaRequestBuilder(URLs.DATA, Endpoints.VERSION_2, Endpoints.STOCKS, @@ -1658,7 +1731,7 @@ public BarsResponse getBars(String symbol, ZonedDateTime start, ZonedDateTime en * Historical Market Data */ public Map getSnapshots(List symbols) throws AlpacaAPIRequestException { - Preconditions.checkNotNull(symbols); + checkNotNull(symbols); Preconditions.checkState(!symbols.isEmpty()); AlpacaRequestBuilder urlBuilder = new AlpacaRequestBuilder(URLs.DATA, Endpoints.VERSION_2, @@ -1712,7 +1785,7 @@ public Map getSnapshots(List symbols) throws AlpacaAPI * Historical Market Data */ public Snapshot getSnapshot(String symbol) throws AlpacaAPIRequestException { - Preconditions.checkNotNull(symbol); + checkNotNull(symbol); AlpacaRequestBuilder urlBuilder = new AlpacaRequestBuilder(URLs.DATA, Endpoints.VERSION_2, Endpoints.STOCKS, diff --git a/src/main/java/net/jacobpeterson/alpaca/AlpacaConstants.java b/src/main/java/net/jacobpeterson/alpaca/AlpacaConstants.java deleted file mode 100644 index 4c8642d9..00000000 --- a/src/main/java/net/jacobpeterson/alpaca/AlpacaConstants.java +++ /dev/null @@ -1,90 +0,0 @@ -package net.jacobpeterson.alpaca; - -/** - * {@link AlpacaConstants} are constants use by {@link AlpacaAPI}. - */ -public class AlpacaConstants { - - /** - * Defines URLs for {@link AlpacaAPI} - */ - public static class URLs { - - public static final String DATA = "https://data.alpaca.markets"; - } - - /** - * Defines endpoints for {@link AlpacaAPI}. - */ - public static class Endpoints { - - public static final String VERSION_2 = "v2"; - public static final String STOCKS = "stocks"; - public static final String CLOCK = "clock"; - public static final String CALENDAR = "calendar"; - public static final String ASSETS = "assets"; - public static final String POSITIONS = "positions"; - public static final String ACCOUNT = "account"; - public static final String ACTIVITIES = "activities"; - public static final String CONFIGURATIONS = "configurations"; - public static final String ORDERS_BY_CLIENT_ORDER_ID = "orders:by_client_order_id"; - public static final String ORDERS = "orders"; - public static final String WATCHLISTS = "watchlists"; - public static final String PORTFOLIO = "portfolio"; - public static final String HISTORY = "history"; - public static final String TRADES = "trades"; - public static final String QUOTES = "quotes"; - public static final String BARS = "bars"; - public static final String SNAPSHOT = "snapshot"; - public static final String SNAPSHOTS = "snapshots"; - public static final String LATEST = "latest"; - } - - /** - * Defines parameters for {@link AlpacaAPI}. - */ - public static class Parameters { - - public static final String SYMBOLS = "symbols"; - public static final String END = "end"; - public static final String START = "start"; - public static final String DATE = "date"; - public static final String ASSET_CLASS = "asset_class"; - public static final String CLIENT_ORDER_ID = "client_order_id"; - public static final String ORDER_CLASS = "order_class"; - public static final String TAKE_PROFIT = "take_profit"; - public static final String STOP_LOSS = "stop_loss"; - public static final String STOP_PRICE = "stop_price"; - public static final String TRAIL_PRICE = "trail_price"; - public static final String TRAIL_PERCENT = "trail_percent"; - public static final String TRAIL = "trail"; - public static final String LIMIT_PRICE = "limit_price"; - public static final String TIME_IN_FORCE = "time_in_force"; - public static final String EXTENDED_HOURS = "extended_hours"; - public static final String TYPE = "type"; - public static final String SIDE = "side"; - public static final String QTY = "qty"; - public static final String NOTIONAL = "notional"; - public static final String SYMBOL = "symbol"; - public static final String DIRECTION = "direction"; - public static final String NESTED = "nested"; - public static final String UNTIL = "until"; - public static final String AFTER = "after"; - public static final String LIMIT = "limit"; - public static final String ACTIVITY_TYPES = "activity_types"; - public static final String STATUS = "status"; - public static final String PERIOD = "period"; - public static final String TIMEFRAME = "timeframe"; - public static final String DATE_END = "date_end"; - public static final String PAGE_SIZE = "page_size"; - public static final String PAGE_TOKEN = "page_token"; - } - - /** - * Defines miscellaneous fields for {@link AlpacaAPI}. - */ - public static class Fields { - - public static final String ACTIVITY_TYPE = "activity_type"; - } -} diff --git a/src/main/java/net/jacobpeterson/alpaca/abstracts/enums/APIName.java b/src/main/java/net/jacobpeterson/alpaca/abstracts/enums/APIName.java deleted file mode 100644 index 1f8ff1e1..00000000 --- a/src/main/java/net/jacobpeterson/alpaca/abstracts/enums/APIName.java +++ /dev/null @@ -1,14 +0,0 @@ -package net.jacobpeterson.alpaca.abstracts.enums; - -/** - * The {@link APIName} interface is for enums classes to define enum members with API names. - */ -public interface APIName { - - /** - * Gets the API name. - * - * @return the API name - */ - String getAPIName(); -} diff --git a/src/main/java/net/jacobpeterson/alpaca/abstracts/enums/SortDirection.java b/src/main/java/net/jacobpeterson/alpaca/abstracts/enums/SortDirection.java deleted file mode 100644 index dde6620f..00000000 --- a/src/main/java/net/jacobpeterson/alpaca/abstracts/enums/SortDirection.java +++ /dev/null @@ -1,30 +0,0 @@ -package net.jacobpeterson.alpaca.abstracts.enums; - -/** - * {@link SortDirection} defines enums for directional sorting. - */ -public enum SortDirection implements APIName { - - /** The Ascending {@link SortDirection}. */ - ASCENDING("asc"), - - /** The Descending {@link SortDirection}. */ - DESCENDING("desc"); - - /** The API name. */ - String apiName; - - /** - * Instantiates a new {@link SortDirection}. - * - * @param apiName the api name - */ - SortDirection(String apiName) { - this.apiName = apiName; - } - - @Override - public String getAPIName() { - return apiName; - } -} diff --git a/src/main/java/net/jacobpeterson/alpaca/abstracts/rest/exception/AbstractAPIRequestException.java b/src/main/java/net/jacobpeterson/alpaca/abstracts/rest/exception/AbstractAPIRequestException.java index 5fbae9ac..73580dfe 100644 --- a/src/main/java/net/jacobpeterson/alpaca/abstracts/rest/exception/AbstractAPIRequestException.java +++ b/src/main/java/net/jacobpeterson/alpaca/abstracts/rest/exception/AbstractAPIRequestException.java @@ -19,15 +19,6 @@ public abstract class AbstractAPIRequestException extends Exception { protected Integer apiResponseCode; protected String apiResponseMessage; - /** - * Instantiates a new {@link AbstractAPIRequestException}. - * - * @param cause the cause - */ - public AbstractAPIRequestException(Exception cause) { - super(cause); - } - /** * Instantiates a new {@link AbstractAPIRequestException}. * @@ -53,7 +44,9 @@ public AbstractAPIRequestException(String apiName, HttpResponse htt protected abstract void parseAPIExceptionMessage(); /** - * {@inheritDoc} This will call {@link #parseAPIExceptionMessage()} and return a formatted message. + * {@inheritDoc} + *
+ * This will call {@link #parseAPIExceptionMessage()} and return a formatted message. */ @Override public String getMessage() { diff --git a/src/main/java/net/jacobpeterson/alpaca/properties/AlpacaProperties.java b/src/main/java/net/jacobpeterson/alpaca/properties/AlpacaProperties.java index 3ae083d0..71025ccf 100644 --- a/src/main/java/net/jacobpeterson/alpaca/properties/AlpacaProperties.java +++ b/src/main/java/net/jacobpeterson/alpaca/properties/AlpacaProperties.java @@ -1,11 +1,6 @@ package net.jacobpeterson.alpaca.properties; import net.jacobpeterson.alpaca.AlpacaAPI; -import net.jacobpeterson.alpaca.util.reflection.ReflectionUtil; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.util.stream.Collectors; import static net.jacobpeterson.alpaca.util.properties.PropertyUtil.getProperty; @@ -14,8 +9,6 @@ */ public class AlpacaProperties { - private static final Logger LOGGER = LoggerFactory.getLogger(AlpacaProperties.class); - private static final String ALPACA_PROPERTIES_FILE = "alpaca.properties"; private static final String ALPACA_DEFAULT_PROPERTIES_FILE = "alpaca.default.properties"; @@ -43,25 +36,4 @@ public class AlpacaProperties { /** The value of {@link #USER_AGENT_KEY} in {@link #ALPACA_PROPERTIES_FILE}. */ public static final String USER_AGENT_VALUE = getProperty(ALPACA_PROPERTIES_FILE, ALPACA_DEFAULT_PROPERTIES_FILE, USER_AGENT_KEY); - - /** - * Static {@link #toString()}. - * - * @return the static {@link #toString()} - */ - public static String staticToString() { - return AlpacaProperties.class.getName() + "[" + - ReflectionUtil.getAllDeclaredFields(AlpacaProperties.class).stream() - .filter(field -> !field.getName().contains("LOGGER")) - .map(field -> { - try { - return field.getName() + " = " + field.get(null); - } catch (IllegalAccessException exception) { - LOGGER.error("Could not get field!", exception); - } - - return field.getName() + " = ?"; - }).collect(Collectors.joining(", ")) - + "]"; - } } diff --git a/src/main/java/net/jacobpeterson/alpaca/properties/enums/DataAPIType.java b/src/main/java/net/jacobpeterson/alpaca/properties/enums/DataAPIType.java index 552b5c8d..1c0801c8 100644 --- a/src/main/java/net/jacobpeterson/alpaca/properties/enums/DataAPIType.java +++ b/src/main/java/net/jacobpeterson/alpaca/properties/enums/DataAPIType.java @@ -43,18 +43,18 @@ public static DataAPIType fromPropertyName(String propertyName) { } /** - * Gets URL. + * Gets {@link #url}. * - * @return the URL + * @return a {@link String} */ public String getURL() { return url; } /** - * Gets property name. + * Gets {@link #propertyName}. * - * @return the property name + * @return a {@link String} */ public String getPropertyName() { return propertyName; diff --git a/src/main/java/net/jacobpeterson/alpaca/properties/enums/EndpointAPIType.java b/src/main/java/net/jacobpeterson/alpaca/properties/enums/EndpointAPIType.java index 05af1341..911661fa 100644 --- a/src/main/java/net/jacobpeterson/alpaca/properties/enums/EndpointAPIType.java +++ b/src/main/java/net/jacobpeterson/alpaca/properties/enums/EndpointAPIType.java @@ -43,18 +43,18 @@ public static EndpointAPIType fromPropertyName(String propertyName) { } /** - * Gets URL. + * Gets {@link #url}. * - * @return the URL + * @return a {@link String} */ public String getURL() { return url; } /** - * Gets property name. + * Gets {@link #propertyName}. * - * @return the property name + * @return a {@link String} */ public String getPropertyName() { return propertyName; diff --git a/src/main/java/net/jacobpeterson/alpaca/util/reflection/ReflectionUtil.java b/src/main/java/net/jacobpeterson/alpaca/util/reflection/ReflectionUtil.java deleted file mode 100644 index c2138871..00000000 --- a/src/main/java/net/jacobpeterson/alpaca/util/reflection/ReflectionUtil.java +++ /dev/null @@ -1,68 +0,0 @@ -package net.jacobpeterson.alpaca.util.reflection; - -import java.lang.reflect.Field; -import java.util.ArrayList; -import java.util.HashMap; - -/** - * {@link ReflectionUtil} is a utility class for Java Reflection. - */ -public class ReflectionUtil { - - private static final HashMap, ArrayList> ALL_DECLARED_FIELDS_CACHE = new HashMap<>(); - - /** - * Gets all declared {@link Field}s in the inheritance tree. This method is very inefficient so caching of results - * has been implemented. - * - * @param clazz the {@link Class} - * - * @return all the declared {@link Field}s - */ - public static ArrayList getAllDeclaredFields(Class clazz) { - // Check cache - ArrayList cachedFields = ALL_DECLARED_FIELDS_CACHE.get(clazz); - if (cachedFields != null) { - return cachedFields; - } - - ArrayList declaredFields = new ArrayList<>(); - - // Loop while currentClass has a superclass and add declared fields - Class currentClass = clazz; - do { - for (Field currentDeclaredField : currentClass.getDeclaredFields()) { - // Check if the field name already exists in the list - if (declaredFields.stream() - .map(Field::getName) - .noneMatch(fieldName -> fieldName.equals(currentDeclaredField.getName()))) { - declaredFields.add(currentDeclaredField); - } - } - - currentClass = currentClass.getSuperclass(); - } while (currentClass != null); - - ALL_DECLARED_FIELDS_CACHE.put(clazz, declaredFields); - - return declaredFields; - } - - /** - * Gets a {@link Field}s in the inheritance tree. - * - * @param clazz the {@link Class} - * @param fieldName the {@link Field} name - * - * @return the declared {@link Field} (null if not found) - */ - public static Field getDeclaredField(Class clazz, String fieldName) { - for (Field field : getAllDeclaredFields(clazz)) { - if (field.getName().equals(fieldName)) { - return field; - } - } - - return null; - } -} diff --git a/src/main/java/net/jacobpeterson/alpaca/websocket/broker/message/AlpacaStreamMessageType.java b/src/main/java/net/jacobpeterson/alpaca/websocket/broker/message/AlpacaStreamMessageType.java deleted file mode 100644 index 4922b598..00000000 --- a/src/main/java/net/jacobpeterson/alpaca/websocket/broker/message/AlpacaStreamMessageType.java +++ /dev/null @@ -1,54 +0,0 @@ -package net.jacobpeterson.alpaca.websocket.broker.message; - -import com.google.gson.annotations.SerializedName; -import net.jacobpeterson.alpaca.abstracts.enums.APIName; -import net.jacobpeterson.alpaca.abstracts.websocket.message.StreamMessageType; -import net.jacobpeterson.alpaca.util.gson.GsonUtil; - -/** - * {@link AlpacaStreamMessageType} defines enums for various message types for {@link - * net.jacobpeterson.alpaca.websocket.broker.listener.AlpacaStreamListener}. - */ -public enum AlpacaStreamMessageType implements StreamMessageType, APIName { - - /** The listening status {@link AlpacaStreamMessageType}. */ - @SerializedName("listening") - LISTENING(false), - - /** The authorization status {@link AlpacaStreamMessageType}. */ - @SerializedName("authorization") - AUTHORIZATION(false), - - /** The trade updates {@link AlpacaStreamMessageType}. */ - @SerializedName("trade_updates") - TRADE_UPDATES(true), - - /** The account updates {@link AlpacaStreamMessageType}. */ - @SerializedName("account_updates") - ACCOUNT_UPDATES(true); - - private final boolean isAPISubscribable; - - /** - * Instantiates a new {@link AlpacaStreamMessageType}. - * - * @param isAPISubscribable true if API is subscribable - */ - AlpacaStreamMessageType(boolean isAPISubscribable) { - this.isAPISubscribable = isAPISubscribable; - } - - @Override - public String getAPIName() { - return GsonUtil.GSON.toJsonTree(this).getAsString(); - } - - /** - * Returns true if API is subscribable. - * - * @return true if API is subscribable - */ - public boolean isAPISubscribable() { - return isAPISubscribable; - } -} diff --git a/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/message/MarketDataMessageType.java b/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/message/MarketDataMessageType.java deleted file mode 100644 index 8835670d..00000000 --- a/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/message/MarketDataMessageType.java +++ /dev/null @@ -1,63 +0,0 @@ -package net.jacobpeterson.alpaca.websocket.marketdata.message; - -import com.google.gson.annotations.SerializedName; -import net.jacobpeterson.alpaca.abstracts.enums.APIName; -import net.jacobpeterson.alpaca.abstracts.websocket.message.StreamMessageType; - -/** - * {@link MarketDataMessageType} defines enums for various market data stream message types. - */ -public enum MarketDataMessageType implements StreamMessageType, APIName { - - /** The success {@link MarketDataMessageType}. */ - @SerializedName("success") - SUCCESS("success", false), - - /** The error {@link MarketDataMessageType}. */ - @SerializedName("error") - ERROR("error", false), - - /** The subscription {@link MarketDataMessageType}. */ - @SerializedName("subscription") - SUBSCRIPTION("subscription", false), - - /** The trade {@link MarketDataMessageType}. */ - @SerializedName("t") - TRADE("t", true), - - /** The quote {@link MarketDataMessageType}. */ - @SerializedName("q") - QUOTE("q", true), - - /** The bar {@link MarketDataMessageType}. */ - @SerializedName("b") - BAR("b", true); - - private final String apiName; - private final boolean isAPISubscribable; - - /** - * Instantiates a new {@link MarketDataMessageType}. - * - * @param apiName the API name - * @param isAPISubscribable true if API is subscribable - */ - MarketDataMessageType(String apiName, boolean isAPISubscribable) { - this.apiName = apiName; - this.isAPISubscribable = isAPISubscribable; - } - - @Override - public String getAPIName() { - return apiName; - } - - /** - * Returns true if API is subscribable. - * - * @return true if API is subscribable - */ - public boolean isAPISubscribable() { - return isAPISubscribable; - } -} diff --git a/src/test/java/live/net/jacobpeterson/alpaca/AlpacaAPIEndpointTest.java b/src/test/java/net/jacobpeterson/alpaca/test/live/AlpacaAPIEndpointTest.java similarity index 99% rename from src/test/java/live/net/jacobpeterson/alpaca/AlpacaAPIEndpointTest.java rename to src/test/java/net/jacobpeterson/alpaca/test/live/AlpacaAPIEndpointTest.java index 11975c9c..2a263d4b 100644 --- a/src/test/java/live/net/jacobpeterson/alpaca/AlpacaAPIEndpointTest.java +++ b/src/test/java/net/jacobpeterson/alpaca/test/live/AlpacaAPIEndpointTest.java @@ -1,4 +1,4 @@ -package live.net.jacobpeterson.alpaca; +package net.jacobpeterson.alpaca.test.live; import net.jacobpeterson.alpaca.AlpacaAPI; import net.jacobpeterson.alpaca.abstracts.enums.SortDirection; diff --git a/src/test/java/mock/net/jacobpeterson/alpaca/AlpacaAPITest.java b/src/test/java/net/jacobpeterson/alpaca/test/mock/AlpacaAPITest.java similarity index 92% rename from src/test/java/mock/net/jacobpeterson/alpaca/AlpacaAPITest.java rename to src/test/java/net/jacobpeterson/alpaca/test/mock/AlpacaAPITest.java index 324bd9b1..61ebebb2 100644 --- a/src/test/java/mock/net/jacobpeterson/alpaca/AlpacaAPITest.java +++ b/src/test/java/net/jacobpeterson/alpaca/test/mock/AlpacaAPITest.java @@ -1,4 +1,4 @@ -package mock.net.jacobpeterson.alpaca; +package net.jacobpeterson.alpaca.test.mock; import net.jacobpeterson.alpaca.AlpacaAPI; import net.jacobpeterson.alpaca.properties.enums.DataAPIType; @@ -11,9 +11,7 @@ public class AlpacaAPITest { static { - // Log trace-level - System.setProperty("org.slf4j.simpleLogger.log.net.jacobpeterson", "trace"); - System.setProperty("org.slf4j.simpleLogger.log.mock.net.jacobpeterson", "trace"); + System.setProperty("org.slf4j.simpleLogger.log.net.jacobpeterson.alpaca", "trace"); } /** diff --git a/src/test/resources/alpaca.default.properties b/src/test/resources/alpaca.default.properties index 08092cab..88ad6abe 100644 --- a/src/test/resources/alpaca.default.properties +++ b/src/test/resources/alpaca.default.properties @@ -2,4 +2,4 @@ key_id=ABCDEFGHIJKLM secret=NOPQURSTUVWXYZ endpoint_api_type=paper data_api_type=iex -user_agent=JavaRuntimeEnvironment/ Unirest/1.4.9 +user_agent=JavaRuntimeEnvironment/ OkHttp/4.9.1 From 34172998780f1d48d2450f48b6f26cb9d5a577c9 Mon Sep 17 00:00:00 2001 From: Jacob Peterson Date: Sat, 10 Jul 2021 01:19:49 -0600 Subject: [PATCH 121/271] Refactor, create AlpacaClient, create AbstractEndpoint implementations --- .editorconfig | 4 +- .../account_configuration.json | 22 - .../{ => endpoint}/account/account.json | 4 +- .../account/enums/account_status.json | 0 .../account_activities/account_activity.json | 4 +- .../enums/activity_type.json | 0 .../enums/trade_activity_side.json | 0 .../enums/trade_activity_type.json | 0 .../non_trade_activity.json | 2 +- .../account_activities/trade_activity.json | 8 +- .../account_configuration.json | 22 + .../enums/dtbp_check.json | 0 .../enums/trade_confirm_email.json | 0 schema_json/{ => endpoint}/asset/asset.json | 4 +- .../asset/enums/asset_status.json | 0 .../{ => endpoint}/calendar/calendar.json | 0 schema_json/{ => endpoint}/clock/clock.json | 0 .../common/enums/sort_direction.json | 0 .../market_data/historical/bar/bar.json | 0 .../historical/bar/bars_response.json | 4 +- .../quote/latest_quote_response.json | 4 +- .../market_data/historical/quote/quote.json | 0 .../historical/quote/quotes_response.json | 4 +- .../historical/snapshot/snapshot.json | 26 + .../trade/latest_trade_response.json | 4 +- .../market_data/historical/trade/trade.json | 0 .../historical/trade/trades_response.json | 4 +- .../market_data/realtime/bar/bar_message.json | 0 .../realtime/control/error_message.json | 0 .../control/subscriptions_message.json | 0 .../realtime/control/success_message.json | 0 .../enums/market_data_message_type.json | 0 .../realtime/market_data_message.json | 4 +- .../realtime/quote/quote_message.json | 0 .../market_data/realtime/symbol_message.json | 0 .../realtime/trade/trade_message.json | 0 .../{ => endpoint}/order/cancelled_order.json | 4 +- .../order/enums/current_order_status.json | 9 + .../order/enums/order_class.json | 0 .../order/enums/order_side.json | 0 .../order/enums/order_status.json | 0 .../order/enums/order_time_in_force.json | 0 .../order/enums/order_type.json | 0 schema_json/{ => endpoint}/order/order.json | 24 +- .../enums/portfolio_period_unit.json | 0 .../enums/portfolio_time_frame.json | 10 +- .../portfolio_history/portfolio_history.json | 18 + .../portfolio_history_data_point.json | 22 + .../portfolio_history_response.json} | 2 +- .../position/close_position_order.json | 4 +- .../{ => endpoint}/position/position.json | 0 .../streaming/account/account_update.json | 2 +- .../account/account_update_message.json | 13 + .../streaming/alpaca_stream_message.json | 4 +- .../authorization/authorization_data.json | 0 .../authorization/authorization_message.json | 4 +- .../enums/alpaca_stream_message_type.json | 0 .../streaming/listening/listening_data.json | 4 +- .../listening/listening_message.json | 4 +- .../trade/enums/trade_update_event.json | 0 .../streaming/trade/trade_update.json | 8 +- .../streaming/trade/trade_update_message.json | 4 +- .../{ => endpoint}/watchlist/watchlist.json | 0 .../historical/snapshot/snapshot.json | 26 - schema_json/properties/data_api_type.json | 9 + schema_json/properties/endpoint_api_type.json | 9 + .../account/account_update_message.json | 13 - .../net/jacobpeterson/alpaca/AlpacaAPI.java | 1871 ----------------- .../abstracts/rest/AbstractRequest.java | 284 --- .../rest/AbstractRequestBuilder.java | 129 -- .../AbstractAPIRequestException.java | 109 - .../alpaca/properties/AlpacaProperties.java | 39 - .../alpaca/properties/enums/DataAPIType.java | 62 - .../properties/enums/EndpointAPIType.java | 62 - .../alpaca/refactor/AlpacaAPI.java | 292 +++ .../refactor/properties/AlpacaProperties.java | 46 + .../alpaca/refactor/rest/AlpacaClient.java | 224 ++ .../refactor/rest/AlpacaClientException.java | 165 ++ .../rest/endpoint/AbstractEndpoint.java | 23 + .../endpoint/AccountActivitiesEndpoint.java | 147 ++ .../AccountConfigurationEndpoint.java | 63 + .../rest/endpoint/AccountEndpoint.java | 38 + .../rest/endpoint/AssetsEndpoint.java | 78 + .../rest/endpoint/CalendarEndpoint.java | 67 + .../refactor/rest/endpoint/ClockEndpoint.java | 38 + .../rest/endpoint/MarketDataEndpoint.java | 275 +++ .../rest/endpoint/OrdersEndpoint.java | 633 ++++++ .../endpoint/PortfolioHistoryEndpoint.java | 99 + .../rest/endpoint/PositionsEndpoint.java | 127 ++ .../rest/endpoint/WatchlistEndpoint.java | 211 ++ .../util/doc/Documentation2JSONSchema.java | 2 +- .../util/format/FormatUtil.java | 2 +- .../alpaca/refactor/util/gson/GsonUtil.java | 21 + .../util/gson/LocalDateAdapter.java | 2 +- .../util/gson/ZonedDateTimeAdapter.java | 2 +- .../refactor/util/okhttp/JSONBodyBuilder.java | 63 + .../util/properties/PropertyUtil.java | 2 +- .../alpaca/rest/AlpacaRequest.java | 227 -- .../alpaca/rest/AlpacaRequestBuilder.java | 31 - .../exception/AlpacaAPIRequestException.java | 67 - .../alpaca/util/gson/GsonUtil.java | 95 - .../broker/client/AlpacaWebsocketClient.java | 6 +- .../client/AlpacaWebsocketClientEndpoint.java | 2 +- .../client/MarketDataWebsocketClient.java | 6 +- .../MarketDataWebsocketClientEndpoint.java | 2 +- src/main/resources/alpaca.default.properties | 2 +- ...PIEndpointTest.java => AlpacaAPITest.java} | 104 +- .../alpaca/test/mock/AlpacaAPITest.java | 6 +- upload_to_maven_central.sh | 2 - 109 files changed, 2871 insertions(+), 3168 deletions(-) delete mode 100644 schema_json/account_configuration/account_configuration.json rename schema_json/{ => endpoint}/account/account.json (95%) rename schema_json/{ => endpoint}/account/enums/account_status.json (100%) rename schema_json/{ => endpoint}/account_activities/account_activity.json (65%) rename schema_json/{ => endpoint}/account_activities/enums/activity_type.json (100%) rename schema_json/{ => endpoint}/account_activities/enums/trade_activity_side.json (100%) rename schema_json/{ => endpoint}/account_activities/enums/trade_activity_type.json (100%) rename schema_json/{ => endpoint}/account_activities/non_trade_activity.json (93%) rename schema_json/{ => endpoint}/account_activities/trade_activity.json (74%) create mode 100644 schema_json/endpoint/account_configuration/account_configuration.json rename schema_json/{ => endpoint}/account_configuration/enums/dtbp_check.json (100%) rename schema_json/{ => endpoint}/account_configuration/enums/trade_confirm_email.json (100%) rename schema_json/{ => endpoint}/asset/asset.json (88%) rename schema_json/{ => endpoint}/asset/enums/asset_status.json (100%) rename schema_json/{ => endpoint}/calendar/calendar.json (100%) rename schema_json/{ => endpoint}/clock/clock.json (100%) rename schema_json/{ => endpoint}/common/enums/sort_direction.json (100%) rename schema_json/{ => endpoint}/market_data/historical/bar/bar.json (100%) rename schema_json/{ => endpoint}/market_data/historical/bar/bars_response.json (82%) rename schema_json/{ => endpoint}/market_data/historical/quote/latest_quote_response.json (72%) rename schema_json/{ => endpoint}/market_data/historical/quote/quote.json (100%) rename schema_json/{ => endpoint}/market_data/historical/quote/quotes_response.json (81%) create mode 100644 schema_json/endpoint/market_data/historical/snapshot/snapshot.json rename schema_json/{ => endpoint}/market_data/historical/trade/latest_trade_response.json (72%) rename schema_json/{ => endpoint}/market_data/historical/trade/trade.json (100%) rename schema_json/{ => endpoint}/market_data/historical/trade/trades_response.json (81%) rename schema_json/{ => endpoint}/market_data/realtime/bar/bar_message.json (100%) rename schema_json/{ => endpoint}/market_data/realtime/control/error_message.json (100%) rename schema_json/{ => endpoint}/market_data/realtime/control/subscriptions_message.json (100%) rename schema_json/{ => endpoint}/market_data/realtime/control/success_message.json (100%) rename schema_json/{ => endpoint}/market_data/realtime/enums/market_data_message_type.json (100%) rename schema_json/{ => endpoint}/market_data/realtime/market_data_message.json (59%) rename schema_json/{ => endpoint}/market_data/realtime/quote/quote_message.json (100%) rename schema_json/{ => endpoint}/market_data/realtime/symbol_message.json (100%) rename schema_json/{ => endpoint}/market_data/realtime/trade/trade_message.json (100%) rename schema_json/{ => endpoint}/order/cancelled_order.json (81%) create mode 100644 schema_json/endpoint/order/enums/current_order_status.json rename schema_json/{ => endpoint}/order/enums/order_class.json (100%) rename schema_json/{ => endpoint}/order/enums/order_side.json (100%) rename schema_json/{ => endpoint}/order/enums/order_status.json (100%) rename schema_json/{ => endpoint}/order/enums/order_time_in_force.json (100%) rename schema_json/{ => endpoint}/order/enums/order_type.json (100%) rename schema_json/{ => endpoint}/order/order.json (76%) rename schema_json/{ => endpoint}/portfolio_history/enums/portfolio_period_unit.json (100%) rename schema_json/{ => endpoint}/portfolio_history/enums/portfolio_time_frame.json (64%) create mode 100644 schema_json/endpoint/portfolio_history/portfolio_history.json create mode 100644 schema_json/endpoint/portfolio_history/portfolio_history_data_point.json rename schema_json/{portfolio_history/portfolio_history.json => endpoint/portfolio_history/portfolio_history_response.json} (90%) rename schema_json/{ => endpoint}/position/close_position_order.json (82%) rename schema_json/{ => endpoint}/position/position.json (100%) rename schema_json/{ => endpoint}/streaming/account/account_update.json (64%) create mode 100644 schema_json/endpoint/streaming/account/account_update_message.json rename schema_json/{ => endpoint}/streaming/alpaca_stream_message.json (55%) rename schema_json/{ => endpoint}/streaming/authorization/authorization_data.json (100%) rename schema_json/{ => endpoint}/streaming/authorization/authorization_message.json (55%) rename schema_json/{ => endpoint}/streaming/enums/alpaca_stream_message_type.json (100%) rename schema_json/{ => endpoint}/streaming/listening/listening_data.json (68%) rename schema_json/{ => endpoint}/streaming/listening/listening_message.json (57%) rename schema_json/{ => endpoint}/streaming/trade/enums/trade_update_event.json (100%) rename schema_json/{ => endpoint}/streaming/trade/trade_update.json (57%) rename schema_json/{ => endpoint}/streaming/trade/trade_update_message.json (59%) rename schema_json/{ => endpoint}/watchlist/watchlist.json (100%) delete mode 100644 schema_json/market_data/historical/snapshot/snapshot.json create mode 100644 schema_json/properties/data_api_type.json create mode 100644 schema_json/properties/endpoint_api_type.json delete mode 100644 schema_json/streaming/account/account_update_message.json delete mode 100644 src/main/java/net/jacobpeterson/alpaca/AlpacaAPI.java delete mode 100644 src/main/java/net/jacobpeterson/alpaca/abstracts/rest/AbstractRequest.java delete mode 100644 src/main/java/net/jacobpeterson/alpaca/abstracts/rest/AbstractRequestBuilder.java delete mode 100644 src/main/java/net/jacobpeterson/alpaca/abstracts/rest/exception/AbstractAPIRequestException.java delete mode 100644 src/main/java/net/jacobpeterson/alpaca/properties/AlpacaProperties.java delete mode 100644 src/main/java/net/jacobpeterson/alpaca/properties/enums/DataAPIType.java delete mode 100644 src/main/java/net/jacobpeterson/alpaca/properties/enums/EndpointAPIType.java create mode 100644 src/main/java/net/jacobpeterson/alpaca/refactor/AlpacaAPI.java create mode 100644 src/main/java/net/jacobpeterson/alpaca/refactor/properties/AlpacaProperties.java create mode 100644 src/main/java/net/jacobpeterson/alpaca/refactor/rest/AlpacaClient.java create mode 100644 src/main/java/net/jacobpeterson/alpaca/refactor/rest/AlpacaClientException.java create mode 100644 src/main/java/net/jacobpeterson/alpaca/refactor/rest/endpoint/AbstractEndpoint.java create mode 100644 src/main/java/net/jacobpeterson/alpaca/refactor/rest/endpoint/AccountActivitiesEndpoint.java create mode 100644 src/main/java/net/jacobpeterson/alpaca/refactor/rest/endpoint/AccountConfigurationEndpoint.java create mode 100644 src/main/java/net/jacobpeterson/alpaca/refactor/rest/endpoint/AccountEndpoint.java create mode 100644 src/main/java/net/jacobpeterson/alpaca/refactor/rest/endpoint/AssetsEndpoint.java create mode 100644 src/main/java/net/jacobpeterson/alpaca/refactor/rest/endpoint/CalendarEndpoint.java create mode 100644 src/main/java/net/jacobpeterson/alpaca/refactor/rest/endpoint/ClockEndpoint.java create mode 100644 src/main/java/net/jacobpeterson/alpaca/refactor/rest/endpoint/MarketDataEndpoint.java create mode 100644 src/main/java/net/jacobpeterson/alpaca/refactor/rest/endpoint/OrdersEndpoint.java create mode 100644 src/main/java/net/jacobpeterson/alpaca/refactor/rest/endpoint/PortfolioHistoryEndpoint.java create mode 100644 src/main/java/net/jacobpeterson/alpaca/refactor/rest/endpoint/PositionsEndpoint.java create mode 100644 src/main/java/net/jacobpeterson/alpaca/refactor/rest/endpoint/WatchlistEndpoint.java rename src/main/java/net/jacobpeterson/alpaca/{ => refactor}/util/doc/Documentation2JSONSchema.java (99%) rename src/main/java/net/jacobpeterson/alpaca/{ => refactor}/util/format/FormatUtil.java (96%) create mode 100644 src/main/java/net/jacobpeterson/alpaca/refactor/util/gson/GsonUtil.java rename src/main/java/net/jacobpeterson/alpaca/{ => refactor}/util/gson/LocalDateAdapter.java (95%) rename src/main/java/net/jacobpeterson/alpaca/{ => refactor}/util/gson/ZonedDateTimeAdapter.java (95%) create mode 100644 src/main/java/net/jacobpeterson/alpaca/refactor/util/okhttp/JSONBodyBuilder.java rename src/main/java/net/jacobpeterson/alpaca/{ => refactor}/util/properties/PropertyUtil.java (98%) delete mode 100644 src/main/java/net/jacobpeterson/alpaca/rest/AlpacaRequest.java delete mode 100644 src/main/java/net/jacobpeterson/alpaca/rest/AlpacaRequestBuilder.java delete mode 100644 src/main/java/net/jacobpeterson/alpaca/rest/exception/AlpacaAPIRequestException.java delete mode 100644 src/main/java/net/jacobpeterson/alpaca/util/gson/GsonUtil.java rename src/test/java/net/jacobpeterson/alpaca/test/live/{AlpacaAPIEndpointTest.java => AlpacaAPITest.java} (65%) delete mode 100755 upload_to_maven_central.sh diff --git a/.editorconfig b/.editorconfig index e835e84c..f8b11fdd 100644 --- a/.editorconfig +++ b/.editorconfig @@ -65,7 +65,7 @@ ij_java_case_statement_on_separate_line = true ij_java_catch_on_new_line = false ij_java_class_annotation_wrap = split_into_lines ij_java_class_brace_style = end_of_line -ij_java_class_count_to_use_import_on_demand = 50 +ij_java_class_count_to_use_import_on_demand = 5 ij_java_class_names_in_javadoc = 1 ij_java_do_not_indent_top_level_class_members = false ij_java_do_not_wrap_after_single_annotation = false @@ -129,7 +129,7 @@ ij_java_method_parameters_new_line_after_left_paren = false ij_java_method_parameters_right_paren_on_new_line = false ij_java_method_parameters_wrap = normal ij_java_modifier_list_wrap = false -ij_java_names_count_to_use_import_on_demand = 3 +ij_java_names_count_to_use_import_on_demand = 5 ij_java_parameter_annotation_wrap = normal ij_java_parentheses_expression_new_line_after_left_paren = false ij_java_parentheses_expression_right_paren_on_new_line = false diff --git a/schema_json/account_configuration/account_configuration.json b/schema_json/account_configuration/account_configuration.json deleted file mode 100644 index 9bf8fc35..00000000 --- a/schema_json/account_configuration/account_configuration.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "type": "object", - "title": "See Account Configuration.", - "properties": { - "dtbp_check": { - "existingJavaType": "net.jacobpeterson.alpaca.model.accountconfiguration.enums.DTBPCheck", - "title": "The {@link net.jacobpeterson.alpaca.model.accountconfiguration.enums.DTBPCheck}. Controls Day Trading Margin Call (DTMC) checks." - }, - "trade_confirm_email": { - "existingJavaType": "net.jacobpeterson.alpaca.model.accountconfiguration.enums.TradeConfirmEmail", - "title": "The {@link net.jacobpeterson.alpaca.model.accountconfiguration.enums.TradeConfirmEmail}. If {@link net.jacobpeterson.alpaca.model.accountconfiguration.enums.TradeConfirmEmail#NONE}, emails for order fills are not sent." - }, - "suspend_trade": { - "existingJavaType": "java.lang.Boolean", - "title": "If true, new orders are blocked." - }, - "no_shorting": { - "existingJavaType": "java.lang.Boolean", - "title": "If true, account becomes long-only mode." - } - } -} diff --git a/schema_json/account/account.json b/schema_json/endpoint/account/account.json similarity index 95% rename from schema_json/account/account.json rename to schema_json/endpoint/account/account.json index 7bbf8633..a6e6d506 100644 --- a/schema_json/account/account.json +++ b/schema_json/endpoint/account/account.json @@ -11,8 +11,8 @@ "title": "Account number." }, "status": { - "existingJavaType": "net.jacobpeterson.alpaca.model.account.enums.AccountStatus", - "title": "The {@link net.jacobpeterson.alpaca.model.account.enums.AccountStatus}." + "existingJavaType": "net.jacobpeterson.alpaca.model.endpoint.account.enums.AccountStatus", + "title": "The {@link net.jacobpeterson.alpaca.model.endpoint.account.enums.AccountStatus}." }, "currency": { "existingJavaType": "java.lang.String", diff --git a/schema_json/account/enums/account_status.json b/schema_json/endpoint/account/enums/account_status.json similarity index 100% rename from schema_json/account/enums/account_status.json rename to schema_json/endpoint/account/enums/account_status.json diff --git a/schema_json/account_activities/account_activity.json b/schema_json/endpoint/account_activities/account_activity.json similarity index 65% rename from schema_json/account_activities/account_activity.json rename to schema_json/endpoint/account_activities/account_activity.json index 2e931066..bfa36b8f 100644 --- a/schema_json/account_activities/account_activity.json +++ b/schema_json/endpoint/account_activities/account_activity.json @@ -3,8 +3,8 @@ "title": "See Account Activities.", "properties": { "activity_type": { - "existingJavaType": "net.jacobpeterson.alpaca.model.accountactivities.enums.ActivityType", - "title": "The {@link net.jacobpeterson.alpaca.model.accountactivities.enums.ActivityType}." + "existingJavaType": "net.jacobpeterson.alpaca.model.endpoint.accountactivities.enums.ActivityType", + "title": "The {@link net.jacobpeterson.alpaca.model.endpoint.accountactivities.enums.ActivityType}." }, "id": { "existingJavaType": "java.lang.String", diff --git a/schema_json/account_activities/enums/activity_type.json b/schema_json/endpoint/account_activities/enums/activity_type.json similarity index 100% rename from schema_json/account_activities/enums/activity_type.json rename to schema_json/endpoint/account_activities/enums/activity_type.json diff --git a/schema_json/account_activities/enums/trade_activity_side.json b/schema_json/endpoint/account_activities/enums/trade_activity_side.json similarity index 100% rename from schema_json/account_activities/enums/trade_activity_side.json rename to schema_json/endpoint/account_activities/enums/trade_activity_side.json diff --git a/schema_json/account_activities/enums/trade_activity_type.json b/schema_json/endpoint/account_activities/enums/trade_activity_type.json similarity index 100% rename from schema_json/account_activities/enums/trade_activity_type.json rename to schema_json/endpoint/account_activities/enums/trade_activity_type.json diff --git a/schema_json/account_activities/non_trade_activity.json b/schema_json/endpoint/account_activities/non_trade_activity.json similarity index 93% rename from schema_json/account_activities/non_trade_activity.json rename to schema_json/endpoint/account_activities/non_trade_activity.json index b4ee73b2..c66a15af 100644 --- a/schema_json/account_activities/non_trade_activity.json +++ b/schema_json/endpoint/account_activities/non_trade_activity.json @@ -2,7 +2,7 @@ "type": "object", "title": "See Account Activities.", "extends": { - "existingJavaType": "net.jacobpeterson.alpaca.model.accountactivities.AccountActivity" + "existingJavaType": "net.jacobpeterson.alpaca.model.endpoint.accountactivities.AccountActivity" }, "properties": { "date": { diff --git a/schema_json/account_activities/trade_activity.json b/schema_json/endpoint/account_activities/trade_activity.json similarity index 74% rename from schema_json/account_activities/trade_activity.json rename to schema_json/endpoint/account_activities/trade_activity.json index 7561047d..5e817291 100644 --- a/schema_json/account_activities/trade_activity.json +++ b/schema_json/endpoint/account_activities/trade_activity.json @@ -22,8 +22,8 @@ "title": "The number of shares involved in the trade execution." }, "side": { - "existingJavaType": "net.jacobpeterson.alpaca.model.accountactivities.enums.TradeActivitySide", - "title": "The {@link net.jacobpeterson.alpaca.model.accountactivities.enums.TradeActivitySide}." + "existingJavaType": "net.jacobpeterson.alpaca.model.endpoint.accountactivities.enums.TradeActivitySide", + "title": "The {@link net.jacobpeterson.alpaca.model.endpoint.accountactivities.enums.TradeActivitySide}." }, "symbol": { "existingJavaType": "java.lang.String", @@ -38,8 +38,8 @@ "title": "The id for the order that filled. Always in GUID format." }, "type": { - "existingJavaType": "net.jacobpeterson.alpaca.model.accountactivities.enums.TradeActivityType", - "title": "The {@link net.jacobpeterson.alpaca.model.accountactivities.enums.TradeActivityType}." + "existingJavaType": "net.jacobpeterson.alpaca.model.endpoint.accountactivities.enums.TradeActivityType", + "title": "The {@link net.jacobpeterson.alpaca.model.endpoint.accountactivities.enums.TradeActivityType}." } } } diff --git a/schema_json/endpoint/account_configuration/account_configuration.json b/schema_json/endpoint/account_configuration/account_configuration.json new file mode 100644 index 00000000..704ee4e1 --- /dev/null +++ b/schema_json/endpoint/account_configuration/account_configuration.json @@ -0,0 +1,22 @@ +{ + "type": "object", + "title": "See Account Configuration.", + "properties": { + "dtbp_check": { + "existingJavaType": "net.jacobpeterson.alpaca.model.endpoint.accountconfiguration.enums.DTBPCheck", + "title": "The {@link net.jacobpeterson.alpaca.model.endpoint.accountconfiguration.enums.DTBPCheck}. Controls Day Trading Margin Call (DTMC) checks." + }, + "trade_confirm_email": { + "existingJavaType": "net.jacobpeterson.alpaca.model.endpoint.accountconfiguration.enums.TradeConfirmEmail", + "title": "The {@link net.jacobpeterson.alpaca.model.endpoint.accountconfiguration.enums.TradeConfirmEmail}. If {@link net.jacobpeterson.alpaca.model.endpoint.accountconfiguration.enums.TradeConfirmEmail#NONE}, emails for order fills are not sent." + }, + "suspend_trade": { + "existingJavaType": "java.lang.Boolean", + "title": "If true, new orders are blocked." + }, + "no_shorting": { + "existingJavaType": "java.lang.Boolean", + "title": "If true, account becomes long-only mode." + } + } +} diff --git a/schema_json/account_configuration/enums/dtbp_check.json b/schema_json/endpoint/account_configuration/enums/dtbp_check.json similarity index 100% rename from schema_json/account_configuration/enums/dtbp_check.json rename to schema_json/endpoint/account_configuration/enums/dtbp_check.json diff --git a/schema_json/account_configuration/enums/trade_confirm_email.json b/schema_json/endpoint/account_configuration/enums/trade_confirm_email.json similarity index 100% rename from schema_json/account_configuration/enums/trade_confirm_email.json rename to schema_json/endpoint/account_configuration/enums/trade_confirm_email.json diff --git a/schema_json/asset/asset.json b/schema_json/endpoint/asset/asset.json similarity index 88% rename from schema_json/asset/asset.json rename to schema_json/endpoint/asset/asset.json index 49cd8218..b563bf4a 100644 --- a/schema_json/asset/asset.json +++ b/schema_json/endpoint/asset/asset.json @@ -23,8 +23,8 @@ "title": "The official name of the asset." }, "status": { - "existingJavaType": "net.jacobpeterson.alpaca.model.asset.enums.AssetStatus", - "title": "The {@link net.jacobpeterson.alpaca.model.asset.enums.AssetStatus}." + "existingJavaType": "net.jacobpeterson.alpaca.model.endpoint.asset.enums.AssetStatus", + "title": "The {@link net.jacobpeterson.alpaca.model.endpoint.asset.enums.AssetStatus}." }, "tradable": { "existingJavaType": "java.lang.Boolean", diff --git a/schema_json/asset/enums/asset_status.json b/schema_json/endpoint/asset/enums/asset_status.json similarity index 100% rename from schema_json/asset/enums/asset_status.json rename to schema_json/endpoint/asset/enums/asset_status.json diff --git a/schema_json/calendar/calendar.json b/schema_json/endpoint/calendar/calendar.json similarity index 100% rename from schema_json/calendar/calendar.json rename to schema_json/endpoint/calendar/calendar.json diff --git a/schema_json/clock/clock.json b/schema_json/endpoint/clock/clock.json similarity index 100% rename from schema_json/clock/clock.json rename to schema_json/endpoint/clock/clock.json diff --git a/schema_json/common/enums/sort_direction.json b/schema_json/endpoint/common/enums/sort_direction.json similarity index 100% rename from schema_json/common/enums/sort_direction.json rename to schema_json/endpoint/common/enums/sort_direction.json diff --git a/schema_json/market_data/historical/bar/bar.json b/schema_json/endpoint/market_data/historical/bar/bar.json similarity index 100% rename from schema_json/market_data/historical/bar/bar.json rename to schema_json/endpoint/market_data/historical/bar/bar.json diff --git a/schema_json/market_data/historical/bar/bars_response.json b/schema_json/endpoint/market_data/historical/bar/bars_response.json similarity index 82% rename from schema_json/market_data/historical/bar/bars_response.json rename to schema_json/endpoint/market_data/historical/bar/bars_response.json index 28692460..a876dcf8 100644 --- a/schema_json/market_data/historical/bar/bars_response.json +++ b/schema_json/endpoint/market_data/historical/bar/bars_response.json @@ -3,8 +3,8 @@ "title": "See Historical Data.", "properties": { "bars": { - "existingJavaType": "java.util.ArrayList", - "title": "The {@link java.util.ArrayList} of {@link net.jacobpeterson.alpaca.model.marketdata.historical.bar.Bar}s." + "existingJavaType": "java.util.ArrayList", + "title": "The {@link java.util.ArrayList} of {@link net.jacobpeterson.alpaca.model.endpoint.marketdata.historical.bar.Bar}s." }, "symbol": { "existingJavaType": "java.lang.String", diff --git a/schema_json/market_data/historical/quote/latest_quote_response.json b/schema_json/endpoint/market_data/historical/quote/latest_quote_response.json similarity index 72% rename from schema_json/market_data/historical/quote/latest_quote_response.json rename to schema_json/endpoint/market_data/historical/quote/latest_quote_response.json index e7fda37a..54477417 100644 --- a/schema_json/market_data/historical/quote/latest_quote_response.json +++ b/schema_json/endpoint/market_data/historical/quote/latest_quote_response.json @@ -7,8 +7,8 @@ "title": "Symbol that was queried." }, "quote": { - "existingJavaType": "net.jacobpeterson.alpaca.model.marketdata.historical.quote.Quote", - "title": "The latest {@link net.jacobpeterson.alpaca.model.marketdata.historical.quote.Quote}." + "existingJavaType": "net.jacobpeterson.alpaca.model.endpoint.marketdata.historical.quote.Quote", + "title": "The latest {@link net.jacobpeterson.alpaca.model.endpoint.marketdata.historical.quote.Quote}." } } } diff --git a/schema_json/market_data/historical/quote/quote.json b/schema_json/endpoint/market_data/historical/quote/quote.json similarity index 100% rename from schema_json/market_data/historical/quote/quote.json rename to schema_json/endpoint/market_data/historical/quote/quote.json diff --git a/schema_json/market_data/historical/quote/quotes_response.json b/schema_json/endpoint/market_data/historical/quote/quotes_response.json similarity index 81% rename from schema_json/market_data/historical/quote/quotes_response.json rename to schema_json/endpoint/market_data/historical/quote/quotes_response.json index 15810872..88c1286e 100644 --- a/schema_json/market_data/historical/quote/quotes_response.json +++ b/schema_json/endpoint/market_data/historical/quote/quotes_response.json @@ -3,8 +3,8 @@ "title": "See Historical Data.", "properties": { "quotes": { - "existingJavaType": "java.util.ArrayList", - "title": "The {@link java.util.ArrayList} of {@link net.jacobpeterson.alpaca.model.marketdata.historical.quote.Quote}s." + "existingJavaType": "java.util.ArrayList", + "title": "The {@link java.util.ArrayList} of {@link net.jacobpeterson.alpaca.model.endpoint.marketdata.historical.quote.Quote}s." }, "symbol": { "existingJavaType": "java.lang.String", diff --git a/schema_json/endpoint/market_data/historical/snapshot/snapshot.json b/schema_json/endpoint/market_data/historical/snapshot/snapshot.json new file mode 100644 index 00000000..c2a0acf0 --- /dev/null +++ b/schema_json/endpoint/market_data/historical/snapshot/snapshot.json @@ -0,0 +1,26 @@ +{ + "type": "object", + "title": "See Historical Data.", + "properties": { + "latestTrade": { + "existingJavaType": "net.jacobpeterson.alpaca.model.endpoint.marketdata.historical.trade.Trade", + "title": "The latest {@link net.jacobpeterson.alpaca.model.endpoint.marketdata.historical.trade.Trade}." + }, + "latestQuote": { + "existingJavaType": "net.jacobpeterson.alpaca.model.endpoint.marketdata.historical.quote.Quote", + "title": "The latest {@link net.jacobpeterson.alpaca.model.endpoint.marketdata.historical.quote.Quote}." + }, + "minuteBar": { + "existingJavaType": "net.jacobpeterson.alpaca.model.endpoint.marketdata.historical.bar.Bar", + "title": "The minute {@link net.jacobpeterson.alpaca.model.endpoint.marketdata.historical.bar.Bar}." + }, + "dailyBar": { + "existingJavaType": "net.jacobpeterson.alpaca.model.endpoint.marketdata.historical.bar.Bar", + "title": "The daily {@link net.jacobpeterson.alpaca.model.endpoint.marketdata.historical.bar.Bar}." + }, + "prevDailyBar": { + "existingJavaType": "net.jacobpeterson.alpaca.model.endpoint.marketdata.historical.bar.Bar", + "title": "The previous daily {@link net.jacobpeterson.alpaca.model.endpoint.marketdata.historical.bar.Bar}." + } + } +} diff --git a/schema_json/market_data/historical/trade/latest_trade_response.json b/schema_json/endpoint/market_data/historical/trade/latest_trade_response.json similarity index 72% rename from schema_json/market_data/historical/trade/latest_trade_response.json rename to schema_json/endpoint/market_data/historical/trade/latest_trade_response.json index d71856aa..3025a719 100644 --- a/schema_json/market_data/historical/trade/latest_trade_response.json +++ b/schema_json/endpoint/market_data/historical/trade/latest_trade_response.json @@ -7,8 +7,8 @@ "title": "Symbol that was queried." }, "trade": { - "existingJavaType": "net.jacobpeterson.alpaca.model.marketdata.historical.trade.Trade", - "title": "The latest {@link net.jacobpeterson.alpaca.model.marketdata.historical.trade.Trade}." + "existingJavaType": "net.jacobpeterson.alpaca.model.endpoint.marketdata.historical.trade.Trade", + "title": "The latest {@link net.jacobpeterson.alpaca.model.endpoint.marketdata.historical.trade.Trade}." } } } diff --git a/schema_json/market_data/historical/trade/trade.json b/schema_json/endpoint/market_data/historical/trade/trade.json similarity index 100% rename from schema_json/market_data/historical/trade/trade.json rename to schema_json/endpoint/market_data/historical/trade/trade.json diff --git a/schema_json/market_data/historical/trade/trades_response.json b/schema_json/endpoint/market_data/historical/trade/trades_response.json similarity index 81% rename from schema_json/market_data/historical/trade/trades_response.json rename to schema_json/endpoint/market_data/historical/trade/trades_response.json index 810bc260..928f6f24 100644 --- a/schema_json/market_data/historical/trade/trades_response.json +++ b/schema_json/endpoint/market_data/historical/trade/trades_response.json @@ -3,8 +3,8 @@ "title": "See Historical Data.", "properties": { "trades": { - "existingJavaType": "java.util.ArrayList", - "title": "The {@link java.util.ArrayList} of {@link net.jacobpeterson.alpaca.model.marketdata.historical.trade.Trade}s." + "existingJavaType": "java.util.ArrayList", + "title": "The {@link java.util.ArrayList} of {@link net.jacobpeterson.alpaca.model.endpoint.marketdata.historical.trade.Trade}s." }, "symbol": { "existingJavaType": "java.lang.String", diff --git a/schema_json/market_data/realtime/bar/bar_message.json b/schema_json/endpoint/market_data/realtime/bar/bar_message.json similarity index 100% rename from schema_json/market_data/realtime/bar/bar_message.json rename to schema_json/endpoint/market_data/realtime/bar/bar_message.json diff --git a/schema_json/market_data/realtime/control/error_message.json b/schema_json/endpoint/market_data/realtime/control/error_message.json similarity index 100% rename from schema_json/market_data/realtime/control/error_message.json rename to schema_json/endpoint/market_data/realtime/control/error_message.json diff --git a/schema_json/market_data/realtime/control/subscriptions_message.json b/schema_json/endpoint/market_data/realtime/control/subscriptions_message.json similarity index 100% rename from schema_json/market_data/realtime/control/subscriptions_message.json rename to schema_json/endpoint/market_data/realtime/control/subscriptions_message.json diff --git a/schema_json/market_data/realtime/control/success_message.json b/schema_json/endpoint/market_data/realtime/control/success_message.json similarity index 100% rename from schema_json/market_data/realtime/control/success_message.json rename to schema_json/endpoint/market_data/realtime/control/success_message.json diff --git a/schema_json/market_data/realtime/enums/market_data_message_type.json b/schema_json/endpoint/market_data/realtime/enums/market_data_message_type.json similarity index 100% rename from schema_json/market_data/realtime/enums/market_data_message_type.json rename to schema_json/endpoint/market_data/realtime/enums/market_data_message_type.json diff --git a/schema_json/market_data/realtime/market_data_message.json b/schema_json/endpoint/market_data/realtime/market_data_message.json similarity index 59% rename from schema_json/market_data/realtime/market_data_message.json rename to schema_json/endpoint/market_data/realtime/market_data_message.json index 5a96da74..66e00245 100644 --- a/schema_json/market_data/realtime/market_data_message.json +++ b/schema_json/endpoint/market_data/realtime/market_data_message.json @@ -6,9 +6,9 @@ "title": "See Real-time Data.", "properties": { "T": { - "existingJavaType": "net.jacobpeterson.alpaca.model.marketdata.realtime.enums.MarketDataMessageType", + "existingJavaType": "net.jacobpeterson.alpaca.model.endpoint.marketdata.realtime.enums.MarketDataMessageType", "javaName": "messageType", - "title": "The {@link net.jacobpeterson.alpaca.model.marketdata.realtime.enums.MarketDataMessageType}." + "title": "The {@link net.jacobpeterson.alpaca.model.endpoint.marketdata.realtime.enums.MarketDataMessageType}." } } } diff --git a/schema_json/market_data/realtime/quote/quote_message.json b/schema_json/endpoint/market_data/realtime/quote/quote_message.json similarity index 100% rename from schema_json/market_data/realtime/quote/quote_message.json rename to schema_json/endpoint/market_data/realtime/quote/quote_message.json diff --git a/schema_json/market_data/realtime/symbol_message.json b/schema_json/endpoint/market_data/realtime/symbol_message.json similarity index 100% rename from schema_json/market_data/realtime/symbol_message.json rename to schema_json/endpoint/market_data/realtime/symbol_message.json diff --git a/schema_json/market_data/realtime/trade/trade_message.json b/schema_json/endpoint/market_data/realtime/trade/trade_message.json similarity index 100% rename from schema_json/market_data/realtime/trade/trade_message.json rename to schema_json/endpoint/market_data/realtime/trade/trade_message.json diff --git a/schema_json/order/cancelled_order.json b/schema_json/endpoint/order/cancelled_order.json similarity index 81% rename from schema_json/order/cancelled_order.json rename to schema_json/endpoint/order/cancelled_order.json index 96cd3967..be79774c 100644 --- a/schema_json/order/cancelled_order.json +++ b/schema_json/endpoint/order/cancelled_order.json @@ -11,9 +11,9 @@ "title": "The HTTP status code of the cancelled order." }, "body": { - "existingJavaType": "net.jacobpeterson.alpaca.model.order.Order", + "existingJavaType": "net.jacobpeterson.alpaca.model.endpoint.order.Order", "javaName": "order", - "title": "The cancelled {@link net.jacobpeterson.alpaca.model.order.Order}." + "title": "The cancelled {@link net.jacobpeterson.alpaca.model.endpoint.order.Order}." } } } diff --git a/schema_json/endpoint/order/enums/current_order_status.json b/schema_json/endpoint/order/enums/current_order_status.json new file mode 100644 index 00000000..25a5829f --- /dev/null +++ b/schema_json/endpoint/order/enums/current_order_status.json @@ -0,0 +1,9 @@ +{ + "type": "string", + "title": "See Orders.", + "enum": [ + "open", + "closed", + "all" + ] +} diff --git a/schema_json/order/enums/order_class.json b/schema_json/endpoint/order/enums/order_class.json similarity index 100% rename from schema_json/order/enums/order_class.json rename to schema_json/endpoint/order/enums/order_class.json diff --git a/schema_json/order/enums/order_side.json b/schema_json/endpoint/order/enums/order_side.json similarity index 100% rename from schema_json/order/enums/order_side.json rename to schema_json/endpoint/order/enums/order_side.json diff --git a/schema_json/order/enums/order_status.json b/schema_json/endpoint/order/enums/order_status.json similarity index 100% rename from schema_json/order/enums/order_status.json rename to schema_json/endpoint/order/enums/order_status.json diff --git a/schema_json/order/enums/order_time_in_force.json b/schema_json/endpoint/order/enums/order_time_in_force.json similarity index 100% rename from schema_json/order/enums/order_time_in_force.json rename to schema_json/endpoint/order/enums/order_time_in_force.json diff --git a/schema_json/order/enums/order_type.json b/schema_json/endpoint/order/enums/order_type.json similarity index 100% rename from schema_json/order/enums/order_type.json rename to schema_json/endpoint/order/enums/order_type.json diff --git a/schema_json/order/order.json b/schema_json/endpoint/order/order.json similarity index 76% rename from schema_json/order/order.json rename to schema_json/endpoint/order/order.json index d9b5c0d6..4af8dde8 100644 --- a/schema_json/order/order.json +++ b/schema_json/endpoint/order/order.json @@ -79,24 +79,24 @@ "title": "Filled average price." }, "order_class": { - "existingJavaType": "net.jacobpeterson.alpaca.model.order.enums.OrderClass", - "title": "The {@link net.jacobpeterson.alpaca.model.order.enums.OrderClass}. For details of non-simple order classes, please see Bracket Order Overview." + "existingJavaType": "net.jacobpeterson.alpaca.model.endpoint.order.enums.OrderClass", + "title": "The {@link net.jacobpeterson.alpaca.model.endpoint.order.enums.OrderClass}. For details of non-simple order classes, please see Bracket Order Overview." }, "order_type": { "existingJavaType": "java.lang.String", "title": "(Deprecated with just type field below.)." }, "type": { - "existingJavaType": "net.jacobpeterson.alpaca.model.order.enums.OrderType", - "title": "The {@link net.jacobpeterson.alpaca.model.order.enums.OrderType}." + "existingJavaType": "net.jacobpeterson.alpaca.model.endpoint.order.enums.OrderType", + "title": "The {@link net.jacobpeterson.alpaca.model.endpoint.order.enums.OrderType}." }, "side": { - "existingJavaType": "net.jacobpeterson.alpaca.model.order.enums.OrderSide", - "title": "The {@link net.jacobpeterson.alpaca.model.order.enums.OrderSide}." + "existingJavaType": "net.jacobpeterson.alpaca.model.endpoint.order.enums.OrderSide", + "title": "The {@link net.jacobpeterson.alpaca.model.endpoint.order.enums.OrderSide}." }, "time_in_force": { - "existingJavaType": "net.jacobpeterson.alpaca.model.order.enums.OrderTimeInForce", - "title": "The {@link net.jacobpeterson.alpaca.model.order.enums.OrderTimeInForce}." + "existingJavaType": "net.jacobpeterson.alpaca.model.endpoint.order.enums.OrderTimeInForce", + "title": "The {@link net.jacobpeterson.alpaca.model.endpoint.order.enums.OrderTimeInForce}." }, "limit_price": { "existingJavaType": "java.lang.String", @@ -107,16 +107,16 @@ "title": "Stop price." }, "status": { - "existingJavaType": "net.jacobpeterson.alpaca.model.order.enums.OrderStatus", - "title": "The {@link net.jacobpeterson.alpaca.model.order.enums.OrderStatus}." + "existingJavaType": "net.jacobpeterson.alpaca.model.endpoint.order.enums.OrderStatus", + "title": "The {@link net.jacobpeterson.alpaca.model.endpoint.order.enums.OrderStatus}." }, "extended_hours": { "existingJavaType": "java.lang.Boolean", "title": "If true, eligible for execution outside regular trading hours." }, "legs": { - "existingJavaType": "java.util.ArrayList", - "title": "When querying non-simple order_class orders in a nested style, an {@link java.util.ArrayList} of {@link net.jacobpeterson.alpaca.model.order.Order} entities associated with this order. Otherwise, null." + "existingJavaType": "java.util.ArrayList", + "title": "When querying non-simple order_class orders in a nested style, an {@link java.util.ArrayList} of {@link net.jacobpeterson.alpaca.model.endpoint.order.Order} entities associated with this order. Otherwise, null." }, "trail_percent": { "existingJavaType": "java.lang.String", diff --git a/schema_json/portfolio_history/enums/portfolio_period_unit.json b/schema_json/endpoint/portfolio_history/enums/portfolio_period_unit.json similarity index 100% rename from schema_json/portfolio_history/enums/portfolio_period_unit.json rename to schema_json/endpoint/portfolio_history/enums/portfolio_period_unit.json diff --git a/schema_json/portfolio_history/enums/portfolio_time_frame.json b/schema_json/endpoint/portfolio_history/enums/portfolio_time_frame.json similarity index 64% rename from schema_json/portfolio_history/enums/portfolio_time_frame.json rename to schema_json/endpoint/portfolio_history/enums/portfolio_time_frame.json index 2cd977e2..0fb614c7 100644 --- a/schema_json/portfolio_history/enums/portfolio_time_frame.json +++ b/schema_json/endpoint/portfolio_history/enums/portfolio_time_frame.json @@ -10,19 +10,19 @@ ], "javaEnums": [ { - "description": "ONE_MIN" + "name": "ONE_MIN" }, { - "description": "FIVE_MINUTE" + "name": "FIVE_MINUTE" }, { - "description": "FIFTEEN_MINUTE" + "name": "FIFTEEN_MINUTE" }, { - "description": "ONE_HOUR" + "name": "ONE_HOUR" }, { - "description": "ONE_DAY" + "name": "ONE_DAY" } ] } diff --git a/schema_json/endpoint/portfolio_history/portfolio_history.json b/schema_json/endpoint/portfolio_history/portfolio_history.json new file mode 100644 index 00000000..f9f833b4 --- /dev/null +++ b/schema_json/endpoint/portfolio_history/portfolio_history.json @@ -0,0 +1,18 @@ +{ + "type": "object", + "title": "See Portfolio History.", + "properties": { + "data_points": { + "existingJavaType": "java.util.ArrayList", + "title": "The {@link java.util.ArrayList} of {@link net.jacobpeterson.alpaca.model.endpoint.portfoliohistory.PortfolioHistoryDataPoint}s." + }, + "base_value": { + "existingJavaType": "java.lang.Double", + "title": "Basis in dollar of the profit loss calculation." + }, + "timeframe": { + "existingJavaType": "net.jacobpeterson.alpaca.model.endpoint.portfoliohistory.enums.PortfolioTimeFrame", + "title": "Time window size of each data element." + } + } +} diff --git a/schema_json/endpoint/portfolio_history/portfolio_history_data_point.json b/schema_json/endpoint/portfolio_history/portfolio_history_data_point.json new file mode 100644 index 00000000..d9b80bc3 --- /dev/null +++ b/schema_json/endpoint/portfolio_history/portfolio_history_data_point.json @@ -0,0 +1,22 @@ +{ + "type": "object", + "title": "See Portfolio History.", + "properties": { + "timestamp": { + "existingJavaType": "java.lang.Long", + "title": "Time of each data element." + }, + "equity": { + "existingJavaType": "java.lang.Double", + "title": "Equity value of the account in dollar amount as of the end of each time window." + }, + "profit_loss": { + "existingJavaType": "java.lang.Double", + "title": "Profit/loss in dollar from the base value." + }, + "profit_loss_percent": { + "existingJavaType": "java.lang.Double", + "title": "Profit/loss in percentage from the base value." + } + } +} diff --git a/schema_json/portfolio_history/portfolio_history.json b/schema_json/endpoint/portfolio_history/portfolio_history_response.json similarity index 90% rename from schema_json/portfolio_history/portfolio_history.json rename to schema_json/endpoint/portfolio_history/portfolio_history_response.json index c24275de..d43235f7 100644 --- a/schema_json/portfolio_history/portfolio_history.json +++ b/schema_json/endpoint/portfolio_history/portfolio_history_response.json @@ -23,7 +23,7 @@ "title": "Basis in dollar of the profit loss calculation." }, "timeframe": { - "existingJavaType": "net.jacobpeterson.alpaca.model.portfolio.enums.PortfolioTimeFrame", + "existingJavaType": "net.jacobpeterson.alpaca.model.endpoint.portfoliohistory.enums.PortfolioTimeFrame", "title": "Time window size of each data element." } } diff --git a/schema_json/position/close_position_order.json b/schema_json/endpoint/position/close_position_order.json similarity index 82% rename from schema_json/position/close_position_order.json rename to schema_json/endpoint/position/close_position_order.json index b1aacddd..5ce79575 100644 --- a/schema_json/position/close_position_order.json +++ b/schema_json/endpoint/position/close_position_order.json @@ -11,9 +11,9 @@ "title": "The HTTP status code of the position-closing order." }, "body": { - "existingJavaType": "net.jacobpeterson.alpaca.model.order.Order", + "existingJavaType": "net.jacobpeterson.alpaca.model.endpoint.order.Order", "javaName": "order", - "title": "The position-closing {@link net.jacobpeterson.alpaca.model.order.Order}." + "title": "The position-closing {@link net.jacobpeterson.alpaca.model.endpoint.order.Order}." } } } diff --git a/schema_json/position/position.json b/schema_json/endpoint/position/position.json similarity index 100% rename from schema_json/position/position.json rename to schema_json/endpoint/position/position.json diff --git a/schema_json/streaming/account/account_update.json b/schema_json/endpoint/streaming/account/account_update.json similarity index 64% rename from schema_json/streaming/account/account_update.json rename to schema_json/endpoint/streaming/account/account_update.json index 887b1acc..7123cdd9 100644 --- a/schema_json/streaming/account/account_update.json +++ b/schema_json/endpoint/streaming/account/account_update.json @@ -1,7 +1,7 @@ { "type": "object", "extends": { - "existingJavaType": "net.jacobpeterson.alpaca.model.account.Account" + "existingJavaType": "net.jacobpeterson.alpaca.model.endpoint.account.Account" }, "title": "See Streaming." } diff --git a/schema_json/endpoint/streaming/account/account_update_message.json b/schema_json/endpoint/streaming/account/account_update_message.json new file mode 100644 index 00000000..bef98c61 --- /dev/null +++ b/schema_json/endpoint/streaming/account/account_update_message.json @@ -0,0 +1,13 @@ +{ + "type": "object", + "extends": { + "existingJavaType": "net.jacobpeterson.alpaca.model.endpoint.streaming.AlpacaStreamMessage" + }, + "title": "See Streaming.", + "properties": { + "data": { + "existingJavaType": "net.jacobpeterson.alpaca.model.endpoint.streaming.account.AccountUpdate", + "title": "The {@link net.jacobpeterson.alpaca.model.endpoint.streaming.account.AccountUpdate}." + } + } +} diff --git a/schema_json/streaming/alpaca_stream_message.json b/schema_json/endpoint/streaming/alpaca_stream_message.json similarity index 55% rename from schema_json/streaming/alpaca_stream_message.json rename to schema_json/endpoint/streaming/alpaca_stream_message.json index 6e7f4833..f523ab42 100644 --- a/schema_json/streaming/alpaca_stream_message.json +++ b/schema_json/endpoint/streaming/alpaca_stream_message.json @@ -6,8 +6,8 @@ "title": "See Streaming.", "properties": { "stream": { - "existingJavaType": "net.jacobpeterson.alpaca.model.streaming.enums.AlpacaStreamMessageType", - "title": "The {@link net.jacobpeterson.alpaca.model.streaming.enums.AlpacaStreamMessageType}." + "existingJavaType": "net.jacobpeterson.alpaca.model.endpoint.streaming.enums.AlpacaStreamMessageType", + "title": "The {@link net.jacobpeterson.alpaca.model.endpoint.streaming.enums.AlpacaStreamMessageType}." } } } diff --git a/schema_json/streaming/authorization/authorization_data.json b/schema_json/endpoint/streaming/authorization/authorization_data.json similarity index 100% rename from schema_json/streaming/authorization/authorization_data.json rename to schema_json/endpoint/streaming/authorization/authorization_data.json diff --git a/schema_json/streaming/authorization/authorization_message.json b/schema_json/endpoint/streaming/authorization/authorization_message.json similarity index 55% rename from schema_json/streaming/authorization/authorization_message.json rename to schema_json/endpoint/streaming/authorization/authorization_message.json index c7a8c9cf..ecf6ead6 100644 --- a/schema_json/streaming/authorization/authorization_message.json +++ b/schema_json/endpoint/streaming/authorization/authorization_message.json @@ -6,8 +6,8 @@ }, "properties": { "data": { - "existingJavaType": "net.jacobpeterson.alpaca.model.streaming.authorization.AuthorizationData", - "title": "The {@link net.jacobpeterson.alpaca.model.streaming.authorization.AuthorizationData}." + "existingJavaType": "net.jacobpeterson.alpaca.model.endpoint.streaming.authorization.AuthorizationData", + "title": "The {@link net.jacobpeterson.alpaca.model.endpoint.streaming.authorization.AuthorizationData}." } } } diff --git a/schema_json/streaming/enums/alpaca_stream_message_type.json b/schema_json/endpoint/streaming/enums/alpaca_stream_message_type.json similarity index 100% rename from schema_json/streaming/enums/alpaca_stream_message_type.json rename to schema_json/endpoint/streaming/enums/alpaca_stream_message_type.json diff --git a/schema_json/streaming/listening/listening_data.json b/schema_json/endpoint/streaming/listening/listening_data.json similarity index 68% rename from schema_json/streaming/listening/listening_data.json rename to schema_json/endpoint/streaming/listening/listening_data.json index a5a3db2f..8d7cd2c7 100644 --- a/schema_json/streaming/listening/listening_data.json +++ b/schema_json/endpoint/streaming/listening/listening_data.json @@ -3,8 +3,8 @@ "title": "See Streaming.", "properties": { "streams": { - "existingJavaType": "java.util.ArrayList", - "title": "The {@link java.util.ArrayList} of {@link net.jacobpeterson.alpaca.model.streaming.enums.AlpacaStreamMessageType}s." + "existingJavaType": "java.util.ArrayList", + "title": "The {@link java.util.ArrayList} of {@link net.jacobpeterson.alpaca.model.endpoint.streaming.enums.AlpacaStreamMessageType}s." } } } diff --git a/schema_json/streaming/listening/listening_message.json b/schema_json/endpoint/streaming/listening/listening_message.json similarity index 57% rename from schema_json/streaming/listening/listening_message.json rename to schema_json/endpoint/streaming/listening/listening_message.json index 8a230446..e095fedc 100644 --- a/schema_json/streaming/listening/listening_message.json +++ b/schema_json/endpoint/streaming/listening/listening_message.json @@ -6,8 +6,8 @@ }, "properties": { "data": { - "existingJavaType": "net.jacobpeterson.alpaca.model.streaming.listening.ListeningData", - "title": "The {@link net.jacobpeterson.alpaca.model.streaming.listening.ListeningData}." + "existingJavaType": "net.jacobpeterson.alpaca.model.endpoint.streaming.listening.ListeningData", + "title": "The {@link net.jacobpeterson.alpaca.model.endpoint.streaming.listening.ListeningData}." } } } diff --git a/schema_json/streaming/trade/enums/trade_update_event.json b/schema_json/endpoint/streaming/trade/enums/trade_update_event.json similarity index 100% rename from schema_json/streaming/trade/enums/trade_update_event.json rename to schema_json/endpoint/streaming/trade/enums/trade_update_event.json diff --git a/schema_json/streaming/trade/trade_update.json b/schema_json/endpoint/streaming/trade/trade_update.json similarity index 57% rename from schema_json/streaming/trade/trade_update.json rename to schema_json/endpoint/streaming/trade/trade_update.json index ffa7558e..46188863 100644 --- a/schema_json/streaming/trade/trade_update.json +++ b/schema_json/endpoint/streaming/trade/trade_update.json @@ -3,8 +3,8 @@ "title": "See Streaming.", "properties": { "event": { - "existingJavaType": "net.jacobpeterson.alpaca.model.streaming.trade.enums.TradeUpdateEvent", - "title": "The {@link net.jacobpeterson.alpaca.model.streaming.trade.enums.TradeUpdateEvent}." + "existingJavaType": "net.jacobpeterson.alpaca.model.endpoint.streaming.trade.enums.TradeUpdateEvent", + "title": "The {@link net.jacobpeterson.alpaca.model.endpoint.streaming.trade.enums.TradeUpdateEvent}." }, "price": { "existingJavaType": "java.lang.String", @@ -19,8 +19,8 @@ "title": "The position quantity." }, "order": { - "existingJavaType": "net.jacobpeterson.alpaca.model.order.Order", - "title": "The {@link net.jacobpeterson.alpaca.model.order.Order}." + "existingJavaType": "net.jacobpeterson.alpaca.model.endpoint.order.Order", + "title": "The {@link net.jacobpeterson.alpaca.model.endpoint.order.Order}." } } } diff --git a/schema_json/streaming/trade/trade_update_message.json b/schema_json/endpoint/streaming/trade/trade_update_message.json similarity index 59% rename from schema_json/streaming/trade/trade_update_message.json rename to schema_json/endpoint/streaming/trade/trade_update_message.json index 4ab2ec6a..89107eb8 100644 --- a/schema_json/streaming/trade/trade_update_message.json +++ b/schema_json/endpoint/streaming/trade/trade_update_message.json @@ -6,8 +6,8 @@ "title": "See Streaming.", "properties": { "data": { - "existingJavaType": "net.jacobpeterson.alpaca.model.streaming.trade.TradeUpdate", - "title": "The {@link net.jacobpeterson.alpaca.model.streaming.trade.TradeUpdate}." + "existingJavaType": "net.jacobpeterson.alpaca.model.endpoint.streaming.trade.TradeUpdate", + "title": "The {@link net.jacobpeterson.alpaca.model.endpoint.streaming.trade.TradeUpdate}." } } } diff --git a/schema_json/watchlist/watchlist.json b/schema_json/endpoint/watchlist/watchlist.json similarity index 100% rename from schema_json/watchlist/watchlist.json rename to schema_json/endpoint/watchlist/watchlist.json diff --git a/schema_json/market_data/historical/snapshot/snapshot.json b/schema_json/market_data/historical/snapshot/snapshot.json deleted file mode 100644 index 97661731..00000000 --- a/schema_json/market_data/historical/snapshot/snapshot.json +++ /dev/null @@ -1,26 +0,0 @@ -{ - "type": "object", - "title": "See Historical Data.", - "properties": { - "latestTrade": { - "existingJavaType": "net.jacobpeterson.alpaca.model.marketdata.historical.trade.Trade", - "title": "The latest {@link net.jacobpeterson.alpaca.model.marketdata.historical.trade.Trade}." - }, - "latestQuote": { - "existingJavaType": "net.jacobpeterson.alpaca.model.marketdata.historical.quote.Quote", - "title": "The latest {@link net.jacobpeterson.alpaca.model.marketdata.historical.quote.Quote}." - }, - "minuteBar": { - "existingJavaType": "net.jacobpeterson.alpaca.model.marketdata.historical.bar.Bar", - "title": "The minute {@link net.jacobpeterson.alpaca.model.marketdata.historical.bar.Bar}." - }, - "dailyBar": { - "existingJavaType": "net.jacobpeterson.alpaca.model.marketdata.historical.bar.Bar", - "title": "The daily {@link net.jacobpeterson.alpaca.model.marketdata.historical.bar.Bar}." - }, - "prevDailyBar": { - "existingJavaType": "net.jacobpeterson.alpaca.model.marketdata.historical.bar.Bar", - "title": "The previous daily {@link net.jacobpeterson.alpaca.model.marketdata.historical.bar.Bar}." - } - } -} diff --git a/schema_json/properties/data_api_type.json b/schema_json/properties/data_api_type.json new file mode 100644 index 00000000..3a782d77 --- /dev/null +++ b/schema_json/properties/data_api_type.json @@ -0,0 +1,9 @@ +{ + "type": "string", + "javaName": "DataAPIType", + "title": "Defines the data APIs to use with Alpaca.", + "enum": [ + "iex", + "sip" + ] +} diff --git a/schema_json/properties/endpoint_api_type.json b/schema_json/properties/endpoint_api_type.json new file mode 100644 index 00000000..c0eacd4c --- /dev/null +++ b/schema_json/properties/endpoint_api_type.json @@ -0,0 +1,9 @@ +{ + "type": "string", + "javaName": "EndpointAPIType", + "title": "Defines the API endpoints to use with Alpaca.", + "enum": [ + "live", + "paper" + ] +} diff --git a/schema_json/streaming/account/account_update_message.json b/schema_json/streaming/account/account_update_message.json deleted file mode 100644 index 410c5d9a..00000000 --- a/schema_json/streaming/account/account_update_message.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "type": "object", - "extends": { - "existingJavaType": "net.jacobpeterson.alpaca.model.streaming.AlpacaStreamMessage" - }, - "title": "See Streaming.", - "properties": { - "data": { - "existingJavaType": "net.jacobpeterson.alpaca.model.streaming.account.AccountUpdate", - "title": "The {@link net.jacobpeterson.alpaca.model.streaming.account.AccountUpdate}." - } - } -} diff --git a/src/main/java/net/jacobpeterson/alpaca/AlpacaAPI.java b/src/main/java/net/jacobpeterson/alpaca/AlpacaAPI.java deleted file mode 100644 index 4f0f146a..00000000 --- a/src/main/java/net/jacobpeterson/alpaca/AlpacaAPI.java +++ /dev/null @@ -1,1871 +0,0 @@ -package net.jacobpeterson.alpaca; - -import com.google.common.base.Preconditions; -import com.google.gson.JsonArray; -import com.google.gson.JsonElement; -import com.google.gson.JsonObject; -import com.google.gson.JsonPrimitive; -import com.google.gson.reflect.TypeToken; -import net.jacobpeterson.alpaca.abstracts.websocket.client.WebsocketStateListener; -import net.jacobpeterson.alpaca.abstracts.websocket.exception.WebsocketException; -import net.jacobpeterson.alpaca.model.account.Account; -import net.jacobpeterson.alpaca.model.accountactivities.AccountActivity; -import net.jacobpeterson.alpaca.model.accountactivities.NonTradeActivity; -import net.jacobpeterson.alpaca.model.accountactivities.TradeActivity; -import net.jacobpeterson.alpaca.model.accountactivities.enums.ActivityType; -import net.jacobpeterson.alpaca.model.accountconfiguration.AccountConfiguration; -import net.jacobpeterson.alpaca.model.asset.Asset; -import net.jacobpeterson.alpaca.model.asset.enums.AssetStatus; -import net.jacobpeterson.alpaca.model.marketdata.historical.bar.Bar; -import net.jacobpeterson.alpaca.model.marketdata.historical.bar.BarsResponse; -import net.jacobpeterson.alpaca.model.marketdata.historical.quote.LatestQuoteResponse; -import net.jacobpeterson.alpaca.model.marketdata.historical.quote.Quote; -import net.jacobpeterson.alpaca.model.marketdata.historical.quote.QuotesResponse; -import net.jacobpeterson.alpaca.model.marketdata.historical.trade.LatestTradeResponse; -import net.jacobpeterson.alpaca.model.marketdata.historical.trade.Trade; -import net.jacobpeterson.alpaca.model.marketdata.historical.trade.TradesResponse; -import net.jacobpeterson.alpaca.model.order.CancelledOrder; -import net.jacobpeterson.alpaca.model.order.Order; -import net.jacobpeterson.alpaca.model.order.enums.OrderClass; -import net.jacobpeterson.alpaca.model.order.enums.OrderSide; -import net.jacobpeterson.alpaca.model.order.enums.OrderStatus; -import net.jacobpeterson.alpaca.model.order.enums.OrderTimeInForce; -import net.jacobpeterson.alpaca.model.order.enums.OrderType; -import net.jacobpeterson.alpaca.model.portfoliohistory.PortfolioHistory; -import net.jacobpeterson.alpaca.model.portfoliohistory.enums.PortfolioPeriodUnit; -import net.jacobpeterson.alpaca.model.portfoliohistory.enums.PortfolioTimeFrame; -import net.jacobpeterson.alpaca.model.position.ClosePositionOrder; -import net.jacobpeterson.alpaca.model.watchlist.Watchlist; -import net.jacobpeterson.alpaca.properties.AlpacaProperties; -import net.jacobpeterson.alpaca.properties.enums.DataAPIType; -import net.jacobpeterson.alpaca.properties.enums.EndpointAPIType; -import net.jacobpeterson.alpaca.rest.AlpacaRequest; -import net.jacobpeterson.alpaca.rest.AlpacaRequestBuilder; -import net.jacobpeterson.alpaca.rest.exception.AlpacaAPIRequestException; -import net.jacobpeterson.alpaca.util.format.FormatUtil; -import net.jacobpeterson.alpaca.util.gson.GsonUtil; -import net.jacobpeterson.alpaca.websocket.broker.client.AlpacaWebsocketClient; -import net.jacobpeterson.alpaca.websocket.broker.listener.AlpacaStreamListener; -import net.jacobpeterson.alpaca.websocket.marketdata.client.MarketDataWebsocketClient; -import net.jacobpeterson.alpaca.websocket.marketdata.listener.MarketDataListener; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.io.InputStream; -import java.time.LocalDate; -import java.time.ZonedDateTime; -import java.time.format.DateTimeFormatter; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.StringJoiner; -import java.util.stream.Collectors; - -import static com.google.common.base.Preconditions.checkNotNull; - -/** - * {@link AlpacaAPI} contains several methods to interface with Alpaca. You will generally only need one instance of it - * in your application. Note that many methods allow null to be passed in as a parameter if it is - * optional. - *
- * Note that most of these methods are blocking methods and this class in not thread-safe. - * - * @see Alpaca API documentation - */ -public class AlpacaAPI { - - /** - * Defines URLs constants. - */ - static class URLs { - - public static final String DATA = "https://data.alpaca.markets"; - } - - /** - * Defines endpoint constants. - */ - static class Endpoints { - - public static final String VERSION_2 = "v2"; - public static final String STOCKS = "stocks"; - public static final String CLOCK = "clock"; - public static final String CALENDAR = "calendar"; - public static final String ASSETS = "assets"; - public static final String POSITIONS = "positions"; - public static final String ACCOUNT = "account"; - public static final String ACTIVITIES = "activities"; - public static final String CONFIGURATIONS = "configurations"; - public static final String ORDERS_BY_CLIENT_ORDER_ID = "orders:by_client_order_id"; - public static final String ORDERS = "orders"; - public static final String WATCHLISTS = "watchlists"; - public static final String PORTFOLIO = "portfolio"; - public static final String HISTORY = "history"; - public static final String TRADES = "trades"; - public static final String QUOTES = "quotes"; - public static final String BARS = "bars"; - public static final String SNAPSHOT = "snapshot"; - public static final String SNAPSHOTS = "snapshots"; - public static final String LATEST = "latest"; - } - - /** - * Defines parameters constants. - */ - static class Parameters { - - public static final String SYMBOLS = "symbols"; - public static final String END = "end"; - public static final String START = "start"; - public static final String DATE = "date"; - public static final String ASSET_CLASS = "asset_class"; - public static final String CLIENT_ORDER_ID = "client_order_id"; - public static final String ORDER_CLASS = "order_class"; - public static final String TAKE_PROFIT = "take_profit"; - public static final String STOP_LOSS = "stop_loss"; - public static final String STOP_PRICE = "stop_price"; - public static final String TRAIL_PRICE = "trail_price"; - public static final String TRAIL_PERCENT = "trail_percent"; - public static final String TRAIL = "trail"; - public static final String LIMIT_PRICE = "limit_price"; - public static final String TIME_IN_FORCE = "time_in_force"; - public static final String EXTENDED_HOURS = "extended_hours"; - public static final String TYPE = "type"; - public static final String SIDE = "side"; - public static final String QTY = "qty"; - public static final String NOTIONAL = "notional"; - public static final String SYMBOL = "symbol"; - public static final String DIRECTION = "direction"; - public static final String NESTED = "nested"; - public static final String UNTIL = "until"; - public static final String AFTER = "after"; - public static final String LIMIT = "limit"; - public static final String ACTIVITY_TYPES = "activity_types"; - public static final String STATUS = "status"; - public static final String PERIOD = "period"; - public static final String TIMEFRAME = "timeframe"; - public static final String DATE_END = "date_end"; - public static final String PAGE_SIZE = "page_size"; - public static final String PAGE_TOKEN = "page_token"; - } - - /** - * Defines field constants. - */ - static class Fields { - - public static final String ACTIVITY_TYPE = "activity_type"; - } - - private static final Logger LOGGER = LoggerFactory.getLogger(AlpacaAPI.class); - - private final String keyID; - private final String apiURL; - private final AlpacaRequest alpacaRequest; - private final AlpacaWebsocketClient alpacaWebSocketClient; - private final MarketDataWebsocketClient marketDataWebSocketClient; - - /** - * Instantiates a new {@link AlpacaAPI} using properties specified in alpaca.properties file (or their - * associated defaults). - */ - public AlpacaAPI() { - this(AlpacaProperties.KEY_ID_VALUE, - AlpacaProperties.SECRET_VALUE, - EndpointAPIType.fromPropertyName(AlpacaProperties.ENDPOINT_API_TYPE_VALUE), - DataAPIType.fromPropertyName(AlpacaProperties.DATA_API_TYPE_VALUE)); - } - - /** - * Instantiates a new {@link AlpacaAPI}. - * - * @param keyID the key ID - * @param secret the secret - */ - public AlpacaAPI(String keyID, String secret) { - this(keyID, secret, null, - EndpointAPIType.fromPropertyName(AlpacaProperties.ENDPOINT_API_TYPE_VALUE), - DataAPIType.fromPropertyName(AlpacaProperties.DATA_API_TYPE_VALUE)); - } - - /** - * Instantiates a new {@link AlpacaAPI}. - * - * @param keyID the key ID - * @param secret the secret - * @param endpointAPIType the {@link EndpointAPIType} - * @param dataAPIType the {@link DataAPIType} - */ - public AlpacaAPI(String keyID, String secret, EndpointAPIType endpointAPIType, DataAPIType dataAPIType) { - this(keyID, secret, null, endpointAPIType, dataAPIType); - } - - /** - * Instantiates a new {@link AlpacaAPI}. - * - * @param oAuthToken the OAuth token - */ - public AlpacaAPI(String oAuthToken) { - this(null, null, oAuthToken, - EndpointAPIType.fromPropertyName(AlpacaProperties.ENDPOINT_API_TYPE_VALUE), - DataAPIType.fromPropertyName(AlpacaProperties.DATA_API_TYPE_VALUE)); - } - - /** - * Instantiates a new {@link AlpacaAPI}. - * - * @param keyID the key ID - * @param secret the secret - * @param oAuthToken the OAuth token - * @param endpointAPIType the {@link EndpointAPIType} - * @param dataAPIType the {@link DataAPIType} - */ - public AlpacaAPI(String keyID, String secret, String oAuthToken, EndpointAPIType endpointAPIType, - DataAPIType dataAPIType) { - Preconditions.checkState((keyID != null && secret != null) || oAuthToken != null, - "You must specify a (KeyID and Secret) or an OAuthToken!"); - checkNotNull(endpointAPIType); - checkNotNull(dataAPIType); - - this.keyID = keyID; - this.apiURL = endpointAPIType.getURL(); - - boolean isOAuth = oAuthToken != null; - - alpacaRequest = isOAuth ? new AlpacaRequest(oAuthToken) : - new AlpacaRequest(keyID, secret); - alpacaWebSocketClient = isOAuth ? new AlpacaWebsocketClient(oAuthToken, endpointAPIType) : - new AlpacaWebsocketClient(keyID, secret, endpointAPIType); - marketDataWebSocketClient = isOAuth ? new MarketDataWebsocketClient(oAuthToken, dataAPIType) : - new MarketDataWebsocketClient(keyID, secret, dataAPIType); - - LOGGER.debug("{}", this); - } - - /** - * Returns the {@link Account} associated with the API key. - * - * @return the {@link Account} - * - * @throws AlpacaAPIRequestException thrown for {@link AlpacaAPIRequestException}s - * @see Get the Account - */ - public Account getAccount() throws AlpacaAPIRequestException { - AlpacaRequestBuilder urlBuilder = new AlpacaRequestBuilder(apiURL, Endpoints.VERSION_2, - Endpoints.ACCOUNT); - - return alpacaRequest.get(urlBuilder, Account.class); - } - - /** - * Returns {@link AccountActivity} entries for many or for a specific type of activity. - * - * @param date The date for which you want to see activities. - * @param until The response will contain only activities submitted before this date. (Cannot be used with - * date.) - * @param after The response will contain only activities submitted after this date. (Cannot be used with - * date.) - * @param sortDirection asc or desc (default desc if unspecified.) - * @param pageSize The maximum number of entries to return in the response. (See the section on paging.) - * @param pageToken The ID of the end of your current page of results. (See the section on paging.) - * @param activityTypes the activity types (null for all activities) - * - * @return the {@link AccountActivity} {@link List} - * - * @throws AlpacaAPIRequestException thrown for {@link AlpacaAPIRequestException}s - * @see Gets the Account - * Activities - */ - public List getAccountActivities(ZonedDateTime date, ZonedDateTime until, ZonedDateTime after, - SortDirection sortDirection, Integer pageSize, String pageToken, ActivityType... activityTypes) - throws AlpacaAPIRequestException { - AlpacaRequestBuilder urlBuilder = new AlpacaRequestBuilder(apiURL, Endpoints.VERSION_2, - Endpoints.ACCOUNT, - Endpoints.ACTIVITIES); - - if (activityTypes != null) { // Check if we don't want all activity types - if (activityTypes.length == 1) { // Get one activity - urlBuilder.appendEndpoint(activityTypes[0].getAPIName()); - } else { // Get list of activities - urlBuilder.appendURLParameter(Parameters.ACTIVITY_TYPES, Arrays.stream(activityTypes) - .map(ActivityType::getAPIName).collect(Collectors.joining(","))); // Makes comma-separated list - } - } - - if (date != null) { - urlBuilder.appendURLParameter(Parameters.DATE, - date.format(DateTimeFormatter.ISO_OFFSET_DATE_TIME)); - } - - if (until != null) { - urlBuilder.appendURLParameter(Parameters.UNTIL, - until.format(DateTimeFormatter.ISO_OFFSET_DATE_TIME)); - } - - if (after != null) { - urlBuilder.appendURLParameter(Parameters.AFTER, - after.format(DateTimeFormatter.ISO_OFFSET_DATE_TIME)); - } - - if (sortDirection != null) { - urlBuilder.appendURLParameter(Parameters.DIRECTION, sortDirection.getAPIName()); - } - - if (pageSize != null) { - urlBuilder.appendURLParameter(Parameters.PAGE_SIZE, pageSize.toString()); - } - - if (pageToken != null) { - urlBuilder.appendURLParameter(Parameters.PAGE_TOKEN, pageToken); - } - - HttpResponse response; - try { - response = alpacaRequest.invokeGet(urlBuilder); - } catch (UnirestException exception) { - throw new AlpacaAPIRequestException(exception); - } - - if (response.getStatus() != 200) { - throw new AlpacaAPIRequestException(response); - } - - JsonElement responseJsonElement = alpacaRequest.getResponseJSON(response); - ArrayList accountActivities = new ArrayList<>(); - - if (responseJsonElement instanceof JsonArray) { - JsonArray responseJsonArray = (JsonArray) responseJsonElement; - - for (JsonElement arrayJsonElement : responseJsonArray) { // Loop through response array - if (arrayJsonElement instanceof JsonObject) { - JsonObject arrayJsonObject = arrayJsonElement.getAsJsonObject(); - - if (arrayJsonObject.has(ACTIVITY_TYPE)) { - String activityType = arrayJsonObject.get(ACTIVITY_TYPE).getAsString(); - AccountActivity accountActivity; - - // A 'TradeActivity' always has 'activity_type' field as 'FILL' - if (activityType.equals(ActivityType.FILL.getAPIName())) { - accountActivity = GsonUtil.GSON.fromJson(arrayJsonObject, TradeActivity.class); - } else { - accountActivity = GsonUtil.GSON.fromJson(arrayJsonObject, NonTradeActivity.class); - } - - if (accountActivity.getActivityType() == null) { - LOGGER.error("No Activity Type enum exists for {}\nReport this!", activityType); - } - - accountActivities.add(accountActivity); - } else { - LOGGER.error("JSON Object does not have 'activity_type' in response! {}", arrayJsonObject); - } - } else { - throw new IllegalStateException("All array elements must be objects!"); - } - } - return accountActivities; - } else { - throw new IllegalStateException("The response must be an array!"); - } - } - - /** - * Returns the current {@link AccountConfiguration} values. - * - * @return the {@link AccountConfiguration} - * - * @throws AlpacaAPIRequestException thrown for {@link AlpacaAPIRequestException}s - * @see Account - * Configuration - */ - public AccountConfiguration getAccountConfiguration() throws AlpacaAPIRequestException { - AlpacaRequestBuilder urlBuilder = new AlpacaRequestBuilder(apiURL, Endpoints.VERSION_2, - Endpoints.ACCOUNT, - Endpoints.CONFIGURATIONS); - - return alpacaRequest.get(urlBuilder, AccountConfiguration.class); - } - - /** - * Sets {@link AccountConfiguration}. - * - * @param accountConfiguration the account configuration - * - * @return the updated {@link AccountConfiguration} - * - * @throws AlpacaAPIRequestException thrown for {@link AlpacaAPIRequestException}s - * @see Account - * Configuration - */ - public AccountConfiguration setAccountConfiguration(AccountConfiguration accountConfiguration) - throws AlpacaAPIRequestException { - checkNotNull(accountConfiguration); - - AlpacaRequestBuilder urlBuilder = new AlpacaRequestBuilder(apiURL, Endpoints.VERSION_2, - Endpoints.ACCOUNT, - Endpoints.CONFIGURATIONS); - urlBuilder.setCustomBody(GsonUtil.GSON.toJson(accountConfiguration)); - - return alpacaRequest.patch(urlBuilder, AccountConfiguration.class); - } - - /** - * Retrieves a list of orders for the account, filtered by the supplied query parameters. - * - * @param status Order status to be queried. open, closed or all. Defaults to open. - * @param limit The maximum number of orders in response. Defaults to 50 and max is 500. - * @param after The response will include only ones submitted after this timestamp (exclusive) - * @param until The response will include only ones submitted until this timestamp (exclusive) - * @param sortDirection The chronological order of response based on the submission time. asc or desc. Defaults to - * desc. - * @param nested If true, the result will roll up multi-leg orders under the legs field of primary order. - * @param symbols A list of symbols to filter the result by (null for no filter). - * - * @return the orders - * - * @throws AlpacaAPIRequestException thrown for {@link AlpacaAPIRequestException}s - * @see Orders - */ - public List getOrders(OrderStatus status, Integer limit, ZonedDateTime after, ZonedDateTime until, - SortDirection sortDirection, Boolean nested, List symbols) throws AlpacaAPIRequestException { - AlpacaRequestBuilder urlBuilder = new AlpacaRequestBuilder(apiURL, Endpoints.VERSION_2, - Endpoints.ORDERS); - - if (status != null) { - urlBuilder.appendURLParameter(Parameters.STATUS, status.getAPIName()); - } - - if (limit != null) { - urlBuilder.appendURLParameter(Parameters.LIMIT, limit.toString()); - } - - if (after != null) { - urlBuilder.appendURLParameter(Parameters.AFTER, - after.format(DateTimeFormatter.ISO_OFFSET_DATE_TIME)); - } - - if (until != null) { - urlBuilder.appendURLParameter(Parameters.UNTIL, - until.format(DateTimeFormatter.ISO_OFFSET_DATE_TIME)); - } - - if (sortDirection != null) { - urlBuilder.appendURLParameter(Parameters.DIRECTION, sortDirection.getAPIName()); - } - - if (nested != null) { - urlBuilder.appendURLParameter(Parameters.NESTED, nested.toString()); - } - - if (symbols != null && !symbols.isEmpty()) { - urlBuilder.appendURLParameter(Parameters.SYMBOLS, String.join(",", symbols)); - } - - return alpacaRequest.get(urlBuilder, new TypeToken>() {}.getType()); - } - - /** - * Places a new order for the given account. An order request may be rejected if the account is not authorized for - * trading, or if the tradable balance is insufficient to fill the order. Note: many of the parameters for this - * method can be set to null if they aren't required for your order type. - * - * @param symbol symbol or asset ID to identify the asset to trade - * @param quantity number of shares to trade. Can be fractional. - * @param notional dollar amount to trade. Cannot work with qty. Can only work for market order types - * and day for time in force. - * @param side buy or sell - * @param type market, limit, stop, or stop_limit - * @param timeInForce day, gtc, opg, cls, ioc, fok. Please see Understand Orders for more info. - * @param limitPrice required if type is limit or stop_limit - * @param stopPrice required if type is stop or stop_limit - * @param trailPrice this or trail_percent is required if type is trailing_stop - * @param trailPercent this or trail_price is required if type is trailing_stop - * @param extendedHours (default) false. If true, order will be eligible to execute in premarket/afterhours. - * Only works with type limit and time_in_force day. - * @param clientOrderId A unique identifier for the order. Automatically generated if not sent. - * @param orderClass Simple, bracket, oco, or oto. For details of non-simple order classes, please see - * Bracket Order Overview on the Alpaca Docs. - * @param takeProfitLimitPrice Additional parameter for take-profit leg of advanced orders. Required for bracket - * orders. - * @param stopLossStopPrice Additional parameters for stop-loss leg of advanced orders. Required for bracket - * orders. - * @param stopLossLimitPrice Additional parameters for stop-loss leg of advanced orders. The stop-loss order - * becomes a stop-limit order if specified. - * - * @return the order - * - * @throws AlpacaAPIRequestException thrown for {@link AlpacaAPIRequestException}s - * @see Orders - */ - public Order requestNewOrder(String symbol, Double quantity, Double notional, OrderSide side, OrderType type, - OrderTimeInForce timeInForce, Double limitPrice, Double stopPrice, Double trailPrice, Double trailPercent, - Boolean extendedHours, String clientOrderId, OrderClass orderClass, Double takeProfitLimitPrice, - Double stopLossStopPrice, Double stopLossLimitPrice) - throws AlpacaAPIRequestException { - checkNotNull(symbol); - Preconditions.checkState(quantity != null ^ notional != null, "Either 'quantity' or 'notional' are required."); - checkNotNull(side); - checkNotNull(type); - checkNotNull(timeInForce); - - AlpacaRequestBuilder urlBuilder = new AlpacaRequestBuilder(apiURL, Endpoints.VERSION_2, Endpoints.ORDERS); - - urlBuilder.appendJSONBodyProperty(Parameters.SYMBOL, symbol); - urlBuilder.appendJSONBodyProperty(Parameters.SIDE, side.getAPIName()); - urlBuilder.appendJSONBodyProperty(Parameters.TYPE, type.getAPIName()); - urlBuilder.appendJSONBodyProperty(Parameters.TIME_IN_FORCE, timeInForce.getAPIName()); - - if (quantity != null) { - urlBuilder.appendJSONBodyProperty(Parameters.QTY, quantity.toString()); - } else { - urlBuilder.appendJSONBodyProperty(Parameters.NOTIONAL, notional.toString()); - } - - if (limitPrice != null) { - urlBuilder.appendJSONBodyProperty(Parameters.LIMIT_PRICE, - FormatUtil.toCurrencyFormat(limitPrice)); - } - - if (stopPrice != null) { - urlBuilder.appendJSONBodyProperty(Parameters.STOP_PRICE, - FormatUtil.toCurrencyFormat(stopPrice)); - } - - if (trailPrice != null) { - urlBuilder.appendJSONBodyProperty(Parameters.TRAIL_PRICE, - FormatUtil.toCurrencyFormat(trailPrice)); - } - - if (trailPercent != null) { - urlBuilder.appendJSONBodyProperty(Parameters.TRAIL_PERCENT, - FormatUtil.toCurrencyFormat(trailPercent)); - } - - if (extendedHours != null) { - urlBuilder.appendJSONBodyJSONProperty(Parameters.EXTENDED_HOURS, - new JsonPrimitive(extendedHours)); - } - - if (clientOrderId != null) { - urlBuilder.appendJSONBodyProperty(Parameters.CLIENT_ORDER_ID, clientOrderId); - } - - if (orderClass != null) { - urlBuilder.appendJSONBodyProperty(Parameters.ORDER_CLASS, orderClass.getAPIName()); - } - - if (takeProfitLimitPrice != null) { - JsonObject takeProfit = new JsonObject(); - takeProfit.addProperty(Parameters.LIMIT_PRICE, takeProfitLimitPrice); - - urlBuilder.appendJSONBodyJSONProperty(Parameters.TAKE_PROFIT, takeProfit); - } - - if (stopLossStopPrice != null || stopLossLimitPrice != null) { - JsonObject stopLoss = new JsonObject(); - - if (stopLossStopPrice != null) { - stopLoss.addProperty(Parameters.STOP_PRICE, stopLossStopPrice); - } - if (stopLossLimitPrice != null) { - stopLoss.addProperty(Parameters.LIMIT_PRICE, stopLossLimitPrice); - } - - urlBuilder.appendJSONBodyJSONProperty(Parameters.STOP_LOSS, stopLoss); - } - - return alpacaRequest.post(urlBuilder, Order.class); - } - - /** - * A market order is a request to buy or sell a security at the currently available market price. It provides the - * most likely method of filling an order. Market orders fill nearly instantaneously. This method calls {@link - * #requestNewOrder(String, Double, Double, OrderSide, OrderType, OrderTimeInForce, Double, Double, Double, Double, - * Boolean, String, OrderClass, Double, Double, Double)} with {@link OrderType#MARKET}. - * - * @param symbol symbol or asset ID to identify the asset to trade - * @param quantity number of shares to trade - * @param side buy or sell - * @param timeInForce day, gtc, opg, cls, ioc, fok. Please see Understand Orders for more info. - * - * @return the order - * - * @throws AlpacaAPIRequestException thrown for {@link AlpacaAPIRequestException}s - * @see Order Types - */ - public Order requestNewMarketOrder(String symbol, Integer quantity, OrderSide side, OrderTimeInForce timeInForce) - throws AlpacaAPIRequestException { - return requestNewOrder(symbol, quantity.doubleValue(), null, side, OrderType.MARKET, timeInForce, null, null, - null, null, null, null, OrderClass.SIMPLE, null, null, null); - } - - /** - * A market order is a request to buy or sell a security at the currently available market price. It provides the - * most likely method of filling an order. Market orders fill nearly instantaneously. This method calls {@link - * #requestNewOrder(String, Double, Double, OrderSide, OrderType, OrderTimeInForce, Double, Double, Double, Double, - * Boolean, String, OrderClass, Double, Double, Double)} with {@link OrderType#MARKET} and {@link - * OrderTimeInForce#DAY} with a fractional quantity. - * - * @param symbol symbol or asset ID to identify the asset to trade - * @param quantity number of shares to trade. Can be fractional. - * @param side buy or sell - * - * @return the order - * - * @throws AlpacaAPIRequestException thrown for {@link AlpacaAPIRequestException}s - * @see Order Types - */ - public Order requestNewFractionalMarketOrder(String symbol, Double quantity, OrderSide side) - throws AlpacaAPIRequestException { - return requestNewOrder(symbol, quantity, null, side, OrderType.MARKET, OrderTimeInForce.DAY, null, null, null, - null, null, null, null, null, null, null); - } - - /** - * A market order is a request to buy or sell a security at the currently available market price. It provides the - * most likely method of filling an order. Market orders fill nearly instantaneously. This method calls {@link - * #requestNewOrder(String, Double, Double, OrderSide, OrderType, OrderTimeInForce, Double, Double, Double, Double, - * Boolean, String, OrderClass, Double, Double, Double)} with {@link OrderType#MARKET} and {@link - * OrderTimeInForce#DAY} with a notional dollar amount. - * - * @param symbol symbol or asset ID to identify the asset to trade - * @param notional dollar amount to trade - * @param side buy or sell - * - * @return the order - * - * @throws AlpacaAPIRequestException thrown for {@link AlpacaAPIRequestException}s - * @see Order Types - */ - public Order requestNewNotionalMarketOrder(String symbol, Double notional, OrderSide side) - throws AlpacaAPIRequestException { - return requestNewOrder(symbol, null, notional, side, OrderType.MARKET, OrderTimeInForce.DAY, null, null, null, - null, null, null, null, null, null, null); - } - - /** - * A limit order is an order to buy or sell at a specified price or better. A buy limit order (a limit order to buy) - * is executed at the specified limit price or lower (i.e., better). Conversely, a sell limit order (a limit order - * to sell) is executed at the specified limit price or higher (better). Unlike a market order, you have to specify - * the limit price parameter when submitting your order. This method calls {@link #requestNewOrder(String, Double, - * Double, OrderSide, OrderType, OrderTimeInForce, Double, Double, Double, Double, Boolean, String, OrderClass, - * Double, Double, Double)} with {@link OrderType#LIMIT}. - * - * @param symbol symbol or asset ID to identify the asset to trade - * @param quantity number of shares to trade - * @param side buy or sell - * @param timeInForce day, gtc, opg, cls, ioc, fok. Please see Understand Orders for more info. - * @param limitPrice required if type is limit or stop_limit - * @param extendedHours (default) false. If true, order will be eligible to execute in premarket/afterhours. Only - * works with type limit and time_in_force day. - * - * @return the order - * - * @throws AlpacaAPIRequestException thrown for {@link AlpacaAPIRequestException}s - * @see Order Types - */ - public Order requestNewLimitOrder(String symbol, Integer quantity, OrderSide side, OrderTimeInForce timeInForce, - Double limitPrice, Boolean extendedHours) throws AlpacaAPIRequestException { - return requestNewOrder(symbol, quantity.doubleValue(), null, side, OrderType.LIMIT, timeInForce, limitPrice, - null, null, null, extendedHours, null, OrderClass.SIMPLE, null, null, null); - } - - /** - * A stop (market) order is an order to buy or sell a security when its price moves past a particular point, - * ensuring a higher probability of achieving a predetermined entry or exit price. Once the market price crosses the - * specified stop price, the stop order becomes a market order. Alpaca converts buy stop orders into stop limit - * orders with a limit price that is 4% higher than a stop price < $50 (or 2.5% higher than a stop price >= - * $50). Sell stop orders are not converted into stop limit orders. This method calls {@link - * #requestNewOrder(String, Double, Double, OrderSide, OrderType, OrderTimeInForce, Double, Double, Double, Double, - * Boolean, String, OrderClass, Double, Double, Double)} with {@link OrderType#STOP}. - * - * @param symbol symbol or asset ID to identify the asset to trade - * @param quantity number of shares to trade - * @param side buy or sell - * @param timeInForce day, gtc, opg, cls, ioc, fok. Please see Understand Orders for more info. - * @param stopPrice required if type is stop or stop_limit - * @param extendedHours (default) false. If true, order will be eligible to execute in premarket/afterhours. Only - * works with type limit and time_in_force day. - * - * @return the order - * - * @throws AlpacaAPIRequestException thrown for {@link AlpacaAPIRequestException}s - * @see Order Types - */ - public Order requestNewStopOrder(String symbol, Integer quantity, OrderSide side, OrderTimeInForce timeInForce, - Double stopPrice, Boolean extendedHours) throws AlpacaAPIRequestException { - return requestNewOrder(symbol, quantity.doubleValue(), null, side, OrderType.STOP, timeInForce, null, stopPrice, - null, null, extendedHours, null, OrderClass.SIMPLE, null, null, null); - } - - /** - * A stop-limit order is a conditional trade over a set time frame that combines the features of a stop order with - * those of a limit order and is used to mitigate risk. The stop-limit order will be executed at a specified limit - * price, or better, after a given stop price has been reached. Once the stop price is reached, the stop-limit order - * becomes a limit order to buy or sell at the limit price or better. This method calls {@link - * #requestNewOrder(String, Double, Double, OrderSide, OrderType, OrderTimeInForce, Double, Double, Double, Double, - * Boolean, String, OrderClass, Double, Double, Double)} with {@link OrderType#STOP_LIMIT}. - * - * @param symbol symbol or asset ID to identify the asset to trade - * @param quantity number of shares to trade - * @param side buy or sell - * @param timeInForce day, gtc, opg, cls, ioc, fok. Please see Understand Orders for more info. - * @param limitPrice required if type is limit or stop_limit - * @param stopPrice required if type is stop or stop_limit - * @param extendedHours (default) false. If true, order will be eligible to execute in premarket/afterhours. Only - * works with type limit and time_in_force day. - * - * @return the order - * - * @throws AlpacaAPIRequestException thrown for {@link AlpacaAPIRequestException}s - * @see Order Types - */ - public Order requestNewStopLimitOrder(String symbol, Integer quantity, OrderSide side, OrderTimeInForce timeInForce, - Double limitPrice, Double stopPrice, Boolean extendedHours) throws AlpacaAPIRequestException { - return requestNewOrder(symbol, quantity.doubleValue(), null, side, OrderType.STOP_LIMIT, timeInForce, - limitPrice, stopPrice, null, null, extendedHours, null, OrderClass.SIMPLE, null, null, null); - } - - /** - * A bracket order is a chain of three orders that can be used to manage your position entry and exit. It is a - * common use case of an OTOCO (One Triggers OCO {One Cancels Other}) order. This method calls {@link - * #requestNewOrder(String, Double, Double, OrderSide, OrderType, OrderTimeInForce, Double, Double, Double, Double, - * Boolean, String, OrderClass, Double, Double, Double)} with {@link OrderType#MARKET} and with parameters for a - * bracket order. - * - * @param symbol symbol or asset ID to identify the asset to trade - * @param quantity number of shares to trade - * @param side buy or sell - * @param timeInForce day, gtc, opg, cls, ioc, fok. Please see Understand Orders for more info. - * @param takeProfitLimitPrice Additional parameter for take-profit leg of advanced orders. Required for bracket - * orders. - * @param stopLossStopPrice Additional parameters for stop-loss leg of advanced orders. Required for bracket - * orders. - * @param stopLossLimitPrice Additional parameters for stop-loss leg of advanced orders. The stop-loss order - * becomes a stop-limit order if specified. - * - * @return the order - * - * @throws AlpacaAPIRequestException thrown for {@link AlpacaAPIRequestException}s - * @see Order Types - */ - public Order requestNewMarketBracketOrder(String symbol, Integer quantity, OrderSide side, - OrderTimeInForce timeInForce, Double takeProfitLimitPrice, Double stopLossStopPrice, - Double stopLossLimitPrice) throws AlpacaAPIRequestException { - return requestNewOrder(symbol, quantity.doubleValue(), null, side, OrderType.MARKET, timeInForce, null, null, - null, null, null, null, OrderClass.BRACKET, takeProfitLimitPrice, stopLossStopPrice, - stopLossLimitPrice); - } - - /** - * A bracket order is a chain of three orders that can be used to manage your position entry and exit. It is a - * common use case of an OTOCO (One Triggers OCO {One Cancels Other}) order. This method calls {@link - * #requestNewOrder(String, Double, Double, OrderSide, OrderType, OrderTimeInForce, Double, Double, Double, Double, - * Boolean, String, OrderClass, Double, Double, Double)} with {@link OrderType#LIMIT} and with parameters for a - * bracket order. - * - * @param symbol symbol or asset ID to identify the asset to trade - * @param quantity number of shares to trade - * @param side buy or sell - * @param timeInForce day, gtc, opg, cls, ioc, fok. Please see Understand Orders for more info. - * @param limitPrice required if type is limit or stop_limit - * @param extendedHours (default) false. If true, order will be eligible to execute in premarket/afterhours. - * Only works with type limit and time_in_force day. - * @param takeProfitLimitPrice Additional parameter for take-profit leg of advanced orders. Required for bracket - * orders. - * @param stopLossStopPrice Additional parameters for stop-loss leg of advanced orders. Required for bracket - * orders. - * @param stopLossLimitPrice Additional parameters for stop-loss leg of advanced orders. The stop-loss order - * becomes a stop-limit order if specified. - * - * @return the order - * - * @throws AlpacaAPIRequestException thrown for {@link AlpacaAPIRequestException}s - * @see Order Types - */ - public Order requestNewLimitBracketOrder(String symbol, Integer quantity, OrderSide side, - OrderTimeInForce timeInForce, Double limitPrice, Boolean extendedHours, Double takeProfitLimitPrice, - Double stopLossStopPrice, Double stopLossLimitPrice) throws AlpacaAPIRequestException { - return requestNewOrder(symbol, quantity.doubleValue(), null, side, OrderType.LIMIT, timeInForce, limitPrice, - null, null, null, extendedHours, null, OrderClass.BRACKET, takeProfitLimitPrice, stopLossStopPrice, - stopLossLimitPrice); - } - - /** - * OCO (One-Cancels-Other) is another type of advanced order type. This is a set of two orders with the same side - * (buy/buy or sell/sell) and currently only exit order is supported. In other words, this is the second part of the - * bracket orders where the entry order is already filled, and you can submit the take-profit and stop-loss in one - * order submission. This method calls {@link #requestNewOrder(String, Double, Double, OrderSide, OrderType, - * OrderTimeInForce, Double, Double, Double, Double, Boolean, String, OrderClass, Double, Double, Double)} with - * parameters for an OCO order type. - * - * @param symbol symbol or asset ID to identify the asset to trade - * @param quantity number of shares to trade - * @param side buy or sell - * @param timeInForce day, gtc, opg, cls, ioc, fok. Please see Understand Orders for more info. - * @param extendedHours (default) false. If true, order will be eligible to execute in premarket/afterhours. - * Only works with type limit and time_in_force day. - * @param takeProfitLimitPrice Additional parameter for take-profit leg of advanced orders. - * @param stopLossStopPrice Additional parameters for stop-loss leg of advanced orders. - * @param stopLossLimitPrice Additional parameters for stop-loss leg of advanced orders. The stop-loss order - * becomes a stop-limit order if specified. - * - * @return the order - * - * @throws AlpacaAPIRequestException thrown for {@link AlpacaAPIRequestException}s - * @see Order Types - */ - public Order requestNewOCOOrder(String symbol, Integer quantity, OrderSide side, - OrderTimeInForce timeInForce, Boolean extendedHours, Double takeProfitLimitPrice, - Double stopLossStopPrice, Double stopLossLimitPrice) throws AlpacaAPIRequestException { - return requestNewOrder(symbol, quantity.doubleValue(), null, side, OrderType.LIMIT, timeInForce, null, null, - null, null, extendedHours, null, OrderClass.OCO, takeProfitLimitPrice, stopLossStopPrice, - stopLossLimitPrice); - } - - /** - * OTO (One-Triggers-Other) is a variant of bracket order. It takes one of the take-profit or stop-loss order in - * addition to the entry order. This method calls {@link #requestNewOrder(String, Double, Double, OrderSide, - * OrderType, OrderTimeInForce, Double, Double, Double, Double, Boolean, String, OrderClass, Double, Double, - * Double)} with {@link OrderType#LIMIT} and with parameters for an OTO order type. - * - * @param symbol symbol or asset ID to identify the asset to trade - * @param quantity number of shares to trade - * @param side buy or sell - * @param timeInForce day, gtc, opg, cls, ioc, fok. Please see Understand Orders for more info. - * @param takeProfitLimitPrice Additional parameter for take-profit leg of advanced orders. - * @param stopLossStopPrice Additional parameters for stop-loss leg of advanced orders. - * @param stopLossLimitPrice Additional parameters for stop-loss leg of advanced orders. The stop-loss order - * becomes a stop-limit order if specified. - * - * @return the order - * - * @throws AlpacaAPIRequestException thrown for {@link AlpacaAPIRequestException}s - * @see Order Types - */ - public Order requestNewOTOMarketOrder(String symbol, Integer quantity, OrderSide side, - OrderTimeInForce timeInForce, Double takeProfitLimitPrice, Double stopLossStopPrice, - Double stopLossLimitPrice) throws AlpacaAPIRequestException { - return requestNewOrder(symbol, quantity.doubleValue(), null, side, OrderType.MARKET, timeInForce, null, null, - null, null, null, null, OrderClass.OTO, takeProfitLimitPrice, stopLossStopPrice, stopLossLimitPrice); - } - - /** - * OTO (One-Triggers-Other) is a variant of bracket order. It takes one of the take-profit or stop-loss order in - * addition to the entry order. This method calls {@link #requestNewOrder(String, Double, Double, OrderSide, - * OrderType, OrderTimeInForce, Double, Double, Double, Double, Boolean, String, OrderClass, Double, Double, - * Double)} with {@link OrderType#LIMIT} and with parameters for an OTO order type. - * - * @param symbol symbol or asset ID to identify the asset to trade - * @param quantity number of shares to trade - * @param side buy or sell - * @param timeInForce day, gtc, opg, cls, ioc, fok. Please see Understand Orders for more info. - * @param limitPrice required if type is limit or stop_limit - * @param extendedHours (default) false. If true, order will be eligible to execute in premarket/afterhours. - * Only works with type limit and time_in_force day. - * @param takeProfitLimitPrice Additional parameter for take-profit leg of advanced orders. - * @param stopLossStopPrice Additional parameters for stop-loss leg of advanced orders. - * @param stopLossLimitPrice Additional parameters for stop-loss leg of advanced orders. The stop-loss order - * becomes a stop-limit order if specified. - * - * @return the order - * - * @throws AlpacaAPIRequestException thrown for {@link AlpacaAPIRequestException}s - * @see Order Types - */ - public Order requestNewOTOLimitOrder(String symbol, Integer quantity, OrderSide side, - OrderTimeInForce timeInForce, Double limitPrice, Boolean extendedHours, Double takeProfitLimitPrice, - Double stopLossStopPrice, Double stopLossLimitPrice) throws AlpacaAPIRequestException { - return requestNewOrder(symbol, quantity.doubleValue(), null, side, OrderType.LIMIT, timeInForce, limitPrice, - null, null, null, extendedHours, null, OrderClass.OTO, takeProfitLimitPrice, stopLossStopPrice, - stopLossLimitPrice); - } - - /** - * OTO (One-Triggers-Other) is a variant of bracket order. It takes one of the take-profit or stop-loss order in - * addition to the entry order. This method calls {@link #requestNewOrder(String, Double, Double, OrderSide, - * OrderType, OrderTimeInForce, Double, Double, Double, Double, Boolean, String, OrderClass, Double, Double, - * Double)} with {@link OrderType#STOP} and with parameters for an OTO order type. - * - * @param symbol symbol or asset ID to identify the asset to trade - * @param quantity number of shares to trade - * @param side buy or sell - * @param timeInForce day, gtc, opg, cls, ioc, fok. Please see Understand Orders for more info. - * @param stopPrice required if type is stop or stop_limit - * @param extendedHours (default) false. If true, order will be eligible to execute in premarket/afterhours. - * Only works with type limit and time_in_force day. - * @param takeProfitLimitPrice Additional parameter for take-profit leg of advanced orders. - * @param stopLossStopPrice Additional parameters for stop-loss leg of advanced orders. - * @param stopLossLimitPrice Additional parameters for stop-loss leg of advanced orders. The stop-loss order - * becomes a stop-limit order if specified. - * - * @return the order - * - * @throws AlpacaAPIRequestException thrown for {@link AlpacaAPIRequestException}s - * @see Order Types - */ - public Order requestNewOTOStopOrder(String symbol, Integer quantity, OrderSide side, - OrderTimeInForce timeInForce, Double stopPrice, Boolean extendedHours, Double takeProfitLimitPrice, - Double stopLossStopPrice, Double stopLossLimitPrice) throws AlpacaAPIRequestException { - return requestNewOrder(symbol, quantity.doubleValue(), null, side, OrderType.STOP, timeInForce, null, stopPrice, - null, null, extendedHours, null, OrderClass.OTO, takeProfitLimitPrice, stopLossStopPrice, - stopLossLimitPrice); - } - - /** - * OTO (One-Triggers-Other) is a variant of bracket order. It takes one of the take-profit or stop-loss order in - * addition to the entry order. This method calls {@link #requestNewOrder(String, Double, Double, OrderSide, - * OrderType, OrderTimeInForce, Double, Double, Double, Double, Boolean, String, OrderClass, Double, Double, - * Double)} with {@link OrderType#STOP_LIMIT} and with parameters for an OTO order type. - * - * @param symbol symbol or asset ID to identify the asset to trade - * @param quantity number of shares to trade - * @param side buy or sell - * @param timeInForce day, gtc, opg, cls, ioc, fok. Please see Understand Orders for more info. - * @param limitPrice required if type is limit or stop_limit - * @param stopPrice required if type is stop or stop_limit - * @param extendedHours (default) false. If true, order will be eligible to execute in premarket/afterhours. - * Only works with type limit and time_in_force day. - * @param takeProfitLimitPrice Additional parameter for take-profit leg of advanced orders. - * @param stopLossStopPrice Additional parameters for stop-loss leg of advanced orders. - * @param stopLossLimitPrice Additional parameters for stop-loss leg of advanced orders. The stop-loss order - * becomes a stop-limit order if specified. - * - * @return the order - * - * @throws AlpacaAPIRequestException thrown for {@link AlpacaAPIRequestException}s - * @see Order Types - */ - public Order requestNewOTOStopLimitOrder(String symbol, Integer quantity, OrderSide side, - OrderTimeInForce timeInForce, Double limitPrice, Double stopPrice, Boolean extendedHours, - Double takeProfitLimitPrice, Double stopLossStopPrice, Double stopLossLimitPrice) - throws AlpacaAPIRequestException { - return requestNewOrder(symbol, quantity.doubleValue(), null, side, OrderType.STOP_LIMIT, timeInForce, - limitPrice, stopPrice, null, null, extendedHours, null, OrderClass.OTO, takeProfitLimitPrice, - stopLossStopPrice, stopLossLimitPrice); - } - - /** - * Trailing stop orders allow you to continuously and automatically keep updating the stop price threshold based on - * the stock price movement. This method calls {@link #requestNewOrder(String, Double, Double, OrderSide, OrderType, - * OrderTimeInForce, Double, Double, Double, Double, Boolean, String, OrderClass, Double, Double, Double)} with - * {@link OrderType#TRAILING_STOP} and with parameters for a Trailing Stop Price order type. - * - * @param symbol symbol or asset ID to identify the asset to trade - * @param quantity number of shares to trade - * @param side buy or sell - * @param timeInForce day, gtc, opg, cls, ioc, fok. Please see Understand Orders for more info. - * @param trailPrice this or trail_percent is required if type is trailing_stop - * @param extendedHours (default) false. If true, order will be eligible to execute in premarket/afterhours. Only - * works with type limit and time_in_force day. - * - * @return the order - * - * @throws AlpacaAPIRequestException thrown for {@link AlpacaAPIRequestException}s - * @see Order Types - */ - public Order requestNewTrailingStopPriceOrder(String symbol, Integer quantity, OrderSide side, - OrderTimeInForce timeInForce, Double trailPrice, Boolean extendedHours) throws AlpacaAPIRequestException { - return requestNewOrder(symbol, quantity.doubleValue(), null, side, OrderType.TRAILING_STOP, timeInForce, null, - null, trailPrice, null, extendedHours, null, null, null, null, null); - } - - /** - * Trailing stop orders allow you to continuously and automatically keep updating the stop price threshold based on - * the stock price movement. This method calls {@link #requestNewOrder(String, Double, Double, OrderSide, OrderType, - * OrderTimeInForce, Double, Double, Double, Double, Boolean, String, OrderClass, Double, Double, Double)} with - * {@link OrderType#TRAILING_STOP} and with parameters for a Trailing Stop Percent order type. - * - * @param symbol symbol or asset ID to identify the asset to trade - * @param quantity number of shares to trade - * @param side buy or sell - * @param timeInForce day, gtc, opg, cls, ioc, fok. Please see Understand Orders for more info. - * @param trailPercent this or trail_price is required if type is trailing_stop - * @param extendedHours (default) false. If true, order will be eligible to execute in premarket/afterhours. Only - * works with type limit and time_in_force day. - * - * @return the order - * - * @throws AlpacaAPIRequestException thrown for {@link AlpacaAPIRequestException}s - * @see Order Types - */ - public Order requestNewTrailingStopPercentOrder(String symbol, Integer quantity, OrderSide side, - OrderTimeInForce timeInForce, Double trailPercent, Boolean extendedHours) throws AlpacaAPIRequestException { - return requestNewOrder(symbol, quantity.doubleValue(), null, side, OrderType.TRAILING_STOP, timeInForce, null, - null, null, trailPercent, extendedHours, null, null, null, null, null); - } - - /** - * Retrieves a single order for the given order_id. - * - * @param orderID Order ID - * @param nested If true, the result will roll up multi-leg orders under the legs field of primary order. - * - * @return the order - * - * @throws AlpacaAPIRequestException thrown for {@link AlpacaAPIRequestException}s - * @see Orders - */ - public Order getOrder(String orderID, Boolean nested) throws AlpacaAPIRequestException { - checkNotNull(orderID); - - AlpacaRequestBuilder urlBuilder = new AlpacaRequestBuilder(apiURL, Endpoints.VERSION_2, - Endpoints.ORDERS, - orderID); - - if (nested != null) { - urlBuilder.appendURLParameter(Parameters.NESTED, nested.toString()); - } - - return alpacaRequest.get(urlBuilder, Order.class); - } - - /** - * Retrieves a single order for the given client_order_id. - * - * @param clientOrderId the client order id - * - * @return the order by client id - * - * @throws AlpacaAPIRequestException thrown for {@link AlpacaAPIRequestException}s - * @see Orders - */ - public Order getOrderByClientID(String clientOrderId) throws AlpacaAPIRequestException { - checkNotNull(clientOrderId); - - AlpacaRequestBuilder urlBuilder = new AlpacaRequestBuilder(apiURL, Endpoints.VERSION_2, - Endpoints.ORDERS_BY_CLIENT_ORDER_ID); - urlBuilder.appendURLParameter(Parameters.CLIENT_ORDER_ID, clientOrderId); - - return alpacaRequest.get(urlBuilder, Order.class); - } - - /** - * Replaces a single order with updated parameters. Each parameter overrides the corresponding attribute of the - * existing order. The other attributes remain the same as the existing order. - * - * @param orderID Order id - * @param quantity number of shares to trade - * @param timeInForce day, gtc, opg, cls, ioc, fok. Please see Understand Orders for more info. - * @param limitPrice required if type is limit or stop_limit - * @param stopPrice required if type is stop or stop_limit - * @param trail the new value of the trail_price or trail_percent value (works only for type=trailing_stop) - * @param clientOrderId A unique identifier for the order. Automatically generated if not sent. - * - * @return the order - * - * @throws AlpacaAPIRequestException thrown for {@link AlpacaAPIRequestException}s - * @see Orders - */ - public Order replaceOrder(String orderID, Integer quantity, OrderTimeInForce timeInForce, Double limitPrice, - Double stopPrice, Double trail, String clientOrderId) throws AlpacaAPIRequestException { - checkNotNull(orderID); - - AlpacaRequestBuilder urlBuilder = new AlpacaRequestBuilder(apiURL, Endpoints.VERSION_2, - Endpoints.ORDERS, - orderID); - - if (quantity != null) { - urlBuilder.appendJSONBodyProperty(Parameters.QTY, quantity.toString()); - } - - if (timeInForce != null) { - urlBuilder.appendJSONBodyProperty(Parameters.TIME_IN_FORCE, timeInForce.getAPIName()); - } - - if (limitPrice != null) { - urlBuilder.appendJSONBodyProperty(Parameters.LIMIT_PRICE, - FormatUtil.toCurrencyFormat(limitPrice)); - } - - if (stopPrice != null) { - urlBuilder.appendJSONBodyProperty(Parameters.STOP_PRICE, - FormatUtil.toCurrencyFormat(stopPrice)); - } - - if (trail != null) { - urlBuilder.appendJSONBodyProperty(Parameters.TRAIL, - FormatUtil.toCurrencyFormat(trail)); - } - - if (clientOrderId != null) { - urlBuilder.appendJSONBodyProperty(Parameters.CLIENT_ORDER_ID, clientOrderId); - } - return alpacaRequest.patch(urlBuilder, Order.class); - } - - /** - * Attempts to cancel all open orders. A response will be provided for each order that is attempted to be cancelled. - * If an order is no longer cancelable, the server will respond with status 500 and reject the request. - * - * @return the array list of cancelled orders - * - * @throws AlpacaAPIRequestException thrown for {@link AlpacaAPIRequestException}s - * @see Orders - */ - public ArrayList cancelAllOrders() throws AlpacaAPIRequestException { - AlpacaRequestBuilder urlBuilder = new AlpacaRequestBuilder(apiURL, Endpoints.VERSION_2, - Endpoints.ORDERS); - - try { - HttpResponse response = alpacaRequest.invokeDelete(urlBuilder); - - if (response.getStatus() != 207) { // This returns a 207 multi-status message - throw new AlpacaAPIRequestException(response); - } - - return alpacaRequest.getResponseObject(response, new TypeToken>() {}.getType()); - } catch (UnirestException exception) { - throw new AlpacaAPIRequestException(exception); - } - } - - /** - * Attempts to cancel an open order. If the order is no longer cancelable (example: status=order_filled), the server - * will respond with status 422, and reject the request. - * - * @param orderId Order ID - * - * @return true, if successful - * - * @throws AlpacaAPIRequestException thrown for {@link AlpacaAPIRequestException}s - * @see Orders - */ - public boolean cancelOrder(String orderId) throws AlpacaAPIRequestException { - checkNotNull(orderId); - - AlpacaRequestBuilder urlBuilder = new AlpacaRequestBuilder(apiURL, Endpoints.VERSION_2, - Endpoints.ORDERS, - orderId); - - try { - HttpResponse response = alpacaRequest.invokeDelete(urlBuilder); - - if (!(response.getStatus() == 200 || response.getStatus() == 204)) { - throw new AlpacaAPIRequestException(response); - } - - return true; - } catch (UnirestException exception) { - throw new AlpacaAPIRequestException(exception); - } - } - - /** - * Gets the open positions. - * - * @return the open positions - * - * @throws AlpacaAPIRequestException thrown for {@link AlpacaAPIRequestException}s - * @see Positions - */ - public ArrayList getOpenPositions() throws AlpacaAPIRequestException { - AlpacaRequestBuilder urlBuilder = new AlpacaRequestBuilder(apiURL, Endpoints.VERSION_2, - Endpoints.POSITIONS); - - return alpacaRequest.get(urlBuilder, new TypeToken>() {}.getType()); - } - - /** - * Gets the open position by symbol. - * - * @param symbol the symbol or asset_id (required) - * - * @return the open position by symbol - * - * @throws AlpacaAPIRequestException thrown for {@link AlpacaAPIRequestException}s - * @see Positions - */ - public Position getOpenPositionBySymbol(String symbol) throws AlpacaAPIRequestException { - checkNotNull(symbol); - - AlpacaRequestBuilder urlBuilder = new AlpacaRequestBuilder(apiURL, Endpoints.VERSION_2, - Endpoints.POSITIONS, - symbol.trim()); - - return alpacaRequest.get(urlBuilder, Position.class); - } - - /** - * Closes (liquidates) all of the account’s open long and short positions. A response will be provided for each - * order that is attempted to be cancelled. If an order is no longer cancelable, the server will respond with status - * 500 and reject the request. - * - * @return a list of closing position orders - * - * @throws AlpacaAPIRequestException thrown for {@link AlpacaAPIRequestException}s - * @see Positions - */ - public ArrayList closeAllPositions() throws AlpacaAPIRequestException { - AlpacaRequestBuilder urlBuilder = new AlpacaRequestBuilder(apiURL, Endpoints.VERSION_2, - Endpoints.POSITIONS); - - return alpacaRequest.delete(urlBuilder, new TypeToken>() {}.getType()); - } - - /** - * Closes (liquidates) the account’s open position for the given symbol. Works for both long and short positions. - * - * @param symbol symbol or asset_id - * - * @return a closing position order - * - * @throws AlpacaAPIRequestException thrown for {@link AlpacaAPIRequestException}s - * @see Positions - */ - public Order closePosition(String symbol) throws AlpacaAPIRequestException { - checkNotNull(symbol); - - AlpacaRequestBuilder urlBuilder = new AlpacaRequestBuilder(apiURL, Endpoints.VERSION_2, - Endpoints.POSITIONS, - symbol); - - return alpacaRequest.delete(urlBuilder, Order.class); - } - - /** - * Get a list of assets. - * - * @param assetStatus e.g. “active”. By default, all statuses are included. - * @param assetClass Defaults to us_equity. - * - * @return the assets - * - * @throws AlpacaAPIRequestException thrown for {@link AlpacaAPIRequestException}s - * @see Assets - */ - public ArrayList getAssets(AssetStatus assetStatus, String assetClass) throws AlpacaAPIRequestException { - AlpacaRequestBuilder urlBuilder = new AlpacaRequestBuilder(apiURL, Endpoints.VERSION_2, - Endpoints.ASSETS); - - if (assetStatus != null) { - urlBuilder.appendURLParameter(Parameters.STATUS, assetStatus.getAPIName()); - } - - if (assetClass != null) { - urlBuilder.appendURLParameter(Parameters.ASSET_CLASS, assetClass.trim()); - } - - return alpacaRequest.get(urlBuilder, new TypeToken>() {}.getType()); - } - - /** - * Gets the asset by symbol or asset_id. - * - * @param symbolOrAssetID the symbol or asset_id (required) - * - * @return the asset by symbol - * - * @throws AlpacaAPIRequestException thrown for {@link AlpacaAPIRequestException}s - * @see Assets - */ - public Asset getAssetBySymbol(String symbolOrAssetID) throws AlpacaAPIRequestException { - checkNotNull(symbolOrAssetID); - - AlpacaRequestBuilder urlBuilder = new AlpacaRequestBuilder(apiURL, Endpoints.VERSION_2, - Endpoints.ASSETS, - symbolOrAssetID.trim()); - - return alpacaRequest.get(urlBuilder, Asset.class); - } - - /** - * Returns the list of watchlists registered under the account. - * - * @return the watch lists - * - * @throws AlpacaAPIRequestException thrown for {@link AlpacaAPIRequestException}s - * @see Watchlists - */ - public ArrayList getWatchlists() throws AlpacaAPIRequestException { - AlpacaRequestBuilder urlBuilder = new AlpacaRequestBuilder(apiURL, Endpoints.VERSION_2, - Endpoints.WATCHLISTS); - - return alpacaRequest.get(urlBuilder, new TypeToken>() {}.getType()); - } - - /** - * Create a new watchlist with initial set of assets. - * - * @param name arbitrary name string, up to 64 characters - * @param symbols set of symbol string - * - * @return the created watchlist - * - * @throws AlpacaAPIRequestException thrown for {@link AlpacaAPIRequestException}s - * @see Watchlists - */ - public Watchlist createWatchlist(String name, String... symbols) throws AlpacaAPIRequestException { - checkNotNull(name); - Preconditions.checkState(name.length() <= 64); - - AlpacaRequestBuilder urlBuilder = new AlpacaRequestBuilder(apiURL, Endpoints.VERSION_2, - Endpoints.WATCHLISTS); - - urlBuilder.appendJSONBodyProperty("name", name); - - if (symbols != null) { - JsonArray symbolsArray = new JsonArray(); - Arrays.stream(symbols).forEach(symbolsArray::add); - - urlBuilder.appendJSONBodyJSONProperty("symbols", symbolsArray); - } - - return alpacaRequest.post(urlBuilder, Watchlist.class); - } - - /** - * Returns a watchlist identified by the ID. - * - * @param watchlistID Watchlist ID - * - * @return the watchlist - * - * @throws AlpacaAPIRequestException thrown for {@link AlpacaAPIRequestException}s - * @see Watchlists - */ - public Watchlist getWatchlist(String watchlistID) throws AlpacaAPIRequestException { - checkNotNull(watchlistID); - - AlpacaRequestBuilder urlBuilder = new AlpacaRequestBuilder(apiURL, Endpoints.VERSION_2, - Endpoints.WATCHLISTS, - watchlistID); - - return alpacaRequest.get(urlBuilder, Watchlist.class); - } - - /** - * Update the name and/or content of watchlist. - * - * @param watchlistID Watchlist ID - * @param name the new watchlist name - * @param symbols the new list of symbol names to replace the watchlist content - * - * @return the updated watchlist - * - * @throws AlpacaAPIRequestException thrown for {@link AlpacaAPIRequestException}s - * @see Watchlists - */ - public Watchlist updateWatchlist(String watchlistID, String name, String... symbols) - throws AlpacaAPIRequestException { - checkNotNull(watchlistID); - - AlpacaRequestBuilder urlBuilder = new AlpacaRequestBuilder(apiURL, Endpoints.VERSION_2, - Endpoints.WATCHLISTS, - watchlistID); - - if (name != null) { - urlBuilder.appendJSONBodyProperty("name", name); - } - - if (symbols != null) { - JsonArray symbolsArray = new JsonArray(); - Arrays.stream(symbols).forEach(symbolsArray::add); - - urlBuilder.appendJSONBodyJSONProperty("symbols", symbolsArray); - } - - return alpacaRequest.put(urlBuilder, Watchlist.class); - } - - /** - * Append an asset for the symbol to the end of watchlist asset list - * - * @param watchlistID Watchlist ID - * @param symbol the symbol name to add to the watchlist - * - * @return the watchlist - * - * @throws AlpacaAPIRequestException thrown for {@link AlpacaAPIRequestException}s - * @see Watchlists - */ - public Watchlist addWatchlistAsset(String watchlistID, String symbol) throws AlpacaAPIRequestException { - checkNotNull(watchlistID); - checkNotNull(symbol); - - AlpacaRequestBuilder urlBuilder = new AlpacaRequestBuilder(apiURL, Endpoints.VERSION_2, - Endpoints.WATCHLISTS, - watchlistID); - - urlBuilder.appendJSONBodyProperty("symbol", symbol); - - return alpacaRequest.post(urlBuilder, Watchlist.class); - } - - /** - * Delete a watchlist. This is a permanent deletion. - * - * @param watchlistID Watchlist ID - * - * @return if the watchlist was deleted - * - * @throws AlpacaAPIRequestException thrown for {@link AlpacaAPIRequestException}s - * @see Watchlists - */ - public boolean deleteWatchlist(String watchlistID) throws AlpacaAPIRequestException { - checkNotNull(watchlistID); - - AlpacaRequestBuilder urlBuilder = new AlpacaRequestBuilder(apiURL, Endpoints.VERSION_2, - Endpoints.WATCHLISTS, - watchlistID); - - try { - HttpResponse response = alpacaRequest.invokeDelete(urlBuilder); - - if (!(response.getStatus() == 200 || response.getStatus() == 204)) { - throw new AlpacaAPIRequestException(response); - } - - return true; - } catch (UnirestException exception) { - throw new AlpacaAPIRequestException(exception); - } - } - - /** - * Delete one entry for an asset by symbol name. - * - * @param watchlistID Watchlist ID - * @param symbol symbol name to remove from the watchlist content - * - * @return the updated watchlist - * - * @throws AlpacaAPIRequestException thrown for {@link AlpacaAPIRequestException}s - * @see Watchlists - */ - public Watchlist removeSymbolFromWatchlist(String watchlistID, String symbol) throws AlpacaAPIRequestException { - checkNotNull(watchlistID); - checkNotNull(symbol); - - AlpacaRequestBuilder urlBuilder = new AlpacaRequestBuilder(apiURL, Endpoints.VERSION_2, - Endpoints.WATCHLISTS, - watchlistID, - symbol); - - return alpacaRequest.delete(urlBuilder, Watchlist.class); - } - - /** - * Returns timeseries data about equity and profit/loss (P/L) of the account in requested timespan. - * - * @param periodLength The duration of the data in "number + unit", such as 1D, where unit can be D for day, W for - * week, M for month and A for year. Defaults to 1M. This parameter is the number. - * @param periodUnit The duration of the data in "number + unit", such as 1D, where unit can be D for day, W for - * week, M for month and A for year. Defaults to 1M. This parameter is the unit. - * @param timeFrame The resolution of time window. 1Min, 5Min, 15Min, 1H, or 1D. If omitted, 1Min for less than - * 7 days period, 15Min for less than 30 days, or otherwise 1D. - * @param dateEnd The date the data is returned up to, in "YYYY-MM-DD" format. Defaults to the current market - * date (rolls over at the market open if extended_hours is false, otherwise at 7am ET) - * @param extendedHours If true, include extended hours in the result. This is effective only for timeframe less - * than 1D. - * - * @return the portfolio history - * - * @throws AlpacaAPIRequestException thrown for {@link AlpacaAPIRequestException}s - */ - public PortfolioHistory getPortfolioHistory(Integer periodLength, PortfolioPeriodUnit periodUnit, - PortfolioTimeFrame timeFrame, LocalDate dateEnd, Boolean extendedHours) throws AlpacaAPIRequestException { - AlpacaRequestBuilder urlBuilder = new AlpacaRequestBuilder(apiURL, Endpoints.VERSION_2, - Endpoints.ACCOUNT, - Endpoints.PORTFOLIO, - Endpoints.HISTORY); - - if (periodLength != null && periodUnit != null) { - urlBuilder.appendURLParameter(Parameters.PERIOD, periodLength + periodUnit.getAPIName()); - } - - if (timeFrame != null) { - urlBuilder.appendURLParameter(Parameters.TIMEFRAME, timeFrame.getAPIName()); - } - - if (dateEnd != null) { - urlBuilder.appendURLParameter(Parameters.DATE_END, dateEnd.format(DateTimeFormatter.ISO_DATE)); - } - - if (extendedHours != null) { - urlBuilder.appendURLParameter(Parameters.EXTENDED_HOURS, extendedHours.toString()); - } - - return alpacaRequest.get(urlBuilder, PortfolioHistory.class); - } - - /** - * Returns the market {@link Calendar} from 1970 to 2029. - * - * @return the {@link Calendar} {@link List} - * - * @throws AlpacaAPIRequestException thrown for {@link AlpacaAPIRequestException}s - * @see Calendar - */ - public List getCalendar() throws AlpacaAPIRequestException { - return getCalendar(null, null); - } - - /** - * Returns the market {@link Calendar}. - * - * @param start the first date to retrieve data for (inclusive) - * @param end the last date to retrieve data for (inclusive) - * - * @return the {@link Calendar} {@link List} - * - * @throws AlpacaAPIRequestException thrown for {@link AlpacaAPIRequestException}s - * @see Calendar - */ - public List getCalendar(LocalDate start, LocalDate end) throws AlpacaAPIRequestException { - AlpacaRequestBuilder urlBuilder = new AlpacaRequestBuilder(apiURL, Endpoints.VERSION_2, Endpoints.CALENDAR); - - if (start != null) { - urlBuilder.appendURLParameter(Parameters.START, start.format(DateTimeFormatter.ISO_DATE)); - } - - if (end != null) { - urlBuilder.appendURLParameter(Parameters.END, end.format(DateTimeFormatter.ISO_DATE)); - } - - return alpacaRequest.get(urlBuilder, new TypeToken>() {}.getType()); - } - - /** - * Returns the market {@link Clock}. - * - * @return the {@link Clock} - * - * @throws AlpacaAPIRequestException thrown for {@link AlpacaAPIRequestException}s - * @see Clock - */ - public Clock getClock() throws AlpacaAPIRequestException { - AlpacaRequestBuilder urlBuilder = new AlpacaRequestBuilder(apiURL, Endpoints.VERSION_2, - Endpoints.CLOCK); - - return alpacaRequest.get(urlBuilder, Clock.class); - } - - /** - * Gets {@link Trade} historical data for the requested security. - * - * @param symbol the symbol to query for - * @param start filter data equal to or after this {@link ZonedDateTime}. Fractions of a second are not - * accepted - * @param end filter data equal to or before this {@link ZonedDateTime}. Fractions of a second are not - * accepted - * @param limit number of data points to return. Must be in range 1-10000, defaults to 1000 if null is given - * @param pageToken pagination token to continue from - * - * @return the {@link TradesResponse} - * - * @throws AlpacaAPIRequestException thrown for {@link AlpacaAPIRequestException}s - * @see - * Historical Market Data - */ - public TradesResponse getTrades(String symbol, ZonedDateTime start, ZonedDateTime end, Integer limit, - String pageToken) throws AlpacaAPIRequestException { - checkNotNull(symbol); - checkNotNull(start); - checkNotNull(end); - - AlpacaRequestBuilder urlBuilder = new AlpacaRequestBuilder(URLs.DATA, Endpoints.VERSION_2, - Endpoints.STOCKS, - symbol, - Endpoints.TRADES); - - urlBuilder.appendURLParameter(Parameters.START, FormatUtil.toRFC3339Format(start)); - urlBuilder.appendURLParameter(Parameters.END, FormatUtil.toRFC3339Format(end)); - - if (limit != null) { - urlBuilder.appendURLParameter(Parameters.LIMIT, limit.toString()); - } - - if (pageToken != null) { - urlBuilder.appendURLParameter(Parameters.PAGE_TOKEN, pageToken); - } - - return alpacaRequest.get(urlBuilder, TradesResponse.class); - } - - /** - * The Latest trade API provides the latest {@link Trade} data for a given ticker symbol. - * - * @param symbol the symbol to query for - * - * @return the {@link LatestTradeResponse} - * - * @throws AlpacaAPIRequestException thrown for {@link AlpacaAPIRequestException}s - * @see - * Historical Market Data - */ - public LatestTradeResponse getLatestTrade(String symbol) throws AlpacaAPIRequestException { - checkNotNull(symbol); - - AlpacaRequestBuilder urlBuilder = new AlpacaRequestBuilder(URLs.DATA, Endpoints.VERSION_2, - Endpoints.STOCKS, - symbol, - Endpoints.TRADES, - Endpoints.LATEST); - - return alpacaRequest.get(urlBuilder, LatestTradeResponse.class); - } - - /** - * Gets {@link Quote} (NBBO or National Best Bid and Offer) historical data for the requested security. - * - * @param symbol the symbol to query for - * @param start filter data equal to or after this {@link ZonedDateTime}. Fractions of a second are not - * accepted - * @param end filter data equal to or before this {@link ZonedDateTime}. Fractions of a second are not - * accepted - * @param limit number of data points to return. Must be in range 1-10000, defaults to 1000 if null is given - * @param pageToken pagination token to continue from - * - * @return the {@link QuotesResponse} - * - * @throws AlpacaAPIRequestException thrown for {@link AlpacaAPIRequestException}s - * @see - * Historical Market Data - */ - public QuotesResponse getQuotes(String symbol, ZonedDateTime start, ZonedDateTime end, Integer limit, - String pageToken) throws AlpacaAPIRequestException { - checkNotNull(symbol); - checkNotNull(start); - checkNotNull(end); - - AlpacaRequestBuilder urlBuilder = new AlpacaRequestBuilder(URLs.DATA, Endpoints.VERSION_2, - Endpoints.STOCKS, - symbol, - Endpoints.QUOTES); - - urlBuilder.appendURLParameter(Parameters.START, FormatUtil.toRFC3339Format(start)); - urlBuilder.appendURLParameter(Parameters.END, FormatUtil.toRFC3339Format(end)); - - if (limit != null) { - urlBuilder.appendURLParameter(Parameters.LIMIT, limit.toString()); - } - - if (pageToken != null) { - urlBuilder.appendURLParameter(Parameters.PAGE_TOKEN, pageToken); - } - - return alpacaRequest.get(urlBuilder, QuotesResponse.class); - } - - /** - * The Latest quote API provides the latest quote data for a given ticker symbol. - * - * @param symbol the symbol to query for - * - * @return the {@link LatestQuoteResponse} - * - * @throws AlpacaAPIRequestException thrown for {@link AlpacaAPIRequestException}s - * @see - * Historical Market Data - */ - public LatestQuoteResponse getLatestQuote(String symbol) throws AlpacaAPIRequestException { - checkNotNull(symbol); - - AlpacaRequestBuilder urlBuilder = new AlpacaRequestBuilder(URLs.DATA, Endpoints.VERSION_2, - Endpoints.STOCKS, - symbol, - Endpoints.QUOTES, - Endpoints.LATEST); - - return alpacaRequest.get(urlBuilder, LatestQuoteResponse.class); - } - - /** - * Gets {@link Bar} aggregate historical data for the requested security. - * - * @param symbol the symbol to query for - * @param start filter data equal to or after this {@link ZonedDateTime}. Fractions of a second are not - * accepted - * @param end filter data equal to or before this {@link ZonedDateTime}. Fractions of a second are not - * accepted - * @param limit number of data points to return. Must be in range 1-10000, defaults to 1000 if null is given - * @param pageToken pagination token to continue from - * @param timeFrame the {@link BarsTimeFrame} for the aggregation - * - * @return the {@link BarsResponse} - * - * @throws AlpacaAPIRequestException thrown for {@link AlpacaAPIRequestException}s - * @see - * Historical Market Data - */ - public BarsResponse getBars(String symbol, ZonedDateTime start, ZonedDateTime end, Integer limit, - String pageToken, BarsTimeFrame timeFrame) throws AlpacaAPIRequestException { - checkNotNull(symbol); - checkNotNull(start); - checkNotNull(end); - checkNotNull(timeFrame); - - AlpacaRequestBuilder urlBuilder = new AlpacaRequestBuilder(URLs.DATA, Endpoints.VERSION_2, - Endpoints.STOCKS, - symbol, - Endpoints.BARS); - - urlBuilder.appendURLParameter(Parameters.START, FormatUtil.toRFC3339Format(start)); - urlBuilder.appendURLParameter(Parameters.END, FormatUtil.toRFC3339Format(end)); - urlBuilder.appendURLParameter(Parameters.TIMEFRAME, timeFrame.getAPIName()); - - if (limit != null) { - urlBuilder.appendURLParameter(Parameters.LIMIT, limit.toString()); - } - - if (pageToken != null) { - urlBuilder.appendURLParameter(Parameters.PAGE_TOKEN, pageToken); - } - - return alpacaRequest.get(urlBuilder, BarsResponse.class); - } - - /** - * The Snapshot API for one ticker provides the latest trade, latest quote, minute bar daily bar and previous daily - * bar data for a given ticker symbol - * - * @param symbols the symbols to query for - * - * @return a {@link Map} with they keys being the symbol and the value being the {@link Snapshot} - * - * @throws AlpacaAPIRequestException thrown for {@link AlpacaAPIRequestException}s - * @see - * Historical Market Data - */ - public Map getSnapshots(List symbols) throws AlpacaAPIRequestException { - checkNotNull(symbols); - Preconditions.checkState(!symbols.isEmpty()); - - AlpacaRequestBuilder urlBuilder = new AlpacaRequestBuilder(URLs.DATA, Endpoints.VERSION_2, - Endpoints.STOCKS, - Endpoints.SNAPSHOTS); - - urlBuilder.appendURLParameter(Parameters.SYMBOLS, String.join(",", symbols)); - - HttpResponse response; - try { - response = alpacaRequest.invokeGet(urlBuilder); - } catch (UnirestException exception) { - throw new AlpacaAPIRequestException(exception); - } - - if (response.getStatus() != 200) { - throw new AlpacaAPIRequestException(response); - } - - JsonElement responseJsonElement = alpacaRequest.getResponseJSON(response); - HashMap snapshotsOfSymbols = new HashMap<>(); - - if (responseJsonElement instanceof JsonObject) { - JsonObject responseJsonObject = (JsonObject) responseJsonElement; - - for (String symbol : responseJsonObject.keySet()) { // Loop through response object keys - JsonElement jsonObjectElement = responseJsonObject.get(symbol); - - if (jsonObjectElement instanceof JsonObject) { - snapshotsOfSymbols.put(symbol, GsonUtil.GSON.fromJson(jsonObjectElement, Snapshot.class)); - } else { - throw new IllegalStateException("The response object must only contain objects!"); - } - } - return snapshotsOfSymbols; - } else { - throw new IllegalStateException("The response must be an object!"); - } - } - - /** - * The Snapshot API for one ticker provides the latest trade, latest quote, minute bar daily bar and previous daily - * bar data for a given ticker symbol - * - * @param symbol the symbol to query for - * - * @return the {@link Snapshot} - * - * @throws AlpacaAPIRequestException thrown for {@link AlpacaAPIRequestException}s - * @see - * Historical Market Data - */ - public Snapshot getSnapshot(String symbol) throws AlpacaAPIRequestException { - checkNotNull(symbol); - - AlpacaRequestBuilder urlBuilder = new AlpacaRequestBuilder(URLs.DATA, Endpoints.VERSION_2, - Endpoints.STOCKS, - symbol, - Endpoints.SNAPSHOT); - - return alpacaRequest.get(urlBuilder, Snapshot.class); - } - - /** - * Adds the {@link AlpacaStreamListener}. Note that when the first {@link AlpacaStreamListener} is added, the - * Websocket connection is created. - * - * @param streamListener the stream listener - * - * @throws WebsocketException thrown for {@link WebsocketException}s - */ - public void addAlpacaStreamListener(AlpacaStreamListener streamListener) throws WebsocketException { - alpacaWebSocketClient.addListener(streamListener); - } - - /** - * Removes the {@link AlpacaStreamListener}. Note that when the last {@link AlpacaStreamListener} is removed, the - * Websocket connection is closed. - * - * @param streamListener the {@link AlpacaStreamListener} - * - * @throws WebsocketException thrown for {@link WebsocketException}s - */ - public void removeAlpacaStreamListener(AlpacaStreamListener streamListener) throws WebsocketException { - alpacaWebSocketClient.removeListener(streamListener); - } - - /** - * Sets the {@link AlpacaWebsocketClient} {@link WebsocketStateListener} to listen to websocket state changes. - * - * @param websocketStateListener the {@link WebsocketStateListener} - */ - public void setAlpacaStreamWebsocketStateListener(WebsocketStateListener websocketStateListener) { - alpacaWebSocketClient.setWebsocketStateListener(websocketStateListener); - } - - /** - * Adds the {@link MarketDataListener}. Note that when the first {@link AlpacaStreamListener} is added, the - * Websocket connection is created. - * - * @param streamListener the {@link MarketDataListener} - * - * @throws WebsocketException thrown for {@link WebsocketException}s - */ - public void addMarketDataStreamListener(MarketDataListener streamListener) throws WebsocketException { - marketDataWebSocketClient.addListener(streamListener); - } - - /** - * Removes the {@link MarketDataListener}. Note that when the last {@link MarketDataListener} is removed, the - * Websocket connection is closed. - * - * @param streamListener the {@link MarketDataListener} - * - * @throws WebsocketException thrown for {@link WebsocketException}s - */ - public void removeMarketDataStreamListener(MarketDataListener streamListener) throws WebsocketException { - marketDataWebSocketClient.removeListener(streamListener); - } - - /** - * Sets the {@link MarketDataWebsocketClient} {@link WebsocketStateListener} to listen to websocket state changes. - * - * @param websocketStateListener the {@link WebsocketStateListener} - */ - public void setMarketDataStreamWebsocketStateListener(WebsocketStateListener websocketStateListener) { - marketDataWebSocketClient.setWebsocketStateListener(websocketStateListener); - } - - @Override - public String toString() { - return new StringJoiner(", ", this.getClass().getSimpleName() + "[", "]") - .add("apiURL = " + apiURL) - .add("keyID = " + keyID) - .toString(); - } -} diff --git a/src/main/java/net/jacobpeterson/alpaca/abstracts/rest/AbstractRequest.java b/src/main/java/net/jacobpeterson/alpaca/abstracts/rest/AbstractRequest.java deleted file mode 100644 index 2b9dcd4b..00000000 --- a/src/main/java/net/jacobpeterson/alpaca/abstracts/rest/AbstractRequest.java +++ /dev/null @@ -1,284 +0,0 @@ -package net.jacobpeterson.alpaca.abstracts.rest; - -import com.google.gson.JsonElement; -import com.google.gson.JsonParser; -import com.google.gson.stream.JsonReader; -import com.mashape.unirest.http.HttpResponse; -import com.mashape.unirest.http.Unirest; -import com.mashape.unirest.http.exceptions.UnirestException; -import com.mashape.unirest.request.GetRequest; -import com.mashape.unirest.request.HttpRequestWithBody; -import net.jacobpeterson.alpaca.util.gson.GsonUtil; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.io.IOException; -import java.io.InputStream; -import java.io.InputStreamReader; -import java.lang.reflect.Type; -import java.util.HashMap; -import java.util.Map; - -/** - * {@link AbstractRequest} contains methods for HTTP requests. - */ -public abstract class AbstractRequest { - - private static final Logger LOGGER = LoggerFactory.getLogger(AbstractRequest.class); - - protected final Map headers; - - /** - * Instantiates a new {@link AbstractRequest}. - */ - public AbstractRequest() { - headers = new HashMap<>(); - } - - /** - * Invoke GET request. - * - * @param abstractRequestBuilder the {@link AbstractRequestBuilder} - * - * @return the {@link HttpResponse} - * - * @throws UnirestException thrown for {@link UnirestException}s - */ - public HttpResponse invokeGet(AbstractRequestBuilder abstractRequestBuilder) throws UnirestException { - String url = abstractRequestBuilder.buildURL(); - - LOGGER.debug("GET URL {}", url); - - GetRequest request = Unirest.get(url); - - if (!headers.isEmpty()) { - request.headers(headers); - - LOGGER.debug("GET Headers: {}", headers); - } - - return request.asBinary(); - } - - /** - * Invoke HEAD request. - * - * @param abstractRequestBuilder the {@link AbstractRequestBuilder} - * - * @return the {@link HttpResponse} - * - * @throws UnirestException thrown for {@link UnirestException}s - */ - public HttpResponse invokeHead(AbstractRequestBuilder abstractRequestBuilder) throws UnirestException { - String url = abstractRequestBuilder.buildURL(); - - LOGGER.debug("HEAD URL {}", url); - - GetRequest request = Unirest.head(url); - - if (!headers.isEmpty()) { - request.headers(headers); - - LOGGER.debug("HEAD Headers: {}", headers); - } - - return request.asBinary(); - } - - /** - * Invoke POST request. - * - * @param abstractRequestBuilder the {@link AbstractRequestBuilder} - * - * @return the {@link HttpResponse} - * - * @throws UnirestException thrown for {@link UnirestException}s - */ - public HttpResponse invokePost(AbstractRequestBuilder abstractRequestBuilder) throws UnirestException { - String url = abstractRequestBuilder.buildURL(); - - LOGGER.debug("POST URL: {}", url); - - HttpRequestWithBody request = Unirest.post(url); - - if (!headers.isEmpty()) { - request.headers(headers); - - LOGGER.debug("POST Headers: {}", headers); - } - - String body = abstractRequestBuilder.buildBody(); - if (body != null) { - request.body(body); - - LOGGER.debug("POST Body: {}", body); - } - - return request.asBinary(); - } - - /** - * Invoke PATCH request. - * - * @param abstractRequestBuilder the {@link AbstractRequestBuilder} - * - * @return the {@link HttpResponse} - * - * @throws UnirestException thrown for {@link UnirestException}s - */ - public HttpResponse invokePatch(AbstractRequestBuilder abstractRequestBuilder) - throws UnirestException { - String url = abstractRequestBuilder.buildURL(); - - LOGGER.debug("PATCH URL {}", url); - - HttpRequestWithBody request = Unirest.patch(url); - - if (!headers.isEmpty()) { - request.headers(headers); - - LOGGER.debug("PATCH Headers: {}", headers); - } - - String body = abstractRequestBuilder.buildBody(); - if (body != null) { - request.body(body); - - LOGGER.debug("PATCH Body: {}", body); - } - - return request.asBinary(); - } - - /** - * Invoke PUT request. - * - * @param abstractRequestBuilder the {@link AbstractRequestBuilder} - * - * @return the {@link HttpResponse} - * - * @throws UnirestException thrown for {@link UnirestException}s - */ - public HttpResponse invokePut(AbstractRequestBuilder abstractRequestBuilder) throws UnirestException { - String url = abstractRequestBuilder.buildURL(); - - LOGGER.debug("PUT URL {}", url); - - HttpRequestWithBody request = Unirest.put(url); - - if (!headers.isEmpty()) { - request.headers(headers); - - LOGGER.debug("PUT Headers: {}", headers); - } - - String body = abstractRequestBuilder.buildBody(); - if (body != null) { - request.body(body); - - LOGGER.debug("PUT Body: " + body); - } - - return request.asBinary(); - } - - /** - * Invoke DELETE request. - * - * @param abstractRequestBuilder the {@link AbstractRequestBuilder} - * - * @return the {@link HttpResponse} - * - * @throws UnirestException thrown for {@link UnirestException}s - */ - public HttpResponse invokeDelete(AbstractRequestBuilder abstractRequestBuilder) - throws UnirestException { - String url = abstractRequestBuilder.buildURL(); - - LOGGER.debug("DELETE URL {}", url); - - HttpRequestWithBody request = Unirest.delete(url); - - if (!headers.isEmpty()) { - request.headers(headers); - - LOGGER.debug("DELETE Headers: {}", headers); - } - - String body = abstractRequestBuilder.buildBody(); - if (body != null) { - request.body(body); - - LOGGER.debug("DELETE Body: {}", body); - } - - return request.asBinary(); - } - - /** - * Invoke OPTIONS request. - * - * @param abstractRequestBuilder the {@link AbstractRequestBuilder} - * - * @return the {@link HttpResponse} - * - * @throws UnirestException thrown for {@link UnirestException}s - */ - public HttpResponse invokeOptions(AbstractRequestBuilder abstractRequestBuilder) - throws UnirestException { - String url = abstractRequestBuilder.buildURL(); - - LOGGER.debug("OPTIONS URL {}", url); - - HttpRequestWithBody request = Unirest.options(url); - - if (!headers.isEmpty()) { - request.headers(headers); - - LOGGER.debug("OPTIONS Headers: {}", headers); - } - - String body = abstractRequestBuilder.buildBody(); - if (body != null) { - request.body(body); - - LOGGER.debug("OPTIONS Body: {}", body); - } - - return request.asBinary(); - } - - /** - * Gets a parsed Object with {@link GsonUtil#GSON} given {@link HttpResponse#getRawBody()} JSON. - * - * @param the generic type - * @param httpResponse the {@link HttpResponse} - * @param type the type of object - * - * @return the {@link HttpResponse#getRawBody()} parsed Object - */ - public T getResponseObject(HttpResponse httpResponse, Type type) { - try (JsonReader jsonReader = new JsonReader(new InputStreamReader(httpResponse.getRawBody()))) { - return GsonUtil.GSON.fromJson(jsonReader, type); - } catch (IOException ioException) { - LOGGER.error("Could not parse response JSON object", ioException); - return null; - } - } - - /** - * Gets a {@link JsonElement} with {@link GsonUtil#GSON} given {@link HttpResponse#getRawBody()} JSON. - * - * @param httpResponse the {@link HttpResponse} - * - * @return the {@link HttpResponse#getRawBody()} {@link JsonElement} - */ - public JsonElement getResponseJSON(HttpResponse httpResponse) { - try (JsonReader jsonReader = new JsonReader(new InputStreamReader(httpResponse.getRawBody()))) { - return JsonParser.parseReader(jsonReader); - } catch (IOException ioException) { - LOGGER.error("Could not parse response JSON object", ioException); - return null; - } - } -} diff --git a/src/main/java/net/jacobpeterson/alpaca/abstracts/rest/AbstractRequestBuilder.java b/src/main/java/net/jacobpeterson/alpaca/abstracts/rest/AbstractRequestBuilder.java deleted file mode 100644 index e829ea32..00000000 --- a/src/main/java/net/jacobpeterson/alpaca/abstracts/rest/AbstractRequestBuilder.java +++ /dev/null @@ -1,129 +0,0 @@ -package net.jacobpeterson.alpaca.abstracts.rest; - -import com.google.gson.JsonElement; -import com.google.gson.JsonObject; - -import java.util.ArrayList; -import java.util.LinkedHashMap; -import java.util.List; -import java.util.Map; - -/** - * {@link AbstractRequestBuilder} is a builder for URL Strings. - */ -public abstract class AbstractRequestBuilder { - - private static final String URL_SEPARATOR = "/"; - - private final String baseURL; - private final List appendedEndpoints; - private final Map urlParameters; - private final JsonObject bodyJsonObject; - - private String customBody; - - /** - * Instantiates a new {@link AbstractRequestBuilder}. - * - * @param baseURL the base URL - */ - public AbstractRequestBuilder(String baseURL) { - this.baseURL = baseURL; - - appendedEndpoints = new ArrayList<>(); - urlParameters = new LinkedHashMap<>(); - bodyJsonObject = new JsonObject(); - } - - /** - * Appends an endpoint. - * - * @param endpoint the endpoint - */ - public void appendEndpoint(String endpoint) { - appendedEndpoints.add(endpoint); - } - - /** - * Appends a URL parameter. - * - * @param urlKey the URL key - * @param urlValue the URL value - */ - public void appendURLParameter(String urlKey, String urlValue) { - urlParameters.put(urlKey, urlValue); - } - - /** - * Appends a JSON string property to {@link #buildBody()}. - * - * @param bodyKey the body key - * @param bodyValue the body value - */ - public void appendJSONBodyProperty(String bodyKey, String bodyValue) { - bodyJsonObject.addProperty(bodyKey, bodyValue); - } - - /** - * Appends a {@link JsonElement} property to {@link #buildBody()}. - * - * @param bodyKey the body key - * @param bodyValue the {@link JsonElement} body value - */ - public void appendJSONBodyJSONProperty(String bodyKey, JsonElement bodyValue) { - bodyJsonObject.add(bodyKey, bodyValue); - } - - /** - * Builds the URL string {@link AbstractRequestBuilder}. - * - * @return the built URL string - */ - public String buildURL() { - StringBuilder builder = new StringBuilder(baseURL); - - for (String appendedEndpoint : appendedEndpoints) { - builder.append(URL_SEPARATOR).append(appendedEndpoint); - } - - if (!urlParameters.isEmpty()) { - builder.append('?'); - - for (Map.Entry entry : urlParameters.entrySet()) { - builder.append(entry.getKey().trim()); - builder.append('='); - builder.append(entry.getValue().trim()); - builder.append('&'); - } - - // removes last '&' - builder.deleteCharAt(builder.length() - 1); - } - - return builder.toString(); - } - - /** - * Builds the body string of this {@link AbstractRequestBuilder}. - * - * @return the body string - */ - public String buildBody() { - if (customBody != null) { - return customBody; - } else if (bodyJsonObject.size() > 0) { - return bodyJsonObject.toString(); - } else { - return null; - } - } - - /** - * Sets a custom body string for {@link #buildBody()}. - * - * @param customBody the custom body string - */ - public void setCustomBody(String customBody) { - this.customBody = customBody; - } -} diff --git a/src/main/java/net/jacobpeterson/alpaca/abstracts/rest/exception/AbstractAPIRequestException.java b/src/main/java/net/jacobpeterson/alpaca/abstracts/rest/exception/AbstractAPIRequestException.java deleted file mode 100644 index 73580dfe..00000000 --- a/src/main/java/net/jacobpeterson/alpaca/abstracts/rest/exception/AbstractAPIRequestException.java +++ /dev/null @@ -1,109 +0,0 @@ -package net.jacobpeterson.alpaca.abstracts.rest.exception; - -import com.mashape.unirest.http.HttpResponse; - -import java.io.InputStream; - -/** - * {@link AbstractAPIRequestException} represents {@link HttpResponse} request exceptions. - */ -public abstract class AbstractAPIRequestException extends Exception { - - protected static final String CODE_KEY = "code"; - protected static final String MESSAGE_KEY = "message"; - - protected String apiName; - protected HttpResponse httpResponse; - protected Integer requestStatusCode; - protected String requestStatusText; - protected Integer apiResponseCode; - protected String apiResponseMessage; - - /** - * Instantiates a new {@link AbstractAPIRequestException}. - * - * @param apiName the api name - * @param httpResponse the http response - */ - public AbstractAPIRequestException(String apiName, HttpResponse httpResponse) { - super(); - - this.apiName = apiName; - this.httpResponse = httpResponse; - - this.requestStatusCode = httpResponse.getStatus(); - this.requestStatusText = httpResponse.getStatusText(); - } - - /** - * Parse the API exception message to populate the following fields: - *

- * {@link AbstractAPIRequestException#requestStatusCode}, {@link AbstractAPIRequestException#requestStatusText}, - * {@link AbstractAPIRequestException#apiResponseCode}, {@link AbstractAPIRequestException#apiResponseMessage} - */ - protected abstract void parseAPIExceptionMessage(); - - /** - * {@inheritDoc} - *
- * This will call {@link #parseAPIExceptionMessage()} and return a formatted message. - */ - @Override - public String getMessage() { - if (getCause() == null) { - this.parseAPIExceptionMessage(); - - StringBuilder messageBuilder = new StringBuilder(apiName).append(" API Request Exception! : "); - messageBuilder.append("Status Code = ").append(requestStatusCode); - messageBuilder.append(", Status Text = \"").append(requestStatusText).append("\""); - - if (apiResponseCode != null) { - messageBuilder.append(", API Response Code = ").append(apiResponseCode); - } - - if (apiResponseMessage != null) { - messageBuilder.append(", API Response Message = \"").append(apiResponseMessage).append("\""); - } - - return messageBuilder.toString(); - } else { - return super.getMessage(); - } - } - - /** - * Gets request status code. - * - * @return the request status code - */ - public Integer getRequestStatusCode() { - return requestStatusCode; - } - - /** - * Gets request status text. - * - * @return the request status text - */ - public String getRequestStatusText() { - return requestStatusText; - } - - /** - * Gets API response code. - * - * @return the API response code - */ - public Integer getAPIResponseCode() { - return apiResponseCode; - } - - /** - * Gets API response message. - * - * @return the API response message - */ - public String getAPIResponseMessage() { - return apiResponseMessage; - } -} diff --git a/src/main/java/net/jacobpeterson/alpaca/properties/AlpacaProperties.java b/src/main/java/net/jacobpeterson/alpaca/properties/AlpacaProperties.java deleted file mode 100644 index 71025ccf..00000000 --- a/src/main/java/net/jacobpeterson/alpaca/properties/AlpacaProperties.java +++ /dev/null @@ -1,39 +0,0 @@ -package net.jacobpeterson.alpaca.properties; - -import net.jacobpeterson.alpaca.AlpacaAPI; - -import static net.jacobpeterson.alpaca.util.properties.PropertyUtil.getProperty; - -/** - * {@link AlpacaProperties} defines properties for {@link AlpacaAPI}. - */ -public class AlpacaProperties { - - private static final String ALPACA_PROPERTIES_FILE = "alpaca.properties"; - private static final String ALPACA_DEFAULT_PROPERTIES_FILE = "alpaca.default.properties"; - - private static final String KEY_ID_KEY = "key_id"; - /** The value of {@link #KEY_ID_KEY} in {@link #ALPACA_PROPERTIES_FILE}. */ - public static final String KEY_ID_VALUE = getProperty(ALPACA_PROPERTIES_FILE, ALPACA_DEFAULT_PROPERTIES_FILE, - KEY_ID_KEY); - - private static final String SECRET_KEY = "secret"; - /** The value of {@link #SECRET_KEY} in {@link #ALPACA_PROPERTIES_FILE}. */ - public static final String SECRET_VALUE = getProperty(ALPACA_PROPERTIES_FILE, ALPACA_DEFAULT_PROPERTIES_FILE, - SECRET_KEY); - - private static final String ENDPOINT_API_TYPE_KEY = "endpoint_api_type"; - /** The value of {@link #ENDPOINT_API_TYPE_KEY} in {@link #ALPACA_PROPERTIES_FILE}. */ - public static final String ENDPOINT_API_TYPE_VALUE = getProperty(ALPACA_PROPERTIES_FILE, - ALPACA_DEFAULT_PROPERTIES_FILE, ENDPOINT_API_TYPE_KEY); - - private static final String DATA_API_TYPE_KEY = "data_api_type"; - /** The value of {@link #DATA_API_TYPE_KEY} in {@link #ALPACA_PROPERTIES_FILE}. */ - public static final String DATA_API_TYPE_VALUE = getProperty(ALPACA_PROPERTIES_FILE, - ALPACA_DEFAULT_PROPERTIES_FILE, DATA_API_TYPE_KEY); - - private static final String USER_AGENT_KEY = "user_agent"; - /** The value of {@link #USER_AGENT_KEY} in {@link #ALPACA_PROPERTIES_FILE}. */ - public static final String USER_AGENT_VALUE = getProperty(ALPACA_PROPERTIES_FILE, ALPACA_DEFAULT_PROPERTIES_FILE, - USER_AGENT_KEY); -} diff --git a/src/main/java/net/jacobpeterson/alpaca/properties/enums/DataAPIType.java b/src/main/java/net/jacobpeterson/alpaca/properties/enums/DataAPIType.java deleted file mode 100644 index 1c0801c8..00000000 --- a/src/main/java/net/jacobpeterson/alpaca/properties/enums/DataAPIType.java +++ /dev/null @@ -1,62 +0,0 @@ -package net.jacobpeterson.alpaca.properties.enums; - -/** - * {@link DataAPIType} defines the types of Data APIs to use with Alpaca. - */ -public enum DataAPIType { - - /** The IEX {@link DataAPIType}. */ - IEX("wss://stream.data.alpaca.markets/v2/iex", "iex"), - - /** The SIP {@link DataAPIType}. */ - SIP("wss://stream.data.alpaca.markets/v2/sip", "sip"); - - private final String url; - private final String propertyName; - - /** - * Instantiates a new {@link DataAPIType}. - * - * @param url the URL - * @param propertyName the property name - */ - DataAPIType(String url, String propertyName) { - this.url = url; - this.propertyName = propertyName; - } - - /** - * Gets a {@link DataAPIType} from its {@link DataAPIType#getPropertyName()}. - * - * @param propertyName the property name - * - * @return the {@link DataAPIType} - */ - public static DataAPIType fromPropertyName(String propertyName) { - for (DataAPIType dataAPIType : values()) { - if (dataAPIType.getPropertyName().equals(propertyName)) { - return dataAPIType; - } - } - - return null; - } - - /** - * Gets {@link #url}. - * - * @return a {@link String} - */ - public String getURL() { - return url; - } - - /** - * Gets {@link #propertyName}. - * - * @return a {@link String} - */ - public String getPropertyName() { - return propertyName; - } -} diff --git a/src/main/java/net/jacobpeterson/alpaca/properties/enums/EndpointAPIType.java b/src/main/java/net/jacobpeterson/alpaca/properties/enums/EndpointAPIType.java deleted file mode 100644 index 911661fa..00000000 --- a/src/main/java/net/jacobpeterson/alpaca/properties/enums/EndpointAPIType.java +++ /dev/null @@ -1,62 +0,0 @@ -package net.jacobpeterson.alpaca.properties.enums; - -/** - * {@link EndpointAPIType} defines the types of API endpoint to use with Alpaca. - */ -public enum EndpointAPIType { - - /** The live {@link EndpointAPIType}. */ - LIVE("https://api.alpaca.markets", "live"), - - /** The paper {@link EndpointAPIType}. */ - PAPER("https://paper-api.alpaca.markets", "paper"); - - private final String url; - private final String propertyName; - - /** - * Instantiates a new {@link EndpointAPIType}. - * - * @param url the URL - * @param propertyName the property name - */ - EndpointAPIType(String url, String propertyName) { - this.url = url; - this.propertyName = propertyName; - } - - /** - * Gets an {@link EndpointAPIType} from its {@link EndpointAPIType#getPropertyName()}. - * - * @param propertyName the property name - * - * @return the {@link EndpointAPIType} - */ - public static EndpointAPIType fromPropertyName(String propertyName) { - for (EndpointAPIType endpointAPIType : values()) { - if (endpointAPIType.getPropertyName().equals(propertyName)) { - return endpointAPIType; - } - } - - return null; - } - - /** - * Gets {@link #url}. - * - * @return a {@link String} - */ - public String getURL() { - return url; - } - - /** - * Gets {@link #propertyName}. - * - * @return a {@link String} - */ - public String getPropertyName() { - return propertyName; - } -} diff --git a/src/main/java/net/jacobpeterson/alpaca/refactor/AlpacaAPI.java b/src/main/java/net/jacobpeterson/alpaca/refactor/AlpacaAPI.java new file mode 100644 index 00000000..486ae674 --- /dev/null +++ b/src/main/java/net/jacobpeterson/alpaca/refactor/AlpacaAPI.java @@ -0,0 +1,292 @@ +package net.jacobpeterson.alpaca.refactor; + +import net.jacobpeterson.alpaca.model.properties.DataAPIType; +import net.jacobpeterson.alpaca.model.properties.EndpointAPIType; +import net.jacobpeterson.alpaca.refactor.properties.AlpacaProperties; +import net.jacobpeterson.alpaca.refactor.rest.AlpacaClient; +import net.jacobpeterson.alpaca.refactor.rest.endpoint.*; + +import static com.google.common.base.Preconditions.checkNotNull; + +/** + * {@link AlpacaAPI} contains several instances of various {@link AbstractEndpoint}s that enable you to interface with + * Alpaca. You will generally only need one instance of this class in your application. Note that many methods inside + * the various {@link AbstractEndpoint}s allow null to be passed in as a parameter if it is optional. + * + * @see Alpaca API Documentation + */ +public class AlpacaAPI { + + private static final String VERSION_2_PATH_SEGMENT = "v2"; + + private final AlpacaClient brokerClient; + private final AlpacaClient dataClient; + // Ordering of fields/methods below are analogous to the ordering in the Alpaca documentation + private final AccountEndpoint accountEndpoint; + private final MarketDataEndpoint marketDataEndpoint; + private final OrdersEndpoint ordersEndpoint; + private final PositionsEndpoint positionsEndpoint; + private final AssetsEndpoint assetsEndpoint; + private final WatchlistEndpoint watchlistEndpoint; + private final CalendarEndpoint calendarEndpoint; + private final ClockEndpoint clockEndpoint; + private final AccountConfigurationEndpoint accountConfigurationEndpoint; + private final AccountActivitiesEndpoint accountActivitiesEndpoint; + private final PortfolioHistoryEndpoint portfolioHistoryEndpoint; + + /** + * Instantiates a new {@link AlpacaAPI} using properties specified in alpaca.properties file (or their + * associated defaults). + */ + public AlpacaAPI() { + this(AlpacaProperties.KEY_ID, + AlpacaProperties.SECRET_KEY, + AlpacaProperties.ENDPOINT_API_TYPE, + AlpacaProperties.DATA_API_TYPE); + } + + /** + * Instantiates a new {@link AlpacaAPI}. + * + * @param keyID the key ID + * @param secret the secret + */ + public AlpacaAPI(String keyID, String secret) { + this(keyID, secret, null, + AlpacaProperties.ENDPOINT_API_TYPE, + AlpacaProperties.DATA_API_TYPE); + } + + /** + * Instantiates a new {@link AlpacaAPI}. + * + * @param keyID the key ID + * @param secret the secret + * @param endpointAPIType the {@link EndpointAPIType} + * @param dataAPIType the {@link DataAPIType} + */ + public AlpacaAPI(String keyID, String secret, EndpointAPIType endpointAPIType, DataAPIType dataAPIType) { + this(keyID, secret, null, endpointAPIType, dataAPIType); + } + + /** + * Instantiates a new {@link AlpacaAPI}. + * + * @param oAuthToken the OAuth token. Note that the Data API v2 does not work with OAuth tokens. + */ + public AlpacaAPI(String oAuthToken) { + this(null, null, oAuthToken, + AlpacaProperties.ENDPOINT_API_TYPE, + AlpacaProperties.DATA_API_TYPE); + } + + /** + * Instantiates a new {@link AlpacaAPI}. + * + * @param keyID the key ID + * @param secretKey the secret key + * @param oAuthToken the OAuth token + * @param endpointAPIType the {@link EndpointAPIType} + * @param dataAPIType the {@link DataAPIType} + */ + public AlpacaAPI(String keyID, String secretKey, String oAuthToken, EndpointAPIType endpointAPIType, + DataAPIType dataAPIType) { + checkNotNull(endpointAPIType); + checkNotNull(dataAPIType); + + String brokerHostSubdomain; + switch (endpointAPIType) { + case LIVE: + brokerHostSubdomain = "live"; + break; + case PAPER: + brokerHostSubdomain = "paper-api"; + break; + default: + throw new UnsupportedOperationException(); + } + + if (oAuthToken == null) { + brokerClient = new AlpacaClient(keyID, secretKey, brokerHostSubdomain, VERSION_2_PATH_SEGMENT); + dataClient = new AlpacaClient(keyID, secretKey, "data", VERSION_2_PATH_SEGMENT); + } else { + brokerClient = new AlpacaClient(oAuthToken, brokerHostSubdomain, VERSION_2_PATH_SEGMENT); + dataClient = null; + } + + accountEndpoint = new AccountEndpoint(brokerClient); + marketDataEndpoint = dataClient == null ? null : new MarketDataEndpoint(dataClient); + ordersEndpoint = new OrdersEndpoint(brokerClient); + positionsEndpoint = new PositionsEndpoint(brokerClient); + assetsEndpoint = new AssetsEndpoint(brokerClient); + watchlistEndpoint = new WatchlistEndpoint(brokerClient); + calendarEndpoint = new CalendarEndpoint(brokerClient); + clockEndpoint = new ClockEndpoint(brokerClient); + accountConfigurationEndpoint = new AccountConfigurationEndpoint(brokerClient); + accountActivitiesEndpoint = new AccountActivitiesEndpoint(brokerClient); + portfolioHistoryEndpoint = new PortfolioHistoryEndpoint(brokerClient); + } + + /** + * @return {@link AccountEndpoint} + */ + public AccountEndpoint account() { + return accountEndpoint; + } + + /** + * @return the {@link MarketDataEndpoint} + */ + public MarketDataEndpoint marketData() { + return marketDataEndpoint; + } + + /** + * @return {@link OrdersEndpoint} + */ + public OrdersEndpoint orders() { + return ordersEndpoint; + } + + /** + * @return {@link PositionsEndpoint} + */ + public PositionsEndpoint positions() { + return positionsEndpoint; + } + + /** + * @return {@link AssetsEndpoint} + */ + public AssetsEndpoint assets() { + return assetsEndpoint; + } + + /** + * @return {@link WatchlistEndpoint} + */ + public WatchlistEndpoint watchlist() { + return watchlistEndpoint; + } + + /** + * @return {@link CalendarEndpoint} + */ + public CalendarEndpoint calendar() { + return calendarEndpoint; + } + + /** + * @return {@link ClockEndpoint} + */ + public ClockEndpoint clock() { + return clockEndpoint; + } + + /** + * @return the {@link AccountConfigurationEndpoint} + */ + public AccountConfigurationEndpoint accountConfiguration() { + return accountConfigurationEndpoint; + } + + /** + * @return the {@link AccountActivitiesEndpoint} + */ + public AccountActivitiesEndpoint accountActivities() { + return accountActivitiesEndpoint; + } + + /** + * @return the {@link PortfolioHistoryEndpoint} + */ + public PortfolioHistoryEndpoint portfolioHistory() { + return portfolioHistoryEndpoint; + } + + // TODO + // /** + // * Adds the {@link AlpacaStreamListener}. Note that when the first {@link AlpacaStreamListener} is added, the + // * Websocket connection is created. + // * + // * @param streamListener the stream listener + // * + // * @throws WebsocketException thrown for {@link WebsocketException}s + // */ + // public void addAlpacaStreamListener(AlpacaStreamListener streamListener) throws WebsocketException { + // alpacaWebSocketClient.addListener(streamListener); + // } + // + // /** + // * Removes the {@link AlpacaStreamListener}. Note that when the last {@link AlpacaStreamListener} is removed, the + // * Websocket connection is closed. + // * + // * @param streamListener the {@link AlpacaStreamListener} + // * + // * @throws WebsocketException thrown for {@link WebsocketException}s + // */ + // public void removeAlpacaStreamListener(AlpacaStreamListener streamListener) throws WebsocketException { + // alpacaWebSocketClient.removeListener(streamListener); + // } + // + // /** + // * Sets the {@link AlpacaWebsocketClient} {@link WebsocketStateListener} to listen to websocket state changes. + // * + // * @param websocketStateListener the {@link WebsocketStateListener} + // */ + // public void setAlpacaStreamWebsocketStateListener(WebsocketStateListener websocketStateListener) { + // alpacaWebSocketClient.setWebsocketStateListener(websocketStateListener); + // } + // + // /** + // * Adds the {@link MarketDataListener}. Note that when the first {@link AlpacaStreamListener} is added, the + // * Websocket connection is created. + // * + // * @param streamListener the {@link MarketDataListener} + // * + // * @throws WebsocketException thrown for {@link WebsocketException}s + // */ + // public void addMarketDataStreamListener(MarketDataListener streamListener) throws WebsocketException { + // marketDataWebSocketClient.addListener(streamListener); + // } + // + // /** + // * Removes the {@link MarketDataListener}. Note that when the last {@link MarketDataListener} is removed, the + // * Websocket connection is closed. + // * + // * @param streamListener the {@link MarketDataListener} + // * + // * @throws WebsocketException thrown for {@link WebsocketException}s + // */ + // public void removeMarketDataStreamListener(MarketDataListener streamListener) throws WebsocketException { + // marketDataWebSocketClient.removeListener(streamListener); + // } + // + // /** + // * Sets the {@link MarketDataWebsocketClient} {@link WebsocketStateListener} to listen to websocket state + // changes. + // * + // * @param websocketStateListener the {@link WebsocketStateListener} + // */ + // public void setMarketDataStreamWebsocketStateListener(WebsocketStateListener websocketStateListener) { + // marketDataWebSocketClient.setWebsocketStateListener(websocketStateListener); + // } + + /** + * Gets {@link #brokerClient}. + * + * @return the broker {@link AlpacaClient} + */ + public AlpacaClient getBrokerClient() { + return brokerClient; + } + + /** + * Gets {@link #dataClient}. + * + * @return the data {@link AlpacaClient} + */ + public AlpacaClient getDataClient() { + return dataClient; + } +} diff --git a/src/main/java/net/jacobpeterson/alpaca/refactor/properties/AlpacaProperties.java b/src/main/java/net/jacobpeterson/alpaca/refactor/properties/AlpacaProperties.java new file mode 100644 index 00000000..fbbb41e5 --- /dev/null +++ b/src/main/java/net/jacobpeterson/alpaca/refactor/properties/AlpacaProperties.java @@ -0,0 +1,46 @@ +package net.jacobpeterson.alpaca.refactor.properties; + +import net.jacobpeterson.alpaca.model.properties.DataAPIType; +import net.jacobpeterson.alpaca.model.properties.EndpointAPIType; +import net.jacobpeterson.alpaca.refactor.AlpacaAPI; + +import static net.jacobpeterson.alpaca.refactor.util.properties.PropertyUtil.getProperty; + +/** + * {@link AlpacaProperties} defines properties for {@link AlpacaAPI}. + */ +public class AlpacaProperties { + + private static final String ALPACA_PROPERTIES_FILE = "alpaca.properties"; + private static final String ALPACA_DEFAULT_PROPERTIES_FILE = "alpaca.default.properties"; + + private static final String KEY_ID_KEY = "key_id"; + /** The value of {@link #KEY_ID_KEY} in {@link #ALPACA_PROPERTIES_FILE}. */ + public static final String KEY_ID = getProperty( + ALPACA_PROPERTIES_FILE, ALPACA_DEFAULT_PROPERTIES_FILE, + KEY_ID_KEY); + + private static final String SECRET_KEY_KEY = "secret_key"; + /** The value of {@link #SECRET_KEY_KEY} in {@link #ALPACA_PROPERTIES_FILE}. */ + public static final String SECRET_KEY = getProperty( + ALPACA_PROPERTIES_FILE, ALPACA_DEFAULT_PROPERTIES_FILE, + SECRET_KEY_KEY); + + private static final String ENDPOINT_API_TYPE_KEY = "endpoint_api_type"; + /** The value of {@link #ENDPOINT_API_TYPE_KEY} in {@link #ALPACA_PROPERTIES_FILE}. */ + public static final EndpointAPIType ENDPOINT_API_TYPE = EndpointAPIType.fromValue(getProperty( + ALPACA_PROPERTIES_FILE, ALPACA_DEFAULT_PROPERTIES_FILE, + ENDPOINT_API_TYPE_KEY)); + + private static final String DATA_API_TYPE_KEY = "data_api_type"; + /** The value of {@link #DATA_API_TYPE_KEY} in {@link #ALPACA_PROPERTIES_FILE}. */ + public static final DataAPIType DATA_API_TYPE = DataAPIType.fromValue(getProperty( + ALPACA_PROPERTIES_FILE, ALPACA_DEFAULT_PROPERTIES_FILE, + DATA_API_TYPE_KEY)); + + private static final String USER_AGENT_KEY = "user_agent"; + /** The value of {@link #USER_AGENT_KEY} in {@link #ALPACA_PROPERTIES_FILE}. */ + public static final String USER_AGENT = getProperty( + ALPACA_PROPERTIES_FILE, ALPACA_DEFAULT_PROPERTIES_FILE, + USER_AGENT_KEY); +} diff --git a/src/main/java/net/jacobpeterson/alpaca/refactor/rest/AlpacaClient.java b/src/main/java/net/jacobpeterson/alpaca/refactor/rest/AlpacaClient.java new file mode 100644 index 00000000..7e7de26a --- /dev/null +++ b/src/main/java/net/jacobpeterson/alpaca/refactor/rest/AlpacaClient.java @@ -0,0 +1,224 @@ +package net.jacobpeterson.alpaca.refactor.rest; + +import com.google.gson.Gson; +import com.google.gson.JsonElement; +import com.google.gson.JsonParser; +import net.jacobpeterson.alpaca.refactor.properties.AlpacaProperties; +import okhttp3.*; + +import java.io.IOException; +import java.io.Reader; +import java.lang.reflect.Type; +import java.util.function.Predicate; + +import static com.google.common.base.Preconditions.checkArgument; +import static net.jacobpeterson.alpaca.refactor.util.gson.GsonUtil.GSON; + +/** + * {@link AlpacaClient} represents an HTTP RestAPI client for Alpaca. + */ +public class AlpacaClient { + + private final HttpUrl baseURL; + private final Headers requestHeaders; + + private OkHttpClient okHttpClient; + + /** + * Instantiates a new {@link AlpacaClient}. + * + * @param keyID the key ID + * @param secretKey the secret key + * @param alpacaSubdomain the Alpaca subdomain + * @param versionPathSegment the version path segment e.g. "v2" + */ + public AlpacaClient(String keyID, String secretKey, String alpacaSubdomain, String versionPathSegment) { + this(keyID, secretKey, null, alpacaSubdomain, versionPathSegment); + } + + /** + * Instantiates a new {@link AlpacaClient}. + * + * @param oAuthToken the OAuth token + * @param alpacaSubdomain the Alpaca subdomain + * @param versionPathSegment the version path segment e.g. "v2" + */ + public AlpacaClient(String oAuthToken, String alpacaSubdomain, String versionPathSegment) { + this(null, null, oAuthToken, alpacaSubdomain, versionPathSegment); + } + + /** + * Instantiates a new {@link AlpacaClient}. + * + * @param keyID the key ID + * @param secretKey the secret key + * @param oAuthToken the OAuth token + * @param alpacaSubdomain the Alpaca subdomain + * @param versionPathSegment the version path segment e.g. "v2" + */ + protected AlpacaClient(String keyID, String secretKey, String oAuthToken, String alpacaSubdomain, + String versionPathSegment) { + checkArgument((keyID != null && secretKey != null) || oAuthToken != null, + "You must specify a (KeyID and Secret Key) or an OAuthToken!"); + + baseURL = new HttpUrl.Builder() + .scheme("https") + .host(alpacaSubdomain + ".alpaca.markets") + .addPathSegment(versionPathSegment) + .build(); + + Headers.Builder requestHeadersBuilder = new Headers.Builder(); + requestHeadersBuilder.add("User-Agent", AlpacaProperties.USER_AGENT); + if (oAuthToken != null) { + requestHeadersBuilder.add("Authorization", "Bearer " + oAuthToken); + } else { + requestHeadersBuilder + .add("APCA-API-KEY-ID", keyID) + .add("APCA-API-SECRET-KEY", secretKey); + } + requestHeaders = requestHeadersBuilder.build(); + + okHttpClient = new OkHttpClient(); + } + + /** + * Gets a new {@link HttpUrl.Builder}. + * + * @return a {@link HttpUrl.Builder} + */ + public HttpUrl.Builder urlBuilder() { + return baseURL.newBuilder(); + } + + /** + * Gets a new {@link Request.Builder} with {@link #requestHeaders}. + * + * @param httpUrl the {@link HttpUrl} + * + * @return a {@link Request.Builder} + */ + public Request.Builder requestBuilder(HttpUrl httpUrl) { + return new Request.Builder().headers(requestHeaders).url(httpUrl); + } + + /** + * Calls {@link #requestObject(Request, Predicate, Type)} with a success code of 200. + */ + public T requestObject(Request request, Type type) throws AlpacaClientException { + return requestObject(request, (code) -> code == 200, type); + } + + /** + * Requests an object given a {@link Request} that is deserialized via {@link Gson#fromJson(Reader, Type)}. + * + * @param the type of object + * @param request the {@link Request} + * @param isSuccessCode throws {@link AlpacaClientException} if passed in {@link Response#code()} to the {@link + * Predicate#test(Object)} returns false + * @param type the object {@link Type} + * + * @return the requested object + * + * @throws AlpacaClientException thrown for {@link AlpacaClientException}s + */ + public T requestObject(Request request, Predicate isSuccessCode, Type type) + throws AlpacaClientException { + return GSON.fromJson(requestJSON(request, isSuccessCode), type); + } + + /** + * Calls {@link #requestJSON(Request, Predicate)} with a success code of 200. + */ + public JsonElement requestJSON(Request request) throws AlpacaClientException { + return requestJSON(request, (code) -> code == 200); + } + + /** + * Requests a {@link JsonElement} given a {@link Request}. + * + * @param request the {@link Request} + * @param isSuccessCode throws {@link AlpacaClientException} if passed in {@link Response#code()} to the {@link + * Predicate#test(Object)} returns false + * + * @return the {@link JsonElement} + * + * @throws AlpacaClientException thrown for {@link AlpacaClientException}s + */ + public JsonElement requestJSON(Request request, Predicate isSuccessCode) throws AlpacaClientException { + return JsonParser.parseReader(request(request, isSuccessCode).body().charStream()); + } + + /** + * Sends a {@link Request} and returns a {@link Response}. + * + * @param request the {@link Request} + * @param isSuccessCode throws {@link AlpacaClientException} if passed in {@link Response#code()} to the {@link + * Predicate#test(Object)} returns false + * + * @return the {@link Response} + * + * @throws AlpacaClientException thrown for {@link AlpacaClientException}s + */ + public Response request(Request request, Predicate isSuccessCode) throws AlpacaClientException { + try { + Response response = executeRequest(request); + if (!isSuccessCode.test(response.code())) { + throw new AlpacaClientException(response); + } + return response; + } catch (Exception exception) { + throw new AlpacaClientException(exception); + } + } + + /** + * Executes a {@link Request}. + * + * @param request the {@link Request} + * + * @return the {@link Response} + * + * @throws IOException thrown for {@link IOException}s + */ + public Response executeRequest(Request request) throws IOException { + try (Response response = okHttpClient.newCall(request).execute()) { + return response; + } + } + + /** + * Gets {@link #baseURL}. + * + * @return the base {@link HttpUrl} + */ + public HttpUrl getBaseURL() { + return baseURL; + } + + /** + * Gets {@link #requestHeaders}. + * + * @return the request {@link Headers} + */ + public Headers getRequestHeaders() { + return requestHeaders; + } + + /** + * Gets {@link #okHttpClient}. + * + * @return the {@link OkHttpClient} + */ + public OkHttpClient getOkHttpClient() { + return okHttpClient; + } + + /** + * Sets {@link #okHttpClient}. + * + * @param okHttpClient the {@link OkHttpClient} + */ + public void setOkHttpClient(OkHttpClient okHttpClient) { + this.okHttpClient = okHttpClient; + } +} diff --git a/src/main/java/net/jacobpeterson/alpaca/refactor/rest/AlpacaClientException.java b/src/main/java/net/jacobpeterson/alpaca/refactor/rest/AlpacaClientException.java new file mode 100644 index 00000000..9070b112 --- /dev/null +++ b/src/main/java/net/jacobpeterson/alpaca/refactor/rest/AlpacaClientException.java @@ -0,0 +1,165 @@ +package net.jacobpeterson.alpaca.refactor.rest; + +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import com.google.gson.JsonParser; +import okhttp3.Response; + +public class AlpacaClientException extends Exception { + + private static final String CODE_KEY = "code"; + private static final String MESSAGE_KEY = "message"; + + private Response response; + private Integer requestStatusCode; + private String requestStatusMessage; + private Integer apiResponseCode; + private String apiResponseMessage; + + /** + * Instantiates a new {@link AlpacaClientException}. + * + * @param message the message + */ + public AlpacaClientException(String message) { + super(message); + } + + /** + * Instantiates a new {@link AlpacaClientException}. + * + * @param cause the cause {@link Throwable} + */ + public AlpacaClientException(Throwable cause) { + super(cause); + } + + /** + * Instantiates a new {@link AlpacaClientException}. + * + * @param message the message + * @param cause the cause {@link Throwable} + */ + public AlpacaClientException(String message, Throwable cause) { + super(message, cause); + } + + /** + * Instantiates a new {@link AlpacaClientException}. + * + * @param response the {@link Response} containing an API response code and message JSON + */ + public AlpacaClientException(Response response) { + this.response = response; + } + + @Override + public String getMessage() { + if (response == null) { + return super.getMessage(); + } else { + requestStatusCode = response.code(); + requestStatusMessage = response.message(); + + try { + return parseAPIErrorResponse(); + } catch (Exception ignored) { + return super.getMessage(); + } + } + } + + /** + * Parses an API error response of the following format: + *

+     *  {
+     *     "code": 505,
+     *     "message": "Error message"
+     *  }
+     * 
+ * and sets {@link #apiResponseCode} and {@link #apiResponseMessage} accordingly. + * + * @return a formatted message of the error response + */ + private String parseAPIErrorResponse() { + JsonElement responseJsonElement = JsonParser.parseReader(response.body().charStream()); + if (responseJsonElement instanceof JsonObject) { + JsonObject responseJsonObject = responseJsonElement.getAsJsonObject(); + + if (responseJsonObject.has(CODE_KEY)) { + apiResponseCode = responseJsonObject.get(CODE_KEY).getAsInt(); + } + + if (responseJsonObject.has(MESSAGE_KEY)) { + apiResponseMessage = responseJsonObject.get(MESSAGE_KEY).getAsString(); + } + } + + // Just use the response JSON if the message could not be parsed. + if (apiResponseMessage == null) { + apiResponseMessage = responseJsonElement.toString(); + } + + // Build message + StringBuilder messageBuilder = new StringBuilder(); + messageBuilder.append("Status Code: ").append(requestStatusCode); + messageBuilder.append(", Status Message: \"").append(requestStatusMessage).append("\""); + + if (apiResponseCode != null) { + messageBuilder.append(", API Response Code: ").append(apiResponseCode); + } + + if (apiResponseMessage != null) { + messageBuilder.append(", API Response Message: \"").append(apiResponseMessage).append("\""); + } + + return messageBuilder.toString(); + } + + /** + * Gets the {@link #requestStatusCode}. + * + * @return an {@link Integer} + */ + public Integer getRequestStatusCode() { + return requestStatusCode; + } + + /** + * Gets the {@link #requestStatusMessage}. + * + * @return a {@link String} + */ + public String getRequestStatusMessage() { + return requestStatusMessage; + } + + /** + * Gets the {@link #apiResponseCode}. + * + * @return an {@link Integer} + */ + public Integer getAPIResponseCode() { + return apiResponseCode; + } + + /** + * Gets the {@link #apiResponseMessage}. + * + * @return a {@link String} + */ + public String getAPIResponseMessage() { + return apiResponseMessage; + } + + @Override + public String toString() { + return "AlpacaClientException{" + + "response=" + response + + ", requestStatusCode=" + requestStatusCode + + ", requestStatusMessage='" + requestStatusMessage + '\'' + + ", apiResponseCode=" + apiResponseCode + + ", apiResponseMessage='" + apiResponseMessage + '\'' + + "}"; + } +} diff --git a/src/main/java/net/jacobpeterson/alpaca/refactor/rest/endpoint/AbstractEndpoint.java b/src/main/java/net/jacobpeterson/alpaca/refactor/rest/endpoint/AbstractEndpoint.java new file mode 100644 index 00000000..7c50cef2 --- /dev/null +++ b/src/main/java/net/jacobpeterson/alpaca/refactor/rest/endpoint/AbstractEndpoint.java @@ -0,0 +1,23 @@ +package net.jacobpeterson.alpaca.refactor.rest.endpoint; + +import net.jacobpeterson.alpaca.refactor.rest.AlpacaClient; + +/** + * {@link AbstractEndpoint} is an abstract class representing a RestAPI endpoint. + */ +public class AbstractEndpoint { + + protected final AlpacaClient alpacaClient; + protected final String endpointPathSegment; + + /** + * Instantiates a new {@link AbstractEndpoint}. + * + * @param alpacaClient the {@link AlpacaClient} + * @param endpointPathSegment the endpoint path segment relative to {@link AlpacaClient#getBaseURL()} + */ + public AbstractEndpoint(AlpacaClient alpacaClient, String endpointPathSegment) { + this.alpacaClient = alpacaClient; + this.endpointPathSegment = endpointPathSegment; + } +} diff --git a/src/main/java/net/jacobpeterson/alpaca/refactor/rest/endpoint/AccountActivitiesEndpoint.java b/src/main/java/net/jacobpeterson/alpaca/refactor/rest/endpoint/AccountActivitiesEndpoint.java new file mode 100644 index 00000000..b4136b63 --- /dev/null +++ b/src/main/java/net/jacobpeterson/alpaca/refactor/rest/endpoint/AccountActivitiesEndpoint.java @@ -0,0 +1,147 @@ +package net.jacobpeterson.alpaca.refactor.rest.endpoint; + +import com.google.gson.JsonArray; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import net.jacobpeterson.alpaca.model.endpoint.accountactivities.AccountActivity; +import net.jacobpeterson.alpaca.model.endpoint.accountactivities.NonTradeActivity; +import net.jacobpeterson.alpaca.model.endpoint.accountactivities.TradeActivity; +import net.jacobpeterson.alpaca.model.endpoint.accountactivities.enums.ActivityType; +import net.jacobpeterson.alpaca.model.endpoint.common.enums.SortDirection; +import net.jacobpeterson.alpaca.refactor.rest.AlpacaClient; +import net.jacobpeterson.alpaca.refactor.rest.AlpacaClientException; +import net.jacobpeterson.alpaca.refactor.util.gson.GsonUtil; +import okhttp3.HttpUrl; +import okhttp3.Request; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.time.ZonedDateTime; +import java.time.format.DateTimeFormatter; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.stream.Collectors; + +/** + * {@link AbstractEndpoint} for + * Account + * Activities. + */ +public class AccountActivitiesEndpoint extends AbstractEndpoint { + + private static final Logger LOGGER = LoggerFactory.getLogger(AccountActivitiesEndpoint.class); + + private static final String ACTIVITY_TYPE_FIELD = "activity_type"; + + /** + * Instantiates a new {@link AccountActivitiesEndpoint}. + * + * @param alpacaClient the {@link AlpacaClient} + */ + public AccountActivitiesEndpoint(AlpacaClient alpacaClient) { + super(alpacaClient, "account/activities"); + } + + /** + * Returns {@link AccountActivity} entries for many {@link ActivityType}s or for a specific {@link ActivityType}. + * + * @param date the date for which you want to see activities. + * @param until the response will contain only activities submitted before this date. (Cannot be used with + * date.) + * @param after the response will contain only activities submitted after this date. (Cannot be used with + * date.) + * @param sortDirection the {@link SortDirection} (defaults to {@link SortDirection#DESCENDING} if unspecified.) + * @param pageSize the maximum number of entries to return in the response. (See the section on paging.) + * @param pageToken the ID of the end of your current page of results. (See the section on paging.) + * @param activityTypes the {@link ActivityType}s (null for all {@link ActivityType}s) + * + * @return a {@link List} of {@link AccountActivity}s + * + * @throws AlpacaClientException thrown for {@link AlpacaClientException}s + */ + public List get(ZonedDateTime date, ZonedDateTime until, ZonedDateTime after, + SortDirection sortDirection, Integer pageSize, String pageToken, ActivityType... activityTypes) + throws AlpacaClientException { + HttpUrl.Builder urlBuilder = alpacaClient.urlBuilder() + .addPathSegments(endpointPathSegment); + + if (activityTypes != null) { // Check if we don't want all activity types + if (activityTypes.length == 1) { // Get one activity + urlBuilder.addPathSegment(activityTypes[0].toString()); + } else { // Get list of activities + urlBuilder.addQueryParameter("activity_types", + // Makes comma-separated list + Arrays.stream(activityTypes).map(ActivityType::toString).collect(Collectors.joining(","))); + } + } + + if (date != null) { + urlBuilder.addQueryParameter("date", date.format(DateTimeFormatter.ISO_OFFSET_DATE_TIME)); + } + + if (until != null) { + urlBuilder.addQueryParameter("until", until.format(DateTimeFormatter.ISO_OFFSET_DATE_TIME)); + } + + if (after != null) { + urlBuilder.addQueryParameter("after", after.format(DateTimeFormatter.ISO_OFFSET_DATE_TIME)); + } + + if (sortDirection != null) { + urlBuilder.addQueryParameter("direction", sortDirection.toString()); + } + + if (pageSize != null) { + urlBuilder.addQueryParameter("page_size", pageSize.toString()); + } + + if (pageToken != null) { + urlBuilder.addQueryParameter("page_token", pageToken); + } + + Request request = alpacaClient.requestBuilder(urlBuilder.build()) + .get() + .build(); + + JsonElement responseJSON = alpacaClient.requestJSON(request); + if (!(responseJSON instanceof JsonArray)) { + throw new IllegalStateException( + String.format("The response must be an array! Received: %s", responseJSON)); + } + + ArrayList accountActivities = new ArrayList<>(); + for (JsonElement responseArrayElement : responseJSON.getAsJsonArray()) { + if (!(responseArrayElement instanceof JsonObject)) { + throw new IllegalStateException( + String.format("All array elements must be objects! Received: %s", responseArrayElement)); + } + + JsonObject responseArrayObject = responseArrayElement.getAsJsonObject(); + JsonElement activityTypeElement = responseArrayObject.get(ACTIVITY_TYPE_FIELD); + if (activityTypeElement == null) { + throw new IllegalStateException( + String.format("Activity type elements must have %s field! Received: %s", + ACTIVITY_TYPE_FIELD, responseArrayElement)); + } + + String activityType = activityTypeElement.getAsString(); + AccountActivity accountActivity; + + // A 'TradeActivity' always has 'activity_type' field as 'FILL' + if (activityType.equals(ActivityType.FILL.toString())) { + accountActivity = GsonUtil.GSON.fromJson(responseArrayObject, TradeActivity.class); + } else { + accountActivity = GsonUtil.GSON.fromJson(responseArrayObject, NonTradeActivity.class); + } + + if (accountActivity.getActivityType() == null) { + LOGGER.error("No ActivityType enum exists for {}. Report this!", activityType); + } + + accountActivities.add(accountActivity); + } + + return accountActivities; + } +} diff --git a/src/main/java/net/jacobpeterson/alpaca/refactor/rest/endpoint/AccountConfigurationEndpoint.java b/src/main/java/net/jacobpeterson/alpaca/refactor/rest/endpoint/AccountConfigurationEndpoint.java new file mode 100644 index 00000000..25de8fe5 --- /dev/null +++ b/src/main/java/net/jacobpeterson/alpaca/refactor/rest/endpoint/AccountConfigurationEndpoint.java @@ -0,0 +1,63 @@ +package net.jacobpeterson.alpaca.refactor.rest.endpoint; + +import net.jacobpeterson.alpaca.model.endpoint.accountconfiguration.AccountConfiguration; +import net.jacobpeterson.alpaca.refactor.rest.AlpacaClient; +import net.jacobpeterson.alpaca.refactor.rest.AlpacaClientException; +import net.jacobpeterson.alpaca.refactor.util.okhttp.JSONBodyBuilder; +import okhttp3.HttpUrl; +import okhttp3.Request; + +import static com.google.common.base.Preconditions.checkNotNull; +import static net.jacobpeterson.alpaca.refactor.util.gson.GsonUtil.GSON; + +/** + * {@link AbstractEndpoint} for + * Account Configuration. + */ +public class AccountConfigurationEndpoint extends AbstractEndpoint { + + /** + * Instantiates a new {@link AccountConfigurationEndpoint}. + * + * @param alpacaClient the {@link AlpacaClient} + */ + public AccountConfigurationEndpoint(AlpacaClient alpacaClient) { + super(alpacaClient, "account/configurations"); + } + + /** + * Returns the {@link AccountConfiguration}. + * + * @return the {@link AccountConfiguration} + * + * @throws AlpacaClientException thrown for {@link AlpacaClientException}s + */ + public AccountConfiguration get() throws AlpacaClientException { + HttpUrl.Builder urlBuilder = alpacaClient.urlBuilder() + .addPathSegments(endpointPathSegment); + Request request = alpacaClient.requestBuilder(urlBuilder.build()) + .get() + .build(); + return alpacaClient.requestObject(request, AccountConfiguration.class); + } + + /** + * Sets the {@link AccountConfiguration}. + * + * @param accountConfiguration the {@link AccountConfiguration} + * + * @return the updated {@link AccountConfiguration} + * + * @throws AlpacaClientException thrown for {@link AlpacaClientException}s + */ + public AccountConfiguration set(AccountConfiguration accountConfiguration) throws AlpacaClientException { + checkNotNull(accountConfiguration); + + HttpUrl.Builder urlBuilder = alpacaClient.urlBuilder() + .addPathSegments(endpointPathSegment); + Request request = alpacaClient.requestBuilder(urlBuilder.build()) + .patch(new JSONBodyBuilder(GSON.toJson(accountConfiguration)).build()) + .build(); + return alpacaClient.requestObject(request, AccountConfiguration.class); + } +} diff --git a/src/main/java/net/jacobpeterson/alpaca/refactor/rest/endpoint/AccountEndpoint.java b/src/main/java/net/jacobpeterson/alpaca/refactor/rest/endpoint/AccountEndpoint.java new file mode 100644 index 00000000..ef4a4eea --- /dev/null +++ b/src/main/java/net/jacobpeterson/alpaca/refactor/rest/endpoint/AccountEndpoint.java @@ -0,0 +1,38 @@ +package net.jacobpeterson.alpaca.refactor.rest.endpoint; + +import net.jacobpeterson.alpaca.model.endpoint.account.Account; +import net.jacobpeterson.alpaca.refactor.rest.AlpacaClient; +import net.jacobpeterson.alpaca.refactor.rest.AlpacaClientException; +import okhttp3.HttpUrl; +import okhttp3.Request; + +/** + * {@link AbstractEndpoint} for Account. + */ +public class AccountEndpoint extends AbstractEndpoint { + + /** + * Instantiates a new {@link AccountEndpoint}. + * + * @param alpacaClient the {@link AlpacaClient} + */ + public AccountEndpoint(AlpacaClient alpacaClient) { + super(alpacaClient, "account"); + } + + /** + * Returns the {@link Account}. + * + * @return the {@link Account} + * + * @throws AlpacaClientException thrown for {@link AlpacaClientException}s + */ + public Account get() throws AlpacaClientException { + HttpUrl.Builder urlBuilder = alpacaClient.urlBuilder() + .addPathSegment(endpointPathSegment); + Request request = alpacaClient.requestBuilder(urlBuilder.build()) + .get() + .build(); + return alpacaClient.requestObject(request, Account.class); + } +} diff --git a/src/main/java/net/jacobpeterson/alpaca/refactor/rest/endpoint/AssetsEndpoint.java b/src/main/java/net/jacobpeterson/alpaca/refactor/rest/endpoint/AssetsEndpoint.java new file mode 100644 index 00000000..8cbbded3 --- /dev/null +++ b/src/main/java/net/jacobpeterson/alpaca/refactor/rest/endpoint/AssetsEndpoint.java @@ -0,0 +1,78 @@ +package net.jacobpeterson.alpaca.refactor.rest.endpoint; + +import com.google.gson.reflect.TypeToken; +import net.jacobpeterson.alpaca.model.endpoint.asset.Asset; +import net.jacobpeterson.alpaca.model.endpoint.asset.enums.AssetStatus; +import net.jacobpeterson.alpaca.refactor.rest.AlpacaClient; +import net.jacobpeterson.alpaca.refactor.rest.AlpacaClientException; +import okhttp3.HttpUrl; +import okhttp3.Request; + +import java.util.ArrayList; +import java.util.List; + +import static com.google.common.base.Preconditions.checkNotNull; + +/** + * {@link AbstractEndpoint} for Assets. + */ +public class AssetsEndpoint extends AbstractEndpoint { + + /** + * Instantiates a new {@link AssetsEndpoint}. + * + * @param alpacaClient the {@link AlpacaClient} + */ + public AssetsEndpoint(AlpacaClient alpacaClient) { + super(alpacaClient, "assets"); + } + + /** + * Get a list of {@link Asset}s. + * + * @param assetStatus the {@link AssetStatus}. By default, all {@link AssetStatus}es are included. + * @param assetClass the asset class. Defaults to "us_equity". + * + * @return a {@link List} of {@link Asset}s + * + * @throws AlpacaClientException thrown for {@link AlpacaClientException}s + */ + public List get(AssetStatus assetStatus, String assetClass) throws AlpacaClientException { + HttpUrl.Builder urlBuilder = alpacaClient.urlBuilder() + .addPathSegment(endpointPathSegment); + + if (assetStatus != null) { + urlBuilder.addQueryParameter("status", assetStatus.toString()); + } + + if (assetClass != null) { + urlBuilder.addQueryParameter("asset_class", assetClass); + } + + Request request = alpacaClient.requestBuilder(urlBuilder.build()) + .get() + .build(); + return alpacaClient.requestObject(request, new TypeToken>() {}.getType()); + } + + /** + * Gets an {@link Asset} by a symbol or {@link Asset#getId()}. + * + * @param symbolOrAssetID the symbol or {@link Asset#getId()} + * + * @return the {@link Asset} + * + * @throws AlpacaClientException thrown for {@link AlpacaClientException}s + */ + public Asset getBySymbol(String symbolOrAssetID) throws AlpacaClientException { + checkNotNull(symbolOrAssetID); + + HttpUrl.Builder urlBuilder = alpacaClient.urlBuilder() + .addPathSegment(endpointPathSegment) + .addPathSegment(symbolOrAssetID); + Request request = alpacaClient.requestBuilder(urlBuilder.build()) + .get() + .build(); + return alpacaClient.requestObject(request, Asset.class); + } +} diff --git a/src/main/java/net/jacobpeterson/alpaca/refactor/rest/endpoint/CalendarEndpoint.java b/src/main/java/net/jacobpeterson/alpaca/refactor/rest/endpoint/CalendarEndpoint.java new file mode 100644 index 00000000..02aa9b98 --- /dev/null +++ b/src/main/java/net/jacobpeterson/alpaca/refactor/rest/endpoint/CalendarEndpoint.java @@ -0,0 +1,67 @@ +package net.jacobpeterson.alpaca.refactor.rest.endpoint; + +import com.google.gson.reflect.TypeToken; +import net.jacobpeterson.alpaca.model.endpoint.calendar.Calendar; +import net.jacobpeterson.alpaca.refactor.rest.AlpacaClient; +import net.jacobpeterson.alpaca.refactor.rest.AlpacaClientException; +import okhttp3.HttpUrl; +import okhttp3.Request; + +import java.time.LocalDate; +import java.time.format.DateTimeFormatter; +import java.util.ArrayList; +import java.util.List; + +/** + * {@link AbstractEndpoint} for Calendar. + */ +public class CalendarEndpoint extends AbstractEndpoint { + + /** + * Instantiates a new {@link CalendarEndpoint}. + * + * @param alpacaClient the {@link AlpacaClient} + */ + public CalendarEndpoint(AlpacaClient alpacaClient) { + super(alpacaClient, "calendar"); + } + + /** + * Returns the market {@link Calendar} from 1970 to 2029. + * + * @return a {@link List} of {@link Calendar}s + * + * @throws AlpacaClientException thrown for {@link AlpacaClientException}s + */ + public List get() throws AlpacaClientException { + return get(null, null); + } + + /** + * Returns the market {@link Calendar}. + * + * @param start the first date to retrieve data for (inclusive) + * @param end the last date to retrieve data for (inclusive) + * + * @return a {@link List} of {@link Calendar}s + * + * @throws AlpacaClientException thrown for {@link AlpacaClientException}s + */ + public List get(LocalDate start, LocalDate end) throws AlpacaClientException { + HttpUrl.Builder urlBuilder = alpacaClient.urlBuilder() + .addPathSegment(endpointPathSegment); + + if (start != null) { + urlBuilder.addQueryParameter("start", start.format(DateTimeFormatter.ISO_DATE)); + } + + if (end != null) { + urlBuilder.addQueryParameter("end", end.format(DateTimeFormatter.ISO_DATE)); + } + + Request request = alpacaClient.requestBuilder(urlBuilder.build()) + .get() + .build(); + return alpacaClient.requestObject(request, new TypeToken>() {}.getType()); + } +} diff --git a/src/main/java/net/jacobpeterson/alpaca/refactor/rest/endpoint/ClockEndpoint.java b/src/main/java/net/jacobpeterson/alpaca/refactor/rest/endpoint/ClockEndpoint.java new file mode 100644 index 00000000..8af8b38e --- /dev/null +++ b/src/main/java/net/jacobpeterson/alpaca/refactor/rest/endpoint/ClockEndpoint.java @@ -0,0 +1,38 @@ +package net.jacobpeterson.alpaca.refactor.rest.endpoint; + +import net.jacobpeterson.alpaca.model.endpoint.clock.Clock; +import net.jacobpeterson.alpaca.refactor.rest.AlpacaClient; +import net.jacobpeterson.alpaca.refactor.rest.AlpacaClientException; +import okhttp3.HttpUrl; +import okhttp3.Request; + +/** + * {@link AbstractEndpoint} for Clock. + */ +public class ClockEndpoint extends AbstractEndpoint { + + /** + * Instantiates a new {@link ClockEndpoint}. + * + * @param alpacaClient the {@link AlpacaClient} + */ + public ClockEndpoint(AlpacaClient alpacaClient) { + super(alpacaClient, "clock"); + } + + /** + * Returns the market {@link Clock}. + * + * @return the market {@link Clock} + * + * @throws AlpacaClientException thrown for {@link AlpacaClientException}s + */ + public Clock get() throws AlpacaClientException { + HttpUrl.Builder urlBuilder = alpacaClient.urlBuilder() + .addPathSegment(endpointPathSegment); + Request request = alpacaClient.requestBuilder(urlBuilder.build()) + .get() + .build(); + return alpacaClient.requestObject(request, Clock.class); + } +} diff --git a/src/main/java/net/jacobpeterson/alpaca/refactor/rest/endpoint/MarketDataEndpoint.java b/src/main/java/net/jacobpeterson/alpaca/refactor/rest/endpoint/MarketDataEndpoint.java new file mode 100644 index 00000000..48ac6dbe --- /dev/null +++ b/src/main/java/net/jacobpeterson/alpaca/refactor/rest/endpoint/MarketDataEndpoint.java @@ -0,0 +1,275 @@ +package net.jacobpeterson.alpaca.refactor.rest.endpoint; + +import net.jacobpeterson.alpaca.refactor.rest.AlpacaClient; + +/** + * {@link AbstractEndpoint} for + * Market + * Data API v2. + */ +public class MarketDataEndpoint extends AbstractEndpoint { + + /** + * Instantiates a new {@link MarketDataEndpoint}. + * + * @param alpacaClient the {@link AlpacaClient} + */ + public MarketDataEndpoint(AlpacaClient alpacaClient) { + super(alpacaClient, null); + } + + // TODO + // /** + // * Gets {@link Trade} historical data for the requested security. + // * + // * @param symbol the symbol to query for + // * @param start filter data equal to or after this {@link ZonedDateTime}. Fractions of a second are not + // * accepted + // * @param end filter data equal to or before this {@link ZonedDateTime}. Fractions of a second are not + // * accepted + // * @param limit number of data points to return. Must be in range 1-10000, defaults to 1000 if null is given + // * @param pageToken pagination token to continue from + // * + // * @return the {@link TradesResponse} + // * + // * @throws AlpacaAPIRequestException thrown for {@link AlpacaAPIRequestException}s + // * @see + // * Historical Market Data + // */ + // public TradesResponse getTrades(String symbol, ZonedDateTime start, ZonedDateTime end, Integer limit, + // String pageToken) throws AlpacaAPIRequestException { + // checkNotNull(symbol); + // checkNotNull(start); + // checkNotNull(end); + // + // AlpacaRequestBuilder urlBuilder = new AlpacaRequestBuilder(URLs.DATA, Endpoints.VERSION_2, + // Endpoints.STOCKS, + // symbol, + // Endpoints.TRADES); + // + // urlBuilder.appendURLParameter(Parameters.START, FormatUtil.toRFC3339Format(start)); + // urlBuilder.appendURLParameter(Parameters.END, FormatUtil.toRFC3339Format(end)); + // + // if (limit != null) { + // urlBuilder.appendURLParameter(Parameters.LIMIT, limit.toString()); + // } + // + // if (pageToken != null) { + // urlBuilder.appendURLParameter(Parameters.PAGE_TOKEN, pageToken); + // } + // + // return alpacaRequest.get(urlBuilder, TradesResponse.class); + // } + // + // /** + // * The Latest trade API provides the latest {@link Trade} data for a given ticker symbol. + // * + // * @param symbol the symbol to query for + // * + // * @return the {@link LatestTradeResponse} + // * + // * @throws AlpacaAPIRequestException thrown for {@link AlpacaAPIRequestException}s + // * @see + // * Historical Market Data + // */ + // public LatestTradeResponse getLatestTrade(String symbol) throws AlpacaAPIRequestException { + // checkNotNull(symbol); + // + // AlpacaRequestBuilder urlBuilder = new AlpacaRequestBuilder(URLs.DATA, Endpoints.VERSION_2, + // Endpoints.STOCKS, + // symbol, + // Endpoints.TRADES, + // Endpoints.LATEST); + // + // return alpacaRequest.get(urlBuilder, LatestTradeResponse.class); + // } + // + // /** + // * Gets {@link Quote} (NBBO or National Best Bid and Offer) historical data for the requested security. + // * + // * @param symbol the symbol to query for + // * @param start filter data equal to or after this {@link ZonedDateTime}. Fractions of a second are not + // * accepted + // * @param end filter data equal to or before this {@link ZonedDateTime}. Fractions of a second are not + // * accepted + // * @param limit number of data points to return. Must be in range 1-10000, defaults to 1000 if null is given + // * @param pageToken pagination token to continue from + // * + // * @return the {@link QuotesResponse} + // * + // * @throws AlpacaAPIRequestException thrown for {@link AlpacaAPIRequestException}s + // * @see + // * Historical Market Data + // */ + // public QuotesResponse getQuotes(String symbol, ZonedDateTime start, ZonedDateTime end, Integer limit, + // String pageToken) throws AlpacaAPIRequestException { + // checkNotNull(symbol); + // checkNotNull(start); + // checkNotNull(end); + // + // AlpacaRequestBuilder urlBuilder = new AlpacaRequestBuilder(URLs.DATA, Endpoints.VERSION_2, + // Endpoints.STOCKS, + // symbol, + // Endpoints.QUOTES); + // + // urlBuilder.appendURLParameter(Parameters.START, FormatUtil.toRFC3339Format(start)); + // urlBuilder.appendURLParameter(Parameters.END, FormatUtil.toRFC3339Format(end)); + // + // if (limit != null) { + // urlBuilder.appendURLParameter(Parameters.LIMIT, limit.toString()); + // } + // + // if (pageToken != null) { + // urlBuilder.appendURLParameter(Parameters.PAGE_TOKEN, pageToken); + // } + // + // return alpacaRequest.get(urlBuilder, QuotesResponse.class); + // } + // + // /** + // * The Latest quote API provides the latest quote data for a given ticker symbol. + // * + // * @param symbol the symbol to query for + // * + // * @return the {@link LatestQuoteResponse} + // * + // * @throws AlpacaAPIRequestException thrown for {@link AlpacaAPIRequestException}s + // * @see + // * Historical Market Data + // */ + // public LatestQuoteResponse getLatestQuote(String symbol) throws AlpacaAPIRequestException { + // checkNotNull(symbol); + // + // AlpacaRequestBuilder urlBuilder = new AlpacaRequestBuilder(URLs.DATA, Endpoints.VERSION_2, + // Endpoints.STOCKS, + // symbol, + // Endpoints.QUOTES, + // Endpoints.LATEST); + // + // return alpacaRequest.get(urlBuilder, LatestQuoteResponse.class); + // } + // + // /** + // * Gets {@link Bar} aggregate historical data for the requested security. + // * + // * @param symbol the symbol to query for + // * @param start filter data equal to or after this {@link ZonedDateTime}. Fractions of a second are not + // * accepted + // * @param end filter data equal to or before this {@link ZonedDateTime}. Fractions of a second are not + // * accepted + // * @param limit number of data points to return. Must be in range 1-10000, defaults to 1000 if null is given + // * @param pageToken pagination token to continue from + // * @param timeFrame the {@link BarsTimeFrame} for the aggregation + // * + // * @return the {@link BarsResponse} + // * + // * @throws AlpacaAPIRequestException thrown for {@link AlpacaAPIRequestException}s + // * @see + // * Historical Market Data + // */ + // public BarsResponse getBars(String symbol, ZonedDateTime start, ZonedDateTime end, Integer limit, + // String pageToken, BarsTimeFrame timeFrame) throws AlpacaAPIRequestException { + // checkNotNull(symbol); + // checkNotNull(start); + // checkNotNull(end); + // checkNotNull(timeFrame); + // + // AlpacaRequestBuilder urlBuilder = new AlpacaRequestBuilder(URLs.DATA, Endpoints.VERSION_2, + // Endpoints.STOCKS, + // symbol, + // Endpoints.BARS); + // + // urlBuilder.appendURLParameter(Parameters.START, FormatUtil.toRFC3339Format(start)); + // urlBuilder.appendURLParameter(Parameters.END, FormatUtil.toRFC3339Format(end)); + // urlBuilder.appendURLParameter(Parameters.TIMEFRAME, timeFrame.getAPIName()); + // + // if (limit != null) { + // urlBuilder.appendURLParameter(Parameters.LIMIT, limit.toString()); + // } + // + // if (pageToken != null) { + // urlBuilder.appendURLParameter(Parameters.PAGE_TOKEN, pageToken); + // } + // + // return alpacaRequest.get(urlBuilder, BarsResponse.class); + // } + // + // /** + // * The Snapshot API for one ticker provides the latest trade, latest quote, minute bar daily bar and previous + // daily + // * bar data for a given ticker symbol + // * + // * @param symbols the symbols to query for + // * + // * @return a {@link Map} with they keys being the symbol and the value being the {@link Snapshot} + // * + // * @throws AlpacaAPIRequestException thrown for {@link AlpacaAPIRequestException}s + // * @see + // * Historical Market Data + // */ + // public Map getSnapshots(List symbols) throws AlpacaAPIRequestException { + // checkNotNull(symbols); + // Preconditions.checkState(!symbols.isEmpty()); + // + // AlpacaRequestBuilder urlBuilder = new AlpacaRequestBuilder(URLs.DATA, Endpoints.VERSION_2, + // Endpoints.STOCKS, + // Endpoints.SNAPSHOTS); + // + // urlBuilder.appendURLParameter(Parameters.SYMBOLS, String.join(",", symbols)); + // + // HttpResponse response; + // try { + // response = alpacaRequest.invokeGet(urlBuilder); + // } catch (UnirestException exception) { + // throw new AlpacaAPIRequestException(exception); + // } + // + // if (response.getStatus() != 200) { + // throw new AlpacaAPIRequestException(response); + // } + // + // JsonElement responseJsonElement = alpacaRequest.getResponseJSON(response); + // HashMap snapshotsOfSymbols = new HashMap<>(); + // + // if (responseJsonElement instanceof JsonObject) { + // JsonObject responseJsonObject = (JsonObject) responseJsonElement; + // + // for (String symbol : responseJsonObject.keySet()) { // Loop through response object keys + // JsonElement jsonObjectElement = responseJsonObject.get(symbol); + // + // if (jsonObjectElement instanceof JsonObject) { + // snapshotsOfSymbols.put(symbol, GsonUtil.GSON.fromJson(jsonObjectElement, Snapshot.class)); + // } else { + // throw new IllegalStateException("The response object must only contain objects!"); + // } + // } + // return snapshotsOfSymbols; + // } else { + // throw new IllegalStateException("The response must be an object!"); + // } + // } + // + // /** + // * The Snapshot API for one ticker provides the latest trade, latest quote, minute bar daily bar and previous + // daily + // * bar data for a given ticker symbol + // * + // * @param symbol the symbol to query for + // * + // * @return the {@link Snapshot} + // * + // * @throws AlpacaAPIRequestException thrown for {@link AlpacaAPIRequestException}s + // * @see + // * Historical Market Data + // */ + // public Snapshot getSnapshot(String symbol) throws AlpacaAPIRequestException { + // checkNotNull(symbol); + // + // AlpacaRequestBuilder urlBuilder = new AlpacaRequestBuilder(URLs.DATA, Endpoints.VERSION_2, + // Endpoints.STOCKS, + // symbol, + // Endpoints.SNAPSHOT); + // + // return alpacaRequest.get(urlBuilder, Snapshot.class); + // } +} diff --git a/src/main/java/net/jacobpeterson/alpaca/refactor/rest/endpoint/OrdersEndpoint.java b/src/main/java/net/jacobpeterson/alpaca/refactor/rest/endpoint/OrdersEndpoint.java new file mode 100644 index 00000000..966c81ff --- /dev/null +++ b/src/main/java/net/jacobpeterson/alpaca/refactor/rest/endpoint/OrdersEndpoint.java @@ -0,0 +1,633 @@ +package net.jacobpeterson.alpaca.refactor.rest.endpoint; + +import com.google.gson.JsonObject; +import com.google.gson.JsonPrimitive; +import com.google.gson.reflect.TypeToken; +import net.jacobpeterson.alpaca.model.endpoint.common.enums.SortDirection; +import net.jacobpeterson.alpaca.model.endpoint.order.CancelledOrder; +import net.jacobpeterson.alpaca.model.endpoint.order.Order; +import net.jacobpeterson.alpaca.model.endpoint.order.enums.*; +import net.jacobpeterson.alpaca.refactor.rest.AlpacaClient; +import net.jacobpeterson.alpaca.refactor.rest.AlpacaClientException; +import net.jacobpeterson.alpaca.refactor.util.format.FormatUtil; +import net.jacobpeterson.alpaca.refactor.util.okhttp.JSONBodyBuilder; +import okhttp3.HttpUrl; +import okhttp3.Request; + +import java.time.ZonedDateTime; +import java.time.format.DateTimeFormatter; +import java.util.ArrayList; +import java.util.List; + +import static com.google.common.base.Preconditions.checkNotNull; +import static com.google.common.base.Preconditions.checkState; + +/** + * {@link AbstractEndpoint} for Orders. + */ +public class OrdersEndpoint extends AbstractEndpoint { + + /** + * Instantiates a new {@link OrdersEndpoint}. + * + * @param alpacaClient the {@link AlpacaClient} + */ + public OrdersEndpoint(AlpacaClient alpacaClient) { + super(alpacaClient, "orders"); + } + + /** + * Retrieves a list of {@link Order}s for the account, filtered by the supplied query parameters. + * + * @param status {@link CurrentOrderStatus} to be queried. Defaults to {@link CurrentOrderStatus#OPEN}. + * @param limit the maximum number of orders in response. Defaults to 50 and max is 500. + * @param after the response will include only ones submitted after this timestamp (exclusive) + * @param until the response will include only ones submitted until this timestamp (exclusive) + * @param direction the chronological order of response based on the submission time. Defaults to {@link + * SortDirection#DESCENDING}. + * @param nested if true, the result will roll up multi-leg orders under the legs field of primary order. + * @param symbols a {@link List} of symbols to filter the result by (null for no filter). + * + * @return a {@link List} of {@link Order}s + * + * @throws AlpacaClientException thrown for {@link AlpacaClientException}s + */ + public List get(CurrentOrderStatus status, Integer limit, ZonedDateTime after, ZonedDateTime until, + SortDirection direction, Boolean nested, List symbols) throws AlpacaClientException { + HttpUrl.Builder urlBuilder = alpacaClient.urlBuilder() + .addPathSegment(endpointPathSegment); + + if (status != null) { + urlBuilder.addQueryParameter("status", status.toString()); + } + + if (limit != null) { + urlBuilder.addQueryParameter("limit", limit.toString()); + } + + if (after != null) { + urlBuilder.addQueryParameter("after", after.format(DateTimeFormatter.ISO_OFFSET_DATE_TIME)); + } + + if (until != null) { + urlBuilder.addQueryParameter("until", until.format(DateTimeFormatter.ISO_OFFSET_DATE_TIME)); + } + + if (direction != null) { + urlBuilder.addQueryParameter("direction", direction.toString()); + } + + if (nested != null) { + urlBuilder.addQueryParameter("nested", nested.toString()); + } + + if (symbols != null && !symbols.isEmpty()) { + urlBuilder.addQueryParameter("symbols", String.join(",", symbols)); + } + + Request request = alpacaClient.requestBuilder(urlBuilder.build()) + .get() + .build(); + return alpacaClient.requestObject(request, new TypeToken>() {}.getType()); + } + + /** + * Places a new order for the given account. An order request may be rejected if the account is not authorized for + * trading, or if the tradable balance is insufficient to fill the order. Note: many of the parameters for this + * method can be set to null if they aren't required for the requested {@link OrderType}. + * + * @param symbol symbol or asset ID to identify the asset to trade + * @param quantity number of shares to trade. Can be fractional. + * @param notional dollar amount to trade. Cannot work with quantity. Can only work for + * {@link OrderType#MARKET} and {@link OrderTimeInForce#DAY}. + * @param side the {@link OrderSide} + * @param type the {@link OrderType} + * @param timeInForce the {@link OrderTimeInForce} + * @param limitPrice required if type is {@link OrderType#LIMIT} or {@link OrderType#STOP_LIMIT} + * @param stopPrice required if type is {@link OrderType#STOP} or {@link OrderType#STOP_LIMIT} + * @param trailPrice this or trail_percent is required if type is {@link + * OrderType#TRAILING_STOP} + * @param trailPercent this or trail_price is required if type is {@link + * OrderType#TRAILING_STOP} + * @param extendedHours (default) false. If true, order will be eligible to execute in premarket/afterhours. + * Only works with type {@link OrderType#LIMIT} and {@link OrderTimeInForce#DAY}. + * @param clientOrderId a unique identifier for the order. Automatically generated if null. + * @param orderClass the {@link OrderClass}. For details of non-simple order classes, please see "Bracket + * Order Overview" on the Alpaca Docs. + * @param takeProfitLimitPrice additional parameter for take-profit leg of advanced orders. Required for {@link + * OrderClass#BRACKET}. + * @param stopLossStopPrice additional parameters for stop-loss leg of advanced orders. Required for {@link + * OrderClass#BRACKET}. + * @param stopLossLimitPrice additional parameters for stop-loss leg of advanced orders. The stop-loss order + * becomes a stop-limit order if specified. + * + * @return the requested {@link Order} + * + * @throws AlpacaClientException thrown for {@link AlpacaClientException}s + */ + public Order requestOrder(String symbol, Double quantity, Double notional, OrderSide side, OrderType type, + OrderTimeInForce timeInForce, Double limitPrice, Double stopPrice, Double trailPrice, Double trailPercent, + Boolean extendedHours, String clientOrderId, OrderClass orderClass, Double takeProfitLimitPrice, + Double stopLossStopPrice, Double stopLossLimitPrice) + throws AlpacaClientException { + checkNotNull(symbol); + checkState(quantity != null ^ notional != null, "Either 'quantity' or 'notional' are required."); + checkNotNull(side); + checkNotNull(type); + checkNotNull(timeInForce); + + HttpUrl.Builder urlBuilder = alpacaClient.urlBuilder() + .addPathSegment(endpointPathSegment); + + JSONBodyBuilder jsonBodyBuilder = new JSONBodyBuilder(); + jsonBodyBuilder.appendJSONBodyProperty("symbol", symbol); + jsonBodyBuilder.appendJSONBodyProperty("qty", side.toString()); + jsonBodyBuilder.appendJSONBodyProperty("type", type.toString()); + jsonBodyBuilder.appendJSONBodyProperty("time_in_force", timeInForce.toString()); + + if (quantity != null) { + jsonBodyBuilder.appendJSONBodyProperty("qty", quantity.toString()); + } else { + jsonBodyBuilder.appendJSONBodyProperty("notional", notional.toString()); + } + + if (limitPrice != null) { + jsonBodyBuilder.appendJSONBodyProperty("limit_price", FormatUtil.toCurrencyFormat(limitPrice)); + } + + if (stopPrice != null) { + jsonBodyBuilder.appendJSONBodyProperty("stop_price", FormatUtil.toCurrencyFormat(stopPrice)); + } + + if (trailPrice != null) { + jsonBodyBuilder.appendJSONBodyProperty("trail_price", FormatUtil.toCurrencyFormat(trailPrice)); + } + + if (trailPercent != null) { + jsonBodyBuilder.appendJSONBodyProperty("trail_percent", FormatUtil.toCurrencyFormat(trailPercent)); + } + + if (extendedHours != null) { + jsonBodyBuilder.appendJSONBodyJSONProperty("extended_hours", new JsonPrimitive(extendedHours)); + } + + if (clientOrderId != null) { + jsonBodyBuilder.appendJSONBodyProperty("client_order_id", clientOrderId); + } + + if (orderClass != null) { + jsonBodyBuilder.appendJSONBodyProperty("order_class", orderClass.toString()); + } + + if (takeProfitLimitPrice != null) { + JsonObject takeProfitObject = new JsonObject(); + takeProfitObject.addProperty("limit_price", takeProfitLimitPrice); + jsonBodyBuilder.appendJSONBodyJSONProperty("take_profit", takeProfitObject); + } + + if (stopLossStopPrice != null || stopLossLimitPrice != null) { + JsonObject stopLossObject = new JsonObject(); + + if (stopLossStopPrice != null) { + stopLossObject.addProperty("stop_price", stopLossStopPrice); + } + + if (stopLossLimitPrice != null) { + stopLossObject.addProperty("limit_price", stopLossLimitPrice); + } + + jsonBodyBuilder.appendJSONBodyJSONProperty("stop_loss", stopLossObject); + } + + Request request = alpacaClient.requestBuilder(urlBuilder.build()) + .post(jsonBodyBuilder.build()) + .build(); + return alpacaClient.requestObject(request, Order.class); + } + + /** + * A market order is a request to buy or sell a security at the currently available market price. It provides the + * most likely method of filling an order. Market orders fill nearly instantaneously. This method calls {@link + * #requestOrder(String, Double, Double, OrderSide, OrderType, OrderTimeInForce, Double, Double, Double, Double, + * Boolean, String, OrderClass, Double, Double, Double)} with {@link OrderType#MARKET} so check the Javadoc for that + * method for the parameter details. + * + * @return the requested {@link Order} + * + * @throws AlpacaClientException thrown for {@link AlpacaClientException}s + */ + public Order requestMarketOrder(String symbol, Integer quantity, OrderSide side, OrderTimeInForce timeInForce) + throws AlpacaClientException { + return requestOrder(symbol, quantity.doubleValue(), null, side, OrderType.MARKET, timeInForce, null, null, + null, null, null, null, OrderClass.SIMPLE, null, null, null); + } + + /** + * A market order is a request to buy or sell a security at the currently available market price. It provides the + * most likely method of filling an order. Market orders fill nearly instantaneously. This method calls {@link + * #requestOrder(String, Double, Double, OrderSide, OrderType, OrderTimeInForce, Double, Double, Double, Double, + * Boolean, String, OrderClass, Double, Double, Double)} with {@link OrderType#MARKET} and {@link + * OrderTimeInForce#DAY} with a fractional quantity so check the Javadoc for that method for the parameter details. + * + * @return the requested {@link Order} + * + * @throws AlpacaClientException thrown for {@link AlpacaClientException}s + */ + public Order requestFractionalMarketOrder(String symbol, Double quantity, OrderSide side) + throws AlpacaClientException { + return requestOrder(symbol, quantity, null, side, OrderType.MARKET, OrderTimeInForce.DAY, null, null, null, + null, null, null, null, null, null, null); + } + + /** + * A market order is a request to buy or sell a security at the currently available market price. It provides the + * most likely method of filling an order. Market orders fill nearly instantaneously. This method calls {@link + * #requestOrder(String, Double, Double, OrderSide, OrderType, OrderTimeInForce, Double, Double, Double, Double, + * Boolean, String, OrderClass, Double, Double, Double)} with {@link OrderType#MARKET} and {@link + * OrderTimeInForce#DAY} with a notional dollar amount so check the Javadoc for that method for the parameter + * details. + * + * @return the requested {@link Order} + * + * @throws AlpacaClientException thrown for {@link AlpacaClientException}s + */ + public Order requestNotionalMarketOrder(String symbol, Double notional, OrderSide side) + throws AlpacaClientException { + return requestOrder(symbol, null, notional, side, OrderType.MARKET, OrderTimeInForce.DAY, null, null, null, + null, null, null, null, null, null, null); + } + + /** + * A limit order is an order to buy or sell at a specified price or better. A buy limit order (a limit order to buy) + * is executed at the specified limit price or lower (i.e., better). Conversely, a sell limit order (a limit order + * to sell) is executed at the specified limit price or higher (better). Unlike a market order, you have to specify + * the limit price parameter when submitting your order. This method calls {@link #requestOrder(String, Double, + * Double, OrderSide, OrderType, OrderTimeInForce, Double, Double, Double, Double, Boolean, String, OrderClass, + * Double, Double, Double)} with {@link OrderType#LIMIT} so check the Javadoc for that method for the parameter + * details. + * + * @return the requested {@link Order} + * + * @throws AlpacaClientException thrown for {@link AlpacaClientException}s + */ + public Order requestLimitOrder(String symbol, Integer quantity, OrderSide side, OrderTimeInForce timeInForce, + Double limitPrice, Boolean extendedHours) throws AlpacaClientException { + return requestOrder(symbol, quantity.doubleValue(), null, side, OrderType.LIMIT, timeInForce, limitPrice, + null, null, null, extendedHours, null, OrderClass.SIMPLE, null, null, null); + } + + /** + * A stop (market) order is an order to buy or sell a security when its price moves past a particular point, + * ensuring a higher probability of achieving a predetermined entry or exit price. Once the market price crosses the + * specified stop price, the stop order becomes a market order. Alpaca converts buy stop orders into stop limit + * orders with a limit price that is 4% higher than a stop price < $50 (or 2.5% higher than a stop price >= + * $50). Sell stop orders are not converted into stop limit orders. This method calls {@link #requestOrder(String, + * Double, Double, OrderSide, OrderType, OrderTimeInForce, Double, Double, Double, Double, Boolean, String, + * OrderClass, Double, Double, Double)} with {@link OrderType#STOP} so check the Javadoc for that method for the + * parameter details. + * + * @return the requested {@link Order} + * + * @throws AlpacaClientException thrown for {@link AlpacaClientException}s + */ + public Order requestStopOrder(String symbol, Integer quantity, OrderSide side, OrderTimeInForce timeInForce, + Double stopPrice, Boolean extendedHours) throws AlpacaClientException { + return requestOrder(symbol, quantity.doubleValue(), null, side, OrderType.STOP, timeInForce, null, stopPrice, + null, null, extendedHours, null, OrderClass.SIMPLE, null, null, null); + } + + /** + * A stop-limit order is a conditional trade over a set time frame that combines the features of a stop order with + * those of a limit order and is used to mitigate risk. The stop-limit order will be executed at a specified limit + * price, or better, after a given stop price has been reached. Once the stop price is reached, the stop-limit order + * becomes a limit order to buy or sell at the limit price or better. This method calls {@link #requestOrder(String, + * Double, Double, OrderSide, OrderType, OrderTimeInForce, Double, Double, Double, Double, Boolean, String, + * OrderClass, Double, Double, Double)} with {@link OrderType#STOP_LIMIT} so check the Javadoc for that method for + * the parameter details. + * + * @return the requested {@link Order} + * + * @throws AlpacaClientException thrown for {@link AlpacaClientException}s + */ + public Order requestStopLimitOrder(String symbol, Integer quantity, OrderSide side, OrderTimeInForce timeInForce, + Double limitPrice, Double stopPrice, Boolean extendedHours) throws AlpacaClientException { + return requestOrder(symbol, quantity.doubleValue(), null, side, OrderType.STOP_LIMIT, timeInForce, + limitPrice, stopPrice, null, null, extendedHours, null, OrderClass.SIMPLE, null, null, null); + } + + /** + * A bracket order is a chain of three orders that can be used to manage your position entry and exit. It is a + * common use case of an OTOCO (One Triggers OCO {One Cancels Other}) order. This method calls {@link + * #requestOrder(String, Double, Double, OrderSide, OrderType, OrderTimeInForce, Double, Double, Double, Double, + * Boolean, String, OrderClass, Double, Double, Double)} with {@link OrderType#MARKET} and with parameters for a + * bracket order so check the Javadoc for that method for the parameter details. + * + * @return the requested {@link Order} + * + * @throws AlpacaClientException thrown for {@link AlpacaClientException}s + */ + public Order requestMarketBracketOrder(String symbol, Integer quantity, OrderSide side, + OrderTimeInForce timeInForce, Double takeProfitLimitPrice, Double stopLossStopPrice, + Double stopLossLimitPrice) throws AlpacaClientException { + return requestOrder(symbol, quantity.doubleValue(), null, side, OrderType.MARKET, timeInForce, null, null, + null, null, null, null, OrderClass.BRACKET, takeProfitLimitPrice, stopLossStopPrice, + stopLossLimitPrice); + } + + /** + * A bracket order is a chain of three orders that can be used to manage your position entry and exit. It is a + * common use case of an OTOCO (One Triggers OCO {One Cancels Other}) order. This method calls {@link + * #requestOrder(String, Double, Double, OrderSide, OrderType, OrderTimeInForce, Double, Double, Double, Double, + * Boolean, String, OrderClass, Double, Double, Double)} with {@link OrderType#LIMIT} and with parameters for a + * bracket order so check the Javadoc for that method for the parameter details. + * + * @return the requested {@link Order} + * + * @throws AlpacaClientException thrown for {@link AlpacaClientException}s + */ + public Order requestLimitBracketOrder(String symbol, Integer quantity, OrderSide side, + OrderTimeInForce timeInForce, Double limitPrice, Boolean extendedHours, Double takeProfitLimitPrice, + Double stopLossStopPrice, Double stopLossLimitPrice) throws AlpacaClientException { + return requestOrder(symbol, quantity.doubleValue(), null, side, OrderType.LIMIT, timeInForce, limitPrice, + null, null, null, extendedHours, null, OrderClass.BRACKET, takeProfitLimitPrice, stopLossStopPrice, + stopLossLimitPrice); + } + + /** + * OCO (One-Cancels-Other) is another type of advanced order type. This is a set of two orders with the same side + * (buy/buy or sell/sell) and currently only exit order is supported. In other words, this is the second part of the + * bracket orders where the entry order is already filled, and you can submit the take-profit and stop-loss in one + * order submission. This method calls {@link #requestOrder(String, Double, Double, OrderSide, OrderType, + * OrderTimeInForce, Double, Double, Double, Double, Boolean, String, OrderClass, Double, Double, Double)} with + * parameters for a {@link OrderClass#ONE_CANCELS_OTHER} so check the Javadoc for that method for the parameter + * details. + * + * @return the requested {@link Order} + * + * @throws AlpacaClientException thrown for {@link AlpacaClientException}s + */ + public Order requestOCOOrder(String symbol, Integer quantity, OrderSide side, + OrderTimeInForce timeInForce, Boolean extendedHours, Double takeProfitLimitPrice, + Double stopLossStopPrice, Double stopLossLimitPrice) throws AlpacaClientException { + return requestOrder(symbol, quantity.doubleValue(), null, side, OrderType.LIMIT, timeInForce, null, null, + null, null, extendedHours, null, OrderClass.ONE_CANCELS_OTHER, takeProfitLimitPrice, stopLossStopPrice, + stopLossLimitPrice); + } + + /** + * OTO (One-Triggers-Other) is a variant of bracket order. It takes one of the take-profit or stop-loss order in + * addition to the entry order. This method calls {@link #requestOrder(String, Double, Double, OrderSide, OrderType, + * OrderTimeInForce, Double, Double, Double, Double, Boolean, String, OrderClass, Double, Double, Double)} with + * {@link OrderType#LIMIT} and with parameters for a {@link OrderClass#ONE_CANCELS_OTHER} so check the Javadoc for + * that method for the parameter details. + * + * @return the requested {@link Order} + * + * @throws AlpacaClientException thrown for {@link AlpacaClientException}s + */ + public Order requestOTOMarketOrder(String symbol, Integer quantity, OrderSide side, + OrderTimeInForce timeInForce, Double takeProfitLimitPrice, Double stopLossStopPrice, + Double stopLossLimitPrice) throws AlpacaClientException { + return requestOrder(symbol, quantity.doubleValue(), null, side, OrderType.MARKET, timeInForce, null, null, + null, null, null, null, OrderClass.ONE_CANCELS_OTHER, takeProfitLimitPrice, stopLossStopPrice, + stopLossLimitPrice); + } + + /** + * OTO (One-Triggers-Other) is a variant of bracket order. It takes one of the take-profit or stop-loss order in + * addition to the entry order. This method calls {@link #requestOrder(String, Double, Double, OrderSide, OrderType, + * OrderTimeInForce, Double, Double, Double, Double, Boolean, String, OrderClass, Double, Double, Double)} with + * {@link OrderType#LIMIT} and with parameters for a {@link OrderClass#ONE_CANCELS_OTHER} so check the Javadoc for + * that method for the parameter details. + * + * @return the requested {@link Order} + * + * @throws AlpacaClientException thrown for {@link AlpacaClientException}s + */ + public Order requestOTOLimitOrder(String symbol, Integer quantity, OrderSide side, + OrderTimeInForce timeInForce, Double limitPrice, Boolean extendedHours, Double takeProfitLimitPrice, + Double stopLossStopPrice, Double stopLossLimitPrice) throws AlpacaClientException { + return requestOrder(symbol, quantity.doubleValue(), null, side, OrderType.LIMIT, timeInForce, limitPrice, + null, null, null, extendedHours, null, OrderClass.ONE_CANCELS_OTHER, takeProfitLimitPrice, + stopLossStopPrice, stopLossLimitPrice); + } + + /** + * OTO (One-Triggers-Other) is a variant of bracket order. It takes one of the take-profit or stop-loss order in + * addition to the entry order. This method calls {@link #requestOrder(String, Double, Double, OrderSide, OrderType, + * OrderTimeInForce, Double, Double, Double, Double, Boolean, String, OrderClass, Double, Double, Double)} with + * {@link OrderType#STOP} and with parameters for a {@link OrderClass#ONE_CANCELS_OTHER} so check the Javadoc for + * that method for the parameter details. + * + * @return the requested {@link Order} + * + * @throws AlpacaClientException thrown for {@link AlpacaClientException}s + */ + public Order requestOTOStopOrder(String symbol, Integer quantity, OrderSide side, + OrderTimeInForce timeInForce, Double stopPrice, Boolean extendedHours, Double takeProfitLimitPrice, + Double stopLossStopPrice, Double stopLossLimitPrice) throws AlpacaClientException { + return requestOrder(symbol, quantity.doubleValue(), null, side, OrderType.STOP, timeInForce, null, stopPrice, + null, null, extendedHours, null, OrderClass.ONE_CANCELS_OTHER, takeProfitLimitPrice, stopLossStopPrice, + stopLossLimitPrice); + } + + /** + * OTO (One-Triggers-Other) is a variant of bracket order. It takes one of the take-profit or stop-loss order in + * addition to the entry order. This method calls {@link #requestOrder(String, Double, Double, OrderSide, OrderType, + * OrderTimeInForce, Double, Double, Double, Double, Boolean, String, OrderClass, Double, Double, Double)} with + * {@link OrderType#STOP_LIMIT} and with parameters for a {@link OrderClass#ONE_CANCELS_OTHER} so check the Javadoc + * for that method for the parameter details. + * + * @return the requested {@link Order} + * + * @throws AlpacaClientException thrown for {@link AlpacaClientException}s + */ + public Order requestOTOStopLimitOrder(String symbol, Integer quantity, OrderSide side, + OrderTimeInForce timeInForce, Double limitPrice, Double stopPrice, Boolean extendedHours, + Double takeProfitLimitPrice, Double stopLossStopPrice, Double stopLossLimitPrice) + throws AlpacaClientException { + return requestOrder(symbol, quantity.doubleValue(), null, side, OrderType.STOP_LIMIT, timeInForce, + limitPrice, stopPrice, null, null, extendedHours, null, OrderClass.ONE_CANCELS_OTHER, + takeProfitLimitPrice, stopLossStopPrice, stopLossLimitPrice); + } + + /** + * Trailing stop orders allow you to continuously and automatically keep updating the stop price threshold based on + * the stock price movement. This method calls {@link #requestOrder(String, Double, Double, OrderSide, OrderType, + * OrderTimeInForce, Double, Double, Double, Double, Boolean, String, OrderClass, Double, Double, Double)} with + * {@link OrderType#TRAILING_STOP} and with parameters for a {@link OrderType#TRAILING_STOP} so check the Javadoc + * for that method for the parameter details. + * + * @return the requested {@link Order} + * + * @throws AlpacaClientException thrown for {@link AlpacaClientException}s + */ + public Order requestTrailingStopPriceOrder(String symbol, Integer quantity, OrderSide side, + OrderTimeInForce timeInForce, Double trailPrice, Boolean extendedHours) throws AlpacaClientException { + return requestOrder(symbol, quantity.doubleValue(), null, side, OrderType.TRAILING_STOP, timeInForce, null, + null, trailPrice, null, extendedHours, null, null, null, null, null); + } + + /** + * Trailing stop orders allow you to continuously and automatically keep updating the stop price threshold based on + * the stock price movement. This method calls {@link #requestOrder(String, Double, Double, OrderSide, OrderType, + * OrderTimeInForce, Double, Double, Double, Double, Boolean, String, OrderClass, Double, Double, Double)} with + * {@link OrderType#TRAILING_STOP} and with parameters for a {@link OrderType#TRAILING_STOP} so check the Javadoc + * for that method for the parameter details. + * + * @return the requested {@link Order} + * + * @throws AlpacaClientException thrown for {@link AlpacaClientException}s + */ + public Order requestTrailingStopPercentOrder(String symbol, Integer quantity, OrderSide side, + OrderTimeInForce timeInForce, Double trailPercent, Boolean extendedHours) throws AlpacaClientException { + return requestOrder(symbol, quantity.doubleValue(), null, side, OrderType.TRAILING_STOP, timeInForce, null, + null, null, trailPercent, extendedHours, null, null, null, null, null); + } + + /** + * Retrieves a single {@link Order} for the given orderID. + * + * @param orderID the {@link Order#getId()} + * @param nested if true, the result will roll up multi-leg orders under the legs field of primary order. + * + * @return the {@link Order} + * + * @throws AlpacaClientException thrown for {@link AlpacaClientException}s + */ + public Order get(String orderID, Boolean nested) throws AlpacaClientException { + checkNotNull(orderID); + + HttpUrl.Builder urlBuilder = alpacaClient.urlBuilder() + .addPathSegment(endpointPathSegment) + .addPathSegment(orderID); + + if (nested != null) { + urlBuilder.addQueryParameter("nested", nested.toString()); + } + + Request request = alpacaClient.requestBuilder(urlBuilder.build()) + .get() + .build(); + return alpacaClient.requestObject(request, Order.class); + } + + /** + * Retrieves a single {@link Order} for the given clientOrderId. + * + * @param clientOrderID the {@link Order#getClientOrderId()} + * + * @return the {@link Order} + * + * @throws AlpacaClientException thrown for {@link AlpacaClientException}s + */ + public Order getByClientID(String clientOrderID) throws AlpacaClientException { + checkNotNull(clientOrderID); + + HttpUrl.Builder urlBuilder = alpacaClient.urlBuilder() + .addPathSegment("orders:by_client_order_id"); + urlBuilder.addQueryParameter("client_order_id", clientOrderID); + + Request request = alpacaClient.requestBuilder(urlBuilder.build()) + .get() + .build(); + return alpacaClient.requestObject(request, Order.class); + } + + /** + * Replaces a single {@link Order} with updated parameters. Each parameter overrides the corresponding attribute of + * the existing {@link Order}. The other attributes remain the same as the existing {@link Order}. + * + * @param orderID the {@link Order#getId()} + * @param quantity number of shares to trade. Can be fractional. + * @param timeInForce the {@link OrderTimeInForce} + * @param limitPrice required if type is {@link OrderType#LIMIT} or {@link OrderType#STOP_LIMIT} + * @param stopPrice required if type is {@link OrderType#STOP} or {@link OrderType#STOP_LIMIT} + * @param trail the new value of the trail_price or trail_percent value (works + * only for {@link Order#getType()} == {@link OrderType#TRAILING_STOP}) + * @param clientOrderId a unique identifier for the order. Automatically generated if null. + * + * @return a new {@link Order} + * + * @throws AlpacaClientException thrown for {@link AlpacaClientException}s + */ + public Order replace(String orderID, Integer quantity, OrderTimeInForce timeInForce, Double limitPrice, + Double stopPrice, Double trail, String clientOrderId) throws AlpacaClientException { + checkNotNull(orderID); + + HttpUrl.Builder urlBuilder = alpacaClient.urlBuilder() + .addPathSegment(endpointPathSegment) + .addPathSegment(orderID); + + JSONBodyBuilder jsonBodyBuilder = new JSONBodyBuilder(); + + if (quantity != null) { + jsonBodyBuilder.appendJSONBodyProperty("qty", quantity.toString()); + } + + if (timeInForce != null) { + jsonBodyBuilder.appendJSONBodyProperty("time_in_force", timeInForce.toString()); + } + + if (limitPrice != null) { + jsonBodyBuilder.appendJSONBodyProperty("limit_price", FormatUtil.toCurrencyFormat(limitPrice)); + } + + if (stopPrice != null) { + jsonBodyBuilder.appendJSONBodyProperty("stop_price", FormatUtil.toCurrencyFormat(stopPrice)); + } + + if (trail != null) { + jsonBodyBuilder.appendJSONBodyProperty("trail", FormatUtil.toCurrencyFormat(trail)); + } + + if (clientOrderId != null) { + jsonBodyBuilder.appendJSONBodyProperty("client_order_id", clientOrderId); + } + + Request request = alpacaClient.requestBuilder(urlBuilder.build()) + .patch(jsonBodyBuilder.build()) + .build(); + return alpacaClient.requestObject(request, Order.class); + } + + /** + * Attempts to cancel all open {@link Order}s. A response will be provided for each {@link Order} that is attempted + * to be cancelled. If an {@link Order} is no longer cancelable, the server will respond with status + * 500 and reject the request. + * + * @return a {@link List} of {@link CancelledOrder}s + * + * @throws AlpacaClientException thrown for {@link AlpacaClientException}s + */ + public ArrayList cancelAll() throws AlpacaClientException { + HttpUrl.Builder urlBuilder = alpacaClient.urlBuilder() + .addPathSegment(endpointPathSegment); + Request request = alpacaClient.requestBuilder(urlBuilder.build()) + .delete() + .build(); + return alpacaClient.requestObject(request, (code) -> code == 200 || code == 207, + new TypeToken>() {}.getType()); + } + + /** + * Attempts to cancel an open {@link Order}. If the {@link Order} is no longer cancelable (example: {@link + * Order#getStatus()} == {@link OrderStatus#FILLED}), the server will respond with status + * 422, and reject the request. + * + * @param orderID the {@link Order#getId()} + * + * @throws AlpacaClientException thrown for {@link AlpacaClientException}s + */ + public void cancel(String orderID) throws AlpacaClientException { + checkNotNull(orderID); + + HttpUrl.Builder urlBuilder = alpacaClient.urlBuilder() + .addPathSegment(endpointPathSegment) + .addPathSegment(orderID); + Request request = alpacaClient.requestBuilder(urlBuilder.build()) + .delete() + .build(); + alpacaClient.request(request, (code) -> code == 200 || code == 204); + } +} diff --git a/src/main/java/net/jacobpeterson/alpaca/refactor/rest/endpoint/PortfolioHistoryEndpoint.java b/src/main/java/net/jacobpeterson/alpaca/refactor/rest/endpoint/PortfolioHistoryEndpoint.java new file mode 100644 index 00000000..72f507f6 --- /dev/null +++ b/src/main/java/net/jacobpeterson/alpaca/refactor/rest/endpoint/PortfolioHistoryEndpoint.java @@ -0,0 +1,99 @@ +package net.jacobpeterson.alpaca.refactor.rest.endpoint; + +import net.jacobpeterson.alpaca.model.endpoint.portfoliohistory.PortfolioHistory; +import net.jacobpeterson.alpaca.model.endpoint.portfoliohistory.PortfolioHistoryDataPoint; +import net.jacobpeterson.alpaca.model.endpoint.portfoliohistory.PortfolioHistoryResponse; +import net.jacobpeterson.alpaca.model.endpoint.portfoliohistory.enums.PortfolioPeriodUnit; +import net.jacobpeterson.alpaca.model.endpoint.portfoliohistory.enums.PortfolioTimeFrame; +import net.jacobpeterson.alpaca.refactor.rest.AlpacaClient; +import net.jacobpeterson.alpaca.refactor.rest.AlpacaClientException; +import okhttp3.HttpUrl; +import okhttp3.Request; + +import java.time.LocalDate; +import java.time.format.DateTimeFormatter; +import java.util.ArrayList; + +/** + * {@link AbstractEndpoint} for + * Portfolio + * History. + */ +public class PortfolioHistoryEndpoint extends AbstractEndpoint { + + /** + * Instantiates a new {@link PortfolioHistoryEndpoint}. + * + * @param alpacaClient the {@link AlpacaClient} + */ + public PortfolioHistoryEndpoint(AlpacaClient alpacaClient) { + super(alpacaClient, "account/portfolio/history"); + } + + /** + * Returns timeseries data about equity and profit/loss (P/L) of the account in requested timespan. + * + * @param periodLength the duration of the periodUnit. Defaults to 1. + * @param periodUnit the {@link PortfolioPeriodUnit}. Defaults to {@link PortfolioPeriodUnit#MONTH}. + * @param timeFrame the resolution of time window. If omitted, {@link PortfolioTimeFrame#ONE_MIN} for less than + * 7 days period, {@link PortfolioTimeFrame#FIFTEEN_MINUTE} for less than 30 days, or otherwise + * {@link PortfolioTimeFrame#ONE_DAY}. + * @param dateEnd the date the data is returned up to. Defaults to the current market date (rolls over at the + * market open if extended_hours is false, otherwise at 7 AM ET) + * @param extendedHours if true, include extended hours in the result. This is effective only for timeframe less + * than {@link PortfolioTimeFrame#ONE_DAY}. + * + * @return the {@link PortfolioHistory} + * + * @throws AlpacaClientException thrown for {@link AlpacaClientException}s + */ + public PortfolioHistory get(Integer periodLength, PortfolioPeriodUnit periodUnit, PortfolioTimeFrame timeFrame, + LocalDate dateEnd, Boolean extendedHours) throws AlpacaClientException { + HttpUrl.Builder urlBuilder = alpacaClient.urlBuilder() + .addPathSegments(endpointPathSegment); + + if (periodLength != null && periodUnit != null) { + urlBuilder.addQueryParameter("period", periodLength + periodUnit.toString()); + } + + if (timeFrame != null) { + urlBuilder.addQueryParameter("timeframe", timeFrame.toString()); + } + + if (dateEnd != null) { + urlBuilder.addQueryParameter("date_end", dateEnd.format(DateTimeFormatter.ISO_DATE)); + } + + if (extendedHours != null) { + urlBuilder.addQueryParameter("extended_hours", extendedHours.toString()); + } + + Request request = alpacaClient.requestBuilder(urlBuilder.build()) + .get() + .build(); + PortfolioHistoryResponse portfolioHistoryResponse = alpacaClient.requestObject(request, + PortfolioHistoryResponse.class); + + // Check if any response arrays differ in size + if (portfolioHistoryResponse.getTimestamp().size() != portfolioHistoryResponse.getEquity().size() || + portfolioHistoryResponse.getEquity().size() != portfolioHistoryResponse.getProfitLoss().size() || + portfolioHistoryResponse.getProfitLoss().size() != portfolioHistoryResponse.getProfitLossPct().size()) { + throw new IllegalStateException("Response arrays should never differ in size!"); + } + + // Add all data points into one POJO + ArrayList dataPoints = new ArrayList<>(); + for (int index = 0; index < portfolioHistoryResponse.getTimestamp().size(); index++) { + dataPoints.add(new PortfolioHistoryDataPoint( + portfolioHistoryResponse.getTimestamp().get(index), + portfolioHistoryResponse.getEquity().get(index), + portfolioHistoryResponse.getProfitLoss().get(index), + portfolioHistoryResponse.getProfitLossPct().get(index))); + } + + return new PortfolioHistory( + dataPoints, + portfolioHistoryResponse.getBaseValue(), + portfolioHistoryResponse.getTimeframe()); + } +} diff --git a/src/main/java/net/jacobpeterson/alpaca/refactor/rest/endpoint/PositionsEndpoint.java b/src/main/java/net/jacobpeterson/alpaca/refactor/rest/endpoint/PositionsEndpoint.java new file mode 100644 index 00000000..7ab64813 --- /dev/null +++ b/src/main/java/net/jacobpeterson/alpaca/refactor/rest/endpoint/PositionsEndpoint.java @@ -0,0 +1,127 @@ +package net.jacobpeterson.alpaca.refactor.rest.endpoint; + +import com.google.gson.reflect.TypeToken; +import net.jacobpeterson.alpaca.model.endpoint.asset.Asset; +import net.jacobpeterson.alpaca.model.endpoint.order.Order; +import net.jacobpeterson.alpaca.model.endpoint.position.ClosePositionOrder; +import net.jacobpeterson.alpaca.model.endpoint.position.Position; +import net.jacobpeterson.alpaca.refactor.rest.AlpacaClient; +import net.jacobpeterson.alpaca.refactor.rest.AlpacaClientException; +import okhttp3.HttpUrl; +import okhttp3.Request; + +import java.util.ArrayList; +import java.util.List; + +import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.base.Preconditions.checkNotNull; + +/** + * {@link AbstractEndpoint} for Positions. + */ +public class PositionsEndpoint extends AbstractEndpoint { + + /** + * Instantiates a new {@link PositionsEndpoint}. + * + * @param alpacaClient the {@link AlpacaClient} + */ + public PositionsEndpoint(AlpacaClient alpacaClient) { + super(alpacaClient, "positions"); + } + + /** + * Retrieves a list of the account's open {@link Position}s. + * + * @return a {@link List} of open {@link Position}s + * + * @throws AlpacaClientException thrown for {@link AlpacaClientException}s + */ + public List get() throws AlpacaClientException { + HttpUrl.Builder urlBuilder = alpacaClient.urlBuilder() + .addPathSegment(endpointPathSegment); + Request request = alpacaClient.requestBuilder(urlBuilder.build()) + .get() + .build(); + return alpacaClient.requestObject(request, new TypeToken>() {}.getType()); + } + + /** + * Retrieves the account's open {@link Position}s for the given symbol or {@link Asset#getId()}. + * + * @param symbolOrAssetID the symbol or {@link Asset#getId()} (required) + * + * @return the open {@link Position} + * + * @throws AlpacaClientException thrown for {@link AlpacaClientException}s + */ + public Position getBySymbol(String symbolOrAssetID) throws AlpacaClientException { + checkNotNull(symbolOrAssetID); + + HttpUrl.Builder urlBuilder = alpacaClient.urlBuilder() + .addPathSegment(endpointPathSegment) + .addPathSegment(symbolOrAssetID); + Request request = alpacaClient.requestBuilder(urlBuilder.build()) + .get() + .build(); + return alpacaClient.requestObject(request, Position.class); + } + + /** + * Closes (liquidates) all of the account’s open long and short {@link Position}s. A response will be provided for + * each order that is attempted to be cancelled. If an order is no longer cancelable, the server will respond with + * status 500 and reject the request. + * + * @param cancelOrders if true is specified, cancel all open orders before liquidating all positions + * + * @return a {@link List} of open {@link ClosePositionOrder}s + * + * @throws AlpacaClientException thrown for {@link AlpacaClientException}s + */ + public List closeAll(Boolean cancelOrders) throws AlpacaClientException { + HttpUrl.Builder urlBuilder = alpacaClient.urlBuilder() + .addPathSegment(endpointPathSegment); + + if (cancelOrders != null) { + urlBuilder.addQueryParameter("cancel_orders", cancelOrders.toString()); + } + + Request request = alpacaClient.requestBuilder(urlBuilder.build()) + .delete() + .build(); + // TODO response code might be 207 instead of 200 + return alpacaClient.requestObject(request, new TypeToken>() {}.getType()); + } + + /** + * Closes (liquidates) the account’s open position for the given symbol. Works for both long and short + * positions. + * + * @param symbolOrAssetID the symbol or {@link Asset#getId()} + * + * @return a closing {@link Position} {@link Order} + * + * @throws AlpacaClientException thrown for {@link AlpacaClientException}s + */ + public Order close(String symbolOrAssetID, Integer quantity, Double percentage) throws AlpacaClientException { + checkNotNull(symbolOrAssetID); + checkArgument(quantity != null ^ percentage != null, "Either 'quantity' or 'percentage' are required."); + + HttpUrl.Builder urlBuilder = alpacaClient.urlBuilder() + .addPathSegment(endpointPathSegment) + .addPathSegment(symbolOrAssetID); + + if (quantity != null) { + urlBuilder.addQueryParameter("qty", quantity.toString()); + } + + if (percentage != null) { + urlBuilder.addQueryParameter("percentage", percentage.toString()); + } + + Request request = alpacaClient.requestBuilder(urlBuilder.build()) + .delete() + .build(); + return alpacaClient.requestObject(request, Order.class); + } +} diff --git a/src/main/java/net/jacobpeterson/alpaca/refactor/rest/endpoint/WatchlistEndpoint.java b/src/main/java/net/jacobpeterson/alpaca/refactor/rest/endpoint/WatchlistEndpoint.java new file mode 100644 index 00000000..f572cbda --- /dev/null +++ b/src/main/java/net/jacobpeterson/alpaca/refactor/rest/endpoint/WatchlistEndpoint.java @@ -0,0 +1,211 @@ +package net.jacobpeterson.alpaca.refactor.rest.endpoint; + +import com.google.gson.JsonArray; +import com.google.gson.reflect.TypeToken; +import net.jacobpeterson.alpaca.model.endpoint.asset.Asset; +import net.jacobpeterson.alpaca.model.endpoint.watchlist.Watchlist; +import net.jacobpeterson.alpaca.refactor.rest.AlpacaClient; +import net.jacobpeterson.alpaca.refactor.rest.AlpacaClientException; +import net.jacobpeterson.alpaca.refactor.util.okhttp.JSONBodyBuilder; +import okhttp3.HttpUrl; +import okhttp3.Request; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.base.Preconditions.checkNotNull; + +/** + * {@link AbstractEndpoint} for Watchlists + * . + */ +public class WatchlistEndpoint extends AbstractEndpoint { + + /** + * Instantiates a new {@link WatchlistEndpoint}. + * + * @param alpacaClient the {@link AlpacaClient} + */ + public WatchlistEndpoint(AlpacaClient alpacaClient) { + super(alpacaClient, "watchlists"); + } + + /** + * Returns the list of {@link Watchlist}s registered under the account. + * + * @return a {@link List} of {@link Watchlist}s + * + * @throws AlpacaClientException thrown for {@link AlpacaClientException}s + */ + public List get() throws AlpacaClientException { + HttpUrl.Builder urlBuilder = alpacaClient.urlBuilder() + .addPathSegment(endpointPathSegment); + Request request = alpacaClient.requestBuilder(urlBuilder.build()) + .get() + .build(); + return alpacaClient.requestObject(request, new TypeToken>() {}.getType()); + } + + /** + * Creates a new {@link Watchlist} with initial set of {@link Asset}s. + * + * @param name arbitrary name string, up to 64 characters + * @param symbols set of symbols {@link String}s + * + * @return the created {@link Watchlist} + * + * @throws AlpacaClientException thrown for {@link AlpacaClientException}s + */ + public Watchlist create(String name, String... symbols) throws AlpacaClientException { + checkNotNull(name); + checkArgument(name.length() <= 64); + + HttpUrl.Builder urlBuilder = alpacaClient.urlBuilder() + .addPathSegment(endpointPathSegment); + + JSONBodyBuilder jsonBodyBuilder = new JSONBodyBuilder(); + jsonBodyBuilder.appendJSONBodyProperty("name", name); + + if (symbols != null && symbols.length != 0) { + JsonArray symbolsArray = new JsonArray(); + Arrays.stream(symbols).forEach(symbolsArray::add); + jsonBodyBuilder.appendJSONBodyJSONProperty("symbols", symbolsArray); + } + + Request request = alpacaClient.requestBuilder(urlBuilder.build()) + .post(jsonBodyBuilder.build()) + .build(); + return alpacaClient.requestObject(request, Watchlist.class); + } + + /** + * Returns a {@link Watchlist} identified by the ID. + * + * @param watchlistID the {@link Watchlist#getId()} + * + * @return the {@link Watchlist} + * + * @throws AlpacaClientException thrown for {@link AlpacaClientException}s + */ + public Watchlist get(String watchlistID) throws AlpacaClientException { + checkNotNull(watchlistID); + + HttpUrl.Builder urlBuilder = alpacaClient.urlBuilder() + .addPathSegment(endpointPathSegment) + .addPathSegment(watchlistID); + Request request = alpacaClient.requestBuilder(urlBuilder.build()) + .get() + .build(); + return alpacaClient.requestObject(request, Watchlist.class); + } + + /** + * Updates the name and/or content of a {@link Watchlist}. + * + * @param watchlistID the {@link Watchlist#getId()} + * @param name the new {@link Watchlist} name + * @param symbols the new list of symbol names to replace the {@link Watchlist} content + * + * @return the updated {@link Watchlist} + * + * @throws AlpacaClientException thrown for {@link AlpacaClientException}s + */ + public Watchlist update(String watchlistID, String name, String... symbols) + throws AlpacaClientException { + checkNotNull(watchlistID); + checkArgument(name == null || name.length() <= 64); + + HttpUrl.Builder urlBuilder = alpacaClient.urlBuilder() + .addPathSegment(endpointPathSegment) + .addPathSegment(watchlistID); + + JSONBodyBuilder jsonBodyBuilder = new JSONBodyBuilder(); + + if (name != null) { + jsonBodyBuilder.appendJSONBodyProperty("name", name); + } + + if (symbols != null && symbols.length != 0) { + JsonArray symbolsArray = new JsonArray(); + Arrays.stream(symbols).forEach(symbolsArray::add); + jsonBodyBuilder.appendJSONBodyJSONProperty("symbols", symbolsArray); + } + + Request request = alpacaClient.requestBuilder(urlBuilder.build()) + .put(jsonBodyBuilder.build()) + .build(); + return alpacaClient.requestObject(request, Watchlist.class); + } + + /** + * Append an asset for the symbol to the end of {@link Watchlist} asset list. + * + * @param watchlistID the {@link Watchlist#getId()} + * @param symbol the symbol name to add to the {@link Watchlist} + * + * @return the updated {@link Watchlist} + * + * @throws AlpacaClientException thrown for {@link AlpacaClientException}s + */ + public Watchlist addAsset(String watchlistID, String symbol) throws AlpacaClientException { + checkNotNull(watchlistID); + checkNotNull(symbol); + + HttpUrl.Builder urlBuilder = alpacaClient.urlBuilder() + .addPathSegment(endpointPathSegment) + .addPathSegment(watchlistID); + + JSONBodyBuilder jsonBodyBuilder = new JSONBodyBuilder(); + jsonBodyBuilder.appendJSONBodyProperty("symbol", symbol); + + Request request = alpacaClient.requestBuilder(urlBuilder.build()) + .post(jsonBodyBuilder.build()) + .build(); + return alpacaClient.requestObject(request, Watchlist.class); + } + + /** + * Deletes a {@link Watchlist}. This is a permanent deletion. + * + * @param watchlistID the {@link Watchlist#getId()} + * + * @throws AlpacaClientException thrown for {@link AlpacaClientException}s + */ + public void delete(String watchlistID) throws AlpacaClientException { + checkNotNull(watchlistID); + + HttpUrl.Builder urlBuilder = alpacaClient.urlBuilder() + .addPathSegment(endpointPathSegment) + .addPathSegment(watchlistID); + Request request = alpacaClient.requestBuilder(urlBuilder.build()) + .delete() + .build(); + alpacaClient.request(request, (code) -> code == 200 || code == 204); + } + + /** + * Delete one entry for an asset by symbol name. + * + * @param watchlistID the {@link Watchlist#getId()} + * @param symbol symbol name to remove from the {@link Watchlist} content + * + * @return the updated {@link Watchlist} + * + * @throws AlpacaClientException thrown for {@link AlpacaClientException}s + */ + public Watchlist removeSymbol(String watchlistID, String symbol) throws AlpacaClientException { + checkNotNull(watchlistID); + checkNotNull(symbol); + + HttpUrl.Builder urlBuilder = alpacaClient.urlBuilder() + .addPathSegment(endpointPathSegment) + .addPathSegment(watchlistID) + .addPathSegment(symbol); + Request request = alpacaClient.requestBuilder(urlBuilder.build()) + .delete() + .build(); + return alpacaClient.requestObject(request, (code) -> code == 200 || code == 204, Watchlist.class); + } +} diff --git a/src/main/java/net/jacobpeterson/alpaca/util/doc/Documentation2JSONSchema.java b/src/main/java/net/jacobpeterson/alpaca/refactor/util/doc/Documentation2JSONSchema.java similarity index 99% rename from src/main/java/net/jacobpeterson/alpaca/util/doc/Documentation2JSONSchema.java rename to src/main/java/net/jacobpeterson/alpaca/refactor/util/doc/Documentation2JSONSchema.java index 29fbf597..4d4f26ad 100644 --- a/src/main/java/net/jacobpeterson/alpaca/util/doc/Documentation2JSONSchema.java +++ b/src/main/java/net/jacobpeterson/alpaca/refactor/util/doc/Documentation2JSONSchema.java @@ -1,4 +1,4 @@ -package net.jacobpeterson.alpaca.util.doc; +package net.jacobpeterson.alpaca.refactor.util.doc; import org.w3c.dom.Document; import org.w3c.dom.NodeList; diff --git a/src/main/java/net/jacobpeterson/alpaca/util/format/FormatUtil.java b/src/main/java/net/jacobpeterson/alpaca/refactor/util/format/FormatUtil.java similarity index 96% rename from src/main/java/net/jacobpeterson/alpaca/util/format/FormatUtil.java rename to src/main/java/net/jacobpeterson/alpaca/refactor/util/format/FormatUtil.java index 39567d24..656d9bd0 100644 --- a/src/main/java/net/jacobpeterson/alpaca/util/format/FormatUtil.java +++ b/src/main/java/net/jacobpeterson/alpaca/refactor/util/format/FormatUtil.java @@ -1,4 +1,4 @@ -package net.jacobpeterson.alpaca.util.format; +package net.jacobpeterson.alpaca.refactor.util.format; import java.text.DecimalFormat; import java.text.DecimalFormatSymbols; diff --git a/src/main/java/net/jacobpeterson/alpaca/refactor/util/gson/GsonUtil.java b/src/main/java/net/jacobpeterson/alpaca/refactor/util/gson/GsonUtil.java new file mode 100644 index 00000000..6c6cb150 --- /dev/null +++ b/src/main/java/net/jacobpeterson/alpaca/refactor/util/gson/GsonUtil.java @@ -0,0 +1,21 @@ +package net.jacobpeterson.alpaca.refactor.util.gson; + +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; + +import java.time.LocalDate; +import java.time.ZonedDateTime; + +/** + * {@link GsonUtil} contains utility methods relating to {@link Gson}. + */ +public class GsonUtil { + + /** A {@link Gson} instance with registered date/time adapters. */ + public static final Gson GSON = new GsonBuilder() + .registerTypeAdapter(ZonedDateTime.class, new ZonedDateTimeAdapter()) + .registerTypeAdapter(LocalDate.class, new LocalDateAdapter()) + .enableComplexMapKeySerialization() + .setLenient() + .create(); +} diff --git a/src/main/java/net/jacobpeterson/alpaca/util/gson/LocalDateAdapter.java b/src/main/java/net/jacobpeterson/alpaca/refactor/util/gson/LocalDateAdapter.java similarity index 95% rename from src/main/java/net/jacobpeterson/alpaca/util/gson/LocalDateAdapter.java rename to src/main/java/net/jacobpeterson/alpaca/refactor/util/gson/LocalDateAdapter.java index 5a09b3cb..67744898 100644 --- a/src/main/java/net/jacobpeterson/alpaca/util/gson/LocalDateAdapter.java +++ b/src/main/java/net/jacobpeterson/alpaca/refactor/util/gson/LocalDateAdapter.java @@ -1,4 +1,4 @@ -package net.jacobpeterson.alpaca.util.gson; +package net.jacobpeterson.alpaca.refactor.util.gson; import com.google.gson.JsonDeserializationContext; import com.google.gson.JsonDeserializer; diff --git a/src/main/java/net/jacobpeterson/alpaca/util/gson/ZonedDateTimeAdapter.java b/src/main/java/net/jacobpeterson/alpaca/refactor/util/gson/ZonedDateTimeAdapter.java similarity index 95% rename from src/main/java/net/jacobpeterson/alpaca/util/gson/ZonedDateTimeAdapter.java rename to src/main/java/net/jacobpeterson/alpaca/refactor/util/gson/ZonedDateTimeAdapter.java index 728161fd..a8206604 100644 --- a/src/main/java/net/jacobpeterson/alpaca/util/gson/ZonedDateTimeAdapter.java +++ b/src/main/java/net/jacobpeterson/alpaca/refactor/util/gson/ZonedDateTimeAdapter.java @@ -1,4 +1,4 @@ -package net.jacobpeterson.alpaca.util.gson; +package net.jacobpeterson.alpaca.refactor.util.gson; import com.google.gson.JsonDeserializationContext; import com.google.gson.JsonDeserializer; diff --git a/src/main/java/net/jacobpeterson/alpaca/refactor/util/okhttp/JSONBodyBuilder.java b/src/main/java/net/jacobpeterson/alpaca/refactor/util/okhttp/JSONBodyBuilder.java new file mode 100644 index 00000000..84852b7a --- /dev/null +++ b/src/main/java/net/jacobpeterson/alpaca/refactor/util/okhttp/JSONBodyBuilder.java @@ -0,0 +1,63 @@ +package net.jacobpeterson.alpaca.refactor.util.okhttp; + +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import okhttp3.MediaType; +import okhttp3.RequestBody; + +/** + * {@link JSONBodyBuilder} is builder HTTP bodies. + */ +public class JSONBodyBuilder { + + /** The UTF-8 JSON {@link MediaType}. */ + public static final MediaType JSON_MEDIA_TYPE = MediaType.get("application/json; charset=utf-8"); + + private String bodyJSON; + private JsonObject bodyJsonObject; + + /** + * Instantiates a new {@link JSONBodyBuilder}. + */ + public JSONBodyBuilder() { + bodyJsonObject = new JsonObject(); + } + + /** + * Instantiates a new {@link JSONBodyBuilder}. + * + * @param bodyJSON a JSON body {@link String} that is used instead of the internal {@link #bodyJsonObject} + */ + public JSONBodyBuilder(String bodyJSON) { + this.bodyJSON = bodyJSON; + } + + /** + * Appends a {@link String} to {@link #bodyJsonObject}. + * + * @param key the key + * @param value the value + */ + public void appendJSONBodyProperty(String key, String value) { + bodyJsonObject.addProperty(key, value); + } + + /** + * Appends a {@link JsonElement} property to {@link #bodyJsonObject}. + * + * @param key the key + * @param value the {@link JsonElement} value + */ + public void appendJSONBodyJSONProperty(String key, JsonElement value) { + bodyJsonObject.add(key, value); + } + + /** + * Builds a {@link RequestBody}. + * + * @return a {@link RequestBody} + */ + public RequestBody build() { + return RequestBody.create(bodyJSON != null ? bodyJSON : bodyJsonObject.toString(), JSON_MEDIA_TYPE); + } +} diff --git a/src/main/java/net/jacobpeterson/alpaca/util/properties/PropertyUtil.java b/src/main/java/net/jacobpeterson/alpaca/refactor/util/properties/PropertyUtil.java similarity index 98% rename from src/main/java/net/jacobpeterson/alpaca/util/properties/PropertyUtil.java rename to src/main/java/net/jacobpeterson/alpaca/refactor/util/properties/PropertyUtil.java index e8e27bc2..131c117f 100644 --- a/src/main/java/net/jacobpeterson/alpaca/util/properties/PropertyUtil.java +++ b/src/main/java/net/jacobpeterson/alpaca/refactor/util/properties/PropertyUtil.java @@ -1,4 +1,4 @@ -package net.jacobpeterson.alpaca.util.properties; +package net.jacobpeterson.alpaca.refactor.util.properties; import org.slf4j.Logger; import org.slf4j.LoggerFactory; diff --git a/src/main/java/net/jacobpeterson/alpaca/rest/AlpacaRequest.java b/src/main/java/net/jacobpeterson/alpaca/rest/AlpacaRequest.java deleted file mode 100644 index 88d2c5cc..00000000 --- a/src/main/java/net/jacobpeterson/alpaca/rest/AlpacaRequest.java +++ /dev/null @@ -1,227 +0,0 @@ -package net.jacobpeterson.alpaca.rest; - -import com.google.common.base.Preconditions; -import com.mashape.unirest.http.HttpResponse; -import com.mashape.unirest.http.exceptions.UnirestException; -import net.jacobpeterson.alpaca.AlpacaAPI; -import net.jacobpeterson.alpaca.abstracts.rest.AbstractRequest; -import net.jacobpeterson.alpaca.properties.AlpacaProperties; -import net.jacobpeterson.alpaca.rest.exception.AlpacaAPIRequestException; - -import java.io.InputStream; -import java.lang.reflect.Type; - -/** - * {@link AlpacaRequest} contains methods for HTTP requests for {@link AlpacaAPI}. - */ -public class AlpacaRequest extends AbstractRequest { - - private static final String USER_AGENT_KEY = "user-agent"; - private static final String API_KEY_ID = "APCA-API-KEY-ID"; - private static final String API_SECRET_KEY = "APCA-API-SECRET-KEY"; - private static final String AUTH_TOKEN = "Authorization"; - - /** - * Instantiates a new {@link AlpacaRequest}. - * - * @param keyID the key ID - * @param secretKey the secret key - */ - public AlpacaRequest(String keyID, String secretKey) { - super(); - Preconditions.checkNotNull(keyID); - Preconditions.checkNotNull(secretKey); - - headers.put(USER_AGENT_KEY, AlpacaProperties.USER_AGENT_VALUE); - headers.put(API_KEY_ID, keyID); - headers.put(API_SECRET_KEY, secretKey); - } - - /** - * Instantiates a new {@link AlpacaRequest}. - * - * @param oAuthToken the OAuth token - */ - public AlpacaRequest(String oAuthToken) { - super(); - Preconditions.checkNotNull(oAuthToken); - - headers.put(USER_AGENT_KEY, AlpacaProperties.USER_AGENT_VALUE); - headers.put(AUTH_TOKEN, "Bearer " + oAuthToken); - } - - /** - * Invokes a GET request and handles/returns the response object. - * - * @param the type - * @param urlBuilder the {@link AlpacaRequestBuilder} - * @param type the type to return - * - * @return the response object type - * - * @throws AlpacaAPIRequestException thrown for {@link AlpacaAPIRequestException}s - */ - public T get(AlpacaRequestBuilder urlBuilder, Type type) throws AlpacaAPIRequestException { - try { - HttpResponse response = invokeGet(urlBuilder); - - if (response.getStatus() != 200) { - throw new AlpacaAPIRequestException(response); - } - - return getResponseObject(response, type); - } catch (UnirestException exception) { - throw new AlpacaAPIRequestException(exception); - } - } - - /** - * Invokes a HEAD request and handles/returns the response object. - * - * @param the type - * @param urlBuilder the {@link AlpacaRequestBuilder} - * @param type the type to return - * - * @return the response object type - * - * @throws AlpacaAPIRequestException thrown for {@link AlpacaAPIRequestException}s - */ - public T head(AlpacaRequestBuilder urlBuilder, Type type) throws AlpacaAPIRequestException { - try { - HttpResponse response = invokeHead(urlBuilder); - - if (response.getStatus() != 200) { - throw new AlpacaAPIRequestException(response); - } - - return getResponseObject(response, type); - } catch (UnirestException exception) { - throw new AlpacaAPIRequestException(exception); - } - } - - /** - * Invokes a POST request and handles/returns the response object. - * - * @param the type - * @param urlBuilder the {@link AlpacaRequestBuilder} - * @param type the type to return - * - * @return the response object type - * - * @throws AlpacaAPIRequestException thrown for {@link AlpacaAPIRequestException}s - */ - public T post(AlpacaRequestBuilder urlBuilder, Type type) throws AlpacaAPIRequestException { - try { - HttpResponse response = invokePost(urlBuilder); - - if (response.getStatus() != 200) { - throw new AlpacaAPIRequestException(response); - } - - return getResponseObject(response, type); - } catch (UnirestException exception) { - throw new AlpacaAPIRequestException(exception); - } - } - - /** - * Invokes a PATCH request and handles/returns the response object. - * - * @param the type - * @param urlBuilder the {@link AlpacaRequestBuilder} - * @param type the type to return - * - * @return the response object type - * - * @throws AlpacaAPIRequestException thrown for {@link AlpacaAPIRequestException}s - */ - public T patch(AlpacaRequestBuilder urlBuilder, Type type) throws AlpacaAPIRequestException { - try { - HttpResponse response = invokePatch(urlBuilder); - - if (response.getStatus() != 200) { - throw new AlpacaAPIRequestException(response); - } - - return getResponseObject(response, type); - } catch (UnirestException exception) { - throw new AlpacaAPIRequestException(exception); - } - } - - /** - * Invokes a POST request and handles/returns the response object. - * - * @param the type - * @param urlBuilder the {@link AlpacaRequestBuilder} - * @param type the type to return - * - * @return the response object type - * - * @throws AlpacaAPIRequestException thrown for {@link AlpacaAPIRequestException}s - */ - public T put(AlpacaRequestBuilder urlBuilder, Type type) throws AlpacaAPIRequestException { - try { - HttpResponse response = invokePut(urlBuilder); - - if (response.getStatus() != 200) { - throw new AlpacaAPIRequestException(response); - } - - return getResponseObject(response, type); - } catch (UnirestException exception) { - throw new AlpacaAPIRequestException(exception); - } - } - - /** - * Invokes a DELETE request and handles/returns the response object. - * - * @param the type - * @param urlBuilder the {@link AlpacaRequestBuilder} - * @param type the type to return - * - * @return the response object type - * - * @throws AlpacaAPIRequestException thrown for {@link AlpacaAPIRequestException}s - */ - public T delete(AlpacaRequestBuilder urlBuilder, Type type) throws AlpacaAPIRequestException { - try { - HttpResponse response = invokeDelete(urlBuilder); - - if (response.getStatus() != 200) { - throw new AlpacaAPIRequestException(response); - } - - return getResponseObject(response, type); - } catch (UnirestException exception) { - throw new AlpacaAPIRequestException(exception); - } - } - - /** - * Invokes a OPTIONS request and handles/returns the response object. - * - * @param the type - * @param urlBuilder the {@link AlpacaRequestBuilder} - * @param type the type to return - * - * @return the response object type - * - * @throws AlpacaAPIRequestException thrown for {@link AlpacaAPIRequestException}s - */ - public T options(AlpacaRequestBuilder urlBuilder, Type type) throws AlpacaAPIRequestException { - try { - HttpResponse response = invokeOptions(urlBuilder); - - if (response.getStatus() != 200) { - throw new AlpacaAPIRequestException(response); - } - - return getResponseObject(response, type); - } catch (UnirestException exception) { - throw new AlpacaAPIRequestException(exception); - } - } -} diff --git a/src/main/java/net/jacobpeterson/alpaca/rest/AlpacaRequestBuilder.java b/src/main/java/net/jacobpeterson/alpaca/rest/AlpacaRequestBuilder.java deleted file mode 100644 index a7c67d78..00000000 --- a/src/main/java/net/jacobpeterson/alpaca/rest/AlpacaRequestBuilder.java +++ /dev/null @@ -1,31 +0,0 @@ -package net.jacobpeterson.alpaca.rest; - -import net.jacobpeterson.alpaca.AlpacaAPI; -import net.jacobpeterson.alpaca.abstracts.rest.AbstractRequestBuilder; - -import java.util.Arrays; - -/** - * {@link AlpacaRequestBuilder} is a builder for URL Strings for {@link AlpacaAPI}. - */ -public class AlpacaRequestBuilder extends AbstractRequestBuilder { - - /** - * Instantiates a new {@link AlpacaRequestBuilder}. - * - * @param baseUrl the base URL - * @param apiVersion the API version - * @param endpoints the endpoints - */ - public AlpacaRequestBuilder(String baseUrl, String apiVersion, String... endpoints) { - super(baseUrl); - - if (apiVersion != null) { - super.appendEndpoint(apiVersion); - } - - if (endpoints != null) { - Arrays.stream(endpoints).forEach(super::appendEndpoint); - } - } -} diff --git a/src/main/java/net/jacobpeterson/alpaca/rest/exception/AlpacaAPIRequestException.java b/src/main/java/net/jacobpeterson/alpaca/rest/exception/AlpacaAPIRequestException.java deleted file mode 100644 index 2103b235..00000000 --- a/src/main/java/net/jacobpeterson/alpaca/rest/exception/AlpacaAPIRequestException.java +++ /dev/null @@ -1,67 +0,0 @@ -package net.jacobpeterson.alpaca.rest.exception; - -import com.google.gson.JsonElement; -import com.google.gson.JsonObject; -import com.google.gson.JsonParser; -import com.mashape.unirest.http.HttpResponse; -import net.jacobpeterson.alpaca.AlpacaAPI; -import net.jacobpeterson.alpaca.abstracts.rest.exception.AbstractAPIRequestException; -import net.jacobpeterson.alpaca.util.gson.GsonUtil; - -import java.io.InputStream; -import java.io.InputStreamReader; - -/** - * {@link AlpacaAPIRequestException} represents {@link HttpResponse} {@link Exception}s for {@link AlpacaAPI}. - */ -public class AlpacaAPIRequestException extends AbstractAPIRequestException { - - /** - * Instantiates a new {@link AlpacaAPIRequestException}. - * - * @param cause the cause - */ - public AlpacaAPIRequestException(Exception cause) { - super(cause); - } - - /** - * Instantiates a new {@link AlpacaAPIRequestException}. - * - * @param httpResponse the {@link HttpResponse} - */ - public AlpacaAPIRequestException(HttpResponse httpResponse) { - super("Alpaca", httpResponse); - } - - /** - * Parse a standard API exception response of the following format: - *
-     *  {
-     *     "code": 40010001,
-     *     "message": "Error message"
-     *  }
-     * 
- */ - @Override - protected void parseAPIExceptionMessage() { - JsonElement responseJsonElement = JsonParser.parseString(new InputStreamReader(httpResponse.getBody())); - - if (responseJsonElement instanceof JsonObject) { - JsonObject responseJsonObject = (JsonObject) responseJsonElement; - - if (responseJsonObject.has(CODE_KEY)) { - apiResponseCode = responseJsonObject.get(CODE_KEY).getAsInt(); - } - - if (responseJsonObject.has(MESSAGE_KEY)) { - apiResponseMessage = responseJsonObject.get(MESSAGE_KEY).getAsString(); - } - } - - // Just use the response JSON if the message could not be parsed. - if (apiResponseMessage == null) { - apiResponseMessage = responseJsonElement.toString(); - } - } -} diff --git a/src/main/java/net/jacobpeterson/alpaca/util/gson/GsonUtil.java b/src/main/java/net/jacobpeterson/alpaca/util/gson/GsonUtil.java deleted file mode 100644 index fdfc31ba..00000000 --- a/src/main/java/net/jacobpeterson/alpaca/util/gson/GsonUtil.java +++ /dev/null @@ -1,95 +0,0 @@ -package net.jacobpeterson.alpaca.util.gson; - -import com.google.gson.Gson; -import com.google.gson.GsonBuilder; -import com.google.gson.JsonObject; -import com.google.gson.annotations.SerializedName; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.lang.annotation.Annotation; -import java.lang.reflect.Field; -import java.time.LocalDate; -import java.time.ZonedDateTime; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Set; - -/** - * {@link GsonUtil} contains utility methods relating to {@link Gson}. - */ -public class GsonUtil { - - private static final Logger LOGGER = LoggerFactory.getLogger(GsonUtil.class); - private static final HashMap, ArrayList> CLASS_ANNOTATION_CACHE = new HashMap<>(); - public static final Gson GSON = new GsonBuilder() - .registerTypeAdapter(ZonedDateTime.class, new ZonedDateTimeAdapter()) - .registerTypeAdapter(LocalDate.class, new LocalDateAdapter()) - .enableComplexMapKeySerialization() - .setLenient() - .create(); - - /** - * Checks if all {@link Gson} {@link SerializedName} annotation values (including inherited ones) in the JSON POJO - * are present in the immediate JSON object. - * - * @param jsonPOJOClass the JSON POJO class - * @param jsonObject the JSON object - * - * @return a boolean - */ - public static boolean doesGsonPOJOMatch(Class jsonPOJOClass, JsonObject jsonObject) { - List gsonSerializedNameAnnotations = getGsonSerializedNameAnnotations(jsonPOJOClass); - Set jsonObjectKeys = jsonObject.keySet(); - - List classKeys = new ArrayList<>(); - gsonSerializedNameAnnotations.forEach((c) -> classKeys.add(c.value())); - - for (String key : jsonObjectKeys) { - if (!classKeys.contains(key)) { - LOGGER.trace("Class '{}' did not contain key '{}'", jsonPOJOClass, key); - return false; - } - } - - return true; - } - - /** - * Gets {@link Gson} {@link SerializedName} annotations from a given {@link Class} - * - * @param clazz the {@link Class} - * - * @return a {@link List} of {@link SerializedName}s - */ - private static synchronized List getGsonSerializedNameAnnotations(Class clazz) { - // Use a caching system because Reflection can be quite expensive - if (CLASS_ANNOTATION_CACHE.containsKey(clazz)) { - return CLASS_ANNOTATION_CACHE.get(clazz); - } - - LOGGER.debug("Caching Gson @SerializedName annotations for {}", clazz.getName()); - - // Below is expensive, but it only has to be done once per class. - - List serializedNameAnnotations = new ArrayList<>(); - - Class currentClass = clazz; - do { - for (Field field : currentClass.getDeclaredFields()) { // Loop through all the fields - for (Annotation annotation : field.getDeclaredAnnotations()) { // Loop through all the field annotations - if (annotation instanceof SerializedName) { - serializedNameAnnotations.add((SerializedName) annotation); - } - } - } - - currentClass = currentClass.getSuperclass(); - } while (currentClass != null); // Loop through all inherited classes - - CLASS_ANNOTATION_CACHE.put(clazz, serializedNameAnnotations); - - return serializedNameAnnotations; - } -} diff --git a/src/main/java/net/jacobpeterson/alpaca/websocket/broker/client/AlpacaWebsocketClient.java b/src/main/java/net/jacobpeterson/alpaca/websocket/broker/client/AlpacaWebsocketClient.java index d4b053f9..e152fa20 100644 --- a/src/main/java/net/jacobpeterson/alpaca/websocket/broker/client/AlpacaWebsocketClient.java +++ b/src/main/java/net/jacobpeterson/alpaca/websocket/broker/client/AlpacaWebsocketClient.java @@ -7,7 +7,7 @@ import com.google.gson.JsonParser; import com.google.gson.JsonPrimitive; import com.google.gson.JsonSyntaxException; -import net.jacobpeterson.alpaca.AlpacaAPI; +import net.jacobpeterson.alpaca.refactor.AlpacaAPI; import net.jacobpeterson.alpaca.abstracts.websocket.client.WebsocketClient; import net.jacobpeterson.alpaca.abstracts.websocket.client.WebsocketStateListener; import net.jacobpeterson.alpaca.abstracts.websocket.exception.WebsocketException; @@ -16,8 +16,8 @@ import net.jacobpeterson.alpaca.domain.streaming.authorization.AuthorizationMessage; import net.jacobpeterson.alpaca.domain.streaming.listening.ListeningMessage; import net.jacobpeterson.alpaca.domain.streaming.trade.TradeUpdateMessage; -import net.jacobpeterson.alpaca.properties.enums.EndpointAPIType; -import net.jacobpeterson.alpaca.util.gson.GsonUtil; +import net.jacobpeterson.alpaca.refactor.properties.enums.EndpointAPIType; +import net.jacobpeterson.alpaca.refactor.util.gson.GsonUtil; import net.jacobpeterson.alpaca.websocket.broker.listener.AlpacaStreamListener; import net.jacobpeterson.alpaca.websocket.broker.message.AlpacaStreamMessageType; import org.slf4j.Logger; diff --git a/src/main/java/net/jacobpeterson/alpaca/websocket/broker/client/AlpacaWebsocketClientEndpoint.java b/src/main/java/net/jacobpeterson/alpaca/websocket/broker/client/AlpacaWebsocketClientEndpoint.java index a9c215ac..c6b2466b 100644 --- a/src/main/java/net/jacobpeterson/alpaca/websocket/broker/client/AlpacaWebsocketClientEndpoint.java +++ b/src/main/java/net/jacobpeterson/alpaca/websocket/broker/client/AlpacaWebsocketClientEndpoint.java @@ -1,6 +1,6 @@ package net.jacobpeterson.alpaca.websocket.broker.client; -import net.jacobpeterson.alpaca.AlpacaAPI; +import net.jacobpeterson.alpaca.refactor.AlpacaAPI; import net.jacobpeterson.alpaca.abstracts.websocket.client.AbstractWebsocketClientEndpoint; import javax.websocket.ClientEndpoint; diff --git a/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/client/MarketDataWebsocketClient.java b/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/client/MarketDataWebsocketClient.java index 8dd9f932..0b72ebbb 100644 --- a/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/client/MarketDataWebsocketClient.java +++ b/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/client/MarketDataWebsocketClient.java @@ -5,7 +5,7 @@ import com.google.gson.JsonElement; import com.google.gson.JsonObject; import com.google.gson.JsonParser; -import net.jacobpeterson.alpaca.AlpacaAPI; +import net.jacobpeterson.alpaca.refactor.AlpacaAPI; import net.jacobpeterson.alpaca.abstracts.websocket.client.WebsocketClient; import net.jacobpeterson.alpaca.abstracts.websocket.client.WebsocketStateListener; import net.jacobpeterson.alpaca.abstracts.websocket.exception.WebsocketException; @@ -17,8 +17,8 @@ import net.jacobpeterson.alpaca.domain.marketdata.realtime.control.SuccessMessage; import net.jacobpeterson.alpaca.domain.marketdata.realtime.quote.QuoteMessage; import net.jacobpeterson.alpaca.domain.marketdata.realtime.trade.TradeMessage; -import net.jacobpeterson.alpaca.properties.enums.DataAPIType; -import net.jacobpeterson.alpaca.util.gson.GsonUtil; +import net.jacobpeterson.alpaca.refactor.properties.enums.DataAPIType; +import net.jacobpeterson.alpaca.refactor.util.gson.GsonUtil; import net.jacobpeterson.alpaca.websocket.broker.client.AlpacaWebsocketClient; import net.jacobpeterson.alpaca.websocket.marketdata.listener.MarketDataListener; import net.jacobpeterson.alpaca.websocket.marketdata.message.MarketDataMessageType; diff --git a/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/client/MarketDataWebsocketClientEndpoint.java b/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/client/MarketDataWebsocketClientEndpoint.java index f82d0be0..96a6ceef 100644 --- a/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/client/MarketDataWebsocketClientEndpoint.java +++ b/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/client/MarketDataWebsocketClientEndpoint.java @@ -1,6 +1,6 @@ package net.jacobpeterson.alpaca.websocket.marketdata.client; -import net.jacobpeterson.alpaca.AlpacaAPI; +import net.jacobpeterson.alpaca.refactor.AlpacaAPI; import net.jacobpeterson.alpaca.abstracts.websocket.client.AbstractWebsocketClientEndpoint; import javax.websocket.ClientEndpoint; diff --git a/src/main/resources/alpaca.default.properties b/src/main/resources/alpaca.default.properties index 02242abb..ecc83759 100644 --- a/src/main/resources/alpaca.default.properties +++ b/src/main/resources/alpaca.default.properties @@ -1,6 +1,6 @@ #Example: #key_id = -#secret = +#secret_key = #endpoint_api_type = #data_api_type = #user_agent = diff --git a/src/test/java/net/jacobpeterson/alpaca/test/live/AlpacaAPIEndpointTest.java b/src/test/java/net/jacobpeterson/alpaca/test/live/AlpacaAPITest.java similarity index 65% rename from src/test/java/net/jacobpeterson/alpaca/test/live/AlpacaAPIEndpointTest.java rename to src/test/java/net/jacobpeterson/alpaca/test/live/AlpacaAPITest.java index 2a263d4b..3839695e 100644 --- a/src/test/java/net/jacobpeterson/alpaca/test/live/AlpacaAPIEndpointTest.java +++ b/src/test/java/net/jacobpeterson/alpaca/test/live/AlpacaAPITest.java @@ -1,17 +1,23 @@ package net.jacobpeterson.alpaca.test.live; -import net.jacobpeterson.alpaca.AlpacaAPI; -import net.jacobpeterson.alpaca.abstracts.enums.SortDirection; -import net.jacobpeterson.alpaca.domain.account.Account; -import net.jacobpeterson.alpaca.domain.accountactivities.AccountActivity; -import net.jacobpeterson.alpaca.domain.accountactivities.NonTradeActivity; -import net.jacobpeterson.alpaca.domain.accountactivities.TradeActivity; -import net.jacobpeterson.alpaca.domain.accountconfiguration.AccountConfiguration; -import net.jacobpeterson.alpaca.domain.clock.Clock; -import net.jacobpeterson.alpaca.domain.order.Order; -import net.jacobpeterson.alpaca.enums.activity.ActivityType; -import net.jacobpeterson.alpaca.enums.order.OrderStatus; -import net.jacobpeterson.alpaca.rest.exception.AlpacaAPIRequestException; +import net.jacobpeterson.alpaca.model.endpoint.account.Account; +import net.jacobpeterson.alpaca.model.endpoint.accountactivities.AccountActivity; +import net.jacobpeterson.alpaca.model.endpoint.accountactivities.NonTradeActivity; +import net.jacobpeterson.alpaca.model.endpoint.accountactivities.TradeActivity; +import net.jacobpeterson.alpaca.model.endpoint.accountactivities.enums.ActivityType; +import net.jacobpeterson.alpaca.model.endpoint.accountconfiguration.AccountConfiguration; +import net.jacobpeterson.alpaca.model.endpoint.accountconfiguration.enums.DTBPCheck; +import net.jacobpeterson.alpaca.model.endpoint.accountconfiguration.enums.TradeConfirmEmail; +import net.jacobpeterson.alpaca.model.endpoint.clock.Clock; +import net.jacobpeterson.alpaca.model.endpoint.common.enums.SortDirection; +import net.jacobpeterson.alpaca.model.endpoint.order.Order; +import net.jacobpeterson.alpaca.model.endpoint.order.enums.CurrentOrderStatus; +import net.jacobpeterson.alpaca.refactor.AlpacaAPI; +import net.jacobpeterson.alpaca.refactor.rest.AlpacaClientException; +import net.jacobpeterson.alpaca.refactor.rest.endpoint.AccountConfigurationEndpoint; +import net.jacobpeterson.alpaca.refactor.rest.endpoint.AccountEndpoint; +import net.jacobpeterson.alpaca.refactor.rest.endpoint.ClockEndpoint; +import net.jacobpeterson.alpaca.refactor.rest.endpoint.OrdersEndpoint; import org.junit.jupiter.api.*; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -19,24 +25,21 @@ import java.time.ZonedDateTime; import java.util.List; -import static org.junit.jupiter.api.Assertions.assertFalse; -import static org.junit.jupiter.api.Assertions.assertNotNull; -import static org.junit.jupiter.api.Assertions.fail; +import static org.junit.jupiter.api.Assertions.*; /** - * {@link AlpacaAPIEndpointTest} tests live endpoints using Alpaca Paper credentials given in the + * {@link AlpacaAPITest} tests live endpoints using Alpaca Paper credentials given in the * alpaca.properties file on the classpath. */ @TestMethodOrder(MethodOrderer.OrderAnnotation.class) -public class AlpacaAPIEndpointTest { +public class AlpacaAPITest { - private static final Logger LOGGER = LoggerFactory.getLogger(AlpacaAPIEndpointTest.class); + private static final Logger LOGGER = LoggerFactory.getLogger(AlpacaAPITest.class); private static final int RATE_LIMIT_MILLIS = 200; // Wait 200ms between every test to prevent rate-limiting static { // Log trace-level System.setProperty("org.slf4j.simpleLogger.log.net.jacobpeterson", "trace"); - System.setProperty("org.slf4j.simpleLogger.log.live.net.jacobpeterson", "trace"); } private static AlpacaAPI alpacaAPI; @@ -73,14 +76,14 @@ public void afterEach() { } /** - * Tests {@link AlpacaAPI#getClock()}. + * Tests {@link ClockEndpoint#get()}. * - * @throws AlpacaAPIRequestException thrown for {@link AlpacaAPIRequestException}s + * @throws AlpacaClientException thrown for {@link AlpacaClientException}s */ @Test @org.junit.jupiter.api.Order(1) - public void test_getClock() throws AlpacaAPIRequestException { - Clock clock = alpacaAPI.getClock(); + public void test_ClockEndpoint_get() throws AlpacaClientException { + Clock clock = alpacaAPI.clock().get(); assertNotNull(clock); LOGGER.debug("{}", clock); @@ -99,15 +102,15 @@ public void test_getClock() throws AlpacaAPIRequestException { } /** - * Tests {@link AlpacaAPI#getAccount()}. + * Tests {@link AccountEndpoint#get()}. * - * @throws AlpacaAPIRequestException thrown for {@link AlpacaAPIRequestException}s - * @throws NumberFormatException thrown for {@link NumberFormatException}s + * @throws AlpacaClientException thrown for {@link AlpacaClientException}s + * @throws NumberFormatException thrown for {@link NumberFormatException}s */ @Test @SuppressWarnings("ResultOfMethodCallIgnored") - public void test_getAccount() throws AlpacaAPIRequestException, NumberFormatException { - Account account = alpacaAPI.getAccount(); + public void test_AccountEndpoint_get() throws AlpacaClientException, NumberFormatException { + Account account = alpacaAPI.account().get(); assertNotNull(account); LOGGER.debug("{}", account); @@ -144,14 +147,15 @@ public void test_getAccount() throws AlpacaAPIRequestException, NumberFormatExce } /** - * Tests @{@link AlpacaAPI#getAccountActivities(ZonedDateTime, ZonedDateTime, ZonedDateTime, SortDirection, Integer, - * String, ActivityType...)} one {@link AccountActivity} exists until now. + * Tests @{@link net.jacobpeterson.alpaca.refactor.rest.endpoint.AccountActivitiesEndpoint#get(ZonedDateTime, + * ZonedDateTime, ZonedDateTime, SortDirection, Integer, String, ActivityType...)} one {@link AccountActivity} + * exists until now. * - * @throws AlpacaAPIRequestException thrown for {@link AlpacaAPIRequestException}s + * @throws AlpacaClientException thrown for {@link AlpacaClientException}s */ @Test - public void test_getAccountActivities_One_Activity_Exists_Until_Now() throws AlpacaAPIRequestException { - List accountActivities = alpacaAPI.getAccountActivities( + public void test_AccountActivitiesEndpoint_get_One_Activity_Exists_Until_Now() throws AlpacaClientException { + List accountActivities = alpacaAPI.accountActivities().get( null, ZonedDateTime.now(), null, @@ -192,14 +196,14 @@ public void test_getAccountActivities_One_Activity_Exists_Until_Now() throws Alp } /** - * Test {@link AlpacaAPI#getAccountConfiguration()}. + * Test {@link AccountConfigurationEndpoint#get()}. * - * @throws AlpacaAPIRequestException thrown for {@link AlpacaAPIRequestException}s + * @throws AlpacaClientException thrown for {@link AlpacaClientException}s */ @Test @org.junit.jupiter.api.Order(1) - public void test_getAccountConfiguration() throws AlpacaAPIRequestException { - AccountConfiguration accountConfiguration = alpacaAPI.getAccountConfiguration(); + public void test_AccountConfigurationEndpoint_get() throws AlpacaClientException { + AccountConfiguration accountConfiguration = alpacaAPI.accountConfiguration().get(); assertNotNull(accountConfiguration); LOGGER.debug("{}", accountConfiguration); @@ -209,40 +213,40 @@ public void test_getAccountConfiguration() throws AlpacaAPIRequestException { assertNotNull(accountConfiguration.getSuspendTrade()); assertNotNull(accountConfiguration.getNoShorting()); - AlpacaAPIEndpointTest.accountConfiguration = accountConfiguration; + AlpacaAPITest.accountConfiguration = accountConfiguration; } /** - * Test {@link AlpacaAPI#setAccountConfiguration(AccountConfiguration)}. + * Test {@link AccountConfigurationEndpoint#set(AccountConfiguration)}. * - * @throws AlpacaAPIRequestException thrown for {@link AlpacaAPIRequestException}s + * @throws AlpacaClientException thrown for {@link AlpacaClientException}s */ @Test @org.junit.jupiter.api.Order(2) - public void test_setAccountConfiguration() throws AlpacaAPIRequestException { + public void test_AccountConfigurationEndpoint_set() throws AlpacaClientException { if (accountConfiguration == null) { AccountConfiguration newAccountConfiguration = new AccountConfiguration( - "both", - "none", + DTBPCheck.BOTH, + TradeConfirmEmail.NONE, false, false); LOGGER.info("Settings Account Configuration to: {}", newAccountConfiguration); - alpacaAPI.setAccountConfiguration(newAccountConfiguration); + alpacaAPI.accountConfiguration().set(newAccountConfiguration); } else { - alpacaAPI.setAccountConfiguration(accountConfiguration); + alpacaAPI.accountConfiguration().set(accountConfiguration); } } /** - * Test {@link AlpacaAPI#getOrders(OrderStatus, Integer, ZonedDateTime, ZonedDateTime, SortDirection, Boolean, + * Test {@link OrdersEndpoint#get(CurrentOrderStatus, Integer, ZonedDateTime, ZonedDateTime, SortDirection, Boolean, * List)} one {@link Order} exists until now. * - * @throws AlpacaAPIRequestException thrown for {@link AlpacaAPIRequestException}s + * @throws AlpacaClientException thrown for {@link AlpacaClientException}s */ @Test - public void test_getOrders_One_Order_Exists_Until_now() throws AlpacaAPIRequestException { - List orders = alpacaAPI.getOrders( - OrderStatus.ALL, + public void test_OrdersEndpoint_get_One_Order_Exists_Until_now() throws AlpacaClientException { + List orders = alpacaAPI.orders().get( + CurrentOrderStatus.ALL, 1, null, ZonedDateTime.now(), diff --git a/src/test/java/net/jacobpeterson/alpaca/test/mock/AlpacaAPITest.java b/src/test/java/net/jacobpeterson/alpaca/test/mock/AlpacaAPITest.java index 61ebebb2..7cb569ff 100644 --- a/src/test/java/net/jacobpeterson/alpaca/test/mock/AlpacaAPITest.java +++ b/src/test/java/net/jacobpeterson/alpaca/test/mock/AlpacaAPITest.java @@ -1,8 +1,8 @@ package net.jacobpeterson.alpaca.test.mock; -import net.jacobpeterson.alpaca.AlpacaAPI; -import net.jacobpeterson.alpaca.properties.enums.DataAPIType; -import net.jacobpeterson.alpaca.properties.enums.EndpointAPIType; +import net.jacobpeterson.alpaca.model.properties.DataAPIType; +import net.jacobpeterson.alpaca.model.properties.EndpointAPIType; +import net.jacobpeterson.alpaca.refactor.AlpacaAPI; import org.junit.jupiter.api.Test; /** diff --git a/upload_to_maven_central.sh b/upload_to_maven_central.sh deleted file mode 100755 index 4de8adfe..00000000 --- a/upload_to_maven_central.sh +++ /dev/null @@ -1,2 +0,0 @@ -./gradlew clean build uploadArchives -./gradlew closeAndReleaseRepository From 46c675fc95dd200907a5033d9f864c18e392517b Mon Sep 17 00:00:00 2001 From: Jacob Peterson Date: Sat, 10 Jul 2021 10:33:09 -0600 Subject: [PATCH 122/271] Finish the rest of AbstractEndpoint implementations --- .../historical/bar/enums/bars_time_frame.json | 20 + .../endpoint/AccountActivitiesEndpoint.java | 22 +- .../rest/endpoint/MarketDataEndpoint.java | 515 +++++++++--------- .../endpoint/PortfolioHistoryEndpoint.java | 26 +- 4 files changed, 300 insertions(+), 283 deletions(-) create mode 100644 schema_json/endpoint/market_data/historical/bar/enums/bars_time_frame.json diff --git a/schema_json/endpoint/market_data/historical/bar/enums/bars_time_frame.json b/schema_json/endpoint/market_data/historical/bar/enums/bars_time_frame.json new file mode 100644 index 00000000..89eb394f --- /dev/null +++ b/schema_json/endpoint/market_data/historical/bar/enums/bars_time_frame.json @@ -0,0 +1,20 @@ +{ + "type": "string", + "title": "See Historical Data.", + "enum": [ + "1Min", + "1Hour", + "1Day" + ], + "javaEnums": [ + { + "name": "ONE_MINUTE" + }, + { + "name": "ONE_HOUR" + }, + { + "name": "ONE_DAY" + } + ] +} diff --git a/src/main/java/net/jacobpeterson/alpaca/refactor/rest/endpoint/AccountActivitiesEndpoint.java b/src/main/java/net/jacobpeterson/alpaca/refactor/rest/endpoint/AccountActivitiesEndpoint.java index b4136b63..898ce066 100644 --- a/src/main/java/net/jacobpeterson/alpaca/refactor/rest/endpoint/AccountActivitiesEndpoint.java +++ b/src/main/java/net/jacobpeterson/alpaca/refactor/rest/endpoint/AccountActivitiesEndpoint.java @@ -23,6 +23,8 @@ import java.util.List; import java.util.stream.Collectors; +import static com.google.common.base.Preconditions.checkState; + /** * {@link AbstractEndpoint} for * Account @@ -105,25 +107,19 @@ public List get(ZonedDateTime date, ZonedDateTime until, ZonedD .build(); JsonElement responseJSON = alpacaClient.requestJSON(request); - if (!(responseJSON instanceof JsonArray)) { - throw new IllegalStateException( - String.format("The response must be an array! Received: %s", responseJSON)); - } + checkState(responseJSON instanceof JsonArray, + String.format("The response must be an array! Received: %s", responseJSON)); ArrayList accountActivities = new ArrayList<>(); for (JsonElement responseArrayElement : responseJSON.getAsJsonArray()) { - if (!(responseArrayElement instanceof JsonObject)) { - throw new IllegalStateException( - String.format("All array elements must be objects! Received: %s", responseArrayElement)); - } + checkState(responseArrayElement instanceof JsonObject, + String.format("All array elements must be objects! Received: %s", responseArrayElement)); JsonObject responseArrayObject = responseArrayElement.getAsJsonObject(); JsonElement activityTypeElement = responseArrayObject.get(ACTIVITY_TYPE_FIELD); - if (activityTypeElement == null) { - throw new IllegalStateException( - String.format("Activity type elements must have %s field! Received: %s", - ACTIVITY_TYPE_FIELD, responseArrayElement)); - } + checkState(activityTypeElement != null, + String.format("Activity type elements must have %s field! Received: %s", + ACTIVITY_TYPE_FIELD, responseArrayElement)); String activityType = activityTypeElement.getAsString(); AccountActivity accountActivity; diff --git a/src/main/java/net/jacobpeterson/alpaca/refactor/rest/endpoint/MarketDataEndpoint.java b/src/main/java/net/jacobpeterson/alpaca/refactor/rest/endpoint/MarketDataEndpoint.java index 48ac6dbe..a1603143 100644 --- a/src/main/java/net/jacobpeterson/alpaca/refactor/rest/endpoint/MarketDataEndpoint.java +++ b/src/main/java/net/jacobpeterson/alpaca/refactor/rest/endpoint/MarketDataEndpoint.java @@ -1,11 +1,34 @@ package net.jacobpeterson.alpaca.refactor.rest.endpoint; +import com.google.gson.reflect.TypeToken; +import net.jacobpeterson.alpaca.model.endpoint.marketdata.historical.bar.Bar; +import net.jacobpeterson.alpaca.model.endpoint.marketdata.historical.bar.BarsResponse; +import net.jacobpeterson.alpaca.model.endpoint.marketdata.historical.bar.enums.BarsTimeFrame; +import net.jacobpeterson.alpaca.model.endpoint.marketdata.historical.quote.LatestQuoteResponse; +import net.jacobpeterson.alpaca.model.endpoint.marketdata.historical.quote.Quote; +import net.jacobpeterson.alpaca.model.endpoint.marketdata.historical.quote.QuotesResponse; +import net.jacobpeterson.alpaca.model.endpoint.marketdata.historical.snapshot.Snapshot; +import net.jacobpeterson.alpaca.model.endpoint.marketdata.historical.trade.LatestTradeResponse; +import net.jacobpeterson.alpaca.model.endpoint.marketdata.historical.trade.Trade; +import net.jacobpeterson.alpaca.model.endpoint.marketdata.historical.trade.TradesResponse; import net.jacobpeterson.alpaca.refactor.rest.AlpacaClient; +import net.jacobpeterson.alpaca.refactor.rest.AlpacaClientException; +import net.jacobpeterson.alpaca.refactor.util.format.FormatUtil; +import okhttp3.HttpUrl; +import okhttp3.Request; + +import java.time.ZonedDateTime; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.base.Preconditions.checkNotNull; /** * {@link AbstractEndpoint} for - * Market - * Data API v2. + * Historical + * Market Data API v2. */ public class MarketDataEndpoint extends AbstractEndpoint { @@ -15,261 +38,239 @@ public class MarketDataEndpoint extends AbstractEndpoint { * @param alpacaClient the {@link AlpacaClient} */ public MarketDataEndpoint(AlpacaClient alpacaClient) { - super(alpacaClient, null); + super(alpacaClient, "stocks"); + } + + /** + * Gets {@link Trade} historical data for the requested security. + * + * @param symbol the symbol to query for + * @param start filter data equal to or after this {@link ZonedDateTime}. Fractions of a second are not + * accepted. + * @param end filter data equal to or before this {@link ZonedDateTime}. Fractions of a second are not + * accepted. + * @param limit number of data points to return. Must be in range 1-10000, defaults to 1000 if null + * is given + * @param pageToken pagination token to continue from + * + * @return the {@link TradesResponse} + * + * @throws AlpacaClientException thrown for {@link AlpacaClientException}s + */ + public TradesResponse getTrades(String symbol, ZonedDateTime start, ZonedDateTime end, Integer limit, + String pageToken) throws AlpacaClientException { + checkNotNull(symbol); + checkNotNull(start); + checkNotNull(end); + + HttpUrl.Builder urlBuilder = alpacaClient.urlBuilder() + .addPathSegment(endpointPathSegment) + .addPathSegment(symbol) + .addPathSegment("trades"); + + urlBuilder.addQueryParameter("start", FormatUtil.toRFC3339Format(start)); + urlBuilder.addQueryParameter("end", FormatUtil.toRFC3339Format(end)); + + if (limit != null) { + urlBuilder.addQueryParameter("limit", limit.toString()); + } + + if (pageToken != null) { + urlBuilder.addQueryParameter("page_token", pageToken); + } + + Request request = alpacaClient.requestBuilder(urlBuilder.build()) + .get() + .build(); + return alpacaClient.requestObject(request, TradesResponse.class); + } + + /** + * The Latest Trade API provides the latest {@link Trade} data for a given ticker symbol. + * + * @param symbol the symbol to query for + * + * @return the {@link LatestTradeResponse} + * + * @throws AlpacaClientException thrown for {@link AlpacaClientException}s + */ + public LatestTradeResponse getLatestTrade(String symbol) throws AlpacaClientException { + checkNotNull(symbol); + + HttpUrl.Builder urlBuilder = alpacaClient.urlBuilder() + .addPathSegment(endpointPathSegment) + .addPathSegment(symbol) + .addPathSegment("trades") + .addPathSegment("latest"); + + Request request = alpacaClient.requestBuilder(urlBuilder.build()) + .get() + .build(); + return alpacaClient.requestObject(request, LatestTradeResponse.class); + } + + /** + * Gets {@link Quote} (NBBO or National Best Bid and Offer) historical data for the requested security. + * + * @param symbol the symbol to query for + * @param start filter data equal to or after this {@link ZonedDateTime}. Fractions of a second are not + * accepted. + * @param end filter data equal to or before this {@link ZonedDateTime}. Fractions of a second are not + * accepted. + * @param limit number of data points to return. Must be in range 1-10000, defaults to 1000 if null + * is given + * @param pageToken pagination token to continue from + * + * @return the {@link QuotesResponse} + * + * @throws AlpacaClientException thrown for {@link AlpacaClientException}s + */ + public QuotesResponse getQuotes(String symbol, ZonedDateTime start, ZonedDateTime end, Integer limit, + String pageToken) throws AlpacaClientException { + checkNotNull(symbol); + checkNotNull(start); + checkNotNull(end); + + HttpUrl.Builder urlBuilder = alpacaClient.urlBuilder() + .addPathSegment(endpointPathSegment) + .addPathSegment(symbol) + .addPathSegment("quotes"); + + urlBuilder.addQueryParameter("start", FormatUtil.toRFC3339Format(start)); + urlBuilder.addQueryParameter("end", FormatUtil.toRFC3339Format(end)); + + if (limit != null) { + urlBuilder.addQueryParameter("limit", limit.toString()); + } + + if (pageToken != null) { + urlBuilder.addQueryParameter("page_token", pageToken); + } + + Request request = alpacaClient.requestBuilder(urlBuilder.build()) + .get() + .build(); + return alpacaClient.requestObject(request, QuotesResponse.class); + } + + /** + * The Latest Quote API provides the latest quote data for a given ticker symbol. + * + * @param symbol the symbol to query for + * + * @return the {@link LatestQuoteResponse} + * + * @throws AlpacaClientException thrown for {@link AlpacaClientException}s + */ + public LatestQuoteResponse getLatestQuote(String symbol) throws AlpacaClientException { + checkNotNull(symbol); + + HttpUrl.Builder urlBuilder = alpacaClient.urlBuilder() + .addPathSegment(endpointPathSegment) + .addPathSegment(symbol) + .addPathSegment("quotes") + .addPathSegment("latest"); + + Request request = alpacaClient.requestBuilder(urlBuilder.build()) + .get() + .build(); + return alpacaClient.requestObject(request, LatestQuoteResponse.class); + } + + /** + * Gets {@link Bar} aggregate historical data for the requested security. + * + * @param symbol the symbol to query for + * @param start filter data equal to or after this {@link ZonedDateTime}. Fractions of a second are not + * accepted. + * @param end filter data equal to or before this {@link ZonedDateTime}. Fractions of a second are not + * accepted. + * @param limit number of data points to return. Must be in range 1-10000, defaults to 1000 if null + * is given + * @param pageToken pagination token to continue from + * @param timeFrame the {@link BarsTimeFrame} for the aggregation + * + * @return the {@link BarsResponse} + * + * @throws AlpacaClientException thrown for {@link AlpacaClientException}s + */ + public BarsResponse getBars(String symbol, ZonedDateTime start, ZonedDateTime end, Integer limit, + String pageToken, BarsTimeFrame timeFrame) throws AlpacaClientException { + checkNotNull(symbol); + checkNotNull(start); + checkNotNull(end); + checkNotNull(timeFrame); + + HttpUrl.Builder urlBuilder = alpacaClient.urlBuilder() + .addPathSegment(endpointPathSegment) + .addPathSegment(symbol) + .addPathSegment("bars"); + + urlBuilder.addQueryParameter("start", FormatUtil.toRFC3339Format(start)); + urlBuilder.addQueryParameter("end", FormatUtil.toRFC3339Format(end)); + urlBuilder.addQueryParameter("timeframe", timeFrame.toString()); + + if (limit != null) { + urlBuilder.addQueryParameter("limit", limit.toString()); + } + + if (pageToken != null) { + urlBuilder.addQueryParameter("page_token", pageToken); + } + + Request request = alpacaClient.requestBuilder(urlBuilder.build()) + .get() + .build(); + return alpacaClient.requestObject(request, BarsResponse.class); } - // TODO - // /** - // * Gets {@link Trade} historical data for the requested security. - // * - // * @param symbol the symbol to query for - // * @param start filter data equal to or after this {@link ZonedDateTime}. Fractions of a second are not - // * accepted - // * @param end filter data equal to or before this {@link ZonedDateTime}. Fractions of a second are not - // * accepted - // * @param limit number of data points to return. Must be in range 1-10000, defaults to 1000 if null is given - // * @param pageToken pagination token to continue from - // * - // * @return the {@link TradesResponse} - // * - // * @throws AlpacaAPIRequestException thrown for {@link AlpacaAPIRequestException}s - // * @see - // * Historical Market Data - // */ - // public TradesResponse getTrades(String symbol, ZonedDateTime start, ZonedDateTime end, Integer limit, - // String pageToken) throws AlpacaAPIRequestException { - // checkNotNull(symbol); - // checkNotNull(start); - // checkNotNull(end); - // - // AlpacaRequestBuilder urlBuilder = new AlpacaRequestBuilder(URLs.DATA, Endpoints.VERSION_2, - // Endpoints.STOCKS, - // symbol, - // Endpoints.TRADES); - // - // urlBuilder.appendURLParameter(Parameters.START, FormatUtil.toRFC3339Format(start)); - // urlBuilder.appendURLParameter(Parameters.END, FormatUtil.toRFC3339Format(end)); - // - // if (limit != null) { - // urlBuilder.appendURLParameter(Parameters.LIMIT, limit.toString()); - // } - // - // if (pageToken != null) { - // urlBuilder.appendURLParameter(Parameters.PAGE_TOKEN, pageToken); - // } - // - // return alpacaRequest.get(urlBuilder, TradesResponse.class); - // } - // - // /** - // * The Latest trade API provides the latest {@link Trade} data for a given ticker symbol. - // * - // * @param symbol the symbol to query for - // * - // * @return the {@link LatestTradeResponse} - // * - // * @throws AlpacaAPIRequestException thrown for {@link AlpacaAPIRequestException}s - // * @see - // * Historical Market Data - // */ - // public LatestTradeResponse getLatestTrade(String symbol) throws AlpacaAPIRequestException { - // checkNotNull(symbol); - // - // AlpacaRequestBuilder urlBuilder = new AlpacaRequestBuilder(URLs.DATA, Endpoints.VERSION_2, - // Endpoints.STOCKS, - // symbol, - // Endpoints.TRADES, - // Endpoints.LATEST); - // - // return alpacaRequest.get(urlBuilder, LatestTradeResponse.class); - // } - // - // /** - // * Gets {@link Quote} (NBBO or National Best Bid and Offer) historical data for the requested security. - // * - // * @param symbol the symbol to query for - // * @param start filter data equal to or after this {@link ZonedDateTime}. Fractions of a second are not - // * accepted - // * @param end filter data equal to or before this {@link ZonedDateTime}. Fractions of a second are not - // * accepted - // * @param limit number of data points to return. Must be in range 1-10000, defaults to 1000 if null is given - // * @param pageToken pagination token to continue from - // * - // * @return the {@link QuotesResponse} - // * - // * @throws AlpacaAPIRequestException thrown for {@link AlpacaAPIRequestException}s - // * @see - // * Historical Market Data - // */ - // public QuotesResponse getQuotes(String symbol, ZonedDateTime start, ZonedDateTime end, Integer limit, - // String pageToken) throws AlpacaAPIRequestException { - // checkNotNull(symbol); - // checkNotNull(start); - // checkNotNull(end); - // - // AlpacaRequestBuilder urlBuilder = new AlpacaRequestBuilder(URLs.DATA, Endpoints.VERSION_2, - // Endpoints.STOCKS, - // symbol, - // Endpoints.QUOTES); - // - // urlBuilder.appendURLParameter(Parameters.START, FormatUtil.toRFC3339Format(start)); - // urlBuilder.appendURLParameter(Parameters.END, FormatUtil.toRFC3339Format(end)); - // - // if (limit != null) { - // urlBuilder.appendURLParameter(Parameters.LIMIT, limit.toString()); - // } - // - // if (pageToken != null) { - // urlBuilder.appendURLParameter(Parameters.PAGE_TOKEN, pageToken); - // } - // - // return alpacaRequest.get(urlBuilder, QuotesResponse.class); - // } - // - // /** - // * The Latest quote API provides the latest quote data for a given ticker symbol. - // * - // * @param symbol the symbol to query for - // * - // * @return the {@link LatestQuoteResponse} - // * - // * @throws AlpacaAPIRequestException thrown for {@link AlpacaAPIRequestException}s - // * @see - // * Historical Market Data - // */ - // public LatestQuoteResponse getLatestQuote(String symbol) throws AlpacaAPIRequestException { - // checkNotNull(symbol); - // - // AlpacaRequestBuilder urlBuilder = new AlpacaRequestBuilder(URLs.DATA, Endpoints.VERSION_2, - // Endpoints.STOCKS, - // symbol, - // Endpoints.QUOTES, - // Endpoints.LATEST); - // - // return alpacaRequest.get(urlBuilder, LatestQuoteResponse.class); - // } - // - // /** - // * Gets {@link Bar} aggregate historical data for the requested security. - // * - // * @param symbol the symbol to query for - // * @param start filter data equal to or after this {@link ZonedDateTime}. Fractions of a second are not - // * accepted - // * @param end filter data equal to or before this {@link ZonedDateTime}. Fractions of a second are not - // * accepted - // * @param limit number of data points to return. Must be in range 1-10000, defaults to 1000 if null is given - // * @param pageToken pagination token to continue from - // * @param timeFrame the {@link BarsTimeFrame} for the aggregation - // * - // * @return the {@link BarsResponse} - // * - // * @throws AlpacaAPIRequestException thrown for {@link AlpacaAPIRequestException}s - // * @see - // * Historical Market Data - // */ - // public BarsResponse getBars(String symbol, ZonedDateTime start, ZonedDateTime end, Integer limit, - // String pageToken, BarsTimeFrame timeFrame) throws AlpacaAPIRequestException { - // checkNotNull(symbol); - // checkNotNull(start); - // checkNotNull(end); - // checkNotNull(timeFrame); - // - // AlpacaRequestBuilder urlBuilder = new AlpacaRequestBuilder(URLs.DATA, Endpoints.VERSION_2, - // Endpoints.STOCKS, - // symbol, - // Endpoints.BARS); - // - // urlBuilder.appendURLParameter(Parameters.START, FormatUtil.toRFC3339Format(start)); - // urlBuilder.appendURLParameter(Parameters.END, FormatUtil.toRFC3339Format(end)); - // urlBuilder.appendURLParameter(Parameters.TIMEFRAME, timeFrame.getAPIName()); - // - // if (limit != null) { - // urlBuilder.appendURLParameter(Parameters.LIMIT, limit.toString()); - // } - // - // if (pageToken != null) { - // urlBuilder.appendURLParameter(Parameters.PAGE_TOKEN, pageToken); - // } - // - // return alpacaRequest.get(urlBuilder, BarsResponse.class); - // } - // - // /** - // * The Snapshot API for one ticker provides the latest trade, latest quote, minute bar daily bar and previous - // daily - // * bar data for a given ticker symbol - // * - // * @param symbols the symbols to query for - // * - // * @return a {@link Map} with they keys being the symbol and the value being the {@link Snapshot} - // * - // * @throws AlpacaAPIRequestException thrown for {@link AlpacaAPIRequestException}s - // * @see - // * Historical Market Data - // */ - // public Map getSnapshots(List symbols) throws AlpacaAPIRequestException { - // checkNotNull(symbols); - // Preconditions.checkState(!symbols.isEmpty()); - // - // AlpacaRequestBuilder urlBuilder = new AlpacaRequestBuilder(URLs.DATA, Endpoints.VERSION_2, - // Endpoints.STOCKS, - // Endpoints.SNAPSHOTS); - // - // urlBuilder.appendURLParameter(Parameters.SYMBOLS, String.join(",", symbols)); - // - // HttpResponse response; - // try { - // response = alpacaRequest.invokeGet(urlBuilder); - // } catch (UnirestException exception) { - // throw new AlpacaAPIRequestException(exception); - // } - // - // if (response.getStatus() != 200) { - // throw new AlpacaAPIRequestException(response); - // } - // - // JsonElement responseJsonElement = alpacaRequest.getResponseJSON(response); - // HashMap snapshotsOfSymbols = new HashMap<>(); - // - // if (responseJsonElement instanceof JsonObject) { - // JsonObject responseJsonObject = (JsonObject) responseJsonElement; - // - // for (String symbol : responseJsonObject.keySet()) { // Loop through response object keys - // JsonElement jsonObjectElement = responseJsonObject.get(symbol); - // - // if (jsonObjectElement instanceof JsonObject) { - // snapshotsOfSymbols.put(symbol, GsonUtil.GSON.fromJson(jsonObjectElement, Snapshot.class)); - // } else { - // throw new IllegalStateException("The response object must only contain objects!"); - // } - // } - // return snapshotsOfSymbols; - // } else { - // throw new IllegalStateException("The response must be an object!"); - // } - // } - // - // /** - // * The Snapshot API for one ticker provides the latest trade, latest quote, minute bar daily bar and previous - // daily - // * bar data for a given ticker symbol - // * - // * @param symbol the symbol to query for - // * - // * @return the {@link Snapshot} - // * - // * @throws AlpacaAPIRequestException thrown for {@link AlpacaAPIRequestException}s - // * @see - // * Historical Market Data - // */ - // public Snapshot getSnapshot(String symbol) throws AlpacaAPIRequestException { - // checkNotNull(symbol); - // - // AlpacaRequestBuilder urlBuilder = new AlpacaRequestBuilder(URLs.DATA, Endpoints.VERSION_2, - // Endpoints.STOCKS, - // symbol, - // Endpoints.SNAPSHOT); - // - // return alpacaRequest.get(urlBuilder, Snapshot.class); - // } + /** + * The Snapshot API for one ticker provides the latest trade, latest quote, minute bar daily bar, and previous daily + * bar data for a given ticker symbol + * + * @param symbols a {@link List} of symbols to query for + * + * @return a {@link Map} with they keys being the symbol and their values being the {@link Snapshot} + * + * @throws AlpacaClientException thrown for {@link AlpacaClientException}s + */ + public Map getSnapshots(List symbols) throws AlpacaClientException { + checkNotNull(symbols); + checkArgument(!symbols.isEmpty()); + + HttpUrl.Builder urlBuilder = alpacaClient.urlBuilder() + .addPathSegment(endpointPathSegment) + .addPathSegment("snapshots"); + + urlBuilder.addQueryParameter("symbols", String.join(",", symbols)); + + Request request = alpacaClient.requestBuilder(urlBuilder.build()) + .get() + .build(); + return alpacaClient.requestObject(request, new TypeToken>() {}.getType()); + } + + /** + * The Snapshot API for one ticker provides the latest trade, latest quote, minute bar daily bar and previous daily + * bar data for a given ticker symbol + * + * @param symbol the symbol to query for + * + * @return the {@link Snapshot} + * + * @throws AlpacaClientException thrown for {@link AlpacaClientException}s + */ + public Snapshot getSnapshot(String symbol) throws AlpacaClientException { + checkNotNull(symbol); + + HttpUrl.Builder urlBuilder = alpacaClient.urlBuilder() + .addPathSegment(endpointPathSegment) + .addPathSegment(symbol) + .addPathSegment("shapshot"); + + Request request = alpacaClient.requestBuilder(urlBuilder.build()) + .get() + .build(); + return alpacaClient.requestObject(request, Snapshot.class); + } } diff --git a/src/main/java/net/jacobpeterson/alpaca/refactor/rest/endpoint/PortfolioHistoryEndpoint.java b/src/main/java/net/jacobpeterson/alpaca/refactor/rest/endpoint/PortfolioHistoryEndpoint.java index 72f507f6..0a29483a 100644 --- a/src/main/java/net/jacobpeterson/alpaca/refactor/rest/endpoint/PortfolioHistoryEndpoint.java +++ b/src/main/java/net/jacobpeterson/alpaca/refactor/rest/endpoint/PortfolioHistoryEndpoint.java @@ -1,5 +1,6 @@ package net.jacobpeterson.alpaca.refactor.rest.endpoint; +import com.google.common.base.Preconditions; import net.jacobpeterson.alpaca.model.endpoint.portfoliohistory.PortfolioHistory; import net.jacobpeterson.alpaca.model.endpoint.portfoliohistory.PortfolioHistoryDataPoint; import net.jacobpeterson.alpaca.model.endpoint.portfoliohistory.PortfolioHistoryResponse; @@ -71,29 +72,28 @@ public PortfolioHistory get(Integer periodLength, PortfolioPeriodUnit periodUnit Request request = alpacaClient.requestBuilder(urlBuilder.build()) .get() .build(); - PortfolioHistoryResponse portfolioHistoryResponse = alpacaClient.requestObject(request, + PortfolioHistoryResponse response = alpacaClient.requestObject(request, PortfolioHistoryResponse.class); // Check if any response arrays differ in size - if (portfolioHistoryResponse.getTimestamp().size() != portfolioHistoryResponse.getEquity().size() || - portfolioHistoryResponse.getEquity().size() != portfolioHistoryResponse.getProfitLoss().size() || - portfolioHistoryResponse.getProfitLoss().size() != portfolioHistoryResponse.getProfitLossPct().size()) { - throw new IllegalStateException("Response arrays should never differ in size!"); - } + Preconditions.checkState(response.getTimestamp().size() == response.getEquity().size() && + response.getEquity().size() == response.getProfitLoss().size() && + response.getProfitLoss().size() == response.getProfitLossPct().size(), + "Response arrays should not differ in size!"); // Add all data points into one POJO ArrayList dataPoints = new ArrayList<>(); - for (int index = 0; index < portfolioHistoryResponse.getTimestamp().size(); index++) { + for (int index = 0; index < response.getTimestamp().size(); index++) { dataPoints.add(new PortfolioHistoryDataPoint( - portfolioHistoryResponse.getTimestamp().get(index), - portfolioHistoryResponse.getEquity().get(index), - portfolioHistoryResponse.getProfitLoss().get(index), - portfolioHistoryResponse.getProfitLossPct().get(index))); + response.getTimestamp().get(index), + response.getEquity().get(index), + response.getProfitLoss().get(index), + response.getProfitLossPct().get(index))); } return new PortfolioHistory( dataPoints, - portfolioHistoryResponse.getBaseValue(), - portfolioHistoryResponse.getTimeframe()); + response.getBaseValue(), + response.getTimeframe()); } } From 56259aaa367f683cd0491876cac0b4d7f997214b Mon Sep 17 00:00:00 2001 From: Jacob Peterson Date: Sat, 10 Jul 2021 16:06:21 -0600 Subject: [PATCH 123/271] Add AlpacaWebsocket, Streaming Websocket, and associated interfaces, refactor and clean up a bit --- .../account_activities/trade_activity.json | 2 +- .../market_data/realtime/bar/bar_message.json | 2 +- .../realtime/control/error_message.json | 2 +- .../control/subscriptions_message.json | 2 +- .../realtime/control/success_message.json | 2 +- .../realtime/quote/quote_message.json | 2 +- .../market_data/realtime/symbol_message.json | 2 +- .../realtime/trade/trade_message.json | 2 +- .../streaming/account/account_update.json | 7 - .../account/account_update_message.json | 13 - .../authorization/authorization_message.json | 2 +- ..._type.json => streaming_message_type.json} | 3 +- .../streaming/listening/listening_data.json | 4 +- .../listening/listening_message.json | 2 +- ...am_message.json => streaming_message.json} | 6 +- .../streaming/trade/trade_update_message.json | 2 +- schema_json/endpoint/watchlist/watchlist.json | 2 +- .../websocket/listener/StreamListener.java | 21 -- .../websocket/message/StreamMessage.java | 7 - .../websocket/message/StreamMessageType.java | 7 - .../alpaca/refactor/AlpacaAPI.java | 47 ++- .../alpaca/refactor/rest/AlpacaClient.java | 48 ++- .../rest/endpoint/AbstractEndpoint.java | 2 +- .../endpoint/AccountActivitiesEndpoint.java | 16 +- .../rest/endpoint/MarketDataEndpoint.java | 2 +- .../endpoint/PortfolioHistoryEndpoint.java | 4 +- .../rest/endpoint/WatchlistEndpoint.java | 4 +- .../refactor/websocket/AlpacaWebsocket.java | 220 ++++++++++++ .../websocket/AlpacaWebsocketInterface.java | 40 +++ .../marketdata/MarketDataWebsocket.java | 6 + .../MarketDataWebsocketInterface.java | 9 + .../streaming/StreamingListener.java | 18 + .../streaming/StreamingWebsocket.java | 234 +++++++++++++ .../StreamingWebsocketInterface.java | 31 ++ .../broker/client/AlpacaWebsocketClient.java | 320 ------------------ .../client/AlpacaWebsocketClientEndpoint.java | 51 --- .../broker/listener/AlpacaStreamListener.java | 22 -- .../listener/AlpacaStreamListenerAdapter.java | 37 -- .../alpaca/test/mock/AlpacaAPITest.java | 7 +- 39 files changed, 652 insertions(+), 558 deletions(-) delete mode 100644 schema_json/endpoint/streaming/account/account_update.json delete mode 100644 schema_json/endpoint/streaming/account/account_update_message.json rename schema_json/endpoint/streaming/enums/{alpaca_stream_message_type.json => streaming_message_type.json} (81%) rename schema_json/endpoint/streaming/{alpaca_stream_message.json => streaming_message.json} (79%) delete mode 100644 src/main/java/net/jacobpeterson/alpaca/abstracts/websocket/listener/StreamListener.java delete mode 100644 src/main/java/net/jacobpeterson/alpaca/abstracts/websocket/message/StreamMessage.java delete mode 100644 src/main/java/net/jacobpeterson/alpaca/abstracts/websocket/message/StreamMessageType.java create mode 100644 src/main/java/net/jacobpeterson/alpaca/refactor/websocket/AlpacaWebsocket.java create mode 100644 src/main/java/net/jacobpeterson/alpaca/refactor/websocket/AlpacaWebsocketInterface.java create mode 100644 src/main/java/net/jacobpeterson/alpaca/refactor/websocket/marketdata/MarketDataWebsocket.java create mode 100644 src/main/java/net/jacobpeterson/alpaca/refactor/websocket/marketdata/MarketDataWebsocketInterface.java create mode 100644 src/main/java/net/jacobpeterson/alpaca/refactor/websocket/streaming/StreamingListener.java create mode 100644 src/main/java/net/jacobpeterson/alpaca/refactor/websocket/streaming/StreamingWebsocket.java create mode 100644 src/main/java/net/jacobpeterson/alpaca/refactor/websocket/streaming/StreamingWebsocketInterface.java delete mode 100644 src/main/java/net/jacobpeterson/alpaca/websocket/broker/client/AlpacaWebsocketClient.java delete mode 100644 src/main/java/net/jacobpeterson/alpaca/websocket/broker/client/AlpacaWebsocketClientEndpoint.java delete mode 100644 src/main/java/net/jacobpeterson/alpaca/websocket/broker/listener/AlpacaStreamListener.java delete mode 100644 src/main/java/net/jacobpeterson/alpaca/websocket/broker/listener/AlpacaStreamListenerAdapter.java diff --git a/schema_json/endpoint/account_activities/trade_activity.json b/schema_json/endpoint/account_activities/trade_activity.json index 5e817291..0c6dd5f4 100644 --- a/schema_json/endpoint/account_activities/trade_activity.json +++ b/schema_json/endpoint/account_activities/trade_activity.json @@ -2,7 +2,7 @@ "type": "object", "title": "See Account Activities.", "extends": { - "existingJavaType": "net.jacobpeterson.alpaca.domain.accountactivities.AccountActivity" + "existingJavaType": "net.jacobpeterson.alpaca.model.endpoint.accountactivities.AccountActivity" }, "properties": { "cum_qty": { diff --git a/schema_json/endpoint/market_data/realtime/bar/bar_message.json b/schema_json/endpoint/market_data/realtime/bar/bar_message.json index 9688c494..cc6aa003 100644 --- a/schema_json/endpoint/market_data/realtime/bar/bar_message.json +++ b/schema_json/endpoint/market_data/realtime/bar/bar_message.json @@ -1,7 +1,7 @@ { "type": "object", "extends": { - "existingJavaType": "net.jacobpeterson.alpaca.domain.marketdata.realtime.SymbolMessage" + "existingJavaType": "net.jacobpeterson.alpaca.model.endpoint.marketdata.realtime.SymbolMessage" }, "title": "See Real-time Data.", "properties": { diff --git a/schema_json/endpoint/market_data/realtime/control/error_message.json b/schema_json/endpoint/market_data/realtime/control/error_message.json index 5453dce0..6dca33c4 100644 --- a/schema_json/endpoint/market_data/realtime/control/error_message.json +++ b/schema_json/endpoint/market_data/realtime/control/error_message.json @@ -1,7 +1,7 @@ { "type": "object", "extends": { - "existingJavaType": "net.jacobpeterson.alpaca.domain.marketdata.realtime.MarketDataMessage" + "existingJavaType": "net.jacobpeterson.alpaca.model.endpoint.marketdata.realtime.MarketDataMessage" }, "title": "See Real-time Data.", "properties": { diff --git a/schema_json/endpoint/market_data/realtime/control/subscriptions_message.json b/schema_json/endpoint/market_data/realtime/control/subscriptions_message.json index 41e0dec8..19756e7a 100644 --- a/schema_json/endpoint/market_data/realtime/control/subscriptions_message.json +++ b/schema_json/endpoint/market_data/realtime/control/subscriptions_message.json @@ -1,7 +1,7 @@ { "type": "object", "extends": { - "existingJavaType": "net.jacobpeterson.alpaca.domain.marketdata.realtime.MarketDataMessage" + "existingJavaType": "net.jacobpeterson.alpaca.model.endpoint.marketdata.realtime.MarketDataMessage" }, "title": "See Real-time Data.", "properties": { diff --git a/schema_json/endpoint/market_data/realtime/control/success_message.json b/schema_json/endpoint/market_data/realtime/control/success_message.json index 1103e67c..76ba8a8c 100644 --- a/schema_json/endpoint/market_data/realtime/control/success_message.json +++ b/schema_json/endpoint/market_data/realtime/control/success_message.json @@ -1,7 +1,7 @@ { "type": "object", "extends": { - "existingJavaType": "net.jacobpeterson.alpaca.domain.marketdata.realtime.MarketDataMessage" + "existingJavaType": "net.jacobpeterson.alpaca.model.endpoint.marketdata.realtime.MarketDataMessage" }, "title": "See Real-time Data.", "properties": { diff --git a/schema_json/endpoint/market_data/realtime/quote/quote_message.json b/schema_json/endpoint/market_data/realtime/quote/quote_message.json index 2e3179f3..a8ff47b3 100644 --- a/schema_json/endpoint/market_data/realtime/quote/quote_message.json +++ b/schema_json/endpoint/market_data/realtime/quote/quote_message.json @@ -1,7 +1,7 @@ { "type": "object", "extends": { - "existingJavaType": "net.jacobpeterson.alpaca.domain.marketdata.realtime.SymbolMessage" + "existingJavaType": "net.jacobpeterson.alpaca.model.endpoint.marketdata.realtime.SymbolMessage" }, "title": "See Real-time Data.", "properties": { diff --git a/schema_json/endpoint/market_data/realtime/symbol_message.json b/schema_json/endpoint/market_data/realtime/symbol_message.json index e69ef37c..961d15c0 100644 --- a/schema_json/endpoint/market_data/realtime/symbol_message.json +++ b/schema_json/endpoint/market_data/realtime/symbol_message.json @@ -1,7 +1,7 @@ { "type": "object", "extends": { - "existingJavaType": "net.jacobpeterson.alpaca.domain.marketdata.realtime.MarketDataMessage" + "existingJavaType": "net.jacobpeterson.alpaca.model.endpoint.marketdata.realtime.MarketDataMessage" }, "title": "See Real-time Data.", "properties": { diff --git a/schema_json/endpoint/market_data/realtime/trade/trade_message.json b/schema_json/endpoint/market_data/realtime/trade/trade_message.json index f2bcca33..7d01e2b8 100644 --- a/schema_json/endpoint/market_data/realtime/trade/trade_message.json +++ b/schema_json/endpoint/market_data/realtime/trade/trade_message.json @@ -1,7 +1,7 @@ { "type": "object", "extends": { - "existingJavaType": "net.jacobpeterson.alpaca.domain.marketdata.realtime.SymbolMessage" + "existingJavaType": "net.jacobpeterson.alpaca.model.endpoint.marketdata.realtime.SymbolMessage" }, "title": "See Real-time Data.", "properties": { diff --git a/schema_json/endpoint/streaming/account/account_update.json b/schema_json/endpoint/streaming/account/account_update.json deleted file mode 100644 index 7123cdd9..00000000 --- a/schema_json/endpoint/streaming/account/account_update.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "type": "object", - "extends": { - "existingJavaType": "net.jacobpeterson.alpaca.model.endpoint.account.Account" - }, - "title": "See Streaming." -} diff --git a/schema_json/endpoint/streaming/account/account_update_message.json b/schema_json/endpoint/streaming/account/account_update_message.json deleted file mode 100644 index bef98c61..00000000 --- a/schema_json/endpoint/streaming/account/account_update_message.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "type": "object", - "extends": { - "existingJavaType": "net.jacobpeterson.alpaca.model.endpoint.streaming.AlpacaStreamMessage" - }, - "title": "See Streaming.", - "properties": { - "data": { - "existingJavaType": "net.jacobpeterson.alpaca.model.endpoint.streaming.account.AccountUpdate", - "title": "The {@link net.jacobpeterson.alpaca.model.endpoint.streaming.account.AccountUpdate}." - } - } -} diff --git a/schema_json/endpoint/streaming/authorization/authorization_message.json b/schema_json/endpoint/streaming/authorization/authorization_message.json index ecf6ead6..5e266609 100644 --- a/schema_json/endpoint/streaming/authorization/authorization_message.json +++ b/schema_json/endpoint/streaming/authorization/authorization_message.json @@ -2,7 +2,7 @@ "type": "object", "title": "See Streaming.", "extends": { - "existingJavaType": "net.jacobpeterson.alpaca.domain.streaming.AlpacaStreamMessage" + "existingJavaType": "net.jacobpeterson.alpaca.model.endpoint.streaming.StreamingMessage" }, "properties": { "data": { diff --git a/schema_json/endpoint/streaming/enums/alpaca_stream_message_type.json b/schema_json/endpoint/streaming/enums/streaming_message_type.json similarity index 81% rename from schema_json/endpoint/streaming/enums/alpaca_stream_message_type.json rename to schema_json/endpoint/streaming/enums/streaming_message_type.json index 315f94be..454ee643 100644 --- a/schema_json/endpoint/streaming/enums/alpaca_stream_message_type.json +++ b/schema_json/endpoint/streaming/enums/streaming_message_type.json @@ -4,7 +4,6 @@ "enum": [ "listening", "authorization", - "trade_updates", - "account_updates" + "trade_updates" ] } diff --git a/schema_json/endpoint/streaming/listening/listening_data.json b/schema_json/endpoint/streaming/listening/listening_data.json index 8d7cd2c7..eb9d6eb9 100644 --- a/schema_json/endpoint/streaming/listening/listening_data.json +++ b/schema_json/endpoint/streaming/listening/listening_data.json @@ -3,8 +3,8 @@ "title": "See Streaming.", "properties": { "streams": { - "existingJavaType": "java.util.ArrayList", - "title": "The {@link java.util.ArrayList} of {@link net.jacobpeterson.alpaca.model.endpoint.streaming.enums.AlpacaStreamMessageType}s." + "existingJavaType": "java.util.ArrayList", + "title": "The {@link java.util.ArrayList} of {@link net.jacobpeterson.alpaca.model.endpoint.streaming.enums.StreamingMessageType}s." } } } diff --git a/schema_json/endpoint/streaming/listening/listening_message.json b/schema_json/endpoint/streaming/listening/listening_message.json index e095fedc..24413cb8 100644 --- a/schema_json/endpoint/streaming/listening/listening_message.json +++ b/schema_json/endpoint/streaming/listening/listening_message.json @@ -2,7 +2,7 @@ "type": "object", "title": "See Streaming.", "extends": { - "existingJavaType": "net.jacobpeterson.alpaca.domain.streaming.AlpacaStreamMessage" + "existingJavaType": "net.jacobpeterson.alpaca.model.endpoint.streaming.StreamingMessage" }, "properties": { "data": { diff --git a/schema_json/endpoint/streaming/alpaca_stream_message.json b/schema_json/endpoint/streaming/streaming_message.json similarity index 79% rename from schema_json/endpoint/streaming/alpaca_stream_message.json rename to schema_json/endpoint/streaming/streaming_message.json index f523ab42..4b32a67b 100644 --- a/schema_json/endpoint/streaming/alpaca_stream_message.json +++ b/schema_json/endpoint/streaming/streaming_message.json @@ -1,13 +1,13 @@ { "type": "object", "javaInterfaces": [ - "net.jacobpeterson.alpaca.abstracts.websocket.message.StreamMessage" + "net.jacobpeterson.alpaca.abstracts.websocket.message.StreamingMessage" ], "title": "See Streaming.", "properties": { "stream": { - "existingJavaType": "net.jacobpeterson.alpaca.model.endpoint.streaming.enums.AlpacaStreamMessageType", - "title": "The {@link net.jacobpeterson.alpaca.model.endpoint.streaming.enums.AlpacaStreamMessageType}." + "existingJavaType": "net.jacobpeterson.alpaca.model.endpoint.streaming.enums.StreamingMessageType", + "title": "The {@link net.jacobpeterson.alpaca.model.endpoint.streaming.enums.StreamingMessageType}." } } } diff --git a/schema_json/endpoint/streaming/trade/trade_update_message.json b/schema_json/endpoint/streaming/trade/trade_update_message.json index 89107eb8..014d3d6f 100644 --- a/schema_json/endpoint/streaming/trade/trade_update_message.json +++ b/schema_json/endpoint/streaming/trade/trade_update_message.json @@ -1,7 +1,7 @@ { "type": "object", "extends": { - "existingJavaType": "net.jacobpeterson.alpaca.domain.streaming.AlpacaStreamMessage" + "existingJavaType": "net.jacobpeterson.alpaca.model.endpoint.streaming.StreamingMessage" }, "title": "See Streaming.", "properties": { diff --git a/schema_json/endpoint/watchlist/watchlist.json b/schema_json/endpoint/watchlist/watchlist.json index 9bc2d429..0cfa52a0 100644 --- a/schema_json/endpoint/watchlist/watchlist.json +++ b/schema_json/endpoint/watchlist/watchlist.json @@ -23,7 +23,7 @@ "title": "Account ID." }, "assets": { - "existingJavaType": "java.util.ArrayList", + "existingJavaType": "java.util.ArrayList", "title": "The content of this watchlist, in the order as registered by the client." } } diff --git a/src/main/java/net/jacobpeterson/alpaca/abstracts/websocket/listener/StreamListener.java b/src/main/java/net/jacobpeterson/alpaca/abstracts/websocket/listener/StreamListener.java deleted file mode 100644 index 9b7cb113..00000000 --- a/src/main/java/net/jacobpeterson/alpaca/abstracts/websocket/listener/StreamListener.java +++ /dev/null @@ -1,21 +0,0 @@ -package net.jacobpeterson.alpaca.abstracts.websocket.listener; - -import net.jacobpeterson.alpaca.abstracts.websocket.message.StreamMessage; -import net.jacobpeterson.alpaca.abstracts.websocket.message.StreamMessageType; - -/** - * {@link StreamListener} is an interface for listening to stream updates. - * - * @param the {@link StreamMessageType} type parameter - * @param the {@link StreamMessage} type parameter - */ -public interface StreamListener { - - /** - * Called on a stream update. - * - * @param streamMessageType the {@link StreamMessageType} - * @param streamMessage the {@link StreamMessage} - */ - void onStreamUpdate(T streamMessageType, M streamMessage); -} diff --git a/src/main/java/net/jacobpeterson/alpaca/abstracts/websocket/message/StreamMessage.java b/src/main/java/net/jacobpeterson/alpaca/abstracts/websocket/message/StreamMessage.java deleted file mode 100644 index b0822851..00000000 --- a/src/main/java/net/jacobpeterson/alpaca/abstracts/websocket/message/StreamMessage.java +++ /dev/null @@ -1,7 +0,0 @@ -package net.jacobpeterson.alpaca.abstracts.websocket.message; - -/** - * {@link StreamMessage} is an interface to define stream messages. - */ -public interface StreamMessage { -} diff --git a/src/main/java/net/jacobpeterson/alpaca/abstracts/websocket/message/StreamMessageType.java b/src/main/java/net/jacobpeterson/alpaca/abstracts/websocket/message/StreamMessageType.java deleted file mode 100644 index 4275202b..00000000 --- a/src/main/java/net/jacobpeterson/alpaca/abstracts/websocket/message/StreamMessageType.java +++ /dev/null @@ -1,7 +0,0 @@ -package net.jacobpeterson.alpaca.abstracts.websocket.message; - -/** - * {@link StreamMessageType} is an interface to define a stream message type. - */ -public interface StreamMessageType { -} diff --git a/src/main/java/net/jacobpeterson/alpaca/refactor/AlpacaAPI.java b/src/main/java/net/jacobpeterson/alpaca/refactor/AlpacaAPI.java index 486ae674..3f973ee4 100644 --- a/src/main/java/net/jacobpeterson/alpaca/refactor/AlpacaAPI.java +++ b/src/main/java/net/jacobpeterson/alpaca/refactor/AlpacaAPI.java @@ -1,10 +1,14 @@ package net.jacobpeterson.alpaca.refactor; +import devcsrj.okhttp3.logging.HttpLoggingInterceptor; import net.jacobpeterson.alpaca.model.properties.DataAPIType; import net.jacobpeterson.alpaca.model.properties.EndpointAPIType; import net.jacobpeterson.alpaca.refactor.properties.AlpacaProperties; import net.jacobpeterson.alpaca.refactor.rest.AlpacaClient; import net.jacobpeterson.alpaca.refactor.rest.endpoint.*; +import okhttp3.OkHttpClient; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import static com.google.common.base.Preconditions.checkNotNull; @@ -17,8 +21,11 @@ */ public class AlpacaAPI { + private static final Logger LOGGER = LoggerFactory.getLogger(AlpacaAPI.class); + private static final String VERSION_2_PATH_SEGMENT = "v2"; + private final OkHttpClient okHttpClient; private final AlpacaClient brokerClient; private final AlpacaClient dataClient; // Ordering of fields/methods below are analogous to the ordering in the Alpaca documentation @@ -52,7 +59,7 @@ public AlpacaAPI() { * @param secret the secret */ public AlpacaAPI(String keyID, String secret) { - this(keyID, secret, null, + this(null, keyID, secret, null, AlpacaProperties.ENDPOINT_API_TYPE, AlpacaProperties.DATA_API_TYPE); } @@ -66,7 +73,7 @@ public AlpacaAPI(String keyID, String secret) { * @param dataAPIType the {@link DataAPIType} */ public AlpacaAPI(String keyID, String secret, EndpointAPIType endpointAPIType, DataAPIType dataAPIType) { - this(keyID, secret, null, endpointAPIType, dataAPIType); + this(null, keyID, secret, null, endpointAPIType, dataAPIType); } /** @@ -75,7 +82,7 @@ public AlpacaAPI(String keyID, String secret, EndpointAPIType endpointAPIType, D * @param oAuthToken the OAuth token. Note that the Data API v2 does not work with OAuth tokens. */ public AlpacaAPI(String oAuthToken) { - this(null, null, oAuthToken, + this(null, null, null, oAuthToken, AlpacaProperties.ENDPOINT_API_TYPE, AlpacaProperties.DATA_API_TYPE); } @@ -83,17 +90,31 @@ public AlpacaAPI(String oAuthToken) { /** * Instantiates a new {@link AlpacaAPI}. * + * @param okHttpClient the {@link OkHttpClient} or null to create a default instance * @param keyID the key ID * @param secretKey the secret key * @param oAuthToken the OAuth token * @param endpointAPIType the {@link EndpointAPIType} * @param dataAPIType the {@link DataAPIType} */ - public AlpacaAPI(String keyID, String secretKey, String oAuthToken, EndpointAPIType endpointAPIType, - DataAPIType dataAPIType) { + public AlpacaAPI(OkHttpClient okHttpClient, String keyID, String secretKey, String oAuthToken, + EndpointAPIType endpointAPIType, DataAPIType dataAPIType) { checkNotNull(endpointAPIType); checkNotNull(dataAPIType); + // Create default 'okHttpClient' + if (okHttpClient == null) { + OkHttpClient.Builder clientBuilder = new OkHttpClient.Builder(); + + if (LOGGER.isDebugEnabled()) { + clientBuilder.addInterceptor(new HttpLoggingInterceptor(LOGGER)); + } + + okHttpClient = clientBuilder.build(); + } + + this.okHttpClient = okHttpClient; + String brokerHostSubdomain; switch (endpointAPIType) { case LIVE: @@ -107,10 +128,11 @@ public AlpacaAPI(String keyID, String secretKey, String oAuthToken, EndpointAPIT } if (oAuthToken == null) { - brokerClient = new AlpacaClient(keyID, secretKey, brokerHostSubdomain, VERSION_2_PATH_SEGMENT); - dataClient = new AlpacaClient(keyID, secretKey, "data", VERSION_2_PATH_SEGMENT); + brokerClient = new AlpacaClient(okHttpClient, keyID, secretKey, + brokerHostSubdomain, VERSION_2_PATH_SEGMENT); + dataClient = new AlpacaClient(okHttpClient, keyID, secretKey, "data", VERSION_2_PATH_SEGMENT); } else { - brokerClient = new AlpacaClient(oAuthToken, brokerHostSubdomain, VERSION_2_PATH_SEGMENT); + brokerClient = new AlpacaClient(okHttpClient, oAuthToken, brokerHostSubdomain, VERSION_2_PATH_SEGMENT); dataClient = null; } @@ -272,6 +294,15 @@ public PortfolioHistoryEndpoint portfolioHistory() { // marketDataWebSocketClient.setWebsocketStateListener(websocketStateListener); // } + /** + * Gets {@link #okHttpClient}. + * + * @return the {@link OkHttpClient} + */ + public OkHttpClient getOkHttpClient() { + return okHttpClient; + } + /** * Gets {@link #brokerClient}. * diff --git a/src/main/java/net/jacobpeterson/alpaca/refactor/rest/AlpacaClient.java b/src/main/java/net/jacobpeterson/alpaca/refactor/rest/AlpacaClient.java index 7e7de26a..9fb340ac 100644 --- a/src/main/java/net/jacobpeterson/alpaca/refactor/rest/AlpacaClient.java +++ b/src/main/java/net/jacobpeterson/alpaca/refactor/rest/AlpacaClient.java @@ -12,6 +12,7 @@ import java.util.function.Predicate; import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.base.Preconditions.checkNotNull; import static net.jacobpeterson.alpaca.refactor.util.gson.GsonUtil.GSON; /** @@ -19,47 +20,56 @@ */ public class AlpacaClient { + private final OkHttpClient okHttpClient; private final HttpUrl baseURL; private final Headers requestHeaders; - private OkHttpClient okHttpClient; - /** * Instantiates a new {@link AlpacaClient}. * + * @param okHttpClient the {@link OkHttpClient} * @param keyID the key ID * @param secretKey the secret key * @param alpacaSubdomain the Alpaca subdomain * @param versionPathSegment the version path segment e.g. "v2" */ - public AlpacaClient(String keyID, String secretKey, String alpacaSubdomain, String versionPathSegment) { - this(keyID, secretKey, null, alpacaSubdomain, versionPathSegment); + public AlpacaClient(OkHttpClient okHttpClient, String keyID, String secretKey, String alpacaSubdomain, + String versionPathSegment) { + this(okHttpClient, keyID, secretKey, null, alpacaSubdomain, versionPathSegment); } /** * Instantiates a new {@link AlpacaClient}. * + * @param okHttpClient the {@link OkHttpClient} * @param oAuthToken the OAuth token * @param alpacaSubdomain the Alpaca subdomain * @param versionPathSegment the version path segment e.g. "v2" */ - public AlpacaClient(String oAuthToken, String alpacaSubdomain, String versionPathSegment) { - this(null, null, oAuthToken, alpacaSubdomain, versionPathSegment); + public AlpacaClient(OkHttpClient okHttpClient, String oAuthToken, String alpacaSubdomain, + String versionPathSegment) { + this(okHttpClient, null, null, oAuthToken, alpacaSubdomain, versionPathSegment); } /** * Instantiates a new {@link AlpacaClient}. * + * @param okHttpClient the {@link OkHttpClient} * @param keyID the key ID * @param secretKey the secret key * @param oAuthToken the OAuth token * @param alpacaSubdomain the Alpaca subdomain * @param versionPathSegment the version path segment e.g. "v2" */ - protected AlpacaClient(String keyID, String secretKey, String oAuthToken, String alpacaSubdomain, - String versionPathSegment) { - checkArgument((keyID != null && secretKey != null) || oAuthToken != null, + protected AlpacaClient(OkHttpClient okHttpClient, String keyID, String secretKey, String oAuthToken, + String alpacaSubdomain, String versionPathSegment) { + checkNotNull(okHttpClient); + checkArgument((keyID != null && secretKey != null) ^ oAuthToken != null, "You must specify a (KeyID and Secret Key) or an OAuthToken!"); + checkNotNull(alpacaSubdomain); + checkNotNull(versionPathSegment); + + this.okHttpClient = okHttpClient; baseURL = new HttpUrl.Builder() .scheme("https") @@ -77,8 +87,6 @@ protected AlpacaClient(String keyID, String secretKey, String oAuthToken, String .add("APCA-API-SECRET-KEY", secretKey); } requestHeaders = requestHeadersBuilder.build(); - - okHttpClient = new OkHttpClient(); } /** @@ -203,22 +211,4 @@ public HttpUrl getBaseURL() { public Headers getRequestHeaders() { return requestHeaders; } - - /** - * Gets {@link #okHttpClient}. - * - * @return the {@link OkHttpClient} - */ - public OkHttpClient getOkHttpClient() { - return okHttpClient; - } - - /** - * Sets {@link #okHttpClient}. - * - * @param okHttpClient the {@link OkHttpClient} - */ - public void setOkHttpClient(OkHttpClient okHttpClient) { - this.okHttpClient = okHttpClient; - } } diff --git a/src/main/java/net/jacobpeterson/alpaca/refactor/rest/endpoint/AbstractEndpoint.java b/src/main/java/net/jacobpeterson/alpaca/refactor/rest/endpoint/AbstractEndpoint.java index 7c50cef2..2c5617eb 100644 --- a/src/main/java/net/jacobpeterson/alpaca/refactor/rest/endpoint/AbstractEndpoint.java +++ b/src/main/java/net/jacobpeterson/alpaca/refactor/rest/endpoint/AbstractEndpoint.java @@ -5,7 +5,7 @@ /** * {@link AbstractEndpoint} is an abstract class representing a RestAPI endpoint. */ -public class AbstractEndpoint { +public abstract class AbstractEndpoint { protected final AlpacaClient alpacaClient; protected final String endpointPathSegment; diff --git a/src/main/java/net/jacobpeterson/alpaca/refactor/rest/endpoint/AccountActivitiesEndpoint.java b/src/main/java/net/jacobpeterson/alpaca/refactor/rest/endpoint/AccountActivitiesEndpoint.java index 898ce066..90c7a1f9 100644 --- a/src/main/java/net/jacobpeterson/alpaca/refactor/rest/endpoint/AccountActivitiesEndpoint.java +++ b/src/main/java/net/jacobpeterson/alpaca/refactor/rest/endpoint/AccountActivitiesEndpoint.java @@ -10,7 +10,6 @@ import net.jacobpeterson.alpaca.model.endpoint.common.enums.SortDirection; import net.jacobpeterson.alpaca.refactor.rest.AlpacaClient; import net.jacobpeterson.alpaca.refactor.rest.AlpacaClientException; -import net.jacobpeterson.alpaca.refactor.util.gson.GsonUtil; import okhttp3.HttpUrl; import okhttp3.Request; import org.slf4j.Logger; @@ -24,6 +23,7 @@ import java.util.stream.Collectors; import static com.google.common.base.Preconditions.checkState; +import static net.jacobpeterson.alpaca.refactor.util.gson.GsonUtil.GSON; /** * {@link AbstractEndpoint} for @@ -107,28 +107,26 @@ public List get(ZonedDateTime date, ZonedDateTime until, ZonedD .build(); JsonElement responseJSON = alpacaClient.requestJSON(request); - checkState(responseJSON instanceof JsonArray, - String.format("The response must be an array! Received: %s", responseJSON)); + checkState(responseJSON instanceof JsonArray, "The response must be an array! Received: %s", responseJSON); ArrayList accountActivities = new ArrayList<>(); for (JsonElement responseArrayElement : responseJSON.getAsJsonArray()) { checkState(responseArrayElement instanceof JsonObject, - String.format("All array elements must be objects! Received: %s", responseArrayElement)); + "All array elements must be objects! Received: %s", responseArrayElement); JsonObject responseArrayObject = responseArrayElement.getAsJsonObject(); JsonElement activityTypeElement = responseArrayObject.get(ACTIVITY_TYPE_FIELD); - checkState(activityTypeElement != null, - String.format("Activity type elements must have %s field! Received: %s", - ACTIVITY_TYPE_FIELD, responseArrayElement)); + checkState(activityTypeElement != null, "Activity type elements must have %s field! Received: %s", + ACTIVITY_TYPE_FIELD, responseArrayElement); String activityType = activityTypeElement.getAsString(); AccountActivity accountActivity; // A 'TradeActivity' always has 'activity_type' field as 'FILL' if (activityType.equals(ActivityType.FILL.toString())) { - accountActivity = GsonUtil.GSON.fromJson(responseArrayObject, TradeActivity.class); + accountActivity = GSON.fromJson(responseArrayObject, TradeActivity.class); } else { - accountActivity = GsonUtil.GSON.fromJson(responseArrayObject, NonTradeActivity.class); + accountActivity = GSON.fromJson(responseArrayObject, NonTradeActivity.class); } if (accountActivity.getActivityType() == null) { diff --git a/src/main/java/net/jacobpeterson/alpaca/refactor/rest/endpoint/MarketDataEndpoint.java b/src/main/java/net/jacobpeterson/alpaca/refactor/rest/endpoint/MarketDataEndpoint.java index a1603143..2c668ed8 100644 --- a/src/main/java/net/jacobpeterson/alpaca/refactor/rest/endpoint/MarketDataEndpoint.java +++ b/src/main/java/net/jacobpeterson/alpaca/refactor/rest/endpoint/MarketDataEndpoint.java @@ -236,7 +236,7 @@ public BarsResponse getBars(String symbol, ZonedDateTime start, ZonedDateTime en */ public Map getSnapshots(List symbols) throws AlpacaClientException { checkNotNull(symbols); - checkArgument(!symbols.isEmpty()); + checkArgument(!symbols.isEmpty(), "'symbols' cannot be empty!"); HttpUrl.Builder urlBuilder = alpacaClient.urlBuilder() .addPathSegment(endpointPathSegment) diff --git a/src/main/java/net/jacobpeterson/alpaca/refactor/rest/endpoint/PortfolioHistoryEndpoint.java b/src/main/java/net/jacobpeterson/alpaca/refactor/rest/endpoint/PortfolioHistoryEndpoint.java index 0a29483a..96dc7415 100644 --- a/src/main/java/net/jacobpeterson/alpaca/refactor/rest/endpoint/PortfolioHistoryEndpoint.java +++ b/src/main/java/net/jacobpeterson/alpaca/refactor/rest/endpoint/PortfolioHistoryEndpoint.java @@ -15,6 +15,8 @@ import java.time.format.DateTimeFormatter; import java.util.ArrayList; +import static com.google.common.base.Preconditions.checkState; + /** * {@link AbstractEndpoint} for * Portfolio @@ -76,7 +78,7 @@ public PortfolioHistory get(Integer periodLength, PortfolioPeriodUnit periodUnit PortfolioHistoryResponse.class); // Check if any response arrays differ in size - Preconditions.checkState(response.getTimestamp().size() == response.getEquity().size() && + checkState(response.getTimestamp().size() == response.getEquity().size() && response.getEquity().size() == response.getProfitLoss().size() && response.getProfitLoss().size() == response.getProfitLossPct().size(), "Response arrays should not differ in size!"); diff --git a/src/main/java/net/jacobpeterson/alpaca/refactor/rest/endpoint/WatchlistEndpoint.java b/src/main/java/net/jacobpeterson/alpaca/refactor/rest/endpoint/WatchlistEndpoint.java index f572cbda..5ea8df42 100644 --- a/src/main/java/net/jacobpeterson/alpaca/refactor/rest/endpoint/WatchlistEndpoint.java +++ b/src/main/java/net/jacobpeterson/alpaca/refactor/rest/endpoint/WatchlistEndpoint.java @@ -60,7 +60,7 @@ public List get() throws AlpacaClientException { */ public Watchlist create(String name, String... symbols) throws AlpacaClientException { checkNotNull(name); - checkArgument(name.length() <= 64); + checkArgument(name.length() <= 64, "'name' cannot be longer than 64 characters!"); HttpUrl.Builder urlBuilder = alpacaClient.urlBuilder() .addPathSegment(endpointPathSegment); @@ -115,7 +115,7 @@ public Watchlist get(String watchlistID) throws AlpacaClientException { public Watchlist update(String watchlistID, String name, String... symbols) throws AlpacaClientException { checkNotNull(watchlistID); - checkArgument(name == null || name.length() <= 64); + checkArgument(name == null || name.length() <= 64, "'name' cannot be longer than 64 characters!"); HttpUrl.Builder urlBuilder = alpacaClient.urlBuilder() .addPathSegment(endpointPathSegment) diff --git a/src/main/java/net/jacobpeterson/alpaca/refactor/websocket/AlpacaWebsocket.java b/src/main/java/net/jacobpeterson/alpaca/refactor/websocket/AlpacaWebsocket.java new file mode 100644 index 00000000..a3e233f9 --- /dev/null +++ b/src/main/java/net/jacobpeterson/alpaca/refactor/websocket/AlpacaWebsocket.java @@ -0,0 +1,220 @@ +package net.jacobpeterson.alpaca.refactor.websocket; + +import okhttp3.*; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.concurrent.ForkJoinPool; + +import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.base.Preconditions.checkNotNull; + +/** + * {@link AlpacaWebsocket} represents an abstract websocket for Alpaca. + */ +public abstract class AlpacaWebsocket extends WebSocketListener implements AlpacaWebsocketInterface { + + /** + * Defines a websocket normal closure code. + * + * @see WebSocket#close(int, String) + */ + public static final int WEBSOCKET_NORMAL_CLOSURE_CODE = 1000; + + /** + * Defines a websocket normal closure message. + * + * @see WebSocket#close(int, String) + */ + public static final String WEBSOCKET_NORMAL_CLOSURE_MESSAGE = "Normal closure"; + + /** Defines the maximum number of reconnection attempts to be made by an {@link AlpacaWebsocket}. */ + public static int MAX_RECONNECT_ATTEMPTS = 5; + + /** + * Defines the millisecond sleep interval between reconnection attempts made by an {@link AlpacaWebsocket}. + */ + public static int RECONNECT_SLEEP_INTERVAL = 1000; + + private static final Logger LOGGER = LoggerFactory.getLogger(AlpacaWebsocket.class); + + protected final OkHttpClient okHttpClient; + protected final HttpUrl websocketURL; + protected final String websocketName; + protected final String keyID; + protected final String secretKey; + protected final String oAuthToken; + protected final boolean useOAuth; + + protected WebSocket websocket; + protected boolean connected; + protected boolean authenticated; + protected boolean intentionalClose; + protected int reconnectAttempts; + protected boolean automaticallyReconnect; + + /** + * Instantiates a {@link AlpacaWebsocket}. + * + * @param okHttpClient the {@link OkHttpClient} + * @param websocketURL the websocket {@link HttpUrl} + * @param websocketName the websocket name + * @param keyID the key ID + * @param secretKey the secret key + * @param oAuthToken the OAuth token + */ + public AlpacaWebsocket(OkHttpClient okHttpClient, HttpUrl websocketURL, String websocketName, + String keyID, String secretKey, String oAuthToken) { + checkNotNull(okHttpClient); + checkNotNull(websocketURL); + checkNotNull(websocketName); + checkArgument((keyID != null && secretKey != null) ^ oAuthToken != null, + "You must specify a (KeyID and Secret Key) or an OAuthToken!"); + + this.okHttpClient = okHttpClient; + this.websocketURL = websocketURL; + this.websocketName = websocketName; + this.keyID = keyID; + this.secretKey = secretKey; + this.oAuthToken = oAuthToken; + useOAuth = oAuthToken != null; + + automaticallyReconnect = true; + } + + @Override + public void connect() { + if (!isConnected()) { + Request websocketRequest = new Request.Builder() + .url(websocketURL) + .get() + .build(); + websocket = okHttpClient.newWebSocket(websocketRequest, this); + } + } + + @Override + public void disconnect() { + if (isConnected() && websocket != null) { + intentionalClose = true; + websocket.close(WEBSOCKET_NORMAL_CLOSURE_CODE, WEBSOCKET_NORMAL_CLOSURE_MESSAGE); + } else { + cleanupState(); + } + } + + @Override + public boolean isConnected() { + return connected; + } + + @Override + public boolean isAuthenticated() { + return authenticated; + } + + @Override + public void onOpen(@NotNull WebSocket webSocket, @NotNull Response response) { + LOGGER.info("{} websocket opened.", websocketName); + LOGGER.debug("{} websocket response: {}", websocketName, response); + + connected = true; + + if (reconnectAttempts > 0) { + reconnectAttempts = 0; + onReconnection(); + } else { + onConnection(); + } + } + + @Override + public void onClosed(@NotNull WebSocket webSocket, int code, @NotNull String reason) { + if (intentionalClose) { + LOGGER.info("{} websocket closed. Code: {}, Reason: {}", websocketName, code, reason); + cleanupState(); + } else { + LOGGER.error("{} websocket closed unintentionally! Code: {}, Reason: {}", websocketName, code, reason); + handleReconnectionAttempt(); + } + } + + @Override + public void onFailure(@NotNull WebSocket webSocket, @NotNull Throwable cause, @Nullable Response response) { + LOGGER.error("{} websocket failure! Response: {}", websocketName, response, cause); + handleReconnectionAttempt(); + } + + /** + * Attempts to reconnect the disconnected {@link #websocket}. + */ + private void handleReconnectionAttempt() { + if (!automaticallyReconnect) { + return; + } + + if (reconnectAttempts < MAX_RECONNECT_ATTEMPTS) { + LOGGER.info("Attempting to reconnect {} websocket in {} milliseconds...", + websocketName, RECONNECT_SLEEP_INTERVAL); + + reconnectAttempts++; + + ForkJoinPool.commonPool().execute(() -> { + try { + Thread.sleep(RECONNECT_SLEEP_INTERVAL); + } catch (InterruptedException ignored) {} + + connect(); + }); + } else { + LOGGER.error("Exhausted {} reconnection attempts. Not attempting to reconnect.", MAX_RECONNECT_ATTEMPTS); + cleanupState(); + } + } + + /** + * Cleans up this instances state variables. + */ + private void cleanupState() { + websocket = null; + connected = false; + authenticated = false; + intentionalClose = false; + reconnectAttempts = 0; + } + + /** + * Called when a websocket connection is made. + */ + protected abstract void onConnection(); + + /** + * Called when a websocket reconnection is made after unintentional disconnection. + */ + protected abstract void onReconnection(); + + /** + * Sends an authentication message to authenticate this websocket stream. + */ + protected abstract void sendAuthenticationMessage(); + + /** + * Gets {@link #automaticallyReconnect}. + * + * @return a boolean + */ + public boolean doesAutomaticallyReconnect() { + return automaticallyReconnect; + } + + /** + * Sets {@link #automaticallyReconnect}. + * + * @param automaticallyReconnect the boolean + */ + public void setAutomaticallyReconnect(boolean automaticallyReconnect) { + this.automaticallyReconnect = automaticallyReconnect; + } +} diff --git a/src/main/java/net/jacobpeterson/alpaca/refactor/websocket/AlpacaWebsocketInterface.java b/src/main/java/net/jacobpeterson/alpaca/refactor/websocket/AlpacaWebsocketInterface.java new file mode 100644 index 00000000..be1de862 --- /dev/null +++ b/src/main/java/net/jacobpeterson/alpaca/refactor/websocket/AlpacaWebsocketInterface.java @@ -0,0 +1,40 @@ +package net.jacobpeterson.alpaca.refactor.websocket; + +/** + * {@link AlpacaWebsocketInterface} defines an interface for Alpaca websockets. + */ +public interface AlpacaWebsocketInterface { + + /** + * Connects this Websocket. + */ + void connect(); + + /** + * Disconnects this Websocket. + */ + void disconnect(); + + /** + * Returns true if this websocket is connected. + * + * @return a boolean + */ + boolean isConnected(); + + /** + * Returns true if this websocket is authenticated. + * + * @return a boolean + */ + boolean isAuthenticated(); + + /** + * True if {@link #isConnected()} and {@link #isAuthenticated()}. + * + * @return a boolean + */ + default boolean isValid() { + return isConnected() && isAuthenticated(); + } +} diff --git a/src/main/java/net/jacobpeterson/alpaca/refactor/websocket/marketdata/MarketDataWebsocket.java b/src/main/java/net/jacobpeterson/alpaca/refactor/websocket/marketdata/MarketDataWebsocket.java new file mode 100644 index 00000000..4d72807f --- /dev/null +++ b/src/main/java/net/jacobpeterson/alpaca/refactor/websocket/marketdata/MarketDataWebsocket.java @@ -0,0 +1,6 @@ +package net.jacobpeterson.alpaca.refactor.websocket.marketdata; + +import net.jacobpeterson.alpaca.refactor.websocket.AlpacaWebsocket; + +public class MarketDataWebsocket extends AlpacaWebsocket implements MarketDataWebsocketInterface { +} diff --git a/src/main/java/net/jacobpeterson/alpaca/refactor/websocket/marketdata/MarketDataWebsocketInterface.java b/src/main/java/net/jacobpeterson/alpaca/refactor/websocket/marketdata/MarketDataWebsocketInterface.java new file mode 100644 index 00000000..a15ac764 --- /dev/null +++ b/src/main/java/net/jacobpeterson/alpaca/refactor/websocket/marketdata/MarketDataWebsocketInterface.java @@ -0,0 +1,9 @@ +package net.jacobpeterson.alpaca.refactor.websocket.marketdata; + +import net.jacobpeterson.alpaca.refactor.websocket.AlpacaWebsocketInterface; + +/** + * {@link MarketDataWebsocketInterface} is an {@link AlpacaWebsocketInterface} for a {@link MarketDataWebsocket}. + */ +public interface MarketDataWebsocketInterface extends AlpacaWebsocketInterface { +} diff --git a/src/main/java/net/jacobpeterson/alpaca/refactor/websocket/streaming/StreamingListener.java b/src/main/java/net/jacobpeterson/alpaca/refactor/websocket/streaming/StreamingListener.java new file mode 100644 index 00000000..750a3d24 --- /dev/null +++ b/src/main/java/net/jacobpeterson/alpaca/refactor/websocket/streaming/StreamingListener.java @@ -0,0 +1,18 @@ +package net.jacobpeterson.alpaca.refactor.websocket.streaming; + +import net.jacobpeterson.alpaca.model.endpoint.streaming.StreamingMessage; +import net.jacobpeterson.alpaca.model.endpoint.streaming.enums.StreamingMessageType; + +/** + * {@link StreamingListener} defines a listener interface for {@link StreamingWebsocket} + */ +public interface StreamingListener { + + /** + * Called when a {@link StreamingMessage} is received. + * + * @param streamingMessageType the {@link StreamingMessageType} + * @param streamingMessage the {@link StreamingMessage} + */ + void onMessage(StreamingMessageType streamingMessageType, StreamingMessage streamingMessage); +} diff --git a/src/main/java/net/jacobpeterson/alpaca/refactor/websocket/streaming/StreamingWebsocket.java b/src/main/java/net/jacobpeterson/alpaca/refactor/websocket/streaming/StreamingWebsocket.java new file mode 100644 index 00000000..906a4e4d --- /dev/null +++ b/src/main/java/net/jacobpeterson/alpaca/refactor/websocket/streaming/StreamingWebsocket.java @@ -0,0 +1,234 @@ +package net.jacobpeterson.alpaca.refactor.websocket.streaming; + +import com.google.gson.*; +import net.jacobpeterson.alpaca.model.endpoint.streaming.StreamingMessage; +import net.jacobpeterson.alpaca.model.endpoint.streaming.authorization.AuthorizationData; +import net.jacobpeterson.alpaca.model.endpoint.streaming.authorization.AuthorizationMessage; +import net.jacobpeterson.alpaca.model.endpoint.streaming.enums.StreamingMessageType; +import net.jacobpeterson.alpaca.model.endpoint.streaming.listening.ListeningMessage; +import net.jacobpeterson.alpaca.model.endpoint.streaming.trade.TradeUpdateMessage; +import net.jacobpeterson.alpaca.refactor.websocket.AlpacaWebsocket; +import okhttp3.HttpUrl; +import okhttp3.OkHttpClient; +import okhttp3.WebSocket; +import okio.ByteString; +import org.jetbrains.annotations.NotNull; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.ArrayList; +import java.util.List; + +import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.base.Preconditions.checkNotNull; +import static com.google.common.base.Preconditions.checkState; +import static net.jacobpeterson.alpaca.refactor.util.gson.GsonUtil.GSON; + +public class StreamingWebsocket extends AlpacaWebsocket implements StreamingWebsocketInterface { + + private static final Logger LOGGER = LoggerFactory.getLogger(StreamingWebsocket.class); + private static final String STREAM_ELEMENT_KEY = "stream"; + + /** + * Creates a {@link HttpUrl} for {@link StreamingWebsocket} for the given alpacaSubdomain. + * + * @param alpacaSubdomain the Alpaca subdomain + * + * @return a {@link HttpUrl} + */ + private static HttpUrl createWebsocketURL(String alpacaSubdomain) { + return new HttpUrl.Builder() + .scheme("wss") + .host(alpacaSubdomain + ".alpaca.markets") + .addPathSegment("stream") + .build(); + } + + private final List listeners; + private final Object listenersLock; + private final List listenersToAdd; + private final List listenersToRemove; + + /** + * Instantiates a new {@link StreamingWebsocket}. + * + * @param okHttpClient the {@link OkHttpClient} + * @param alpacaSubdomain the Alpaca subdomain + * @param keyID the key ID + * @param secretKey the secret key + * @param oAuthToken the OAuth token + */ + public StreamingWebsocket(OkHttpClient okHttpClient, String alpacaSubdomain, + String keyID, String secretKey, String oAuthToken) { + super(okHttpClient, createWebsocketURL(alpacaSubdomain), "Streaming", keyID, secretKey, oAuthToken); + + listeners = new ArrayList<>(); + listenersLock = new Object(); + listenersToAdd = new ArrayList<>(); + listenersToRemove = new ArrayList<>(); + } + + @Override + protected void onConnection() { + sendAuthenticationMessage(); + } + + @Override + protected void onReconnection() { + sendAuthenticationMessage(); + } + + @Override + protected void sendAuthenticationMessage() { + /* Authentication message format: + * { + * "action": "authenticate", + * "data": { + * "key_id": "{YOUR_API_KEY_ID}", + * "secret_key": "{YOUR_API_SECRET_KEY}" + * } + * } + */ + + JsonObject authObject = new JsonObject(); + authObject.addProperty("action", "authenticate"); + + JsonObject authData = new JsonObject(); + + if (useOAuth) { + authData.addProperty("oauth_token", oAuthToken); + } else { + authData.addProperty("key_id", keyID); + authData.addProperty("secret_key", secretKey); + } + + authObject.add("data", authData); + + LOGGER.info("{} websocket sending authentication message...", websocketName); + websocket.send(authObject.toString()); + } + + // This websocket uses binary frames and not text frames. + @Override + public void onMessage(@NotNull WebSocket webSocket, @NotNull ByteString byteString) { + String message = byteString.utf8(); + + JsonElement messageElement = JsonParser.parseString(message); + checkState(messageElement instanceof JsonObject, "Message must be a JsonObject! Received: %s", messageElement); + + JsonObject messageObject = messageElement.getAsJsonObject(); + JsonElement streamElement = messageObject.get(STREAM_ELEMENT_KEY); + checkState(streamElement instanceof JsonPrimitive, + "Message must contain %s element! Received: %s", STREAM_ELEMENT_KEY, messageElement); + + StreamingMessageType streamingMessageType = GSON.fromJson(streamElement, StreamingMessageType.class); + switch (streamingMessageType) { + case AUTHORIZATION: + AuthorizationMessage authorizationMessage = GSON.fromJson(messageObject, AuthorizationMessage.class); + callListeners(streamingMessageType, authorizationMessage); + + authenticated = isAuthorizationMessageSuccess(authorizationMessage); + if (!authenticated) { + LOGGER.error("{} websocket not authenticated! Received: {}. Disconnecting...", + websocketName, authorizationMessage); + disconnect(); + } else { + LOGGER.info("{} websocket authenticated.", websocketName); + } + break; + case LISTENING: + ListeningMessage listeningMessage = GSON.fromJson(messageObject, ListeningMessage.class); + callListeners(streamingMessageType, listeningMessage); + LOGGER.debug("{}", listeningMessage); + break; + case TRADE_UPDATES: + TradeUpdateMessage tradeUpdateMessage = GSON.fromJson(messageObject, TradeUpdateMessage.class); + callListeners(streamingMessageType, tradeUpdateMessage); + LOGGER.debug("{}", tradeUpdateMessage); + break; + default: + throw new UnsupportedOperationException(); + } + } + + /** + * Returns true if {@link AuthorizationMessage} states success. + * + * @param authorizationMessage the {@link AuthorizationMessage} + * + * @return a boolean + */ + private boolean isAuthorizationMessageSuccess(AuthorizationMessage authorizationMessage) { + AuthorizationData authorizationData = authorizationMessage.getData(); + return authorizationData.getStatus().equalsIgnoreCase("authorized") && + authorizationData.getAction().equalsIgnoreCase("authenticate"); + } + + /** + * Calls the {@link StreamingListener}s in {@link #listeners}. + * + * @param streamingMessageType the {@link StreamingMessageType} + * @param streamingMessage the {@link StreamingMessage} + */ + private void callListeners(StreamingMessageType streamingMessageType, StreamingMessage streamingMessage) { + // Remove all 'StreamingListener's that were request to be removed then add all 'StreamingListener's + // that were request to be added on the last call to the listeners. + synchronized (listenersLock) { + listeners.removeAll(listenersToRemove); + listenersToRemove.clear(); + + listeners.addAll(listenersToAdd); + listenersToAdd.clear(); + } + + // Note that this doesn't need to acquire 'listenersLock' since 'listeners' is isolated + // to this instance and is not modified outside a lock. + for (StreamingListener streamingListener : listeners) { + streamingListener.onMessage(streamingMessageType, streamingMessage); + } + } + + @Override + public void subscriptions(StreamingMessageType... streamingMessageTypes) { + checkNotNull(streamingMessageTypes); + checkArgument(streamingMessageTypes.length > 0, "'streamingMessageTypes' must be have at least one element."); + + // Stream request format: + // { + // "action": "listen", + // "data": { + // "streams": ["account_updates", "trade_updates"] + // } + // } + + JsonObject requestObject = new JsonObject(); + requestObject.addProperty("action", "listen"); + + JsonArray streamsArray = new JsonArray(); + for (StreamingMessageType streamingMessageType : streamingMessageTypes) { + streamsArray.add(streamingMessageType.toString()); + } + + JsonObject dataObject = new JsonObject(); + dataObject.add("streams", streamsArray); + + requestObject.add("data", dataObject); + + websocket.send(requestObject.toString()); + LOGGER.info("Requested subscriptions: {}.", streamsArray); + } + + @Override + public void addListener(StreamingListener streamingListener) { + synchronized (listenersLock) { + listenersToAdd.add(streamingListener); + } + } + + @Override + public void removeListener(StreamingListener streamingListener) { + synchronized (listenersLock) { + listenersToRemove.add(streamingListener); + } + } +} diff --git a/src/main/java/net/jacobpeterson/alpaca/refactor/websocket/streaming/StreamingWebsocketInterface.java b/src/main/java/net/jacobpeterson/alpaca/refactor/websocket/streaming/StreamingWebsocketInterface.java new file mode 100644 index 00000000..7ed7dd95 --- /dev/null +++ b/src/main/java/net/jacobpeterson/alpaca/refactor/websocket/streaming/StreamingWebsocketInterface.java @@ -0,0 +1,31 @@ +package net.jacobpeterson.alpaca.refactor.websocket.streaming; + +import net.jacobpeterson.alpaca.model.endpoint.streaming.enums.StreamingMessageType; +import net.jacobpeterson.alpaca.refactor.websocket.AlpacaWebsocketInterface; + +/** + * {@link StreamingWebsocketInterface} is an {@link AlpacaWebsocketInterface} for a {@link StreamingWebsocket}. + */ +public interface StreamingWebsocketInterface extends AlpacaWebsocketInterface { + + /** + * Sets subscribed {@link StreamingMessageType}s to streamingMessageTypes. + * + * @param streamingMessageTypes the {@link StreamingMessageType}s + */ + void subscriptions(StreamingMessageType... streamingMessageTypes); + + /** + * Adds a {@link StreamingListener}. + * + * @param streamingListener the {@link StreamingListener} + */ + void addListener(StreamingListener streamingListener); + + /** + * Remove a {@link StreamingListener}. + * + * @param streamingListener the {@link StreamingListener} + */ + void removeListener(StreamingListener streamingListener); +} diff --git a/src/main/java/net/jacobpeterson/alpaca/websocket/broker/client/AlpacaWebsocketClient.java b/src/main/java/net/jacobpeterson/alpaca/websocket/broker/client/AlpacaWebsocketClient.java deleted file mode 100644 index e152fa20..00000000 --- a/src/main/java/net/jacobpeterson/alpaca/websocket/broker/client/AlpacaWebsocketClient.java +++ /dev/null @@ -1,320 +0,0 @@ -package net.jacobpeterson.alpaca.websocket.broker.client; - -import com.google.common.base.Preconditions; -import com.google.gson.JsonArray; -import com.google.gson.JsonElement; -import com.google.gson.JsonObject; -import com.google.gson.JsonParser; -import com.google.gson.JsonPrimitive; -import com.google.gson.JsonSyntaxException; -import net.jacobpeterson.alpaca.refactor.AlpacaAPI; -import net.jacobpeterson.alpaca.abstracts.websocket.client.WebsocketClient; -import net.jacobpeterson.alpaca.abstracts.websocket.client.WebsocketStateListener; -import net.jacobpeterson.alpaca.abstracts.websocket.exception.WebsocketException; -import net.jacobpeterson.alpaca.domain.streaming.AlpacaStreamMessage; -import net.jacobpeterson.alpaca.domain.streaming.account.AccountUpdateMessage; -import net.jacobpeterson.alpaca.domain.streaming.authorization.AuthorizationMessage; -import net.jacobpeterson.alpaca.domain.streaming.listening.ListeningMessage; -import net.jacobpeterson.alpaca.domain.streaming.trade.TradeUpdateMessage; -import net.jacobpeterson.alpaca.refactor.properties.enums.EndpointAPIType; -import net.jacobpeterson.alpaca.refactor.util.gson.GsonUtil; -import net.jacobpeterson.alpaca.websocket.broker.listener.AlpacaStreamListener; -import net.jacobpeterson.alpaca.websocket.broker.message.AlpacaStreamMessageType; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import javax.websocket.DeploymentException; -import java.io.IOException; -import java.net.URI; -import java.net.URISyntaxException; -import java.util.ArrayList; -import java.util.HashSet; -import java.util.List; -import java.util.Set; -import java.util.stream.Collectors; - -/** - * {@link AlpacaWebsocketClient} represents a client for the {@link AlpacaAPI} Websocket stream. - */ -public class AlpacaWebsocketClient implements WebsocketClient { - - private static final Logger LOGGER = LoggerFactory.getLogger(AlpacaWebsocketClient.class); - private static final String STREAM_KEY = "stream"; - - private final String keyID; - private final String secret; - private final String oAuthToken; - private final String streamAPIURL; - private final List listeners; - - private AlpacaWebsocketClientEndpoint alpacaWebsocketClientEndpoint; - private WebsocketStateListener websocketStateListener; - private boolean authenticated; - - /** - * Instantiates a new {@link AlpacaWebsocketClient}. - * - * @param keyID the key ID - * @param secret the secret - * @param endpointAPIType the {@link EndpointAPIType} - */ - public AlpacaWebsocketClient(String keyID, String secret, EndpointAPIType endpointAPIType) { - this(keyID, secret, null, endpointAPIType); - } - - /** - * Instantiates a new {@link AlpacaWebsocketClient}. - * - * @param oAuthToken the OAuth token - * @param endpointAPIType the {@link EndpointAPIType} - */ - public AlpacaWebsocketClient(String oAuthToken, EndpointAPIType endpointAPIType) { - this(null, null, oAuthToken, endpointAPIType); - } - - /** - * Instantiates a new {@link AlpacaWebsocketClient}. - * - * @param keyID the key ID - * @param secret the secret - * @param oAuthToken the OAuth token - */ - private AlpacaWebsocketClient(String keyID, String secret, String oAuthToken, EndpointAPIType endpointAPIType) { - this.keyID = keyID; - this.secret = secret; - this.oAuthToken = oAuthToken; - this.streamAPIURL = endpointAPIType.getURL().replace("https", "wss") + "/stream"; - - listeners = new ArrayList<>(); - } - - @Override - public void addListener(AlpacaStreamListener streamListener) throws WebsocketException { - if (listeners.isEmpty()) { - try { - connect(); - } catch (IOException | URISyntaxException | DeploymentException exception) { - throw new WebsocketException(exception); - } - } - - listeners.add(streamListener); - - submitStreamRequestUpdate(); - } - - @Override - public void removeListener(AlpacaStreamListener streamListener) throws WebsocketException { - listeners.remove(streamListener); - - if (listeners.isEmpty() && isConnected()) { - try { - disconnect(); - } catch (Exception exception) { - throw new WebsocketException(exception); - } - } else { - submitStreamRequestUpdate(); - } - } - - @Override - public void connect() throws URISyntaxException, IOException, DeploymentException { - LOGGER.info("Connecting..."); - - alpacaWebsocketClientEndpoint = new AlpacaWebsocketClientEndpoint(this, new URI(streamAPIURL)); - alpacaWebsocketClientEndpoint.setWebsocketStateListener(websocketStateListener); - alpacaWebsocketClientEndpoint.setAutomaticallyReconnect(true); - alpacaWebsocketClientEndpoint.connect(); - - LOGGER.info("Connected."); - } - - @Override - public void disconnect() throws Exception { - LOGGER.info("Disconnecting..."); - - if (alpacaWebsocketClientEndpoint != null) { - alpacaWebsocketClientEndpoint.disconnect(); - } - - LOGGER.info("Disconnected."); - } - - @Override - public void sendAuthenticationMessage() { - /* Format of message is: - * { - * "action": "authenticate", - * "data": { - * "key_id": "{YOUR_API_KEY_ID}", - * "secret_key": "{YOUR_API_SECRET_KEY}" - * } - * } - */ - - JsonObject authRequest = new JsonObject(); - authRequest.addProperty("action", "authenticate"); - - JsonObject payload = new JsonObject(); - - if (oAuthToken != null) { - payload.addProperty("oauth_token", oAuthToken); - } else { - payload.addProperty("key_id", keyID); - payload.addProperty("secret_key", secret); - } - - authRequest.add("data", payload); - - alpacaWebsocketClientEndpoint.sendMessage(authRequest.toString()); - } - - @Override - public void handleResubscribing() { - submitStreamRequestUpdate(); - } - - @Override - public void handleWebsocketMessage(String message) { - JsonElement messageJsonElement = JsonParser.parseString(message); - - Preconditions.checkState(messageJsonElement instanceof JsonObject); - - JsonObject messageJsonObject = messageJsonElement.getAsJsonObject(); - JsonElement streamJsonElement = messageJsonObject.get(STREAM_KEY); - - if (streamJsonElement instanceof JsonPrimitive) { - try { - AlpacaStreamMessageType alpacaStreamMessageType = GsonUtil.GSON.fromJson(streamJsonElement, - AlpacaStreamMessageType.class); - - switch (alpacaStreamMessageType) { - case AUTHORIZATION: - AuthorizationMessage authorizationMessage = GsonUtil.GSON.fromJson(messageJsonObject, - AuthorizationMessage.class); - sendStreamMessageToListeners(alpacaStreamMessageType, authorizationMessage); - - authenticated = isAuthorizationMessageSuccess(authorizationMessage); - - LOGGER.debug("{}", authorizationMessage); - break; - case LISTENING: - ListeningMessage listeningMessage = GsonUtil.GSON.fromJson(messageJsonObject, - ListeningMessage.class); - sendStreamMessageToListeners(alpacaStreamMessageType, listeningMessage); - - LOGGER.debug("{}", listeningMessage); - break; - case TRADE_UPDATES: - sendStreamMessageToListeners(alpacaStreamMessageType, GsonUtil.GSON.fromJson(messageJsonObject, - TradeUpdateMessage.class)); - break; - case ACCOUNT_UPDATES: - sendStreamMessageToListeners(alpacaStreamMessageType, GsonUtil.GSON.fromJson(messageJsonObject, - AccountUpdateMessage.class)); - break; - default: - LOGGER.error("Unhandled stream type: {}", alpacaStreamMessageType); - } - } catch (JsonSyntaxException exception) { - LOGGER.error("Could not parse message: {}\n{}", messageJsonObject, exception); - } - } else { - LOGGER.error("Unknown stream message: {}", messageJsonObject); - } - } - - @Override - public void sendStreamMessageToListeners(AlpacaStreamMessageType alpacaStreamMessageType, - AlpacaStreamMessage alpacaStreamMessage) { - for (AlpacaStreamListener alpacaStreamListener : new ArrayList<>(listeners)) { - if (alpacaStreamListener.getStreamMessageTypes() == null || - alpacaStreamListener.getStreamMessageTypes().isEmpty() || - alpacaStreamListener.getStreamMessageTypes().contains(alpacaStreamMessageType)) { - alpacaStreamListener.onStreamUpdate(alpacaStreamMessageType, alpacaStreamMessage); - } - } - } - - @Override - public boolean isConnected() { - return alpacaWebsocketClientEndpoint != null && - alpacaWebsocketClientEndpoint.getUserSession() != null && - alpacaWebsocketClientEndpoint.getUserSession().isOpen(); - } - - @Override - public boolean isAuthenticated() { - return authenticated; - } - - @Override - public void setWebsocketStateListener(WebsocketStateListener websocketStateListener) { - this.websocketStateListener = websocketStateListener; - } - - /** - * Returns true if {@link AuthorizationMessage} states success. - * - * @param authorizationMessage the {@link AuthorizationMessage} - * - * @return true if {@link AuthorizationMessage} states success - */ - private boolean isAuthorizationMessageSuccess(AuthorizationMessage authorizationMessage) { - return authorizationMessage.getData().getStatus().equalsIgnoreCase("authorized") && - authorizationMessage.getData().getAction().equalsIgnoreCase("authenticate"); - } - - /** - * Submits a stream request. - */ - private void submitStreamRequestUpdate() { - // Stream request example: - // { - // "action": "listen", - // "data": { - // "streams": ["account_updates", "trade_updates"] - // } - // } - - JsonObject streamRequestJsonObject = new JsonObject(); - streamRequestJsonObject.addProperty("action", "listen"); - - JsonArray streamsJsonArray = new JsonArray(); - getRegisteredMessageTypes().forEach(alpacaStreamMessageType -> - streamsJsonArray.add(alpacaStreamMessageType.getAPIName())); - - JsonObject dataJsonObject = new JsonObject(); - dataJsonObject.add("streams", streamsJsonArray); - - streamRequestJsonObject.add("data", dataJsonObject); - - alpacaWebsocketClientEndpoint.sendMessage(streamRequestJsonObject.toString()); - - LOGGER.info("Requested subscriptions to update to {}", streamsJsonArray); - } - - /** - * Gets the registered {@link AlpacaStreamMessageType}. - * - * @return the registered {@link AlpacaStreamMessageType} - */ - public Set getRegisteredMessageTypes() { - Set registeredStreamMessageTypes = new HashSet<>(); - - for (AlpacaStreamListener alpacaStreamListener : new ArrayList<>(listeners)) { - Set alpacaStreamMessageTypes = alpacaStreamListener.getStreamMessageTypes(); - - // if its empty, assume they want everything - Set streamMessageTypesToAdd = alpacaStreamMessageTypes == null ? new HashSet<>() : - alpacaStreamMessageTypes.stream().filter(AlpacaStreamMessageType::isAPISubscribable) - .collect(Collectors.toSet()); - - registeredStreamMessageTypes.addAll(streamMessageTypesToAdd); - } - - return registeredStreamMessageTypes; - } -} diff --git a/src/main/java/net/jacobpeterson/alpaca/websocket/broker/client/AlpacaWebsocketClientEndpoint.java b/src/main/java/net/jacobpeterson/alpaca/websocket/broker/client/AlpacaWebsocketClientEndpoint.java deleted file mode 100644 index c6b2466b..00000000 --- a/src/main/java/net/jacobpeterson/alpaca/websocket/broker/client/AlpacaWebsocketClientEndpoint.java +++ /dev/null @@ -1,51 +0,0 @@ -package net.jacobpeterson.alpaca.websocket.broker.client; - -import net.jacobpeterson.alpaca.refactor.AlpacaAPI; -import net.jacobpeterson.alpaca.abstracts.websocket.client.AbstractWebsocketClientEndpoint; - -import javax.websocket.ClientEndpoint; -import javax.websocket.CloseReason; -import javax.websocket.OnClose; -import javax.websocket.OnError; -import javax.websocket.OnMessage; -import javax.websocket.OnOpen; -import javax.websocket.Session; -import java.net.URI; -import java.nio.charset.StandardCharsets; - -/** - * {@link AlpacaWebsocketClientEndpoint} is used for handling a Websocket directly for {@link AlpacaAPI}. - */ -@ClientEndpoint(subprotocols = "BINARY") -public class AlpacaWebsocketClientEndpoint extends AbstractWebsocketClientEndpoint { - - /** - * Instantiates a new {@link AlpacaWebsocketClientEndpoint}. - * - * @param websocketClient the {@link AlpacaWebsocketClient} - * @param endpointURI the endpoint {@link URI} - */ - public AlpacaWebsocketClientEndpoint(AlpacaWebsocketClient websocketClient, URI endpointURI) { - super(websocketClient, endpointURI, "AlpacaWebsocketThread"); - } - - @OnOpen - public void onOpenAnnotated(Session userSession) { - super.onOpen(userSession); - } - - @OnClose - public void onCloseAnnotated(Session userSession, CloseReason reason) { - super.onClose(userSession, reason); - } - - @OnMessage - public void onMessageAnnotated(byte[] message) { - super.onMessage(new String(message, StandardCharsets.UTF_8)); - } - - @OnError - public void onErrorAnnotated(Throwable throwable) { - super.onError(throwable); - } -} diff --git a/src/main/java/net/jacobpeterson/alpaca/websocket/broker/listener/AlpacaStreamListener.java b/src/main/java/net/jacobpeterson/alpaca/websocket/broker/listener/AlpacaStreamListener.java deleted file mode 100644 index 50b8863a..00000000 --- a/src/main/java/net/jacobpeterson/alpaca/websocket/broker/listener/AlpacaStreamListener.java +++ /dev/null @@ -1,22 +0,0 @@ -package net.jacobpeterson.alpaca.websocket.broker.listener; - -import net.jacobpeterson.alpaca.abstracts.websocket.listener.StreamListener; -import net.jacobpeterson.alpaca.domain.streaming.AlpacaStreamMessage; -import net.jacobpeterson.alpaca.websocket.broker.message.AlpacaStreamMessageType; - -import java.util.Set; - -/** - * An asynchronous update interface for receiving websocket messages for - * {@link net.jacobpeterson.alpaca.websocket.broker.client.AlpacaWebsocketClient}. - */ -public interface AlpacaStreamListener extends StreamListener { - - /** - * Gets the {@link AlpacaStreamMessageType}s for this {@link AlpacaStreamListener}. Null or empty to listen to all - * other stream messages. - * - * @return the {@link AlpacaStreamMessageType}s - */ - Set getStreamMessageTypes(); -} diff --git a/src/main/java/net/jacobpeterson/alpaca/websocket/broker/listener/AlpacaStreamListenerAdapter.java b/src/main/java/net/jacobpeterson/alpaca/websocket/broker/listener/AlpacaStreamListenerAdapter.java deleted file mode 100644 index 5785b074..00000000 --- a/src/main/java/net/jacobpeterson/alpaca/websocket/broker/listener/AlpacaStreamListenerAdapter.java +++ /dev/null @@ -1,37 +0,0 @@ -package net.jacobpeterson.alpaca.websocket.broker.listener; - -import net.jacobpeterson.alpaca.domain.streaming.AlpacaStreamMessage; -import net.jacobpeterson.alpaca.websocket.broker.message.AlpacaStreamMessageType; - -import java.util.Arrays; -import java.util.HashSet; -import java.util.Set; - -/** - * {@link AlpacaStreamListenerAdapter} is an adapter for {@link AlpacaStreamListener}. - */ -public class AlpacaStreamListenerAdapter implements AlpacaStreamListener { - - private final HashSet streamUpdateTypes; - - /** - * Instantiates a new {@link AlpacaStreamListenerAdapter}. - * - * @param streamUpdateTypes the {@link AlpacaStreamMessageType}s - */ - public AlpacaStreamListenerAdapter(AlpacaStreamMessageType... streamUpdateTypes) { - this.streamUpdateTypes = new HashSet<>(); - - if (streamUpdateTypes != null && streamUpdateTypes.length > 0) { - this.streamUpdateTypes.addAll(Arrays.asList(streamUpdateTypes)); - } - } - - @Override - public Set getStreamMessageTypes() { - return streamUpdateTypes; - } - - @Override - public void onStreamUpdate(AlpacaStreamMessageType streamMessageType, AlpacaStreamMessage streamMessage) { } -} diff --git a/src/test/java/net/jacobpeterson/alpaca/test/mock/AlpacaAPITest.java b/src/test/java/net/jacobpeterson/alpaca/test/mock/AlpacaAPITest.java index 7cb569ff..50d64025 100644 --- a/src/test/java/net/jacobpeterson/alpaca/test/mock/AlpacaAPITest.java +++ b/src/test/java/net/jacobpeterson/alpaca/test/mock/AlpacaAPITest.java @@ -3,6 +3,7 @@ import net.jacobpeterson.alpaca.model.properties.DataAPIType; import net.jacobpeterson.alpaca.model.properties.EndpointAPIType; import net.jacobpeterson.alpaca.refactor.AlpacaAPI; +import okhttp3.OkHttpClient; import org.junit.jupiter.api.Test; /** @@ -56,7 +57,7 @@ public void testAlpacaAPIConstructor_oAuthToken() { } /** - * Tests {@link AlpacaAPI#AlpacaAPI(String, String, String, EndpointAPIType, DataAPIType)}. + * Tests {@link AlpacaAPI#AlpacaAPI(OkHttpClient, String, String, String, EndpointAPIType, DataAPIType)}. */ @Test public void testAlpacaAPIConstructor_keyID_secret_oAuthToken_endpointAPIType_dataAPIType() { @@ -64,7 +65,7 @@ public void testAlpacaAPIConstructor_keyID_secret_oAuthToken_endpointAPIType_dat String secret = "NOPQURSTUVWXYZ"; String oAuthToken = "ABCDEFGHIJKLMNOPQURSTUVWXYZ"; - new AlpacaAPI(keyID, secret, oAuthToken, EndpointAPIType.PAPER, DataAPIType.IEX); - new AlpacaAPI(keyID, secret, oAuthToken, EndpointAPIType.LIVE, DataAPIType.SIP); + new AlpacaAPI(null, keyID, secret, oAuthToken, EndpointAPIType.PAPER, DataAPIType.IEX); + new AlpacaAPI(null, keyID, secret, oAuthToken, EndpointAPIType.LIVE, DataAPIType.SIP); } } From 39628d7d1c4126f431037651a1d19844ee301cca Mon Sep 17 00:00:00 2001 From: Jacob Peterson Date: Sat, 10 Jul 2021 19:28:32 -0600 Subject: [PATCH 124/271] Refactor, fix auto-closing of Reponse and body().charStream() --- build.gradle | 2 +- .../account/enums/account_status.json | 6 +- .../control/subscriptions_message.json | 6 +- .../realtime/market_data_message.json | 3 - .../endpoint/streaming/streaming_message.json | 3 - .../alpaca/{refactor => }/AlpacaAPI.java | 79 +--- .../AbstractWebsocketClientEndpoint.java | 229 ------------ .../websocket/client/WebsocketClient.java | 94 ----- .../client/WebsocketStateListener.java | 24 -- .../exception/WebsocketException.java | 37 -- .../properties/AlpacaProperties.java | 17 +- .../marketdata/MarketDataWebsocket.java | 6 - .../streaming/StreamingListener.java | 18 - .../{refactor => }/rest/AlpacaClient.java | 45 ++- .../rest/AlpacaClientException.java | 66 ++-- .../rest/endpoint/AbstractEndpoint.java | 4 +- .../endpoint/AccountActivitiesEndpoint.java | 12 +- .../AccountConfigurationEndpoint.java | 12 +- .../rest/endpoint/AccountEndpoint.java | 6 +- .../rest/endpoint/AssetsEndpoint.java | 6 +- .../rest/endpoint/CalendarEndpoint.java | 6 +- .../rest/endpoint/ClockEndpoint.java | 6 +- .../rest/endpoint/MarketDataEndpoint.java | 8 +- .../rest/endpoint/OrdersEndpoint.java | 12 +- .../endpoint/PortfolioHistoryEndpoint.java | 7 +- .../rest/endpoint/PositionsEndpoint.java | 6 +- .../rest/endpoint/WatchlistEndpoint.java | 10 +- .../util/doc/Documentation2JSONSchema.java | 8 +- .../util/format/FormatUtil.java | 2 +- .../{refactor => }/util/gson/GsonUtil.java | 2 +- .../util/gson/LocalDateAdapter.java | 10 +- .../util/gson/ZonedDateTimeAdapter.java | 10 +- .../util/okhttp/JSONBodyBuilder.java | 4 +- .../util/okhttp/WebsocketStateListener.java | 32 ++ .../util/properties/PropertyUtil.java | 2 +- .../websocket/AlpacaWebsocket.java | 34 +- .../websocket/AlpacaWebsocketInterface.java | 2 +- .../AlpacaWebsocketMessageListener.java | 19 + .../marketdata/MarketDataListener.java | 11 + .../marketdata/MarketDataWebsocket.java | 4 + .../MarketDataWebsocketInterface.java | 4 +- .../client/MarketDataWebsocketClient.java | 342 ------------------ .../MarketDataWebsocketClientEndpoint.java | 51 --- .../listener/MarketDataListener.java | 23 -- .../listener/MarketDataListenerAdapter.java | 65 ---- .../streaming/StreamingListener.java | 11 + .../streaming/StreamingWebsocket.java | 15 +- .../StreamingWebsocketInterface.java | 4 +- .../alpaca/test/live/AlpacaAPITest.java | 15 +- .../alpaca/test/mock/AlpacaAPITest.java | 2 +- src/test/resources/alpaca.default.properties | 2 +- 51 files changed, 287 insertions(+), 1117 deletions(-) rename src/main/java/net/jacobpeterson/alpaca/{refactor => }/AlpacaAPI.java (71%) delete mode 100644 src/main/java/net/jacobpeterson/alpaca/abstracts/websocket/client/AbstractWebsocketClientEndpoint.java delete mode 100644 src/main/java/net/jacobpeterson/alpaca/abstracts/websocket/client/WebsocketClient.java delete mode 100644 src/main/java/net/jacobpeterson/alpaca/abstracts/websocket/client/WebsocketStateListener.java delete mode 100644 src/main/java/net/jacobpeterson/alpaca/abstracts/websocket/exception/WebsocketException.java rename src/main/java/net/jacobpeterson/alpaca/{refactor => }/properties/AlpacaProperties.java (80%) delete mode 100644 src/main/java/net/jacobpeterson/alpaca/refactor/websocket/marketdata/MarketDataWebsocket.java delete mode 100644 src/main/java/net/jacobpeterson/alpaca/refactor/websocket/streaming/StreamingListener.java rename src/main/java/net/jacobpeterson/alpaca/{refactor => }/rest/AlpacaClient.java (80%) rename src/main/java/net/jacobpeterson/alpaca/{refactor => }/rest/AlpacaClientException.java (63%) rename src/main/java/net/jacobpeterson/alpaca/{refactor => }/rest/endpoint/AbstractEndpoint.java (84%) rename src/main/java/net/jacobpeterson/alpaca/{refactor => }/rest/endpoint/AccountActivitiesEndpoint.java (92%) rename src/main/java/net/jacobpeterson/alpaca/{refactor => }/rest/endpoint/AccountConfigurationEndpoint.java (83%) rename src/main/java/net/jacobpeterson/alpaca/{refactor => }/rest/endpoint/AccountEndpoint.java (85%) rename src/main/java/net/jacobpeterson/alpaca/{refactor => }/rest/endpoint/AssetsEndpoint.java (93%) rename src/main/java/net/jacobpeterson/alpaca/{refactor => }/rest/endpoint/CalendarEndpoint.java (91%) rename src/main/java/net/jacobpeterson/alpaca/{refactor => }/rest/endpoint/ClockEndpoint.java (85%) rename src/main/java/net/jacobpeterson/alpaca/{refactor => }/rest/endpoint/MarketDataEndpoint.java (97%) rename src/main/java/net/jacobpeterson/alpaca/{refactor => }/rest/endpoint/OrdersEndpoint.java (98%) rename src/main/java/net/jacobpeterson/alpaca/{refactor => }/rest/endpoint/PortfolioHistoryEndpoint.java (95%) rename src/main/java/net/jacobpeterson/alpaca/{refactor => }/rest/endpoint/PositionsEndpoint.java (96%) rename src/main/java/net/jacobpeterson/alpaca/{refactor => }/rest/endpoint/WatchlistEndpoint.java (95%) rename src/main/java/net/jacobpeterson/alpaca/{refactor => }/util/doc/Documentation2JSONSchema.java (95%) rename src/main/java/net/jacobpeterson/alpaca/{refactor => }/util/format/FormatUtil.java (96%) rename src/main/java/net/jacobpeterson/alpaca/{refactor => }/util/gson/GsonUtil.java (92%) rename src/main/java/net/jacobpeterson/alpaca/{refactor => }/util/gson/LocalDateAdapter.java (68%) rename src/main/java/net/jacobpeterson/alpaca/{refactor => }/util/gson/ZonedDateTimeAdapter.java (69%) rename src/main/java/net/jacobpeterson/alpaca/{refactor => }/util/okhttp/JSONBodyBuilder.java (94%) create mode 100644 src/main/java/net/jacobpeterson/alpaca/util/okhttp/WebsocketStateListener.java rename src/main/java/net/jacobpeterson/alpaca/{refactor => }/util/properties/PropertyUtil.java (98%) rename src/main/java/net/jacobpeterson/alpaca/{refactor => }/websocket/AlpacaWebsocket.java (86%) rename src/main/java/net/jacobpeterson/alpaca/{refactor => }/websocket/AlpacaWebsocketInterface.java (93%) create mode 100644 src/main/java/net/jacobpeterson/alpaca/websocket/AlpacaWebsocketMessageListener.java create mode 100644 src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/MarketDataListener.java create mode 100644 src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/MarketDataWebsocket.java rename src/main/java/net/jacobpeterson/alpaca/{refactor => }/websocket/marketdata/MarketDataWebsocketInterface.java (59%) delete mode 100644 src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/client/MarketDataWebsocketClient.java delete mode 100644 src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/client/MarketDataWebsocketClientEndpoint.java delete mode 100644 src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/listener/MarketDataListener.java delete mode 100644 src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/listener/MarketDataListenerAdapter.java create mode 100644 src/main/java/net/jacobpeterson/alpaca/websocket/streaming/StreamingListener.java rename src/main/java/net/jacobpeterson/alpaca/{refactor => }/websocket/streaming/StreamingWebsocket.java (92%) rename src/main/java/net/jacobpeterson/alpaca/{refactor => }/websocket/streaming/StreamingWebsocketInterface.java (86%) diff --git a/build.gradle b/build.gradle index 9608f1b0..b1b355e5 100644 --- a/build.gradle +++ b/build.gradle @@ -100,7 +100,7 @@ test { doFirst { def testPackageArgument = "testPackage" filter.includeTestsMatching(project.hasProperty(testPackageArgument) && - project.getProperty(testPackageArgument).equals("live") ? "live.*" : "mock.*") + project.getProperty(testPackageArgument).equals("live") ? "*.live.*" : "*.mock.*") } } diff --git a/schema_json/endpoint/account/enums/account_status.json b/schema_json/endpoint/account/enums/account_status.json index 7cb842bf..65963f0d 100644 --- a/schema_json/endpoint/account/enums/account_status.json +++ b/schema_json/endpoint/account/enums/account_status.json @@ -8,7 +8,8 @@ "ACCOUNT_UPDATED", "APPROVAL_PENDING", "ACTIVE", - "REJECTED" + "REJECTED", + "DISABLED" ], "javaEnums": [ { @@ -31,6 +32,9 @@ }, { "description": "The account application has been rejected." + }, + { + "description": "The account has been disabled." } ] } diff --git a/schema_json/endpoint/market_data/realtime/control/subscriptions_message.json b/schema_json/endpoint/market_data/realtime/control/subscriptions_message.json index 19756e7a..6602ed55 100644 --- a/schema_json/endpoint/market_data/realtime/control/subscriptions_message.json +++ b/schema_json/endpoint/market_data/realtime/control/subscriptions_message.json @@ -7,15 +7,15 @@ "properties": { "trades": { "existingJavaType": "java.util.ArrayList", - "title": "The {@link java.util.Arraylist} of subscribed trades." + "title": "The {@link java.util.ArrayList} of subscribed trades." }, "quotes": { "existingJavaType": "java.util.ArrayList", - "title": "The {@link java.util.Arraylist} of subscribed quotes." + "title": "The {@link java.util.ArrayList} of subscribed quotes." }, "bars": { "existingJavaType": "java.util.ArrayList", - "title": "The {@link java.util.Arraylist} of subscribed bars." + "title": "The {@link java.util.ArrayList} of subscribed bars." } } } diff --git a/schema_json/endpoint/market_data/realtime/market_data_message.json b/schema_json/endpoint/market_data/realtime/market_data_message.json index 66e00245..3e43d789 100644 --- a/schema_json/endpoint/market_data/realtime/market_data_message.json +++ b/schema_json/endpoint/market_data/realtime/market_data_message.json @@ -1,8 +1,5 @@ { "type": "object", - "javaInterfaces": [ - "net.jacobpeterson.alpaca.abstracts.websocket.message.StreamMessage" - ], "title": "See Real-time Data.", "properties": { "T": { diff --git a/schema_json/endpoint/streaming/streaming_message.json b/schema_json/endpoint/streaming/streaming_message.json index 4b32a67b..c7799c71 100644 --- a/schema_json/endpoint/streaming/streaming_message.json +++ b/schema_json/endpoint/streaming/streaming_message.json @@ -1,8 +1,5 @@ { "type": "object", - "javaInterfaces": [ - "net.jacobpeterson.alpaca.abstracts.websocket.message.StreamingMessage" - ], "title": "See Streaming.", "properties": { "stream": { diff --git a/src/main/java/net/jacobpeterson/alpaca/refactor/AlpacaAPI.java b/src/main/java/net/jacobpeterson/alpaca/AlpacaAPI.java similarity index 71% rename from src/main/java/net/jacobpeterson/alpaca/refactor/AlpacaAPI.java rename to src/main/java/net/jacobpeterson/alpaca/AlpacaAPI.java index 3f973ee4..a5521317 100644 --- a/src/main/java/net/jacobpeterson/alpaca/refactor/AlpacaAPI.java +++ b/src/main/java/net/jacobpeterson/alpaca/AlpacaAPI.java @@ -1,11 +1,11 @@ -package net.jacobpeterson.alpaca.refactor; +package net.jacobpeterson.alpaca; import devcsrj.okhttp3.logging.HttpLoggingInterceptor; import net.jacobpeterson.alpaca.model.properties.DataAPIType; import net.jacobpeterson.alpaca.model.properties.EndpointAPIType; -import net.jacobpeterson.alpaca.refactor.properties.AlpacaProperties; -import net.jacobpeterson.alpaca.refactor.rest.AlpacaClient; -import net.jacobpeterson.alpaca.refactor.rest.endpoint.*; +import net.jacobpeterson.alpaca.properties.AlpacaProperties; +import net.jacobpeterson.alpaca.rest.AlpacaClient; +import net.jacobpeterson.alpaca.rest.endpoint.*; import okhttp3.OkHttpClient; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -104,7 +104,8 @@ public AlpacaAPI(OkHttpClient okHttpClient, String keyID, String secretKey, Stri // Create default 'okHttpClient' if (okHttpClient == null) { - OkHttpClient.Builder clientBuilder = new OkHttpClient.Builder(); + OkHttpClient.Builder clientBuilder = new OkHttpClient.Builder() + .cache(null); // Ensure response caching is disabled if (LOGGER.isDebugEnabled()) { clientBuilder.addInterceptor(new HttpLoggingInterceptor(LOGGER)); @@ -226,74 +227,6 @@ public PortfolioHistoryEndpoint portfolioHistory() { return portfolioHistoryEndpoint; } - // TODO - // /** - // * Adds the {@link AlpacaStreamListener}. Note that when the first {@link AlpacaStreamListener} is added, the - // * Websocket connection is created. - // * - // * @param streamListener the stream listener - // * - // * @throws WebsocketException thrown for {@link WebsocketException}s - // */ - // public void addAlpacaStreamListener(AlpacaStreamListener streamListener) throws WebsocketException { - // alpacaWebSocketClient.addListener(streamListener); - // } - // - // /** - // * Removes the {@link AlpacaStreamListener}. Note that when the last {@link AlpacaStreamListener} is removed, the - // * Websocket connection is closed. - // * - // * @param streamListener the {@link AlpacaStreamListener} - // * - // * @throws WebsocketException thrown for {@link WebsocketException}s - // */ - // public void removeAlpacaStreamListener(AlpacaStreamListener streamListener) throws WebsocketException { - // alpacaWebSocketClient.removeListener(streamListener); - // } - // - // /** - // * Sets the {@link AlpacaWebsocketClient} {@link WebsocketStateListener} to listen to websocket state changes. - // * - // * @param websocketStateListener the {@link WebsocketStateListener} - // */ - // public void setAlpacaStreamWebsocketStateListener(WebsocketStateListener websocketStateListener) { - // alpacaWebSocketClient.setWebsocketStateListener(websocketStateListener); - // } - // - // /** - // * Adds the {@link MarketDataListener}. Note that when the first {@link AlpacaStreamListener} is added, the - // * Websocket connection is created. - // * - // * @param streamListener the {@link MarketDataListener} - // * - // * @throws WebsocketException thrown for {@link WebsocketException}s - // */ - // public void addMarketDataStreamListener(MarketDataListener streamListener) throws WebsocketException { - // marketDataWebSocketClient.addListener(streamListener); - // } - // - // /** - // * Removes the {@link MarketDataListener}. Note that when the last {@link MarketDataListener} is removed, the - // * Websocket connection is closed. - // * - // * @param streamListener the {@link MarketDataListener} - // * - // * @throws WebsocketException thrown for {@link WebsocketException}s - // */ - // public void removeMarketDataStreamListener(MarketDataListener streamListener) throws WebsocketException { - // marketDataWebSocketClient.removeListener(streamListener); - // } - // - // /** - // * Sets the {@link MarketDataWebsocketClient} {@link WebsocketStateListener} to listen to websocket state - // changes. - // * - // * @param websocketStateListener the {@link WebsocketStateListener} - // */ - // public void setMarketDataStreamWebsocketStateListener(WebsocketStateListener websocketStateListener) { - // marketDataWebSocketClient.setWebsocketStateListener(websocketStateListener); - // } - /** * Gets {@link #okHttpClient}. * diff --git a/src/main/java/net/jacobpeterson/alpaca/abstracts/websocket/client/AbstractWebsocketClientEndpoint.java b/src/main/java/net/jacobpeterson/alpaca/abstracts/websocket/client/AbstractWebsocketClientEndpoint.java deleted file mode 100644 index 72a8ed37..00000000 --- a/src/main/java/net/jacobpeterson/alpaca/abstracts/websocket/client/AbstractWebsocketClientEndpoint.java +++ /dev/null @@ -1,229 +0,0 @@ -package net.jacobpeterson.alpaca.abstracts.websocket.client; - -import org.eclipse.jetty.util.component.LifeCycle; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import javax.websocket.CloseReason; -import javax.websocket.CloseReason.CloseCodes; -import javax.websocket.ContainerProvider; -import javax.websocket.DeploymentException; -import javax.websocket.Session; -import javax.websocket.WebSocketContainer; -import java.io.IOException; -import java.net.URI; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; - -/** - * {@link AbstractWebsocketClientEndpoint} is used for handling a Websocket directly. - *
- * NOTES: You MUST annotate a subclass's {@link #onOpen(Session)}, {@link #onClose(Session, CloseReason)}, {@link - * #onMessage(String)}, and {@link #onError(Throwable)} with {@link javax.websocket.OnOpen}, {@link - * javax.websocket.OnClose}, {@link javax.websocket.OnMessage}, and {@link javax.websocket.OnError} respectively and - * with the appropriate websocket sub-protocols because Websocket annotations don't work with inheritance. - * - * @param the {@link WebsocketClient} type parameter - */ -public abstract class AbstractWebsocketClientEndpoint> { - - private static final Logger LOGGER = LoggerFactory.getLogger(AbstractWebsocketClientEndpoint.class); - - private final T websocketClient; - private final URI endpointURI; - private final String messageThreadName; - - /** - * {@link ExecutorService} passes message handlers to a different thread, which will prevent overflow of server - * buffers which may cause a disconnect from not consuming data fast enough on the client end. - */ - private ExecutorService executorService; - private WebsocketStateListener websocketStateListener; - private boolean automaticallyReconnect; - private Session userSession; - - /** - * Instantiates a new {@link AbstractWebsocketClientEndpoint}. - * - * @param websocketClient the {@link WebsocketClient} type - * @param endpointURI the endpoint {@link URI} - * @param messageThreadName the message thread name - */ - public AbstractWebsocketClientEndpoint(T websocketClient, URI endpointURI, String messageThreadName) { - this.websocketClient = websocketClient; - this.endpointURI = endpointURI; - this.messageThreadName = messageThreadName; - - automaticallyReconnect = true; - } - - /** - * Connects this Websocket. - * - * @throws DeploymentException throw for {@link DeploymentException}s - * @throws IOException throw for {@link IOException}s - */ - public void connect() throws DeploymentException, IOException { - executorService = Executors.newSingleThreadExecutor(runnable -> new Thread(runnable, messageThreadName)); - - WebSocketContainer container = ContainerProvider.getWebSocketContainer(); - - LOGGER.info("Connecting to {}", endpointURI); - container.connectToServer(this, endpointURI); - } - - /** - * Disconnects this Websocket. - * - * @throws Exception throw for {@link Exception}s - */ - public void disconnect() throws Exception { - automaticallyReconnect = false; - - if (userSession != null) { - userSession.close(); - } - - cleanUp(); - } - - /** - * Handles the Websocket open. - * - * @param userSession the user {@link Session} - */ - protected void onOpen(Session userSession) { - this.userSession = userSession; - - LOGGER.debug("onOpen {}", userSession); - LOGGER.info("Websocket opened... Authenticating..."); - if (websocketStateListener != null) { - websocketStateListener.onConnected(); - } - websocketClient.sendAuthenticationMessage(); - } - - /** - * Handles the Websocket close. - * - * @param userSession the user {@link Session} - * @param reason the {@link CloseReason} - */ - protected void onClose(Session userSession, CloseReason reason) { - LOGGER.debug("onClose {}", userSession); - - if (websocketStateListener != null) { - websocketStateListener.onDisconnected(); - } - - if (!reason.getCloseCode().equals(CloseCodes.NORMAL_CLOSURE) && automaticallyReconnect) { - - LOGGER.info("Reconnecting due to closure: {}", - CloseCodes.getCloseCode(reason.getCloseCode().getCode())); - - try { - connect(); - websocketClient.handleResubscribing(); - } catch (Exception exception) { - LOGGER.error("Could not reconnect!", exception); - } - } else { - try { - cleanUp(); - } catch (Exception exception) { - LOGGER.error("Could not clean up websocket client endpoint!", exception); - } - - LOGGER.info("Websocket closed"); - } - } - - /** - * Cleans up websocket-related threads. - * - * @throws Exception thrown for {@link Exception}s - */ - private void cleanUp() throws Exception { - if (userSession != null) { - WebSocketContainer webSocketContainer = userSession.getContainer(); - if (webSocketContainer instanceof LifeCycle) { - ((LifeCycle) webSocketContainer).stop(); // Closes all websocket-related threads - } - } - - if (executorService != null) { - executorService.shutdown(); - } - } - - /** - * Called when the Websocket message is received. - * - * @param message the message - */ - protected void onMessage(String message) { - executorService.execute(() -> websocketClient.handleWebsocketMessage(message)); - } - - /** - * Handles the Websocket error. - * - * @param throwable the {@link Throwable} - */ - protected void onError(Throwable throwable) { - LOGGER.error("Websocket Error!", throwable); - if (websocketStateListener != null) { - websocketStateListener.onError(throwable); - } - } - - /** - * Sends a message through the Websocket. - * - * @param message the message - */ - public void sendMessage(String message) { - LOGGER.debug("sendMessage {}", message); - - userSession.getAsyncRemote().sendText(message); - } - - /** - * Sets {@link #websocketStateListener}. - * - * @param websocketStateListener the {@link WebsocketStateListener} - */ - public void setWebsocketStateListener(WebsocketStateListener websocketStateListener) { - this.websocketStateListener = websocketStateListener; - } - - /** - * Returns true if the Websocket is automatically reconnected except when {@link CloseReason} is {@link - * CloseCodes#NORMAL_CLOSURE}. - * - * @return true if the Websocket is automatically reconnected except when {@link CloseReason} is {@link - * CloseCodes#NORMAL_CLOSURE}. - */ - public boolean doesAutomaticallyReconnect() { - return automaticallyReconnect; - } - - /** - * Sets the Websocket to automatically reconnected except when {@link CloseReason} is {@link - * CloseCodes#NORMAL_CLOSURE}. - * - * @param automaticallyReconnect true to automatically reconnect - */ - public void setAutomaticallyReconnect(boolean automaticallyReconnect) { - this.automaticallyReconnect = automaticallyReconnect; - } - - /** - * Gets user {@link Session}. - * - * @return the user {@link Session} - */ - public Session getUserSession() { - return userSession; - } -} diff --git a/src/main/java/net/jacobpeterson/alpaca/abstracts/websocket/client/WebsocketClient.java b/src/main/java/net/jacobpeterson/alpaca/abstracts/websocket/client/WebsocketClient.java deleted file mode 100644 index 7aa4ff7a..00000000 --- a/src/main/java/net/jacobpeterson/alpaca/abstracts/websocket/client/WebsocketClient.java +++ /dev/null @@ -1,94 +0,0 @@ -package net.jacobpeterson.alpaca.abstracts.websocket.client; - -import net.jacobpeterson.alpaca.abstracts.websocket.exception.WebsocketException; -import net.jacobpeterson.alpaca.abstracts.websocket.listener.StreamListener; -import net.jacobpeterson.alpaca.abstracts.websocket.message.StreamMessage; -import net.jacobpeterson.alpaca.abstracts.websocket.message.StreamMessageType; - -/** - * {@link WebsocketClient} represents a client for a Websocket. - * - * @param the {@link StreamListener} type parameter - * @param the {@link StreamMessageType} type parameter - * @param the {@link StreamMessage} type parameter - */ -public interface WebsocketClient, T extends StreamMessageType, M extends StreamMessage> { - - /** - * Adds a {@link StreamListener}. Note that this method is blocking. - * - * @param streamListener the {@link StreamListener} - * - * @throws WebsocketException thrown for {@link WebsocketException}s - */ - void addListener(L streamListener) throws WebsocketException; - - /** - * Removes a {@link StreamListener}. Note that this method is blocking. - * - * @param streamListener the {@link StreamListener} - * - * @throws WebsocketException thrown for {@link WebsocketException}s - */ - void removeListener(L streamListener) throws WebsocketException; - - /** - * Connects the Websocket. Note that this method is blocking. - * - * @throws Exception thrown for {@link Exception}s - */ - void connect() throws Exception; - - /** - * Disconnects the Websocket. Note that this method is blocking. - * - * @throws Exception thrown for {@link Exception}s - */ - void disconnect() throws Exception; - - /** - * Sends the authentication message. Note that this method is blocking. - */ - void sendAuthenticationMessage(); - - /** - * Handles resubscribing. Note that this method is blocking. - */ - void handleResubscribing(); - - /** - * Handles a Websocket message. - * - * @param message the message - */ - void handleWebsocketMessage(String message); - - /** - * Sends a {@link StreamMessage} to {@link StreamListener}s. - * - * @param streamMessageType the {@link StreamMessageType} - * @param streamMessage the {@link StreamMessage} - */ - void sendStreamMessageToListeners(T streamMessageType, M streamMessage); - - /** - * Returns true if the Websocket is connected. - * - * @return true if the Websocket is connected - */ - boolean isConnected(); - - /** - * Returns true if the Websocket is authenticated. - * - * @return true if the Websocket is authenticated - */ - boolean isAuthenticated(); - - /** - * Sets the {@link WebsocketStateListener} for this {@link WebsocketClient}. - * - * @param websocketStateListener the {@link WebsocketStateListener} - */ - void setWebsocketStateListener(WebsocketStateListener websocketStateListener); -} diff --git a/src/main/java/net/jacobpeterson/alpaca/abstracts/websocket/client/WebsocketStateListener.java b/src/main/java/net/jacobpeterson/alpaca/abstracts/websocket/client/WebsocketStateListener.java deleted file mode 100644 index 36c76ebd..00000000 --- a/src/main/java/net/jacobpeterson/alpaca/abstracts/websocket/client/WebsocketStateListener.java +++ /dev/null @@ -1,24 +0,0 @@ -package net.jacobpeterson.alpaca.abstracts.websocket.client; - -/** - * {@link WebsocketStateListener} listens for various state changes in a {@link WebsocketClient}. - */ -public interface WebsocketStateListener { - - /** - * Called when the {@link WebsocketClient} is connected. - */ - void onConnected(); - - /** - * Called when the {@link WebsocketClient} is disconnected. - */ - void onDisconnected(); - - /** - * Called when a {@link WebsocketClient} error has occurred. - * - * @param cause the cause {@link Throwable} - */ - void onError(Throwable cause); -} diff --git a/src/main/java/net/jacobpeterson/alpaca/abstracts/websocket/exception/WebsocketException.java b/src/main/java/net/jacobpeterson/alpaca/abstracts/websocket/exception/WebsocketException.java deleted file mode 100644 index 6d15d207..00000000 --- a/src/main/java/net/jacobpeterson/alpaca/abstracts/websocket/exception/WebsocketException.java +++ /dev/null @@ -1,37 +0,0 @@ -package net.jacobpeterson.alpaca.abstracts.websocket.exception; - -import java.io.IOException; - -/** - * {@link WebsocketException} is used for Websocket-related exceptions. - */ -public class WebsocketException extends IOException { - - /** - * Instantiates a new {@link WebsocketException}. - * - * @param message the message - */ - public WebsocketException(String message) { - super(message); - } - - /** - * Instantiates a new {@link WebsocketException}. - * - * @param message the message - * @param cause the cause - */ - public WebsocketException(String message, Throwable cause) { - super(message, cause); - } - - /** - * Instantiates a new {@link WebsocketException}. - * - * @param exception the {@link Exception} - */ - public WebsocketException(Exception exception) { - super(exception.getMessage(), exception.getCause()); - } -} diff --git a/src/main/java/net/jacobpeterson/alpaca/refactor/properties/AlpacaProperties.java b/src/main/java/net/jacobpeterson/alpaca/properties/AlpacaProperties.java similarity index 80% rename from src/main/java/net/jacobpeterson/alpaca/refactor/properties/AlpacaProperties.java rename to src/main/java/net/jacobpeterson/alpaca/properties/AlpacaProperties.java index fbbb41e5..80d2450f 100644 --- a/src/main/java/net/jacobpeterson/alpaca/refactor/properties/AlpacaProperties.java +++ b/src/main/java/net/jacobpeterson/alpaca/properties/AlpacaProperties.java @@ -1,10 +1,9 @@ -package net.jacobpeterson.alpaca.refactor.properties; +package net.jacobpeterson.alpaca.properties; +import net.jacobpeterson.alpaca.AlpacaAPI; import net.jacobpeterson.alpaca.model.properties.DataAPIType; import net.jacobpeterson.alpaca.model.properties.EndpointAPIType; -import net.jacobpeterson.alpaca.refactor.AlpacaAPI; - -import static net.jacobpeterson.alpaca.refactor.util.properties.PropertyUtil.getProperty; +import net.jacobpeterson.alpaca.util.properties.PropertyUtil; /** * {@link AlpacaProperties} defines properties for {@link AlpacaAPI}. @@ -16,31 +15,31 @@ public class AlpacaProperties { private static final String KEY_ID_KEY = "key_id"; /** The value of {@link #KEY_ID_KEY} in {@link #ALPACA_PROPERTIES_FILE}. */ - public static final String KEY_ID = getProperty( + public static final String KEY_ID = PropertyUtil.getProperty( ALPACA_PROPERTIES_FILE, ALPACA_DEFAULT_PROPERTIES_FILE, KEY_ID_KEY); private static final String SECRET_KEY_KEY = "secret_key"; /** The value of {@link #SECRET_KEY_KEY} in {@link #ALPACA_PROPERTIES_FILE}. */ - public static final String SECRET_KEY = getProperty( + public static final String SECRET_KEY = PropertyUtil.getProperty( ALPACA_PROPERTIES_FILE, ALPACA_DEFAULT_PROPERTIES_FILE, SECRET_KEY_KEY); private static final String ENDPOINT_API_TYPE_KEY = "endpoint_api_type"; /** The value of {@link #ENDPOINT_API_TYPE_KEY} in {@link #ALPACA_PROPERTIES_FILE}. */ - public static final EndpointAPIType ENDPOINT_API_TYPE = EndpointAPIType.fromValue(getProperty( + public static final EndpointAPIType ENDPOINT_API_TYPE = EndpointAPIType.fromValue(PropertyUtil.getProperty( ALPACA_PROPERTIES_FILE, ALPACA_DEFAULT_PROPERTIES_FILE, ENDPOINT_API_TYPE_KEY)); private static final String DATA_API_TYPE_KEY = "data_api_type"; /** The value of {@link #DATA_API_TYPE_KEY} in {@link #ALPACA_PROPERTIES_FILE}. */ - public static final DataAPIType DATA_API_TYPE = DataAPIType.fromValue(getProperty( + public static final DataAPIType DATA_API_TYPE = DataAPIType.fromValue(PropertyUtil.getProperty( ALPACA_PROPERTIES_FILE, ALPACA_DEFAULT_PROPERTIES_FILE, DATA_API_TYPE_KEY)); private static final String USER_AGENT_KEY = "user_agent"; /** The value of {@link #USER_AGENT_KEY} in {@link #ALPACA_PROPERTIES_FILE}. */ - public static final String USER_AGENT = getProperty( + public static final String USER_AGENT = PropertyUtil.getProperty( ALPACA_PROPERTIES_FILE, ALPACA_DEFAULT_PROPERTIES_FILE, USER_AGENT_KEY); } diff --git a/src/main/java/net/jacobpeterson/alpaca/refactor/websocket/marketdata/MarketDataWebsocket.java b/src/main/java/net/jacobpeterson/alpaca/refactor/websocket/marketdata/MarketDataWebsocket.java deleted file mode 100644 index 4d72807f..00000000 --- a/src/main/java/net/jacobpeterson/alpaca/refactor/websocket/marketdata/MarketDataWebsocket.java +++ /dev/null @@ -1,6 +0,0 @@ -package net.jacobpeterson.alpaca.refactor.websocket.marketdata; - -import net.jacobpeterson.alpaca.refactor.websocket.AlpacaWebsocket; - -public class MarketDataWebsocket extends AlpacaWebsocket implements MarketDataWebsocketInterface { -} diff --git a/src/main/java/net/jacobpeterson/alpaca/refactor/websocket/streaming/StreamingListener.java b/src/main/java/net/jacobpeterson/alpaca/refactor/websocket/streaming/StreamingListener.java deleted file mode 100644 index 750a3d24..00000000 --- a/src/main/java/net/jacobpeterson/alpaca/refactor/websocket/streaming/StreamingListener.java +++ /dev/null @@ -1,18 +0,0 @@ -package net.jacobpeterson.alpaca.refactor.websocket.streaming; - -import net.jacobpeterson.alpaca.model.endpoint.streaming.StreamingMessage; -import net.jacobpeterson.alpaca.model.endpoint.streaming.enums.StreamingMessageType; - -/** - * {@link StreamingListener} defines a listener interface for {@link StreamingWebsocket} - */ -public interface StreamingListener { - - /** - * Called when a {@link StreamingMessage} is received. - * - * @param streamingMessageType the {@link StreamingMessageType} - * @param streamingMessage the {@link StreamingMessage} - */ - void onMessage(StreamingMessageType streamingMessageType, StreamingMessage streamingMessage); -} diff --git a/src/main/java/net/jacobpeterson/alpaca/refactor/rest/AlpacaClient.java b/src/main/java/net/jacobpeterson/alpaca/rest/AlpacaClient.java similarity index 80% rename from src/main/java/net/jacobpeterson/alpaca/refactor/rest/AlpacaClient.java rename to src/main/java/net/jacobpeterson/alpaca/rest/AlpacaClient.java index 9fb340ac..bd7cebfb 100644 --- a/src/main/java/net/jacobpeterson/alpaca/refactor/rest/AlpacaClient.java +++ b/src/main/java/net/jacobpeterson/alpaca/rest/AlpacaClient.java @@ -1,9 +1,10 @@ -package net.jacobpeterson.alpaca.refactor.rest; +package net.jacobpeterson.alpaca.rest; import com.google.gson.Gson; import com.google.gson.JsonElement; import com.google.gson.JsonParser; -import net.jacobpeterson.alpaca.refactor.properties.AlpacaProperties; +import net.jacobpeterson.alpaca.properties.AlpacaProperties; +import net.jacobpeterson.alpaca.util.gson.GsonUtil; import okhttp3.*; import java.io.IOException; @@ -13,7 +14,7 @@ import static com.google.common.base.Preconditions.checkArgument; import static com.google.common.base.Preconditions.checkNotNull; -import static net.jacobpeterson.alpaca.refactor.util.gson.GsonUtil.GSON; +import static com.google.common.base.Preconditions.checkState; /** * {@link AlpacaClient} represents an HTTP RestAPI client for Alpaca. @@ -131,7 +132,7 @@ public T requestObject(Request request, Type type) throws AlpacaClientExcept */ public T requestObject(Request request, Predicate isSuccessCode, Type type) throws AlpacaClientException { - return GSON.fromJson(requestJSON(request, isSuccessCode), type); + return GsonUtil.GSON.fromJson(requestJSON(request, isSuccessCode), type); } /** @@ -153,7 +154,29 @@ public JsonElement requestJSON(Request request) throws AlpacaClientException { * @throws AlpacaClientException thrown for {@link AlpacaClientException}s */ public JsonElement requestJSON(Request request, Predicate isSuccessCode) throws AlpacaClientException { - return JsonParser.parseReader(request(request, isSuccessCode).body().charStream()); + try (Response response = request(request, isSuccessCode)) { + ResponseBody responseBody = response.body(); + checkState(responseBody != null); + + try (Reader charStream = responseBody.charStream()) { + return JsonParser.parseReader(charStream); + } catch (IOException ioException) { + throw new AlpacaClientException(ioException); + } + } + } + + /** + * Sends a {@link Request} and ignores any {@link ResponseBody}. + * + * @param request the {@link Request} + * @param isSuccessCode throws {@link AlpacaClientException} if passed in {@link Response#code()} to the {@link + * Predicate#test(Object)} returns false + * + * @throws AlpacaClientException thrown for {@link AlpacaClientException}s + */ + public void requestVoid(Request request, Predicate isSuccessCode) throws AlpacaClientException { + request(request, isSuccessCode).close(); } /** @@ -163,7 +186,7 @@ public JsonElement requestJSON(Request request, Predicate isSuccessCode * @param isSuccessCode throws {@link AlpacaClientException} if passed in {@link Response#code()} to the {@link * Predicate#test(Object)} returns false * - * @return the {@link Response} + * @return the {@link Response}. Be sure to call {@link Response#close()} after use. * * @throws AlpacaClientException thrown for {@link AlpacaClientException}s */ @@ -174,8 +197,8 @@ public Response request(Request request, Predicate isSuccessCode) throw throw new AlpacaClientException(response); } return response; - } catch (Exception exception) { - throw new AlpacaClientException(exception); + } catch (IOException ioException) { + throw new AlpacaClientException(ioException); } } @@ -184,14 +207,12 @@ public Response request(Request request, Predicate isSuccessCode) throw * * @param request the {@link Request} * - * @return the {@link Response} + * @return the {@link Response}. Be sure to call {@link Response#close()} after use. * * @throws IOException thrown for {@link IOException}s */ public Response executeRequest(Request request) throws IOException { - try (Response response = okHttpClient.newCall(request).execute()) { - return response; - } + return okHttpClient.newCall(request).execute(); } /** diff --git a/src/main/java/net/jacobpeterson/alpaca/refactor/rest/AlpacaClientException.java b/src/main/java/net/jacobpeterson/alpaca/rest/AlpacaClientException.java similarity index 63% rename from src/main/java/net/jacobpeterson/alpaca/refactor/rest/AlpacaClientException.java rename to src/main/java/net/jacobpeterson/alpaca/rest/AlpacaClientException.java index 9070b112..0018b2cc 100644 --- a/src/main/java/net/jacobpeterson/alpaca/refactor/rest/AlpacaClientException.java +++ b/src/main/java/net/jacobpeterson/alpaca/rest/AlpacaClientException.java @@ -1,9 +1,15 @@ -package net.jacobpeterson.alpaca.refactor.rest; +package net.jacobpeterson.alpaca.rest; import com.google.gson.JsonElement; import com.google.gson.JsonObject; import com.google.gson.JsonParser; import okhttp3.Response; +import okhttp3.ResponseBody; + +import java.io.IOException; +import java.io.Reader; + +import static com.google.common.base.Preconditions.checkState; public class AlpacaClientException extends Exception { @@ -49,7 +55,7 @@ public AlpacaClientException(String message, Throwable cause) { * * @param response the {@link Response} containing an API response code and message JSON */ - public AlpacaClientException(Response response) { + protected AlpacaClientException(Response response) { this.response = response; } @@ -80,40 +86,48 @@ public String getMessage() { * and sets {@link #apiResponseCode} and {@link #apiResponseMessage} accordingly. * * @return a formatted message of the error response + * + * @throws IOException thrown for {@link IOException}s */ - private String parseAPIErrorResponse() { - JsonElement responseJsonElement = JsonParser.parseReader(response.body().charStream()); - if (responseJsonElement instanceof JsonObject) { - JsonObject responseJsonObject = responseJsonElement.getAsJsonObject(); + private String parseAPIErrorResponse() throws IOException { + ResponseBody responseBody = response.body(); + checkState(responseBody != null); + + try (Reader charStream = responseBody.charStream()) { + JsonElement responseJsonElement = JsonParser.parseReader(charStream); + + if (responseJsonElement instanceof JsonObject) { + JsonObject responseJsonObject = responseJsonElement.getAsJsonObject(); + + if (responseJsonObject.has(CODE_KEY)) { + apiResponseCode = responseJsonObject.get(CODE_KEY).getAsInt(); + } - if (responseJsonObject.has(CODE_KEY)) { - apiResponseCode = responseJsonObject.get(CODE_KEY).getAsInt(); + if (responseJsonObject.has(MESSAGE_KEY)) { + apiResponseMessage = responseJsonObject.get(MESSAGE_KEY).getAsString(); + } } - if (responseJsonObject.has(MESSAGE_KEY)) { - apiResponseMessage = responseJsonObject.get(MESSAGE_KEY).getAsString(); + // Just use the response JSON if the message could not be parsed. + if (apiResponseMessage == null) { + apiResponseMessage = responseJsonElement.toString(); } - } - // Just use the response JSON if the message could not be parsed. - if (apiResponseMessage == null) { - apiResponseMessage = responseJsonElement.toString(); - } + // Build message + StringBuilder messageBuilder = new StringBuilder(); + messageBuilder.append("Status Code: ").append(requestStatusCode); + messageBuilder.append(", Status Message: \"").append(requestStatusMessage).append("\""); - // Build message - StringBuilder messageBuilder = new StringBuilder(); - messageBuilder.append("Status Code: ").append(requestStatusCode); - messageBuilder.append(", Status Message: \"").append(requestStatusMessage).append("\""); + if (apiResponseCode != null) { + messageBuilder.append(", API Response Code: ").append(apiResponseCode); + } - if (apiResponseCode != null) { - messageBuilder.append(", API Response Code: ").append(apiResponseCode); - } + if (apiResponseMessage != null) { + messageBuilder.append(", API Response Message: \"").append(apiResponseMessage).append("\""); + } - if (apiResponseMessage != null) { - messageBuilder.append(", API Response Message: \"").append(apiResponseMessage).append("\""); + return messageBuilder.toString(); } - - return messageBuilder.toString(); } /** diff --git a/src/main/java/net/jacobpeterson/alpaca/refactor/rest/endpoint/AbstractEndpoint.java b/src/main/java/net/jacobpeterson/alpaca/rest/endpoint/AbstractEndpoint.java similarity index 84% rename from src/main/java/net/jacobpeterson/alpaca/refactor/rest/endpoint/AbstractEndpoint.java rename to src/main/java/net/jacobpeterson/alpaca/rest/endpoint/AbstractEndpoint.java index 2c5617eb..d1fb88db 100644 --- a/src/main/java/net/jacobpeterson/alpaca/refactor/rest/endpoint/AbstractEndpoint.java +++ b/src/main/java/net/jacobpeterson/alpaca/rest/endpoint/AbstractEndpoint.java @@ -1,6 +1,6 @@ -package net.jacobpeterson.alpaca.refactor.rest.endpoint; +package net.jacobpeterson.alpaca.rest.endpoint; -import net.jacobpeterson.alpaca.refactor.rest.AlpacaClient; +import net.jacobpeterson.alpaca.rest.AlpacaClient; /** * {@link AbstractEndpoint} is an abstract class representing a RestAPI endpoint. diff --git a/src/main/java/net/jacobpeterson/alpaca/refactor/rest/endpoint/AccountActivitiesEndpoint.java b/src/main/java/net/jacobpeterson/alpaca/rest/endpoint/AccountActivitiesEndpoint.java similarity index 92% rename from src/main/java/net/jacobpeterson/alpaca/refactor/rest/endpoint/AccountActivitiesEndpoint.java rename to src/main/java/net/jacobpeterson/alpaca/rest/endpoint/AccountActivitiesEndpoint.java index 90c7a1f9..c12d0cc1 100644 --- a/src/main/java/net/jacobpeterson/alpaca/refactor/rest/endpoint/AccountActivitiesEndpoint.java +++ b/src/main/java/net/jacobpeterson/alpaca/rest/endpoint/AccountActivitiesEndpoint.java @@ -1,4 +1,4 @@ -package net.jacobpeterson.alpaca.refactor.rest.endpoint; +package net.jacobpeterson.alpaca.rest.endpoint; import com.google.gson.JsonArray; import com.google.gson.JsonElement; @@ -8,8 +8,9 @@ import net.jacobpeterson.alpaca.model.endpoint.accountactivities.TradeActivity; import net.jacobpeterson.alpaca.model.endpoint.accountactivities.enums.ActivityType; import net.jacobpeterson.alpaca.model.endpoint.common.enums.SortDirection; -import net.jacobpeterson.alpaca.refactor.rest.AlpacaClient; -import net.jacobpeterson.alpaca.refactor.rest.AlpacaClientException; +import net.jacobpeterson.alpaca.rest.AlpacaClient; +import net.jacobpeterson.alpaca.rest.AlpacaClientException; +import net.jacobpeterson.alpaca.util.gson.GsonUtil; import okhttp3.HttpUrl; import okhttp3.Request; import org.slf4j.Logger; @@ -23,7 +24,6 @@ import java.util.stream.Collectors; import static com.google.common.base.Preconditions.checkState; -import static net.jacobpeterson.alpaca.refactor.util.gson.GsonUtil.GSON; /** * {@link AbstractEndpoint} for @@ -124,9 +124,9 @@ public List get(ZonedDateTime date, ZonedDateTime until, ZonedD // A 'TradeActivity' always has 'activity_type' field as 'FILL' if (activityType.equals(ActivityType.FILL.toString())) { - accountActivity = GSON.fromJson(responseArrayObject, TradeActivity.class); + accountActivity = GsonUtil.GSON.fromJson(responseArrayObject, TradeActivity.class); } else { - accountActivity = GSON.fromJson(responseArrayObject, NonTradeActivity.class); + accountActivity = GsonUtil.GSON.fromJson(responseArrayObject, NonTradeActivity.class); } if (accountActivity.getActivityType() == null) { diff --git a/src/main/java/net/jacobpeterson/alpaca/refactor/rest/endpoint/AccountConfigurationEndpoint.java b/src/main/java/net/jacobpeterson/alpaca/rest/endpoint/AccountConfigurationEndpoint.java similarity index 83% rename from src/main/java/net/jacobpeterson/alpaca/refactor/rest/endpoint/AccountConfigurationEndpoint.java rename to src/main/java/net/jacobpeterson/alpaca/rest/endpoint/AccountConfigurationEndpoint.java index 25de8fe5..63b5bd13 100644 --- a/src/main/java/net/jacobpeterson/alpaca/refactor/rest/endpoint/AccountConfigurationEndpoint.java +++ b/src/main/java/net/jacobpeterson/alpaca/rest/endpoint/AccountConfigurationEndpoint.java @@ -1,14 +1,14 @@ -package net.jacobpeterson.alpaca.refactor.rest.endpoint; +package net.jacobpeterson.alpaca.rest.endpoint; import net.jacobpeterson.alpaca.model.endpoint.accountconfiguration.AccountConfiguration; -import net.jacobpeterson.alpaca.refactor.rest.AlpacaClient; -import net.jacobpeterson.alpaca.refactor.rest.AlpacaClientException; -import net.jacobpeterson.alpaca.refactor.util.okhttp.JSONBodyBuilder; +import net.jacobpeterson.alpaca.rest.AlpacaClient; +import net.jacobpeterson.alpaca.rest.AlpacaClientException; +import net.jacobpeterson.alpaca.util.gson.GsonUtil; +import net.jacobpeterson.alpaca.util.okhttp.JSONBodyBuilder; import okhttp3.HttpUrl; import okhttp3.Request; import static com.google.common.base.Preconditions.checkNotNull; -import static net.jacobpeterson.alpaca.refactor.util.gson.GsonUtil.GSON; /** * {@link AbstractEndpoint} for @@ -56,7 +56,7 @@ public AccountConfiguration set(AccountConfiguration accountConfiguration) throw HttpUrl.Builder urlBuilder = alpacaClient.urlBuilder() .addPathSegments(endpointPathSegment); Request request = alpacaClient.requestBuilder(urlBuilder.build()) - .patch(new JSONBodyBuilder(GSON.toJson(accountConfiguration)).build()) + .patch(new JSONBodyBuilder(GsonUtil.GSON.toJson(accountConfiguration)).build()) .build(); return alpacaClient.requestObject(request, AccountConfiguration.class); } diff --git a/src/main/java/net/jacobpeterson/alpaca/refactor/rest/endpoint/AccountEndpoint.java b/src/main/java/net/jacobpeterson/alpaca/rest/endpoint/AccountEndpoint.java similarity index 85% rename from src/main/java/net/jacobpeterson/alpaca/refactor/rest/endpoint/AccountEndpoint.java rename to src/main/java/net/jacobpeterson/alpaca/rest/endpoint/AccountEndpoint.java index ef4a4eea..b6c1cf97 100644 --- a/src/main/java/net/jacobpeterson/alpaca/refactor/rest/endpoint/AccountEndpoint.java +++ b/src/main/java/net/jacobpeterson/alpaca/rest/endpoint/AccountEndpoint.java @@ -1,8 +1,8 @@ -package net.jacobpeterson.alpaca.refactor.rest.endpoint; +package net.jacobpeterson.alpaca.rest.endpoint; import net.jacobpeterson.alpaca.model.endpoint.account.Account; -import net.jacobpeterson.alpaca.refactor.rest.AlpacaClient; -import net.jacobpeterson.alpaca.refactor.rest.AlpacaClientException; +import net.jacobpeterson.alpaca.rest.AlpacaClient; +import net.jacobpeterson.alpaca.rest.AlpacaClientException; import okhttp3.HttpUrl; import okhttp3.Request; diff --git a/src/main/java/net/jacobpeterson/alpaca/refactor/rest/endpoint/AssetsEndpoint.java b/src/main/java/net/jacobpeterson/alpaca/rest/endpoint/AssetsEndpoint.java similarity index 93% rename from src/main/java/net/jacobpeterson/alpaca/refactor/rest/endpoint/AssetsEndpoint.java rename to src/main/java/net/jacobpeterson/alpaca/rest/endpoint/AssetsEndpoint.java index 8cbbded3..62cd1210 100644 --- a/src/main/java/net/jacobpeterson/alpaca/refactor/rest/endpoint/AssetsEndpoint.java +++ b/src/main/java/net/jacobpeterson/alpaca/rest/endpoint/AssetsEndpoint.java @@ -1,10 +1,10 @@ -package net.jacobpeterson.alpaca.refactor.rest.endpoint; +package net.jacobpeterson.alpaca.rest.endpoint; import com.google.gson.reflect.TypeToken; import net.jacobpeterson.alpaca.model.endpoint.asset.Asset; import net.jacobpeterson.alpaca.model.endpoint.asset.enums.AssetStatus; -import net.jacobpeterson.alpaca.refactor.rest.AlpacaClient; -import net.jacobpeterson.alpaca.refactor.rest.AlpacaClientException; +import net.jacobpeterson.alpaca.rest.AlpacaClient; +import net.jacobpeterson.alpaca.rest.AlpacaClientException; import okhttp3.HttpUrl; import okhttp3.Request; diff --git a/src/main/java/net/jacobpeterson/alpaca/refactor/rest/endpoint/CalendarEndpoint.java b/src/main/java/net/jacobpeterson/alpaca/rest/endpoint/CalendarEndpoint.java similarity index 91% rename from src/main/java/net/jacobpeterson/alpaca/refactor/rest/endpoint/CalendarEndpoint.java rename to src/main/java/net/jacobpeterson/alpaca/rest/endpoint/CalendarEndpoint.java index 02aa9b98..9fe0c028 100644 --- a/src/main/java/net/jacobpeterson/alpaca/refactor/rest/endpoint/CalendarEndpoint.java +++ b/src/main/java/net/jacobpeterson/alpaca/rest/endpoint/CalendarEndpoint.java @@ -1,9 +1,9 @@ -package net.jacobpeterson.alpaca.refactor.rest.endpoint; +package net.jacobpeterson.alpaca.rest.endpoint; import com.google.gson.reflect.TypeToken; import net.jacobpeterson.alpaca.model.endpoint.calendar.Calendar; -import net.jacobpeterson.alpaca.refactor.rest.AlpacaClient; -import net.jacobpeterson.alpaca.refactor.rest.AlpacaClientException; +import net.jacobpeterson.alpaca.rest.AlpacaClient; +import net.jacobpeterson.alpaca.rest.AlpacaClientException; import okhttp3.HttpUrl; import okhttp3.Request; diff --git a/src/main/java/net/jacobpeterson/alpaca/refactor/rest/endpoint/ClockEndpoint.java b/src/main/java/net/jacobpeterson/alpaca/rest/endpoint/ClockEndpoint.java similarity index 85% rename from src/main/java/net/jacobpeterson/alpaca/refactor/rest/endpoint/ClockEndpoint.java rename to src/main/java/net/jacobpeterson/alpaca/rest/endpoint/ClockEndpoint.java index 8af8b38e..71fb6f25 100644 --- a/src/main/java/net/jacobpeterson/alpaca/refactor/rest/endpoint/ClockEndpoint.java +++ b/src/main/java/net/jacobpeterson/alpaca/rest/endpoint/ClockEndpoint.java @@ -1,8 +1,8 @@ -package net.jacobpeterson.alpaca.refactor.rest.endpoint; +package net.jacobpeterson.alpaca.rest.endpoint; import net.jacobpeterson.alpaca.model.endpoint.clock.Clock; -import net.jacobpeterson.alpaca.refactor.rest.AlpacaClient; -import net.jacobpeterson.alpaca.refactor.rest.AlpacaClientException; +import net.jacobpeterson.alpaca.rest.AlpacaClient; +import net.jacobpeterson.alpaca.rest.AlpacaClientException; import okhttp3.HttpUrl; import okhttp3.Request; diff --git a/src/main/java/net/jacobpeterson/alpaca/refactor/rest/endpoint/MarketDataEndpoint.java b/src/main/java/net/jacobpeterson/alpaca/rest/endpoint/MarketDataEndpoint.java similarity index 97% rename from src/main/java/net/jacobpeterson/alpaca/refactor/rest/endpoint/MarketDataEndpoint.java rename to src/main/java/net/jacobpeterson/alpaca/rest/endpoint/MarketDataEndpoint.java index 2c668ed8..66c698a4 100644 --- a/src/main/java/net/jacobpeterson/alpaca/refactor/rest/endpoint/MarketDataEndpoint.java +++ b/src/main/java/net/jacobpeterson/alpaca/rest/endpoint/MarketDataEndpoint.java @@ -1,4 +1,4 @@ -package net.jacobpeterson.alpaca.refactor.rest.endpoint; +package net.jacobpeterson.alpaca.rest.endpoint; import com.google.gson.reflect.TypeToken; import net.jacobpeterson.alpaca.model.endpoint.marketdata.historical.bar.Bar; @@ -11,9 +11,9 @@ import net.jacobpeterson.alpaca.model.endpoint.marketdata.historical.trade.LatestTradeResponse; import net.jacobpeterson.alpaca.model.endpoint.marketdata.historical.trade.Trade; import net.jacobpeterson.alpaca.model.endpoint.marketdata.historical.trade.TradesResponse; -import net.jacobpeterson.alpaca.refactor.rest.AlpacaClient; -import net.jacobpeterson.alpaca.refactor.rest.AlpacaClientException; -import net.jacobpeterson.alpaca.refactor.util.format.FormatUtil; +import net.jacobpeterson.alpaca.rest.AlpacaClient; +import net.jacobpeterson.alpaca.rest.AlpacaClientException; +import net.jacobpeterson.alpaca.util.format.FormatUtil; import okhttp3.HttpUrl; import okhttp3.Request; diff --git a/src/main/java/net/jacobpeterson/alpaca/refactor/rest/endpoint/OrdersEndpoint.java b/src/main/java/net/jacobpeterson/alpaca/rest/endpoint/OrdersEndpoint.java similarity index 98% rename from src/main/java/net/jacobpeterson/alpaca/refactor/rest/endpoint/OrdersEndpoint.java rename to src/main/java/net/jacobpeterson/alpaca/rest/endpoint/OrdersEndpoint.java index 966c81ff..e9bd6e5f 100644 --- a/src/main/java/net/jacobpeterson/alpaca/refactor/rest/endpoint/OrdersEndpoint.java +++ b/src/main/java/net/jacobpeterson/alpaca/rest/endpoint/OrdersEndpoint.java @@ -1,4 +1,4 @@ -package net.jacobpeterson.alpaca.refactor.rest.endpoint; +package net.jacobpeterson.alpaca.rest.endpoint; import com.google.gson.JsonObject; import com.google.gson.JsonPrimitive; @@ -7,10 +7,10 @@ import net.jacobpeterson.alpaca.model.endpoint.order.CancelledOrder; import net.jacobpeterson.alpaca.model.endpoint.order.Order; import net.jacobpeterson.alpaca.model.endpoint.order.enums.*; -import net.jacobpeterson.alpaca.refactor.rest.AlpacaClient; -import net.jacobpeterson.alpaca.refactor.rest.AlpacaClientException; -import net.jacobpeterson.alpaca.refactor.util.format.FormatUtil; -import net.jacobpeterson.alpaca.refactor.util.okhttp.JSONBodyBuilder; +import net.jacobpeterson.alpaca.rest.AlpacaClient; +import net.jacobpeterson.alpaca.rest.AlpacaClientException; +import net.jacobpeterson.alpaca.util.format.FormatUtil; +import net.jacobpeterson.alpaca.util.okhttp.JSONBodyBuilder; import okhttp3.HttpUrl; import okhttp3.Request; @@ -628,6 +628,6 @@ public void cancel(String orderID) throws AlpacaClientException { Request request = alpacaClient.requestBuilder(urlBuilder.build()) .delete() .build(); - alpacaClient.request(request, (code) -> code == 200 || code == 204); + alpacaClient.requestVoid(request, (code) -> code == 200 || code == 204); } } diff --git a/src/main/java/net/jacobpeterson/alpaca/refactor/rest/endpoint/PortfolioHistoryEndpoint.java b/src/main/java/net/jacobpeterson/alpaca/rest/endpoint/PortfolioHistoryEndpoint.java similarity index 95% rename from src/main/java/net/jacobpeterson/alpaca/refactor/rest/endpoint/PortfolioHistoryEndpoint.java rename to src/main/java/net/jacobpeterson/alpaca/rest/endpoint/PortfolioHistoryEndpoint.java index 96dc7415..8b10a790 100644 --- a/src/main/java/net/jacobpeterson/alpaca/refactor/rest/endpoint/PortfolioHistoryEndpoint.java +++ b/src/main/java/net/jacobpeterson/alpaca/rest/endpoint/PortfolioHistoryEndpoint.java @@ -1,13 +1,12 @@ -package net.jacobpeterson.alpaca.refactor.rest.endpoint; +package net.jacobpeterson.alpaca.rest.endpoint; -import com.google.common.base.Preconditions; import net.jacobpeterson.alpaca.model.endpoint.portfoliohistory.PortfolioHistory; import net.jacobpeterson.alpaca.model.endpoint.portfoliohistory.PortfolioHistoryDataPoint; import net.jacobpeterson.alpaca.model.endpoint.portfoliohistory.PortfolioHistoryResponse; import net.jacobpeterson.alpaca.model.endpoint.portfoliohistory.enums.PortfolioPeriodUnit; import net.jacobpeterson.alpaca.model.endpoint.portfoliohistory.enums.PortfolioTimeFrame; -import net.jacobpeterson.alpaca.refactor.rest.AlpacaClient; -import net.jacobpeterson.alpaca.refactor.rest.AlpacaClientException; +import net.jacobpeterson.alpaca.rest.AlpacaClient; +import net.jacobpeterson.alpaca.rest.AlpacaClientException; import okhttp3.HttpUrl; import okhttp3.Request; diff --git a/src/main/java/net/jacobpeterson/alpaca/refactor/rest/endpoint/PositionsEndpoint.java b/src/main/java/net/jacobpeterson/alpaca/rest/endpoint/PositionsEndpoint.java similarity index 96% rename from src/main/java/net/jacobpeterson/alpaca/refactor/rest/endpoint/PositionsEndpoint.java rename to src/main/java/net/jacobpeterson/alpaca/rest/endpoint/PositionsEndpoint.java index 7ab64813..86ad8cb0 100644 --- a/src/main/java/net/jacobpeterson/alpaca/refactor/rest/endpoint/PositionsEndpoint.java +++ b/src/main/java/net/jacobpeterson/alpaca/rest/endpoint/PositionsEndpoint.java @@ -1,12 +1,12 @@ -package net.jacobpeterson.alpaca.refactor.rest.endpoint; +package net.jacobpeterson.alpaca.rest.endpoint; import com.google.gson.reflect.TypeToken; import net.jacobpeterson.alpaca.model.endpoint.asset.Asset; import net.jacobpeterson.alpaca.model.endpoint.order.Order; import net.jacobpeterson.alpaca.model.endpoint.position.ClosePositionOrder; import net.jacobpeterson.alpaca.model.endpoint.position.Position; -import net.jacobpeterson.alpaca.refactor.rest.AlpacaClient; -import net.jacobpeterson.alpaca.refactor.rest.AlpacaClientException; +import net.jacobpeterson.alpaca.rest.AlpacaClient; +import net.jacobpeterson.alpaca.rest.AlpacaClientException; import okhttp3.HttpUrl; import okhttp3.Request; diff --git a/src/main/java/net/jacobpeterson/alpaca/refactor/rest/endpoint/WatchlistEndpoint.java b/src/main/java/net/jacobpeterson/alpaca/rest/endpoint/WatchlistEndpoint.java similarity index 95% rename from src/main/java/net/jacobpeterson/alpaca/refactor/rest/endpoint/WatchlistEndpoint.java rename to src/main/java/net/jacobpeterson/alpaca/rest/endpoint/WatchlistEndpoint.java index 5ea8df42..3558b041 100644 --- a/src/main/java/net/jacobpeterson/alpaca/refactor/rest/endpoint/WatchlistEndpoint.java +++ b/src/main/java/net/jacobpeterson/alpaca/rest/endpoint/WatchlistEndpoint.java @@ -1,12 +1,12 @@ -package net.jacobpeterson.alpaca.refactor.rest.endpoint; +package net.jacobpeterson.alpaca.rest.endpoint; import com.google.gson.JsonArray; import com.google.gson.reflect.TypeToken; import net.jacobpeterson.alpaca.model.endpoint.asset.Asset; import net.jacobpeterson.alpaca.model.endpoint.watchlist.Watchlist; -import net.jacobpeterson.alpaca.refactor.rest.AlpacaClient; -import net.jacobpeterson.alpaca.refactor.rest.AlpacaClientException; -import net.jacobpeterson.alpaca.refactor.util.okhttp.JSONBodyBuilder; +import net.jacobpeterson.alpaca.rest.AlpacaClient; +import net.jacobpeterson.alpaca.rest.AlpacaClientException; +import net.jacobpeterson.alpaca.util.okhttp.JSONBodyBuilder; import okhttp3.HttpUrl; import okhttp3.Request; @@ -182,7 +182,7 @@ public void delete(String watchlistID) throws AlpacaClientException { Request request = alpacaClient.requestBuilder(urlBuilder.build()) .delete() .build(); - alpacaClient.request(request, (code) -> code == 200 || code == 204); + alpacaClient.requestVoid(request, (code) -> code == 200 || code == 204); } /** diff --git a/src/main/java/net/jacobpeterson/alpaca/refactor/util/doc/Documentation2JSONSchema.java b/src/main/java/net/jacobpeterson/alpaca/util/doc/Documentation2JSONSchema.java similarity index 95% rename from src/main/java/net/jacobpeterson/alpaca/refactor/util/doc/Documentation2JSONSchema.java rename to src/main/java/net/jacobpeterson/alpaca/util/doc/Documentation2JSONSchema.java index 4d4f26ad..0eefc3ea 100644 --- a/src/main/java/net/jacobpeterson/alpaca/refactor/util/doc/Documentation2JSONSchema.java +++ b/src/main/java/net/jacobpeterson/alpaca/util/doc/Documentation2JSONSchema.java @@ -1,4 +1,4 @@ -package net.jacobpeterson.alpaca.refactor.util.doc; +package net.jacobpeterson.alpaca.util.doc; import org.w3c.dom.Document; import org.w3c.dom.NodeList; @@ -7,11 +7,7 @@ import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.DocumentBuilderFactory; import javax.xml.parsers.ParserConfigurationException; -import javax.xml.xpath.XPath; -import javax.xml.xpath.XPathConstants; -import javax.xml.xpath.XPathExpression; -import javax.xml.xpath.XPathExpressionException; -import javax.xml.xpath.XPathFactory; +import javax.xml.xpath.*; import java.io.ByteArrayInputStream; import java.io.IOException; diff --git a/src/main/java/net/jacobpeterson/alpaca/refactor/util/format/FormatUtil.java b/src/main/java/net/jacobpeterson/alpaca/util/format/FormatUtil.java similarity index 96% rename from src/main/java/net/jacobpeterson/alpaca/refactor/util/format/FormatUtil.java rename to src/main/java/net/jacobpeterson/alpaca/util/format/FormatUtil.java index 656d9bd0..39567d24 100644 --- a/src/main/java/net/jacobpeterson/alpaca/refactor/util/format/FormatUtil.java +++ b/src/main/java/net/jacobpeterson/alpaca/util/format/FormatUtil.java @@ -1,4 +1,4 @@ -package net.jacobpeterson.alpaca.refactor.util.format; +package net.jacobpeterson.alpaca.util.format; import java.text.DecimalFormat; import java.text.DecimalFormatSymbols; diff --git a/src/main/java/net/jacobpeterson/alpaca/refactor/util/gson/GsonUtil.java b/src/main/java/net/jacobpeterson/alpaca/util/gson/GsonUtil.java similarity index 92% rename from src/main/java/net/jacobpeterson/alpaca/refactor/util/gson/GsonUtil.java rename to src/main/java/net/jacobpeterson/alpaca/util/gson/GsonUtil.java index 6c6cb150..ef83f668 100644 --- a/src/main/java/net/jacobpeterson/alpaca/refactor/util/gson/GsonUtil.java +++ b/src/main/java/net/jacobpeterson/alpaca/util/gson/GsonUtil.java @@ -1,4 +1,4 @@ -package net.jacobpeterson.alpaca.refactor.util.gson; +package net.jacobpeterson.alpaca.util.gson; import com.google.gson.Gson; import com.google.gson.GsonBuilder; diff --git a/src/main/java/net/jacobpeterson/alpaca/refactor/util/gson/LocalDateAdapter.java b/src/main/java/net/jacobpeterson/alpaca/util/gson/LocalDateAdapter.java similarity index 68% rename from src/main/java/net/jacobpeterson/alpaca/refactor/util/gson/LocalDateAdapter.java rename to src/main/java/net/jacobpeterson/alpaca/util/gson/LocalDateAdapter.java index 67744898..e636cdb5 100644 --- a/src/main/java/net/jacobpeterson/alpaca/refactor/util/gson/LocalDateAdapter.java +++ b/src/main/java/net/jacobpeterson/alpaca/util/gson/LocalDateAdapter.java @@ -1,12 +1,6 @@ -package net.jacobpeterson.alpaca.refactor.util.gson; +package net.jacobpeterson.alpaca.util.gson; -import com.google.gson.JsonDeserializationContext; -import com.google.gson.JsonDeserializer; -import com.google.gson.JsonElement; -import com.google.gson.JsonParseException; -import com.google.gson.JsonPrimitive; -import com.google.gson.JsonSerializationContext; -import com.google.gson.JsonSerializer; +import com.google.gson.*; import java.lang.reflect.Type; import java.time.LocalDate; diff --git a/src/main/java/net/jacobpeterson/alpaca/refactor/util/gson/ZonedDateTimeAdapter.java b/src/main/java/net/jacobpeterson/alpaca/util/gson/ZonedDateTimeAdapter.java similarity index 69% rename from src/main/java/net/jacobpeterson/alpaca/refactor/util/gson/ZonedDateTimeAdapter.java rename to src/main/java/net/jacobpeterson/alpaca/util/gson/ZonedDateTimeAdapter.java index a8206604..febf3d49 100644 --- a/src/main/java/net/jacobpeterson/alpaca/refactor/util/gson/ZonedDateTimeAdapter.java +++ b/src/main/java/net/jacobpeterson/alpaca/util/gson/ZonedDateTimeAdapter.java @@ -1,12 +1,6 @@ -package net.jacobpeterson.alpaca.refactor.util.gson; +package net.jacobpeterson.alpaca.util.gson; -import com.google.gson.JsonDeserializationContext; -import com.google.gson.JsonDeserializer; -import com.google.gson.JsonElement; -import com.google.gson.JsonParseException; -import com.google.gson.JsonPrimitive; -import com.google.gson.JsonSerializationContext; -import com.google.gson.JsonSerializer; +import com.google.gson.*; import java.lang.reflect.Type; import java.time.ZonedDateTime; diff --git a/src/main/java/net/jacobpeterson/alpaca/refactor/util/okhttp/JSONBodyBuilder.java b/src/main/java/net/jacobpeterson/alpaca/util/okhttp/JSONBodyBuilder.java similarity index 94% rename from src/main/java/net/jacobpeterson/alpaca/refactor/util/okhttp/JSONBodyBuilder.java rename to src/main/java/net/jacobpeterson/alpaca/util/okhttp/JSONBodyBuilder.java index 84852b7a..c6f54498 100644 --- a/src/main/java/net/jacobpeterson/alpaca/refactor/util/okhttp/JSONBodyBuilder.java +++ b/src/main/java/net/jacobpeterson/alpaca/util/okhttp/JSONBodyBuilder.java @@ -1,4 +1,4 @@ -package net.jacobpeterson.alpaca.refactor.util.okhttp; +package net.jacobpeterson.alpaca.util.okhttp; import com.google.gson.JsonElement; import com.google.gson.JsonObject; @@ -11,7 +11,7 @@ public class JSONBodyBuilder { /** The UTF-8 JSON {@link MediaType}. */ - public static final MediaType JSON_MEDIA_TYPE = MediaType.get("application/json; charset=utf-8"); + public static final MediaType JSON_MEDIA_TYPE = MediaType.get("application/json; charset=UTF-8"); private String bodyJSON; private JsonObject bodyJsonObject; diff --git a/src/main/java/net/jacobpeterson/alpaca/util/okhttp/WebsocketStateListener.java b/src/main/java/net/jacobpeterson/alpaca/util/okhttp/WebsocketStateListener.java new file mode 100644 index 00000000..38f91d70 --- /dev/null +++ b/src/main/java/net/jacobpeterson/alpaca/util/okhttp/WebsocketStateListener.java @@ -0,0 +1,32 @@ +package net.jacobpeterson.alpaca.util.okhttp; + +import okhttp3.Response; +import okhttp3.WebSocket; + +/** + * {@link WebsocketStateListener} is an interface to listen for various state changes in an {@link WebSocket} instance. + */ +public interface WebsocketStateListener { + + /** + * Called when the {@link WebSocket} is connected. + * + * @param response the HTTP websocket upgrade {@link Response} + */ + void onOpen(Response response); + + /** + * Called when the {@link WebSocket} is disconnected. + * + * @param code the code + * @param reason the reason + */ + void onClosed(int code, String reason); + + /** + * Called when a {@link WebSocket} error has occurred. + * + * @param cause the cause {@link Throwable} + */ + void onFailure(Throwable cause); +} diff --git a/src/main/java/net/jacobpeterson/alpaca/refactor/util/properties/PropertyUtil.java b/src/main/java/net/jacobpeterson/alpaca/util/properties/PropertyUtil.java similarity index 98% rename from src/main/java/net/jacobpeterson/alpaca/refactor/util/properties/PropertyUtil.java rename to src/main/java/net/jacobpeterson/alpaca/util/properties/PropertyUtil.java index 131c117f..e8e27bc2 100644 --- a/src/main/java/net/jacobpeterson/alpaca/refactor/util/properties/PropertyUtil.java +++ b/src/main/java/net/jacobpeterson/alpaca/util/properties/PropertyUtil.java @@ -1,4 +1,4 @@ -package net.jacobpeterson.alpaca.refactor.util.properties; +package net.jacobpeterson.alpaca.util.properties; import org.slf4j.Logger; import org.slf4j.LoggerFactory; diff --git a/src/main/java/net/jacobpeterson/alpaca/refactor/websocket/AlpacaWebsocket.java b/src/main/java/net/jacobpeterson/alpaca/websocket/AlpacaWebsocket.java similarity index 86% rename from src/main/java/net/jacobpeterson/alpaca/refactor/websocket/AlpacaWebsocket.java rename to src/main/java/net/jacobpeterson/alpaca/websocket/AlpacaWebsocket.java index a3e233f9..128bd2b8 100644 --- a/src/main/java/net/jacobpeterson/alpaca/refactor/websocket/AlpacaWebsocket.java +++ b/src/main/java/net/jacobpeterson/alpaca/websocket/AlpacaWebsocket.java @@ -1,5 +1,6 @@ -package net.jacobpeterson.alpaca.refactor.websocket; +package net.jacobpeterson.alpaca.websocket; +import net.jacobpeterson.alpaca.util.okhttp.WebsocketStateListener; import okhttp3.*; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -48,6 +49,7 @@ public abstract class AlpacaWebsocket extends WebSocketListener implements Alpac protected final String oAuthToken; protected final boolean useOAuth; + protected WebsocketStateListener websocketStateListener; protected WebSocket websocket; protected boolean connected; protected boolean authenticated; @@ -117,6 +119,10 @@ public boolean isAuthenticated() { @Override public void onOpen(@NotNull WebSocket webSocket, @NotNull Response response) { + if (websocketStateListener != null) { + websocketStateListener.onOpen(response); + } + LOGGER.info("{} websocket opened.", websocketName); LOGGER.debug("{} websocket response: {}", websocketName, response); @@ -132,6 +138,10 @@ public void onOpen(@NotNull WebSocket webSocket, @NotNull Response response) { @Override public void onClosed(@NotNull WebSocket webSocket, int code, @NotNull String reason) { + if (websocketStateListener != null) { + websocketStateListener.onClosed(code, reason); + } + if (intentionalClose) { LOGGER.info("{} websocket closed. Code: {}, Reason: {}", websocketName, code, reason); cleanupState(); @@ -143,6 +153,10 @@ public void onClosed(@NotNull WebSocket webSocket, int code, @NotNull String rea @Override public void onFailure(@NotNull WebSocket webSocket, @NotNull Throwable cause, @Nullable Response response) { + if (websocketStateListener != null) { + websocketStateListener.onFailure(cause); + } + LOGGER.error("{} websocket failure! Response: {}", websocketName, response, cause); handleReconnectionAttempt(); } @@ -200,6 +214,24 @@ private void cleanupState() { */ protected abstract void sendAuthenticationMessage(); + /** + * Gets {@link #websocketStateListener}. + * + * @return the {@link WebsocketStateListener} + */ + public WebsocketStateListener getWebsocketStateListener() { + return websocketStateListener; + } + + /** + * Sets {@link #websocketStateListener}. + * + * @param websocketStateListener an {@link WebsocketStateListener} + */ + public void setWebsocketStateListener(WebsocketStateListener websocketStateListener) { + this.websocketStateListener = websocketStateListener; + } + /** * Gets {@link #automaticallyReconnect}. * diff --git a/src/main/java/net/jacobpeterson/alpaca/refactor/websocket/AlpacaWebsocketInterface.java b/src/main/java/net/jacobpeterson/alpaca/websocket/AlpacaWebsocketInterface.java similarity index 93% rename from src/main/java/net/jacobpeterson/alpaca/refactor/websocket/AlpacaWebsocketInterface.java rename to src/main/java/net/jacobpeterson/alpaca/websocket/AlpacaWebsocketInterface.java index be1de862..4ff1f41c 100644 --- a/src/main/java/net/jacobpeterson/alpaca/refactor/websocket/AlpacaWebsocketInterface.java +++ b/src/main/java/net/jacobpeterson/alpaca/websocket/AlpacaWebsocketInterface.java @@ -1,4 +1,4 @@ -package net.jacobpeterson.alpaca.refactor.websocket; +package net.jacobpeterson.alpaca.websocket; /** * {@link AlpacaWebsocketInterface} defines an interface for Alpaca websockets. diff --git a/src/main/java/net/jacobpeterson/alpaca/websocket/AlpacaWebsocketMessageListener.java b/src/main/java/net/jacobpeterson/alpaca/websocket/AlpacaWebsocketMessageListener.java new file mode 100644 index 00000000..6c6e3b14 --- /dev/null +++ b/src/main/java/net/jacobpeterson/alpaca/websocket/AlpacaWebsocketMessageListener.java @@ -0,0 +1,19 @@ +package net.jacobpeterson.alpaca.websocket; + +/** + * {@link AlpacaWebsocketMessageListener} defines a listener interface for messages from an {@link AlpacaWebsocket} + * instances. + * + * @param the 'message type' type parameter + * @param the 'message' type parameter + */ +public interface AlpacaWebsocketMessageListener { + + /** + * Called when a message is received. + * + * @param messageType the message type + * @param message the message + */ + void onMessage(T messageType, M message); +} diff --git a/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/MarketDataListener.java b/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/MarketDataListener.java new file mode 100644 index 00000000..c1138209 --- /dev/null +++ b/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/MarketDataListener.java @@ -0,0 +1,11 @@ +package net.jacobpeterson.alpaca.websocket.marketdata; + +import net.jacobpeterson.alpaca.model.endpoint.marketdata.realtime.MarketDataMessage; +import net.jacobpeterson.alpaca.model.endpoint.marketdata.realtime.enums.MarketDataMessageType; +import net.jacobpeterson.alpaca.websocket.AlpacaWebsocketMessageListener; + +/** + * {@link MarketDataListener} defines a listener interface for {@link MarketDataWebsocket} messages. + */ +public interface MarketDataListener extends AlpacaWebsocketMessageListener { +} diff --git a/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/MarketDataWebsocket.java b/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/MarketDataWebsocket.java new file mode 100644 index 00000000..03f2e391 --- /dev/null +++ b/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/MarketDataWebsocket.java @@ -0,0 +1,4 @@ +package net.jacobpeterson.alpaca.websocket.marketdata; + +public class MarketDataWebsocket /*extends AlpacaWebsocket implements MarketDataWebsocketInterface */ { +} diff --git a/src/main/java/net/jacobpeterson/alpaca/refactor/websocket/marketdata/MarketDataWebsocketInterface.java b/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/MarketDataWebsocketInterface.java similarity index 59% rename from src/main/java/net/jacobpeterson/alpaca/refactor/websocket/marketdata/MarketDataWebsocketInterface.java rename to src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/MarketDataWebsocketInterface.java index a15ac764..0ba70038 100644 --- a/src/main/java/net/jacobpeterson/alpaca/refactor/websocket/marketdata/MarketDataWebsocketInterface.java +++ b/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/MarketDataWebsocketInterface.java @@ -1,6 +1,6 @@ -package net.jacobpeterson.alpaca.refactor.websocket.marketdata; +package net.jacobpeterson.alpaca.websocket.marketdata; -import net.jacobpeterson.alpaca.refactor.websocket.AlpacaWebsocketInterface; +import net.jacobpeterson.alpaca.websocket.AlpacaWebsocketInterface; /** * {@link MarketDataWebsocketInterface} is an {@link AlpacaWebsocketInterface} for a {@link MarketDataWebsocket}. diff --git a/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/client/MarketDataWebsocketClient.java b/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/client/MarketDataWebsocketClient.java deleted file mode 100644 index 0b72ebbb..00000000 --- a/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/client/MarketDataWebsocketClient.java +++ /dev/null @@ -1,342 +0,0 @@ -package net.jacobpeterson.alpaca.websocket.marketdata.client; - -import com.google.common.base.Preconditions; -import com.google.gson.JsonArray; -import com.google.gson.JsonElement; -import com.google.gson.JsonObject; -import com.google.gson.JsonParser; -import net.jacobpeterson.alpaca.refactor.AlpacaAPI; -import net.jacobpeterson.alpaca.abstracts.websocket.client.WebsocketClient; -import net.jacobpeterson.alpaca.abstracts.websocket.client.WebsocketStateListener; -import net.jacobpeterson.alpaca.abstracts.websocket.exception.WebsocketException; -import net.jacobpeterson.alpaca.domain.marketdata.realtime.MarketDataMessage; -import net.jacobpeterson.alpaca.domain.marketdata.realtime.SymbolMessage; -import net.jacobpeterson.alpaca.domain.marketdata.realtime.bar.BarMessage; -import net.jacobpeterson.alpaca.domain.marketdata.realtime.control.ErrorMessage; -import net.jacobpeterson.alpaca.domain.marketdata.realtime.control.SubscriptionsMessage; -import net.jacobpeterson.alpaca.domain.marketdata.realtime.control.SuccessMessage; -import net.jacobpeterson.alpaca.domain.marketdata.realtime.quote.QuoteMessage; -import net.jacobpeterson.alpaca.domain.marketdata.realtime.trade.TradeMessage; -import net.jacobpeterson.alpaca.refactor.properties.enums.DataAPIType; -import net.jacobpeterson.alpaca.refactor.util.gson.GsonUtil; -import net.jacobpeterson.alpaca.websocket.broker.client.AlpacaWebsocketClient; -import net.jacobpeterson.alpaca.websocket.marketdata.listener.MarketDataListener; -import net.jacobpeterson.alpaca.websocket.marketdata.message.MarketDataMessageType; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import javax.websocket.DeploymentException; -import java.io.IOException; -import java.net.URI; -import java.net.URISyntaxException; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Set; - -/** - * {@link MarketDataWebsocketClient} represents a client for the {@link AlpacaAPI} market data Websocket stream. - */ -public class MarketDataWebsocketClient implements WebsocketClient { - - private static final Logger LOGGER = LoggerFactory.getLogger(MarketDataWebsocketClient.class); - private static final String ALL_TICKERS = "*"; - private static final String MESSAGE_TYPE_KEY = "T"; - - private final String keyID; - private final String secret; - private final String oAuthToken; - private final String websocketAPIURL; - private final List listeners; - - private MarketDataWebsocketClientEndpoint marketDataWebsocketClientEndpoint; - private WebsocketStateListener websocketStateListener; - private boolean authenticated; - - /** - * Instantiates a new {@link AlpacaWebsocketClient}. - * - * @param keyID the key ID - * @param secret the secret - * @param dataAPIType the {@link DataAPIType} - */ - public MarketDataWebsocketClient(String keyID, String secret, DataAPIType dataAPIType) { - this(keyID, secret, null, dataAPIType); - } - - /** - * Instantiates a new {@link AlpacaWebsocketClient}. - * - * @param oAuthToken the OAuth token - * @param dataAPIType the {@link DataAPIType} - */ - public MarketDataWebsocketClient(String oAuthToken, DataAPIType dataAPIType) { - this(null, null, oAuthToken, dataAPIType); - } - - /** - * Instantiates a new {@link AlpacaWebsocketClient}. - * - * @param keyID the key ID - * @param secret the secret - * @param oAuthToken the OAuth token - * @param dataAPIType the {@link DataAPIType} - */ - private MarketDataWebsocketClient(String keyID, String secret, String oAuthToken, DataAPIType dataAPIType) { - this.keyID = keyID; - this.secret = secret; - this.oAuthToken = oAuthToken; - this.websocketAPIURL = dataAPIType.getURL(); - - listeners = new ArrayList<>(); - } - - @Override - public void addListener(MarketDataListener streamListener) throws WebsocketException { - if (!isConnected()) { - try { - connect(); - } catch (Exception exception) { - throw new WebsocketException(exception); - } - } - - listeners.add(streamListener); - - submitRequest(streamListener, false); - } - - @Override - public void removeListener(MarketDataListener streamListener) throws WebsocketException { - listeners.remove(streamListener); - - if (listeners.isEmpty() && isConnected()) { - try { - disconnect(); - } catch (Exception exception) { - throw new WebsocketException(exception); - } - } else { - submitRequest(streamListener, true); - } - } - - @Override - public void connect() throws URISyntaxException, IOException, DeploymentException { - LOGGER.info("Connecting..."); - - marketDataWebsocketClientEndpoint = new MarketDataWebsocketClientEndpoint(this, new URI(websocketAPIURL)); - marketDataWebsocketClientEndpoint.setWebsocketStateListener(websocketStateListener); - marketDataWebsocketClientEndpoint.setAutomaticallyReconnect(true); - marketDataWebsocketClientEndpoint.connect(); - - LOGGER.info("Connected."); - } - - @Override - public void disconnect() throws Exception { - LOGGER.info("Disconnecting..."); - - if (marketDataWebsocketClientEndpoint != null) { - marketDataWebsocketClientEndpoint.disconnect(); - } - - LOGGER.info("Disconnected."); - } - - @Override - public void sendAuthenticationMessage() { - /* Format of message is: - * {"action": "auth", "key": "{APCA-API-KEY-ID}", "secret": "{APCA-API-SECRET-KEY}"} - */ - - JsonObject authRequest = new JsonObject(); - authRequest.addProperty("action", "auth"); - - if (oAuthToken != null) { - throw new UnsupportedOperationException("OAuth isn't currently implemented!"); - } else { - authRequest.addProperty("key", keyID); - authRequest.addProperty("secret", secret); - } - - marketDataWebsocketClientEndpoint.sendMessage(authRequest.toString()); - } - - @Override - public void handleResubscribing() { - for (MarketDataListener marketDataListener : listeners) { - submitRequest(marketDataListener, false); - } - } - - @Override - public void handleWebsocketMessage(String message) { - JsonElement messageJsonElement = JsonParser.parseString(message); - Preconditions.checkState(messageJsonElement instanceof JsonArray); - - JsonArray messageJsonArray = messageJsonElement.getAsJsonArray(); - - for (JsonElement arrayJsonElement : messageJsonArray) { - if (!(arrayJsonElement instanceof JsonObject)) { - continue; - } - - JsonObject messageJsonObject = arrayJsonElement.getAsJsonObject(); - MarketDataMessageType marketDataMessageType = GsonUtil.GSON.fromJson( - messageJsonObject.get(MESSAGE_TYPE_KEY), MarketDataMessageType.class); - if (marketDataMessageType == null) { - LOGGER.error("Message type not found in message: {}", messageJsonObject); - continue; - } - - MarketDataMessage marketDataMessage; - switch (marketDataMessageType) { - case SUCCESS: - marketDataMessage = GsonUtil.GSON.fromJson(messageJsonObject, SuccessMessage.class); - authenticated = isAuthorizationMessageSuccess((SuccessMessage) marketDataMessage); - break; - case ERROR: - marketDataMessage = GsonUtil.GSON.fromJson(messageJsonObject, ErrorMessage.class); - LOGGER.error("{}", marketDataMessage); - break; - case SUBSCRIPTION: - marketDataMessage = GsonUtil.GSON.fromJson(messageJsonObject, SubscriptionsMessage.class); - break; - case TRADE: - marketDataMessage = GsonUtil.GSON.fromJson(messageJsonObject, TradeMessage.class); - break; - case QUOTE: - marketDataMessage = GsonUtil.GSON.fromJson(messageJsonObject, QuoteMessage.class); - break; - case BAR: - marketDataMessage = GsonUtil.GSON.fromJson(messageJsonObject, BarMessage.class); - break; - default: - LOGGER.error("Message type {} not implemented!", marketDataMessageType); - continue; - } - - LOGGER.debug("{}", marketDataMessage); - sendStreamMessageToListeners(marketDataMessageType, marketDataMessage); - } - } - - @Override - public void sendStreamMessageToListeners(MarketDataMessageType marketDataMessageType, - MarketDataMessage marketDataMessage) { - for (MarketDataListener marketDataListener : new ArrayList<>(listeners)) { - boolean sendToStreamListener = false; - - if (marketDataListener.getDataStreams() == null || marketDataListener.getDataStreams().isEmpty()) { - sendToStreamListener = true; - } else if (!(marketDataMessage instanceof SymbolMessage) && marketDataListener.getDataStreams() - .values().stream().anyMatch(types -> types.contains(marketDataMessageType))) { - sendToStreamListener = true; - } else if (marketDataMessage instanceof SymbolMessage) { - SymbolMessage symbolMessage = (SymbolMessage) marketDataMessage; - Set symbolMessageTypes = marketDataListener.getDataStreams() - .getOrDefault(symbolMessage.getSymbol(), null); - Set allMessageTypes = marketDataListener.getDataStreams() - .getOrDefault(ALL_TICKERS, null); - - if (symbolMessageTypes != null) { - sendToStreamListener = symbolMessageTypes.contains(marketDataMessageType); - } else if (allMessageTypes != null) { - sendToStreamListener = allMessageTypes.contains(marketDataMessageType); - } - } - - if (sendToStreamListener) { - marketDataListener.onStreamUpdate(marketDataMessageType, marketDataMessage); - } - } - } - - @Override - public boolean isConnected() { - return marketDataWebsocketClientEndpoint != null && - marketDataWebsocketClientEndpoint.getUserSession() != null && - marketDataWebsocketClientEndpoint.getUserSession().isOpen(); - } - - @Override - public boolean isAuthenticated() { - return authenticated; - } - - @Override - public void setWebsocketStateListener(WebsocketStateListener websocketStateListener) { - this.websocketStateListener = websocketStateListener; - } - - /** - * Returns true if {@link SuccessMessage} states authentication. - * - * @param successMessage the {@link SuccessMessage} - * - * @return true if {@link SuccessMessage} states authentication - */ - private boolean isAuthorizationMessageSuccess(SuccessMessage successMessage) { - return successMessage.getMessage().equals("authenticated"); - } - - /** - * Submits a (un)subscribe request. - * - * @param marketDataListener the {@link MarketDataListener}s - * @param unsubscribe true to unsubscribe, false to subscribe - */ - private void submitRequest(MarketDataListener marketDataListener, boolean unsubscribe) { - JsonObject streamRequestJsonObject = new JsonObject(); - streamRequestJsonObject.addProperty("action", unsubscribe ? "unsubscribe" : "subscribe"); - - JsonArray tradesJsonArray = new JsonArray(); - JsonArray quotesJsonArray = new JsonArray(); - JsonArray barsJsonArray = new JsonArray(); - - Map> flattenedDataStreams = listeners.stream() - .filter(listener -> listener != marketDataListener) // Filter out listener being added - .flatMap(listener -> listener.getDataStreams().entrySet().stream()) // Flat map all data streams - .collect(HashMap::new, // Collect all data streams into one map - (cumulativeMap, listenerEntry) -> { - Set listenerMessageTypes = - cumulativeMap.getOrDefault(listenerEntry.getKey(), null); - if (listenerMessageTypes != null) { - listenerMessageTypes.addAll(listenerEntry.getValue()); - } else { - cumulativeMap.put(listenerEntry.getKey(), listenerEntry.getValue()); - } - }, - HashMap::putAll); - - for (Map.Entry> listenerEntry : marketDataListener - .getDataStreams().entrySet()) { - Set currentListenedTypes = flattenedDataStreams - .getOrDefault(listenerEntry.getKey(), null); - - if (listenerEntry.getValue().contains(MarketDataMessageType.TRADE) && - (currentListenedTypes == null || !currentListenedTypes.contains(MarketDataMessageType.TRADE))) { - tradesJsonArray.add(listenerEntry.getKey()); - } - if (listenerEntry.getValue().contains(MarketDataMessageType.QUOTE) && - (currentListenedTypes == null || !currentListenedTypes.contains(MarketDataMessageType.QUOTE))) { - quotesJsonArray.add(listenerEntry.getKey()); - } - if (listenerEntry.getValue().contains(MarketDataMessageType.BAR) && - (currentListenedTypes == null || !currentListenedTypes.contains(MarketDataMessageType.BAR))) { - barsJsonArray.add(listenerEntry.getKey()); - } - } - - streamRequestJsonObject.add("trades", tradesJsonArray); - streamRequestJsonObject.add("quotes", quotesJsonArray); - streamRequestJsonObject.add("bars", barsJsonArray); - - marketDataWebsocketClientEndpoint.sendMessage(streamRequestJsonObject.toString()); - - LOGGER.info("Requested subscriptions to update to {}", streamRequestJsonObject); - } -} diff --git a/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/client/MarketDataWebsocketClientEndpoint.java b/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/client/MarketDataWebsocketClientEndpoint.java deleted file mode 100644 index 96a6ceef..00000000 --- a/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/client/MarketDataWebsocketClientEndpoint.java +++ /dev/null @@ -1,51 +0,0 @@ -package net.jacobpeterson.alpaca.websocket.marketdata.client; - -import net.jacobpeterson.alpaca.refactor.AlpacaAPI; -import net.jacobpeterson.alpaca.abstracts.websocket.client.AbstractWebsocketClientEndpoint; - -import javax.websocket.ClientEndpoint; -import javax.websocket.CloseReason; -import javax.websocket.OnClose; -import javax.websocket.OnError; -import javax.websocket.OnMessage; -import javax.websocket.OnOpen; -import javax.websocket.Session; -import java.net.URI; - -/** - * {@link MarketDataWebsocketClientEndpoint} is used for handling a Websocket directly for {@link AlpacaAPI} market - * data. - */ -@ClientEndpoint(subprotocols = "STRING") -public class MarketDataWebsocketClientEndpoint extends AbstractWebsocketClientEndpoint { - - /** - * Instantiates a new {@link MarketDataWebsocketClientEndpoint}. - * - * @param websocketClient the {@link MarketDataWebsocketClient} - * @param endpointURI the endpoint {@link URI} - */ - public MarketDataWebsocketClientEndpoint(MarketDataWebsocketClient websocketClient, URI endpointURI) { - super(websocketClient, endpointURI, "MarketDataWebsocketThread"); - } - - @OnOpen - public void onOpenAnnotated(Session userSession) { - super.onOpen(userSession); - } - - @OnClose - public void onCloseAnnotated(Session userSession, CloseReason reason) { - super.onClose(userSession, reason); - } - - @OnMessage - public void onMessageAnnotated(String message) { - super.onMessage(message); - } - - @OnError - public void onError(Throwable throwable) { - super.onError(throwable); - } -} diff --git a/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/listener/MarketDataListener.java b/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/listener/MarketDataListener.java deleted file mode 100644 index 71ad002f..00000000 --- a/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/listener/MarketDataListener.java +++ /dev/null @@ -1,23 +0,0 @@ -package net.jacobpeterson.alpaca.websocket.marketdata.listener; - -import net.jacobpeterson.alpaca.abstracts.websocket.listener.StreamListener; -import net.jacobpeterson.alpaca.domain.marketdata.realtime.MarketDataMessage; -import net.jacobpeterson.alpaca.websocket.marketdata.message.MarketDataMessageType; - -import java.util.Map; -import java.util.Set; - -/** - * An asynchronous update interface for receiving websocket messages from - * {@link net.jacobpeterson.alpaca.websocket.marketdata.client.MarketDataWebsocketClient} - */ -public interface MarketDataListener extends StreamListener { - - /** - * Gets the {@link MarketDataMessageType} of tickers {@link Map} for this {@link MarketDataListener}. Null or empty - * to listen to all stream messages. - * - * @return the {@link MarketDataMessageType} of tickers {@link Map} - */ - Map> getDataStreams(); -} diff --git a/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/listener/MarketDataListenerAdapter.java b/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/listener/MarketDataListenerAdapter.java deleted file mode 100644 index f38712cd..00000000 --- a/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/listener/MarketDataListenerAdapter.java +++ /dev/null @@ -1,65 +0,0 @@ -package net.jacobpeterson.alpaca.websocket.marketdata.listener; - -import net.jacobpeterson.alpaca.domain.marketdata.realtime.MarketDataMessage; -import net.jacobpeterson.alpaca.websocket.marketdata.message.MarketDataMessageType; - -import java.util.Arrays; -import java.util.HashMap; -import java.util.HashSet; -import java.util.Map; -import java.util.Set; - -/** - * {@link MarketDataListenerAdapter} is an adapter for {@link MarketDataListener}. - */ -public class MarketDataListenerAdapter implements MarketDataListener { - - private final Map> dataStreams = new HashMap<>(); - - /** - * Instantiates a new {@link MarketDataListenerAdapter}. - * - * @param symbol the symbol - * @param dataMessageTypes the {@link MarketDataMessageType}s - */ - public MarketDataListenerAdapter(String symbol, MarketDataMessageType... dataMessageTypes) { - if (dataMessageTypes != null && dataMessageTypes.length > 0) { - dataStreams.put(symbol, new HashSet<>(Arrays.asList(dataMessageTypes))); - } else { - dataStreams.put(symbol, new HashSet<>(Arrays.asList(MarketDataMessageType.values()))); - } - } - - /** - * Instantiates a new {@link MarketDataListenerAdapter}. - * - * @param symbols the symbols - * @param dataMessageTypes the {@link MarketDataMessageType}s - */ - public MarketDataListenerAdapter(Set symbols, MarketDataMessageType... dataMessageTypes) { - for (String symbol : symbols) { - if (dataMessageTypes != null && dataMessageTypes.length > 0) { - dataStreams.put(symbol, new HashSet<>(Arrays.asList(dataMessageTypes))); - } else { - dataStreams.put(symbol, new HashSet<>(Arrays.asList(MarketDataMessageType.values()))); - } - } - } - - /** - * Instantiates a new {@link MarketDataListenerAdapter}. - * - * @param dataStreams the {@link MarketDataMessageType} {@link Set} of symbols {@link Map} - */ - public MarketDataListenerAdapter(Map> dataStreams) { - this.dataStreams.putAll(dataStreams); - } - - @Override - public Map> getDataStreams() { - return dataStreams; - } - - @Override - public void onStreamUpdate(MarketDataMessageType streamMessageType, MarketDataMessage streamMessage) { } -} diff --git a/src/main/java/net/jacobpeterson/alpaca/websocket/streaming/StreamingListener.java b/src/main/java/net/jacobpeterson/alpaca/websocket/streaming/StreamingListener.java new file mode 100644 index 00000000..3ebf714d --- /dev/null +++ b/src/main/java/net/jacobpeterson/alpaca/websocket/streaming/StreamingListener.java @@ -0,0 +1,11 @@ +package net.jacobpeterson.alpaca.websocket.streaming; + +import net.jacobpeterson.alpaca.model.endpoint.streaming.StreamingMessage; +import net.jacobpeterson.alpaca.model.endpoint.streaming.enums.StreamingMessageType; +import net.jacobpeterson.alpaca.websocket.AlpacaWebsocketMessageListener; + +/** + * {@link StreamingListener} defines a listener interface for {@link StreamingWebsocket} messages. + */ +public interface StreamingListener extends AlpacaWebsocketMessageListener { +} diff --git a/src/main/java/net/jacobpeterson/alpaca/refactor/websocket/streaming/StreamingWebsocket.java b/src/main/java/net/jacobpeterson/alpaca/websocket/streaming/StreamingWebsocket.java similarity index 92% rename from src/main/java/net/jacobpeterson/alpaca/refactor/websocket/streaming/StreamingWebsocket.java rename to src/main/java/net/jacobpeterson/alpaca/websocket/streaming/StreamingWebsocket.java index 906a4e4d..011a42f9 100644 --- a/src/main/java/net/jacobpeterson/alpaca/refactor/websocket/streaming/StreamingWebsocket.java +++ b/src/main/java/net/jacobpeterson/alpaca/websocket/streaming/StreamingWebsocket.java @@ -1,4 +1,4 @@ -package net.jacobpeterson.alpaca.refactor.websocket.streaming; +package net.jacobpeterson.alpaca.websocket.streaming; import com.google.gson.*; import net.jacobpeterson.alpaca.model.endpoint.streaming.StreamingMessage; @@ -7,7 +7,8 @@ import net.jacobpeterson.alpaca.model.endpoint.streaming.enums.StreamingMessageType; import net.jacobpeterson.alpaca.model.endpoint.streaming.listening.ListeningMessage; import net.jacobpeterson.alpaca.model.endpoint.streaming.trade.TradeUpdateMessage; -import net.jacobpeterson.alpaca.refactor.websocket.AlpacaWebsocket; +import net.jacobpeterson.alpaca.util.gson.GsonUtil; +import net.jacobpeterson.alpaca.websocket.AlpacaWebsocket; import okhttp3.HttpUrl; import okhttp3.OkHttpClient; import okhttp3.WebSocket; @@ -22,7 +23,6 @@ import static com.google.common.base.Preconditions.checkArgument; import static com.google.common.base.Preconditions.checkNotNull; import static com.google.common.base.Preconditions.checkState; -import static net.jacobpeterson.alpaca.refactor.util.gson.GsonUtil.GSON; public class StreamingWebsocket extends AlpacaWebsocket implements StreamingWebsocketInterface { @@ -121,10 +121,11 @@ public void onMessage(@NotNull WebSocket webSocket, @NotNull ByteString byteStri checkState(streamElement instanceof JsonPrimitive, "Message must contain %s element! Received: %s", STREAM_ELEMENT_KEY, messageElement); - StreamingMessageType streamingMessageType = GSON.fromJson(streamElement, StreamingMessageType.class); + StreamingMessageType streamingMessageType = GsonUtil.GSON.fromJson(streamElement, StreamingMessageType.class); switch (streamingMessageType) { case AUTHORIZATION: - AuthorizationMessage authorizationMessage = GSON.fromJson(messageObject, AuthorizationMessage.class); + AuthorizationMessage authorizationMessage = + GsonUtil.GSON.fromJson(messageObject, AuthorizationMessage.class); callListeners(streamingMessageType, authorizationMessage); authenticated = isAuthorizationMessageSuccess(authorizationMessage); @@ -137,12 +138,12 @@ public void onMessage(@NotNull WebSocket webSocket, @NotNull ByteString byteStri } break; case LISTENING: - ListeningMessage listeningMessage = GSON.fromJson(messageObject, ListeningMessage.class); + ListeningMessage listeningMessage = GsonUtil.GSON.fromJson(messageObject, ListeningMessage.class); callListeners(streamingMessageType, listeningMessage); LOGGER.debug("{}", listeningMessage); break; case TRADE_UPDATES: - TradeUpdateMessage tradeUpdateMessage = GSON.fromJson(messageObject, TradeUpdateMessage.class); + TradeUpdateMessage tradeUpdateMessage = GsonUtil.GSON.fromJson(messageObject, TradeUpdateMessage.class); callListeners(streamingMessageType, tradeUpdateMessage); LOGGER.debug("{}", tradeUpdateMessage); break; diff --git a/src/main/java/net/jacobpeterson/alpaca/refactor/websocket/streaming/StreamingWebsocketInterface.java b/src/main/java/net/jacobpeterson/alpaca/websocket/streaming/StreamingWebsocketInterface.java similarity index 86% rename from src/main/java/net/jacobpeterson/alpaca/refactor/websocket/streaming/StreamingWebsocketInterface.java rename to src/main/java/net/jacobpeterson/alpaca/websocket/streaming/StreamingWebsocketInterface.java index 7ed7dd95..ce79374f 100644 --- a/src/main/java/net/jacobpeterson/alpaca/refactor/websocket/streaming/StreamingWebsocketInterface.java +++ b/src/main/java/net/jacobpeterson/alpaca/websocket/streaming/StreamingWebsocketInterface.java @@ -1,7 +1,7 @@ -package net.jacobpeterson.alpaca.refactor.websocket.streaming; +package net.jacobpeterson.alpaca.websocket.streaming; import net.jacobpeterson.alpaca.model.endpoint.streaming.enums.StreamingMessageType; -import net.jacobpeterson.alpaca.refactor.websocket.AlpacaWebsocketInterface; +import net.jacobpeterson.alpaca.websocket.AlpacaWebsocketInterface; /** * {@link StreamingWebsocketInterface} is an {@link AlpacaWebsocketInterface} for a {@link StreamingWebsocket}. diff --git a/src/test/java/net/jacobpeterson/alpaca/test/live/AlpacaAPITest.java b/src/test/java/net/jacobpeterson/alpaca/test/live/AlpacaAPITest.java index 3839695e..00fe9e96 100644 --- a/src/test/java/net/jacobpeterson/alpaca/test/live/AlpacaAPITest.java +++ b/src/test/java/net/jacobpeterson/alpaca/test/live/AlpacaAPITest.java @@ -12,12 +12,13 @@ import net.jacobpeterson.alpaca.model.endpoint.common.enums.SortDirection; import net.jacobpeterson.alpaca.model.endpoint.order.Order; import net.jacobpeterson.alpaca.model.endpoint.order.enums.CurrentOrderStatus; -import net.jacobpeterson.alpaca.refactor.AlpacaAPI; -import net.jacobpeterson.alpaca.refactor.rest.AlpacaClientException; -import net.jacobpeterson.alpaca.refactor.rest.endpoint.AccountConfigurationEndpoint; -import net.jacobpeterson.alpaca.refactor.rest.endpoint.AccountEndpoint; -import net.jacobpeterson.alpaca.refactor.rest.endpoint.ClockEndpoint; -import net.jacobpeterson.alpaca.refactor.rest.endpoint.OrdersEndpoint; +import net.jacobpeterson.alpaca.AlpacaAPI; +import net.jacobpeterson.alpaca.rest.AlpacaClientException; +import net.jacobpeterson.alpaca.rest.endpoint.AccountConfigurationEndpoint; +import net.jacobpeterson.alpaca.rest.endpoint.AccountEndpoint; +import net.jacobpeterson.alpaca.rest.endpoint.ClockEndpoint; +import net.jacobpeterson.alpaca.rest.endpoint.AccountActivitiesEndpoint; +import net.jacobpeterson.alpaca.rest.endpoint.OrdersEndpoint; import org.junit.jupiter.api.*; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -147,7 +148,7 @@ public void test_AccountEndpoint_get() throws AlpacaClientException, NumberForma } /** - * Tests @{@link net.jacobpeterson.alpaca.refactor.rest.endpoint.AccountActivitiesEndpoint#get(ZonedDateTime, + * Tests @{@link AccountActivitiesEndpoint#get(ZonedDateTime, * ZonedDateTime, ZonedDateTime, SortDirection, Integer, String, ActivityType...)} one {@link AccountActivity} * exists until now. * diff --git a/src/test/java/net/jacobpeterson/alpaca/test/mock/AlpacaAPITest.java b/src/test/java/net/jacobpeterson/alpaca/test/mock/AlpacaAPITest.java index 50d64025..4d143b56 100644 --- a/src/test/java/net/jacobpeterson/alpaca/test/mock/AlpacaAPITest.java +++ b/src/test/java/net/jacobpeterson/alpaca/test/mock/AlpacaAPITest.java @@ -2,7 +2,7 @@ import net.jacobpeterson.alpaca.model.properties.DataAPIType; import net.jacobpeterson.alpaca.model.properties.EndpointAPIType; -import net.jacobpeterson.alpaca.refactor.AlpacaAPI; +import net.jacobpeterson.alpaca.AlpacaAPI; import okhttp3.OkHttpClient; import org.junit.jupiter.api.Test; diff --git a/src/test/resources/alpaca.default.properties b/src/test/resources/alpaca.default.properties index 88ad6abe..58a3702f 100644 --- a/src/test/resources/alpaca.default.properties +++ b/src/test/resources/alpaca.default.properties @@ -1,5 +1,5 @@ key_id=ABCDEFGHIJKLM -secret=NOPQURSTUVWXYZ +secret_key=NOPQURSTUVWXYZ endpoint_api_type=paper data_api_type=iex user_agent=JavaRuntimeEnvironment/ OkHttp/4.9.1 From 1849593f307f22851957ae7b1d5f21cf787c73ca Mon Sep 17 00:00:00 2001 From: Jacob Peterson Date: Sat, 10 Jul 2021 20:49:13 -0600 Subject: [PATCH 125/271] Fix websocket bugs, add authorization Future methods --- .../net/jacobpeterson/alpaca/AlpacaAPI.java | 11 ++++ .../alpaca/rest/AlpacaClientException.java | 11 ---- .../alpaca/rest/endpoint/OrdersEndpoint.java | 26 +++++----- .../alpaca/websocket/AlpacaWebsocket.java | 22 +++++++- .../websocket/AlpacaWebsocketInterface.java | 23 ++++++++ .../streaming/StreamingWebsocket.java | 52 +++++++++++++++---- .../StreamingWebsocketInterface.java | 5 ++ 7 files changed, 114 insertions(+), 36 deletions(-) diff --git a/src/main/java/net/jacobpeterson/alpaca/AlpacaAPI.java b/src/main/java/net/jacobpeterson/alpaca/AlpacaAPI.java index a5521317..e9100f1f 100644 --- a/src/main/java/net/jacobpeterson/alpaca/AlpacaAPI.java +++ b/src/main/java/net/jacobpeterson/alpaca/AlpacaAPI.java @@ -6,6 +6,8 @@ import net.jacobpeterson.alpaca.properties.AlpacaProperties; import net.jacobpeterson.alpaca.rest.AlpacaClient; import net.jacobpeterson.alpaca.rest.endpoint.*; +import net.jacobpeterson.alpaca.websocket.streaming.StreamingWebsocket; +import net.jacobpeterson.alpaca.websocket.streaming.StreamingWebsocketInterface; import okhttp3.OkHttpClient; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -40,6 +42,7 @@ public class AlpacaAPI { private final AccountConfigurationEndpoint accountConfigurationEndpoint; private final AccountActivitiesEndpoint accountActivitiesEndpoint; private final PortfolioHistoryEndpoint portfolioHistoryEndpoint; + private final StreamingWebsocket streamingWebsocket; /** * Instantiates a new {@link AlpacaAPI} using properties specified in alpaca.properties file (or their @@ -148,6 +151,7 @@ public AlpacaAPI(OkHttpClient okHttpClient, String keyID, String secretKey, Stri accountConfigurationEndpoint = new AccountConfigurationEndpoint(brokerClient); accountActivitiesEndpoint = new AccountActivitiesEndpoint(brokerClient); portfolioHistoryEndpoint = new PortfolioHistoryEndpoint(brokerClient); + streamingWebsocket = new StreamingWebsocket(okHttpClient, brokerHostSubdomain, keyID, secretKey, oAuthToken); } /** @@ -227,6 +231,13 @@ public PortfolioHistoryEndpoint portfolioHistory() { return portfolioHistoryEndpoint; } + /** + * @return the {@link StreamingWebsocketInterface} + */ + public StreamingWebsocketInterface streaming() { + return streamingWebsocket; + } + /** * Gets {@link #okHttpClient}. * diff --git a/src/main/java/net/jacobpeterson/alpaca/rest/AlpacaClientException.java b/src/main/java/net/jacobpeterson/alpaca/rest/AlpacaClientException.java index 0018b2cc..e78e58ef 100644 --- a/src/main/java/net/jacobpeterson/alpaca/rest/AlpacaClientException.java +++ b/src/main/java/net/jacobpeterson/alpaca/rest/AlpacaClientException.java @@ -165,15 +165,4 @@ public Integer getAPIResponseCode() { public String getAPIResponseMessage() { return apiResponseMessage; } - - @Override - public String toString() { - return "AlpacaClientException{" + - "response=" + response + - ", requestStatusCode=" + requestStatusCode + - ", requestStatusMessage='" + requestStatusMessage + '\'' + - ", apiResponseCode=" + apiResponseCode + - ", apiResponseMessage='" + apiResponseMessage + '\'' + - "}"; - } } diff --git a/src/main/java/net/jacobpeterson/alpaca/rest/endpoint/OrdersEndpoint.java b/src/main/java/net/jacobpeterson/alpaca/rest/endpoint/OrdersEndpoint.java index e9bd6e5f..ff29de7a 100644 --- a/src/main/java/net/jacobpeterson/alpaca/rest/endpoint/OrdersEndpoint.java +++ b/src/main/java/net/jacobpeterson/alpaca/rest/endpoint/OrdersEndpoint.java @@ -141,7 +141,7 @@ public Order requestOrder(String symbol, Double quantity, Double notional, Order JSONBodyBuilder jsonBodyBuilder = new JSONBodyBuilder(); jsonBodyBuilder.appendJSONBodyProperty("symbol", symbol); - jsonBodyBuilder.appendJSONBodyProperty("qty", side.toString()); + jsonBodyBuilder.appendJSONBodyProperty("side", side.toString()); jsonBodyBuilder.appendJSONBodyProperty("type", type.toString()); jsonBodyBuilder.appendJSONBodyProperty("time_in_force", timeInForce.toString()); @@ -210,7 +210,7 @@ public Order requestOrder(String symbol, Double quantity, Double notional, Order * most likely method of filling an order. Market orders fill nearly instantaneously. This method calls {@link * #requestOrder(String, Double, Double, OrderSide, OrderType, OrderTimeInForce, Double, Double, Double, Double, * Boolean, String, OrderClass, Double, Double, Double)} with {@link OrderType#MARKET} so check the Javadoc for that - * method for the parameter details. + * method for parameter details. * * @return the requested {@link Order} * @@ -227,7 +227,7 @@ public Order requestMarketOrder(String symbol, Integer quantity, OrderSide side, * most likely method of filling an order. Market orders fill nearly instantaneously. This method calls {@link * #requestOrder(String, Double, Double, OrderSide, OrderType, OrderTimeInForce, Double, Double, Double, Double, * Boolean, String, OrderClass, Double, Double, Double)} with {@link OrderType#MARKET} and {@link - * OrderTimeInForce#DAY} with a fractional quantity so check the Javadoc for that method for the parameter details. + * OrderTimeInForce#DAY} with a fractional quantity so check the Javadoc for that method for parameter details. * * @return the requested {@link Order} * @@ -283,7 +283,7 @@ public Order requestLimitOrder(String symbol, Integer quantity, OrderSide side, * orders with a limit price that is 4% higher than a stop price < $50 (or 2.5% higher than a stop price >= * $50). Sell stop orders are not converted into stop limit orders. This method calls {@link #requestOrder(String, * Double, Double, OrderSide, OrderType, OrderTimeInForce, Double, Double, Double, Double, Boolean, String, - * OrderClass, Double, Double, Double)} with {@link OrderType#STOP} so check the Javadoc for that method for the + * OrderClass, Double, Double, Double)} with {@link OrderType#STOP} so check the Javadoc for that method for * parameter details. * * @return the requested {@link Order} @@ -303,7 +303,7 @@ public Order requestStopOrder(String symbol, Integer quantity, OrderSide side, O * becomes a limit order to buy or sell at the limit price or better. This method calls {@link #requestOrder(String, * Double, Double, OrderSide, OrderType, OrderTimeInForce, Double, Double, Double, Double, Boolean, String, * OrderClass, Double, Double, Double)} with {@link OrderType#STOP_LIMIT} so check the Javadoc for that method for - * the parameter details. + * parameter details. * * @return the requested {@link Order} * @@ -320,7 +320,7 @@ public Order requestStopLimitOrder(String symbol, Integer quantity, OrderSide si * common use case of an OTOCO (One Triggers OCO {One Cancels Other}) order. This method calls {@link * #requestOrder(String, Double, Double, OrderSide, OrderType, OrderTimeInForce, Double, Double, Double, Double, * Boolean, String, OrderClass, Double, Double, Double)} with {@link OrderType#MARKET} and with parameters for a - * bracket order so check the Javadoc for that method for the parameter details. + * bracket order so check the Javadoc for that method for parameter details. * * @return the requested {@link Order} * @@ -339,7 +339,7 @@ public Order requestMarketBracketOrder(String symbol, Integer quantity, OrderSid * common use case of an OTOCO (One Triggers OCO {One Cancels Other}) order. This method calls {@link * #requestOrder(String, Double, Double, OrderSide, OrderType, OrderTimeInForce, Double, Double, Double, Double, * Boolean, String, OrderClass, Double, Double, Double)} with {@link OrderType#LIMIT} and with parameters for a - * bracket order so check the Javadoc for that method for the parameter details. + * bracket order so check the Javadoc for that method for parameter details. * * @return the requested {@link Order} * @@ -379,7 +379,7 @@ public Order requestOCOOrder(String symbol, Integer quantity, OrderSide side, * addition to the entry order. This method calls {@link #requestOrder(String, Double, Double, OrderSide, OrderType, * OrderTimeInForce, Double, Double, Double, Double, Boolean, String, OrderClass, Double, Double, Double)} with * {@link OrderType#LIMIT} and with parameters for a {@link OrderClass#ONE_CANCELS_OTHER} so check the Javadoc for - * that method for the parameter details. + * that method for parameter details. * * @return the requested {@link Order} * @@ -398,7 +398,7 @@ public Order requestOTOMarketOrder(String symbol, Integer quantity, OrderSide si * addition to the entry order. This method calls {@link #requestOrder(String, Double, Double, OrderSide, OrderType, * OrderTimeInForce, Double, Double, Double, Double, Boolean, String, OrderClass, Double, Double, Double)} with * {@link OrderType#LIMIT} and with parameters for a {@link OrderClass#ONE_CANCELS_OTHER} so check the Javadoc for - * that method for the parameter details. + * that method for parameter details. * * @return the requested {@link Order} * @@ -417,7 +417,7 @@ public Order requestOTOLimitOrder(String symbol, Integer quantity, OrderSide sid * addition to the entry order. This method calls {@link #requestOrder(String, Double, Double, OrderSide, OrderType, * OrderTimeInForce, Double, Double, Double, Double, Boolean, String, OrderClass, Double, Double, Double)} with * {@link OrderType#STOP} and with parameters for a {@link OrderClass#ONE_CANCELS_OTHER} so check the Javadoc for - * that method for the parameter details. + * that method for parameter details. * * @return the requested {@link Order} * @@ -436,7 +436,7 @@ public Order requestOTOStopOrder(String symbol, Integer quantity, OrderSide side * addition to the entry order. This method calls {@link #requestOrder(String, Double, Double, OrderSide, OrderType, * OrderTimeInForce, Double, Double, Double, Double, Boolean, String, OrderClass, Double, Double, Double)} with * {@link OrderType#STOP_LIMIT} and with parameters for a {@link OrderClass#ONE_CANCELS_OTHER} so check the Javadoc - * for that method for the parameter details. + * for that method for parameter details. * * @return the requested {@link Order} * @@ -456,7 +456,7 @@ public Order requestOTOStopLimitOrder(String symbol, Integer quantity, OrderSide * the stock price movement. This method calls {@link #requestOrder(String, Double, Double, OrderSide, OrderType, * OrderTimeInForce, Double, Double, Double, Double, Boolean, String, OrderClass, Double, Double, Double)} with * {@link OrderType#TRAILING_STOP} and with parameters for a {@link OrderType#TRAILING_STOP} so check the Javadoc - * for that method for the parameter details. + * for that method for parameter details. * * @return the requested {@link Order} * @@ -473,7 +473,7 @@ public Order requestTrailingStopPriceOrder(String symbol, Integer quantity, Orde * the stock price movement. This method calls {@link #requestOrder(String, Double, Double, OrderSide, OrderType, * OrderTimeInForce, Double, Double, Double, Double, Boolean, String, OrderClass, Double, Double, Double)} with * {@link OrderType#TRAILING_STOP} and with parameters for a {@link OrderType#TRAILING_STOP} so check the Javadoc - * for that method for the parameter details. + * for that method for parameter details. * * @return the requested {@link Order} * diff --git a/src/main/java/net/jacobpeterson/alpaca/websocket/AlpacaWebsocket.java b/src/main/java/net/jacobpeterson/alpaca/websocket/AlpacaWebsocket.java index 128bd2b8..fb1a9d56 100644 --- a/src/main/java/net/jacobpeterson/alpaca/websocket/AlpacaWebsocket.java +++ b/src/main/java/net/jacobpeterson/alpaca/websocket/AlpacaWebsocket.java @@ -7,7 +7,9 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import java.util.concurrent.CompletableFuture; import java.util.concurrent.ForkJoinPool; +import java.util.concurrent.Future; import static com.google.common.base.Preconditions.checkArgument; import static com.google.common.base.Preconditions.checkNotNull; @@ -53,6 +55,7 @@ public abstract class AlpacaWebsocket extends WebSocketListener implements Alpac protected WebSocket websocket; protected boolean connected; protected boolean authenticated; + protected CompletableFuture authenticationMessageFuture; protected boolean intentionalClose; protected int reconnectAttempts; protected boolean automaticallyReconnect; @@ -143,7 +146,8 @@ public void onClosed(@NotNull WebSocket webSocket, int code, @NotNull String rea } if (intentionalClose) { - LOGGER.info("{} websocket closed. Code: {}, Reason: {}", websocketName, code, reason); + LOGGER.info("{} websocket closed.", websocketName); + LOGGER.debug("Close code: {}, Reason: {}", code, reason); cleanupState(); } else { LOGGER.error("{} websocket closed unintentionally! Code: {}, Reason: {}", websocketName, code, reason); @@ -191,7 +195,7 @@ private void handleReconnectionAttempt() { /** * Cleans up this instances state variables. */ - private void cleanupState() { + protected void cleanupState() { websocket = null; connected = false; authenticated = false; @@ -214,6 +218,20 @@ private void cleanupState() { */ protected abstract void sendAuthenticationMessage(); + /** + * Gets {@link #authenticationMessageFuture}. + * + * @return a {@link CompletableFuture} {@link Boolean} + */ + @Override + public Future getAuthorizationFuture() { + if (authenticationMessageFuture == null || authenticationMessageFuture.isDone()) { + authenticationMessageFuture = new CompletableFuture<>(); + } + + return authenticationMessageFuture; + } + /** * Gets {@link #websocketStateListener}. * diff --git a/src/main/java/net/jacobpeterson/alpaca/websocket/AlpacaWebsocketInterface.java b/src/main/java/net/jacobpeterson/alpaca/websocket/AlpacaWebsocketInterface.java index 4ff1f41c..7e1b5e87 100644 --- a/src/main/java/net/jacobpeterson/alpaca/websocket/AlpacaWebsocketInterface.java +++ b/src/main/java/net/jacobpeterson/alpaca/websocket/AlpacaWebsocketInterface.java @@ -1,5 +1,8 @@ package net.jacobpeterson.alpaca.websocket; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.Future; + /** * {@link AlpacaWebsocketInterface} defines an interface for Alpaca websockets. */ @@ -29,6 +32,26 @@ public interface AlpacaWebsocketInterface { */ boolean isAuthenticated(); + /** + * Gets a {@link Boolean} {@link Future} that completes when the next authentication message that is received + * indicates a successful websocket authorization or not. + * + * @return a {@link Boolean} {@link Future} + */ + Future getAuthorizationFuture(); + + /** + * Waits for {@link #getAuthorizationFuture()} to complete and returns its value. + * + * @return a boolean + */ + default boolean waitForAuthorization() { + try { + return getAuthorizationFuture().get(); + } catch (InterruptedException | ExecutionException ignored) {} + return false; + } + /** * True if {@link #isConnected()} and {@link #isAuthenticated()}. * diff --git a/src/main/java/net/jacobpeterson/alpaca/websocket/streaming/StreamingWebsocket.java b/src/main/java/net/jacobpeterson/alpaca/websocket/streaming/StreamingWebsocket.java index 011a42f9..2e9f248b 100644 --- a/src/main/java/net/jacobpeterson/alpaca/websocket/streaming/StreamingWebsocket.java +++ b/src/main/java/net/jacobpeterson/alpaca/websocket/streaming/StreamingWebsocket.java @@ -18,6 +18,8 @@ import org.slf4j.LoggerFactory; import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; import java.util.List; import static com.google.common.base.Preconditions.checkArgument; @@ -28,6 +30,8 @@ public class StreamingWebsocket extends AlpacaWebsocket implements StreamingWebs private static final Logger LOGGER = LoggerFactory.getLogger(StreamingWebsocket.class); private static final String STREAM_ELEMENT_KEY = "stream"; + private static final List SUBSCRIBABLE_STREAMING_MESSAGE_TYPES = Collections.singletonList( + StreamingMessageType.TRADE_UPDATES); /** * Creates a {@link HttpUrl} for {@link StreamingWebsocket} for the given alpacaSubdomain. @@ -38,7 +42,7 @@ public class StreamingWebsocket extends AlpacaWebsocket implements StreamingWebs */ private static HttpUrl createWebsocketURL(String alpacaSubdomain) { return new HttpUrl.Builder() - .scheme("wss") + .scheme("https") // HttpUrl.Builder doesn't recognize "wss" scheme, so "https" works fine .host(alpacaSubdomain + ".alpaca.markets") .addPathSegment("stream") .build(); @@ -78,6 +82,15 @@ protected void onReconnection() { sendAuthenticationMessage(); } + @Override + protected void cleanupState() { + super.cleanupState(); + + listeners.clear(); + listenersToAdd.clear(); + listenersToRemove.clear(); + } + @Override protected void sendAuthenticationMessage() { /* Authentication message format: @@ -126,26 +139,31 @@ public void onMessage(@NotNull WebSocket webSocket, @NotNull ByteString byteStri case AUTHORIZATION: AuthorizationMessage authorizationMessage = GsonUtil.GSON.fromJson(messageObject, AuthorizationMessage.class); - callListeners(streamingMessageType, authorizationMessage); authenticated = isAuthorizationMessageSuccess(authorizationMessage); + + if (authenticationMessageFuture != null) { + authenticationMessageFuture.complete(authenticated); + } + if (!authenticated) { - LOGGER.error("{} websocket not authenticated! Received: {}. Disconnecting...", - websocketName, authorizationMessage); - disconnect(); + LOGGER.error("{} websocket not authenticated! Received: {}.", websocketName, authorizationMessage); } else { LOGGER.info("{} websocket authenticated.", websocketName); + LOGGER.debug("{}", authorizationMessage); } + + callListeners(streamingMessageType, authorizationMessage); break; case LISTENING: ListeningMessage listeningMessage = GsonUtil.GSON.fromJson(messageObject, ListeningMessage.class); - callListeners(streamingMessageType, listeningMessage); LOGGER.debug("{}", listeningMessage); + callListeners(streamingMessageType, listeningMessage); break; case TRADE_UPDATES: TradeUpdateMessage tradeUpdateMessage = GsonUtil.GSON.fromJson(messageObject, TradeUpdateMessage.class); - callListeners(streamingMessageType, tradeUpdateMessage); LOGGER.debug("{}", tradeUpdateMessage); + callListeners(streamingMessageType, tradeUpdateMessage); break; default: throw new UnsupportedOperationException(); @@ -191,6 +209,7 @@ private void callListeners(StreamingMessageType streamingMessageType, StreamingM @Override public void subscriptions(StreamingMessageType... streamingMessageTypes) { + checkState(isConnected(), "Websocket must be connected before requesting subscriptions!"); checkNotNull(streamingMessageTypes); checkArgument(streamingMessageTypes.length > 0, "'streamingMessageTypes' must be have at least one element."); @@ -198,7 +217,7 @@ public void subscriptions(StreamingMessageType... streamingMessageTypes) { // { // "action": "listen", // "data": { - // "streams": ["account_updates", "trade_updates"] + // "streams": ["trade_updates"] // } // } @@ -206,8 +225,12 @@ public void subscriptions(StreamingMessageType... streamingMessageTypes) { requestObject.addProperty("action", "listen"); JsonArray streamsArray = new JsonArray(); - for (StreamingMessageType streamingMessageType : streamingMessageTypes) { - streamsArray.add(streamingMessageType.toString()); + Arrays.stream(streamingMessageTypes) + .filter(SUBSCRIBABLE_STREAMING_MESSAGE_TYPES::contains) + .forEach((type) -> streamsArray.add(type.toString())); + + if (streamsArray.isEmpty()) { + return; } JsonObject dataObject = new JsonObject(); @@ -224,6 +247,11 @@ public void addListener(StreamingListener streamingListener) { synchronized (listenersLock) { listenersToAdd.add(streamingListener); } + + if (!isConnected()) { + connect(); + waitForAuthorization(); + } } @Override @@ -231,5 +259,9 @@ public void removeListener(StreamingListener streamingListener) { synchronized (listenersLock) { listenersToRemove.add(streamingListener); } + + if (listeners.size() == 1 && listeners.contains(streamingListener)) { + disconnect(); + } } } diff --git a/src/main/java/net/jacobpeterson/alpaca/websocket/streaming/StreamingWebsocketInterface.java b/src/main/java/net/jacobpeterson/alpaca/websocket/streaming/StreamingWebsocketInterface.java index ce79374f..e71bda03 100644 --- a/src/main/java/net/jacobpeterson/alpaca/websocket/streaming/StreamingWebsocketInterface.java +++ b/src/main/java/net/jacobpeterson/alpaca/websocket/streaming/StreamingWebsocketInterface.java @@ -17,6 +17,9 @@ public interface StreamingWebsocketInterface extends AlpacaWebsocketInterface { /** * Adds a {@link StreamingListener}. + *
+ * Note that this will call {@link StreamingWebsocket#connect()} if the {@link StreamingWebsocket} is not connected + * already. * * @param streamingListener the {@link StreamingListener} */ @@ -24,6 +27,8 @@ public interface StreamingWebsocketInterface extends AlpacaWebsocketInterface { /** * Remove a {@link StreamingListener}. + *
+ * Note that this will call {@link StreamingWebsocket#disconnect()} if this is the last listener being removed. * * @param streamingListener the {@link StreamingListener} */ From 0341303e6028731f2d23f88e9faf14e61eea0cb7 Mon Sep 17 00:00:00 2001 From: Jacob Peterson Date: Sat, 10 Jul 2021 21:05:11 -0600 Subject: [PATCH 126/271] Fix Unit test and add clarity to constructor checkArgument --- .../net/jacobpeterson/alpaca/rest/AlpacaClient.java | 3 ++- .../alpaca/websocket/AlpacaWebsocket.java | 8 ++------ .../jacobpeterson/alpaca/test/mock/AlpacaAPITest.java | 11 +++++++---- 3 files changed, 11 insertions(+), 11 deletions(-) diff --git a/src/main/java/net/jacobpeterson/alpaca/rest/AlpacaClient.java b/src/main/java/net/jacobpeterson/alpaca/rest/AlpacaClient.java index bd7cebfb..9e328e60 100644 --- a/src/main/java/net/jacobpeterson/alpaca/rest/AlpacaClient.java +++ b/src/main/java/net/jacobpeterson/alpaca/rest/AlpacaClient.java @@ -66,7 +66,8 @@ protected AlpacaClient(OkHttpClient okHttpClient, String keyID, String secretKey String alpacaSubdomain, String versionPathSegment) { checkNotNull(okHttpClient); checkArgument((keyID != null && secretKey != null) ^ oAuthToken != null, - "You must specify a (KeyID and Secret Key) or an OAuthToken!"); + "You must specify a (KeyID (%s) and Secret Key (%s)) or an OAuthToken (%s)!", + keyID, secretKey, oAuthToken); checkNotNull(alpacaSubdomain); checkNotNull(versionPathSegment); diff --git a/src/main/java/net/jacobpeterson/alpaca/websocket/AlpacaWebsocket.java b/src/main/java/net/jacobpeterson/alpaca/websocket/AlpacaWebsocket.java index fb1a9d56..e9335821 100644 --- a/src/main/java/net/jacobpeterson/alpaca/websocket/AlpacaWebsocket.java +++ b/src/main/java/net/jacobpeterson/alpaca/websocket/AlpacaWebsocket.java @@ -76,7 +76,8 @@ public AlpacaWebsocket(OkHttpClient okHttpClient, HttpUrl websocketURL, String w checkNotNull(websocketURL); checkNotNull(websocketName); checkArgument((keyID != null && secretKey != null) ^ oAuthToken != null, - "You must specify a (KeyID and Secret Key) or an OAuthToken!"); + "You must specify a (KeyID (%s) and Secret Key (%s)) or an OAuthToken (%s)!", + keyID, secretKey, oAuthToken); this.okHttpClient = okHttpClient; this.websocketURL = websocketURL; @@ -218,11 +219,6 @@ protected void cleanupState() { */ protected abstract void sendAuthenticationMessage(); - /** - * Gets {@link #authenticationMessageFuture}. - * - * @return a {@link CompletableFuture} {@link Boolean} - */ @Override public Future getAuthorizationFuture() { if (authenticationMessageFuture == null || authenticationMessageFuture.isDone()) { diff --git a/src/test/java/net/jacobpeterson/alpaca/test/mock/AlpacaAPITest.java b/src/test/java/net/jacobpeterson/alpaca/test/mock/AlpacaAPITest.java index 4d143b56..d5f4f320 100644 --- a/src/test/java/net/jacobpeterson/alpaca/test/mock/AlpacaAPITest.java +++ b/src/test/java/net/jacobpeterson/alpaca/test/mock/AlpacaAPITest.java @@ -1,8 +1,8 @@ package net.jacobpeterson.alpaca.test.mock; +import net.jacobpeterson.alpaca.AlpacaAPI; import net.jacobpeterson.alpaca.model.properties.DataAPIType; import net.jacobpeterson.alpaca.model.properties.EndpointAPIType; -import net.jacobpeterson.alpaca.AlpacaAPI; import okhttp3.OkHttpClient; import org.junit.jupiter.api.Test; @@ -60,12 +60,15 @@ public void testAlpacaAPIConstructor_oAuthToken() { * Tests {@link AlpacaAPI#AlpacaAPI(OkHttpClient, String, String, String, EndpointAPIType, DataAPIType)}. */ @Test - public void testAlpacaAPIConstructor_keyID_secret_oAuthToken_endpointAPIType_dataAPIType() { + public void testAlpacaAPIConstructor_okHttpClient_keyID_secret_oAuthToken_endpointAPIType_dataAPIType() { + OkHttpClient okHttpClient = new OkHttpClient(); String keyID = "ABCDEFGHIJKLM"; String secret = "NOPQURSTUVWXYZ"; String oAuthToken = "ABCDEFGHIJKLMNOPQURSTUVWXYZ"; - new AlpacaAPI(null, keyID, secret, oAuthToken, EndpointAPIType.PAPER, DataAPIType.IEX); - new AlpacaAPI(null, keyID, secret, oAuthToken, EndpointAPIType.LIVE, DataAPIType.SIP); + new AlpacaAPI(okHttpClient, null, null, oAuthToken, EndpointAPIType.PAPER, DataAPIType.IEX); + new AlpacaAPI(okHttpClient, keyID, secret, null, EndpointAPIType.LIVE, DataAPIType.SIP); + new AlpacaAPI(okHttpClient, null, null, oAuthToken, EndpointAPIType.PAPER, DataAPIType.IEX); + new AlpacaAPI(okHttpClient, keyID, secret, null, EndpointAPIType.LIVE, DataAPIType.SIP); } } From 049a7ab02a697495e3f06572c199656cc4df8e80 Mon Sep 17 00:00:00 2001 From: Jacob Peterson Date: Sun, 11 Jul 2021 01:08:53 -0600 Subject: [PATCH 127/271] Generify Websocket interfaces and classes for better abstraction --- README.md | 1 + .../portfolio_history_data_point.json | 6 +- .../alpaca/rest/AlpacaClient.java | 4 +- .../endpoint/AccountActivitiesEndpoint.java | 6 +- .../AccountConfigurationEndpoint.java | 4 +- .../endpoint/PortfolioHistoryEndpoint.java | 4 +- .../alpaca/util/format/FormatUtil.java | 5 +- .../alpaca/websocket/AlpacaWebsocket.java | 69 +++++++++++- .../websocket/AlpacaWebsocketInterface.java | 24 ++++- .../marketdata/MarketDataWebsocket.java | 91 +++++++++++++++- .../MarketDataWebsocketInterface.java | 32 +++++- .../streaming/StreamingWebsocket.java | 101 +++++------------- .../StreamingWebsocketInterface.java | 21 +--- 13 files changed, 258 insertions(+), 110 deletions(-) diff --git a/README.md b/README.md index a1880534..f2e1cbb2 100644 --- a/README.md +++ b/README.md @@ -544,6 +544,7 @@ Note that the live tests will modify your account minimally. It's meant to test # TODO - Unit Testing (both live and mocked) +- Use [TA4j](https://github.com/ta4j/ta4j) `Num` interface instead of `Double` for number variables so that users can use either `Double` or `BigDecimal` for performance or precision in price data. - Add [TimeSeriesDataStore](https://github.com/Petersoj/TimeSeriesDataStore) using Alpaca Data API Contributions are welcome! diff --git a/schema_json/endpoint/portfolio_history/portfolio_history_data_point.json b/schema_json/endpoint/portfolio_history/portfolio_history_data_point.json index d9b80bc3..8cafa239 100644 --- a/schema_json/endpoint/portfolio_history/portfolio_history_data_point.json +++ b/schema_json/endpoint/portfolio_history/portfolio_history_data_point.json @@ -3,12 +3,12 @@ "title": "See Portfolio History.", "properties": { "timestamp": { - "existingJavaType": "java.lang.Long", - "title": "Time of each data element." + "existingJavaType": "java.time.ZonedDateTime", + "title": "{@link java.time.ZonedDateTime} of this data element." }, "equity": { "existingJavaType": "java.lang.Double", - "title": "Equity value of the account in dollar amount as of the end of each time window." + "title": "Equity value of the account in dollar amount as of the end of this time window." }, "profit_loss": { "existingJavaType": "java.lang.Double", diff --git a/src/main/java/net/jacobpeterson/alpaca/rest/AlpacaClient.java b/src/main/java/net/jacobpeterson/alpaca/rest/AlpacaClient.java index 9e328e60..7c69ec34 100644 --- a/src/main/java/net/jacobpeterson/alpaca/rest/AlpacaClient.java +++ b/src/main/java/net/jacobpeterson/alpaca/rest/AlpacaClient.java @@ -4,7 +4,6 @@ import com.google.gson.JsonElement; import com.google.gson.JsonParser; import net.jacobpeterson.alpaca.properties.AlpacaProperties; -import net.jacobpeterson.alpaca.util.gson.GsonUtil; import okhttp3.*; import java.io.IOException; @@ -15,6 +14,7 @@ import static com.google.common.base.Preconditions.checkArgument; import static com.google.common.base.Preconditions.checkNotNull; import static com.google.common.base.Preconditions.checkState; +import static net.jacobpeterson.alpaca.util.gson.GsonUtil.GSON; /** * {@link AlpacaClient} represents an HTTP RestAPI client for Alpaca. @@ -133,7 +133,7 @@ public T requestObject(Request request, Type type) throws AlpacaClientExcept */ public T requestObject(Request request, Predicate isSuccessCode, Type type) throws AlpacaClientException { - return GsonUtil.GSON.fromJson(requestJSON(request, isSuccessCode), type); + return GSON.fromJson(requestJSON(request, isSuccessCode), type); } /** diff --git a/src/main/java/net/jacobpeterson/alpaca/rest/endpoint/AccountActivitiesEndpoint.java b/src/main/java/net/jacobpeterson/alpaca/rest/endpoint/AccountActivitiesEndpoint.java index c12d0cc1..8db4c3de 100644 --- a/src/main/java/net/jacobpeterson/alpaca/rest/endpoint/AccountActivitiesEndpoint.java +++ b/src/main/java/net/jacobpeterson/alpaca/rest/endpoint/AccountActivitiesEndpoint.java @@ -10,7 +10,6 @@ import net.jacobpeterson.alpaca.model.endpoint.common.enums.SortDirection; import net.jacobpeterson.alpaca.rest.AlpacaClient; import net.jacobpeterson.alpaca.rest.AlpacaClientException; -import net.jacobpeterson.alpaca.util.gson.GsonUtil; import okhttp3.HttpUrl; import okhttp3.Request; import org.slf4j.Logger; @@ -24,6 +23,7 @@ import java.util.stream.Collectors; import static com.google.common.base.Preconditions.checkState; +import static net.jacobpeterson.alpaca.util.gson.GsonUtil.GSON; /** * {@link AbstractEndpoint} for @@ -124,9 +124,9 @@ public List get(ZonedDateTime date, ZonedDateTime until, ZonedD // A 'TradeActivity' always has 'activity_type' field as 'FILL' if (activityType.equals(ActivityType.FILL.toString())) { - accountActivity = GsonUtil.GSON.fromJson(responseArrayObject, TradeActivity.class); + accountActivity = GSON.fromJson(responseArrayObject, TradeActivity.class); } else { - accountActivity = GsonUtil.GSON.fromJson(responseArrayObject, NonTradeActivity.class); + accountActivity = GSON.fromJson(responseArrayObject, NonTradeActivity.class); } if (accountActivity.getActivityType() == null) { diff --git a/src/main/java/net/jacobpeterson/alpaca/rest/endpoint/AccountConfigurationEndpoint.java b/src/main/java/net/jacobpeterson/alpaca/rest/endpoint/AccountConfigurationEndpoint.java index 63b5bd13..b42751ed 100644 --- a/src/main/java/net/jacobpeterson/alpaca/rest/endpoint/AccountConfigurationEndpoint.java +++ b/src/main/java/net/jacobpeterson/alpaca/rest/endpoint/AccountConfigurationEndpoint.java @@ -3,12 +3,12 @@ import net.jacobpeterson.alpaca.model.endpoint.accountconfiguration.AccountConfiguration; import net.jacobpeterson.alpaca.rest.AlpacaClient; import net.jacobpeterson.alpaca.rest.AlpacaClientException; -import net.jacobpeterson.alpaca.util.gson.GsonUtil; import net.jacobpeterson.alpaca.util.okhttp.JSONBodyBuilder; import okhttp3.HttpUrl; import okhttp3.Request; import static com.google.common.base.Preconditions.checkNotNull; +import static net.jacobpeterson.alpaca.util.gson.GsonUtil.GSON; /** * {@link AbstractEndpoint} for @@ -56,7 +56,7 @@ public AccountConfiguration set(AccountConfiguration accountConfiguration) throw HttpUrl.Builder urlBuilder = alpacaClient.urlBuilder() .addPathSegments(endpointPathSegment); Request request = alpacaClient.requestBuilder(urlBuilder.build()) - .patch(new JSONBodyBuilder(GsonUtil.GSON.toJson(accountConfiguration)).build()) + .patch(new JSONBodyBuilder(GSON.toJson(accountConfiguration)).build()) .build(); return alpacaClient.requestObject(request, AccountConfiguration.class); } diff --git a/src/main/java/net/jacobpeterson/alpaca/rest/endpoint/PortfolioHistoryEndpoint.java b/src/main/java/net/jacobpeterson/alpaca/rest/endpoint/PortfolioHistoryEndpoint.java index 8b10a790..c9996813 100644 --- a/src/main/java/net/jacobpeterson/alpaca/rest/endpoint/PortfolioHistoryEndpoint.java +++ b/src/main/java/net/jacobpeterson/alpaca/rest/endpoint/PortfolioHistoryEndpoint.java @@ -7,9 +7,11 @@ import net.jacobpeterson.alpaca.model.endpoint.portfoliohistory.enums.PortfolioTimeFrame; import net.jacobpeterson.alpaca.rest.AlpacaClient; import net.jacobpeterson.alpaca.rest.AlpacaClientException; +import net.jacobpeterson.alpaca.util.format.FormatUtil; import okhttp3.HttpUrl; import okhttp3.Request; +import java.time.Instant; import java.time.LocalDate; import java.time.format.DateTimeFormatter; import java.util.ArrayList; @@ -86,7 +88,7 @@ public PortfolioHistory get(Integer periodLength, PortfolioPeriodUnit periodUnit ArrayList dataPoints = new ArrayList<>(); for (int index = 0; index < response.getTimestamp().size(); index++) { dataPoints.add(new PortfolioHistoryDataPoint( - response.getTimestamp().get(index), + Instant.ofEpochSecond(response.getTimestamp().get(index)).atZone(FormatUtil.NEW_YORK_ZONED_ID), response.getEquity().get(index), response.getProfitLoss().get(index), response.getProfitLossPct().get(index))); diff --git a/src/main/java/net/jacobpeterson/alpaca/util/format/FormatUtil.java b/src/main/java/net/jacobpeterson/alpaca/util/format/FormatUtil.java index 39567d24..0b76f3b5 100644 --- a/src/main/java/net/jacobpeterson/alpaca/util/format/FormatUtil.java +++ b/src/main/java/net/jacobpeterson/alpaca/util/format/FormatUtil.java @@ -14,13 +14,16 @@ */ public class FormatUtil { + /** The {@link ZoneId} for America/New_York. */ + public static final ZoneId NEW_YORK_ZONED_ID = ZoneId.of("America/New_York"); + // Alpaca uses the following rounding mechanics with respect to buy orders: (1) rounded down to two decimal // places if the last trade price is over $1.00; otherwise, rounded down to four decimal places, hence the '#' in // the 3rd and 4th least significant decimal digits. private static final NumberFormat CURRENCY_FORMATTER = new DecimalFormat("#0.00##", DecimalFormatSymbols.getInstance(Locale.US)); private static final DateTimeFormatter RFC_3339_FORMATTER = - DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss'Z'").withZone(ZoneId.of("America/New_York")); + DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss'Z'").withZone(NEW_YORK_ZONED_ID); /** * Formats an arbitrary number to a currency format. e.g. $123.45 diff --git a/src/main/java/net/jacobpeterson/alpaca/websocket/AlpacaWebsocket.java b/src/main/java/net/jacobpeterson/alpaca/websocket/AlpacaWebsocket.java index e9335821..2a819179 100644 --- a/src/main/java/net/jacobpeterson/alpaca/websocket/AlpacaWebsocket.java +++ b/src/main/java/net/jacobpeterson/alpaca/websocket/AlpacaWebsocket.java @@ -7,6 +7,8 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import java.util.ArrayList; +import java.util.List; import java.util.concurrent.CompletableFuture; import java.util.concurrent.ForkJoinPool; import java.util.concurrent.Future; @@ -16,8 +18,13 @@ /** * {@link AlpacaWebsocket} represents an abstract websocket for Alpaca. + * + * @param the {@link AlpacaWebsocketMessageListener} type parameter + * @param the 'message type' type parameter + * @param the 'message' type parameter */ -public abstract class AlpacaWebsocket extends WebSocketListener implements AlpacaWebsocketInterface { +public abstract class AlpacaWebsocket> extends WebSocketListener + implements AlpacaWebsocketInterface { /** * Defines a websocket normal closure code. @@ -50,6 +57,10 @@ public abstract class AlpacaWebsocket extends WebSocketListener implements Alpac protected final String secretKey; protected final String oAuthToken; protected final boolean useOAuth; + protected final List listeners; + protected final Object listenersLock; + protected final List listenersToAdd; + protected final List listenersToRemove; protected WebsocketStateListener websocketStateListener; protected WebSocket websocket; @@ -86,6 +97,10 @@ public AlpacaWebsocket(OkHttpClient okHttpClient, HttpUrl websocketURL, String w this.secretKey = secretKey; this.oAuthToken = oAuthToken; useOAuth = oAuthToken != null; + listeners = new ArrayList<>(); + listenersLock = new Object(); + listenersToAdd = new ArrayList<>(); + listenersToRemove = new ArrayList<>(); automaticallyReconnect = true; } @@ -197,6 +212,10 @@ private void handleReconnectionAttempt() { * Cleans up this instances state variables. */ protected void cleanupState() { + listeners.clear(); + listenersToAdd.clear(); + listenersToRemove.clear(); + websocket = null; connected = false; authenticated = false; @@ -228,6 +247,54 @@ public Future getAuthorizationFuture() { return authenticationMessageFuture; } + /** + * Calls the {@link AlpacaWebsocketMessageListener}s in {@link #listeners}. + * + * @param messageType the message type + * @param message the message + */ + protected void callListeners(T messageType, M message) { + // Remove all listeners that were request to be removed then add all listeners that were request to be added + // on the last 'onMessage' call to the listeners. + synchronized (listenersLock) { + listeners.removeAll(listenersToRemove); + listenersToRemove.clear(); + + listeners.addAll(listenersToAdd); + listenersToAdd.clear(); + } + + // Note that this doesn't need to acquire 'listenersLock' since 'listeners' is isolated + // to this instance and is not modified outside a lock. + for (L listener : listeners) { + listener.onMessage(messageType, message); + } + } + + @Override + public void addListener(L listener) { + synchronized (listenersLock) { + listenersToAdd.add(listener); + } + + if (!isConnected()) { + connect(); + waitForAuthorization(); + } + } + + @Override + public void removeListener(L listener) { + if (listeners.size() == 1 && listeners.contains(listener)) { + disconnect(); + return; + } + + synchronized (listenersLock) { + listenersToRemove.add(listener); + } + } + /** * Gets {@link #websocketStateListener}. * diff --git a/src/main/java/net/jacobpeterson/alpaca/websocket/AlpacaWebsocketInterface.java b/src/main/java/net/jacobpeterson/alpaca/websocket/AlpacaWebsocketInterface.java index 7e1b5e87..22b2676e 100644 --- a/src/main/java/net/jacobpeterson/alpaca/websocket/AlpacaWebsocketInterface.java +++ b/src/main/java/net/jacobpeterson/alpaca/websocket/AlpacaWebsocketInterface.java @@ -5,8 +5,10 @@ /** * {@link AlpacaWebsocketInterface} defines an interface for Alpaca websockets. + * + * @param the {@link AlpacaWebsocketMessageListener} type parameter */ -public interface AlpacaWebsocketInterface { +public interface AlpacaWebsocketInterface> { /** * Connects this Websocket. @@ -60,4 +62,24 @@ default boolean waitForAuthorization() { default boolean isValid() { return isConnected() && isAuthenticated(); } + + /** + * Adds a {@link AlpacaWebsocketMessageListener}. + *
+ * Note that this will call {@link AlpacaWebsocketInterface#connect()} if the {@link AlpacaWebsocketMessageListener} + * is not connected already. + * + * @param listener the {@link AlpacaWebsocketMessageListener} + */ + void addListener(L listener); + + /** + * Remove a {@link AlpacaWebsocketMessageListener}. + *
+ * Note that this will call {@link AlpacaWebsocketInterface#disconnect()} if this is the last listener being + * removed. + * + * @param listener the {@link AlpacaWebsocketMessageListener} + */ + void removeListener(L listener); } diff --git a/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/MarketDataWebsocket.java b/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/MarketDataWebsocket.java index 03f2e391..740a422d 100644 --- a/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/MarketDataWebsocket.java +++ b/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/MarketDataWebsocket.java @@ -1,4 +1,93 @@ package net.jacobpeterson.alpaca.websocket.marketdata; -public class MarketDataWebsocket /*extends AlpacaWebsocket implements MarketDataWebsocketInterface */ { +import net.jacobpeterson.alpaca.model.endpoint.marketdata.realtime.MarketDataMessage; +import net.jacobpeterson.alpaca.model.endpoint.marketdata.realtime.enums.MarketDataMessageType; +import net.jacobpeterson.alpaca.model.properties.DataAPIType; +import net.jacobpeterson.alpaca.websocket.AlpacaWebsocket; +import okhttp3.HttpUrl; +import okhttp3.OkHttpClient; +import okhttp3.WebSocket; +import org.jetbrains.annotations.NotNull; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.Arrays; +import java.util.List; + +/** + * {@link MarketDataWebsocket} is an {@link AlpacaWebsocket} implementation and provides the {@link + * MarketDataWebsocketInterface} interface for + * Real-time + * Market Data + */ +public class MarketDataWebsocket extends AlpacaWebsocket + implements MarketDataWebsocketInterface { + + private static final Logger LOGGER = LoggerFactory.getLogger(MarketDataWebsocket.class); + private static final String MESSAGE_TYPE_ELEMENT_KEY = "T"; + private static final List SUBSCRIBABLE_MARKET_DATA_MESSAGE_TYPES = Arrays.asList( + MarketDataMessageType.TRADE, + MarketDataMessageType.QUOTE, + MarketDataMessageType.BAR); + + /** + * Creates a {@link HttpUrl} for {@link MarketDataWebsocket} with the given dataAPIType. + * + * @param dataAPIType the {@link DataAPIType} + * + * @return a {@link HttpUrl} + */ + private static HttpUrl createWebsocketURL(DataAPIType dataAPIType) { + return new HttpUrl.Builder() + .scheme("https") // HttpUrl.Builder doesn't recognize "wss" scheme, so "https" works fine + .host("stream.data.alpaca.markets") + .addPathSegment("v2") + .addPathSegment(dataAPIType.toString()) + .build(); + } + + /** + * Instantiates a new {@link MarketDataWebsocket}. + * + * @param okHttpClient the {@link OkHttpClient} + * @param dataAPIType the {@link DataAPIType} + * @param keyID the key ID + * @param secretKey the secret key + * @param oAuthToken the OAuth token + */ + public MarketDataWebsocket(OkHttpClient okHttpClient, DataAPIType dataAPIType, + String keyID, String secretKey, String oAuthToken) { + super(okHttpClient, createWebsocketURL(dataAPIType), "Market Data", keyID, secretKey, oAuthToken); + } + + @Override + protected void onConnection() { + // TODO + } + + @Override + protected void onReconnection() { + // TODO + } + + @Override + protected void sendAuthenticationMessage() { + // TODO + } + + // This websocket uses string frames and not binary frames. + @Override + public void onMessage(@NotNull WebSocket webSocket, @NotNull String text) { + // TODO + } + + @Override + public void subscribe(List tradeSymbols, List quoteSymbols, List barSymbols) { + // TODO + } + + @Override + public void unsubscribe(List tradeSymbols, List quoteSymbols, List barSymbols) { + // TODO + } } diff --git a/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/MarketDataWebsocketInterface.java b/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/MarketDataWebsocketInterface.java index 0ba70038..6a9509c8 100644 --- a/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/MarketDataWebsocketInterface.java +++ b/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/MarketDataWebsocketInterface.java @@ -2,8 +2,38 @@ import net.jacobpeterson.alpaca.websocket.AlpacaWebsocketInterface; +import java.util.List; + /** * {@link MarketDataWebsocketInterface} is an {@link AlpacaWebsocketInterface} for a {@link MarketDataWebsocket}. */ -public interface MarketDataWebsocketInterface extends AlpacaWebsocketInterface { +public interface MarketDataWebsocketInterface extends AlpacaWebsocketInterface { + + /** + * Subscribes to trades, quotes, or bars according to the given {@link List} of symbols. + *
+ * Note that any one of the given {@link List}s can contain the wildcard character e.g. "*" to subscribe to ALL + * available symbols. + * + * @param tradeSymbols a {@link List} of symbols to subscribe to trades or null for no change + * @param quoteSymbols a {@link List} of symbols to subscribe to quotes or null for no change + * @param barSymbols a {@link List} of symbols to subscribe to bars or null for no change + * + * @see #unsubscribe(List, List, List) + */ + void subscribe(List tradeSymbols, List quoteSymbols, List barSymbols); + + /** + * Unsubscribes from trades, quotes, or bars according to the given {@link List} of symbols. + *
+ * Note that any one of the given {@link List}s can contain the wildcard character e.g. "*" to unsubscribe from a + * previously subscribed wildcard. + * + * @param tradeSymbols a {@link List} of symbols to unsubscribe from trades or null for no change + * @param quoteSymbols a {@link List} of symbols to unsubscribe from quotes or null for no change + * @param barSymbols a {@link List} of symbols to unsubscribe from bars or null for no change + * + * @see #subscribe(List, List, List) + */ + void unsubscribe(List tradeSymbols, List quoteSymbols, List barSymbols); } diff --git a/src/main/java/net/jacobpeterson/alpaca/websocket/streaming/StreamingWebsocket.java b/src/main/java/net/jacobpeterson/alpaca/websocket/streaming/StreamingWebsocket.java index 2e9f248b..67ba252b 100644 --- a/src/main/java/net/jacobpeterson/alpaca/websocket/streaming/StreamingWebsocket.java +++ b/src/main/java/net/jacobpeterson/alpaca/websocket/streaming/StreamingWebsocket.java @@ -1,5 +1,6 @@ package net.jacobpeterson.alpaca.websocket.streaming; +import com.google.common.collect.Iterables; import com.google.gson.*; import net.jacobpeterson.alpaca.model.endpoint.streaming.StreamingMessage; import net.jacobpeterson.alpaca.model.endpoint.streaming.authorization.AuthorizationData; @@ -7,7 +8,6 @@ import net.jacobpeterson.alpaca.model.endpoint.streaming.enums.StreamingMessageType; import net.jacobpeterson.alpaca.model.endpoint.streaming.listening.ListeningMessage; import net.jacobpeterson.alpaca.model.endpoint.streaming.trade.TradeUpdateMessage; -import net.jacobpeterson.alpaca.util.gson.GsonUtil; import net.jacobpeterson.alpaca.websocket.AlpacaWebsocket; import okhttp3.HttpUrl; import okhttp3.OkHttpClient; @@ -22,11 +22,17 @@ import java.util.Collections; import java.util.List; -import static com.google.common.base.Preconditions.checkArgument; import static com.google.common.base.Preconditions.checkNotNull; import static com.google.common.base.Preconditions.checkState; +import static net.jacobpeterson.alpaca.util.gson.GsonUtil.GSON; -public class StreamingWebsocket extends AlpacaWebsocket implements StreamingWebsocketInterface { +/** + * {@link StreamingWebsocket} is an {@link AlpacaWebsocket} implementation and provides the {@link + * StreamingWebsocketInterface} interface for + * Streaming + */ +public class StreamingWebsocket extends AlpacaWebsocket + implements StreamingWebsocketInterface { private static final Logger LOGGER = LoggerFactory.getLogger(StreamingWebsocket.class); private static final String STREAM_ELEMENT_KEY = "stream"; @@ -34,7 +40,7 @@ public class StreamingWebsocket extends AlpacaWebsocket implements StreamingWebs StreamingMessageType.TRADE_UPDATES); /** - * Creates a {@link HttpUrl} for {@link StreamingWebsocket} for the given alpacaSubdomain. + * Creates a {@link HttpUrl} for {@link StreamingWebsocket} with the given alpacaSubdomain. * * @param alpacaSubdomain the Alpaca subdomain * @@ -48,10 +54,7 @@ private static HttpUrl createWebsocketURL(String alpacaSubdomain) { .build(); } - private final List listeners; - private final Object listenersLock; - private final List listenersToAdd; - private final List listenersToRemove; + private final List listenedStreamMessageTypes; /** * Instantiates a new {@link StreamingWebsocket}. @@ -66,10 +69,7 @@ public StreamingWebsocket(OkHttpClient okHttpClient, String alpacaSubdomain, String keyID, String secretKey, String oAuthToken) { super(okHttpClient, createWebsocketURL(alpacaSubdomain), "Streaming", keyID, secretKey, oAuthToken); - listeners = new ArrayList<>(); - listenersLock = new Object(); - listenersToAdd = new ArrayList<>(); - listenersToRemove = new ArrayList<>(); + listenedStreamMessageTypes = new ArrayList<>(); } @Override @@ -80,15 +80,9 @@ protected void onConnection() { @Override protected void onReconnection() { sendAuthenticationMessage(); - } - - @Override - protected void cleanupState() { - super.cleanupState(); - - listeners.clear(); - listenersToAdd.clear(); - listenersToRemove.clear(); + if (waitForAuthorization()) { + subscriptions(Iterables.toArray(listenedStreamMessageTypes, StreamingMessageType.class)); + } } @Override @@ -134,11 +128,10 @@ public void onMessage(@NotNull WebSocket webSocket, @NotNull ByteString byteStri checkState(streamElement instanceof JsonPrimitive, "Message must contain %s element! Received: %s", STREAM_ELEMENT_KEY, messageElement); - StreamingMessageType streamingMessageType = GsonUtil.GSON.fromJson(streamElement, StreamingMessageType.class); + StreamingMessageType streamingMessageType = GSON.fromJson(streamElement, StreamingMessageType.class); switch (streamingMessageType) { case AUTHORIZATION: - AuthorizationMessage authorizationMessage = - GsonUtil.GSON.fromJson(messageObject, AuthorizationMessage.class); + AuthorizationMessage authorizationMessage = GSON.fromJson(messageObject, AuthorizationMessage.class); authenticated = isAuthorizationMessageSuccess(authorizationMessage); @@ -156,12 +149,16 @@ public void onMessage(@NotNull WebSocket webSocket, @NotNull ByteString byteStri callListeners(streamingMessageType, authorizationMessage); break; case LISTENING: - ListeningMessage listeningMessage = GsonUtil.GSON.fromJson(messageObject, ListeningMessage.class); + ListeningMessage listeningMessage = GSON.fromJson(messageObject, ListeningMessage.class); LOGGER.debug("{}", listeningMessage); + + listenedStreamMessageTypes.clear(); + listenedStreamMessageTypes.addAll(listeningMessage.getData().getStreams()); + callListeners(streamingMessageType, listeningMessage); break; case TRADE_UPDATES: - TradeUpdateMessage tradeUpdateMessage = GsonUtil.GSON.fromJson(messageObject, TradeUpdateMessage.class); + TradeUpdateMessage tradeUpdateMessage = GSON.fromJson(messageObject, TradeUpdateMessage.class); LOGGER.debug("{}", tradeUpdateMessage); callListeners(streamingMessageType, tradeUpdateMessage); break; @@ -183,35 +180,14 @@ private boolean isAuthorizationMessageSuccess(AuthorizationMessage authorization authorizationData.getAction().equalsIgnoreCase("authenticate"); } - /** - * Calls the {@link StreamingListener}s in {@link #listeners}. - * - * @param streamingMessageType the {@link StreamingMessageType} - * @param streamingMessage the {@link StreamingMessage} - */ - private void callListeners(StreamingMessageType streamingMessageType, StreamingMessage streamingMessage) { - // Remove all 'StreamingListener's that were request to be removed then add all 'StreamingListener's - // that were request to be added on the last call to the listeners. - synchronized (listenersLock) { - listeners.removeAll(listenersToRemove); - listenersToRemove.clear(); - - listeners.addAll(listenersToAdd); - listenersToAdd.clear(); - } - - // Note that this doesn't need to acquire 'listenersLock' since 'listeners' is isolated - // to this instance and is not modified outside a lock. - for (StreamingListener streamingListener : listeners) { - streamingListener.onMessage(streamingMessageType, streamingMessage); - } - } - @Override public void subscriptions(StreamingMessageType... streamingMessageTypes) { checkState(isConnected(), "Websocket must be connected before requesting subscriptions!"); checkNotNull(streamingMessageTypes); - checkArgument(streamingMessageTypes.length > 0, "'streamingMessageTypes' must be have at least one element."); + + if (streamingMessageTypes.length == 0) { + return; + } // Stream request format: // { @@ -241,27 +217,4 @@ public void subscriptions(StreamingMessageType... streamingMessageTypes) { websocket.send(requestObject.toString()); LOGGER.info("Requested subscriptions: {}.", streamsArray); } - - @Override - public void addListener(StreamingListener streamingListener) { - synchronized (listenersLock) { - listenersToAdd.add(streamingListener); - } - - if (!isConnected()) { - connect(); - waitForAuthorization(); - } - } - - @Override - public void removeListener(StreamingListener streamingListener) { - synchronized (listenersLock) { - listenersToRemove.add(streamingListener); - } - - if (listeners.size() == 1 && listeners.contains(streamingListener)) { - disconnect(); - } - } } diff --git a/src/main/java/net/jacobpeterson/alpaca/websocket/streaming/StreamingWebsocketInterface.java b/src/main/java/net/jacobpeterson/alpaca/websocket/streaming/StreamingWebsocketInterface.java index e71bda03..f68ad50f 100644 --- a/src/main/java/net/jacobpeterson/alpaca/websocket/streaming/StreamingWebsocketInterface.java +++ b/src/main/java/net/jacobpeterson/alpaca/websocket/streaming/StreamingWebsocketInterface.java @@ -6,7 +6,7 @@ /** * {@link StreamingWebsocketInterface} is an {@link AlpacaWebsocketInterface} for a {@link StreamingWebsocket}. */ -public interface StreamingWebsocketInterface extends AlpacaWebsocketInterface { +public interface StreamingWebsocketInterface extends AlpacaWebsocketInterface { /** * Sets subscribed {@link StreamingMessageType}s to streamingMessageTypes. @@ -14,23 +14,4 @@ public interface StreamingWebsocketInterface extends AlpacaWebsocketInterface { * @param streamingMessageTypes the {@link StreamingMessageType}s */ void subscriptions(StreamingMessageType... streamingMessageTypes); - - /** - * Adds a {@link StreamingListener}. - *
- * Note that this will call {@link StreamingWebsocket#connect()} if the {@link StreamingWebsocket} is not connected - * already. - * - * @param streamingListener the {@link StreamingListener} - */ - void addListener(StreamingListener streamingListener); - - /** - * Remove a {@link StreamingListener}. - *
- * Note that this will call {@link StreamingWebsocket#disconnect()} if this is the last listener being removed. - * - * @param streamingListener the {@link StreamingListener} - */ - void removeListener(StreamingListener streamingListener); } From d33457aea5f9d38cf9567068843cc29212ac3216 Mon Sep 17 00:00:00 2001 From: Jacob Peterson Date: Sun, 11 Jul 2021 14:00:28 -0600 Subject: [PATCH 128/271] Finish MarketDataWebsocket, fix bugs --- build.gradle | 1 + .../net/jacobpeterson/alpaca/AlpacaAPI.java | 24 +- .../alpaca/rest/AlpacaClient.java | 4 - .../alpaca/websocket/AlpacaWebsocket.java | 4 - .../marketdata/MarketDataWebsocket.java | 284 +++++++++++++++++- .../MarketDataWebsocketInterface.java | 78 +++-- .../streaming/StreamingWebsocket.java | 80 +++-- .../StreamingWebsocketInterface.java | 13 +- 8 files changed, 410 insertions(+), 78 deletions(-) diff --git a/build.gradle b/build.gradle index b1b355e5..8abd5869 100644 --- a/build.gradle +++ b/build.gradle @@ -239,6 +239,7 @@ jsonSchema2Pojo { serializable = true includeGetters = true includeSetters = true + includeCopyConstructor = true } // diff --git a/src/main/java/net/jacobpeterson/alpaca/AlpacaAPI.java b/src/main/java/net/jacobpeterson/alpaca/AlpacaAPI.java index e9100f1f..b9eb0071 100644 --- a/src/main/java/net/jacobpeterson/alpaca/AlpacaAPI.java +++ b/src/main/java/net/jacobpeterson/alpaca/AlpacaAPI.java @@ -6,18 +6,23 @@ import net.jacobpeterson.alpaca.properties.AlpacaProperties; import net.jacobpeterson.alpaca.rest.AlpacaClient; import net.jacobpeterson.alpaca.rest.endpoint.*; +import net.jacobpeterson.alpaca.websocket.AlpacaWebsocket; +import net.jacobpeterson.alpaca.websocket.marketdata.MarketDataWebsocket; +import net.jacobpeterson.alpaca.websocket.marketdata.MarketDataWebsocketInterface; import net.jacobpeterson.alpaca.websocket.streaming.StreamingWebsocket; import net.jacobpeterson.alpaca.websocket.streaming.StreamingWebsocketInterface; import okhttp3.OkHttpClient; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import static com.google.common.base.Preconditions.checkArgument; import static com.google.common.base.Preconditions.checkNotNull; /** - * {@link AlpacaAPI} contains several instances of various {@link AbstractEndpoint}s that enable you to interface with - * Alpaca. You will generally only need one instance of this class in your application. Note that many methods inside - * the various {@link AbstractEndpoint}s allow null to be passed in as a parameter if it is optional. + * {@link AlpacaAPI} contains several instances of various {@link AbstractEndpoint}s and {@link AlpacaWebsocket}s that + * enable you to interface with Alpaca. You will generally only need one instance of this class in your application. + * Note that many methods inside the various {@link AbstractEndpoint}s allow null to be passed in as a + * parameter if it is optional. * * @see Alpaca API Documentation */ @@ -43,6 +48,7 @@ public class AlpacaAPI { private final AccountActivitiesEndpoint accountActivitiesEndpoint; private final PortfolioHistoryEndpoint portfolioHistoryEndpoint; private final StreamingWebsocket streamingWebsocket; + private final MarketDataWebsocket marketDataWebsocket; /** * Instantiates a new {@link AlpacaAPI} using properties specified in alpaca.properties file (or their @@ -102,6 +108,9 @@ public AlpacaAPI(String oAuthToken) { */ public AlpacaAPI(OkHttpClient okHttpClient, String keyID, String secretKey, String oAuthToken, EndpointAPIType endpointAPIType, DataAPIType dataAPIType) { + checkArgument((keyID != null && secretKey != null) ^ oAuthToken != null, + "You must specify a (KeyID (%s) and Secret Key (%s)) or an OAuthToken (%s)!", + keyID, secretKey, oAuthToken); checkNotNull(endpointAPIType); checkNotNull(dataAPIType); @@ -135,9 +144,11 @@ public AlpacaAPI(OkHttpClient okHttpClient, String keyID, String secretKey, Stri brokerClient = new AlpacaClient(okHttpClient, keyID, secretKey, brokerHostSubdomain, VERSION_2_PATH_SEGMENT); dataClient = new AlpacaClient(okHttpClient, keyID, secretKey, "data", VERSION_2_PATH_SEGMENT); + marketDataWebsocket = new MarketDataWebsocket(okHttpClient, dataAPIType, keyID, secretKey); } else { brokerClient = new AlpacaClient(okHttpClient, oAuthToken, brokerHostSubdomain, VERSION_2_PATH_SEGMENT); dataClient = null; + marketDataWebsocket = null; } accountEndpoint = new AccountEndpoint(brokerClient); @@ -238,6 +249,13 @@ public StreamingWebsocketInterface streaming() { return streamingWebsocket; } + /** + * @return the {@link MarketDataWebsocketInterface} + */ + public MarketDataWebsocketInterface marketDataStreaming() { + return marketDataWebsocket; + } + /** * Gets {@link #okHttpClient}. * diff --git a/src/main/java/net/jacobpeterson/alpaca/rest/AlpacaClient.java b/src/main/java/net/jacobpeterson/alpaca/rest/AlpacaClient.java index 7c69ec34..0b8f7e01 100644 --- a/src/main/java/net/jacobpeterson/alpaca/rest/AlpacaClient.java +++ b/src/main/java/net/jacobpeterson/alpaca/rest/AlpacaClient.java @@ -11,7 +11,6 @@ import java.lang.reflect.Type; import java.util.function.Predicate; -import static com.google.common.base.Preconditions.checkArgument; import static com.google.common.base.Preconditions.checkNotNull; import static com.google.common.base.Preconditions.checkState; import static net.jacobpeterson.alpaca.util.gson.GsonUtil.GSON; @@ -65,9 +64,6 @@ public AlpacaClient(OkHttpClient okHttpClient, String oAuthToken, String alpacaS protected AlpacaClient(OkHttpClient okHttpClient, String keyID, String secretKey, String oAuthToken, String alpacaSubdomain, String versionPathSegment) { checkNotNull(okHttpClient); - checkArgument((keyID != null && secretKey != null) ^ oAuthToken != null, - "You must specify a (KeyID (%s) and Secret Key (%s)) or an OAuthToken (%s)!", - keyID, secretKey, oAuthToken); checkNotNull(alpacaSubdomain); checkNotNull(versionPathSegment); diff --git a/src/main/java/net/jacobpeterson/alpaca/websocket/AlpacaWebsocket.java b/src/main/java/net/jacobpeterson/alpaca/websocket/AlpacaWebsocket.java index 2a819179..ee8ede19 100644 --- a/src/main/java/net/jacobpeterson/alpaca/websocket/AlpacaWebsocket.java +++ b/src/main/java/net/jacobpeterson/alpaca/websocket/AlpacaWebsocket.java @@ -13,7 +13,6 @@ import java.util.concurrent.ForkJoinPool; import java.util.concurrent.Future; -import static com.google.common.base.Preconditions.checkArgument; import static com.google.common.base.Preconditions.checkNotNull; /** @@ -86,9 +85,6 @@ public AlpacaWebsocket(OkHttpClient okHttpClient, HttpUrl websocketURL, String w checkNotNull(okHttpClient); checkNotNull(websocketURL); checkNotNull(websocketName); - checkArgument((keyID != null && secretKey != null) ^ oAuthToken != null, - "You must specify a (KeyID (%s) and Secret Key (%s)) or an OAuthToken (%s)!", - keyID, secretKey, oAuthToken); this.okHttpClient = okHttpClient; this.websocketURL = websocketURL; diff --git a/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/MarketDataWebsocket.java b/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/MarketDataWebsocket.java index 740a422d..1a9accbe 100644 --- a/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/MarketDataWebsocket.java +++ b/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/MarketDataWebsocket.java @@ -1,7 +1,18 @@ package net.jacobpeterson.alpaca.websocket.marketdata; +import com.google.common.collect.Iterables; +import com.google.gson.JsonArray; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import com.google.gson.JsonParser; import net.jacobpeterson.alpaca.model.endpoint.marketdata.realtime.MarketDataMessage; +import net.jacobpeterson.alpaca.model.endpoint.marketdata.realtime.bar.BarMessage; +import net.jacobpeterson.alpaca.model.endpoint.marketdata.realtime.control.ErrorMessage; +import net.jacobpeterson.alpaca.model.endpoint.marketdata.realtime.control.SubscriptionsMessage; +import net.jacobpeterson.alpaca.model.endpoint.marketdata.realtime.control.SuccessMessage; import net.jacobpeterson.alpaca.model.endpoint.marketdata.realtime.enums.MarketDataMessageType; +import net.jacobpeterson.alpaca.model.endpoint.marketdata.realtime.quote.QuoteMessage; +import net.jacobpeterson.alpaca.model.endpoint.marketdata.realtime.trade.TradeMessage; import net.jacobpeterson.alpaca.model.properties.DataAPIType; import net.jacobpeterson.alpaca.websocket.AlpacaWebsocket; import okhttp3.HttpUrl; @@ -11,8 +22,12 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import java.util.Arrays; -import java.util.List; +import java.util.*; + +import static com.google.common.base.Preconditions.checkNotNull; +import static com.google.common.base.Preconditions.checkState; +import static com.google.common.base.Predicates.not; +import static net.jacobpeterson.alpaca.util.gson.GsonUtil.GSON; /** * {@link MarketDataWebsocket} is an {@link AlpacaWebsocket} implementation and provides the {@link @@ -25,6 +40,10 @@ public class MarketDataWebsocket extends AlpacaWebsocket AUTH_FAILURE_MESSAGES = Arrays.asList( + "auth failed", + "auth timeout", + "not authenticated"); private static final List SUBSCRIBABLE_MARKET_DATA_MESSAGE_TYPES = Arrays.asList( MarketDataMessageType.TRADE, MarketDataMessageType.QUOTE, @@ -39,13 +58,18 @@ public class MarketDataWebsocket extends AlpacaWebsocket listenedMarketDataMessageTypes; + private final Set subscribedTrades; + private final Set subscribedQuotes; + private final Set subscribedBars; + /** * Instantiates a new {@link MarketDataWebsocket}. * @@ -53,41 +77,271 @@ private static HttpUrl createWebsocketURL(DataAPIType dataAPIType) { * @param dataAPIType the {@link DataAPIType} * @param keyID the key ID * @param secretKey the secret key - * @param oAuthToken the OAuth token */ public MarketDataWebsocket(OkHttpClient okHttpClient, DataAPIType dataAPIType, - String keyID, String secretKey, String oAuthToken) { - super(okHttpClient, createWebsocketURL(dataAPIType), "Market Data", keyID, secretKey, oAuthToken); + String keyID, String secretKey) { + super(okHttpClient, createWebsocketURL(dataAPIType), "Market Data", keyID, secretKey, null); + + listenedMarketDataMessageTypes = new HashSet<>(); + subscribedTrades = new HashSet<>(); + subscribedQuotes = new HashSet<>(); + subscribedBars = new HashSet<>(); + } + + @Override + protected void cleanupState() { + super.cleanupState(); + + listenedMarketDataMessageTypes.clear(); } @Override protected void onConnection() { - // TODO + sendAuthenticationMessage(); } @Override protected void onReconnection() { - // TODO + sendAuthenticationMessage(); + if (waitForAuthorization()) { + subscribeToTypes(Iterables.toArray(listenedMarketDataMessageTypes, MarketDataMessageType.class)); + subscribe(subscribedTrades, subscribedQuotes, subscribedBars); + } } @Override protected void sendAuthenticationMessage() { - // TODO + /* Format of message is: + * { + * "action": "auth", + * "key": "{APCA-API-KEY-ID}", + * "secret": "{APCA-API-SECRET-KEY}" + * } + */ + + JsonObject authObject = new JsonObject(); + authObject.addProperty("action", "auth"); + authObject.addProperty("key", keyID); + authObject.addProperty("secret", secretKey); + + LOGGER.info("{} websocket sending authentication message...", websocketName); + websocket.send(authObject.toString()); } // This websocket uses string frames and not binary frames. @Override - public void onMessage(@NotNull WebSocket webSocket, @NotNull String text) { - // TODO + public void onMessage(@NotNull WebSocket webSocket, @NotNull String message) { + JsonElement messageElement = JsonParser.parseString(message); + checkState(messageElement instanceof JsonArray, "Message must be a JsonArray! Received: %s", messageElement); + + JsonArray messageArray = messageElement.getAsJsonArray(); + for (JsonElement arrayElement : messageArray) { + checkState(arrayElement instanceof JsonObject, + "'arrayElement' must be a JsonObject! Received: %s", arrayElement); + + JsonObject messageObject = arrayElement.getAsJsonObject(); + MarketDataMessageType marketDataMessageType = GSON.fromJson( + messageObject.get(MESSAGE_TYPE_ELEMENT_KEY), MarketDataMessageType.class); + checkNotNull(marketDataMessageType, "MarketDataMessageType not found in message: %s", messageObject); + + MarketDataMessage marketDataMessage; + switch (marketDataMessageType) { + case SUCCESS: + marketDataMessage = GSON.fromJson(messageObject, SuccessMessage.class); + LOGGER.debug("{}", marketDataMessage); + + if (isSuccessMessageAuthenticated((SuccessMessage) marketDataMessage)) { + LOGGER.info("{} websocket authenticated.", websocketName); + + authenticated = true; + + if (authenticationMessageFuture != null) { + authenticationMessageFuture.complete(true); + } + } + break; + case ERROR: + marketDataMessage = GSON.fromJson(messageObject, ErrorMessage.class); + + if (isErrorMessageAuthFailure((ErrorMessage) marketDataMessage) && + authenticationMessageFuture != null) { + LOGGER.error("{} websocket not authenticated! Received: {}.", websocketName, marketDataMessage); + + authenticated = false; + + if (authenticationMessageFuture != null) { + authenticationMessageFuture.complete(false); + } + } else { + LOGGER.error("{} websocket error message: {}", websocketName, marketDataMessage); + } + break; + case SUBSCRIPTION: + marketDataMessage = GSON.fromJson(messageObject, SubscriptionsMessage.class); + LOGGER.debug("{}", marketDataMessage); + + // Update 'listenedMarketDataMessageTypes' and the associated subscribed symbols lists + SubscriptionsMessage subscriptionsMessage = (SubscriptionsMessage) marketDataMessage; + handleSubscriptionMessageList(MarketDataMessageType.TRADE, subscriptionsMessage.getTrades(), + subscribedTrades); + handleSubscriptionMessageList(MarketDataMessageType.QUOTE, subscriptionsMessage.getQuotes(), + subscribedQuotes); + handleSubscriptionMessageList(MarketDataMessageType.BAR, subscriptionsMessage.getBars(), + subscribedBars); + break; + case TRADE: + marketDataMessage = GSON.fromJson(messageObject, TradeMessage.class); + break; + case QUOTE: + marketDataMessage = GSON.fromJson(messageObject, QuoteMessage.class); + break; + case BAR: + marketDataMessage = GSON.fromJson(messageObject, BarMessage.class); + break; + default: + LOGGER.error("Message type {} not implemented!", marketDataMessageType); + continue; + } + + if (listenedMarketDataMessageTypes.contains(marketDataMessageType)) { + callListeners(marketDataMessageType, marketDataMessage); + } + } + } + + /** + * Returns true if {@link SuccessMessage#getMessage()} equals "authenticated". + * + * @param successMessage the {@link SuccessMessage} + * + * @return a boolean + */ + private boolean isSuccessMessageAuthenticated(SuccessMessage successMessage) { + return successMessage.getMessage().equalsIgnoreCase("authenticated"); + } + + /** + * Handles a {@link SubscriptionsMessage} for updating {@link #listenedMarketDataMessageTypes} and returns a {@link + * List} of currently subscribe symbols or null. + * + * @param marketDataMessageType the {@link MarketDataMessageType} + * @param newSubscribedSymbols the new subscribed symbols from {@link SubscriptionsMessage} + * @param currentSubscribeSymbols the currently subscribe symbols {@link Set} + */ + private void handleSubscriptionMessageList(MarketDataMessageType marketDataMessageType, + Collection newSubscribedSymbols, Set currentSubscribeSymbols) { + if (newSubscribedSymbols != null && !newSubscribedSymbols.isEmpty()) { + listenedMarketDataMessageTypes.add(marketDataMessageType); + currentSubscribeSymbols.clear(); + currentSubscribeSymbols.addAll(newSubscribedSymbols); + } else { + listenedMarketDataMessageTypes.remove(marketDataMessageType); + currentSubscribeSymbols.clear(); + } + } + + /** + * Returns true if {@link ErrorMessage#getMessage()} is any of {@link #AUTH_FAILURE_MESSAGES}. + * + * @param errorMessage the {@link ErrorMessage} + * + * @return a boolean + */ + private boolean isErrorMessageAuthFailure(ErrorMessage errorMessage) { + return AUTH_FAILURE_MESSAGES.contains(errorMessage.getMessage().toLowerCase()); + } + + @Override + public void subscribeToTypes(MarketDataMessageType... marketDataMessageTypes) { + if (marketDataMessageTypes == null) { + return; + } + + Arrays.stream(marketDataMessageTypes) + .filter(not(SUBSCRIBABLE_MARKET_DATA_MESSAGE_TYPES::contains)) + .forEach(listenedMarketDataMessageTypes::add); + } + + @Override + public void subscribe(Collection tradeSymbols, Collection quoteSymbols, + Collection barSymbols) { + sendSubscriptionUpdate(tradeSymbols, quoteSymbols, barSymbols, true); + } + + @Override + public void unsubscribe(Collection tradeSymbols, Collection quoteSymbols, + Collection barSymbols) { + sendSubscriptionUpdate(tradeSymbols, quoteSymbols, barSymbols, false); + } + + /** + * Sends a subscription update request. + * + * @param tradeSymbols a {@link Collection} of symbols to update for trades or null for no change + * @param quoteSymbols a {@link Collection} of symbols to update for quotes or null for no change + * @param barSymbols a {@link Collection} of symbols to update for bars or null for no change + * @param subscribe true to subscribe, false to unsubscribe + */ + private void sendSubscriptionUpdate(Collection tradeSymbols, Collection quoteSymbols, + Collection barSymbols, boolean subscribe) { + checkState(isConnected(), "{} websocket must be connected before requesting streams!", websocketName); + + /* Format of message is: + * { + * "action": "subscribe", + * "trades": ["AAPL"], + * "quotes": ["AMD", "CLDR"], + * "bars": ["*"] + * } + */ + + JsonObject subscriptionUpdateObject = new JsonObject(); + subscriptionUpdateObject.addProperty("action", subscribe ? "subscribe" : "unsubscribe"); + + addSubscriptionUpdateList(subscriptionUpdateObject, "trades", tradeSymbols); + addSubscriptionUpdateList(subscriptionUpdateObject, "quotes", quoteSymbols); + addSubscriptionUpdateList(subscriptionUpdateObject, "bars", barSymbols); + + boolean updateExists = subscriptionUpdateObject.size() > 1; + if (updateExists) { + websocket.send(subscriptionUpdateObject.toString()); + LOGGER.info("Requested subscriptions update: {}.", subscriptionUpdateObject); + } + } + + /** + * Adds symbols to subscriptionUpdateObject with elementKey if not empty. + * + * @param subscriptionUpdateObject the subscription update {@link JsonObject} + * @param elementKey the element key + * @param symbols the symbols {@link Collection} + */ + private void addSubscriptionUpdateList(JsonObject subscriptionUpdateObject, String elementKey, + Collection symbols) { + if (symbols != null && !symbols.isEmpty()) { + JsonArray symbolArray = new JsonArray(); + symbols.forEach(symbolArray::add); + subscriptionUpdateObject.add(elementKey, symbolArray); + } + } + + @Override + public Collection subscribedTypes() { + return new HashSet<>(listenedMarketDataMessageTypes); + } + + @Override + public Collection subscribedTrades() { + return new HashSet<>(subscribedTrades); } @Override - public void subscribe(List tradeSymbols, List quoteSymbols, List barSymbols) { - // TODO + public Collection subscribedQuotes() { + return new HashSet<>(subscribedQuotes); } @Override - public void unsubscribe(List tradeSymbols, List quoteSymbols, List barSymbols) { - // TODO + public Collection subscribedBars() { + return new HashSet<>(subscribedBars); } } diff --git a/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/MarketDataWebsocketInterface.java b/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/MarketDataWebsocketInterface.java index 6a9509c8..eac2c815 100644 --- a/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/MarketDataWebsocketInterface.java +++ b/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/MarketDataWebsocketInterface.java @@ -1,8 +1,13 @@ package net.jacobpeterson.alpaca.websocket.marketdata; +import net.jacobpeterson.alpaca.model.endpoint.marketdata.realtime.MarketDataMessage; +import net.jacobpeterson.alpaca.model.endpoint.marketdata.realtime.control.ErrorMessage; +import net.jacobpeterson.alpaca.model.endpoint.marketdata.realtime.control.SubscriptionsMessage; +import net.jacobpeterson.alpaca.model.endpoint.marketdata.realtime.control.SuccessMessage; +import net.jacobpeterson.alpaca.model.endpoint.marketdata.realtime.enums.MarketDataMessageType; import net.jacobpeterson.alpaca.websocket.AlpacaWebsocketInterface; -import java.util.List; +import java.util.Collection; /** * {@link MarketDataWebsocketInterface} is an {@link AlpacaWebsocketInterface} for a {@link MarketDataWebsocket}. @@ -10,30 +15,69 @@ public interface MarketDataWebsocketInterface extends AlpacaWebsocketInterface { /** - * Subscribes to trades, quotes, or bars according to the given {@link List} of symbols. + * Subscribe to a specific {@link MarketDataMessage}. That is, {@link ErrorMessage}, {@link SubscriptionsMessage}, + * or {@link SuccessMessage}. + * + * @param marketDataMessageTypes array of any of: {@link MarketDataMessageType#SUCCESS}, {@link + * MarketDataMessageType#ERROR}, or {@link MarketDataMessageType#SUBSCRIPTION} + */ + void subscribeToTypes(MarketDataMessageType... marketDataMessageTypes); + + /** + * Subscribes to trades, quotes, or bars according to the given {@link Collection} of symbols. *
- * Note that any one of the given {@link List}s can contain the wildcard character e.g. "*" to subscribe to ALL - * available symbols. + * Note that any one of the given {@link Collection}s can contain the wildcard character e.g. "*" to subscribe to + * ALL available symbols. * - * @param tradeSymbols a {@link List} of symbols to subscribe to trades or null for no change - * @param quoteSymbols a {@link List} of symbols to subscribe to quotes or null for no change - * @param barSymbols a {@link List} of symbols to subscribe to bars or null for no change + * @param tradeSymbols a {@link Collection} of symbols to subscribe to trades or null for no change + * @param quoteSymbols a {@link Collection} of symbols to subscribe to quotes or null for no change + * @param barSymbols a {@link Collection} of symbols to subscribe to bars or null for no change * - * @see #unsubscribe(List, List, List) + * @see #unsubscribe(Collection, Collection, Collection) */ - void subscribe(List tradeSymbols, List quoteSymbols, List barSymbols); + void subscribe(Collection tradeSymbols, Collection quoteSymbols, Collection barSymbols); /** - * Unsubscribes from trades, quotes, or bars according to the given {@link List} of symbols. + * Unsubscribes from trades, quotes, or bars according to the given {@link Collection} of symbols. *
- * Note that any one of the given {@link List}s can contain the wildcard character e.g. "*" to unsubscribe from a - * previously subscribed wildcard. + * Note that any one of the given {@link Collection}s can contain the wildcard character e.g. "*" to unsubscribe + * from a previously subscribed wildcard. + * + * @param tradeSymbols a {@link Collection} of symbols to unsubscribe from trades or null for no + * change + * @param quoteSymbols a {@link Collection} of symbols to unsubscribe from quotes or null for no + * change + * @param barSymbols a {@link Collection} of symbols to unsubscribe from bars or null for no change + * + * @see #subscribe(Collection, Collection, Collection) + */ + void unsubscribe(Collection tradeSymbols, Collection quoteSymbols, Collection barSymbols); + + /** + * Gets all of the currently subscribed {@link MarketDataMessageType}s. + * + * @return a {@link Collection} of {@link MarketDataMessageType}s + */ + Collection subscribedTypes(); + + /** + * Gets all of the currently subscribed symbols for trade updates. + * + * @return a {@link Collection} of {@link String}s + */ + Collection subscribedTrades(); + + /** + * Gets all of the currently subscribed symbols for quote updates. * - * @param tradeSymbols a {@link List} of symbols to unsubscribe from trades or null for no change - * @param quoteSymbols a {@link List} of symbols to unsubscribe from quotes or null for no change - * @param barSymbols a {@link List} of symbols to unsubscribe from bars or null for no change + * @return a {@link Collection} of {@link String}s + */ + Collection subscribedQuotes(); + + /** + * Gets all of the currently subscribed symbols for bar updates. * - * @see #subscribe(List, List, List) + * @return a {@link Collection} of {@link String}s */ - void unsubscribe(List tradeSymbols, List quoteSymbols, List barSymbols); + Collection subscribedBars(); } diff --git a/src/main/java/net/jacobpeterson/alpaca/websocket/streaming/StreamingWebsocket.java b/src/main/java/net/jacobpeterson/alpaca/websocket/streaming/StreamingWebsocket.java index 67ba252b..1d3b8c08 100644 --- a/src/main/java/net/jacobpeterson/alpaca/websocket/streaming/StreamingWebsocket.java +++ b/src/main/java/net/jacobpeterson/alpaca/websocket/streaming/StreamingWebsocket.java @@ -17,13 +17,11 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collections; -import java.util.List; +import java.util.*; import static com.google.common.base.Preconditions.checkNotNull; import static com.google.common.base.Preconditions.checkState; +import static com.google.common.base.Predicates.not; import static net.jacobpeterson.alpaca.util.gson.GsonUtil.GSON; /** @@ -48,13 +46,13 @@ public class StreamingWebsocket extends AlpacaWebsocket listenedStreamMessageTypes; + private final Set listenedStreamMessageTypes; /** * Instantiates a new {@link StreamingWebsocket}. @@ -69,7 +67,14 @@ public StreamingWebsocket(OkHttpClient okHttpClient, String alpacaSubdomain, String keyID, String secretKey, String oAuthToken) { super(okHttpClient, createWebsocketURL(alpacaSubdomain), "Streaming", keyID, secretKey, oAuthToken); - listenedStreamMessageTypes = new ArrayList<>(); + listenedStreamMessageTypes = new HashSet<>(); + } + + @Override + protected void cleanupState() { + super.cleanupState(); + + listenedStreamMessageTypes.clear(); } @Override @@ -81,7 +86,7 @@ protected void onConnection() { protected void onReconnection() { sendAuthenticationMessage(); if (waitForAuthorization()) { - subscriptions(Iterables.toArray(listenedStreamMessageTypes, StreamingMessageType.class)); + streams(Iterables.toArray(listenedStreamMessageTypes, StreamingMessageType.class)); } } @@ -129,42 +134,48 @@ public void onMessage(@NotNull WebSocket webSocket, @NotNull ByteString byteStri "Message must contain %s element! Received: %s", STREAM_ELEMENT_KEY, messageElement); StreamingMessageType streamingMessageType = GSON.fromJson(streamElement, StreamingMessageType.class); + checkNotNull(streamingMessageType, "StreamingMessageType not found in message: %s", messageObject); + + StreamingMessage streamingMessage; switch (streamingMessageType) { case AUTHORIZATION: - AuthorizationMessage authorizationMessage = GSON.fromJson(messageObject, AuthorizationMessage.class); + streamingMessage = GSON.fromJson(messageObject, AuthorizationMessage.class); - authenticated = isAuthorizationMessageSuccess(authorizationMessage); - - if (authenticationMessageFuture != null) { - authenticationMessageFuture.complete(authenticated); - } + authenticated = isAuthorizationMessageSuccess((AuthorizationMessage) streamingMessage); if (!authenticated) { - LOGGER.error("{} websocket not authenticated! Received: {}.", websocketName, authorizationMessage); + LOGGER.error("{} websocket not authenticated! Received: {}.", websocketName, streamingMessage); } else { LOGGER.info("{} websocket authenticated.", websocketName); - LOGGER.debug("{}", authorizationMessage); + LOGGER.debug("{}", streamingMessage); } - callListeners(streamingMessageType, authorizationMessage); + if (authenticationMessageFuture != null) { + authenticationMessageFuture.complete(authenticated); + } break; case LISTENING: - ListeningMessage listeningMessage = GSON.fromJson(messageObject, ListeningMessage.class); - LOGGER.debug("{}", listeningMessage); - - listenedStreamMessageTypes.clear(); - listenedStreamMessageTypes.addAll(listeningMessage.getData().getStreams()); - - callListeners(streamingMessageType, listeningMessage); + streamingMessage = GSON.fromJson(messageObject, ListeningMessage.class); + LOGGER.debug("{}", streamingMessage); + + // Remove all 'StreamingMessageType's that are no longer listened to and add new ones + List currentTypes = ((ListeningMessage) streamingMessage).getData().getStreams(); + currentTypes.stream() + .filter(not(listenedStreamMessageTypes::contains)) + .forEach(listenedStreamMessageTypes::remove); + listenedStreamMessageTypes.addAll(currentTypes); break; case TRADE_UPDATES: - TradeUpdateMessage tradeUpdateMessage = GSON.fromJson(messageObject, TradeUpdateMessage.class); - LOGGER.debug("{}", tradeUpdateMessage); - callListeners(streamingMessageType, tradeUpdateMessage); + streamingMessage = GSON.fromJson(messageObject, TradeUpdateMessage.class); + LOGGER.debug("{}", streamingMessage); break; default: throw new UnsupportedOperationException(); } + + if (listenedStreamMessageTypes.contains(streamingMessageType)) { + callListeners(streamingMessageType, streamingMessage); + } } /** @@ -181,11 +192,9 @@ private boolean isAuthorizationMessageSuccess(AuthorizationMessage authorization } @Override - public void subscriptions(StreamingMessageType... streamingMessageTypes) { - checkState(isConnected(), "Websocket must be connected before requesting subscriptions!"); - checkNotNull(streamingMessageTypes); - - if (streamingMessageTypes.length == 0) { + public void streams(StreamingMessageType... streamingMessageTypes) { + checkState(isConnected(), "{} websocket must be connected before requesting streams!", websocketName); + if (streamingMessageTypes == null || streamingMessageTypes.length == 0) { return; } @@ -215,6 +224,11 @@ public void subscriptions(StreamingMessageType... streamingMessageTypes) { requestObject.add("data", dataObject); websocket.send(requestObject.toString()); - LOGGER.info("Requested subscriptions: {}.", streamsArray); + LOGGER.info("Requested streams: {}.", streamsArray); + } + + @Override + public Collection streams() { + return new HashSet<>(listenedStreamMessageTypes); } } diff --git a/src/main/java/net/jacobpeterson/alpaca/websocket/streaming/StreamingWebsocketInterface.java b/src/main/java/net/jacobpeterson/alpaca/websocket/streaming/StreamingWebsocketInterface.java index f68ad50f..b2fae07c 100644 --- a/src/main/java/net/jacobpeterson/alpaca/websocket/streaming/StreamingWebsocketInterface.java +++ b/src/main/java/net/jacobpeterson/alpaca/websocket/streaming/StreamingWebsocketInterface.java @@ -3,15 +3,24 @@ import net.jacobpeterson.alpaca.model.endpoint.streaming.enums.StreamingMessageType; import net.jacobpeterson.alpaca.websocket.AlpacaWebsocketInterface; +import java.util.Collection; + /** * {@link StreamingWebsocketInterface} is an {@link AlpacaWebsocketInterface} for a {@link StreamingWebsocket}. */ public interface StreamingWebsocketInterface extends AlpacaWebsocketInterface { /** - * Sets subscribed {@link StreamingMessageType}s to streamingMessageTypes. + * Sets the {@link StreamingMessageType}s to streamingMessageTypes. * * @param streamingMessageTypes the {@link StreamingMessageType}s */ - void subscriptions(StreamingMessageType... streamingMessageTypes); + void streams(StreamingMessageType... streamingMessageTypes); + + /** + * Gets the {@link StreamingMessageType}s. + * + * @return a {@link Collection} of {@link StreamingMessageType} + */ + Collection streams(); } From f7a196380392d04a617b9452671b1602320aaa1c Mon Sep 17 00:00:00 2001 From: Jacob Peterson Date: Sun, 11 Jul 2021 15:46:12 -0600 Subject: [PATCH 129/271] Change auto-connection to subscription methods instead of addListener --- .../alpaca/websocket/AlpacaWebsocket.java | 29 ++++++++----------- .../websocket/AlpacaWebsocketInterface.java | 3 -- .../marketdata/MarketDataWebsocket.java | 5 +++- .../MarketDataWebsocketInterface.java | 4 +++ .../streaming/StreamingWebsocket.java | 6 +++- .../StreamingWebsocketInterface.java | 4 +++ 6 files changed, 29 insertions(+), 22 deletions(-) diff --git a/src/main/java/net/jacobpeterson/alpaca/websocket/AlpacaWebsocket.java b/src/main/java/net/jacobpeterson/alpaca/websocket/AlpacaWebsocket.java index ee8ede19..785e75a8 100644 --- a/src/main/java/net/jacobpeterson/alpaca/websocket/AlpacaWebsocket.java +++ b/src/main/java/net/jacobpeterson/alpaca/websocket/AlpacaWebsocket.java @@ -134,10 +134,6 @@ public boolean isAuthenticated() { @Override public void onOpen(@NotNull WebSocket webSocket, @NotNull Response response) { - if (websocketStateListener != null) { - websocketStateListener.onOpen(response); - } - LOGGER.info("{} websocket opened.", websocketName); LOGGER.debug("{} websocket response: {}", websocketName, response); @@ -149,14 +145,14 @@ public void onOpen(@NotNull WebSocket webSocket, @NotNull Response response) { } else { onConnection(); } - } - @Override - public void onClosed(@NotNull WebSocket webSocket, int code, @NotNull String reason) { if (websocketStateListener != null) { - websocketStateListener.onClosed(code, reason); + websocketStateListener.onOpen(response); } + } + @Override + public void onClosed(@NotNull WebSocket webSocket, int code, @NotNull String reason) { if (intentionalClose) { LOGGER.info("{} websocket closed.", websocketName); LOGGER.debug("Close code: {}, Reason: {}", code, reason); @@ -165,20 +161,24 @@ public void onClosed(@NotNull WebSocket webSocket, int code, @NotNull String rea LOGGER.error("{} websocket closed unintentionally! Code: {}, Reason: {}", websocketName, code, reason); handleReconnectionAttempt(); } + + if (websocketStateListener != null) { + websocketStateListener.onClosed(code, reason); + } } @Override public void onFailure(@NotNull WebSocket webSocket, @NotNull Throwable cause, @Nullable Response response) { + LOGGER.error("{} websocket failure! Response: {}", websocketName, response, cause); + handleReconnectionAttempt(); + if (websocketStateListener != null) { websocketStateListener.onFailure(cause); } - - LOGGER.error("{} websocket failure! Response: {}", websocketName, response, cause); - handleReconnectionAttempt(); } /** - * Attempts to reconnect the disconnected {@link #websocket}. + * Attempts to reconnect the disconnected {@link #websocket} asynchronously. */ private void handleReconnectionAttempt() { if (!automaticallyReconnect) { @@ -272,11 +272,6 @@ public void addListener(L listener) { synchronized (listenersLock) { listenersToAdd.add(listener); } - - if (!isConnected()) { - connect(); - waitForAuthorization(); - } } @Override diff --git a/src/main/java/net/jacobpeterson/alpaca/websocket/AlpacaWebsocketInterface.java b/src/main/java/net/jacobpeterson/alpaca/websocket/AlpacaWebsocketInterface.java index 22b2676e..290f60f4 100644 --- a/src/main/java/net/jacobpeterson/alpaca/websocket/AlpacaWebsocketInterface.java +++ b/src/main/java/net/jacobpeterson/alpaca/websocket/AlpacaWebsocketInterface.java @@ -65,9 +65,6 @@ default boolean isValid() { /** * Adds a {@link AlpacaWebsocketMessageListener}. - *
- * Note that this will call {@link AlpacaWebsocketInterface#connect()} if the {@link AlpacaWebsocketMessageListener} - * is not connected already. * * @param listener the {@link AlpacaWebsocketMessageListener} */ diff --git a/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/MarketDataWebsocket.java b/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/MarketDataWebsocket.java index 1a9accbe..f7c34f78 100644 --- a/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/MarketDataWebsocket.java +++ b/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/MarketDataWebsocket.java @@ -284,7 +284,10 @@ public void unsubscribe(Collection tradeSymbols, Collection quot */ private void sendSubscriptionUpdate(Collection tradeSymbols, Collection quoteSymbols, Collection barSymbols, boolean subscribe) { - checkState(isConnected(), "{} websocket must be connected before requesting streams!", websocketName); + if (!isConnected()) { + connect(); + waitForAuthorization(); + } /* Format of message is: * { diff --git a/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/MarketDataWebsocketInterface.java b/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/MarketDataWebsocketInterface.java index eac2c815..6c4480ce 100644 --- a/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/MarketDataWebsocketInterface.java +++ b/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/MarketDataWebsocketInterface.java @@ -28,6 +28,10 @@ public interface MarketDataWebsocketInterface extends AlpacaWebsocketInterface * Note that any one of the given {@link Collection}s can contain the wildcard character e.g. "*" to subscribe to * ALL available symbols. + *
+ * Note that this will call {@link MarketDataWebsocketInterface#connect()} and {@link + * MarketDataWebsocketInterface#waitForAuthorization()} if {@link MarketDataWebsocketInterface#isConnected()} + * returns false. * * @param tradeSymbols a {@link Collection} of symbols to subscribe to trades or null for no change * @param quoteSymbols a {@link Collection} of symbols to subscribe to quotes or null for no change diff --git a/src/main/java/net/jacobpeterson/alpaca/websocket/streaming/StreamingWebsocket.java b/src/main/java/net/jacobpeterson/alpaca/websocket/streaming/StreamingWebsocket.java index 1d3b8c08..704bbff4 100644 --- a/src/main/java/net/jacobpeterson/alpaca/websocket/streaming/StreamingWebsocket.java +++ b/src/main/java/net/jacobpeterson/alpaca/websocket/streaming/StreamingWebsocket.java @@ -193,11 +193,15 @@ private boolean isAuthorizationMessageSuccess(AuthorizationMessage authorization @Override public void streams(StreamingMessageType... streamingMessageTypes) { - checkState(isConnected(), "{} websocket must be connected before requesting streams!", websocketName); if (streamingMessageTypes == null || streamingMessageTypes.length == 0) { return; } + if (!isConnected()) { + connect(); + waitForAuthorization(); + } + // Stream request format: // { // "action": "listen", diff --git a/src/main/java/net/jacobpeterson/alpaca/websocket/streaming/StreamingWebsocketInterface.java b/src/main/java/net/jacobpeterson/alpaca/websocket/streaming/StreamingWebsocketInterface.java index b2fae07c..9b2dc5ca 100644 --- a/src/main/java/net/jacobpeterson/alpaca/websocket/streaming/StreamingWebsocketInterface.java +++ b/src/main/java/net/jacobpeterson/alpaca/websocket/streaming/StreamingWebsocketInterface.java @@ -12,6 +12,10 @@ public interface StreamingWebsocketInterface extends AlpacaWebsocketInterfacestreamingMessageTypes
. + *
+ * Note that this will call {@link StreamingWebsocketInterface#connect()} and {@link + * StreamingWebsocketInterface#waitForAuthorization()} if {@link StreamingWebsocketInterface#isConnected()} returns + * false. * * @param streamingMessageTypes the {@link StreamingMessageType}s */ From 2e1a813153170bd4627fc99132deffab3563814e Mon Sep 17 00:00:00 2001 From: Jacob Peterson Date: Sun, 11 Jul 2021 21:20:44 -0600 Subject: [PATCH 130/271] Update README completely, refactor, add LocalTimeAdapter --- README.md | 549 ++++++++---------- .../net/jacobpeterson/alpaca/AlpacaAPI.java | 8 +- .../endpoint/AccountActivitiesEndpoint.java | 4 +- .../AccountConfigurationEndpoint.java | 4 +- .../alpaca/rest/endpoint/AccountEndpoint.java | 4 +- ...tractEndpoint.java => AlpacaEndpoint.java} | 8 +- .../alpaca/rest/endpoint/AssetsEndpoint.java | 4 +- .../rest/endpoint/CalendarEndpoint.java | 4 +- .../alpaca/rest/endpoint/ClockEndpoint.java | 4 +- .../rest/endpoint/MarketDataEndpoint.java | 4 +- .../alpaca/rest/endpoint/OrdersEndpoint.java | 4 +- .../endpoint/PortfolioHistoryEndpoint.java | 4 +- .../rest/endpoint/PositionsEndpoint.java | 4 +- .../rest/endpoint/WatchlistEndpoint.java | 4 +- .../alpaca/util/gson/GsonUtil.java | 2 + .../alpaca/util/gson/LocalDateAdapter.java | 2 +- .../alpaca/util/gson/LocalTimeAdapter.java | 24 + .../util/gson/ZonedDateTimeAdapter.java | 2 +- .../marketdata/MarketDataWebsocket.java | 6 +- .../MarketDataWebsocketInterface.java | 10 +- src/main/resources/alpaca.default.properties | 4 +- 21 files changed, 303 insertions(+), 356 deletions(-) rename src/main/java/net/jacobpeterson/alpaca/rest/endpoint/{AbstractEndpoint.java => AlpacaEndpoint.java} (65%) create mode 100644 src/main/java/net/jacobpeterson/alpaca/util/gson/LocalTimeAdapter.java diff --git a/README.md b/README.md index f2e1cbb2..9bbfed2b 100644 --- a/README.md +++ b/README.md @@ -10,7 +10,7 @@ # Overview This is a Java implementation for the Alpaca API. Alpaca lets you trade with algorithms, connect with apps, and build services all with a commission-free stock trading API. This library is community developed and if you have any questions, please ask them on [Github Discussions](https://github.com/Petersoj/alpaca-java/discussions), the [Alpaca Slack #dev-alpaca-java channel](https://alpaca.markets/slack), or on the [Alpaca Forums](https://forum.alpaca.markets/). -Give this repository a star ⭐ if it helped you build an awesome trading algorithm in Java! +Give this repository a star ⭐ if it helped you build a trading algorithm in Java! # Gradle and Maven Integration If you are using Gradle as your build tool, add the following dependency to your `build.gradle` file: @@ -37,18 +37,23 @@ Note that you don't have to use the Maven Central artifacts and instead can just # Configuration Creating an `alpaca.properties` file on the classpath with the following format allows you to easily load properties using the `AlpacaAPI` default constructor: ``` -key_id = -secret = +key_id = +secret_key = endpoint_api_type = data_api_type = ``` -The default values for `alpaca.properties` can be found [here](https://github.com/Petersoj/alpaca-java/tree/master/src/main/resources). +The default values for `alpaca.properties` can be found [here](src/main/resources/alpaca.default.properties). # Logger For logging, this library uses [SLF4j](http://www.slf4j.org/) which serves as an interface for various logging frameworks. This enables you to use whatever logging framework you would like. However, if you do not add a logging framework as a dependency in your project, the console will output a message stating that SLF4j is defaulting to a no-operation (NOP) logger implementation. To enable logging, add a logging framework of your choice as a dependency to your project such as [Log4j 2](http://logging.apache.org/log4j/2.x/index.html), [SLF4j-simple](http://www.slf4j.org/manual.html), or [Apache Commons Logging](https://commons.apache.org/proper/commons-logging/). -# AlpacaAPI -The `AlpacaAPI` class contains several methods to interface with Alpaca. You will generally only need one instance of it in your application. Note that many methods allow `null` to be passed in as a parameter if it is optional. The Alpaca API specification is located [here](https://docs.alpaca.markets/api-documentation/api-v2/) and the `AlpacaAPI` Javadoc is located [here](https://javadoc.io/doc/net.jacobpeterson/alpaca-java/latest/net/jacobpeterson/alpaca/AlpacaAPI.html). +# Examples +Note that the examples below are not exhaustive. Refer to the [Javadoc](https://javadoc.io/doc/net.jacobpeterson/alpaca-java) for all classes and method signatures. + +## [`AlpacaAPI`](src/main/java/net/jacobpeterson/alpaca/AlpacaAPI.java) +The [`AlpacaAPI`](src/main/java/net/jacobpeterson/alpaca/AlpacaAPI.java) class contains several instances of various [`AlpacaEndpoints`](src/main/java/net/jacobpeterson/alpaca/rest/endpoint/AlpacaEndpoint.java) and [`AlpacaWebsockets`](src/main/java/net/jacobpeterson/alpaca/websocket/AlpacaWebsocket.java) to interface with Alpaca. You will generally only need one instance of this class in your application. Note that many methods inside the various [`AlpacaEndpoints`](src/main/java/net/jacobpeterson/alpaca/rest/endpoint/AlpacaEndpoint.java) allow `null` to be passed in as a parameter if it is optional. + +The Alpaca API specification is located [here](https://docs.alpaca.markets/api-documentation/api-v2/) and the [`AlpacaAPI`](src/main/java/net/jacobpeterson/alpaca/AlpacaAPI.java) Javadoc is located [here](https://javadoc.io/doc/net.jacobpeterson/alpaca-java/latest/net/jacobpeterson/alpaca/AlpacaAPI.html). Example usage: ```java @@ -57,7 +62,7 @@ AlpacaAPI alpacaAPI = new AlpacaAPI(); // This constructor passes in a 'keyID' and 'secret' String String keyID = ""; -String secret = ""; +String secretKey = ""; AlpacaAPI alpacaAPI = new AlpacaAPI(keyID, secret); // This constructor is for OAuth tokens @@ -65,184 +70,192 @@ String oAuthToken = ""; AlpacaAPI alpacaAPI = new AlpacaAPI(oAuthToken); ``` -Note that the example uses of the `AlpacaAPI` class methods below are not exhaustive. Refer to the `AlpacaAPI` [Javadoc](https://javadoc.io/doc/net.jacobpeterson/alpaca-java/latest/net/jacobpeterson/alpaca/AlpacaAPI.html) for all method signatures. +Note that this library uses [OkHttp](https://square.github.io/okhttp/) as its HTTP client which creates background threads to service requests. These threads persist even if the main thread exists so if you want to destroy these threads when you're done using [`AlpacaAPI`](src/main/java/net/jacobpeterson/alpaca/AlpacaAPI.java) so your program can exit without calling `System.exit()`, use the following snippet: +```java +alpacaAPI.getOkHttpClient().dispatcher().executorService().shutdown(); +alpacaAPI.getOkHttpClient().connectionPool().evictAll(); +``` + +See the [OkHttpClient Documentation](https://square.github.io/okhttp/4.x/okhttp/okhttp3/-ok-http-client/#shutdown-isnt-necessary) for more information. -## AlpacaAPIRequestException -The `AlpacaAPIRequestException` is thrown anytime an exception occurs when using various `AlpacaAPI` methods. It should be caught and handled within your trading algorithm application accordingly. +## [`AlpacaClientException`](src/main/java/net/jacobpeterson/alpaca/rest/AlpacaClientException.java) +[`AlpacaClientException`](src/main/java/net/jacobpeterson/alpaca/rest/AlpacaClientException.java) is thrown anytime an exception occurs when using various [`AlpacaEndpoints`](src/main/java/net/jacobpeterson/alpaca/rest/endpoint/AlpacaEndpoint.java). It should be caught and handled within your trading algorithm application accordingly. -## Account +## [`AccountEndpoint`](src/main/java/net/jacobpeterson/alpaca/rest/endpoint/AccountEndpoint.java) The Account API serves important information related to an account, including account status, funds available for trade, funds available for withdrawal, and various flags relevant to an account's ability to trade. Example usage: ```java try { - // Get 'Account' information - Account account = alpacaAPI.getAccount(); -} catch (AlpacaAPIRequestException e) { - e.printStackTrace(); + // Get 'Account' information and print it out + Account account = alpacaAPI.account().get(); + System.out.println(account); +} catch (AlpacaClientException exception) { + exception.printStackTrace(); } ``` -## AccountActivity -The Account Activities API provides access to a historical record of transaction activities that have impacted your account. +## [`MarketDataEndpoint`](src/main/java/net/jacobpeterson/alpaca/rest/endpoint/MarketDataEndpoint.java) +The Data API v2 provides market data through an easy-to-use API for historical data. Example usage: ```java try { - // Print all 'AccountActivity's on 12/23/2020 - List accountActivities = alpacaAPI.getAccountActivities( - null, - null, - ZonedDateTime.of(2020, 12, 23, 0, 0, 0, 0, ZoneId.of("America/New_York")), - SortDirection.ASCENDING, + // Get AAPL one hour bars from 7/6/2021 market open + // to 7/8/2021 market close and print them out + BarsResponse aaplBarsResponse = alpacaAPI.marketData().getBars( + "AAPL", + ZonedDateTime.of(2021, 7, 6, 9, 30, 0, 0, ZoneId.of("America/New_York")), + ZonedDateTime.of(2021, 7, 8, 12 + 4, 0, 0, 0, ZoneId.of("America/New_York")), null, null, - (ActivityType[]) null); - for (AccountActivity accountActivity : accountActivities) { - if (accountActivity instanceof TradeActivity) { - System.out.println("TradeActivity: " + (TradeActivity) accountActivity); - } else if (accountActivity instanceof NonTradeActivity) { - System.out.println("NonTradeActivity: " + (NonTradeActivity) accountActivity); - } - } -} catch (AlpacaAPIRequestException e) { - e.printStackTrace(); -} -``` + BarsTimeFrame.ONE_HOUR); + aaplBarsResponse.getBars().forEach(System.out::println); -## AccountConfiguration -The Account Configuration API provides custom configurations about your trading account settings. These configurations control various allow you to modify settings to suit your trading needs. - -Example usage: -```java -try { - // Update the 'AccountConfiguration' to block new orders - AccountConfiguration accountConfiguration = alpacaAPI.getAccountConfiguration(); - accountConfiguration.setSuspendTrade(true); - - alpacaAPI.setAccountConfiguration(accountConfiguration); -} catch (AlpacaAPIRequestException e) { - e.printStackTrace(); + // Get AAPL first 10 trades on 7/8/2021 at market open and print them out + TradesResponse aaplTradesResponse = alpacaAPI.marketData().getTrades( + "AAPL", + ZonedDateTime.of(2021, 7, 8, 9, 30, 0, 0, ZoneId.of("America/New_York")), + ZonedDateTime.of(2021, 7, 8, 9, 31, 0, 0, ZoneId.of("America/New_York")), + 10, + null); + aaplTradesResponse.getTrades().forEach(System.out::println); + + // Print out latest AAPL trade + Trade latestAAPLTrade = alpacaAPI.marketData().getLatestTrade("AAPL").getTrade(); + System.out.printf("Latest AAPL Trade: %s\n", latestAAPLTrade); + + // Print out snapshot of AAPL, GME, and TSLA + Map snapshots = alpacaAPI.marketData() + .getSnapshots(Arrays.asList("AAPL", "GME", "TSLA")); + snapshots.forEach((symbol, snapshot) -> + System.out.printf("Symbol: %s\nSnapshot: %s\n\n", symbol, snapshot)); +} catch (AlpacaClientException exception) { + exception.printStackTrace(); } ``` -## Orders -The Orders API allows a user to monitor, place and cancel their orders with Alpaca. Each order has a unique identifier provided by the client. This client-side unique order ID will be automatically generated by the system if not provided by the client, and will be returned as part of the order object along with the rest of the fields described below. Once an order is placed, it can be queried using the client-side order ID to check the status. Updates on open orders at Alpaca will also be sent over the streaming interface, which is the recommended method of maintaining order state. +## [`OrdersEndpoint`](src/main/java/net/jacobpeterson/alpaca/rest/endpoint/OrdersEndpoint.java) +The Orders API allows a user to monitor, place, and cancel their orders with Alpaca. Example usage: ```java try { - // Print all the 'Order's of AAPL and TSLA after 12/23/2020 - // in ascending (oldest to newest) order - List orders = alpacaAPI.getOrders( - OrderStatus.ALL, + // Print all the 'Order's of AAPL and TSLA until 7/6/2021 in + // ascending (oldest to newest) order + List orders = alpacaAPI.orders().get( + CurrentOrderStatus.ALL, null, - ZonedDateTime.of(2020, 12, 23, 0, 0, 0, 0, ZoneId.of("America/New_York")), null, + ZonedDateTime.of(2021, 7, 6, 0, 0, 0, 0, ZoneId.of("America/New_York")), SortDirection.ASCENDING, true, Arrays.asList("AAPL", "TSLA")); orders.forEach(System.out::println); - + // Request a new limit order for TSLA for 100 shares at a limit price - // of 600.00 and TIF of DAY. - Order limitOrderTSLA = alpacaAPI.requestNewLimitOrder( + // of $653.00 and TIF of DAY. + Order tslaLimitOrder = alpacaAPI.orders().requestLimitOrder( "TSLA", 100, OrderSide.BUY, OrderTimeInForce.DAY, - 600.00, + 653.00, false); - - // Check if my TSLA limit order has filled 5 seconds later and if it hasn't + System.out.printf("Requested %s %s order at %s\n", + tslaLimitOrder.getSymbol(), + tslaLimitOrder.getType(), + tslaLimitOrder.getSubmittedAt()); + + // Check if TSLA limit order has filled 5 seconds later and if it hasn't // replace it with a market order so it fills! Thread.sleep(5000); - limitOrderTSLA = alpacaAPI.getOrder(limitOrderTSLA.getId(), false); - if (limitOrderTSLA.getFilledAt() == null) { - Order replacedOrder = alpacaAPI.replaceOrder( - limitOrderTSLA.getId(), + tslaLimitOrder = alpacaAPI.orders().get(tslaLimitOrder.getId(), false); + if (tslaLimitOrder.getFilledAt() == null && tslaLimitOrder.getStatus().equals(OrderStatus.NEW)) { + Order replacedOrder = alpacaAPI.orders().replace( + tslaLimitOrder.getId(), 100, OrderTimeInForce.DAY, - null, - null, - null, - null); + null, null, null, null); + System.out.printf("Replaced TSLA order: %s\n", replacedOrder); } - - // Cancel all open orders - List cancelledOrders = alpacaAPI.cancelAllOrders(); - for (CancelledOrder cancelledOrder : cancelledOrders) { - System.out.println("Cancelled Order: " + cancelledOrder.getOrder()); - } - - // Request a new fractional market order for 0.5 shares of GME - alpacaAPI.requestNewFractionalMarketOrder("GME", 0.5, OrderSide.BUY); + + // Cancel all open orders after 2 seconds + Thread.sleep(2000); + List cancelledOrders = alpacaAPI.orders().cancelAll(); + cancelledOrders.forEach((cancelledOrder) -> System.out.printf("Cancelled: %s\n", cancelledOrder)); + + // Request a new fractional market order for 0.5 shares of GME + alpacaAPI.orders().requestFractionalMarketOrder("GME", 0.5, OrderSide.BUY); // Request a new notional market order for $25 worth of GME shares - alpacaAPI.requestNewNotionalMarketOrder("GME", 25d, OrderSide.BUY); -} catch (AlpacaAPIRequestException e) { - e.printStackTrace(); + alpacaAPI.orders().requestNotionalMarketOrder("GME", 25d, OrderSide.BUY); +} catch (AlpacaClientException | InterruptedException exception) { + exception.printStackTrace(); } ``` -## Positions -The Positions API provides information about an account's current open positions. The response will include information such as cost basis, shares traded, and market value, which will be updated live as price information is updated. Once a position is closed, it will no longer be queryable through this API. +## [`PositionsEndpoint`](src/main/java/net/jacobpeterson/alpaca/rest/endpoint/PositionsEndpoint.java) +The Positions API provides information about an account's current open positions. Example usage: ```java try { - // Cancel TSLA and AAPL positions if they are open - List openPositions = alpacaAPI.getOpenPositions(); + // Close 50% of all open TSLA and AAPL positions + List openPositions = alpacaAPI.positions().get(); for (Position openPosition : openPositions) { - if (openPosition.getSymbol().equals("TSLA") || - openPosition.getSymbol().equals("AAPL")) { - Order closePositionOrder = alpacaAPI.closePosition(openPosition.getSymbol()); - System.out.println("Closing position: " + closePositionOrder); + if (openPosition.getSymbol().equals("TSLA") || openPosition.getSymbol().equals("AAPL")) { + Order closePositionOrder = alpacaAPI.positions() + .close(openPosition.getSymbol(), null, 50d); + System.out.printf("Closing 50%% of %s position: %s\n", + openPosition.getSymbol(), closePositionOrder); } } -} catch (AlpacaAPIRequestException e) { - e.printStackTrace(); +} catch (AlpacaClientException exception) { + exception.printStackTrace(); } ``` -## Assets -The Assets API serves as the master list of assets available for trade and data consumption from Alpaca. Assets are sorted by asset class, exchange and symbol. +## [`AssetsEndpoint`](src/main/java/net/jacobpeterson/alpaca/rest/endpoint/AssetsEndpoint.java) +The Assets API serves as the master list of assets available for trade and data consumption from Alpaca. Example usage: ```java try { // Print out a CSV of all active US equity 'Asset's - List activeUSEquities = alpacaAPI.getAssets(AssetStatus.ACTIVE, "us_equity"); + List activeUSEquities = alpacaAPI.assets().get(AssetStatus.ACTIVE, null); System.out.println(activeUSEquities .stream().map(Asset::getSymbol) .collect(Collectors.joining(", "))); - + // Print out TSLA 'Asset' information - Asset tslaAsset = alpacaAPI.getAssetBySymbol("TSLA"); + Asset tslaAsset = alpacaAPI.assets().getBySymbol("TSLA"); System.out.println(tslaAsset); -} catch (AlpacaAPIRequestException e) { - e.printStackTrace(); +} catch (AlpacaClientException exception) { + exception.printStackTrace(); } ``` -## Watchlist -The Watchlist API provides CRUD operation for the account's watchlist. An account can have multiple watchlists and each is uniquely identified by ID but can also be addressed by user-defined name. Each watchlist is an ordered list of assets. +## [`WatchlistEndpoint`](src/main/java/net/jacobpeterson/alpaca/rest/endpoint/WatchlistEndpoint.java) +The Watchlist API provides CRUD operation for the account's watchlist. Example usage: ```java try { - // Print out all 'Watchlist' names - List watchlists = alpacaAPI.getWatchlists(); - watchlists.forEach(watchlist -> System.out.println(watchlist.getName())); + // Print out CSV all 'Watchlist' names + List watchlists = alpacaAPI.watchlist().get(); + System.out.println(watchlists.stream() + .map(Watchlist::getName) + .collect(Collectors.joining(", "))); // Create a watchlist for day trading with TSLA and AAPL initially - Watchlist dayTradeWatchlist = alpacaAPI.createWatchlist("Day Trade", "TSLA", "AAPL"); + Watchlist dayTradeWatchlist = alpacaAPI.watchlist().create("Day Trade", "TSLA", "AAPL"); // Remove TSLA and add MSFT to 'dayTradeWatchlist' - alpacaAPI.removeSymbolFromWatchlist(dayTradeWatchlist.getId(), "TSLA"); - alpacaAPI.addWatchlistAsset(dayTradeWatchlist.getId(), "MSFT"); + alpacaAPI.watchlist().removeSymbol(dayTradeWatchlist.getId(), "TSLA"); + alpacaAPI.watchlist().addAsset(dayTradeWatchlist.getId(), "MSFT"); // Set the updated watchlist variable - dayTradeWatchlist = alpacaAPI.getWatchlist(dayTradeWatchlist.getId()); + dayTradeWatchlist = alpacaAPI.watchlist().get(dayTradeWatchlist.getId()); // Print CSV of 'dayTradeWatchlist' 'Asset's System.out.println(dayTradeWatchlist.getAssets() @@ -250,271 +263,177 @@ try { .collect(Collectors.joining(", "))); // Delete the 'dayTradeWatchlist' - alpacaAPI.deleteWatchlist(dayTradeWatchlist.getId()); -} catch (AlpacaAPIRequestException e) { - e.printStackTrace(); -} -``` - -## PortfolioHistory -The portfolio history API returns the timeseries data for equity and profit loss information of the account. - -Example usage: -```java -try { - // Get the 'PortfolioHistory' and print out static information - PortfolioHistory portfolioHistory = alpacaAPI.getPortfolioHistory( - 3, - PortfolioPeriodUnit.DAY, - PortfolioTimeFrame.ONE_HOUR, - LocalDate.of(2020, 12, 23), - false); - System.out.printf("Timeframe: %s, Base value: %s \n", - portfolioHistory.getTimeframe(), - portfolioHistory.getBaseValue()); - - // Loop through all indices and print the dynamic historical information uniformly - int historyUnitSize = portfolioHistory.getTimestamp().size(); - for (int historyIndex = 0; historyIndex < historyUnitSize; historyIndex++) { - System.out.printf("Timestamp: %s, Equity: %s, PnL: %s, PnL%%: %s \n", - portfolioHistory.getTimestamp().get(historyIndex), - portfolioHistory.getEquity().get(historyIndex), - portfolioHistory.getProfitLoss().get(historyIndex), - portfolioHistory.getProfitLossPct().get(historyIndex)); - } -} catch (AlpacaAPIRequestException e) { - e.printStackTrace(); + alpacaAPI.watchlist().delete(dayTradeWatchlist.getId()); +} catch (AlpacaClientException exception) { + exception.printStackTrace(); } ``` -## Calendar -The calendar API serves the full list of market days from 1970 to 2029. It can also be queried by specifying a start and/or end time to narrow down the results. In addition to the dates, the response also contains the specific open and close times for the market days, taking into account early closures. +## [`CalendarEndpoint`](src/main/java/net/jacobpeterson/alpaca/rest/endpoint/CalendarEndpoint.java) +The calendar API serves the full list of market days from 1970 to 2029. Example usage: ```java try { - // Get the 'Calendar' of week of Christmas and print out information - List calendar = alpacaAPI.getCalendar( + // Get the 'Calendar's of the week of Christmas 2020 and print them out + List calendar = alpacaAPI.calendar().get( LocalDate.of(2020, 12, 20), LocalDate.of(2020, 12, 27)); calendar.forEach(System.out::println); -} catch (AlpacaAPIRequestException e) { - e.printStackTrace(); +} catch (AlpacaClientException exception) { + exception.printStackTrace(); } ``` -## Clock +## [`ClockEndpoint`](src/main/java/net/jacobpeterson/alpaca/rest/endpoint/ClockEndpoint.java) The clock API serves the current market timestamp, whether the market is currently open, as well as the times of the next market open and close. Example usage: ```java try { // Get the market 'Clock' and print it out - Clock clock = alpacaAPI.getClock(); + Clock clock = alpacaAPI.clock().get(); System.out.println(clock); -} catch (AlpacaAPIRequestException e) { - e.printStackTrace(); +} catch (AlpacaClientException exception) { + exception.printStackTrace(); } ``` -## Alpaca Streaming -Alpaca offers WebSocket streaming for account and order updates. +## [`AccountConfigurationEndpoint`](src/main/java/net/jacobpeterson/alpaca/rest/endpoint/AccountConfigurationEndpoint.java) +The Account Configuration API provides custom configurations about your trading account settings. These configurations control various allow you to modify settings to suit your trading needs. Example usage: ```java try { - // List to account updates and trade updates from Alpaca and print their messages out - AlpacaStreamListener alpacaStreamListener = new AlpacaStreamListenerAdapter( - AlpacaStreamMessageType.ACCOUNT_UPDATES, - AlpacaStreamMessageType.TRADE_UPDATES) { - @Override - public void onStreamUpdate(AlpacaStreamMessageType streamMessageType, - AlpacaStreamMessage streamMessage) { - switch (streamMessageType) { - case ACCOUNT_UPDATES: - System.out.println((AccountUpdateMessage) streamMessage); - break; - case TRADE_UPDATES: - System.out.println((TradeUpdateMessage) streamMessage); - break; - } - } - }; - - // Add the 'AlpacaStreamListener' - // Note that when the first 'AlpacaStreamListener' is added, the Websocket - // connection is created. - alpacaAPI.addAlpacaStreamListener(alpacaStreamListener); - - // Wait for 5 seconds - Thread.sleep(5000); - - // Remove the 'AlpacaStreamListener' - // Note that when the last 'AlpacaStreamListener' is removed, the Websocket - // connection is closed. - alpacaAPI.removeAlpacaStreamListener(alpacaStreamListener); -} catch (WebsocketException exception) { + // Update the 'AccountConfiguration' to block new orders + AccountConfiguration accountConfiguration = alpacaAPI.accountConfiguration().get(); + accountConfiguration.setSuspendTrade(true); + alpacaAPI.accountConfiguration().set(accountConfiguration); +} catch (AlpacaClientException exception) { exception.printStackTrace(); } ``` -## Trades -The Trades API provides historical trade data for a given ticker symbol in a specified date range. +## [`AccountActivitiesEndpoint`](src/main/java/net/jacobpeterson/alpaca/rest/endpoint/AccountActivitiesEndpoint.java) +The Account Activities API provides access to a historical record of transaction activities that have impacted your account. Example usage: ```java try { - // Get first 100 trades of AAPL of first minute on 3/1/2021 and print them out - TradesResponse appleTradesResponse = alpacaAPI.getTrades( - "AAPL", - ZonedDateTime.of(2021, 3, 1, 9, 30, 0, 0, ZoneId.of("America/New_York")), - ZonedDateTime.of(2021, 3, 1, 9, 31, 0, 0, ZoneId.of("America/New_York")), - 100, - null); - appleTradesResponse.getTrades().forEach(System.out::println); -} catch (AlpacaAPIRequestException e) { - e.printStackTrace(); + // Print all order fill and cash deposit 'AccountActivity's on 7/8/2021 + List accountActivities = alpacaAPI.accountActivities().get( + ZonedDateTime.of(2021, 7, 8, 0, 0, 0, 0, ZoneId.of("America/New_York")), + null, + null, + SortDirection.ASCENDING, + null, + null, + ActivityType.FILL, ActivityType.CSD); + for (AccountActivity accountActivity : accountActivities) { + if (accountActivity instanceof TradeActivity) { + System.out.println("TradeActivity: " + (TradeActivity) accountActivity); + } else if (accountActivity instanceof NonTradeActivity) { + System.out.println("NonTradeActivity: " + (NonTradeActivity) accountActivity); + } + } +} catch (AlpacaClientException exception) { + exception.printStackTrace(); } ``` -## Latest Trade -The Latest trade API provides the latest trade data for a given ticker symbol. +## [`PortfolioHistoryEndpoint`](src/main/java/net/jacobpeterson/alpaca/rest/endpoint/PortfolioHistoryEndpoint.java) +The Portfolio History API returns the timeseries data for equity and profit loss information of the account. Example usage: ```java try { - LatestTradeResponse latestAAPLTrade = alpacaAPI.getLatestTrade("AAPL"); - System.out.println(latestAAPLTrade); -} catch (AlpacaAPIRequestException e) { - e.printStackTrace(); + // Get 3 days of one-hour 'PortfolioHistory' on 7/8/2021 and print out its data points + PortfolioHistory portfolioHistory = alpacaAPI.portfolioHistory().get( + 3, + PortfolioPeriodUnit.DAY, + PortfolioTimeFrame.ONE_HOUR, + LocalDate.of(2021, 7, 8), + false); + System.out.printf("Timeframe: %s, Base value: %s \n", + portfolioHistory.getTimeframe(), + portfolioHistory.getBaseValue()); + portfolioHistory.getDataPoints().forEach(System.out::println); +} catch (AlpacaClientException exception) { + exception.printStackTrace(); } ``` -## Quotes -The Quotes API provides NBBO quotes for a given ticker symbol in a specified date range. +## [`StreamingWebsocket`](src/main/java/net/jacobpeterson/alpaca/websocket/streaming/StreamingWebsocket.java) +Alpaca offers WebSocket streaming of order updates. Example usage: ```java -try { - // Get first 100 quotes of AAPL of first minute on 3/1/2021 and print them out - QuotesResponse appleQuotesResponse = alpacaAPI.getQuotes( - "AAPL", - ZonedDateTime.of(2021, 3, 1, 9, 30, 0, 0, ZoneId.of("America/New_York")), - ZonedDateTime.of(2021, 3, 1, 9, 31, 0, 0, ZoneId.of("America/New_York")), - 100, - null); - appleQuotesResponse.getQuotes().forEach(System.out::println); -} catch (AlpacaAPIRequestException e) { - e.printStackTrace(); -} -``` - -## Latest Quote -The Latest quote API provides the latest quote data for a given ticker symbol. +// Add a 'StreamingListener' that simply prints streaming information +StreamingListener streamingListener = (messageType, message) -> + System.out.printf("%s: %s\n", messageType.name(), message); +alpacaAPI.streaming().addListener(streamingListener); -Example usage: -```java -try { - LatestQuoteResponse latestAAPLQuote = alpacaAPI.getLatestQuote("AAPL"); - System.out.println(latestAAPLQuote); -} catch (AlpacaAPIRequestException e) { - e.printStackTrace(); -} -``` +// Listen to the 'authorization' and 'trade update' streams. +// Note this will connect the websocket and wait for authorization +// if it isn't already connected. +alpacaAPI.streaming().streams( + StreamingMessageType.AUTHORIZATION, + StreamingMessageType.TRADE_UPDATES); -## Bars -The bars API returns aggregate historical data for the requested securities. +// Wait for 5 seconds +Thread.sleep(5000); -Example usage: -```java -try { - // Get hour bars of AAPL from 2/22/2021 at 9:30 AM to 2/24/2021 at 4 PM and print them out - BarsResponse appleBarsResponse = alpacaAPI.getBars( - "AAPL", - ZonedDateTime.of(2021, 2, 22, 9, 30, 0, 0, ZoneId.of("America/New_York")), - ZonedDateTime.of(2021, 2, 24, 12 + 4, 0, 0, 0, ZoneId.of("America/New_York")), - null, - null, - BarsTimeFrame.HOUR); - appleBarsResponse.getBars().forEach(System.out::println); -} catch (AlpacaAPIRequestException e) { - e.printStackTrace(); -} +// Note that when the last 'StreamingListener' is removed, the websocket +// connection is closed. +alpacaAPI.streaming().removeListener(streamingListener); ``` -## Snapshot -The Snapshot API for multiple tickers provides the latest trade, latest quote, minute bar daily bar and previous daily bar data for the given ticker symbols. - -Example usage: +The following methods show how you can control the state of the [`StreamingWebsocket`](src/main/java/net/jacobpeterson/alpaca/websocket/streaming/StreamingWebsocket.java) directly. ```java -try { - Map snapshotsOfSymbols = alpacaAPI.getSnapshots(Arrays.asList("AAPL", "TSLA", "GME")); - snapshotsOfSymbols.entrySet().forEach(System.out::println); - - Snapshot msftSnapshot = alpacaAPI.getSnapshot("MSFT"); - System.out.println(msftSnapshot); -} catch (AlpacaAPIRequestException e) { - e.printStackTrace(); -} +alpacaAPI.streaming().connect(); +alpacaAPI.streaming().waitForAuthorization(); +System.out.println(alpacaAPI.streaming().isValid()); +alpacaAPI.streaming().disconnect(); ``` -## Realtime Market Data -Alpaca's Data API provides websocket streaming for trades, quotes and minute bars. This helps receive the most up to date market information that could help your trading strategy to act upon certain market movement. +## [`MarketDataWebsocket`](src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/MarketDataWebsocket.java) +Alpaca's Data API v2 provides websocket streaming for trades, quotes, and minute bars. This helps receive the most up to date market information that could help your trading strategy to act upon certain market movement. Example usage: ```java -try { - // Listen to TSLA quotes, trades, and minute bars and print their messages out - MarketDataListener listenerTSLA = new MarketDataListenerAdapter( - "TSLA", - MarketDataMessageType.TRADE, - MarketDataMessageType.QUOTE, - MarketDataMessageType.BAR) { - @Override - public void onStreamUpdate(MarketDataMessageType streamMessageType, MarketDataMessage streamMessage) { - switch (streamMessageType) { - case TRADE: - TradeMessage tradeMessage = (TradeMessage) streamMessage; - System.out.printf("Trade: Price=%.2f Size=%d Time=%s\n", - tradeMessage.getPrice(), tradeMessage.getSize(), tradeMessage.getTimestamp()); - break; - case QUOTE: - QuoteMessage quoteMessage = (QuoteMessage) streamMessage; - System.out.printf("Quote: Ask Price=%.2f Ask Size=%d Bid Price=%.2f Bid Size=%d Time=%s\n", - quoteMessage.getAskPrice(), - quoteMessage.getAskSize(), - quoteMessage.getBidPrice(), - quoteMessage.getBidSize(), - quoteMessage.getTimestamp()); - break; - case BAR: - BarMessage barMessage = (BarMessage) streamMessage; - System.out.printf("Bar: O=%.2f H=%.2f L=%.2f C=%.2f Time=%s \n", - barMessage.getOpen(), - barMessage.getHigh(), - barMessage.getLow(), - barMessage.getClose(), - barMessage.getTimestamp()); - break; - } - } - }; - - // Add the 'MarketDataListener' - // Note that when the first 'MarketDataListener' is added, the Websocket - // connection is created. - alpacaAPI.addMarketDataStreamListener(listenerTSLA); - - // Wait for 5 seconds - Thread.sleep(5000); - - // Remove the 'MarketDataListener' - // Note that when the last 'MarketDataListener' is removed, the Websocket - // connection is closed. - alpacaAPI.removeMarketDataStreamListener(listenerTSLA); -} catch (WebsocketException exception) { - exception.printStackTrace(); -} +// Add a 'MarketDataListener' that simply prints market data information +MarketDataListener marketDataListener = (messageType, message) -> + System.out.printf("%s: %s\n", messageType.name(), message); +alpacaAPI.marketDataStreaming().addListener(marketDataListener); + +// Listen to 'SubscriptionsMessage', 'SuccessMessage', and 'ErrorMessage' control messages +// that contain information about the stream's current state. +alpacaAPI.marketDataStreaming().subscribeToControl( + MarketDataMessageType.SUCCESS, + MarketDataMessageType.SUBSCRIPTION, + MarketDataMessageType.ERROR); +// Listen to the AAPL and TSLA trades and all ('*') bars. +// Note this will connect the websocket and wait for authorization +// if it isn't already connected. +alpacaAPI.marketDataStreaming().subscribe( + Arrays.asList("AAPL", "TSLA"), + null, + Arrays.asList("*")); + +// Wait for 5 seconds +Thread.sleep(5000); + +// Note that when the last 'MarketDataListener' is removed, the websocket +// connection is closed. +alpacaAPI.marketDataStreaming().removeListener(marketDataListener); +``` + +The following methods show how you can control the state of the [`MarketDataWebsocket`](src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/MarketDataWebsocket.java) directly. +```java +alpacaAPI.marketDataStreaming().connect(); +alpacaAPI.marketDataStreaming().waitForAuthorization(); +System.out.println(alpacaAPI.marketDataStreaming().isValid()); +alpacaAPI.marketDataStreaming().disconnect(); ``` # Building @@ -543,8 +462,10 @@ To run live endpoint tests with Alpaca Paper credentials, create the `alpaca.pro Note that the live tests will modify your account minimally. It's meant to test live endpoints on a real paper account during market hours to confirm that API methods are working properly. Please read through the live endpoint tests [here](https://github.com/Petersoj/alpaca-java/tree/master/src/test/java/live/net/jacobpeterson/alpaca) before running this testing suite on your own paper account. # TODO -- Unit Testing (both live and mocked) +- Finish Unit Testing (both live and mocked) - Use [TA4j](https://github.com/ta4j/ta4j) `Num` interface instead of `Double` for number variables so that users can use either `Double` or `BigDecimal` for performance or precision in price data. - Add [TimeSeriesDataStore](https://github.com/Petersoj/TimeSeriesDataStore) using Alpaca Data API Contributions are welcome! + +If you are creating a Pull Request, be sure to create a new branch in your forked repository for your feature or bug fix instead of committing directly to the `master` branch in your fork. diff --git a/src/main/java/net/jacobpeterson/alpaca/AlpacaAPI.java b/src/main/java/net/jacobpeterson/alpaca/AlpacaAPI.java index b9eb0071..8798c708 100644 --- a/src/main/java/net/jacobpeterson/alpaca/AlpacaAPI.java +++ b/src/main/java/net/jacobpeterson/alpaca/AlpacaAPI.java @@ -19,10 +19,10 @@ import static com.google.common.base.Preconditions.checkNotNull; /** - * {@link AlpacaAPI} contains several instances of various {@link AbstractEndpoint}s and {@link AlpacaWebsocket}s that - * enable you to interface with Alpaca. You will generally only need one instance of this class in your application. - * Note that many methods inside the various {@link AbstractEndpoint}s allow null to be passed in as a - * parameter if it is optional. + * The {@link AlpacaAPI} class contains several instances of various {@link AlpacaEndpoint}s and {@link + * AlpacaWebsocket}s to interface with Alpaca. You will generally only need one instance of this class in your + * application. Note that many methods inside the various {@link AlpacaEndpoint}s allow null to be passed + * in as a parameter if it is optional. * * @see Alpaca API Documentation */ diff --git a/src/main/java/net/jacobpeterson/alpaca/rest/endpoint/AccountActivitiesEndpoint.java b/src/main/java/net/jacobpeterson/alpaca/rest/endpoint/AccountActivitiesEndpoint.java index 8db4c3de..d1e5b88b 100644 --- a/src/main/java/net/jacobpeterson/alpaca/rest/endpoint/AccountActivitiesEndpoint.java +++ b/src/main/java/net/jacobpeterson/alpaca/rest/endpoint/AccountActivitiesEndpoint.java @@ -26,11 +26,11 @@ import static net.jacobpeterson.alpaca.util.gson.GsonUtil.GSON; /** - * {@link AbstractEndpoint} for + * {@link AlpacaEndpoint} for * Account * Activities. */ -public class AccountActivitiesEndpoint extends AbstractEndpoint { +public class AccountActivitiesEndpoint extends AlpacaEndpoint { private static final Logger LOGGER = LoggerFactory.getLogger(AccountActivitiesEndpoint.class); diff --git a/src/main/java/net/jacobpeterson/alpaca/rest/endpoint/AccountConfigurationEndpoint.java b/src/main/java/net/jacobpeterson/alpaca/rest/endpoint/AccountConfigurationEndpoint.java index b42751ed..2d7c5de0 100644 --- a/src/main/java/net/jacobpeterson/alpaca/rest/endpoint/AccountConfigurationEndpoint.java +++ b/src/main/java/net/jacobpeterson/alpaca/rest/endpoint/AccountConfigurationEndpoint.java @@ -11,10 +11,10 @@ import static net.jacobpeterson.alpaca.util.gson.GsonUtil.GSON; /** - * {@link AbstractEndpoint} for + * {@link AlpacaEndpoint} for * Account Configuration. */ -public class AccountConfigurationEndpoint extends AbstractEndpoint { +public class AccountConfigurationEndpoint extends AlpacaEndpoint { /** * Instantiates a new {@link AccountConfigurationEndpoint}. diff --git a/src/main/java/net/jacobpeterson/alpaca/rest/endpoint/AccountEndpoint.java b/src/main/java/net/jacobpeterson/alpaca/rest/endpoint/AccountEndpoint.java index b6c1cf97..ff57ff67 100644 --- a/src/main/java/net/jacobpeterson/alpaca/rest/endpoint/AccountEndpoint.java +++ b/src/main/java/net/jacobpeterson/alpaca/rest/endpoint/AccountEndpoint.java @@ -7,9 +7,9 @@ import okhttp3.Request; /** - * {@link AbstractEndpoint} for Account. + * {@link AlpacaEndpoint} for Account. */ -public class AccountEndpoint extends AbstractEndpoint { +public class AccountEndpoint extends AlpacaEndpoint { /** * Instantiates a new {@link AccountEndpoint}. diff --git a/src/main/java/net/jacobpeterson/alpaca/rest/endpoint/AbstractEndpoint.java b/src/main/java/net/jacobpeterson/alpaca/rest/endpoint/AlpacaEndpoint.java similarity index 65% rename from src/main/java/net/jacobpeterson/alpaca/rest/endpoint/AbstractEndpoint.java rename to src/main/java/net/jacobpeterson/alpaca/rest/endpoint/AlpacaEndpoint.java index d1fb88db..dc4e1616 100644 --- a/src/main/java/net/jacobpeterson/alpaca/rest/endpoint/AbstractEndpoint.java +++ b/src/main/java/net/jacobpeterson/alpaca/rest/endpoint/AlpacaEndpoint.java @@ -3,20 +3,20 @@ import net.jacobpeterson.alpaca.rest.AlpacaClient; /** - * {@link AbstractEndpoint} is an abstract class representing a RestAPI endpoint. + * {@link AlpacaEndpoint} is an abstract class representing a RestAPI endpoint. */ -public abstract class AbstractEndpoint { +public abstract class AlpacaEndpoint { protected final AlpacaClient alpacaClient; protected final String endpointPathSegment; /** - * Instantiates a new {@link AbstractEndpoint}. + * Instantiates a new {@link AlpacaEndpoint}. * * @param alpacaClient the {@link AlpacaClient} * @param endpointPathSegment the endpoint path segment relative to {@link AlpacaClient#getBaseURL()} */ - public AbstractEndpoint(AlpacaClient alpacaClient, String endpointPathSegment) { + public AlpacaEndpoint(AlpacaClient alpacaClient, String endpointPathSegment) { this.alpacaClient = alpacaClient; this.endpointPathSegment = endpointPathSegment; } diff --git a/src/main/java/net/jacobpeterson/alpaca/rest/endpoint/AssetsEndpoint.java b/src/main/java/net/jacobpeterson/alpaca/rest/endpoint/AssetsEndpoint.java index 62cd1210..c0642115 100644 --- a/src/main/java/net/jacobpeterson/alpaca/rest/endpoint/AssetsEndpoint.java +++ b/src/main/java/net/jacobpeterson/alpaca/rest/endpoint/AssetsEndpoint.java @@ -14,9 +14,9 @@ import static com.google.common.base.Preconditions.checkNotNull; /** - * {@link AbstractEndpoint} for Assets. + * {@link AlpacaEndpoint} for Assets. */ -public class AssetsEndpoint extends AbstractEndpoint { +public class AssetsEndpoint extends AlpacaEndpoint { /** * Instantiates a new {@link AssetsEndpoint}. diff --git a/src/main/java/net/jacobpeterson/alpaca/rest/endpoint/CalendarEndpoint.java b/src/main/java/net/jacobpeterson/alpaca/rest/endpoint/CalendarEndpoint.java index 9fe0c028..03bb2641 100644 --- a/src/main/java/net/jacobpeterson/alpaca/rest/endpoint/CalendarEndpoint.java +++ b/src/main/java/net/jacobpeterson/alpaca/rest/endpoint/CalendarEndpoint.java @@ -13,9 +13,9 @@ import java.util.List; /** - * {@link AbstractEndpoint} for Calendar. + * {@link AlpacaEndpoint} for Calendar. */ -public class CalendarEndpoint extends AbstractEndpoint { +public class CalendarEndpoint extends AlpacaEndpoint { /** * Instantiates a new {@link CalendarEndpoint}. diff --git a/src/main/java/net/jacobpeterson/alpaca/rest/endpoint/ClockEndpoint.java b/src/main/java/net/jacobpeterson/alpaca/rest/endpoint/ClockEndpoint.java index 71fb6f25..6e5e9ae4 100644 --- a/src/main/java/net/jacobpeterson/alpaca/rest/endpoint/ClockEndpoint.java +++ b/src/main/java/net/jacobpeterson/alpaca/rest/endpoint/ClockEndpoint.java @@ -7,9 +7,9 @@ import okhttp3.Request; /** - * {@link AbstractEndpoint} for Clock. + * {@link AlpacaEndpoint} for Clock. */ -public class ClockEndpoint extends AbstractEndpoint { +public class ClockEndpoint extends AlpacaEndpoint { /** * Instantiates a new {@link ClockEndpoint}. diff --git a/src/main/java/net/jacobpeterson/alpaca/rest/endpoint/MarketDataEndpoint.java b/src/main/java/net/jacobpeterson/alpaca/rest/endpoint/MarketDataEndpoint.java index 66c698a4..1b7e3eed 100644 --- a/src/main/java/net/jacobpeterson/alpaca/rest/endpoint/MarketDataEndpoint.java +++ b/src/main/java/net/jacobpeterson/alpaca/rest/endpoint/MarketDataEndpoint.java @@ -26,11 +26,11 @@ import static com.google.common.base.Preconditions.checkNotNull; /** - * {@link AbstractEndpoint} for + * {@link AlpacaEndpoint} for * Historical * Market Data API v2. */ -public class MarketDataEndpoint extends AbstractEndpoint { +public class MarketDataEndpoint extends AlpacaEndpoint { /** * Instantiates a new {@link MarketDataEndpoint}. diff --git a/src/main/java/net/jacobpeterson/alpaca/rest/endpoint/OrdersEndpoint.java b/src/main/java/net/jacobpeterson/alpaca/rest/endpoint/OrdersEndpoint.java index ff29de7a..baead6d8 100644 --- a/src/main/java/net/jacobpeterson/alpaca/rest/endpoint/OrdersEndpoint.java +++ b/src/main/java/net/jacobpeterson/alpaca/rest/endpoint/OrdersEndpoint.java @@ -23,9 +23,9 @@ import static com.google.common.base.Preconditions.checkState; /** - * {@link AbstractEndpoint} for Orders. + * {@link AlpacaEndpoint} for Orders. */ -public class OrdersEndpoint extends AbstractEndpoint { +public class OrdersEndpoint extends AlpacaEndpoint { /** * Instantiates a new {@link OrdersEndpoint}. diff --git a/src/main/java/net/jacobpeterson/alpaca/rest/endpoint/PortfolioHistoryEndpoint.java b/src/main/java/net/jacobpeterson/alpaca/rest/endpoint/PortfolioHistoryEndpoint.java index c9996813..9bab136d 100644 --- a/src/main/java/net/jacobpeterson/alpaca/rest/endpoint/PortfolioHistoryEndpoint.java +++ b/src/main/java/net/jacobpeterson/alpaca/rest/endpoint/PortfolioHistoryEndpoint.java @@ -19,11 +19,11 @@ import static com.google.common.base.Preconditions.checkState; /** - * {@link AbstractEndpoint} for + * {@link AlpacaEndpoint} for * Portfolio * History. */ -public class PortfolioHistoryEndpoint extends AbstractEndpoint { +public class PortfolioHistoryEndpoint extends AlpacaEndpoint { /** * Instantiates a new {@link PortfolioHistoryEndpoint}. diff --git a/src/main/java/net/jacobpeterson/alpaca/rest/endpoint/PositionsEndpoint.java b/src/main/java/net/jacobpeterson/alpaca/rest/endpoint/PositionsEndpoint.java index 86ad8cb0..f5deb1d4 100644 --- a/src/main/java/net/jacobpeterson/alpaca/rest/endpoint/PositionsEndpoint.java +++ b/src/main/java/net/jacobpeterson/alpaca/rest/endpoint/PositionsEndpoint.java @@ -17,9 +17,9 @@ import static com.google.common.base.Preconditions.checkNotNull; /** - * {@link AbstractEndpoint} for Positions. + * {@link AlpacaEndpoint} for Positions. */ -public class PositionsEndpoint extends AbstractEndpoint { +public class PositionsEndpoint extends AlpacaEndpoint { /** * Instantiates a new {@link PositionsEndpoint}. diff --git a/src/main/java/net/jacobpeterson/alpaca/rest/endpoint/WatchlistEndpoint.java b/src/main/java/net/jacobpeterson/alpaca/rest/endpoint/WatchlistEndpoint.java index 3558b041..aeadb183 100644 --- a/src/main/java/net/jacobpeterson/alpaca/rest/endpoint/WatchlistEndpoint.java +++ b/src/main/java/net/jacobpeterson/alpaca/rest/endpoint/WatchlistEndpoint.java @@ -18,10 +18,10 @@ import static com.google.common.base.Preconditions.checkNotNull; /** - * {@link AbstractEndpoint} for Watchlists + * {@link AlpacaEndpoint} for Watchlists * . */ -public class WatchlistEndpoint extends AbstractEndpoint { +public class WatchlistEndpoint extends AlpacaEndpoint { /** * Instantiates a new {@link WatchlistEndpoint}. diff --git a/src/main/java/net/jacobpeterson/alpaca/util/gson/GsonUtil.java b/src/main/java/net/jacobpeterson/alpaca/util/gson/GsonUtil.java index ef83f668..7b94a344 100644 --- a/src/main/java/net/jacobpeterson/alpaca/util/gson/GsonUtil.java +++ b/src/main/java/net/jacobpeterson/alpaca/util/gson/GsonUtil.java @@ -4,6 +4,7 @@ import com.google.gson.GsonBuilder; import java.time.LocalDate; +import java.time.LocalTime; import java.time.ZonedDateTime; /** @@ -15,6 +16,7 @@ public class GsonUtil { public static final Gson GSON = new GsonBuilder() .registerTypeAdapter(ZonedDateTime.class, new ZonedDateTimeAdapter()) .registerTypeAdapter(LocalDate.class, new LocalDateAdapter()) + .registerTypeAdapter(LocalTime.class, new LocalTimeAdapter()) .enableComplexMapKeySerialization() .setLenient() .create(); diff --git a/src/main/java/net/jacobpeterson/alpaca/util/gson/LocalDateAdapter.java b/src/main/java/net/jacobpeterson/alpaca/util/gson/LocalDateAdapter.java index e636cdb5..657a374d 100644 --- a/src/main/java/net/jacobpeterson/alpaca/util/gson/LocalDateAdapter.java +++ b/src/main/java/net/jacobpeterson/alpaca/util/gson/LocalDateAdapter.java @@ -7,7 +7,7 @@ import java.time.format.DateTimeFormatter; /** - * The GSON {@link LocalDateAdapter} (de)serialization adapter. + * {@link LocalDateAdapter} is a {@link Gson} adapter for {@link LocalDate}s. */ public class LocalDateAdapter implements JsonSerializer, JsonDeserializer { diff --git a/src/main/java/net/jacobpeterson/alpaca/util/gson/LocalTimeAdapter.java b/src/main/java/net/jacobpeterson/alpaca/util/gson/LocalTimeAdapter.java new file mode 100644 index 00000000..8cfbae4b --- /dev/null +++ b/src/main/java/net/jacobpeterson/alpaca/util/gson/LocalTimeAdapter.java @@ -0,0 +1,24 @@ +package net.jacobpeterson.alpaca.util.gson; + +import com.google.gson.*; + +import java.lang.reflect.Type; +import java.time.LocalTime; +import java.time.format.DateTimeFormatter; + +/** + * {@link LocalTimeAdapter} is a {@link Gson} adapter for {@link LocalTime}s. + */ +public class LocalTimeAdapter implements JsonSerializer, JsonDeserializer { + + @Override + public JsonElement serialize(LocalTime localTime, Type typeOfSrc, JsonSerializationContext context) { + return new JsonPrimitive(localTime.format(DateTimeFormatter.ISO_LOCAL_TIME)); + } + + @Override + public LocalTime deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) + throws JsonParseException { + return LocalTime.parse(json.getAsJsonPrimitive().getAsString(), DateTimeFormatter.ISO_LOCAL_TIME); + } +} diff --git a/src/main/java/net/jacobpeterson/alpaca/util/gson/ZonedDateTimeAdapter.java b/src/main/java/net/jacobpeterson/alpaca/util/gson/ZonedDateTimeAdapter.java index febf3d49..c78f998c 100644 --- a/src/main/java/net/jacobpeterson/alpaca/util/gson/ZonedDateTimeAdapter.java +++ b/src/main/java/net/jacobpeterson/alpaca/util/gson/ZonedDateTimeAdapter.java @@ -7,7 +7,7 @@ import java.time.format.DateTimeFormatter; /** - * The GSON {@link ZonedDateTimeAdapter} (de)serialization adapter. + * {@link ZonedDateTimeAdapter} is a {@link Gson} adapter for {@link ZonedDateTime}s. */ public class ZonedDateTimeAdapter implements JsonSerializer, JsonDeserializer { diff --git a/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/MarketDataWebsocket.java b/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/MarketDataWebsocket.java index f7c34f78..e6c4024e 100644 --- a/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/MarketDataWebsocket.java +++ b/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/MarketDataWebsocket.java @@ -104,7 +104,7 @@ protected void onConnection() { protected void onReconnection() { sendAuthenticationMessage(); if (waitForAuthorization()) { - subscribeToTypes(Iterables.toArray(listenedMarketDataMessageTypes, MarketDataMessageType.class)); + subscribeToControl(Iterables.toArray(listenedMarketDataMessageTypes, MarketDataMessageType.class)); subscribe(subscribedTrades, subscribedQuotes, subscribedBars); } } @@ -252,7 +252,7 @@ private boolean isErrorMessageAuthFailure(ErrorMessage errorMessage) { } @Override - public void subscribeToTypes(MarketDataMessageType... marketDataMessageTypes) { + public void subscribeToControl(MarketDataMessageType... marketDataMessageTypes) { if (marketDataMessageTypes == null) { return; } @@ -329,7 +329,7 @@ private void addSubscriptionUpdateList(JsonObject subscriptionUpdateObject, Stri } @Override - public Collection subscribedTypes() { + public Collection subscribedControls() { return new HashSet<>(listenedMarketDataMessageTypes); } diff --git a/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/MarketDataWebsocketInterface.java b/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/MarketDataWebsocketInterface.java index 6c4480ce..7c7d4d90 100644 --- a/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/MarketDataWebsocketInterface.java +++ b/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/MarketDataWebsocketInterface.java @@ -15,13 +15,13 @@ public interface MarketDataWebsocketInterface extends AlpacaWebsocketInterface { /** - * Subscribe to a specific {@link MarketDataMessage}. That is, {@link ErrorMessage}, {@link SubscriptionsMessage}, - * or {@link SuccessMessage}. + * Subscribe to a specific control {@link MarketDataMessage}. That is, {@link ErrorMessage}, {@link + * SubscriptionsMessage}, or {@link SuccessMessage}. * * @param marketDataMessageTypes array of any of: {@link MarketDataMessageType#SUCCESS}, {@link * MarketDataMessageType#ERROR}, or {@link MarketDataMessageType#SUBSCRIPTION} */ - void subscribeToTypes(MarketDataMessageType... marketDataMessageTypes); + void subscribeToControl(MarketDataMessageType... marketDataMessageTypes); /** * Subscribes to trades, quotes, or bars according to the given {@link Collection} of symbols. @@ -58,11 +58,11 @@ public interface MarketDataWebsocketInterface extends AlpacaWebsocketInterface tradeSymbols, Collection quoteSymbols, Collection barSymbols); /** - * Gets all of the currently subscribed {@link MarketDataMessageType}s. + * Gets all of the currently subscribed control {@link MarketDataMessageType}s. * * @return a {@link Collection} of {@link MarketDataMessageType}s */ - Collection subscribedTypes(); + Collection subscribedControls(); /** * Gets all of the currently subscribed symbols for trade updates. diff --git a/src/main/resources/alpaca.default.properties b/src/main/resources/alpaca.default.properties index ecc83759..0cef7e43 100644 --- a/src/main/resources/alpaca.default.properties +++ b/src/main/resources/alpaca.default.properties @@ -1,6 +1,6 @@ #Example: -#key_id = -#secret_key = +#key_id = +#secret_key = #endpoint_api_type = #data_api_type = #user_agent = From eced2604b30cfbdb273a5685be4c1636cbdce73f Mon Sep 17 00:00:00 2001 From: Jacob Peterson Date: Mon, 12 Jul 2021 11:41:22 -0600 Subject: [PATCH 131/271] Small update to README, fix Javadoc @links --- README.md | 7 ++++--- .../net/jacobpeterson/alpaca/rest/AlpacaClient.java | 10 +++++----- .../alpaca/websocket/AlpacaWebsocket.java | 4 +++- 3 files changed, 12 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index 9bbfed2b..1ac36583 100644 --- a/README.md +++ b/README.md @@ -70,7 +70,7 @@ String oAuthToken = ""; AlpacaAPI alpacaAPI = new AlpacaAPI(oAuthToken); ``` -Note that this library uses [OkHttp](https://square.github.io/okhttp/) as its HTTP client which creates background threads to service requests. These threads persist even if the main thread exists so if you want to destroy these threads when you're done using [`AlpacaAPI`](src/main/java/net/jacobpeterson/alpaca/AlpacaAPI.java) so your program can exit without calling `System.exit()`, use the following snippet: +Note that this library uses [OkHttp](https://square.github.io/okhttp/) as its HTTP client library which creates background threads to service requests. These threads persist even if the main thread exists so if you want to destroy these threads when you're done using [`AlpacaAPI`](src/main/java/net/jacobpeterson/alpaca/AlpacaAPI.java) so your program can exit without calling `System.exit()`, use the following snippet: ```java alpacaAPI.getOkHttpClient().dispatcher().executorService().shutdown(); alpacaAPI.getOkHttpClient().connectionPool().evictAll(); @@ -168,7 +168,7 @@ try { tslaLimitOrder.getSubmittedAt()); // Check if TSLA limit order has filled 5 seconds later and if it hasn't - // replace it with a market order so it fills! + // replace it with a higher limit order so it fills! Thread.sleep(5000); tslaLimitOrder = alpacaAPI.orders().get(tslaLimitOrder.getId(), false); if (tslaLimitOrder.getFilledAt() == null && tslaLimitOrder.getStatus().equals(OrderStatus.NEW)) { @@ -176,7 +176,8 @@ try { tslaLimitOrder.getId(), 100, OrderTimeInForce.DAY, - null, null, null, null); + 655.00, + null, null, null); System.out.printf("Replaced TSLA order: %s\n", replacedOrder); } diff --git a/src/main/java/net/jacobpeterson/alpaca/rest/AlpacaClient.java b/src/main/java/net/jacobpeterson/alpaca/rest/AlpacaClient.java index 0b8f7e01..17b6df33 100644 --- a/src/main/java/net/jacobpeterson/alpaca/rest/AlpacaClient.java +++ b/src/main/java/net/jacobpeterson/alpaca/rest/AlpacaClient.java @@ -88,20 +88,20 @@ protected AlpacaClient(OkHttpClient okHttpClient, String keyID, String secretKey } /** - * Gets a new {@link HttpUrl.Builder}. + * Gets a new {@link okhttp3.HttpUrl.Builder}. * - * @return a {@link HttpUrl.Builder} + * @return a {@link okhttp3.HttpUrl.Builder} */ public HttpUrl.Builder urlBuilder() { return baseURL.newBuilder(); } /** - * Gets a new {@link Request.Builder} with {@link #requestHeaders}. + * Gets a new {@link okhttp3.Request.Builder} with {@link #requestHeaders}. * - * @param httpUrl the {@link HttpUrl} + * @param httpUrl the {@link okhttp3.HttpUrl} * - * @return a {@link Request.Builder} + * @return a {@link okhttp3.Request.Builder} */ public Request.Builder requestBuilder(HttpUrl httpUrl) { return new Request.Builder().headers(requestHeaders).url(httpUrl); diff --git a/src/main/java/net/jacobpeterson/alpaca/websocket/AlpacaWebsocket.java b/src/main/java/net/jacobpeterson/alpaca/websocket/AlpacaWebsocket.java index 785e75a8..281003bb 100644 --- a/src/main/java/net/jacobpeterson/alpaca/websocket/AlpacaWebsocket.java +++ b/src/main/java/net/jacobpeterson/alpaca/websocket/AlpacaWebsocket.java @@ -194,7 +194,9 @@ private void handleReconnectionAttempt() { ForkJoinPool.commonPool().execute(() -> { try { Thread.sleep(RECONNECT_SLEEP_INTERVAL); - } catch (InterruptedException ignored) {} + } catch (InterruptedException ignored) { + return; + } connect(); }); From 9862c92db2af0fea13c4467b695944c6598e8a4e Mon Sep 17 00:00:00 2001 From: Jacob Peterson Date: Mon, 12 Jul 2021 11:45:47 -0600 Subject: [PATCH 132/271] Update to 8.0.0 --- README.md | 4 ++-- build.gradle | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 1ac36583..d1be702e 100644 --- a/README.md +++ b/README.md @@ -17,7 +17,7 @@ If you are using Gradle as your build tool, add the following dependency to your ``` dependencies { - implementation group: 'net.jacobpeterson', name: 'alpaca-java', version: '7.2.1' + implementation group: 'net.jacobpeterson', name: 'alpaca-java', version: '8.0.0' } ``` @@ -27,7 +27,7 @@ If you are using Maven as your build tool, add the following dependency to your net.jacobpeterson alpaca-java - 7.2.1 + 8.0.0 compile ``` diff --git a/build.gradle b/build.gradle index 8abd5869..80f0ed90 100644 --- a/build.gradle +++ b/build.gradle @@ -30,7 +30,7 @@ tasks.getByName("generateJsonSchema2Pojo").enabled(false) // Disable default 'js archivesBaseName = 'alpaca-java' group = 'net.jacobpeterson' -version = '7.2.1-SNAPSHOT' +version = '8.0.0-SNAPSHOT' repositories { mavenCentral() From faa7febce144af07d0d59abe1e0396536d7c8ac0 Mon Sep 17 00:00:00 2001 From: Jacob Peterson Date: Tue, 13 Jul 2021 11:35:33 -0600 Subject: [PATCH 133/271] Fix ResponseBody resource leak --- .../alpaca/rest/AlpacaClient.java | 19 ++-- .../alpaca/rest/AlpacaClientException.java | 92 +++++++++---------- 2 files changed, 54 insertions(+), 57 deletions(-) diff --git a/src/main/java/net/jacobpeterson/alpaca/rest/AlpacaClient.java b/src/main/java/net/jacobpeterson/alpaca/rest/AlpacaClient.java index 17b6df33..76a836a4 100644 --- a/src/main/java/net/jacobpeterson/alpaca/rest/AlpacaClient.java +++ b/src/main/java/net/jacobpeterson/alpaca/rest/AlpacaClient.java @@ -152,13 +152,16 @@ public JsonElement requestJSON(Request request) throws AlpacaClientException { */ public JsonElement requestJSON(Request request, Predicate isSuccessCode) throws AlpacaClientException { try (Response response = request(request, isSuccessCode)) { - ResponseBody responseBody = response.body(); - checkState(responseBody != null); - - try (Reader charStream = responseBody.charStream()) { - return JsonParser.parseReader(charStream); - } catch (IOException ioException) { - throw new AlpacaClientException(ioException); + try (ResponseBody responseBody = response.body()) { + // Throw exception if 'responseBody' is ever null since we never want to use cachedResponse or + // other types of responses. + checkState(responseBody != null); + + try (Reader charStream = responseBody.charStream()) { + return JsonParser.parseReader(charStream); + } catch (IOException ioException) { + throw new AlpacaClientException(ioException); + } } } } @@ -173,6 +176,8 @@ public JsonElement requestJSON(Request request, Predicate isSuccessCode * @throws AlpacaClientException thrown for {@link AlpacaClientException}s */ public void requestVoid(Request request, Predicate isSuccessCode) throws AlpacaClientException { + // close() will throw an exception if cachedResponse or others were used which is fine + // since we never want to use those types of responses. request(request, isSuccessCode).close(); } diff --git a/src/main/java/net/jacobpeterson/alpaca/rest/AlpacaClientException.java b/src/main/java/net/jacobpeterson/alpaca/rest/AlpacaClientException.java index e78e58ef..40ef1d6c 100644 --- a/src/main/java/net/jacobpeterson/alpaca/rest/AlpacaClientException.java +++ b/src/main/java/net/jacobpeterson/alpaca/rest/AlpacaClientException.java @@ -6,19 +6,14 @@ import okhttp3.Response; import okhttp3.ResponseBody; -import java.io.IOException; -import java.io.Reader; - -import static com.google.common.base.Preconditions.checkState; - public class AlpacaClientException extends Exception { private static final String CODE_KEY = "code"; private static final String MESSAGE_KEY = "message"; - private Response response; - private Integer requestStatusCode; - private String requestStatusMessage; + private String responseBody; + private Integer responseStatusCode; + private String responseStatusMessage; private Integer apiResponseCode; private String apiResponseMessage; @@ -56,17 +51,21 @@ public AlpacaClientException(String message, Throwable cause) { * @param response the {@link Response} containing an API response code and message JSON */ protected AlpacaClientException(Response response) { - this.response = response; + responseStatusCode = response.code(); + responseStatusMessage = response.message(); + + try (ResponseBody clientResponseBody = response.body()) { + if (clientResponseBody != null) { + this.responseBody = clientResponseBody.string(); + } + } catch (Exception ignored) {} } @Override public String getMessage() { - if (response == null) { + if (responseBody == null) { return super.getMessage(); } else { - requestStatusCode = response.code(); - requestStatusMessage = response.message(); - try { return parseAPIErrorResponse(); } catch (Exception ignored) { @@ -86,66 +85,59 @@ public String getMessage() { * and sets {@link #apiResponseCode} and {@link #apiResponseMessage} accordingly. * * @return a formatted message of the error response - * - * @throws IOException thrown for {@link IOException}s */ - private String parseAPIErrorResponse() throws IOException { - ResponseBody responseBody = response.body(); - checkState(responseBody != null); - - try (Reader charStream = responseBody.charStream()) { - JsonElement responseJsonElement = JsonParser.parseReader(charStream); + private String parseAPIErrorResponse() { + JsonElement responseJsonElement = JsonParser.parseString(responseBody); - if (responseJsonElement instanceof JsonObject) { - JsonObject responseJsonObject = responseJsonElement.getAsJsonObject(); + if (responseJsonElement instanceof JsonObject) { + JsonObject responseJsonObject = responseJsonElement.getAsJsonObject(); - if (responseJsonObject.has(CODE_KEY)) { - apiResponseCode = responseJsonObject.get(CODE_KEY).getAsInt(); - } - - if (responseJsonObject.has(MESSAGE_KEY)) { - apiResponseMessage = responseJsonObject.get(MESSAGE_KEY).getAsString(); - } + if (responseJsonObject.has(CODE_KEY)) { + apiResponseCode = responseJsonObject.get(CODE_KEY).getAsInt(); } - // Just use the response JSON if the message could not be parsed. - if (apiResponseMessage == null) { - apiResponseMessage = responseJsonElement.toString(); + if (responseJsonObject.has(MESSAGE_KEY)) { + apiResponseMessage = responseJsonObject.get(MESSAGE_KEY).getAsString(); } + } - // Build message - StringBuilder messageBuilder = new StringBuilder(); - messageBuilder.append("Status Code: ").append(requestStatusCode); - messageBuilder.append(", Status Message: \"").append(requestStatusMessage).append("\""); + // Just use the response JSON if the message could not be parsed. + if (apiResponseMessage == null) { + apiResponseMessage = responseJsonElement.toString(); + } - if (apiResponseCode != null) { - messageBuilder.append(", API Response Code: ").append(apiResponseCode); - } + // Build message + StringBuilder messageBuilder = new StringBuilder(); + messageBuilder.append("Status Code: ").append(responseStatusCode); + messageBuilder.append(", Status Message: \"").append(responseStatusMessage).append("\""); - if (apiResponseMessage != null) { - messageBuilder.append(", API Response Message: \"").append(apiResponseMessage).append("\""); - } + if (apiResponseCode != null) { + messageBuilder.append(", API Response Code: ").append(apiResponseCode); + } - return messageBuilder.toString(); + if (apiResponseMessage != null) { + messageBuilder.append(", API Response Message: \"").append(apiResponseMessage).append("\""); } + + return messageBuilder.toString(); } /** - * Gets the {@link #requestStatusCode}. + * Gets the {@link #responseStatusCode}. * * @return an {@link Integer} */ - public Integer getRequestStatusCode() { - return requestStatusCode; + public Integer getResponseStatusCode() { + return responseStatusCode; } /** - * Gets the {@link #requestStatusMessage}. + * Gets the {@link #responseStatusMessage}. * * @return a {@link String} */ - public String getRequestStatusMessage() { - return requestStatusMessage; + public String getResponseStatusMessage() { + return responseStatusMessage; } /** From e27e06c47248b61f77fb27c8f75f545c771bd802 Mon Sep 17 00:00:00 2001 From: Jacob Peterson Date: Tue, 13 Jul 2021 11:37:38 -0600 Subject: [PATCH 134/271] Update to 8.0.1 --- README.md | 4 ++-- build.gradle | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index d1be702e..e6665456 100644 --- a/README.md +++ b/README.md @@ -17,7 +17,7 @@ If you are using Gradle as your build tool, add the following dependency to your ``` dependencies { - implementation group: 'net.jacobpeterson', name: 'alpaca-java', version: '8.0.0' + implementation group: 'net.jacobpeterson', name: 'alpaca-java', version: '8.0.1' } ``` @@ -27,7 +27,7 @@ If you are using Maven as your build tool, add the following dependency to your net.jacobpeterson alpaca-java - 8.0.0 + 8.0.1 compile ``` diff --git a/build.gradle b/build.gradle index 80f0ed90..0fe9583d 100644 --- a/build.gradle +++ b/build.gradle @@ -30,7 +30,7 @@ tasks.getByName("generateJsonSchema2Pojo").enabled(false) // Disable default 'js archivesBaseName = 'alpaca-java' group = 'net.jacobpeterson' -version = '8.0.0-SNAPSHOT' +version = '8.0.1-SNAPSHOT' repositories { mavenCentral() From 53ef9d8a3a0596bc82492dd22acf4cd17866a695 Mon Sep 17 00:00:00 2001 From: Jacob Peterson Date: Wed, 14 Jul 2021 10:24:02 -0600 Subject: [PATCH 135/271] Fix 'brokerHostSubdomain' for live API type --- src/main/java/net/jacobpeterson/alpaca/AlpacaAPI.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/net/jacobpeterson/alpaca/AlpacaAPI.java b/src/main/java/net/jacobpeterson/alpaca/AlpacaAPI.java index 8798c708..aafd8916 100644 --- a/src/main/java/net/jacobpeterson/alpaca/AlpacaAPI.java +++ b/src/main/java/net/jacobpeterson/alpaca/AlpacaAPI.java @@ -131,7 +131,7 @@ public AlpacaAPI(OkHttpClient okHttpClient, String keyID, String secretKey, Stri String brokerHostSubdomain; switch (endpointAPIType) { case LIVE: - brokerHostSubdomain = "live"; + brokerHostSubdomain = "api"; break; case PAPER: brokerHostSubdomain = "paper-api"; From 5e8b45222414828e5ea7caab82dcc30282c94d44 Mon Sep 17 00:00:00 2001 From: Jacob Peterson Date: Wed, 14 Jul 2021 10:24:44 -0600 Subject: [PATCH 136/271] Update to 8.0.2 --- README.md | 4 ++-- build.gradle | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index e6665456..f214f151 100644 --- a/README.md +++ b/README.md @@ -17,7 +17,7 @@ If you are using Gradle as your build tool, add the following dependency to your ``` dependencies { - implementation group: 'net.jacobpeterson', name: 'alpaca-java', version: '8.0.1' + implementation group: 'net.jacobpeterson', name: 'alpaca-java', version: '8.0.2' } ``` @@ -27,7 +27,7 @@ If you are using Maven as your build tool, add the following dependency to your net.jacobpeterson alpaca-java - 8.0.1 + 8.0.2 compile ``` diff --git a/build.gradle b/build.gradle index 0fe9583d..86b0642a 100644 --- a/build.gradle +++ b/build.gradle @@ -30,7 +30,7 @@ tasks.getByName("generateJsonSchema2Pojo").enabled(false) // Disable default 'js archivesBaseName = 'alpaca-java' group = 'net.jacobpeterson' -version = '8.0.1-SNAPSHOT' +version = '8.0.2-SNAPSHOT' repositories { mavenCentral() From 3672ed27467fad6a54b07ebaa0fb67ff332b22ea Mon Sep 17 00:00:00 2001 From: Andrei Zhemaituk Date: Wed, 14 Jul 2021 20:47:34 -0400 Subject: [PATCH 137/271] Fix of resource loading for application container environments, like spring-boot. https://github.com/Petersoj/alpaca-java/issues/99 --- .../jacobpeterson/alpaca/util/properties/PropertyUtil.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/net/jacobpeterson/alpaca/util/properties/PropertyUtil.java b/src/main/java/net/jacobpeterson/alpaca/util/properties/PropertyUtil.java index e8e27bc2..170afad3 100644 --- a/src/main/java/net/jacobpeterson/alpaca/util/properties/PropertyUtil.java +++ b/src/main/java/net/jacobpeterson/alpaca/util/properties/PropertyUtil.java @@ -85,7 +85,7 @@ public synchronized static Properties loadPropertyFile(String propertyFile, Stri // Load the default property file if exists Properties defaultProperties = null; - InputStream defaultPropertyStream = ClassLoader.getSystemClassLoader().getResourceAsStream(defaultPropertyFile); + InputStream defaultPropertyStream = PropertyUtil.class.getClassLoader().getResourceAsStream(defaultPropertyFile); if (defaultPropertyStream != null) { defaultProperties = new Properties(); @@ -110,7 +110,7 @@ public synchronized static Properties loadPropertyFile(String propertyFile, Stri } // Load the property file - InputStream propertyStream = ClassLoader.getSystemClassLoader().getResourceAsStream(propertyFile); + InputStream propertyStream = PropertyUtil.class.getClassLoader().getResourceAsStream(propertyFile); if (propertyStream != null) { // Add default properties if they were found From e50f79728d9a72a7a0dae8d531dbc3bb0a9a3eaa Mon Sep 17 00:00:00 2001 From: Andrei Zhemaituk Date: Wed, 14 Jul 2021 22:00:25 -0400 Subject: [PATCH 138/271] code review comments addressed for #100 --- .../alpaca/util/properties/PropertyUtil.java | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/src/main/java/net/jacobpeterson/alpaca/util/properties/PropertyUtil.java b/src/main/java/net/jacobpeterson/alpaca/util/properties/PropertyUtil.java index 170afad3..41f0af67 100644 --- a/src/main/java/net/jacobpeterson/alpaca/util/properties/PropertyUtil.java +++ b/src/main/java/net/jacobpeterson/alpaca/util/properties/PropertyUtil.java @@ -85,7 +85,7 @@ public synchronized static Properties loadPropertyFile(String propertyFile, Stri // Load the default property file if exists Properties defaultProperties = null; - InputStream defaultPropertyStream = PropertyUtil.class.getClassLoader().getResourceAsStream(defaultPropertyFile); + InputStream defaultPropertyStream = getClassLoader().getResourceAsStream(defaultPropertyFile); if (defaultPropertyStream != null) { defaultProperties = new Properties(); @@ -110,7 +110,7 @@ public synchronized static Properties loadPropertyFile(String propertyFile, Stri } // Load the property file - InputStream propertyStream = PropertyUtil.class.getClassLoader().getResourceAsStream(propertyFile); + InputStream propertyStream = getClassLoader().getResourceAsStream(propertyFile); if (propertyStream != null) { // Add default properties if they were found @@ -141,4 +141,12 @@ public synchronized static Properties loadPropertyFile(String propertyFile, Stri return properties; } + + private static ClassLoader getClassLoader() { + ClassLoader classLoader = PropertyUtil.class.getClassLoader(); + if (classLoader == null) { + classLoader = ClassLoader.getSystemClassLoader(); + } + return classLoader; + } } From 8ffd1929e1aedb385575936d9f84c6455f755b3d Mon Sep 17 00:00:00 2001 From: Jacob Peterson Date: Wed, 14 Jul 2021 22:03:06 -0600 Subject: [PATCH 139/271] Move static method to local variable --- .../alpaca/util/properties/PropertyUtil.java | 18 +++++++----------- 1 file changed, 7 insertions(+), 11 deletions(-) diff --git a/src/main/java/net/jacobpeterson/alpaca/util/properties/PropertyUtil.java b/src/main/java/net/jacobpeterson/alpaca/util/properties/PropertyUtil.java index 41f0af67..ae853579 100644 --- a/src/main/java/net/jacobpeterson/alpaca/util/properties/PropertyUtil.java +++ b/src/main/java/net/jacobpeterson/alpaca/util/properties/PropertyUtil.java @@ -81,11 +81,14 @@ public static String getProperty(String propertyFile, String defaultPropertyFile * @return the properties */ public synchronized static Properties loadPropertyFile(String propertyFile, String defaultPropertyFile) { - Properties properties = null; + ClassLoader classLoader = PropertyUtil.class.getClassLoader(); + if (classLoader == null) { + classLoader = ClassLoader.getSystemClassLoader(); + } // Load the default property file if exists Properties defaultProperties = null; - InputStream defaultPropertyStream = getClassLoader().getResourceAsStream(defaultPropertyFile); + InputStream defaultPropertyStream = classLoader.getResourceAsStream(defaultPropertyFile); if (defaultPropertyStream != null) { defaultProperties = new Properties(); @@ -110,7 +113,8 @@ public synchronized static Properties loadPropertyFile(String propertyFile, Stri } // Load the property file - InputStream propertyStream = getClassLoader().getResourceAsStream(propertyFile); + Properties properties = null; + InputStream propertyStream = classLoader.getResourceAsStream(propertyFile); if (propertyStream != null) { // Add default properties if they were found @@ -141,12 +145,4 @@ public synchronized static Properties loadPropertyFile(String propertyFile, Stri return properties; } - - private static ClassLoader getClassLoader() { - ClassLoader classLoader = PropertyUtil.class.getClassLoader(); - if (classLoader == null) { - classLoader = ClassLoader.getSystemClassLoader(); - } - return classLoader; - } } From 7dc7b66f744bffdd7d85d1ed74c82efa158ba7d8 Mon Sep 17 00:00:00 2001 From: Jacob Peterson Date: Wed, 14 Jul 2021 22:05:07 -0600 Subject: [PATCH 140/271] Use static import for getProperty --- .../alpaca/properties/AlpacaProperties.java | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/src/main/java/net/jacobpeterson/alpaca/properties/AlpacaProperties.java b/src/main/java/net/jacobpeterson/alpaca/properties/AlpacaProperties.java index 80d2450f..2b8d0b79 100644 --- a/src/main/java/net/jacobpeterson/alpaca/properties/AlpacaProperties.java +++ b/src/main/java/net/jacobpeterson/alpaca/properties/AlpacaProperties.java @@ -3,7 +3,8 @@ import net.jacobpeterson.alpaca.AlpacaAPI; import net.jacobpeterson.alpaca.model.properties.DataAPIType; import net.jacobpeterson.alpaca.model.properties.EndpointAPIType; -import net.jacobpeterson.alpaca.util.properties.PropertyUtil; + +import static net.jacobpeterson.alpaca.util.properties.PropertyUtil.getProperty; /** * {@link AlpacaProperties} defines properties for {@link AlpacaAPI}. @@ -15,31 +16,31 @@ public class AlpacaProperties { private static final String KEY_ID_KEY = "key_id"; /** The value of {@link #KEY_ID_KEY} in {@link #ALPACA_PROPERTIES_FILE}. */ - public static final String KEY_ID = PropertyUtil.getProperty( + public static final String KEY_ID = getProperty( ALPACA_PROPERTIES_FILE, ALPACA_DEFAULT_PROPERTIES_FILE, KEY_ID_KEY); private static final String SECRET_KEY_KEY = "secret_key"; /** The value of {@link #SECRET_KEY_KEY} in {@link #ALPACA_PROPERTIES_FILE}. */ - public static final String SECRET_KEY = PropertyUtil.getProperty( + public static final String SECRET_KEY = getProperty( ALPACA_PROPERTIES_FILE, ALPACA_DEFAULT_PROPERTIES_FILE, SECRET_KEY_KEY); private static final String ENDPOINT_API_TYPE_KEY = "endpoint_api_type"; /** The value of {@link #ENDPOINT_API_TYPE_KEY} in {@link #ALPACA_PROPERTIES_FILE}. */ - public static final EndpointAPIType ENDPOINT_API_TYPE = EndpointAPIType.fromValue(PropertyUtil.getProperty( + public static final EndpointAPIType ENDPOINT_API_TYPE = EndpointAPIType.fromValue(getProperty( ALPACA_PROPERTIES_FILE, ALPACA_DEFAULT_PROPERTIES_FILE, ENDPOINT_API_TYPE_KEY)); private static final String DATA_API_TYPE_KEY = "data_api_type"; /** The value of {@link #DATA_API_TYPE_KEY} in {@link #ALPACA_PROPERTIES_FILE}. */ - public static final DataAPIType DATA_API_TYPE = DataAPIType.fromValue(PropertyUtil.getProperty( + public static final DataAPIType DATA_API_TYPE = DataAPIType.fromValue(getProperty( ALPACA_PROPERTIES_FILE, ALPACA_DEFAULT_PROPERTIES_FILE, DATA_API_TYPE_KEY)); private static final String USER_AGENT_KEY = "user_agent"; /** The value of {@link #USER_AGENT_KEY} in {@link #ALPACA_PROPERTIES_FILE}. */ - public static final String USER_AGENT = PropertyUtil.getProperty( + public static final String USER_AGENT = getProperty( ALPACA_PROPERTIES_FILE, ALPACA_DEFAULT_PROPERTIES_FILE, USER_AGENT_KEY); } From 3872b97f1414a9275b5aeefdf39a70efdfbff70f Mon Sep 17 00:00:00 2001 From: Jacob Peterson Date: Wed, 14 Jul 2021 22:11:40 -0600 Subject: [PATCH 141/271] Add 'held' order status --- schema_json/endpoint/order/enums/order_status.json | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/schema_json/endpoint/order/enums/order_status.json b/schema_json/endpoint/order/enums/order_status.json index 135b39b7..5174e120 100644 --- a/schema_json/endpoint/order/enums/order_status.json +++ b/schema_json/endpoint/order/enums/order_status.json @@ -17,7 +17,8 @@ "stopped", "rejected", "suspended", - "calculated" + "calculated", + "held" ], "javaEnums": [ { @@ -67,6 +68,9 @@ }, { "description": "The order has been completed for the day (either filled or done for day), but remaining settlement calculations are still pending. This state only occurs on rare occasions." + }, + { + "description": "The order is part of a bracket leg that is waiting for the initial order to be filled." } ] } From b041101436dcd3f7ad1d8224254e14c276e7ad8a Mon Sep 17 00:00:00 2001 From: Jacob Peterson Date: Thu, 15 Jul 2021 10:22:32 -0600 Subject: [PATCH 142/271] Update to 8.0.3 --- README.md | 4 ++-- build.gradle | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index f214f151..bad768ad 100644 --- a/README.md +++ b/README.md @@ -17,7 +17,7 @@ If you are using Gradle as your build tool, add the following dependency to your ``` dependencies { - implementation group: 'net.jacobpeterson', name: 'alpaca-java', version: '8.0.2' + implementation group: 'net.jacobpeterson', name: 'alpaca-java', version: '8.0.3' } ``` @@ -27,7 +27,7 @@ If you are using Maven as your build tool, add the following dependency to your net.jacobpeterson alpaca-java - 8.0.2 + 8.0.3 compile ``` diff --git a/build.gradle b/build.gradle index 86b0642a..76b72a3e 100644 --- a/build.gradle +++ b/build.gradle @@ -30,7 +30,7 @@ tasks.getByName("generateJsonSchema2Pojo").enabled(false) // Disable default 'js archivesBaseName = 'alpaca-java' group = 'net.jacobpeterson' -version = '8.0.2-SNAPSHOT' +version = '8.0.3-SNAPSHOT' repositories { mavenCentral() From 612ba47182885d9d5f08f6858b4320926a76dbde Mon Sep 17 00:00:00 2001 From: Jacob Peterson Date: Thu, 15 Jul 2021 20:30:52 -0600 Subject: [PATCH 143/271] Use Collection instead of List as arg, implement TODO --- .../alpaca/rest/endpoint/MarketDataEndpoint.java | 6 +++--- .../jacobpeterson/alpaca/rest/endpoint/OrdersEndpoint.java | 7 ++++--- .../alpaca/rest/endpoint/PositionsEndpoint.java | 4 ++-- 3 files changed, 9 insertions(+), 8 deletions(-) diff --git a/src/main/java/net/jacobpeterson/alpaca/rest/endpoint/MarketDataEndpoint.java b/src/main/java/net/jacobpeterson/alpaca/rest/endpoint/MarketDataEndpoint.java index 1b7e3eed..9a270e21 100644 --- a/src/main/java/net/jacobpeterson/alpaca/rest/endpoint/MarketDataEndpoint.java +++ b/src/main/java/net/jacobpeterson/alpaca/rest/endpoint/MarketDataEndpoint.java @@ -18,8 +18,8 @@ import okhttp3.Request; import java.time.ZonedDateTime; +import java.util.Collection; import java.util.HashMap; -import java.util.List; import java.util.Map; import static com.google.common.base.Preconditions.checkArgument; @@ -228,13 +228,13 @@ public BarsResponse getBars(String symbol, ZonedDateTime start, ZonedDateTime en * The Snapshot API for one ticker provides the latest trade, latest quote, minute bar daily bar, and previous daily * bar data for a given ticker symbol * - * @param symbols a {@link List} of symbols to query for + * @param symbols a {@link Collection} of symbols to query for * * @return a {@link Map} with they keys being the symbol and their values being the {@link Snapshot} * * @throws AlpacaClientException thrown for {@link AlpacaClientException}s */ - public Map getSnapshots(List symbols) throws AlpacaClientException { + public Map getSnapshots(Collection symbols) throws AlpacaClientException { checkNotNull(symbols); checkArgument(!symbols.isEmpty(), "'symbols' cannot be empty!"); diff --git a/src/main/java/net/jacobpeterson/alpaca/rest/endpoint/OrdersEndpoint.java b/src/main/java/net/jacobpeterson/alpaca/rest/endpoint/OrdersEndpoint.java index baead6d8..18b6239d 100644 --- a/src/main/java/net/jacobpeterson/alpaca/rest/endpoint/OrdersEndpoint.java +++ b/src/main/java/net/jacobpeterson/alpaca/rest/endpoint/OrdersEndpoint.java @@ -17,6 +17,7 @@ import java.time.ZonedDateTime; import java.time.format.DateTimeFormatter; import java.util.ArrayList; +import java.util.Collection; import java.util.List; import static com.google.common.base.Preconditions.checkNotNull; @@ -46,14 +47,14 @@ public OrdersEndpoint(AlpacaClient alpacaClient) { * @param direction the chronological order of response based on the submission time. Defaults to {@link * SortDirection#DESCENDING}. * @param nested if true, the result will roll up multi-leg orders under the legs field of primary order. - * @param symbols a {@link List} of symbols to filter the result by (null for no filter). + * @param symbols a {@link Collection} of symbols to filter the result by (null for no filter). * * @return a {@link List} of {@link Order}s * * @throws AlpacaClientException thrown for {@link AlpacaClientException}s */ public List get(CurrentOrderStatus status, Integer limit, ZonedDateTime after, ZonedDateTime until, - SortDirection direction, Boolean nested, List symbols) throws AlpacaClientException { + SortDirection direction, Boolean nested, Collection symbols) throws AlpacaClientException { HttpUrl.Builder urlBuilder = alpacaClient.urlBuilder() .addPathSegment(endpointPathSegment); @@ -600,7 +601,7 @@ public Order replace(String orderID, Integer quantity, OrderTimeInForce timeInFo * * @throws AlpacaClientException thrown for {@link AlpacaClientException}s */ - public ArrayList cancelAll() throws AlpacaClientException { + public List cancelAll() throws AlpacaClientException { HttpUrl.Builder urlBuilder = alpacaClient.urlBuilder() .addPathSegment(endpointPathSegment); Request request = alpacaClient.requestBuilder(urlBuilder.build()) diff --git a/src/main/java/net/jacobpeterson/alpaca/rest/endpoint/PositionsEndpoint.java b/src/main/java/net/jacobpeterson/alpaca/rest/endpoint/PositionsEndpoint.java index f5deb1d4..676537ba 100644 --- a/src/main/java/net/jacobpeterson/alpaca/rest/endpoint/PositionsEndpoint.java +++ b/src/main/java/net/jacobpeterson/alpaca/rest/endpoint/PositionsEndpoint.java @@ -89,8 +89,8 @@ public List closeAll(Boolean cancelOrders) throws AlpacaClie Request request = alpacaClient.requestBuilder(urlBuilder.build()) .delete() .build(); - // TODO response code might be 207 instead of 200 - return alpacaClient.requestObject(request, new TypeToken>() {}.getType()); + return alpacaClient.requestObject(request, (code) -> code == 200 || code == 207, + new TypeToken>() {}.getType()); } /** From 8cfbeef028ecdc03ed6a1efbc0f521a4f2b5c50e Mon Sep 17 00:00:00 2001 From: Jacob Peterson Date: Fri, 16 Jul 2021 21:27:58 -0600 Subject: [PATCH 144/271] Modify editorconfig to not use wildcard so much --- .editorconfig | 4 ++-- .../java/net/jacobpeterson/alpaca/AlpacaAPI.java | 13 ++++++++++++- .../net/jacobpeterson/alpaca/rest/AlpacaClient.java | 7 ++++++- .../alpaca/rest/endpoint/OrdersEndpoint.java | 7 ++++++- .../alpaca/util/doc/Documentation2JSONSchema.java | 6 +++++- .../alpaca/util/gson/LocalDateAdapter.java | 9 ++++++++- .../alpaca/util/gson/LocalTimeAdapter.java | 9 ++++++++- .../alpaca/util/gson/ZonedDateTimeAdapter.java | 9 ++++++++- .../alpaca/util/properties/PropertyUtil.java | 8 ++++---- .../alpaca/websocket/AlpacaWebsocket.java | 7 ++++++- .../websocket/marketdata/MarketDataWebsocket.java | 6 +++++- .../websocket/streaming/StreamingWebsocket.java | 13 +++++++++++-- 12 files changed, 81 insertions(+), 17 deletions(-) diff --git a/.editorconfig b/.editorconfig index f8b11fdd..604f1553 100644 --- a/.editorconfig +++ b/.editorconfig @@ -65,7 +65,7 @@ ij_java_case_statement_on_separate_line = true ij_java_catch_on_new_line = false ij_java_class_annotation_wrap = split_into_lines ij_java_class_brace_style = end_of_line -ij_java_class_count_to_use_import_on_demand = 5 +ij_java_class_count_to_use_import_on_demand = 20 ij_java_class_names_in_javadoc = 1 ij_java_do_not_indent_top_level_class_members = false ij_java_do_not_wrap_after_single_annotation = false @@ -129,7 +129,7 @@ ij_java_method_parameters_new_line_after_left_paren = false ij_java_method_parameters_right_paren_on_new_line = false ij_java_method_parameters_wrap = normal ij_java_modifier_list_wrap = false -ij_java_names_count_to_use_import_on_demand = 5 +ij_java_names_count_to_use_import_on_demand = 20 ij_java_parameter_annotation_wrap = normal ij_java_parentheses_expression_new_line_after_left_paren = false ij_java_parentheses_expression_right_paren_on_new_line = false diff --git a/src/main/java/net/jacobpeterson/alpaca/AlpacaAPI.java b/src/main/java/net/jacobpeterson/alpaca/AlpacaAPI.java index aafd8916..616e53e4 100644 --- a/src/main/java/net/jacobpeterson/alpaca/AlpacaAPI.java +++ b/src/main/java/net/jacobpeterson/alpaca/AlpacaAPI.java @@ -5,7 +5,18 @@ import net.jacobpeterson.alpaca.model.properties.EndpointAPIType; import net.jacobpeterson.alpaca.properties.AlpacaProperties; import net.jacobpeterson.alpaca.rest.AlpacaClient; -import net.jacobpeterson.alpaca.rest.endpoint.*; +import net.jacobpeterson.alpaca.rest.endpoint.AccountActivitiesEndpoint; +import net.jacobpeterson.alpaca.rest.endpoint.AccountConfigurationEndpoint; +import net.jacobpeterson.alpaca.rest.endpoint.AccountEndpoint; +import net.jacobpeterson.alpaca.rest.endpoint.AlpacaEndpoint; +import net.jacobpeterson.alpaca.rest.endpoint.AssetsEndpoint; +import net.jacobpeterson.alpaca.rest.endpoint.CalendarEndpoint; +import net.jacobpeterson.alpaca.rest.endpoint.ClockEndpoint; +import net.jacobpeterson.alpaca.rest.endpoint.MarketDataEndpoint; +import net.jacobpeterson.alpaca.rest.endpoint.OrdersEndpoint; +import net.jacobpeterson.alpaca.rest.endpoint.PortfolioHistoryEndpoint; +import net.jacobpeterson.alpaca.rest.endpoint.PositionsEndpoint; +import net.jacobpeterson.alpaca.rest.endpoint.WatchlistEndpoint; import net.jacobpeterson.alpaca.websocket.AlpacaWebsocket; import net.jacobpeterson.alpaca.websocket.marketdata.MarketDataWebsocket; import net.jacobpeterson.alpaca.websocket.marketdata.MarketDataWebsocketInterface; diff --git a/src/main/java/net/jacobpeterson/alpaca/rest/AlpacaClient.java b/src/main/java/net/jacobpeterson/alpaca/rest/AlpacaClient.java index 76a836a4..90e7265a 100644 --- a/src/main/java/net/jacobpeterson/alpaca/rest/AlpacaClient.java +++ b/src/main/java/net/jacobpeterson/alpaca/rest/AlpacaClient.java @@ -4,7 +4,12 @@ import com.google.gson.JsonElement; import com.google.gson.JsonParser; import net.jacobpeterson.alpaca.properties.AlpacaProperties; -import okhttp3.*; +import okhttp3.Headers; +import okhttp3.HttpUrl; +import okhttp3.OkHttpClient; +import okhttp3.Request; +import okhttp3.Response; +import okhttp3.ResponseBody; import java.io.IOException; import java.io.Reader; diff --git a/src/main/java/net/jacobpeterson/alpaca/rest/endpoint/OrdersEndpoint.java b/src/main/java/net/jacobpeterson/alpaca/rest/endpoint/OrdersEndpoint.java index 18b6239d..a542d12d 100644 --- a/src/main/java/net/jacobpeterson/alpaca/rest/endpoint/OrdersEndpoint.java +++ b/src/main/java/net/jacobpeterson/alpaca/rest/endpoint/OrdersEndpoint.java @@ -6,7 +6,12 @@ import net.jacobpeterson.alpaca.model.endpoint.common.enums.SortDirection; import net.jacobpeterson.alpaca.model.endpoint.order.CancelledOrder; import net.jacobpeterson.alpaca.model.endpoint.order.Order; -import net.jacobpeterson.alpaca.model.endpoint.order.enums.*; +import net.jacobpeterson.alpaca.model.endpoint.order.enums.CurrentOrderStatus; +import net.jacobpeterson.alpaca.model.endpoint.order.enums.OrderClass; +import net.jacobpeterson.alpaca.model.endpoint.order.enums.OrderSide; +import net.jacobpeterson.alpaca.model.endpoint.order.enums.OrderStatus; +import net.jacobpeterson.alpaca.model.endpoint.order.enums.OrderTimeInForce; +import net.jacobpeterson.alpaca.model.endpoint.order.enums.OrderType; import net.jacobpeterson.alpaca.rest.AlpacaClient; import net.jacobpeterson.alpaca.rest.AlpacaClientException; import net.jacobpeterson.alpaca.util.format.FormatUtil; diff --git a/src/main/java/net/jacobpeterson/alpaca/util/doc/Documentation2JSONSchema.java b/src/main/java/net/jacobpeterson/alpaca/util/doc/Documentation2JSONSchema.java index 0eefc3ea..29fbf597 100644 --- a/src/main/java/net/jacobpeterson/alpaca/util/doc/Documentation2JSONSchema.java +++ b/src/main/java/net/jacobpeterson/alpaca/util/doc/Documentation2JSONSchema.java @@ -7,7 +7,11 @@ import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.DocumentBuilderFactory; import javax.xml.parsers.ParserConfigurationException; -import javax.xml.xpath.*; +import javax.xml.xpath.XPath; +import javax.xml.xpath.XPathConstants; +import javax.xml.xpath.XPathExpression; +import javax.xml.xpath.XPathExpressionException; +import javax.xml.xpath.XPathFactory; import java.io.ByteArrayInputStream; import java.io.IOException; diff --git a/src/main/java/net/jacobpeterson/alpaca/util/gson/LocalDateAdapter.java b/src/main/java/net/jacobpeterson/alpaca/util/gson/LocalDateAdapter.java index 657a374d..d1dc5956 100644 --- a/src/main/java/net/jacobpeterson/alpaca/util/gson/LocalDateAdapter.java +++ b/src/main/java/net/jacobpeterson/alpaca/util/gson/LocalDateAdapter.java @@ -1,6 +1,13 @@ package net.jacobpeterson.alpaca.util.gson; -import com.google.gson.*; +import com.google.gson.Gson; +import com.google.gson.JsonDeserializationContext; +import com.google.gson.JsonDeserializer; +import com.google.gson.JsonElement; +import com.google.gson.JsonParseException; +import com.google.gson.JsonPrimitive; +import com.google.gson.JsonSerializationContext; +import com.google.gson.JsonSerializer; import java.lang.reflect.Type; import java.time.LocalDate; diff --git a/src/main/java/net/jacobpeterson/alpaca/util/gson/LocalTimeAdapter.java b/src/main/java/net/jacobpeterson/alpaca/util/gson/LocalTimeAdapter.java index 8cfbae4b..960ccda2 100644 --- a/src/main/java/net/jacobpeterson/alpaca/util/gson/LocalTimeAdapter.java +++ b/src/main/java/net/jacobpeterson/alpaca/util/gson/LocalTimeAdapter.java @@ -1,6 +1,13 @@ package net.jacobpeterson.alpaca.util.gson; -import com.google.gson.*; +import com.google.gson.Gson; +import com.google.gson.JsonDeserializationContext; +import com.google.gson.JsonDeserializer; +import com.google.gson.JsonElement; +import com.google.gson.JsonParseException; +import com.google.gson.JsonPrimitive; +import com.google.gson.JsonSerializationContext; +import com.google.gson.JsonSerializer; import java.lang.reflect.Type; import java.time.LocalTime; diff --git a/src/main/java/net/jacobpeterson/alpaca/util/gson/ZonedDateTimeAdapter.java b/src/main/java/net/jacobpeterson/alpaca/util/gson/ZonedDateTimeAdapter.java index c78f998c..7f1cf2a7 100644 --- a/src/main/java/net/jacobpeterson/alpaca/util/gson/ZonedDateTimeAdapter.java +++ b/src/main/java/net/jacobpeterson/alpaca/util/gson/ZonedDateTimeAdapter.java @@ -1,6 +1,13 @@ package net.jacobpeterson.alpaca.util.gson; -import com.google.gson.*; +import com.google.gson.Gson; +import com.google.gson.JsonDeserializationContext; +import com.google.gson.JsonDeserializer; +import com.google.gson.JsonElement; +import com.google.gson.JsonParseException; +import com.google.gson.JsonPrimitive; +import com.google.gson.JsonSerializationContext; +import com.google.gson.JsonSerializer; import java.lang.reflect.Type; import java.time.ZonedDateTime; diff --git a/src/main/java/net/jacobpeterson/alpaca/util/properties/PropertyUtil.java b/src/main/java/net/jacobpeterson/alpaca/util/properties/PropertyUtil.java index ae853579..50882e47 100644 --- a/src/main/java/net/jacobpeterson/alpaca/util/properties/PropertyUtil.java +++ b/src/main/java/net/jacobpeterson/alpaca/util/properties/PropertyUtil.java @@ -34,8 +34,8 @@ public static String getProperty(String propertyFile, String key) { /** * Gets a string property from a property file. Will try to IO load the properties in the property file if not - * cached already. If the desired property is not found in the 'propertyFile', then the 'defaultPropertyFile' is - * searched. + * cached already. If the desired property is not found in the propertyFile, then the + * defaultPropertyFile is searched. * * @param propertyFile the property file * @param defaultPropertyFile the default property file @@ -49,8 +49,8 @@ public static String getProperty(String propertyFile, String defaultPropertyFile /** * Gets a string property from a property file. Will try to IO load the properties in the property file if not - * cached already. If the desired property is not found in the 'propertyFile', then the 'defaultPropertyFile' is - * searched, and if it's not there, then 'defaultValue' is returned. + * cached already. If the desired property is not found in the propertyFile, then the + * defaultPropertyFile is searched, and if it's not there, then defaultValue is returned. * * @param propertyFile the property file * @param defaultPropertyFile the default property file diff --git a/src/main/java/net/jacobpeterson/alpaca/websocket/AlpacaWebsocket.java b/src/main/java/net/jacobpeterson/alpaca/websocket/AlpacaWebsocket.java index 281003bb..ff074286 100644 --- a/src/main/java/net/jacobpeterson/alpaca/websocket/AlpacaWebsocket.java +++ b/src/main/java/net/jacobpeterson/alpaca/websocket/AlpacaWebsocket.java @@ -1,7 +1,12 @@ package net.jacobpeterson.alpaca.websocket; import net.jacobpeterson.alpaca.util.okhttp.WebsocketStateListener; -import okhttp3.*; +import okhttp3.HttpUrl; +import okhttp3.OkHttpClient; +import okhttp3.Request; +import okhttp3.Response; +import okhttp3.WebSocket; +import okhttp3.WebSocketListener; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import org.slf4j.Logger; diff --git a/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/MarketDataWebsocket.java b/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/MarketDataWebsocket.java index e6c4024e..0d618a15 100644 --- a/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/MarketDataWebsocket.java +++ b/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/MarketDataWebsocket.java @@ -22,7 +22,11 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import java.util.*; +import java.util.Arrays; +import java.util.Collection; +import java.util.HashSet; +import java.util.List; +import java.util.Set; import static com.google.common.base.Preconditions.checkNotNull; import static com.google.common.base.Preconditions.checkState; diff --git a/src/main/java/net/jacobpeterson/alpaca/websocket/streaming/StreamingWebsocket.java b/src/main/java/net/jacobpeterson/alpaca/websocket/streaming/StreamingWebsocket.java index 704bbff4..8dda12dd 100644 --- a/src/main/java/net/jacobpeterson/alpaca/websocket/streaming/StreamingWebsocket.java +++ b/src/main/java/net/jacobpeterson/alpaca/websocket/streaming/StreamingWebsocket.java @@ -1,7 +1,11 @@ package net.jacobpeterson.alpaca.websocket.streaming; import com.google.common.collect.Iterables; -import com.google.gson.*; +import com.google.gson.JsonArray; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import com.google.gson.JsonParser; +import com.google.gson.JsonPrimitive; import net.jacobpeterson.alpaca.model.endpoint.streaming.StreamingMessage; import net.jacobpeterson.alpaca.model.endpoint.streaming.authorization.AuthorizationData; import net.jacobpeterson.alpaca.model.endpoint.streaming.authorization.AuthorizationMessage; @@ -17,7 +21,12 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import java.util.*; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.HashSet; +import java.util.List; +import java.util.Set; import static com.google.common.base.Preconditions.checkNotNull; import static com.google.common.base.Preconditions.checkState; From 678eef0b77f97a34147d17e0d5a17a9cb00c5fa8 Mon Sep 17 00:00:00 2001 From: Jacob Peterson Date: Sat, 17 Jul 2021 16:49:56 -0600 Subject: [PATCH 145/271] Fix trade_update_event.json Javadoc descriptions --- .../trade/enums/trade_update_event.json | 32 +------------------ .../AlpacaWebsocketMessageListener.java | 1 + 2 files changed, 2 insertions(+), 31 deletions(-) diff --git a/schema_json/endpoint/streaming/trade/enums/trade_update_event.json b/schema_json/endpoint/streaming/trade/enums/trade_update_event.json index f66d089a..e422fe52 100644 --- a/schema_json/endpoint/streaming/trade/enums/trade_update_event.json +++ b/schema_json/endpoint/streaming/trade/enums/trade_update_event.json @@ -26,54 +26,24 @@ { "description": "Sent when your order has been completely filled." }, - { - "description": "The time at which the order was filled." - }, - { - "description": "The average price per share at which the order was filled." - }, - { - "description": "The size of your total position, after this fill event, in shares. Positive for long positions, negative for short positions." - }, { "description": "Sent when a number of shares less than the total remaining quantity on your order has been filled." }, - { - "description": "The time at which the shares were filled." - }, - { - "description": "The average price per share at which the shares were filled." - }, - { - "description": "The size of your total position, after this fill event, in shares. Positive for long positions, negative for short positions." - }, { "description": "Sent when your requested cancelation of an order is processed." }, - { - "description": "The time at which the order was canceled." - }, { "description": "Sent when an order has reached the end of its lifespan, as determined by the order’s time in force value." }, - { - "description": "The time at which the order expired." - }, { "description": "Sent when the order is done executing for the day, and will not receive further updates until the next trading day." }, { "description": "Sent when your requested replacement of an order is processed." }, - { - "description": "The time at which the order was replaced." - }, { "description": "Sent when your order has been rejected." }, - { - "description": "The time at which the rejection occurred." - }, { "description": "Sent when the order has been received by Alpaca and routed to the exchanges, but has not yet been accepted for execution." }, @@ -87,7 +57,7 @@ "description": "Sent when the order is awaiting replacement." }, { - "description": "Sent when the order has been completed for the day - it is either “filled” or “done_for_day” - but remaining settlement calculations are still pending." + "description": "Sent when the order has been completed for the day - it is either \"filled\" or Z\"done_for_day\" - but remaining settlement calculations are still pending." }, { "description": "Sent when the order has been suspended and is not eligible for trading." diff --git a/src/main/java/net/jacobpeterson/alpaca/websocket/AlpacaWebsocketMessageListener.java b/src/main/java/net/jacobpeterson/alpaca/websocket/AlpacaWebsocketMessageListener.java index 6c6e3b14..da55535c 100644 --- a/src/main/java/net/jacobpeterson/alpaca/websocket/AlpacaWebsocketMessageListener.java +++ b/src/main/java/net/jacobpeterson/alpaca/websocket/AlpacaWebsocketMessageListener.java @@ -7,6 +7,7 @@ * @param the 'message type' type parameter * @param the 'message' type parameter */ +@FunctionalInterface public interface AlpacaWebsocketMessageListener { /** From 91a466431ef3d345d0f1a2cf8d76c7aa72ea5fbd Mon Sep 17 00:00:00 2001 From: Jacob Peterson Date: Tue, 20 Jul 2021 17:49:04 -0600 Subject: [PATCH 146/271] Fix AlpacaWebsocket reconnection logic as it was resulting in dead-lock in reconnection attempts due to processing sync websocket open event and waiting for authorization to resubscribe. Additionally, remove multiple AlpacaWebsocketMessageListeners feature as it uses expensive concurrency safety even in situations when a user only uses one listener (which is the case most of the time). --- .../alpaca/websocket/AlpacaWebsocket.java | 94 +++++++------------ .../websocket/AlpacaWebsocketInterface.java | 27 ++---- .../marketdata/MarketDataWebsocket.java | 11 ++- .../streaming/StreamingWebsocket.java | 11 ++- 4 files changed, 64 insertions(+), 79 deletions(-) diff --git a/src/main/java/net/jacobpeterson/alpaca/websocket/AlpacaWebsocket.java b/src/main/java/net/jacobpeterson/alpaca/websocket/AlpacaWebsocket.java index ff074286..19dee225 100644 --- a/src/main/java/net/jacobpeterson/alpaca/websocket/AlpacaWebsocket.java +++ b/src/main/java/net/jacobpeterson/alpaca/websocket/AlpacaWebsocket.java @@ -12,8 +12,6 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import java.util.ArrayList; -import java.util.List; import java.util.concurrent.CompletableFuture; import java.util.concurrent.ForkJoinPool; import java.util.concurrent.Future; @@ -61,11 +59,8 @@ public abstract class AlpacaWebsocket listeners; - protected final Object listenersLock; - protected final List listenersToAdd; - protected final List listenersToRemove; + protected L listener; protected WebsocketStateListener websocketStateListener; protected WebSocket websocket; protected boolean connected; @@ -98,10 +93,6 @@ public AlpacaWebsocket(OkHttpClient okHttpClient, HttpUrl websocketURL, String w this.secretKey = secretKey; this.oAuthToken = oAuthToken; useOAuth = oAuthToken != null; - listeners = new ArrayList<>(); - listenersLock = new Object(); - listenersToAdd = new ArrayList<>(); - listenersToRemove = new ArrayList<>(); automaticallyReconnect = true; } @@ -119,7 +110,7 @@ public void connect() { @Override public void disconnect() { - if (isConnected() && websocket != null) { + if (websocket != null && isConnected()) { intentionalClose = true; websocket.close(WEBSOCKET_NORMAL_CLOSURE_CODE, WEBSOCKET_NORMAL_CLOSURE_MESSAGE); } else { @@ -139,17 +130,21 @@ public boolean isAuthenticated() { @Override public void onOpen(@NotNull WebSocket webSocket, @NotNull Response response) { + connected = true; + LOGGER.info("{} websocket opened.", websocketName); LOGGER.debug("{} websocket response: {}", websocketName, response); - connected = true; - - if (reconnectAttempts > 0) { - reconnectAttempts = 0; - onReconnection(); - } else { - onConnection(); - } + // Call 'onConnection' or 'onReconnection' async to avoid any potential dead-locking since this is called + // in sync with 'onMessage' in OkHttp's 'WebSocketListener' + ForkJoinPool.commonPool().execute(() -> { + if (reconnectAttempts > 0) { + reconnectAttempts = 0; + onReconnection(); + } else { + onConnection(); + } + }); if (websocketStateListener != null) { websocketStateListener.onOpen(response); @@ -158,6 +153,8 @@ public void onOpen(@NotNull WebSocket webSocket, @NotNull Response response) { @Override public void onClosed(@NotNull WebSocket webSocket, int code, @NotNull String reason) { + connected = false; + if (intentionalClose) { LOGGER.info("{} websocket closed.", websocketName); LOGGER.debug("Close code: {}, Reason: {}", code, reason); @@ -174,7 +171,12 @@ public void onClosed(@NotNull WebSocket webSocket, int code, @NotNull String rea @Override public void onFailure(@NotNull WebSocket webSocket, @NotNull Throwable cause, @Nullable Response response) { - LOGGER.error("{} websocket failure! Response: {}", websocketName, response, cause); + LOGGER.error("{} websocket failure!", websocketName, cause); + + // A websocket failure occurs when either there is a connection failure or when the client throws + // an Exception when receiving a message. In either case, OkHttp will silently close the websocket + // connection, so try to reopen it. + connected = false; handleReconnectionAttempt(); if (websocketStateListener != null) { @@ -215,24 +217,24 @@ private void handleReconnectionAttempt() { * Cleans up this instances state variables. */ protected void cleanupState() { - listeners.clear(); - listenersToAdd.clear(); - listenersToRemove.clear(); - websocket = null; connected = false; authenticated = false; + if (authenticationMessageFuture != null && !authenticationMessageFuture.isDone()) { + authenticationMessageFuture.complete(false); + } + authenticationMessageFuture = null; intentionalClose = false; reconnectAttempts = 0; } /** - * Called when a websocket connection is made. + * Called asynchronously when a websocket connection is made. */ protected abstract void onConnection(); /** - * Called when a websocket reconnection is made after unintentional disconnection. + * Called asynchronously when a websocket reconnection is made after unintentional disconnection. */ protected abstract void onReconnection(); @@ -243,7 +245,7 @@ protected void cleanupState() { @Override public Future getAuthorizationFuture() { - if (authenticationMessageFuture == null || authenticationMessageFuture.isDone()) { + if (authenticationMessageFuture == null) { authenticationMessageFuture = new CompletableFuture<>(); } @@ -251,46 +253,22 @@ public Future getAuthorizationFuture() { } /** - * Calls the {@link AlpacaWebsocketMessageListener}s in {@link #listeners}. + * Calls the {@link AlpacaWebsocketMessageListener}. * * @param messageType the message type * @param message the message */ - protected void callListeners(T messageType, M message) { - // Remove all listeners that were request to be removed then add all listeners that were request to be added - // on the last 'onMessage' call to the listeners. - synchronized (listenersLock) { - listeners.removeAll(listenersToRemove); - listenersToRemove.clear(); - - listeners.addAll(listenersToAdd); - listenersToAdd.clear(); - } - - // Note that this doesn't need to acquire 'listenersLock' since 'listeners' is isolated - // to this instance and is not modified outside a lock. - for (L listener : listeners) { + protected void callListener(T messageType, M message) { + try { listener.onMessage(messageType, message); + } catch (Exception exception) { + LOGGER.error("{} listener threw exception!", websocketName, exception); } } @Override - public void addListener(L listener) { - synchronized (listenersLock) { - listenersToAdd.add(listener); - } - } - - @Override - public void removeListener(L listener) { - if (listeners.size() == 1 && listeners.contains(listener)) { - disconnect(); - return; - } - - synchronized (listenersLock) { - listenersToRemove.add(listener); - } + public void setListener(L listener) { + this.listener = listener; } /** diff --git a/src/main/java/net/jacobpeterson/alpaca/websocket/AlpacaWebsocketInterface.java b/src/main/java/net/jacobpeterson/alpaca/websocket/AlpacaWebsocketInterface.java index 290f60f4..c508e799 100644 --- a/src/main/java/net/jacobpeterson/alpaca/websocket/AlpacaWebsocketInterface.java +++ b/src/main/java/net/jacobpeterson/alpaca/websocket/AlpacaWebsocketInterface.java @@ -2,6 +2,8 @@ import java.util.concurrent.ExecutionException; import java.util.concurrent.Future; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; /** * {@link AlpacaWebsocketInterface} defines an interface for Alpaca websockets. @@ -35,22 +37,23 @@ public interface AlpacaWebsocketInterface getAuthorizationFuture(); /** - * Waits for {@link #getAuthorizationFuture()} to complete and returns its value. + * Waits for {@link #getAuthorizationFuture()} to complete and returns its value. This will timeout after 10 seconds + * and then return false. * * @return a boolean */ default boolean waitForAuthorization() { try { - return getAuthorizationFuture().get(); - } catch (InterruptedException | ExecutionException ignored) {} + return getAuthorizationFuture().get(10, TimeUnit.SECONDS); + } catch (InterruptedException | ExecutionException | TimeoutException ignored) {} return false; } @@ -64,19 +67,9 @@ default boolean isValid() { } /** - * Adds a {@link AlpacaWebsocketMessageListener}. + * Sets the {@link AlpacaWebsocketMessageListener} for this {@link AlpacaWebsocketInterface}. * * @param listener the {@link AlpacaWebsocketMessageListener} */ - void addListener(L listener); - - /** - * Remove a {@link AlpacaWebsocketMessageListener}. - *
- * Note that this will call {@link AlpacaWebsocketInterface#disconnect()} if this is the last listener being - * removed. - * - * @param listener the {@link AlpacaWebsocketMessageListener} - */ - void removeListener(L listener); + void setListener(L listener); } diff --git a/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/MarketDataWebsocket.java b/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/MarketDataWebsocket.java index 0d618a15..ad189169 100644 --- a/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/MarketDataWebsocket.java +++ b/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/MarketDataWebsocket.java @@ -115,6 +115,9 @@ protected void onReconnection() { @Override protected void sendAuthenticationMessage() { + // Ensures that 'authenticationMessageFuture' exists + getAuthorizationFuture(); + /* Format of message is: * { * "action": "auth", @@ -208,7 +211,7 @@ public void onMessage(@NotNull WebSocket webSocket, @NotNull String message) { } if (listenedMarketDataMessageTypes.contains(marketDataMessageType)) { - callListeners(marketDataMessageType, marketDataMessage); + callListener(marketDataMessageType, marketDataMessage); } } } @@ -290,7 +293,11 @@ private void sendSubscriptionUpdate(Collection tradeSymbols, Collection< Collection barSymbols, boolean subscribe) { if (!isConnected()) { connect(); - waitForAuthorization(); + + if (!waitForAuthorization()) { + LOGGER.error("Not subscribing to streams due to unauthorized {} websocket.", websocketName); + return; + } } /* Format of message is: diff --git a/src/main/java/net/jacobpeterson/alpaca/websocket/streaming/StreamingWebsocket.java b/src/main/java/net/jacobpeterson/alpaca/websocket/streaming/StreamingWebsocket.java index 8dda12dd..ceefd873 100644 --- a/src/main/java/net/jacobpeterson/alpaca/websocket/streaming/StreamingWebsocket.java +++ b/src/main/java/net/jacobpeterson/alpaca/websocket/streaming/StreamingWebsocket.java @@ -101,6 +101,9 @@ protected void onReconnection() { @Override protected void sendAuthenticationMessage() { + // Ensures that 'authenticationMessageFuture' exists + getAuthorizationFuture(); + /* Authentication message format: * { * "action": "authenticate", @@ -183,7 +186,7 @@ public void onMessage(@NotNull WebSocket webSocket, @NotNull ByteString byteStri } if (listenedStreamMessageTypes.contains(streamingMessageType)) { - callListeners(streamingMessageType, streamingMessage); + callListener(streamingMessageType, streamingMessage); } } @@ -208,7 +211,11 @@ public void streams(StreamingMessageType... streamingMessageTypes) { if (!isConnected()) { connect(); - waitForAuthorization(); + + if (!waitForAuthorization()) { + LOGGER.error("Not subscribing to streams due to unauthorized {} websocket.", websocketName); + return; + } } // Stream request format: From 6ce402b6516bc40e0c11ba37986b450666e3f77c Mon Sep 17 00:00:00 2001 From: Jacob Peterson Date: Tue, 20 Jul 2021 18:13:06 -0600 Subject: [PATCH 147/271] Update README, fix non-subscribable message types in StreamingWebsocket --- README.md | 18 ++---------------- .../streaming/StreamingWebsocket.java | 5 +++++ 2 files changed, 7 insertions(+), 16 deletions(-) diff --git a/README.md b/README.md index bad768ad..31513e02 100644 --- a/README.md +++ b/README.md @@ -372,7 +372,7 @@ Example usage: // Add a 'StreamingListener' that simply prints streaming information StreamingListener streamingListener = (messageType, message) -> System.out.printf("%s: %s\n", messageType.name(), message); -alpacaAPI.streaming().addListener(streamingListener); +alpacaAPI.streaming().setListener(streamingListener); // Listen to the 'authorization' and 'trade update' streams. // Note this will connect the websocket and wait for authorization @@ -380,13 +380,6 @@ alpacaAPI.streaming().addListener(streamingListener); alpacaAPI.streaming().streams( StreamingMessageType.AUTHORIZATION, StreamingMessageType.TRADE_UPDATES); - -// Wait for 5 seconds -Thread.sleep(5000); - -// Note that when the last 'StreamingListener' is removed, the websocket -// connection is closed. -alpacaAPI.streaming().removeListener(streamingListener); ``` The following methods show how you can control the state of the [`StreamingWebsocket`](src/main/java/net/jacobpeterson/alpaca/websocket/streaming/StreamingWebsocket.java) directly. @@ -405,7 +398,7 @@ Example usage: // Add a 'MarketDataListener' that simply prints market data information MarketDataListener marketDataListener = (messageType, message) -> System.out.printf("%s: %s\n", messageType.name(), message); -alpacaAPI.marketDataStreaming().addListener(marketDataListener); +alpacaAPI.marketDataStreaming().setListener(marketDataListener); // Listen to 'SubscriptionsMessage', 'SuccessMessage', and 'ErrorMessage' control messages // that contain information about the stream's current state. @@ -420,13 +413,6 @@ alpacaAPI.marketDataStreaming().subscribe( Arrays.asList("AAPL", "TSLA"), null, Arrays.asList("*")); - -// Wait for 5 seconds -Thread.sleep(5000); - -// Note that when the last 'MarketDataListener' is removed, the websocket -// connection is closed. -alpacaAPI.marketDataStreaming().removeListener(marketDataListener); ``` The following methods show how you can control the state of the [`MarketDataWebsocket`](src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/MarketDataWebsocket.java) directly. diff --git a/src/main/java/net/jacobpeterson/alpaca/websocket/streaming/StreamingWebsocket.java b/src/main/java/net/jacobpeterson/alpaca/websocket/streaming/StreamingWebsocket.java index ceefd873..8c52b078 100644 --- a/src/main/java/net/jacobpeterson/alpaca/websocket/streaming/StreamingWebsocket.java +++ b/src/main/java/net/jacobpeterson/alpaca/websocket/streaming/StreamingWebsocket.java @@ -209,6 +209,11 @@ public void streams(StreamingMessageType... streamingMessageTypes) { return; } + // Add all non-subscribable 'streamingMessageTypes' before connecting or sending websocket message + Arrays.stream(streamingMessageTypes) + .filter(not(SUBSCRIBABLE_STREAMING_MESSAGE_TYPES::contains)) + .forEach(listenedStreamMessageTypes::add); + if (!isConnected()) { connect(); From de0e69b3b2dd14a034ef195e4d6ebd6fec29d86c Mon Sep 17 00:00:00 2001 From: Jacob Peterson Date: Wed, 21 Jul 2021 09:20:36 -0600 Subject: [PATCH 148/271] Update to 8.1 --- README.md | 4 ++-- build.gradle | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 31513e02..dad90b54 100644 --- a/README.md +++ b/README.md @@ -17,7 +17,7 @@ If you are using Gradle as your build tool, add the following dependency to your ``` dependencies { - implementation group: 'net.jacobpeterson', name: 'alpaca-java', version: '8.0.3' + implementation group: 'net.jacobpeterson', name: 'alpaca-java', version: '8.1' } ``` @@ -27,7 +27,7 @@ If you are using Maven as your build tool, add the following dependency to your net.jacobpeterson alpaca-java - 8.0.3 + 8.1 compile ``` diff --git a/build.gradle b/build.gradle index 76b72a3e..6e25af5e 100644 --- a/build.gradle +++ b/build.gradle @@ -30,7 +30,7 @@ tasks.getByName("generateJsonSchema2Pojo").enabled(false) // Disable default 'js archivesBaseName = 'alpaca-java' group = 'net.jacobpeterson' -version = '8.0.3-SNAPSHOT' +version = '8.1-SNAPSHOT' repositories { mavenCentral() From baeb5799f14dff70ed8e2adade6fec7b7d40a67d Mon Sep 17 00:00:00 2001 From: Jacob Peterson Date: Mon, 2 Aug 2021 17:18:03 -0600 Subject: [PATCH 149/271] Fix parseAPIErrorResponse so response code and message are included --- .../alpaca/rest/AlpacaClientException.java | 34 +++++++++---------- 1 file changed, 16 insertions(+), 18 deletions(-) diff --git a/src/main/java/net/jacobpeterson/alpaca/rest/AlpacaClientException.java b/src/main/java/net/jacobpeterson/alpaca/rest/AlpacaClientException.java index 40ef1d6c..bcf89c2b 100644 --- a/src/main/java/net/jacobpeterson/alpaca/rest/AlpacaClientException.java +++ b/src/main/java/net/jacobpeterson/alpaca/rest/AlpacaClientException.java @@ -66,11 +66,7 @@ public String getMessage() { if (responseBody == null) { return super.getMessage(); } else { - try { - return parseAPIErrorResponse(); - } catch (Exception ignored) { - return super.getMessage(); - } + return parseAPIErrorResponse(); } } @@ -87,24 +83,26 @@ public String getMessage() { * @return a formatted message of the error response */ private String parseAPIErrorResponse() { - JsonElement responseJsonElement = JsonParser.parseString(responseBody); + try { + JsonElement responseJsonElement = JsonParser.parseString(responseBody); - if (responseJsonElement instanceof JsonObject) { - JsonObject responseJsonObject = responseJsonElement.getAsJsonObject(); + if (responseJsonElement instanceof JsonObject) { + JsonObject responseJsonObject = responseJsonElement.getAsJsonObject(); - if (responseJsonObject.has(CODE_KEY)) { - apiResponseCode = responseJsonObject.get(CODE_KEY).getAsInt(); - } + if (responseJsonObject.has(CODE_KEY)) { + apiResponseCode = responseJsonObject.get(CODE_KEY).getAsInt(); + } - if (responseJsonObject.has(MESSAGE_KEY)) { - apiResponseMessage = responseJsonObject.get(MESSAGE_KEY).getAsString(); + if (responseJsonObject.has(MESSAGE_KEY)) { + apiResponseMessage = responseJsonObject.get(MESSAGE_KEY).getAsString(); + } } - } - // Just use the response JSON if the message could not be parsed. - if (apiResponseMessage == null) { - apiResponseMessage = responseJsonElement.toString(); - } + // Just use the response JSON if the message could not be parsed. + if (apiResponseMessage == null) { + apiResponseMessage = responseJsonElement.toString(); + } + } catch (Exception ignored) {} // Build message StringBuilder messageBuilder = new StringBuilder(); From 997ec27ba85a9cf8b6dd4c7740ffc274bda17b8f Mon Sep 17 00:00:00 2001 From: Jacob Peterson Date: Tue, 3 Aug 2021 16:44:58 -0600 Subject: [PATCH 150/271] Fix NPE if no listener is set in AlpacaWebsocket --- .../alpaca/websocket/AlpacaWebsocket.java | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/main/java/net/jacobpeterson/alpaca/websocket/AlpacaWebsocket.java b/src/main/java/net/jacobpeterson/alpaca/websocket/AlpacaWebsocket.java index 19dee225..331d89da 100644 --- a/src/main/java/net/jacobpeterson/alpaca/websocket/AlpacaWebsocket.java +++ b/src/main/java/net/jacobpeterson/alpaca/websocket/AlpacaWebsocket.java @@ -259,10 +259,12 @@ public Future getAuthorizationFuture() { * @param message the message */ protected void callListener(T messageType, M message) { - try { - listener.onMessage(messageType, message); - } catch (Exception exception) { - LOGGER.error("{} listener threw exception!", websocketName, exception); + if (listener != null) { + try { + listener.onMessage(messageType, message); + } catch (Exception exception) { + LOGGER.error("{} listener threw exception!", websocketName, exception); + } } } From fe54b204c965aa5372f6bf1e52687a79c823a4eb Mon Sep 17 00:00:00 2001 From: cloudygeek Date: Wed, 4 Aug 2021 23:13:46 +0100 Subject: [PATCH 151/271] Update FormatUtil.java TimeZone Bug --- .../net/jacobpeterson/alpaca/util/format/FormatUtil.java | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/src/main/java/net/jacobpeterson/alpaca/util/format/FormatUtil.java b/src/main/java/net/jacobpeterson/alpaca/util/format/FormatUtil.java index 0b76f3b5..d2bf5b82 100644 --- a/src/main/java/net/jacobpeterson/alpaca/util/format/FormatUtil.java +++ b/src/main/java/net/jacobpeterson/alpaca/util/format/FormatUtil.java @@ -22,9 +22,6 @@ public class FormatUtil { // the 3rd and 4th least significant decimal digits. private static final NumberFormat CURRENCY_FORMATTER = new DecimalFormat("#0.00##", DecimalFormatSymbols.getInstance(Locale.US)); - private static final DateTimeFormatter RFC_3339_FORMATTER = - DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss'Z'").withZone(NEW_YORK_ZONED_ID); - /** * Formats an arbitrary number to a currency format. e.g. $123.45 * @@ -37,13 +34,15 @@ public static String toCurrencyFormat(Number numberToFormat) { } /** - * Formats a {@link TemporalAccessor} using {@link #RFC_3339_FORMATTER} {@link DateTimeFormatter}. + * Formats a {@link TemporalAccessor} using {@link DateTimeFormatter#ISO_OFFSET_DATE_TIME}. * * @param zonedDateTime the {@link ZonedDateTime} * * @return the formatted string */ public static String toRFC3339Format(ZonedDateTime zonedDateTime) { - return RFC_3339_FORMATTER.format(zonedDateTime); + //Alpaca requires RFC 3339 (https://www.ietf.org/rfc/rfc3339.txt) date time to be provided which includes time zone. + // ISO 8601 is compatible with RFC 3339. + return DateTimeFormatter.ISO_OFFSET_DATE_TIME.format(zonedDateTime); } } From 206774fe8b3878081801e455b108b187f4722411 Mon Sep 17 00:00:00 2001 From: Jacob Peterson Date: Thu, 5 Aug 2021 11:04:58 -0600 Subject: [PATCH 152/271] Add 'n' and 'vw' fields to Bar --- schema_json/endpoint/market_data/historical/bar/bar.json | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/schema_json/endpoint/market_data/historical/bar/bar.json b/schema_json/endpoint/market_data/historical/bar/bar.json index b39bff53..6be1fe66 100644 --- a/schema_json/endpoint/market_data/historical/bar/bar.json +++ b/schema_json/endpoint/market_data/historical/bar/bar.json @@ -25,6 +25,14 @@ "v": { "existingJavaType": "java.lang.Long", "title": "Volume." + }, + "n": { + "existingJavaType": "java.lang.Long", + "title": "Trade count." + }, + "vw": { + "existingJavaType": "java.lang.Double", + "title": "VWAP (Volume Weighted Average Price)." } } } From 23f126f257daff669f82ff9076d52e277c4d97b6 Mon Sep 17 00:00:00 2001 From: Jacob Peterson Date: Thu, 5 Aug 2021 11:17:30 -0600 Subject: [PATCH 153/271] Fix Javadoc and formatting in FormatUtil --- .../net/jacobpeterson/alpaca/util/format/FormatUtil.java | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/main/java/net/jacobpeterson/alpaca/util/format/FormatUtil.java b/src/main/java/net/jacobpeterson/alpaca/util/format/FormatUtil.java index d2bf5b82..b5a53a27 100644 --- a/src/main/java/net/jacobpeterson/alpaca/util/format/FormatUtil.java +++ b/src/main/java/net/jacobpeterson/alpaca/util/format/FormatUtil.java @@ -22,6 +22,7 @@ public class FormatUtil { // the 3rd and 4th least significant decimal digits. private static final NumberFormat CURRENCY_FORMATTER = new DecimalFormat("#0.00##", DecimalFormatSymbols.getInstance(Locale.US)); + /** * Formats an arbitrary number to a currency format. e.g. $123.45 * @@ -35,14 +36,16 @@ public static String toCurrencyFormat(Number numberToFormat) { /** * Formats a {@link TemporalAccessor} using {@link DateTimeFormatter#ISO_OFFSET_DATE_TIME}. + *
+ * Alpaca requires RFC 3339 formatted timestamps to be provided which includes the timezone. ISO + * 8601 is compatible with RFC 3339 which is what {@link DateTimeFormatter#ISO_OFFSET_DATE_TIME} + * uses. * * @param zonedDateTime the {@link ZonedDateTime} * * @return the formatted string */ public static String toRFC3339Format(ZonedDateTime zonedDateTime) { - //Alpaca requires RFC 3339 (https://www.ietf.org/rfc/rfc3339.txt) date time to be provided which includes time zone. - // ISO 8601 is compatible with RFC 3339. return DateTimeFormatter.ISO_OFFSET_DATE_TIME.format(zonedDateTime); } } From 945bed160093150dfce957905f5e573188fdb689 Mon Sep 17 00:00:00 2001 From: Jacob Peterson Date: Thu, 5 Aug 2021 11:56:38 -0600 Subject: [PATCH 154/271] Modify websocket Javadocs --- .../websocket/AlpacaWebsocketInterface.java | 7 +++++-- .../MarketDataWebsocketInterface.java | 18 +++++++++--------- .../streaming/StreamingWebsocketInterface.java | 4 ++-- 3 files changed, 16 insertions(+), 13 deletions(-) diff --git a/src/main/java/net/jacobpeterson/alpaca/websocket/AlpacaWebsocketInterface.java b/src/main/java/net/jacobpeterson/alpaca/websocket/AlpacaWebsocketInterface.java index c508e799..272774f6 100644 --- a/src/main/java/net/jacobpeterson/alpaca/websocket/AlpacaWebsocketInterface.java +++ b/src/main/java/net/jacobpeterson/alpaca/websocket/AlpacaWebsocketInterface.java @@ -39,14 +39,17 @@ public interface AlpacaWebsocketInterface + * Note that if this {@link AlpacaWebsocketInterface} is already authorized, the returned {@link Future} will likely + * never complete. * * @return a {@link Boolean} {@link Future} */ Future getAuthorizationFuture(); /** - * Waits for {@link #getAuthorizationFuture()} to complete and returns its value. This will timeout after 10 seconds - * and then return false. + * Waits for {@link #getAuthorizationFuture()} to complete and returns its value. After 10 seconds of waiting, this + * will timeout then return false. * * @return a boolean */ diff --git a/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/MarketDataWebsocketInterface.java b/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/MarketDataWebsocketInterface.java index 7c7d4d90..2170e898 100644 --- a/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/MarketDataWebsocketInterface.java +++ b/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/MarketDataWebsocketInterface.java @@ -15,11 +15,11 @@ public interface MarketDataWebsocketInterface extends AlpacaWebsocketInterface { /** - * Subscribe to a specific control {@link MarketDataMessage}. That is, {@link ErrorMessage}, {@link - * SubscriptionsMessage}, or {@link SuccessMessage}. + * Subscribe to a specific control {@link MarketDataMessage} which contain information about the stream's current + * state. That is, an {@link ErrorMessage}, {@link SubscriptionsMessage}, or {@link SuccessMessage}. * - * @param marketDataMessageTypes array of any of: {@link MarketDataMessageType#SUCCESS}, {@link - * MarketDataMessageType#ERROR}, or {@link MarketDataMessageType#SUBSCRIPTION} + * @param marketDataMessageTypes array containing any of the following: {@link MarketDataMessageType#SUCCESS}, + * {@link MarketDataMessageType#ERROR}, or {@link MarketDataMessageType#SUBSCRIPTION} */ void subscribeToControl(MarketDataMessageType... marketDataMessageTypes); @@ -44,7 +44,7 @@ public interface MarketDataWebsocketInterface extends AlpacaWebsocketInterface - * Note that any one of the given {@link Collection}s can contain the wildcard character e.g. "*" to unsubscribe + * Note that any one of the given {@link Collection}s can contain the wildcard character (e.g. "*") to unsubscribe * from a previously subscribed wildcard. * * @param tradeSymbols a {@link Collection} of symbols to unsubscribe from trades or null for no @@ -58,28 +58,28 @@ public interface MarketDataWebsocketInterface extends AlpacaWebsocketInterface tradeSymbols, Collection quoteSymbols, Collection barSymbols); /** - * Gets all of the currently subscribed control {@link MarketDataMessageType}s. + * Gets all the currently subscribed control {@link MarketDataMessageType}s. * * @return a {@link Collection} of {@link MarketDataMessageType}s */ Collection subscribedControls(); /** - * Gets all of the currently subscribed symbols for trade updates. + * Gets all the currently subscribed symbols for trade updates. * * @return a {@link Collection} of {@link String}s */ Collection subscribedTrades(); /** - * Gets all of the currently subscribed symbols for quote updates. + * Gets all the currently subscribed symbols for quote updates. * * @return a {@link Collection} of {@link String}s */ Collection subscribedQuotes(); /** - * Gets all of the currently subscribed symbols for bar updates. + * Gets all the currently subscribed symbols for bar updates. * * @return a {@link Collection} of {@link String}s */ diff --git a/src/main/java/net/jacobpeterson/alpaca/websocket/streaming/StreamingWebsocketInterface.java b/src/main/java/net/jacobpeterson/alpaca/websocket/streaming/StreamingWebsocketInterface.java index 9b2dc5ca..07112a68 100644 --- a/src/main/java/net/jacobpeterson/alpaca/websocket/streaming/StreamingWebsocketInterface.java +++ b/src/main/java/net/jacobpeterson/alpaca/websocket/streaming/StreamingWebsocketInterface.java @@ -11,7 +11,7 @@ public interface StreamingWebsocketInterface extends AlpacaWebsocketInterface { /** - * Sets the {@link StreamingMessageType}s to streamingMessageTypes. + * Sets the {@link StreamingMessageType}s for this stream to the given streamingMessageTypes. *
* Note that this will call {@link StreamingWebsocketInterface#connect()} and {@link * StreamingWebsocketInterface#waitForAuthorization()} if {@link StreamingWebsocketInterface#isConnected()} returns @@ -22,7 +22,7 @@ public interface StreamingWebsocketInterface extends AlpacaWebsocketInterface Date: Thu, 5 Aug 2021 12:04:42 -0600 Subject: [PATCH 155/271] Add websocket clarifications to README --- README.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/README.md b/README.md index dad90b54..f144c7d6 100644 --- a/README.md +++ b/README.md @@ -384,9 +384,13 @@ alpacaAPI.streaming().streams( The following methods show how you can control the state of the [`StreamingWebsocket`](src/main/java/net/jacobpeterson/alpaca/websocket/streaming/StreamingWebsocket.java) directly. ```java +// Note that you usually won't need to call connect() and waitForAuthorization() +// directly, as they are automatically called when subscribing to streams. alpacaAPI.streaming().connect(); alpacaAPI.streaming().waitForAuthorization(); System.out.println(alpacaAPI.streaming().isValid()); + +// You can manually disconnect the websocket as needed alpacaAPI.streaming().disconnect(); ``` @@ -417,9 +421,13 @@ alpacaAPI.marketDataStreaming().subscribe( The following methods show how you can control the state of the [`MarketDataWebsocket`](src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/MarketDataWebsocket.java) directly. ```java +// Note that you usually won't need to call connect() and waitForAuthorization() +// directly, as they are automatically called when subscribing to streams. alpacaAPI.marketDataStreaming().connect(); alpacaAPI.marketDataStreaming().waitForAuthorization(); System.out.println(alpacaAPI.marketDataStreaming().isValid()); + +// You can manually disconnect the websocket as needed alpacaAPI.marketDataStreaming().disconnect(); ``` From 7986444a31b5f3f6cb2664e679392140457b9fe0 Mon Sep 17 00:00:00 2001 From: Jacob Peterson Date: Thu, 5 Aug 2021 13:35:06 -0600 Subject: [PATCH 156/271] Remove auto-connect feature from websockets --- README.md | 56 ++++++++++--------- .../websocket/AlpacaWebsocketInterface.java | 11 ++-- .../marketdata/MarketDataWebsocket.java | 10 +--- .../MarketDataWebsocketInterface.java | 4 -- .../streaming/StreamingWebsocket.java | 14 ++--- .../StreamingWebsocketInterface.java | 4 -- 6 files changed, 44 insertions(+), 55 deletions(-) diff --git a/README.md b/README.md index f144c7d6..b0c47a8d 100644 --- a/README.md +++ b/README.md @@ -374,28 +374,31 @@ StreamingListener streamingListener = (messageType, message) -> System.out.printf("%s: %s\n", messageType.name(), message); alpacaAPI.streaming().setListener(streamingListener); -// Listen to the 'authorization' and 'trade update' streams. -// Note this will connect the websocket and wait for authorization -// if it isn't already connected. -alpacaAPI.streaming().streams( - StreamingMessageType.AUTHORIZATION, - StreamingMessageType.TRADE_UPDATES); -``` +// Listen 'AuthorizationMessage' and 'ListeningMessage' messages that contain +// information about the stream's current state. +alpacaAPI.streaming().streams(StreamingMessageType.AUTHORIZATION, + StreamingMessageType.LISTENING); -The following methods show how you can control the state of the [`StreamingWebsocket`](src/main/java/net/jacobpeterson/alpaca/websocket/streaming/StreamingWebsocket.java) directly. -```java -// Note that you usually won't need to call connect() and waitForAuthorization() -// directly, as they are automatically called when subscribing to streams. +// Connect the websocket and confirm authentication alpacaAPI.streaming().connect(); -alpacaAPI.streaming().waitForAuthorization(); -System.out.println(alpacaAPI.streaming().isValid()); +alpacaAPI.streaming().waitForAuthorization(5, TimeUnit.SECONDS); +if (!alpacaAPI.streaming().isValid()) { + System.out.println("Websocket not valid!"); + return; +} -// You can manually disconnect the websocket as needed +// Listen to the 'trade update' streams. +alpacaAPI.streaming().streams(StreamingMessageType.TRADE_UPDATES); + +// Wait a few seconds +Thread.sleep(5000); + +// Manually disconnect the websocket alpacaAPI.streaming().disconnect(); ``` ## [`MarketDataWebsocket`](src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/MarketDataWebsocket.java) -Alpaca's Data API v2 provides websocket streaming for trades, quotes, and minute bars. This helps receive the most up to date market information that could help your trading strategy to act upon certain market movement. +Alpaca's Data API v2 provides websocket streaming for trades, quotes, and minute bars. This helps receive the most up-to-date market information that could help your trading strategy to act upon certain market movement. Example usage: ```java @@ -410,24 +413,25 @@ alpacaAPI.marketDataStreaming().subscribeToControl( MarketDataMessageType.SUCCESS, MarketDataMessageType.SUBSCRIPTION, MarketDataMessageType.ERROR); + +// Connect the websocket and confirm authentication +alpacaAPI.marketDataStreaming().connect(); +alpacaAPI.marketDataStreaming().waitForAuthorization(5, TimeUnit.SECONDS); +if (!alpacaAPI.marketDataStreaming().isValid()) { + System.out.println("Websocket not valid!"); + return; +} + // Listen to the AAPL and TSLA trades and all ('*') bars. -// Note this will connect the websocket and wait for authorization -// if it isn't already connected. alpacaAPI.marketDataStreaming().subscribe( Arrays.asList("AAPL", "TSLA"), null, Arrays.asList("*")); -``` -The following methods show how you can control the state of the [`MarketDataWebsocket`](src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/MarketDataWebsocket.java) directly. -```java -// Note that you usually won't need to call connect() and waitForAuthorization() -// directly, as they are automatically called when subscribing to streams. -alpacaAPI.marketDataStreaming().connect(); -alpacaAPI.marketDataStreaming().waitForAuthorization(); -System.out.println(alpacaAPI.marketDataStreaming().isValid()); +// Wait a few seconds +Thread.sleep(5000); -// You can manually disconnect the websocket as needed +// Manually disconnect the websocket alpacaAPI.marketDataStreaming().disconnect(); ``` diff --git a/src/main/java/net/jacobpeterson/alpaca/websocket/AlpacaWebsocketInterface.java b/src/main/java/net/jacobpeterson/alpaca/websocket/AlpacaWebsocketInterface.java index 272774f6..e7d8b60c 100644 --- a/src/main/java/net/jacobpeterson/alpaca/websocket/AlpacaWebsocketInterface.java +++ b/src/main/java/net/jacobpeterson/alpaca/websocket/AlpacaWebsocketInterface.java @@ -48,14 +48,17 @@ public interface AlpacaWebsocketInterface getAuthorizationFuture(); /** - * Waits for {@link #getAuthorizationFuture()} to complete and returns its value. After 10 seconds of waiting, this - * will timeout then return false. + * Waits for {@link #getAuthorizationFuture()} to complete and returns its value, except when timeout + * time has elapsed, then this will return false. + * + * @param timeout the timeout time + * @param unit the timeout {@link TimeUnit} * * @return a boolean */ - default boolean waitForAuthorization() { + default boolean waitForAuthorization(long timeout, TimeUnit unit) { try { - return getAuthorizationFuture().get(10, TimeUnit.SECONDS); + return getAuthorizationFuture().get(timeout, unit); } catch (InterruptedException | ExecutionException | TimeoutException ignored) {} return false; } diff --git a/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/MarketDataWebsocket.java b/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/MarketDataWebsocket.java index ad189169..48953560 100644 --- a/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/MarketDataWebsocket.java +++ b/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/MarketDataWebsocket.java @@ -27,6 +27,7 @@ import java.util.HashSet; import java.util.List; import java.util.Set; +import java.util.concurrent.TimeUnit; import static com.google.common.base.Preconditions.checkNotNull; import static com.google.common.base.Preconditions.checkState; @@ -107,7 +108,7 @@ protected void onConnection() { @Override protected void onReconnection() { sendAuthenticationMessage(); - if (waitForAuthorization()) { + if (waitForAuthorization(5, TimeUnit.SECONDS)) { subscribeToControl(Iterables.toArray(listenedMarketDataMessageTypes, MarketDataMessageType.class)); subscribe(subscribedTrades, subscribedQuotes, subscribedBars); } @@ -292,12 +293,7 @@ public void unsubscribe(Collection tradeSymbols, Collection quot private void sendSubscriptionUpdate(Collection tradeSymbols, Collection quoteSymbols, Collection barSymbols, boolean subscribe) { if (!isConnected()) { - connect(); - - if (!waitForAuthorization()) { - LOGGER.error("Not subscribing to streams due to unauthorized {} websocket.", websocketName); - return; - } + throw new IllegalStateException("This websocket must be connected before sending subscription updates!"); } /* Format of message is: diff --git a/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/MarketDataWebsocketInterface.java b/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/MarketDataWebsocketInterface.java index 2170e898..9ac90112 100644 --- a/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/MarketDataWebsocketInterface.java +++ b/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/MarketDataWebsocketInterface.java @@ -28,10 +28,6 @@ public interface MarketDataWebsocketInterface extends AlpacaWebsocketInterface * Note that any one of the given {@link Collection}s can contain the wildcard character e.g. "*" to subscribe to * ALL available symbols. - *
- * Note that this will call {@link MarketDataWebsocketInterface#connect()} and {@link - * MarketDataWebsocketInterface#waitForAuthorization()} if {@link MarketDataWebsocketInterface#isConnected()} - * returns false. * * @param tradeSymbols a {@link Collection} of symbols to subscribe to trades or null for no change * @param quoteSymbols a {@link Collection} of symbols to subscribe to quotes or null for no change diff --git a/src/main/java/net/jacobpeterson/alpaca/websocket/streaming/StreamingWebsocket.java b/src/main/java/net/jacobpeterson/alpaca/websocket/streaming/StreamingWebsocket.java index 8c52b078..57ede8a4 100644 --- a/src/main/java/net/jacobpeterson/alpaca/websocket/streaming/StreamingWebsocket.java +++ b/src/main/java/net/jacobpeterson/alpaca/websocket/streaming/StreamingWebsocket.java @@ -27,6 +27,7 @@ import java.util.HashSet; import java.util.List; import java.util.Set; +import java.util.concurrent.TimeUnit; import static com.google.common.base.Preconditions.checkNotNull; import static com.google.common.base.Preconditions.checkState; @@ -94,7 +95,7 @@ protected void onConnection() { @Override protected void onReconnection() { sendAuthenticationMessage(); - if (waitForAuthorization()) { + if (waitForAuthorization(5, TimeUnit.SECONDS)) { streams(Iterables.toArray(listenedStreamMessageTypes, StreamingMessageType.class)); } } @@ -214,15 +215,6 @@ public void streams(StreamingMessageType... streamingMessageTypes) { .filter(not(SUBSCRIBABLE_STREAMING_MESSAGE_TYPES::contains)) .forEach(listenedStreamMessageTypes::add); - if (!isConnected()) { - connect(); - - if (!waitForAuthorization()) { - LOGGER.error("Not subscribing to streams due to unauthorized {} websocket.", websocketName); - return; - } - } - // Stream request format: // { // "action": "listen", @@ -241,6 +233,8 @@ public void streams(StreamingMessageType... streamingMessageTypes) { if (streamsArray.isEmpty()) { return; + } else if (!isConnected()) { + throw new IllegalStateException("This websocket must be connected before subscribing to streams!"); } JsonObject dataObject = new JsonObject(); diff --git a/src/main/java/net/jacobpeterson/alpaca/websocket/streaming/StreamingWebsocketInterface.java b/src/main/java/net/jacobpeterson/alpaca/websocket/streaming/StreamingWebsocketInterface.java index 07112a68..44f0042e 100644 --- a/src/main/java/net/jacobpeterson/alpaca/websocket/streaming/StreamingWebsocketInterface.java +++ b/src/main/java/net/jacobpeterson/alpaca/websocket/streaming/StreamingWebsocketInterface.java @@ -12,10 +12,6 @@ public interface StreamingWebsocketInterface extends AlpacaWebsocketInterfacestreamingMessageTypes
. - *
- * Note that this will call {@link StreamingWebsocketInterface#connect()} and {@link - * StreamingWebsocketInterface#waitForAuthorization()} if {@link StreamingWebsocketInterface#isConnected()} returns - * false. * * @param streamingMessageTypes the {@link StreamingMessageType}s */ From 2a95a4911f3a0eedd3a8c9b67a07d79f9b059b3f Mon Sep 17 00:00:00 2001 From: Jacob Peterson Date: Thu, 5 Aug 2021 14:17:24 -0600 Subject: [PATCH 157/271] Add websocket clarification to README --- README.md | 8 ++++++-- .../websocket/AlpacaWebsocketInterface.java | 18 +++++++++--------- 2 files changed, 15 insertions(+), 11 deletions(-) diff --git a/README.md b/README.md index b0c47a8d..cca3aa50 100644 --- a/README.md +++ b/README.md @@ -375,7 +375,9 @@ StreamingListener streamingListener = (messageType, message) -> alpacaAPI.streaming().setListener(streamingListener); // Listen 'AuthorizationMessage' and 'ListeningMessage' messages that contain -// information about the stream's current state. +// information about the stream's current state. Note that these are subscribed +// to before the websocket is connected since these messages usually are sent +// upon websocket connection. alpacaAPI.streaming().streams(StreamingMessageType.AUTHORIZATION, StreamingMessageType.LISTENING); @@ -408,7 +410,9 @@ MarketDataListener marketDataListener = (messageType, message) -> alpacaAPI.marketDataStreaming().setListener(marketDataListener); // Listen to 'SubscriptionsMessage', 'SuccessMessage', and 'ErrorMessage' control messages -// that contain information about the stream's current state. +// that contain information about the stream's current state. Note that these are subscribed +// to before the websocket is connected since these messages usually are sent +// upon websocket connection. alpacaAPI.marketDataStreaming().subscribeToControl( MarketDataMessageType.SUCCESS, MarketDataMessageType.SUBSCRIPTION, diff --git a/src/main/java/net/jacobpeterson/alpaca/websocket/AlpacaWebsocketInterface.java b/src/main/java/net/jacobpeterson/alpaca/websocket/AlpacaWebsocketInterface.java index e7d8b60c..f524e1df 100644 --- a/src/main/java/net/jacobpeterson/alpaca/websocket/AlpacaWebsocketInterface.java +++ b/src/main/java/net/jacobpeterson/alpaca/websocket/AlpacaWebsocketInterface.java @@ -36,6 +36,15 @@ public interface AlpacaWebsocketInterface Date: Thu, 5 Aug 2021 14:31:58 -0600 Subject: [PATCH 158/271] Update to 8.2 --- README.md | 4 ++-- build.gradle | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index cca3aa50..e56eab73 100644 --- a/README.md +++ b/README.md @@ -17,7 +17,7 @@ If you are using Gradle as your build tool, add the following dependency to your ``` dependencies { - implementation group: 'net.jacobpeterson', name: 'alpaca-java', version: '8.1' + implementation group: 'net.jacobpeterson', name: 'alpaca-java', version: '8.2' } ``` @@ -27,7 +27,7 @@ If you are using Maven as your build tool, add the following dependency to your net.jacobpeterson alpaca-java - 8.1 + 8.2 compile ``` diff --git a/build.gradle b/build.gradle index 6e25af5e..cbace30e 100644 --- a/build.gradle +++ b/build.gradle @@ -30,7 +30,7 @@ tasks.getByName("generateJsonSchema2Pojo").enabled(false) // Disable default 'js archivesBaseName = 'alpaca-java' group = 'net.jacobpeterson' -version = '8.1-SNAPSHOT' +version = '8.2-SNAPSHOT' repositories { mavenCentral() From 9f6a5493a27381fd76512bec5b48bc7ad9ec3ab4 Mon Sep 17 00:00:00 2001 From: Jacob Peterson Date: Mon, 13 Sep 2021 15:24:52 -0600 Subject: [PATCH 159/271] Add BarAdjustment and BarTimePeriod enums to getBars() --- README.md | 6 ++- .../historical/bar/enums/bar_adjustment.json | 24 ++++++++++++ ...s_time_frame.json => bar_time_period.json} | 12 +++--- .../rest/endpoint/MarketDataEndpoint.java | 39 ++++++++++++------- .../rest/endpoint/PositionsEndpoint.java | 7 +++- 5 files changed, 65 insertions(+), 23 deletions(-) create mode 100644 schema_json/endpoint/market_data/historical/bar/enums/bar_adjustment.json rename schema_json/endpoint/market_data/historical/bar/enums/{bars_time_frame.json => bar_time_period.json} (68%) diff --git a/README.md b/README.md index e56eab73..7886cd50 100644 --- a/README.md +++ b/README.md @@ -101,7 +101,7 @@ The Data API v2 provides market data through an easy-to-use API for historical d Example usage: ```java try { - // Get AAPL one hour bars from 7/6/2021 market open + // Get AAPL one hour, split-adjusted bars from 7/6/2021 market open // to 7/8/2021 market close and print them out BarsResponse aaplBarsResponse = alpacaAPI.marketData().getBars( "AAPL", @@ -109,7 +109,9 @@ try { ZonedDateTime.of(2021, 7, 8, 12 + 4, 0, 0, 0, ZoneId.of("America/New_York")), null, null, - BarsTimeFrame.ONE_HOUR); + 1, + BarTimePeriod.HOUR, + BarAdjustment.SPLIT); aaplBarsResponse.getBars().forEach(System.out::println); // Get AAPL first 10 trades on 7/8/2021 at market open and print them out diff --git a/schema_json/endpoint/market_data/historical/bar/enums/bar_adjustment.json b/schema_json/endpoint/market_data/historical/bar/enums/bar_adjustment.json new file mode 100644 index 00000000..4f28b6ed --- /dev/null +++ b/schema_json/endpoint/market_data/historical/bar/enums/bar_adjustment.json @@ -0,0 +1,24 @@ +{ + "type": "string", + "title": "See Historical Data.", + "enum": [ + "raw", + "split", + "dividend", + "all" + ], + "javaEnums": [ + { + "name": "RAW" + }, + { + "name": "SPLIT" + }, + { + "name": "DIVIDEND" + }, + { + "name": "ALL" + } + ] +} diff --git a/schema_json/endpoint/market_data/historical/bar/enums/bars_time_frame.json b/schema_json/endpoint/market_data/historical/bar/enums/bar_time_period.json similarity index 68% rename from schema_json/endpoint/market_data/historical/bar/enums/bars_time_frame.json rename to schema_json/endpoint/market_data/historical/bar/enums/bar_time_period.json index 89eb394f..627d9413 100644 --- a/schema_json/endpoint/market_data/historical/bar/enums/bars_time_frame.json +++ b/schema_json/endpoint/market_data/historical/bar/enums/bar_time_period.json @@ -2,19 +2,19 @@ "type": "string", "title": "See Historical Data.", "enum": [ - "1Min", - "1Hour", - "1Day" + "Min", + "Hour", + "Day" ], "javaEnums": [ { - "name": "ONE_MINUTE" + "name": "MINUTE" }, { - "name": "ONE_HOUR" + "name": "HOUR" }, { - "name": "ONE_DAY" + "name": "DAY" } ] } diff --git a/src/main/java/net/jacobpeterson/alpaca/rest/endpoint/MarketDataEndpoint.java b/src/main/java/net/jacobpeterson/alpaca/rest/endpoint/MarketDataEndpoint.java index 9a270e21..a1b4c425 100644 --- a/src/main/java/net/jacobpeterson/alpaca/rest/endpoint/MarketDataEndpoint.java +++ b/src/main/java/net/jacobpeterson/alpaca/rest/endpoint/MarketDataEndpoint.java @@ -3,7 +3,8 @@ import com.google.gson.reflect.TypeToken; import net.jacobpeterson.alpaca.model.endpoint.marketdata.historical.bar.Bar; import net.jacobpeterson.alpaca.model.endpoint.marketdata.historical.bar.BarsResponse; -import net.jacobpeterson.alpaca.model.endpoint.marketdata.historical.bar.enums.BarsTimeFrame; +import net.jacobpeterson.alpaca.model.endpoint.marketdata.historical.bar.enums.BarAdjustment; +import net.jacobpeterson.alpaca.model.endpoint.marketdata.historical.bar.enums.BarTimePeriod; import net.jacobpeterson.alpaca.model.endpoint.marketdata.historical.quote.LatestQuoteResponse; import net.jacobpeterson.alpaca.model.endpoint.marketdata.historical.quote.Quote; import net.jacobpeterson.alpaca.model.endpoint.marketdata.historical.quote.QuotesResponse; @@ -180,26 +181,34 @@ public LatestQuoteResponse getLatestQuote(String symbol) throws AlpacaClientExce /** * Gets {@link Bar} aggregate historical data for the requested security. * - * @param symbol the symbol to query for - * @param start filter data equal to or after this {@link ZonedDateTime}. Fractions of a second are not - * accepted. - * @param end filter data equal to or before this {@link ZonedDateTime}. Fractions of a second are not - * accepted. - * @param limit number of data points to return. Must be in range 1-10000, defaults to 1000 if null - * is given - * @param pageToken pagination token to continue from - * @param timeFrame the {@link BarsTimeFrame} for the aggregation + * @param symbol the symbol to query for + * @param start filter data equal to or after this {@link ZonedDateTime}. Fractions of a second are + * not accepted. + * @param end filter data equal to or before this {@link ZonedDateTime}. Fractions of a second are + * not accepted. + * @param limit number of data points to return. Must be in range 1-10000, defaults to 1000 if + * null is given + * @param pageToken pagination token to continue from + * @param barTimePeriodDuration the duration for the given barTimePeriod parameter. e.g. for + * 15Min bars, you would supply 15 for this parameter and + * {@link BarTimePeriod#MINUTE} for the barTimePeriod parameter. + * @param barTimePeriod the {@link BarTimePeriod} e.g. for 15Min bars, you would supply {@link + * BarTimePeriod#MINUTE} for this parameter and 15 for the + * barTimePeriodDuration parameter. + * @param barAdjustment specifies the corporate action adjustment for the stocks. Default value is {@link + * BarAdjustment#RAW}. * * @return the {@link BarsResponse} * * @throws AlpacaClientException thrown for {@link AlpacaClientException}s */ public BarsResponse getBars(String symbol, ZonedDateTime start, ZonedDateTime end, Integer limit, - String pageToken, BarsTimeFrame timeFrame) throws AlpacaClientException { + String pageToken, int barTimePeriodDuration, BarTimePeriod barTimePeriod, BarAdjustment barAdjustment) + throws AlpacaClientException { checkNotNull(symbol); checkNotNull(start); checkNotNull(end); - checkNotNull(timeFrame); + checkNotNull(barTimePeriod); HttpUrl.Builder urlBuilder = alpacaClient.urlBuilder() .addPathSegment(endpointPathSegment) @@ -208,7 +217,7 @@ public BarsResponse getBars(String symbol, ZonedDateTime start, ZonedDateTime en urlBuilder.addQueryParameter("start", FormatUtil.toRFC3339Format(start)); urlBuilder.addQueryParameter("end", FormatUtil.toRFC3339Format(end)); - urlBuilder.addQueryParameter("timeframe", timeFrame.toString()); + urlBuilder.addQueryParameter("timeframe", barTimePeriodDuration + barTimePeriod.toString()); if (limit != null) { urlBuilder.addQueryParameter("limit", limit.toString()); @@ -218,6 +227,10 @@ public BarsResponse getBars(String symbol, ZonedDateTime start, ZonedDateTime en urlBuilder.addQueryParameter("page_token", pageToken); } + if (barAdjustment != null) { + urlBuilder.addQueryParameter("adjustment", barAdjustment.toString()); + } + Request request = alpacaClient.requestBuilder(urlBuilder.build()) .get() .build(); diff --git a/src/main/java/net/jacobpeterson/alpaca/rest/endpoint/PositionsEndpoint.java b/src/main/java/net/jacobpeterson/alpaca/rest/endpoint/PositionsEndpoint.java index 676537ba..26d72861 100644 --- a/src/main/java/net/jacobpeterson/alpaca/rest/endpoint/PositionsEndpoint.java +++ b/src/main/java/net/jacobpeterson/alpaca/rest/endpoint/PositionsEndpoint.java @@ -13,7 +13,6 @@ import java.util.ArrayList; import java.util.List; -import static com.google.common.base.Preconditions.checkArgument; import static com.google.common.base.Preconditions.checkNotNull; /** @@ -98,6 +97,11 @@ public List closeAll(Boolean cancelOrders) throws AlpacaClie * positions. * * @param symbolOrAssetID the symbol or {@link Asset#getId()} + * @param quantity the number of shares to liquidate. Can accept up to 9 decimal points. Cannot + * work with percentage. + * @param percentage the percentage of the position to liquidate. Must be between 0 and + * 100. Would only sell fractional if position is originally fractional. Can + * accept up to 9 decimal points. Cannot work with quantity. * * @return a closing {@link Position} {@link Order} * @@ -105,7 +109,6 @@ public List closeAll(Boolean cancelOrders) throws AlpacaClie */ public Order close(String symbolOrAssetID, Integer quantity, Double percentage) throws AlpacaClientException { checkNotNull(symbolOrAssetID); - checkArgument(quantity != null ^ percentage != null, "Either 'quantity' or 'percentage' are required."); HttpUrl.Builder urlBuilder = alpacaClient.urlBuilder() .addPathSegment(endpointPathSegment) From 1d7609275498816d2e97a7310b3ff853600196e3 Mon Sep 17 00:00:00 2001 From: Jacob Peterson Date: Mon, 13 Sep 2021 15:26:13 -0600 Subject: [PATCH 160/271] Update to 8.3 --- README.md | 4 ++-- build.gradle | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 7886cd50..946e09d6 100644 --- a/README.md +++ b/README.md @@ -17,7 +17,7 @@ If you are using Gradle as your build tool, add the following dependency to your ``` dependencies { - implementation group: 'net.jacobpeterson', name: 'alpaca-java', version: '8.2' + implementation group: 'net.jacobpeterson', name: 'alpaca-java', version: '8.3' } ``` @@ -27,7 +27,7 @@ If you are using Maven as your build tool, add the following dependency to your net.jacobpeterson alpaca-java - 8.2 + 8.3 compile ``` diff --git a/build.gradle b/build.gradle index cbace30e..1eb30114 100644 --- a/build.gradle +++ b/build.gradle @@ -30,7 +30,7 @@ tasks.getByName("generateJsonSchema2Pojo").enabled(false) // Disable default 'js archivesBaseName = 'alpaca-java' group = 'net.jacobpeterson' -version = '8.2-SNAPSHOT' +version = '8.3-SNAPSHOT' repositories { mavenCentral() From 3064fa3d51298f355c966aadb7fe88c390dc0f0e Mon Sep 17 00:00:00 2001 From: Jacob Peterson Date: Mon, 20 Sep 2021 08:11:41 -0600 Subject: [PATCH 161/271] Fix getSnapshot path segment typo --- .../jacobpeterson/alpaca/rest/endpoint/MarketDataEndpoint.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/net/jacobpeterson/alpaca/rest/endpoint/MarketDataEndpoint.java b/src/main/java/net/jacobpeterson/alpaca/rest/endpoint/MarketDataEndpoint.java index a1b4c425..45fc4a08 100644 --- a/src/main/java/net/jacobpeterson/alpaca/rest/endpoint/MarketDataEndpoint.java +++ b/src/main/java/net/jacobpeterson/alpaca/rest/endpoint/MarketDataEndpoint.java @@ -279,7 +279,7 @@ public Snapshot getSnapshot(String symbol) throws AlpacaClientException { HttpUrl.Builder urlBuilder = alpacaClient.urlBuilder() .addPathSegment(endpointPathSegment) .addPathSegment(symbol) - .addPathSegment("shapshot"); + .addPathSegment("snapshot"); Request request = alpacaClient.requestBuilder(urlBuilder.build()) .get() From 9bceea3171eee8078549d02f30138b18ef729dc0 Mon Sep 17 00:00:00 2001 From: Jacob Peterson Date: Mon, 20 Sep 2021 08:12:54 -0600 Subject: [PATCH 162/271] Update to 8.3.1 --- README.md | 4 ++-- build.gradle | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 946e09d6..b8ac95e1 100644 --- a/README.md +++ b/README.md @@ -17,7 +17,7 @@ If you are using Gradle as your build tool, add the following dependency to your ``` dependencies { - implementation group: 'net.jacobpeterson', name: 'alpaca-java', version: '8.3' + implementation group: 'net.jacobpeterson', name: 'alpaca-java', version: '8.3.1' } ``` @@ -27,7 +27,7 @@ If you are using Maven as your build tool, add the following dependency to your net.jacobpeterson alpaca-java - 8.3 + 8.3.1 compile ``` diff --git a/build.gradle b/build.gradle index 1eb30114..d5566da8 100644 --- a/build.gradle +++ b/build.gradle @@ -30,7 +30,7 @@ tasks.getByName("generateJsonSchema2Pojo").enabled(false) // Disable default 'js archivesBaseName = 'alpaca-java' group = 'net.jacobpeterson' -version = '8.3-SNAPSHOT' +version = '8.3.1-SNAPSHOT' repositories { mavenCentral() From 5df5e105706f7135df28255f1019137d8db93e52 Mon Sep 17 00:00:00 2001 From: Jacob Peterson <14127217+Petersoj@users.noreply.github.com> Date: Tue, 12 Oct 2021 17:20:56 -0600 Subject: [PATCH 163/271] Update README, cleanup AlpacaAPITest --- README.md | 3 +- .../alpaca/test/live/AlpacaAPITest.java | 41 +++++++++++-------- 2 files changed, 26 insertions(+), 18 deletions(-) diff --git a/README.md b/README.md index b8ac95e1..f571e41e 100644 --- a/README.md +++ b/README.md @@ -45,7 +45,7 @@ data_api_type = The default values for `alpaca.properties` can be found [here](src/main/resources/alpaca.default.properties). # Logger -For logging, this library uses [SLF4j](http://www.slf4j.org/) which serves as an interface for various logging frameworks. This enables you to use whatever logging framework you would like. However, if you do not add a logging framework as a dependency in your project, the console will output a message stating that SLF4j is defaulting to a no-operation (NOP) logger implementation. To enable logging, add a logging framework of your choice as a dependency to your project such as [Log4j 2](http://logging.apache.org/log4j/2.x/index.html), [SLF4j-simple](http://www.slf4j.org/manual.html), or [Apache Commons Logging](https://commons.apache.org/proper/commons-logging/). +For logging, this library uses [SLF4j](http://www.slf4j.org/) which serves as an interface for various logging frameworks. This enables you to use whatever logging framework you would like. However, if you do not add a logging framework as a dependency in your project, the console will output a message stating that SLF4j is defaulting to a no-operation (NOP) logger implementation. To enable logging, add a logging framework of your choice as a dependency to your project such as [Logback](http://logback.qos.ch/), [Log4j 2](http://logging.apache.org/log4j/2.x/index.html), [SLF4j-simple](http://www.slf4j.org/manual.html), or [Apache Commons Logging](https://commons.apache.org/proper/commons-logging/). # Examples Note that the examples below are not exhaustive. Refer to the [Javadoc](https://javadoc.io/doc/net.jacobpeterson/alpaca-java) for all classes and method signatures. @@ -471,6 +471,7 @@ Note that the live tests will modify your account minimally. It's meant to test - Use [TA4j](https://github.com/ta4j/ta4j) `Num` interface instead of `Double` for number variables so that users can use either `Double` or `BigDecimal` for performance or precision in price data. - Add [TimeSeriesDataStore](https://github.com/Petersoj/TimeSeriesDataStore) using Alpaca Data API +# Contributing Contributions are welcome! If you are creating a Pull Request, be sure to create a new branch in your forked repository for your feature or bug fix instead of committing directly to the `master` branch in your fork. diff --git a/src/test/java/net/jacobpeterson/alpaca/test/live/AlpacaAPITest.java b/src/test/java/net/jacobpeterson/alpaca/test/live/AlpacaAPITest.java index 00fe9e96..326a6aaf 100644 --- a/src/test/java/net/jacobpeterson/alpaca/test/live/AlpacaAPITest.java +++ b/src/test/java/net/jacobpeterson/alpaca/test/live/AlpacaAPITest.java @@ -1,5 +1,6 @@ package net.jacobpeterson.alpaca.test.live; +import net.jacobpeterson.alpaca.AlpacaAPI; import net.jacobpeterson.alpaca.model.endpoint.account.Account; import net.jacobpeterson.alpaca.model.endpoint.accountactivities.AccountActivity; import net.jacobpeterson.alpaca.model.endpoint.accountactivities.NonTradeActivity; @@ -12,21 +13,28 @@ import net.jacobpeterson.alpaca.model.endpoint.common.enums.SortDirection; import net.jacobpeterson.alpaca.model.endpoint.order.Order; import net.jacobpeterson.alpaca.model.endpoint.order.enums.CurrentOrderStatus; -import net.jacobpeterson.alpaca.AlpacaAPI; import net.jacobpeterson.alpaca.rest.AlpacaClientException; +import net.jacobpeterson.alpaca.rest.endpoint.AccountActivitiesEndpoint; import net.jacobpeterson.alpaca.rest.endpoint.AccountConfigurationEndpoint; import net.jacobpeterson.alpaca.rest.endpoint.AccountEndpoint; import net.jacobpeterson.alpaca.rest.endpoint.ClockEndpoint; -import net.jacobpeterson.alpaca.rest.endpoint.AccountActivitiesEndpoint; import net.jacobpeterson.alpaca.rest.endpoint.OrdersEndpoint; -import org.junit.jupiter.api.*; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.MethodOrderer; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestMethodOrder; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.time.ZonedDateTime; +import java.util.Collection; import java.util.List; -import static org.junit.jupiter.api.Assertions.*; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.fail; /** * {@link AlpacaAPITest} tests live endpoints using Alpaca Paper credentials given in the @@ -35,14 +43,14 @@ @TestMethodOrder(MethodOrderer.OrderAnnotation.class) public class AlpacaAPITest { - private static final Logger LOGGER = LoggerFactory.getLogger(AlpacaAPITest.class); - private static final int RATE_LIMIT_MILLIS = 200; // Wait 200ms between every test to prevent rate-limiting - static { // Log trace-level System.setProperty("org.slf4j.simpleLogger.log.net.jacobpeterson", "trace"); } + private static final Logger LOGGER = LoggerFactory.getLogger(AlpacaAPITest.class); + private static final int RATE_LIMIT_MILLIS = 200; // Wait 200ms between every test to prevent rate-limiting + private static AlpacaAPI alpacaAPI; private static boolean marketOpen; private static AccountConfiguration accountConfiguration; @@ -83,7 +91,7 @@ public void afterEach() { */ @Test @org.junit.jupiter.api.Order(1) - public void test_ClockEndpoint_get() throws AlpacaClientException { + public void testClockEndpointGet() throws AlpacaClientException { Clock clock = alpacaAPI.clock().get(); assertNotNull(clock); @@ -110,7 +118,7 @@ public void test_ClockEndpoint_get() throws AlpacaClientException { */ @Test @SuppressWarnings("ResultOfMethodCallIgnored") - public void test_AccountEndpoint_get() throws AlpacaClientException, NumberFormatException { + public void testAccountEndpointGet() throws AlpacaClientException, NumberFormatException { Account account = alpacaAPI.account().get(); assertNotNull(account); @@ -148,14 +156,13 @@ public void test_AccountEndpoint_get() throws AlpacaClientException, NumberForma } /** - * Tests @{@link AccountActivitiesEndpoint#get(ZonedDateTime, - * ZonedDateTime, ZonedDateTime, SortDirection, Integer, String, ActivityType...)} one {@link AccountActivity} - * exists until now. + * Tests @{@link AccountActivitiesEndpoint#get(ZonedDateTime, ZonedDateTime, ZonedDateTime, SortDirection, Integer, + * String, ActivityType...)} one {@link AccountActivity} exists until now. * * @throws AlpacaClientException thrown for {@link AlpacaClientException}s */ @Test - public void test_AccountActivitiesEndpoint_get_One_Activity_Exists_Until_Now() throws AlpacaClientException { + public void testAccountActivitiesEndpointGetOneActivityExistsUntilNow() throws AlpacaClientException { List accountActivities = alpacaAPI.accountActivities().get( null, ZonedDateTime.now(), @@ -203,7 +210,7 @@ public void test_AccountActivitiesEndpoint_get_One_Activity_Exists_Until_Now() t */ @Test @org.junit.jupiter.api.Order(1) - public void test_AccountConfigurationEndpoint_get() throws AlpacaClientException { + public void testAccountConfigurationEndpointGet() throws AlpacaClientException { AccountConfiguration accountConfiguration = alpacaAPI.accountConfiguration().get(); assertNotNull(accountConfiguration); @@ -224,7 +231,7 @@ public void test_AccountConfigurationEndpoint_get() throws AlpacaClientException */ @Test @org.junit.jupiter.api.Order(2) - public void test_AccountConfigurationEndpoint_set() throws AlpacaClientException { + public void testAccountConfigurationEndpointSet() throws AlpacaClientException { if (accountConfiguration == null) { AccountConfiguration newAccountConfiguration = new AccountConfiguration( DTBPCheck.BOTH, @@ -240,12 +247,12 @@ public void test_AccountConfigurationEndpoint_set() throws AlpacaClientException /** * Test {@link OrdersEndpoint#get(CurrentOrderStatus, Integer, ZonedDateTime, ZonedDateTime, SortDirection, Boolean, - * List)} one {@link Order} exists until now. + * Collection)} one {@link Order} exists until now. * * @throws AlpacaClientException thrown for {@link AlpacaClientException}s */ @Test - public void test_OrdersEndpoint_get_One_Order_Exists_Until_now() throws AlpacaClientException { + public void testOrdersEndpointGetOneOrderExistsUntilNow() throws AlpacaClientException { List orders = alpacaAPI.orders().get( CurrentOrderStatus.ALL, 1, From 95cc9352c032cdbc089c151da43557fa1bcabfca Mon Sep 17 00:00:00 2001 From: Jacob Peterson <14127217+Petersoj@users.noreply.github.com> Date: Thu, 11 Nov 2021 00:08:48 -0700 Subject: [PATCH 164/271] Add more notes about AlpacaAPI constructors in README, fix javadocs --- README.md | 9 ++++-- .../net/jacobpeterson/alpaca/AlpacaAPI.java | 32 +++++++++---------- 2 files changed, 23 insertions(+), 18 deletions(-) diff --git a/README.md b/README.md index f571e41e..a0dce667 100644 --- a/README.md +++ b/README.md @@ -60,10 +60,15 @@ Example usage: // This constructor uses the 'alpaca.properties' file on the classpath for configuration AlpacaAPI alpacaAPI = new AlpacaAPI(); -// This constructor passes in a 'keyID' and 'secret' String +// This constructor passes in a 'keyID' and 'secretKey' and uses the endpoint API type and data API +// type defined in the 'alpaca.properties' file (which default to 'paper' and 'iex' respectively) String keyID = ""; String secretKey = ""; -AlpacaAPI alpacaAPI = new AlpacaAPI(keyID, secret); +AlpacaAPI alpacaAPI = new AlpacaAPI(keyID, secretKey); + +// This constructor passes in a 'keyID' and 'secretKey' and uses the passed in endpoint API type +// and data API type (which are 'LIVE' and 'SIP' respectively in this example) +AlpacaAPI alpacaAPI = new AlpacaAPI(keyID, secretKey, EndpointAPIType.LIVE, DataAPIType.SIP); // This constructor is for OAuth tokens String oAuthToken = ""; diff --git a/src/main/java/net/jacobpeterson/alpaca/AlpacaAPI.java b/src/main/java/net/jacobpeterson/alpaca/AlpacaAPI.java index 616e53e4..b874d794 100644 --- a/src/main/java/net/jacobpeterson/alpaca/AlpacaAPI.java +++ b/src/main/java/net/jacobpeterson/alpaca/AlpacaAPI.java @@ -75,11 +75,11 @@ public AlpacaAPI() { /** * Instantiates a new {@link AlpacaAPI}. * - * @param keyID the key ID - * @param secret the secret + * @param keyID the key ID + * @param secretKey the secret key */ - public AlpacaAPI(String keyID, String secret) { - this(null, keyID, secret, null, + public AlpacaAPI(String keyID, String secretKey) { + this(null, keyID, secretKey, null, AlpacaProperties.ENDPOINT_API_TYPE, AlpacaProperties.DATA_API_TYPE); } @@ -88,12 +88,12 @@ public AlpacaAPI(String keyID, String secret) { * Instantiates a new {@link AlpacaAPI}. * * @param keyID the key ID - * @param secret the secret + * @param secretKey the secret key * @param endpointAPIType the {@link EndpointAPIType} * @param dataAPIType the {@link DataAPIType} */ - public AlpacaAPI(String keyID, String secret, EndpointAPIType endpointAPIType, DataAPIType dataAPIType) { - this(null, keyID, secret, null, endpointAPIType, dataAPIType); + public AlpacaAPI(String keyID, String secretKey, EndpointAPIType endpointAPIType, DataAPIType dataAPIType) { + this(null, keyID, secretKey, null, endpointAPIType, dataAPIType); } /** @@ -155,11 +155,9 @@ public AlpacaAPI(OkHttpClient okHttpClient, String keyID, String secretKey, Stri brokerClient = new AlpacaClient(okHttpClient, keyID, secretKey, brokerHostSubdomain, VERSION_2_PATH_SEGMENT); dataClient = new AlpacaClient(okHttpClient, keyID, secretKey, "data", VERSION_2_PATH_SEGMENT); - marketDataWebsocket = new MarketDataWebsocket(okHttpClient, dataAPIType, keyID, secretKey); } else { brokerClient = new AlpacaClient(okHttpClient, oAuthToken, brokerHostSubdomain, VERSION_2_PATH_SEGMENT); dataClient = null; - marketDataWebsocket = null; } accountEndpoint = new AccountEndpoint(brokerClient); @@ -174,10 +172,12 @@ public AlpacaAPI(OkHttpClient okHttpClient, String keyID, String secretKey, Stri accountActivitiesEndpoint = new AccountActivitiesEndpoint(brokerClient); portfolioHistoryEndpoint = new PortfolioHistoryEndpoint(brokerClient); streamingWebsocket = new StreamingWebsocket(okHttpClient, brokerHostSubdomain, keyID, secretKey, oAuthToken); + marketDataWebsocket = dataClient == null ? null : + new MarketDataWebsocket(okHttpClient, dataAPIType, keyID, secretKey); } /** - * @return {@link AccountEndpoint} + * @return the {@link AccountEndpoint} */ public AccountEndpoint account() { return accountEndpoint; @@ -191,42 +191,42 @@ public MarketDataEndpoint marketData() { } /** - * @return {@link OrdersEndpoint} + * @return the {@link OrdersEndpoint} */ public OrdersEndpoint orders() { return ordersEndpoint; } /** - * @return {@link PositionsEndpoint} + * @return the {@link PositionsEndpoint} */ public PositionsEndpoint positions() { return positionsEndpoint; } /** - * @return {@link AssetsEndpoint} + * @return the {@link AssetsEndpoint} */ public AssetsEndpoint assets() { return assetsEndpoint; } /** - * @return {@link WatchlistEndpoint} + * @return the {@link WatchlistEndpoint} */ public WatchlistEndpoint watchlist() { return watchlistEndpoint; } /** - * @return {@link CalendarEndpoint} + * @return the {@link CalendarEndpoint} */ public CalendarEndpoint calendar() { return calendarEndpoint; } /** - * @return {@link ClockEndpoint} + * @return the {@link ClockEndpoint} */ public ClockEndpoint clock() { return clockEndpoint; From a55256c88fda84cdec0fd22f275d2bfaac2d17ca Mon Sep 17 00:00:00 2001 From: Jacob Peterson <14127217+Petersoj@users.noreply.github.com> Date: Fri, 19 Nov 2021 09:19:34 -0700 Subject: [PATCH 165/271] Add SSP and SPLIT ActivityType --- .../account_activities/enums/activity_type.json | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/schema_json/endpoint/account_activities/enums/activity_type.json b/schema_json/endpoint/account_activities/enums/activity_type.json index 03c44af1..9fae432b 100644 --- a/schema_json/endpoint/account_activities/enums/activity_type.json +++ b/schema_json/endpoint/account_activities/enums/activity_type.json @@ -35,7 +35,9 @@ "FEE", "REORG", "SC", - "SSO" + "SSO", + "SSP", + "SPLIT" ], "javaEnums": [ { @@ -139,6 +141,12 @@ }, { "description": "Stock spinoff." + }, + { + "description": "Stock split." + }, + { + "description": "Stock split." } ] } From 1bde139826f8dc4efcddd0f1d7f2b758ad53b284 Mon Sep 17 00:00:00 2001 From: Jacob Peterson <14127217+Petersoj@users.noreply.github.com> Date: Fri, 19 Nov 2021 09:20:48 -0700 Subject: [PATCH 166/271] Update to 8.3.2 --- README.md | 4 ++-- build.gradle | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index a0dce667..d1443c3d 100644 --- a/README.md +++ b/README.md @@ -17,7 +17,7 @@ If you are using Gradle as your build tool, add the following dependency to your ``` dependencies { - implementation group: 'net.jacobpeterson', name: 'alpaca-java', version: '8.3.1' + implementation group: 'net.jacobpeterson', name: 'alpaca-java', version: '8.3.2' } ``` @@ -27,7 +27,7 @@ If you are using Maven as your build tool, add the following dependency to your net.jacobpeterson alpaca-java - 8.3.1 + 8.3.2 compile ``` diff --git a/build.gradle b/build.gradle index d5566da8..da880edf 100644 --- a/build.gradle +++ b/build.gradle @@ -30,7 +30,7 @@ tasks.getByName("generateJsonSchema2Pojo").enabled(false) // Disable default 'js archivesBaseName = 'alpaca-java' group = 'net.jacobpeterson' -version = '8.3.1-SNAPSHOT' +version = '8.3.2-SNAPSHOT' repositories { mavenCentral() From 6a45b2bcd2fbdba6c04135a905cf2559152b5e95 Mon Sep 17 00:00:00 2001 From: kurru Date: Sat, 4 Dec 2021 16:11:11 -0800 Subject: [PATCH 167/271] define new account status enum --- schema_json/endpoint/account/enums/account_status.json | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/schema_json/endpoint/account/enums/account_status.json b/schema_json/endpoint/account/enums/account_status.json index 65963f0d..61f7a081 100644 --- a/schema_json/endpoint/account/enums/account_status.json +++ b/schema_json/endpoint/account/enums/account_status.json @@ -9,7 +9,8 @@ "APPROVAL_PENDING", "ACTIVE", "REJECTED", - "DISABLED" + "DISABLED", + "ACTION_REQUIRED" ], "javaEnums": [ { @@ -35,6 +36,9 @@ }, { "description": "The account has been disabled." + }, + { + "description": "The account requires manual attention." } ] } From 6ea0ff40050666fc15348717f020047c94a346ce Mon Sep 17 00:00:00 2001 From: Jacob Peterson <14127217+Petersoj@users.noreply.github.com> Date: Sun, 5 Dec 2021 17:01:03 -0700 Subject: [PATCH 168/271] Update to 8.3.3 --- README.md | 4 ++-- build.gradle | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index d1443c3d..2be165a6 100644 --- a/README.md +++ b/README.md @@ -17,7 +17,7 @@ If you are using Gradle as your build tool, add the following dependency to your ``` dependencies { - implementation group: 'net.jacobpeterson', name: 'alpaca-java', version: '8.3.2' + implementation group: 'net.jacobpeterson', name: 'alpaca-java', version: '8.3.3' } ``` @@ -27,7 +27,7 @@ If you are using Maven as your build tool, add the following dependency to your net.jacobpeterson alpaca-java - 8.3.2 + 8.3.3 compile ``` diff --git a/build.gradle b/build.gradle index da880edf..88519ffb 100644 --- a/build.gradle +++ b/build.gradle @@ -30,7 +30,7 @@ tasks.getByName("generateJsonSchema2Pojo").enabled(false) // Disable default 'js archivesBaseName = 'alpaca-java' group = 'net.jacobpeterson' -version = '8.3.2-SNAPSHOT' +version = '8.3.3-SNAPSHOT' repositories { mavenCentral() From 6f8f037a349ba47638a4a2672e9d946b73df45fd Mon Sep 17 00:00:00 2001 From: Jacob Peterson <14127217+Petersoj@users.noreply.github.com> Date: Sun, 19 Dec 2021 21:49:15 -0700 Subject: [PATCH 169/271] Add more descriptive field names in JSON schemas, fix javadoc formatting --- README.md | 2 +- .../non_trade_activity.json | 1 + .../account_activities/trade_activity.json | 3 +++ .../market_data/historical/bar/bar.json | 8 ++++++++ .../market_data/historical/quote/quote.json | 8 ++++++++ .../market_data/historical/trade/trade.json | 7 +++++++ .../realtime/quote/quote_message.json | 4 ++-- schema_json/endpoint/order/order.json | 8 ++++---- .../portfolio_history_response.json | 1 + schema_json/endpoint/position/position.json | 6 ++++++ .../streaming/trade/trade_update.json | 1 + .../alpaca/properties/AlpacaProperties.java | 20 ++++++++++++++----- .../endpoint/PortfolioHistoryEndpoint.java | 4 ++-- .../util/doc/Documentation2JSONSchema.java | 3 ++- .../alpaca/util/format/FormatUtil.java | 4 +++- .../alpaca/util/gson/GsonUtil.java | 7 ++++++- .../gson/{ => adapters}/LocalDateAdapter.java | 2 +- .../gson/{ => adapters}/LocalTimeAdapter.java | 2 +- .../{ => adapters}/ZonedDateTimeAdapter.java | 2 +- .../alpaca/util/okhttp/JSONBodyBuilder.java | 4 +++- .../alpaca/websocket/AlpacaWebsocket.java | 4 +++- .../alpaca/test/live/AlpacaAPITest.java | 12 +++++------ 22 files changed, 85 insertions(+), 28 deletions(-) rename src/main/java/net/jacobpeterson/alpaca/util/gson/{ => adapters}/LocalDateAdapter.java (95%) rename src/main/java/net/jacobpeterson/alpaca/util/gson/{ => adapters}/LocalTimeAdapter.java (95%) rename src/main/java/net/jacobpeterson/alpaca/util/gson/{ => adapters}/ZonedDateTimeAdapter.java (95%) diff --git a/README.md b/README.md index 2be165a6..de6fdc01 100644 --- a/README.md +++ b/README.md @@ -35,7 +35,7 @@ If you are using Maven as your build tool, add the following dependency to your Note that you don't have to use the Maven Central artifacts and instead can just install a clone of this project to your local Maven repository as shown in the [Building](#building) section. # Configuration -Creating an `alpaca.properties` file on the classpath with the following format allows you to easily load properties using the `AlpacaAPI` default constructor: +Creating an `alpaca.properties` file on the classpath (e.g. in `src/main/resources/alpaca.properties`) with the following format allows you to easily load properties using the `AlpacaAPI` default constructor: ``` key_id = secret_key = diff --git a/schema_json/endpoint/account_activities/non_trade_activity.json b/schema_json/endpoint/account_activities/non_trade_activity.json index c66a15af..81d8c0fa 100644 --- a/schema_json/endpoint/account_activities/non_trade_activity.json +++ b/schema_json/endpoint/account_activities/non_trade_activity.json @@ -19,6 +19,7 @@ }, "qty": { "existingJavaType": "java.lang.String", + "javaName": "quantity", "title": "For dividend activities, the number of shares that contributed to the payment. Not present for other activity types." }, "per_share_amount": { diff --git a/schema_json/endpoint/account_activities/trade_activity.json b/schema_json/endpoint/account_activities/trade_activity.json index 0c6dd5f4..1697ad1c 100644 --- a/schema_json/endpoint/account_activities/trade_activity.json +++ b/schema_json/endpoint/account_activities/trade_activity.json @@ -7,10 +7,12 @@ "properties": { "cum_qty": { "existingJavaType": "java.lang.String", + "javaName": "cumulativeQuantity", "title": "The cumulative quantity of shares involved in the execution." }, "leaves_qty": { "existingJavaType": "java.lang.String", + "javaName": "remainingQuantity", "title": "For partially_filled orders, the quantity of shares that are left to be filled." }, "price": { @@ -19,6 +21,7 @@ }, "qty": { "existingJavaType": "java.lang.String", + "javaName": "quantity", "title": "The number of shares involved in the trade execution." }, "side": { diff --git a/schema_json/endpoint/market_data/historical/bar/bar.json b/schema_json/endpoint/market_data/historical/bar/bar.json index 6be1fe66..55423fde 100644 --- a/schema_json/endpoint/market_data/historical/bar/bar.json +++ b/schema_json/endpoint/market_data/historical/bar/bar.json @@ -4,34 +4,42 @@ "properties": { "t": { "existingJavaType": "java.time.ZonedDateTime", + "javaName": "timestamp", "title": "Timestamp with nanosecond precision." }, "o": { "existingJavaType": "java.lang.Double", + "javaName": "open", "title": "Open price." }, "h": { "existingJavaType": "java.lang.Double", + "javaName": "high", "title": "High price." }, "l": { "existingJavaType": "java.lang.Double", + "javaName": "low", "title": "Low price." }, "c": { "existingJavaType": "java.lang.Double", + "javaName": "close", "title": "Close price." }, "v": { "existingJavaType": "java.lang.Long", + "javaName": "volume", "title": "Volume." }, "n": { "existingJavaType": "java.lang.Long", + "javaName": "tradeCount", "title": "Trade count." }, "vw": { "existingJavaType": "java.lang.Double", + "javaName": "vwap", "title": "VWAP (Volume Weighted Average Price)." } } diff --git a/schema_json/endpoint/market_data/historical/quote/quote.json b/schema_json/endpoint/market_data/historical/quote/quote.json index 7a238f6e..059c9746 100644 --- a/schema_json/endpoint/market_data/historical/quote/quote.json +++ b/schema_json/endpoint/market_data/historical/quote/quote.json @@ -4,34 +4,42 @@ "properties": { "t": { "existingJavaType": "java.time.ZonedDateTime", + "javaName": "timestamp", "title": "Timestamp with nanosecond precision." }, "ax": { "existingJavaType": "java.lang.String", + "javaName": "askExchange", "title": "Ask exchange." }, "ap": { "existingJavaType": "java.lang.Double", + "javaName": "askPrice", "title": "Ask price." }, "as": { "existingJavaType": "java.lang.Integer", + "javaName": "askSize", "title": "Ask size." }, "bx": { "existingJavaType": "java.lang.String", + "javaName": "bidExchange", "title": "Bid exchange." }, "bp": { "existingJavaType": "java.lang.Double", + "javaName": "bidPrice", "title": "Bid price." }, "bs": { "existingJavaType": "java.lang.Integer", + "javaName": "bidSize", "title": "Bid size." }, "c": { "existingJavaType": "java.util.ArrayList", + "javaName": "conditions", "title": "The {@link java.util.ArrayList} of quote conditions." } } diff --git a/schema_json/endpoint/market_data/historical/trade/trade.json b/schema_json/endpoint/market_data/historical/trade/trade.json index c9303e7b..f7ef48ca 100644 --- a/schema_json/endpoint/market_data/historical/trade/trade.json +++ b/schema_json/endpoint/market_data/historical/trade/trade.json @@ -4,30 +4,37 @@ "properties": { "t": { "existingJavaType": "java.time.ZonedDateTime", + "javaName": "timestamp", "title": "Timestamp with nanosecond precision." }, "x": { "existingJavaType": "java.lang.String", + "javaName": "exchange", "title": "Exchange where the trade happened." }, "p": { "existingJavaType": "java.lang.Double", + "javaName": "price", "title": "Trade price." }, "s": { "existingJavaType": "java.lang.Integer", + "javaName": "size", "title": "Trade size." }, "c": { "existingJavaType": "java.util.ArrayList", + "javaName": "conditions", "title": "The {@link java.util.ArrayList} of trade conditions." }, "i": { "existingJavaType": "java.lang.Long", + "javaName": "tradeID", "title": "Trade ID." }, "z": { "existingJavaType": "java.lang.String", + "javaName": "tape", "title": "Tape." } } diff --git a/schema_json/endpoint/market_data/realtime/quote/quote_message.json b/schema_json/endpoint/market_data/realtime/quote/quote_message.json index a8ff47b3..f1f9606d 100644 --- a/schema_json/endpoint/market_data/realtime/quote/quote_message.json +++ b/schema_json/endpoint/market_data/realtime/quote/quote_message.json @@ -7,7 +7,7 @@ "properties": { "ax": { "existingJavaType": "java.lang.String", - "javaName": "askExchangeCode", + "javaName": "askExchange", "title": "Ask exchange code." }, "ap": { @@ -22,7 +22,7 @@ }, "bx": { "existingJavaType": "java.lang.String", - "javaName": "bidExchangeCode", + "javaName": "bidExchange", "title": "Bid exchange code." }, "bp": { diff --git a/schema_json/endpoint/order/order.json b/schema_json/endpoint/order/order.json index 4af8dde8..c0af5330 100644 --- a/schema_json/endpoint/order/order.json +++ b/schema_json/endpoint/order/order.json @@ -68,24 +68,23 @@ }, "qty": { "existingJavaType": "java.lang.String", + "javaName": "quantity", "title": "Ordered quantity. If entered, notional will be null. Can take up to 9 decimal points." }, "filled_qty": { "existingJavaType": "java.lang.String", + "javaName": "filledQuantity", "title": "Filled quantity." }, "filled_avg_price": { "existingJavaType": "java.lang.String", + "javaName": "averageFillPrice", "title": "Filled average price." }, "order_class": { "existingJavaType": "net.jacobpeterson.alpaca.model.endpoint.order.enums.OrderClass", "title": "The {@link net.jacobpeterson.alpaca.model.endpoint.order.enums.OrderClass}. For details of non-simple order classes, please see Bracket Order Overview." }, - "order_type": { - "existingJavaType": "java.lang.String", - "title": "(Deprecated with just type field below.)." - }, "type": { "existingJavaType": "net.jacobpeterson.alpaca.model.endpoint.order.enums.OrderType", "title": "The {@link net.jacobpeterson.alpaca.model.endpoint.order.enums.OrderType}." @@ -128,6 +127,7 @@ }, "hwm": { "existingJavaType": "java.lang.String", + "javaName": "highWaterMark", "title": "High Water Mark - The highest (lowest) market price seen since the trailing stop order was submitted." } } diff --git a/schema_json/endpoint/portfolio_history/portfolio_history_response.json b/schema_json/endpoint/portfolio_history/portfolio_history_response.json index d43235f7..d3ca8177 100644 --- a/schema_json/endpoint/portfolio_history/portfolio_history_response.json +++ b/schema_json/endpoint/portfolio_history/portfolio_history_response.json @@ -16,6 +16,7 @@ }, "profit_loss_pct": { "existingJavaType": "java.util.ArrayList", + "javaName": "profitLossPercent", "title": "Profit/loss in percentage from the base value." }, "base_value": { diff --git a/schema_json/endpoint/position/position.json b/schema_json/endpoint/position/position.json index bee7e824..2f1eb603 100644 --- a/schema_json/endpoint/position/position.json +++ b/schema_json/endpoint/position/position.json @@ -20,10 +20,12 @@ }, "avg_entry_price": { "existingJavaType": "java.lang.String", + "javaName": "averageEntryPrice", "title": "Average entry price of the position." }, "qty": { "existingJavaType": "java.lang.String", + "javaName": "quantity", "title": "The number of shares." }, "side": { @@ -40,18 +42,22 @@ }, "unrealized_pl": { "existingJavaType": "java.lang.String", + "javaName": "unrealizedProfitLoss", "title": "Unrealized profit/loss in dollars." }, "unrealized_plpc": { "existingJavaType": "java.lang.String", + "javaName": "unrealizedProfitLossPercent", "title": "Unrealized profit/loss percent (by a factor of 1)." }, "unrealized_intraday_pl": { "existingJavaType": "java.lang.String", + "javaName": "unrealizedIntradayProfitLoss", "title": "Unrealized profit/loss in dollars for the day." }, "unrealized_intraday_plpc": { "existingJavaType": "java.lang.String", + "javaName": "unrealizedIntradayProfitLossPercent", "title": "Unrealized profit/loss percent (by a factor of 1)." }, "current_price": { diff --git a/schema_json/endpoint/streaming/trade/trade_update.json b/schema_json/endpoint/streaming/trade/trade_update.json index 46188863..339ea01c 100644 --- a/schema_json/endpoint/streaming/trade/trade_update.json +++ b/schema_json/endpoint/streaming/trade/trade_update.json @@ -16,6 +16,7 @@ }, "position_qty": { "existingJavaType": "java.lang.String", + "javaName": "positionQuantity", "title": "The position quantity." }, "order": { diff --git a/src/main/java/net/jacobpeterson/alpaca/properties/AlpacaProperties.java b/src/main/java/net/jacobpeterson/alpaca/properties/AlpacaProperties.java index 2b8d0b79..0c121157 100644 --- a/src/main/java/net/jacobpeterson/alpaca/properties/AlpacaProperties.java +++ b/src/main/java/net/jacobpeterson/alpaca/properties/AlpacaProperties.java @@ -15,31 +15,41 @@ public class AlpacaProperties { private static final String ALPACA_DEFAULT_PROPERTIES_FILE = "alpaca.default.properties"; private static final String KEY_ID_KEY = "key_id"; - /** The value of {@link #KEY_ID_KEY} in {@link #ALPACA_PROPERTIES_FILE}. */ + /** + * The value of {@link #KEY_ID_KEY} in {@link #ALPACA_PROPERTIES_FILE}. + */ public static final String KEY_ID = getProperty( ALPACA_PROPERTIES_FILE, ALPACA_DEFAULT_PROPERTIES_FILE, KEY_ID_KEY); private static final String SECRET_KEY_KEY = "secret_key"; - /** The value of {@link #SECRET_KEY_KEY} in {@link #ALPACA_PROPERTIES_FILE}. */ + /** + * The value of {@link #SECRET_KEY_KEY} in {@link #ALPACA_PROPERTIES_FILE}. + */ public static final String SECRET_KEY = getProperty( ALPACA_PROPERTIES_FILE, ALPACA_DEFAULT_PROPERTIES_FILE, SECRET_KEY_KEY); private static final String ENDPOINT_API_TYPE_KEY = "endpoint_api_type"; - /** The value of {@link #ENDPOINT_API_TYPE_KEY} in {@link #ALPACA_PROPERTIES_FILE}. */ + /** + * The value of {@link #ENDPOINT_API_TYPE_KEY} in {@link #ALPACA_PROPERTIES_FILE}. + */ public static final EndpointAPIType ENDPOINT_API_TYPE = EndpointAPIType.fromValue(getProperty( ALPACA_PROPERTIES_FILE, ALPACA_DEFAULT_PROPERTIES_FILE, ENDPOINT_API_TYPE_KEY)); private static final String DATA_API_TYPE_KEY = "data_api_type"; - /** The value of {@link #DATA_API_TYPE_KEY} in {@link #ALPACA_PROPERTIES_FILE}. */ + /** + * The value of {@link #DATA_API_TYPE_KEY} in {@link #ALPACA_PROPERTIES_FILE}. + */ public static final DataAPIType DATA_API_TYPE = DataAPIType.fromValue(getProperty( ALPACA_PROPERTIES_FILE, ALPACA_DEFAULT_PROPERTIES_FILE, DATA_API_TYPE_KEY)); private static final String USER_AGENT_KEY = "user_agent"; - /** The value of {@link #USER_AGENT_KEY} in {@link #ALPACA_PROPERTIES_FILE}. */ + /** + * The value of {@link #USER_AGENT_KEY} in {@link #ALPACA_PROPERTIES_FILE}. + */ public static final String USER_AGENT = getProperty( ALPACA_PROPERTIES_FILE, ALPACA_DEFAULT_PROPERTIES_FILE, USER_AGENT_KEY); diff --git a/src/main/java/net/jacobpeterson/alpaca/rest/endpoint/PortfolioHistoryEndpoint.java b/src/main/java/net/jacobpeterson/alpaca/rest/endpoint/PortfolioHistoryEndpoint.java index 9bab136d..1d01c19d 100644 --- a/src/main/java/net/jacobpeterson/alpaca/rest/endpoint/PortfolioHistoryEndpoint.java +++ b/src/main/java/net/jacobpeterson/alpaca/rest/endpoint/PortfolioHistoryEndpoint.java @@ -81,7 +81,7 @@ public PortfolioHistory get(Integer periodLength, PortfolioPeriodUnit periodUnit // Check if any response arrays differ in size checkState(response.getTimestamp().size() == response.getEquity().size() && response.getEquity().size() == response.getProfitLoss().size() && - response.getProfitLoss().size() == response.getProfitLossPct().size(), + response.getProfitLoss().size() == response.getProfitLossPercent().size(), "Response arrays should not differ in size!"); // Add all data points into one POJO @@ -91,7 +91,7 @@ public PortfolioHistory get(Integer periodLength, PortfolioPeriodUnit periodUnit Instant.ofEpochSecond(response.getTimestamp().get(index)).atZone(FormatUtil.NEW_YORK_ZONED_ID), response.getEquity().get(index), response.getProfitLoss().get(index), - response.getProfitLossPct().get(index))); + response.getProfitLossPercent().get(index))); } return new PortfolioHistory( diff --git a/src/main/java/net/jacobpeterson/alpaca/util/doc/Documentation2JSONSchema.java b/src/main/java/net/jacobpeterson/alpaca/util/doc/Documentation2JSONSchema.java index 29fbf597..ba0e2208 100644 --- a/src/main/java/net/jacobpeterson/alpaca/util/doc/Documentation2JSONSchema.java +++ b/src/main/java/net/jacobpeterson/alpaca/util/doc/Documentation2JSONSchema.java @@ -16,7 +16,8 @@ import java.io.IOException; /** - * The type {@link Documentation2JSONSchema}. + * The {@link Documentation2JSONSchema} is used strictly to expedite the process of creating JSON schemas for POJO + * generation in this library using Alpaca's website documentation. */ public class Documentation2JSONSchema { diff --git a/src/main/java/net/jacobpeterson/alpaca/util/format/FormatUtil.java b/src/main/java/net/jacobpeterson/alpaca/util/format/FormatUtil.java index b5a53a27..e797c294 100644 --- a/src/main/java/net/jacobpeterson/alpaca/util/format/FormatUtil.java +++ b/src/main/java/net/jacobpeterson/alpaca/util/format/FormatUtil.java @@ -14,7 +14,9 @@ */ public class FormatUtil { - /** The {@link ZoneId} for America/New_York. */ + /** + * The {@link ZoneId} for America/New_York. + */ public static final ZoneId NEW_YORK_ZONED_ID = ZoneId.of("America/New_York"); // Alpaca uses the following rounding mechanics with respect to buy orders: (1) rounded down to two decimal diff --git a/src/main/java/net/jacobpeterson/alpaca/util/gson/GsonUtil.java b/src/main/java/net/jacobpeterson/alpaca/util/gson/GsonUtil.java index 7b94a344..dc86e2c1 100644 --- a/src/main/java/net/jacobpeterson/alpaca/util/gson/GsonUtil.java +++ b/src/main/java/net/jacobpeterson/alpaca/util/gson/GsonUtil.java @@ -2,6 +2,9 @@ import com.google.gson.Gson; import com.google.gson.GsonBuilder; +import net.jacobpeterson.alpaca.util.gson.adapters.LocalDateAdapter; +import net.jacobpeterson.alpaca.util.gson.adapters.LocalTimeAdapter; +import net.jacobpeterson.alpaca.util.gson.adapters.ZonedDateTimeAdapter; import java.time.LocalDate; import java.time.LocalTime; @@ -12,7 +15,9 @@ */ public class GsonUtil { - /** A {@link Gson} instance with registered date/time adapters. */ + /** + * A {@link Gson} instance with registered date/time adapters. + */ public static final Gson GSON = new GsonBuilder() .registerTypeAdapter(ZonedDateTime.class, new ZonedDateTimeAdapter()) .registerTypeAdapter(LocalDate.class, new LocalDateAdapter()) diff --git a/src/main/java/net/jacobpeterson/alpaca/util/gson/LocalDateAdapter.java b/src/main/java/net/jacobpeterson/alpaca/util/gson/adapters/LocalDateAdapter.java similarity index 95% rename from src/main/java/net/jacobpeterson/alpaca/util/gson/LocalDateAdapter.java rename to src/main/java/net/jacobpeterson/alpaca/util/gson/adapters/LocalDateAdapter.java index d1dc5956..a9475c17 100644 --- a/src/main/java/net/jacobpeterson/alpaca/util/gson/LocalDateAdapter.java +++ b/src/main/java/net/jacobpeterson/alpaca/util/gson/adapters/LocalDateAdapter.java @@ -1,4 +1,4 @@ -package net.jacobpeterson.alpaca.util.gson; +package net.jacobpeterson.alpaca.util.gson.adapters; import com.google.gson.Gson; import com.google.gson.JsonDeserializationContext; diff --git a/src/main/java/net/jacobpeterson/alpaca/util/gson/LocalTimeAdapter.java b/src/main/java/net/jacobpeterson/alpaca/util/gson/adapters/LocalTimeAdapter.java similarity index 95% rename from src/main/java/net/jacobpeterson/alpaca/util/gson/LocalTimeAdapter.java rename to src/main/java/net/jacobpeterson/alpaca/util/gson/adapters/LocalTimeAdapter.java index 960ccda2..9d6bb735 100644 --- a/src/main/java/net/jacobpeterson/alpaca/util/gson/LocalTimeAdapter.java +++ b/src/main/java/net/jacobpeterson/alpaca/util/gson/adapters/LocalTimeAdapter.java @@ -1,4 +1,4 @@ -package net.jacobpeterson.alpaca.util.gson; +package net.jacobpeterson.alpaca.util.gson.adapters; import com.google.gson.Gson; import com.google.gson.JsonDeserializationContext; diff --git a/src/main/java/net/jacobpeterson/alpaca/util/gson/ZonedDateTimeAdapter.java b/src/main/java/net/jacobpeterson/alpaca/util/gson/adapters/ZonedDateTimeAdapter.java similarity index 95% rename from src/main/java/net/jacobpeterson/alpaca/util/gson/ZonedDateTimeAdapter.java rename to src/main/java/net/jacobpeterson/alpaca/util/gson/adapters/ZonedDateTimeAdapter.java index 7f1cf2a7..e10f1be1 100644 --- a/src/main/java/net/jacobpeterson/alpaca/util/gson/ZonedDateTimeAdapter.java +++ b/src/main/java/net/jacobpeterson/alpaca/util/gson/adapters/ZonedDateTimeAdapter.java @@ -1,4 +1,4 @@ -package net.jacobpeterson.alpaca.util.gson; +package net.jacobpeterson.alpaca.util.gson.adapters; import com.google.gson.Gson; import com.google.gson.JsonDeserializationContext; diff --git a/src/main/java/net/jacobpeterson/alpaca/util/okhttp/JSONBodyBuilder.java b/src/main/java/net/jacobpeterson/alpaca/util/okhttp/JSONBodyBuilder.java index c6f54498..11944345 100644 --- a/src/main/java/net/jacobpeterson/alpaca/util/okhttp/JSONBodyBuilder.java +++ b/src/main/java/net/jacobpeterson/alpaca/util/okhttp/JSONBodyBuilder.java @@ -10,7 +10,9 @@ */ public class JSONBodyBuilder { - /** The UTF-8 JSON {@link MediaType}. */ + /** + * The UTF-8 JSON {@link MediaType}. + */ public static final MediaType JSON_MEDIA_TYPE = MediaType.get("application/json; charset=UTF-8"); private String bodyJSON; diff --git a/src/main/java/net/jacobpeterson/alpaca/websocket/AlpacaWebsocket.java b/src/main/java/net/jacobpeterson/alpaca/websocket/AlpacaWebsocket.java index 331d89da..232d23f2 100644 --- a/src/main/java/net/jacobpeterson/alpaca/websocket/AlpacaWebsocket.java +++ b/src/main/java/net/jacobpeterson/alpaca/websocket/AlpacaWebsocket.java @@ -42,7 +42,9 @@ public abstract class AlpacaWebsocket Date: Mon, 20 Dec 2021 02:03:01 -0700 Subject: [PATCH 170/271] Refactor, add CryptoMarketDataEndpoint --- README.md | 43 ++- schema_json/endpoint/asset/asset.json | 5 +- .../endpoint/asset/enums/asset_class.json | 8 + .../bar => common}/enums/bar_time_period.json | 2 +- .../crypto/historical/bar/bar.json | 51 +++ .../crypto/historical/bar/bars_response.json | 18 ++ .../crypto/historical/enums/exchange.json | 20 ++ .../quote/latest_quote_response.json | 14 + .../crypto/historical/quote/quote.json | 36 +++ .../historical/quote/quotes_response.json | 18 ++ .../historical/trade/enums/taker_side.json | 16 + .../trade/latest_trade_response.json | 14 + .../crypto/historical/trade/trade.json | 36 +++ .../historical/trade/trades_response.json | 18 ++ .../crypto/historical/xbbo/xbbo.json | 41 +++ .../crypto/historical/xbbo/xbbo_response.json | 14 + .../{ => stock}/historical/bar/bar.json | 0 .../historical/bar/bars_response.json | 4 +- .../historical/bar/enums/bar_adjustment.json | 0 .../quote/latest_quote_response.json | 4 +- .../{ => stock}/historical/quote/quote.json | 0 .../historical/quote/quotes_response.json | 4 +- .../historical/snapshot/snapshot.json | 20 +- .../trade/latest_trade_response.json | 4 +- .../{ => stock}/historical/trade/trade.json | 0 .../historical/trade/trades_response.json | 4 +- .../{ => stock}/realtime/bar/bar_message.json | 2 +- .../realtime/control/error_message.json | 2 +- .../control/subscriptions_message.json | 2 +- .../realtime/control/success_message.json | 2 +- .../enums/market_data_message_type.json | 0 .../realtime/market_data_message.json | 4 +- .../realtime/quote/quote_message.json | 2 +- .../{ => stock}/realtime/symbol_message.json | 2 +- .../realtime/trade/trade_message.json | 2 +- schema_json/endpoint/position/position.json | 2 +- .../net/jacobpeterson/alpaca/AlpacaAPI.java | 57 ++-- .../alpaca/rest/AlpacaClient.java | 10 - .../alpaca/rest/AlpacaClientException.java | 20 -- .../endpoint/AccountActivitiesEndpoint.java | 2 +- .../alpaca/rest/endpoint/AssetsEndpoint.java | 7 +- .../alpaca/rest/endpoint/OrdersEndpoint.java | 4 +- .../marketdata/CryptoMarketDataEndpoint.java | 296 ++++++++++++++++++ .../StockMarketDataEndpoint.java} | 44 +-- .../alpaca/websocket/AlpacaWebsocket.java | 20 -- .../marketdata/MarketDataListener.java | 4 +- .../marketdata/MarketDataWebsocket.java | 16 +- .../MarketDataWebsocketInterface.java | 10 +- 48 files changed, 744 insertions(+), 160 deletions(-) create mode 100644 schema_json/endpoint/asset/enums/asset_class.json rename schema_json/endpoint/market_data/{historical/bar => common}/enums/bar_time_period.json (75%) create mode 100644 schema_json/endpoint/market_data/crypto/historical/bar/bar.json create mode 100644 schema_json/endpoint/market_data/crypto/historical/bar/bars_response.json create mode 100644 schema_json/endpoint/market_data/crypto/historical/enums/exchange.json create mode 100644 schema_json/endpoint/market_data/crypto/historical/quote/latest_quote_response.json create mode 100644 schema_json/endpoint/market_data/crypto/historical/quote/quote.json create mode 100644 schema_json/endpoint/market_data/crypto/historical/quote/quotes_response.json create mode 100644 schema_json/endpoint/market_data/crypto/historical/trade/enums/taker_side.json create mode 100644 schema_json/endpoint/market_data/crypto/historical/trade/latest_trade_response.json create mode 100644 schema_json/endpoint/market_data/crypto/historical/trade/trade.json create mode 100644 schema_json/endpoint/market_data/crypto/historical/trade/trades_response.json create mode 100644 schema_json/endpoint/market_data/crypto/historical/xbbo/xbbo.json create mode 100644 schema_json/endpoint/market_data/crypto/historical/xbbo/xbbo_response.json rename schema_json/endpoint/market_data/{ => stock}/historical/bar/bar.json (100%) rename schema_json/endpoint/market_data/{ => stock}/historical/bar/bars_response.json (81%) rename schema_json/endpoint/market_data/{ => stock}/historical/bar/enums/bar_adjustment.json (100%) rename schema_json/endpoint/market_data/{ => stock}/historical/quote/latest_quote_response.json (82%) rename schema_json/endpoint/market_data/{ => stock}/historical/quote/quote.json (100%) rename schema_json/endpoint/market_data/{ => stock}/historical/quote/quotes_response.json (80%) rename schema_json/endpoint/market_data/{ => stock}/historical/snapshot/snapshot.json (67%) rename schema_json/endpoint/market_data/{ => stock}/historical/trade/latest_trade_response.json (82%) rename schema_json/endpoint/market_data/{ => stock}/historical/trade/trade.json (100%) rename schema_json/endpoint/market_data/{ => stock}/historical/trade/trades_response.json (80%) rename schema_json/endpoint/market_data/{ => stock}/realtime/bar/bar_message.json (95%) rename schema_json/endpoint/market_data/{ => stock}/realtime/control/error_message.json (91%) rename schema_json/endpoint/market_data/{ => stock}/realtime/control/subscriptions_message.json (94%) rename schema_json/endpoint/market_data/{ => stock}/realtime/control/success_message.json (89%) rename schema_json/endpoint/market_data/{ => stock}/realtime/enums/market_data_message_type.json (100%) rename schema_json/endpoint/market_data/{ => stock}/realtime/market_data_message.json (75%) rename schema_json/endpoint/market_data/{ => stock}/realtime/quote/quote_message.json (97%) rename schema_json/endpoint/market_data/{ => stock}/realtime/symbol_message.json (89%) rename schema_json/endpoint/market_data/{ => stock}/realtime/trade/trade_message.json (96%) create mode 100644 src/main/java/net/jacobpeterson/alpaca/rest/endpoint/marketdata/CryptoMarketDataEndpoint.java rename src/main/java/net/jacobpeterson/alpaca/rest/endpoint/{MarketDataEndpoint.java => marketdata/StockMarketDataEndpoint.java} (86%) diff --git a/README.md b/README.md index de6fdc01..a0947ec0 100644 --- a/README.md +++ b/README.md @@ -100,15 +100,42 @@ try { } ``` -## [`MarketDataEndpoint`](src/main/java/net/jacobpeterson/alpaca/rest/endpoint/MarketDataEndpoint.java) -The Data API v2 provides market data through an easy-to-use API for historical data. +## [`CryptoMarketDataEndpoint`](src/main/java/net/jacobpeterson/alpaca/rest/endpoint/marketdata/CryptoMarketDataEndpoint.java) +Alpaca provides cryptocurrency data from multiple venues/exchanges, namely: Coinbase, ErisX, and FTX. Note that it does not route orders to all venues. + +Example usage: +```java +try { + // Get BTCUSD 50 one-hour bars starting on 12/18/2021 from Coinbase and print them out + BarsResponse btcBarsResponse = alpacaAPI.cryptoMarketData().getBars( + "BTCUSD", + Arrays.asList(Exchange.COINBASE), + ZonedDateTime.of(2021, 12, 18, 0, 0, 0, 0, ZoneId.of("America/New_York")), + 50, + null, + 1, + BarTimePeriod.HOUR); + btcBarsResponse.getBars().forEach(System.out::println); + + // Get the Best Bid and Offer across multiple exchanges (XBBO) and print it out + XbboResponse etcXBBO = alpacaAPI.cryptoMarketData().getXBBO( + "ETHUSD", + null); + System.out.println(etcXBBO); +} catch (AlpacaClientException exception) { + exception.printStackTrace(); +} +``` + +## [`StockMarketDataEndpoint`](src/main/java/net/jacobpeterson/alpaca/rest/endpoint/marketdata/StockMarketDataEndpoint.java) +The Data API v2 provides market data through an easy-to-use API for historical stock market data. Example usage: ```java try { // Get AAPL one hour, split-adjusted bars from 7/6/2021 market open // to 7/8/2021 market close and print them out - BarsResponse aaplBarsResponse = alpacaAPI.marketData().getBars( + BarsResponse aaplBarsResponse = alpacaAPI.stockMarketData().getBars( "AAPL", ZonedDateTime.of(2021, 7, 6, 9, 30, 0, 0, ZoneId.of("America/New_York")), ZonedDateTime.of(2021, 7, 8, 12 + 4, 0, 0, 0, ZoneId.of("America/New_York")), @@ -120,7 +147,7 @@ try { aaplBarsResponse.getBars().forEach(System.out::println); // Get AAPL first 10 trades on 7/8/2021 at market open and print them out - TradesResponse aaplTradesResponse = alpacaAPI.marketData().getTrades( + TradesResponse aaplTradesResponse = alpacaAPI.stockMarketData().getTrades( "AAPL", ZonedDateTime.of(2021, 7, 8, 9, 30, 0, 0, ZoneId.of("America/New_York")), ZonedDateTime.of(2021, 7, 8, 9, 31, 0, 0, ZoneId.of("America/New_York")), @@ -129,11 +156,11 @@ try { aaplTradesResponse.getTrades().forEach(System.out::println); // Print out latest AAPL trade - Trade latestAAPLTrade = alpacaAPI.marketData().getLatestTrade("AAPL").getTrade(); + Trade latestAAPLTrade = alpacaAPI.stockMarketData().getLatestTrade("AAPL").getTrade(); System.out.printf("Latest AAPL Trade: %s\n", latestAAPLTrade); // Print out snapshot of AAPL, GME, and TSLA - Map snapshots = alpacaAPI.marketData() + Map snapshots = alpacaAPI.stockMarketData() .getSnapshots(Arrays.asList("AAPL", "GME", "TSLA")); snapshots.forEach((symbol, snapshot) -> System.out.printf("Symbol: %s\nSnapshot: %s\n\n", symbol, snapshot)); @@ -229,8 +256,8 @@ The Assets API serves as the master list of assets available for trade and data Example usage: ```java try { - // Print out a CSV of all active US equity 'Asset's - List activeUSEquities = alpacaAPI.assets().get(AssetStatus.ACTIVE, null); + // Print out a CSV of all active US Equity 'Asset's + List activeUSEquities = alpacaAPI.assets().get(AssetStatus.ACTIVE, AssetClass.US_EQUITY); System.out.println(activeUSEquities .stream().map(Asset::getSymbol) .collect(Collectors.joining(", "))); diff --git a/schema_json/endpoint/asset/asset.json b/schema_json/endpoint/asset/asset.json index b563bf4a..f5ab8b9c 100644 --- a/schema_json/endpoint/asset/asset.json +++ b/schema_json/endpoint/asset/asset.json @@ -7,8 +7,9 @@ "title": "Asset ID." }, "class": { - "existingJavaType": "java.lang.String", - "title": "\"us_equity\"" + "existingJavaType": "net.jacobpeterson.alpaca.model.endpoint.asset.enums.AssetClass", + "javaName": "assetClass", + "title": "The {@link net.jacobpeterson.alpaca.model.endpoint.asset.enums.AssetClass}." }, "exchange": { "existingJavaType": "java.lang.String", diff --git a/schema_json/endpoint/asset/enums/asset_class.json b/schema_json/endpoint/asset/enums/asset_class.json new file mode 100644 index 00000000..ac36f8e7 --- /dev/null +++ b/schema_json/endpoint/asset/enums/asset_class.json @@ -0,0 +1,8 @@ +{ + "type": "string", + "title": "See Assets.", + "enum": [ + "us_equity", + "crypto" + ] +} diff --git a/schema_json/endpoint/market_data/historical/bar/enums/bar_time_period.json b/schema_json/endpoint/market_data/common/enums/bar_time_period.json similarity index 75% rename from schema_json/endpoint/market_data/historical/bar/enums/bar_time_period.json rename to schema_json/endpoint/market_data/common/enums/bar_time_period.json index 627d9413..cccf0508 100644 --- a/schema_json/endpoint/market_data/historical/bar/enums/bar_time_period.json +++ b/schema_json/endpoint/market_data/common/enums/bar_time_period.json @@ -1,6 +1,6 @@ { "type": "string", - "title": "See Historical Data.", + "title": "See Market Data.", "enum": [ "Min", "Hour", diff --git a/schema_json/endpoint/market_data/crypto/historical/bar/bar.json b/schema_json/endpoint/market_data/crypto/historical/bar/bar.json new file mode 100644 index 00000000..c677ba92 --- /dev/null +++ b/schema_json/endpoint/market_data/crypto/historical/bar/bar.json @@ -0,0 +1,51 @@ +{ + "type": "object", + "title": "See Crypto Historical Data.", + "properties": { + "t": { + "existingJavaType": "java.time.ZonedDateTime", + "javaName": "timestamp", + "title": "Timestamp with nanosecond precision." + }, + "x": { + "existingJavaType": "net.jacobpeterson.alpaca.model.endpoint.marketdata.crypto.historical.enums.Exchange", + "javaName": "exchange", + "title": "Exchange." + }, + "o": { + "existingJavaType": "java.lang.Double", + "javaName": "open", + "title": "Open price." + }, + "h": { + "existingJavaType": "java.lang.Double", + "javaName": "high", + "title": "High price." + }, + "l": { + "existingJavaType": "java.lang.Double", + "javaName": "low", + "title": "Low price." + }, + "c": { + "existingJavaType": "java.lang.Double", + "javaName": "close", + "title": "Close price." + }, + "v": { + "existingJavaType": "java.lang.Double", + "javaName": "volume", + "title": "Volume." + }, + "n": { + "existingJavaType": "java.lang.Long", + "javaName": "tradeCount", + "title": "Trade count." + }, + "vw": { + "existingJavaType": "java.lang.Double", + "javaName": "vwap", + "title": "VWAP (Volume Weighted Average Price)." + } + } +} diff --git a/schema_json/endpoint/market_data/crypto/historical/bar/bars_response.json b/schema_json/endpoint/market_data/crypto/historical/bar/bars_response.json new file mode 100644 index 00000000..21f5cddb --- /dev/null +++ b/schema_json/endpoint/market_data/crypto/historical/bar/bars_response.json @@ -0,0 +1,18 @@ +{ + "type": "object", + "title": "See Historical Data.", + "properties": { + "bars": { + "existingJavaType": "java.util.ArrayList", + "title": "The {@link java.util.ArrayList} of {@link net.jacobpeterson.alpaca.model.endpoint.marketdata.crypto.historical.bar.Bar}s." + }, + "symbol": { + "existingJavaType": "java.lang.String", + "title": "Symbol that was queried." + }, + "next_page_token": { + "existingJavaType": "java.lang.String", + "title": "Token that can be used to query the next page." + } + } +} diff --git a/schema_json/endpoint/market_data/crypto/historical/enums/exchange.json b/schema_json/endpoint/market_data/crypto/historical/enums/exchange.json new file mode 100644 index 00000000..1ea41bd6 --- /dev/null +++ b/schema_json/endpoint/market_data/crypto/historical/enums/exchange.json @@ -0,0 +1,20 @@ +{ + "type": "string", + "title": "See Crypto Historical Data.", + "enum": [ + "ERSX", + "FTX", + "CBSE" + ], + "javaEnums": [ + { + "name": "ERISX" + }, + { + "name": "FTX" + }, + { + "name": "COINBASE" + } + ] +} diff --git a/schema_json/endpoint/market_data/crypto/historical/quote/latest_quote_response.json b/schema_json/endpoint/market_data/crypto/historical/quote/latest_quote_response.json new file mode 100644 index 00000000..044d252f --- /dev/null +++ b/schema_json/endpoint/market_data/crypto/historical/quote/latest_quote_response.json @@ -0,0 +1,14 @@ +{ + "type": "object", + "title": "See Crypto Historical Data.", + "properties": { + "symbol": { + "existingJavaType": "java.lang.String", + "title": "Symbol that was queried." + }, + "quote": { + "existingJavaType": "net.jacobpeterson.alpaca.model.endpoint.marketdata.crypto.historical.quote.Quote", + "title": "The latest {@link net.jacobpeterson.alpaca.model.endpoint.marketdata.crypto.historical.quote.Quote}." + } + } +} diff --git a/schema_json/endpoint/market_data/crypto/historical/quote/quote.json b/schema_json/endpoint/market_data/crypto/historical/quote/quote.json new file mode 100644 index 00000000..ecca5a55 --- /dev/null +++ b/schema_json/endpoint/market_data/crypto/historical/quote/quote.json @@ -0,0 +1,36 @@ +{ + "type": "object", + "title": "See Crypto Historical Data.", + "properties": { + "t": { + "existingJavaType": "java.time.ZonedDateTime", + "javaName": "timestamp", + "title": "Timestamp with nanosecond precision." + }, + "x": { + "existingJavaType": "net.jacobpeterson.alpaca.model.endpoint.marketdata.crypto.historical.enums.Exchange", + "javaName": "exchange", + "title": "Exchange." + }, + "ap": { + "existingJavaType": "java.lang.Double", + "javaName": "askPrice", + "title": "Ask price." + }, + "as": { + "existingJavaType": "java.lang.Double", + "javaName": "askSize", + "title": "Ask size." + }, + "bp": { + "existingJavaType": "java.lang.Double", + "javaName": "bidPrice", + "title": "Bid price." + }, + "bs": { + "existingJavaType": "java.lang.Double", + "javaName": "bidSize", + "title": "Bid size." + } + } +} diff --git a/schema_json/endpoint/market_data/crypto/historical/quote/quotes_response.json b/schema_json/endpoint/market_data/crypto/historical/quote/quotes_response.json new file mode 100644 index 00000000..212635c3 --- /dev/null +++ b/schema_json/endpoint/market_data/crypto/historical/quote/quotes_response.json @@ -0,0 +1,18 @@ +{ + "type": "object", + "title": "See Crypto Historical Data.", + "properties": { + "quotes": { + "existingJavaType": "java.util.ArrayList", + "title": "The {@link java.util.ArrayList} of {@link net.jacobpeterson.alpaca.model.endpoint.marketdata.crypto.historical.quote.Quote}s." + }, + "symbol": { + "existingJavaType": "java.lang.String", + "title": "Symbol that was queried." + }, + "next_page_token": { + "existingJavaType": "java.lang.String", + "title": "Token that can be used to query the next page." + } + } +} diff --git a/schema_json/endpoint/market_data/crypto/historical/trade/enums/taker_side.json b/schema_json/endpoint/market_data/crypto/historical/trade/enums/taker_side.json new file mode 100644 index 00000000..9604bc54 --- /dev/null +++ b/schema_json/endpoint/market_data/crypto/historical/trade/enums/taker_side.json @@ -0,0 +1,16 @@ +{ + "type": "string", + "title": "See Crypto Historical Data.", + "enum": [ + "B", + "S" + ], + "javaEnums": [ + { + "name": "BUY" + }, + { + "name": "SELL" + } + ] +} diff --git a/schema_json/endpoint/market_data/crypto/historical/trade/latest_trade_response.json b/schema_json/endpoint/market_data/crypto/historical/trade/latest_trade_response.json new file mode 100644 index 00000000..089f9528 --- /dev/null +++ b/schema_json/endpoint/market_data/crypto/historical/trade/latest_trade_response.json @@ -0,0 +1,14 @@ +{ + "type": "object", + "title": "See Crypto Historical Data.", + "properties": { + "symbol": { + "existingJavaType": "java.lang.String", + "title": "Symbol that was queried." + }, + "trade": { + "existingJavaType": "net.jacobpeterson.alpaca.model.endpoint.marketdata.crypto.historical.trade.Trade", + "title": "The latest {@link net.jacobpeterson.alpaca.model.endpoint.marketdata.crypto.historical.trade.Trade}." + } + } +} diff --git a/schema_json/endpoint/market_data/crypto/historical/trade/trade.json b/schema_json/endpoint/market_data/crypto/historical/trade/trade.json new file mode 100644 index 00000000..56fef69b --- /dev/null +++ b/schema_json/endpoint/market_data/crypto/historical/trade/trade.json @@ -0,0 +1,36 @@ +{ + "type": "object", + "title": "See Crypto Historical Data.", + "properties": { + "t": { + "existingJavaType": "java.time.ZonedDateTime", + "javaName": "timestamp", + "title": "Timestamp with nanosecond precision." + }, + "x": { + "existingJavaType": "net.jacobpeterson.alpaca.model.endpoint.marketdata.crypto.historical.enums.Exchange", + "javaName": "exchange", + "title": "Exchange where the trade happened." + }, + "p": { + "existingJavaType": "java.lang.Double", + "javaName": "price", + "title": "Trade price." + }, + "s": { + "existingJavaType": "java.lang.Double", + "javaName": "size", + "title": "Trade size." + }, + "tks": { + "existingJavaType": "net.jacobpeterson.alpaca.model.endpoint.marketdata.crypto.historical.trade.enums.TakerSide", + "javaName": "takerSide", + "title": "The {@link net.jacobpeterson.alpaca.model.endpoint.marketdata.crypto.historical.trade.enums.TakerSide} of the trade." + }, + "i": { + "existingJavaType": "java.lang.Long", + "javaName": "tradeID", + "title": "Trade ID." + } + } +} diff --git a/schema_json/endpoint/market_data/crypto/historical/trade/trades_response.json b/schema_json/endpoint/market_data/crypto/historical/trade/trades_response.json new file mode 100644 index 00000000..68a770a5 --- /dev/null +++ b/schema_json/endpoint/market_data/crypto/historical/trade/trades_response.json @@ -0,0 +1,18 @@ +{ + "type": "object", + "title": "See Crypto Historical Data.", + "properties": { + "trades": { + "existingJavaType": "java.util.ArrayList", + "title": "The {@link java.util.ArrayList} of {@link net.jacobpeterson.alpaca.model.endpoint.marketdata.crypto.historical.trade.Trade}s." + }, + "symbol": { + "existingJavaType": "java.lang.String", + "title": "Symbol that was queried." + }, + "next_page_token": { + "existingJavaType": "java.lang.String", + "title": "Token that can be used to query the next page." + } + } +} diff --git a/schema_json/endpoint/market_data/crypto/historical/xbbo/xbbo.json b/schema_json/endpoint/market_data/crypto/historical/xbbo/xbbo.json new file mode 100644 index 00000000..9646a1ee --- /dev/null +++ b/schema_json/endpoint/market_data/crypto/historical/xbbo/xbbo.json @@ -0,0 +1,41 @@ +{ + "type": "object", + "title": "See Crypto Historical Data.", + "properties": { + "t": { + "existingJavaType": "java.time.ZonedDateTime", + "javaName": "timestamp", + "title": "Timestamp with nanosecond precision." + }, + "ax": { + "existingJavaType": "net.jacobpeterson.alpaca.model.endpoint.marketdata.crypto.historical.enums.Exchange", + "javaName": "askExchange", + "title": "Ask Exchange." + }, + "ap": { + "existingJavaType": "java.lang.Double", + "javaName": "askPrice", + "title": "Ask price." + }, + "as": { + "existingJavaType": "java.lang.Double", + "javaName": "askSize", + "title": "Ask size." + }, + "bx": { + "existingJavaType": "net.jacobpeterson.alpaca.model.endpoint.marketdata.crypto.historical.enums.Exchange", + "javaName": "bidExchange", + "title": "Bid Exchange." + }, + "bp": { + "existingJavaType": "java.lang.Double", + "javaName": "bidPrice", + "title": "Bid price." + }, + "bs": { + "existingJavaType": "java.lang.Double", + "javaName": "bidSize", + "title": "Bid size." + } + } +} diff --git a/schema_json/endpoint/market_data/crypto/historical/xbbo/xbbo_response.json b/schema_json/endpoint/market_data/crypto/historical/xbbo/xbbo_response.json new file mode 100644 index 00000000..b3bbe427 --- /dev/null +++ b/schema_json/endpoint/market_data/crypto/historical/xbbo/xbbo_response.json @@ -0,0 +1,14 @@ +{ + "type": "object", + "title": "See Crypto Historical Data.", + "properties": { + "symbol": { + "existingJavaType": "java.lang.String", + "title": "Symbol that was queried." + }, + "xbbo": { + "existingJavaType": "net.jacobpeterson.alpaca.model.endpoint.marketdata.crypto.historical.xbbo.Xbbo", + "title": "The latest {@link net.jacobpeterson.alpaca.model.endpoint.marketdata.crypto.historical.xbbo.Xbbo}." + } + } +} diff --git a/schema_json/endpoint/market_data/historical/bar/bar.json b/schema_json/endpoint/market_data/stock/historical/bar/bar.json similarity index 100% rename from schema_json/endpoint/market_data/historical/bar/bar.json rename to schema_json/endpoint/market_data/stock/historical/bar/bar.json diff --git a/schema_json/endpoint/market_data/historical/bar/bars_response.json b/schema_json/endpoint/market_data/stock/historical/bar/bars_response.json similarity index 81% rename from schema_json/endpoint/market_data/historical/bar/bars_response.json rename to schema_json/endpoint/market_data/stock/historical/bar/bars_response.json index a876dcf8..ebd21e82 100644 --- a/schema_json/endpoint/market_data/historical/bar/bars_response.json +++ b/schema_json/endpoint/market_data/stock/historical/bar/bars_response.json @@ -3,8 +3,8 @@ "title": "See Historical Data.", "properties": { "bars": { - "existingJavaType": "java.util.ArrayList", - "title": "The {@link java.util.ArrayList} of {@link net.jacobpeterson.alpaca.model.endpoint.marketdata.historical.bar.Bar}s." + "existingJavaType": "java.util.ArrayList", + "title": "The {@link java.util.ArrayList} of {@link net.jacobpeterson.alpaca.model.endpoint.marketdata.stock.historical.bar.Bar}s." }, "symbol": { "existingJavaType": "java.lang.String", diff --git a/schema_json/endpoint/market_data/historical/bar/enums/bar_adjustment.json b/schema_json/endpoint/market_data/stock/historical/bar/enums/bar_adjustment.json similarity index 100% rename from schema_json/endpoint/market_data/historical/bar/enums/bar_adjustment.json rename to schema_json/endpoint/market_data/stock/historical/bar/enums/bar_adjustment.json diff --git a/schema_json/endpoint/market_data/historical/quote/latest_quote_response.json b/schema_json/endpoint/market_data/stock/historical/quote/latest_quote_response.json similarity index 82% rename from schema_json/endpoint/market_data/historical/quote/latest_quote_response.json rename to schema_json/endpoint/market_data/stock/historical/quote/latest_quote_response.json index 54477417..1250c5f8 100644 --- a/schema_json/endpoint/market_data/historical/quote/latest_quote_response.json +++ b/schema_json/endpoint/market_data/stock/historical/quote/latest_quote_response.json @@ -7,8 +7,8 @@ "title": "Symbol that was queried." }, "quote": { - "existingJavaType": "net.jacobpeterson.alpaca.model.endpoint.marketdata.historical.quote.Quote", - "title": "The latest {@link net.jacobpeterson.alpaca.model.endpoint.marketdata.historical.quote.Quote}." + "existingJavaType": "net.jacobpeterson.alpaca.model.endpoint.marketdata.stock.historical.quote.Quote", + "title": "The latest {@link net.jacobpeterson.alpaca.model.endpoint.marketdata.stock.historical.quote.Quote}." } } } diff --git a/schema_json/endpoint/market_data/historical/quote/quote.json b/schema_json/endpoint/market_data/stock/historical/quote/quote.json similarity index 100% rename from schema_json/endpoint/market_data/historical/quote/quote.json rename to schema_json/endpoint/market_data/stock/historical/quote/quote.json diff --git a/schema_json/endpoint/market_data/historical/quote/quotes_response.json b/schema_json/endpoint/market_data/stock/historical/quote/quotes_response.json similarity index 80% rename from schema_json/endpoint/market_data/historical/quote/quotes_response.json rename to schema_json/endpoint/market_data/stock/historical/quote/quotes_response.json index 88c1286e..6ba96682 100644 --- a/schema_json/endpoint/market_data/historical/quote/quotes_response.json +++ b/schema_json/endpoint/market_data/stock/historical/quote/quotes_response.json @@ -3,8 +3,8 @@ "title": "See Historical Data.", "properties": { "quotes": { - "existingJavaType": "java.util.ArrayList", - "title": "The {@link java.util.ArrayList} of {@link net.jacobpeterson.alpaca.model.endpoint.marketdata.historical.quote.Quote}s." + "existingJavaType": "java.util.ArrayList", + "title": "The {@link java.util.ArrayList} of {@link net.jacobpeterson.alpaca.model.endpoint.marketdata.stock.historical.quote.Quote}s." }, "symbol": { "existingJavaType": "java.lang.String", diff --git a/schema_json/endpoint/market_data/historical/snapshot/snapshot.json b/schema_json/endpoint/market_data/stock/historical/snapshot/snapshot.json similarity index 67% rename from schema_json/endpoint/market_data/historical/snapshot/snapshot.json rename to schema_json/endpoint/market_data/stock/historical/snapshot/snapshot.json index c2a0acf0..f42b18af 100644 --- a/schema_json/endpoint/market_data/historical/snapshot/snapshot.json +++ b/schema_json/endpoint/market_data/stock/historical/snapshot/snapshot.json @@ -3,24 +3,24 @@ "title": "See Historical Data.", "properties": { "latestTrade": { - "existingJavaType": "net.jacobpeterson.alpaca.model.endpoint.marketdata.historical.trade.Trade", - "title": "The latest {@link net.jacobpeterson.alpaca.model.endpoint.marketdata.historical.trade.Trade}." + "existingJavaType": "net.jacobpeterson.alpaca.model.endpoint.marketdata.stock.historical.trade.Trade", + "title": "The latest {@link net.jacobpeterson.alpaca.model.endpoint.marketdata.stock.historical.trade.Trade}." }, "latestQuote": { - "existingJavaType": "net.jacobpeterson.alpaca.model.endpoint.marketdata.historical.quote.Quote", - "title": "The latest {@link net.jacobpeterson.alpaca.model.endpoint.marketdata.historical.quote.Quote}." + "existingJavaType": "net.jacobpeterson.alpaca.model.endpoint.marketdata.stock.historical.quote.Quote", + "title": "The latest {@link net.jacobpeterson.alpaca.model.endpoint.marketdata.stock.historical.quote.Quote}." }, "minuteBar": { - "existingJavaType": "net.jacobpeterson.alpaca.model.endpoint.marketdata.historical.bar.Bar", - "title": "The minute {@link net.jacobpeterson.alpaca.model.endpoint.marketdata.historical.bar.Bar}." + "existingJavaType": "net.jacobpeterson.alpaca.model.endpoint.marketdata.stock.historical.bar.Bar", + "title": "The minute {@link net.jacobpeterson.alpaca.model.endpoint.marketdata.stock.historical.bar.Bar}." }, "dailyBar": { - "existingJavaType": "net.jacobpeterson.alpaca.model.endpoint.marketdata.historical.bar.Bar", - "title": "The daily {@link net.jacobpeterson.alpaca.model.endpoint.marketdata.historical.bar.Bar}." + "existingJavaType": "net.jacobpeterson.alpaca.model.endpoint.marketdata.stock.historical.bar.Bar", + "title": "The daily {@link net.jacobpeterson.alpaca.model.endpoint.marketdata.stock.historical.bar.Bar}." }, "prevDailyBar": { - "existingJavaType": "net.jacobpeterson.alpaca.model.endpoint.marketdata.historical.bar.Bar", - "title": "The previous daily {@link net.jacobpeterson.alpaca.model.endpoint.marketdata.historical.bar.Bar}." + "existingJavaType": "net.jacobpeterson.alpaca.model.endpoint.marketdata.stock.historical.bar.Bar", + "title": "The previous daily {@link net.jacobpeterson.alpaca.model.endpoint.marketdata.stock.historical.bar.Bar}." } } } diff --git a/schema_json/endpoint/market_data/historical/trade/latest_trade_response.json b/schema_json/endpoint/market_data/stock/historical/trade/latest_trade_response.json similarity index 82% rename from schema_json/endpoint/market_data/historical/trade/latest_trade_response.json rename to schema_json/endpoint/market_data/stock/historical/trade/latest_trade_response.json index 3025a719..f5be1b9d 100644 --- a/schema_json/endpoint/market_data/historical/trade/latest_trade_response.json +++ b/schema_json/endpoint/market_data/stock/historical/trade/latest_trade_response.json @@ -7,8 +7,8 @@ "title": "Symbol that was queried." }, "trade": { - "existingJavaType": "net.jacobpeterson.alpaca.model.endpoint.marketdata.historical.trade.Trade", - "title": "The latest {@link net.jacobpeterson.alpaca.model.endpoint.marketdata.historical.trade.Trade}." + "existingJavaType": "net.jacobpeterson.alpaca.model.endpoint.marketdata.stock.historical.trade.Trade", + "title": "The latest {@link net.jacobpeterson.alpaca.model.endpoint.marketdata.stock.historical.trade.Trade}." } } } diff --git a/schema_json/endpoint/market_data/historical/trade/trade.json b/schema_json/endpoint/market_data/stock/historical/trade/trade.json similarity index 100% rename from schema_json/endpoint/market_data/historical/trade/trade.json rename to schema_json/endpoint/market_data/stock/historical/trade/trade.json diff --git a/schema_json/endpoint/market_data/historical/trade/trades_response.json b/schema_json/endpoint/market_data/stock/historical/trade/trades_response.json similarity index 80% rename from schema_json/endpoint/market_data/historical/trade/trades_response.json rename to schema_json/endpoint/market_data/stock/historical/trade/trades_response.json index 928f6f24..503b6d9f 100644 --- a/schema_json/endpoint/market_data/historical/trade/trades_response.json +++ b/schema_json/endpoint/market_data/stock/historical/trade/trades_response.json @@ -3,8 +3,8 @@ "title": "See Historical Data.", "properties": { "trades": { - "existingJavaType": "java.util.ArrayList", - "title": "The {@link java.util.ArrayList} of {@link net.jacobpeterson.alpaca.model.endpoint.marketdata.historical.trade.Trade}s." + "existingJavaType": "java.util.ArrayList", + "title": "The {@link java.util.ArrayList} of {@link net.jacobpeterson.alpaca.model.endpoint.marketdata.stock.historical.trade.Trade}s." }, "symbol": { "existingJavaType": "java.lang.String", diff --git a/schema_json/endpoint/market_data/realtime/bar/bar_message.json b/schema_json/endpoint/market_data/stock/realtime/bar/bar_message.json similarity index 95% rename from schema_json/endpoint/market_data/realtime/bar/bar_message.json rename to schema_json/endpoint/market_data/stock/realtime/bar/bar_message.json index cc6aa003..7abf464d 100644 --- a/schema_json/endpoint/market_data/realtime/bar/bar_message.json +++ b/schema_json/endpoint/market_data/stock/realtime/bar/bar_message.json @@ -1,7 +1,7 @@ { "type": "object", "extends": { - "existingJavaType": "net.jacobpeterson.alpaca.model.endpoint.marketdata.realtime.SymbolMessage" + "existingJavaType": "net.jacobpeterson.alpaca.model.endpoint.marketdata.stock.realtime.SymbolMessage" }, "title": "See Real-time Data.", "properties": { diff --git a/schema_json/endpoint/market_data/realtime/control/error_message.json b/schema_json/endpoint/market_data/stock/realtime/control/error_message.json similarity index 91% rename from schema_json/endpoint/market_data/realtime/control/error_message.json rename to schema_json/endpoint/market_data/stock/realtime/control/error_message.json index 6dca33c4..1ef275ab 100644 --- a/schema_json/endpoint/market_data/realtime/control/error_message.json +++ b/schema_json/endpoint/market_data/stock/realtime/control/error_message.json @@ -1,7 +1,7 @@ { "type": "object", "extends": { - "existingJavaType": "net.jacobpeterson.alpaca.model.endpoint.marketdata.realtime.MarketDataMessage" + "existingJavaType": "net.jacobpeterson.alpaca.model.endpoint.marketdata.stock.realtime.MarketDataMessage" }, "title": "See Real-time Data.", "properties": { diff --git a/schema_json/endpoint/market_data/realtime/control/subscriptions_message.json b/schema_json/endpoint/market_data/stock/realtime/control/subscriptions_message.json similarity index 94% rename from schema_json/endpoint/market_data/realtime/control/subscriptions_message.json rename to schema_json/endpoint/market_data/stock/realtime/control/subscriptions_message.json index 6602ed55..76bfc479 100644 --- a/schema_json/endpoint/market_data/realtime/control/subscriptions_message.json +++ b/schema_json/endpoint/market_data/stock/realtime/control/subscriptions_message.json @@ -1,7 +1,7 @@ { "type": "object", "extends": { - "existingJavaType": "net.jacobpeterson.alpaca.model.endpoint.marketdata.realtime.MarketDataMessage" + "existingJavaType": "net.jacobpeterson.alpaca.model.endpoint.marketdata.stock.realtime.MarketDataMessage" }, "title": "See Real-time Data.", "properties": { diff --git a/schema_json/endpoint/market_data/realtime/control/success_message.json b/schema_json/endpoint/market_data/stock/realtime/control/success_message.json similarity index 89% rename from schema_json/endpoint/market_data/realtime/control/success_message.json rename to schema_json/endpoint/market_data/stock/realtime/control/success_message.json index 76ba8a8c..d7225e02 100644 --- a/schema_json/endpoint/market_data/realtime/control/success_message.json +++ b/schema_json/endpoint/market_data/stock/realtime/control/success_message.json @@ -1,7 +1,7 @@ { "type": "object", "extends": { - "existingJavaType": "net.jacobpeterson.alpaca.model.endpoint.marketdata.realtime.MarketDataMessage" + "existingJavaType": "net.jacobpeterson.alpaca.model.endpoint.marketdata.stock.realtime.MarketDataMessage" }, "title": "See Real-time Data.", "properties": { diff --git a/schema_json/endpoint/market_data/realtime/enums/market_data_message_type.json b/schema_json/endpoint/market_data/stock/realtime/enums/market_data_message_type.json similarity index 100% rename from schema_json/endpoint/market_data/realtime/enums/market_data_message_type.json rename to schema_json/endpoint/market_data/stock/realtime/enums/market_data_message_type.json diff --git a/schema_json/endpoint/market_data/realtime/market_data_message.json b/schema_json/endpoint/market_data/stock/realtime/market_data_message.json similarity index 75% rename from schema_json/endpoint/market_data/realtime/market_data_message.json rename to schema_json/endpoint/market_data/stock/realtime/market_data_message.json index 3e43d789..5ee64bef 100644 --- a/schema_json/endpoint/market_data/realtime/market_data_message.json +++ b/schema_json/endpoint/market_data/stock/realtime/market_data_message.json @@ -3,9 +3,9 @@ "title": "See Real-time Data.", "properties": { "T": { - "existingJavaType": "net.jacobpeterson.alpaca.model.endpoint.marketdata.realtime.enums.MarketDataMessageType", + "existingJavaType": "net.jacobpeterson.alpaca.model.endpoint.marketdata.stock.realtime.enums.MarketDataMessageType", "javaName": "messageType", - "title": "The {@link net.jacobpeterson.alpaca.model.endpoint.marketdata.realtime.enums.MarketDataMessageType}." + "title": "The {@link net.jacobpeterson.alpaca.model.endpoint.marketdata.stock.realtime.enums.MarketDataMessageType}." } } } diff --git a/schema_json/endpoint/market_data/realtime/quote/quote_message.json b/schema_json/endpoint/market_data/stock/realtime/quote/quote_message.json similarity index 97% rename from schema_json/endpoint/market_data/realtime/quote/quote_message.json rename to schema_json/endpoint/market_data/stock/realtime/quote/quote_message.json index f1f9606d..3c7c650c 100644 --- a/schema_json/endpoint/market_data/realtime/quote/quote_message.json +++ b/schema_json/endpoint/market_data/stock/realtime/quote/quote_message.json @@ -1,7 +1,7 @@ { "type": "object", "extends": { - "existingJavaType": "net.jacobpeterson.alpaca.model.endpoint.marketdata.realtime.SymbolMessage" + "existingJavaType": "net.jacobpeterson.alpaca.model.endpoint.marketdata.stock.realtime.SymbolMessage" }, "title": "See Real-time Data.", "properties": { diff --git a/schema_json/endpoint/market_data/realtime/symbol_message.json b/schema_json/endpoint/market_data/stock/realtime/symbol_message.json similarity index 89% rename from schema_json/endpoint/market_data/realtime/symbol_message.json rename to schema_json/endpoint/market_data/stock/realtime/symbol_message.json index 961d15c0..acc6327c 100644 --- a/schema_json/endpoint/market_data/realtime/symbol_message.json +++ b/schema_json/endpoint/market_data/stock/realtime/symbol_message.json @@ -1,7 +1,7 @@ { "type": "object", "extends": { - "existingJavaType": "net.jacobpeterson.alpaca.model.endpoint.marketdata.realtime.MarketDataMessage" + "existingJavaType": "net.jacobpeterson.alpaca.model.endpoint.marketdata.stock.realtime.MarketDataMessage" }, "title": "See Real-time Data.", "properties": { diff --git a/schema_json/endpoint/market_data/realtime/trade/trade_message.json b/schema_json/endpoint/market_data/stock/realtime/trade/trade_message.json similarity index 96% rename from schema_json/endpoint/market_data/realtime/trade/trade_message.json rename to schema_json/endpoint/market_data/stock/realtime/trade/trade_message.json index 7d01e2b8..e72f5859 100644 --- a/schema_json/endpoint/market_data/realtime/trade/trade_message.json +++ b/schema_json/endpoint/market_data/stock/realtime/trade/trade_message.json @@ -1,7 +1,7 @@ { "type": "object", "extends": { - "existingJavaType": "net.jacobpeterson.alpaca.model.endpoint.marketdata.realtime.SymbolMessage" + "existingJavaType": "net.jacobpeterson.alpaca.model.endpoint.marketdata.stock.realtime.SymbolMessage" }, "title": "See Real-time Data.", "properties": { diff --git a/schema_json/endpoint/position/position.json b/schema_json/endpoint/position/position.json index 2f1eb603..7ebb619f 100644 --- a/schema_json/endpoint/position/position.json +++ b/schema_json/endpoint/position/position.json @@ -12,7 +12,7 @@ }, "exchange": { "existingJavaType": "java.lang.String", - "title": "Exchange name of the asset." + "title": "Exchange name of the asset (\"ErisX\" for crypto)." }, "asset_class": { "existingJavaType": "java.lang.String", diff --git a/src/main/java/net/jacobpeterson/alpaca/AlpacaAPI.java b/src/main/java/net/jacobpeterson/alpaca/AlpacaAPI.java index b874d794..67813131 100644 --- a/src/main/java/net/jacobpeterson/alpaca/AlpacaAPI.java +++ b/src/main/java/net/jacobpeterson/alpaca/AlpacaAPI.java @@ -12,11 +12,12 @@ import net.jacobpeterson.alpaca.rest.endpoint.AssetsEndpoint; import net.jacobpeterson.alpaca.rest.endpoint.CalendarEndpoint; import net.jacobpeterson.alpaca.rest.endpoint.ClockEndpoint; -import net.jacobpeterson.alpaca.rest.endpoint.MarketDataEndpoint; import net.jacobpeterson.alpaca.rest.endpoint.OrdersEndpoint; import net.jacobpeterson.alpaca.rest.endpoint.PortfolioHistoryEndpoint; import net.jacobpeterson.alpaca.rest.endpoint.PositionsEndpoint; import net.jacobpeterson.alpaca.rest.endpoint.WatchlistEndpoint; +import net.jacobpeterson.alpaca.rest.endpoint.marketdata.CryptoMarketDataEndpoint; +import net.jacobpeterson.alpaca.rest.endpoint.marketdata.StockMarketDataEndpoint; import net.jacobpeterson.alpaca.websocket.AlpacaWebsocket; import net.jacobpeterson.alpaca.websocket.marketdata.MarketDataWebsocket; import net.jacobpeterson.alpaca.websocket.marketdata.MarketDataWebsocketInterface; @@ -42,13 +43,16 @@ public class AlpacaAPI { private static final Logger LOGGER = LoggerFactory.getLogger(AlpacaAPI.class); private static final String VERSION_2_PATH_SEGMENT = "v2"; + private static final String VERSION_1_BETA_1_PATH_SEGMENT = "v1beta1"; private final OkHttpClient okHttpClient; private final AlpacaClient brokerClient; - private final AlpacaClient dataClient; + private final AlpacaClient cryptoDataClient; + private final AlpacaClient stockDataClient; // Ordering of fields/methods below are analogous to the ordering in the Alpaca documentation private final AccountEndpoint accountEndpoint; - private final MarketDataEndpoint marketDataEndpoint; + private final CryptoMarketDataEndpoint cryptoMarketDataEndpoint; + private final StockMarketDataEndpoint stockMarketDataEndpoint; private final OrdersEndpoint ordersEndpoint; private final PositionsEndpoint positionsEndpoint; private final AssetsEndpoint assetsEndpoint; @@ -154,14 +158,17 @@ public AlpacaAPI(OkHttpClient okHttpClient, String keyID, String secretKey, Stri if (oAuthToken == null) { brokerClient = new AlpacaClient(okHttpClient, keyID, secretKey, brokerHostSubdomain, VERSION_2_PATH_SEGMENT); - dataClient = new AlpacaClient(okHttpClient, keyID, secretKey, "data", VERSION_2_PATH_SEGMENT); + cryptoDataClient = new AlpacaClient(okHttpClient, keyID, secretKey, "data", VERSION_1_BETA_1_PATH_SEGMENT); + stockDataClient = new AlpacaClient(okHttpClient, keyID, secretKey, "data", VERSION_2_PATH_SEGMENT); } else { brokerClient = new AlpacaClient(okHttpClient, oAuthToken, brokerHostSubdomain, VERSION_2_PATH_SEGMENT); - dataClient = null; + cryptoDataClient = null; + stockDataClient = null; } accountEndpoint = new AccountEndpoint(brokerClient); - marketDataEndpoint = dataClient == null ? null : new MarketDataEndpoint(dataClient); + cryptoMarketDataEndpoint = cryptoDataClient == null ? null : new CryptoMarketDataEndpoint(cryptoDataClient); + stockMarketDataEndpoint = stockDataClient == null ? null : new StockMarketDataEndpoint(stockDataClient); ordersEndpoint = new OrdersEndpoint(brokerClient); positionsEndpoint = new PositionsEndpoint(brokerClient); assetsEndpoint = new AssetsEndpoint(brokerClient); @@ -172,7 +179,7 @@ public AlpacaAPI(OkHttpClient okHttpClient, String keyID, String secretKey, Stri accountActivitiesEndpoint = new AccountActivitiesEndpoint(brokerClient); portfolioHistoryEndpoint = new PortfolioHistoryEndpoint(brokerClient); streamingWebsocket = new StreamingWebsocket(okHttpClient, brokerHostSubdomain, keyID, secretKey, oAuthToken); - marketDataWebsocket = dataClient == null ? null : + marketDataWebsocket = stockDataClient == null ? null : new MarketDataWebsocket(okHttpClient, dataAPIType, keyID, secretKey); } @@ -184,10 +191,17 @@ public AccountEndpoint account() { } /** - * @return the {@link MarketDataEndpoint} + * @return the {@link CryptoMarketDataEndpoint} */ - public MarketDataEndpoint marketData() { - return marketDataEndpoint; + public CryptoMarketDataEndpoint cryptoMarketData() { + return cryptoMarketDataEndpoint; + } + + /** + * @return the {@link StockMarketDataEndpoint} + */ + public StockMarketDataEndpoint stockMarketData() { + return stockMarketDataEndpoint; } /** @@ -267,30 +281,19 @@ public MarketDataWebsocketInterface marketDataStreaming() { return marketDataWebsocket; } - /** - * Gets {@link #okHttpClient}. - * - * @return the {@link OkHttpClient} - */ public OkHttpClient getOkHttpClient() { return okHttpClient; } - /** - * Gets {@link #brokerClient}. - * - * @return the broker {@link AlpacaClient} - */ public AlpacaClient getBrokerClient() { return brokerClient; } - /** - * Gets {@link #dataClient}. - * - * @return the data {@link AlpacaClient} - */ - public AlpacaClient getDataClient() { - return dataClient; + public AlpacaClient getCryptoDataClient() { + return cryptoDataClient; + } + + public AlpacaClient getStockDataClient() { + return stockDataClient; } } diff --git a/src/main/java/net/jacobpeterson/alpaca/rest/AlpacaClient.java b/src/main/java/net/jacobpeterson/alpaca/rest/AlpacaClient.java index 90e7265a..12cb6a6f 100644 --- a/src/main/java/net/jacobpeterson/alpaca/rest/AlpacaClient.java +++ b/src/main/java/net/jacobpeterson/alpaca/rest/AlpacaClient.java @@ -222,20 +222,10 @@ public Response executeRequest(Request request) throws IOException { return okHttpClient.newCall(request).execute(); } - /** - * Gets {@link #baseURL}. - * - * @return the base {@link HttpUrl} - */ public HttpUrl getBaseURL() { return baseURL; } - /** - * Gets {@link #requestHeaders}. - * - * @return the request {@link Headers} - */ public Headers getRequestHeaders() { return requestHeaders; } diff --git a/src/main/java/net/jacobpeterson/alpaca/rest/AlpacaClientException.java b/src/main/java/net/jacobpeterson/alpaca/rest/AlpacaClientException.java index bcf89c2b..2114b1e8 100644 --- a/src/main/java/net/jacobpeterson/alpaca/rest/AlpacaClientException.java +++ b/src/main/java/net/jacobpeterson/alpaca/rest/AlpacaClientException.java @@ -120,38 +120,18 @@ private String parseAPIErrorResponse() { return messageBuilder.toString(); } - /** - * Gets the {@link #responseStatusCode}. - * - * @return an {@link Integer} - */ public Integer getResponseStatusCode() { return responseStatusCode; } - /** - * Gets the {@link #responseStatusMessage}. - * - * @return a {@link String} - */ public String getResponseStatusMessage() { return responseStatusMessage; } - /** - * Gets the {@link #apiResponseCode}. - * - * @return an {@link Integer} - */ public Integer getAPIResponseCode() { return apiResponseCode; } - /** - * Gets the {@link #apiResponseMessage}. - * - * @return a {@link String} - */ public String getAPIResponseMessage() { return apiResponseMessage; } diff --git a/src/main/java/net/jacobpeterson/alpaca/rest/endpoint/AccountActivitiesEndpoint.java b/src/main/java/net/jacobpeterson/alpaca/rest/endpoint/AccountActivitiesEndpoint.java index d1e5b88b..adbe8414 100644 --- a/src/main/java/net/jacobpeterson/alpaca/rest/endpoint/AccountActivitiesEndpoint.java +++ b/src/main/java/net/jacobpeterson/alpaca/rest/endpoint/AccountActivitiesEndpoint.java @@ -56,7 +56,7 @@ public AccountActivitiesEndpoint(AlpacaClient alpacaClient) { * @param sortDirection the {@link SortDirection} (defaults to {@link SortDirection#DESCENDING} if unspecified.) * @param pageSize the maximum number of entries to return in the response. (See the section on paging.) * @param pageToken the ID of the end of your current page of results. (See the section on paging.) - * @param activityTypes the {@link ActivityType}s (null for all {@link ActivityType}s) + * @param activityTypes the {@link ActivityType}s (null for all {@link ActivityType}s) * * @return a {@link List} of {@link AccountActivity}s * diff --git a/src/main/java/net/jacobpeterson/alpaca/rest/endpoint/AssetsEndpoint.java b/src/main/java/net/jacobpeterson/alpaca/rest/endpoint/AssetsEndpoint.java index c0642115..6cbf3298 100644 --- a/src/main/java/net/jacobpeterson/alpaca/rest/endpoint/AssetsEndpoint.java +++ b/src/main/java/net/jacobpeterson/alpaca/rest/endpoint/AssetsEndpoint.java @@ -2,6 +2,7 @@ import com.google.gson.reflect.TypeToken; import net.jacobpeterson.alpaca.model.endpoint.asset.Asset; +import net.jacobpeterson.alpaca.model.endpoint.asset.enums.AssetClass; import net.jacobpeterson.alpaca.model.endpoint.asset.enums.AssetStatus; import net.jacobpeterson.alpaca.rest.AlpacaClient; import net.jacobpeterson.alpaca.rest.AlpacaClientException; @@ -31,13 +32,13 @@ public AssetsEndpoint(AlpacaClient alpacaClient) { * Get a list of {@link Asset}s. * * @param assetStatus the {@link AssetStatus}. By default, all {@link AssetStatus}es are included. - * @param assetClass the asset class. Defaults to "us_equity". + * @param assetClass the {@link AssetClass} (null for {@link AssetClass#US_EQUITY}) * * @return a {@link List} of {@link Asset}s * * @throws AlpacaClientException thrown for {@link AlpacaClientException}s */ - public List get(AssetStatus assetStatus, String assetClass) throws AlpacaClientException { + public List get(AssetStatus assetStatus, AssetClass assetClass) throws AlpacaClientException { HttpUrl.Builder urlBuilder = alpacaClient.urlBuilder() .addPathSegment(endpointPathSegment); @@ -46,7 +47,7 @@ public List get(AssetStatus assetStatus, String assetClass) throws Alpaca } if (assetClass != null) { - urlBuilder.addQueryParameter("asset_class", assetClass); + urlBuilder.addQueryParameter("asset_class", assetClass.toString()); } Request request = alpacaClient.requestBuilder(urlBuilder.build()) diff --git a/src/main/java/net/jacobpeterson/alpaca/rest/endpoint/OrdersEndpoint.java b/src/main/java/net/jacobpeterson/alpaca/rest/endpoint/OrdersEndpoint.java index a542d12d..8715d1fc 100644 --- a/src/main/java/net/jacobpeterson/alpaca/rest/endpoint/OrdersEndpoint.java +++ b/src/main/java/net/jacobpeterson/alpaca/rest/endpoint/OrdersEndpoint.java @@ -52,7 +52,9 @@ public OrdersEndpoint(AlpacaClient alpacaClient) { * @param direction the chronological order of response based on the submission time. Defaults to {@link * SortDirection#DESCENDING}. * @param nested if true, the result will roll up multi-leg orders under the legs field of primary order. - * @param symbols a {@link Collection} of symbols to filter the result by (null for no filter). + * @param symbols a {@link Collection} of symbols to filter by (e.g. "AAPL,TSLA,MSFT"). A currency pair is + * required for crypto orders (e.g. "BTCUSD,BCHUSD,LTCUSD,ETCUSD"). null for no + * filter. * * @return a {@link List} of {@link Order}s * diff --git a/src/main/java/net/jacobpeterson/alpaca/rest/endpoint/marketdata/CryptoMarketDataEndpoint.java b/src/main/java/net/jacobpeterson/alpaca/rest/endpoint/marketdata/CryptoMarketDataEndpoint.java new file mode 100644 index 00000000..c044d1f8 --- /dev/null +++ b/src/main/java/net/jacobpeterson/alpaca/rest/endpoint/marketdata/CryptoMarketDataEndpoint.java @@ -0,0 +1,296 @@ +package net.jacobpeterson.alpaca.rest.endpoint.marketdata; + +import net.jacobpeterson.alpaca.model.endpoint.marketdata.common.enums.BarTimePeriod; +import net.jacobpeterson.alpaca.model.endpoint.marketdata.crypto.historical.bar.Bar; +import net.jacobpeterson.alpaca.model.endpoint.marketdata.crypto.historical.bar.BarsResponse; +import net.jacobpeterson.alpaca.model.endpoint.marketdata.crypto.historical.enums.Exchange; +import net.jacobpeterson.alpaca.model.endpoint.marketdata.crypto.historical.quote.LatestQuoteResponse; +import net.jacobpeterson.alpaca.model.endpoint.marketdata.crypto.historical.quote.Quote; +import net.jacobpeterson.alpaca.model.endpoint.marketdata.crypto.historical.quote.QuotesResponse; +import net.jacobpeterson.alpaca.model.endpoint.marketdata.crypto.historical.trade.LatestTradeResponse; +import net.jacobpeterson.alpaca.model.endpoint.marketdata.crypto.historical.trade.Trade; +import net.jacobpeterson.alpaca.model.endpoint.marketdata.crypto.historical.trade.TradesResponse; +import net.jacobpeterson.alpaca.model.endpoint.marketdata.crypto.historical.xbbo.XbboResponse; +import net.jacobpeterson.alpaca.rest.AlpacaClient; +import net.jacobpeterson.alpaca.rest.AlpacaClientException; +import net.jacobpeterson.alpaca.rest.endpoint.AlpacaEndpoint; +import net.jacobpeterson.alpaca.util.format.FormatUtil; +import okhttp3.HttpUrl; +import okhttp3.Request; + +import java.time.ZonedDateTime; +import java.util.Collection; +import java.util.stream.Collectors; + +import static com.google.common.base.Preconditions.checkNotNull; + +/** + * {@link AlpacaEndpoint} for + * Historical + * Crypto Market Data API. + */ +public class CryptoMarketDataEndpoint extends AlpacaEndpoint { + + /** + * Instantiates a new {@link CryptoMarketDataEndpoint}. + * + * @param alpacaClient the {@link AlpacaClient} + */ + public CryptoMarketDataEndpoint(AlpacaClient alpacaClient) { + super(alpacaClient, "crypto"); + } + + /** + * Gets {@link Trade} historical data for the requested crypto symbol. + * + * @param symbol the symbol to query for + * @param exchanges a {@link Collection} of {@link Exchange}s to filter by. null for all exchanges. + * @param start filter data equal to or after this {@link ZonedDateTime}. Fractions of a second are not + * accepted. null for the current day in Central Time. + * @param end filter data equal to or before this {@link ZonedDateTime}. Fractions of a second are not + * accepted. null for now. + * @param limit number of data points to return. Must be in range 1-10000, defaults to 1000 if null + * is given + * @param pageToken pagination token to continue from + * + * @return the {@link TradesResponse} + * + * @throws AlpacaClientException thrown for {@link AlpacaClientException}s + */ + public TradesResponse getTrades(String symbol, Collection exchanges, ZonedDateTime start, + ZonedDateTime end, Integer limit, String pageToken) throws AlpacaClientException { + checkNotNull(symbol); + + HttpUrl.Builder urlBuilder = alpacaClient.urlBuilder() + .addPathSegment(endpointPathSegment) + .addPathSegment(symbol) + .addPathSegment("trades"); + + if (exchanges != null) { + urlBuilder.addQueryParameter("exchanges", exchanges.stream() + .map(Exchange::value) + .collect(Collectors.joining(","))); + } + + if (start != null) { + urlBuilder.addQueryParameter("start", FormatUtil.toRFC3339Format(start)); + } + + if (end != null) { + urlBuilder.addQueryParameter("end", FormatUtil.toRFC3339Format(end)); + } + + if (limit != null) { + urlBuilder.addQueryParameter("limit", limit.toString()); + } + + if (pageToken != null) { + urlBuilder.addQueryParameter("page_token", pageToken); + } + + Request request = alpacaClient.requestBuilder(urlBuilder.build()) + .get() + .build(); + return alpacaClient.requestObject(request, TradesResponse.class); + } + + /** + * Gets the latest {@link Trade} for the requested security. + * + * @param symbol the symbol to query for + * @param exchange the {@link Exchange} to filter the latest {@link Trade} by. + * + * @return the {@link LatestTradeResponse} + * + * @throws AlpacaClientException thrown for {@link AlpacaClientException}s + */ + public LatestTradeResponse getLatestTrade(String symbol, Exchange exchange) throws AlpacaClientException { + checkNotNull(symbol); + checkNotNull(exchange); + + HttpUrl.Builder urlBuilder = alpacaClient.urlBuilder() + .addPathSegment(endpointPathSegment) + .addPathSegment(symbol) + .addPathSegment("trades") + .addPathSegment("latest"); + + urlBuilder.addQueryParameter("exchange", exchange.toString()); + + Request request = alpacaClient.requestBuilder(urlBuilder.build()) + .get() + .build(); + return alpacaClient.requestObject(request, LatestTradeResponse.class); + } + + /** + * Gets {@link Quote} historical data for the requested crypto symbol. + * + * @param symbol the symbol to query for + * @param exchanges a {@link Collection} of {@link Exchange}s to filter by. null for all exchanges. + * @param start filter data equal to or after this {@link ZonedDateTime}. Fractions of a second are not + * accepted. null for the current day in Central Time. + * @param end filter data equal to or before this {@link ZonedDateTime}. Fractions of a second are not + * accepted. null for now. + * @param limit number of data points to return. Must be in range 1-10000, defaults to 1000 if null + * is given + * @param pageToken pagination token to continue from + * + * @return the {@link QuotesResponse} + * + * @throws AlpacaClientException thrown for {@link AlpacaClientException}s + */ + public QuotesResponse getQuotes(String symbol, Collection exchanges, ZonedDateTime start, + ZonedDateTime end, Integer limit, String pageToken) throws AlpacaClientException { + checkNotNull(symbol); + + HttpUrl.Builder urlBuilder = alpacaClient.urlBuilder() + .addPathSegment(endpointPathSegment) + .addPathSegment(symbol) + .addPathSegment("quotes"); + + if (exchanges != null) { + urlBuilder.addQueryParameter("exchanges", exchanges.stream() + .map(Exchange::value) + .collect(Collectors.joining(","))); + } + + if (start != null) { + urlBuilder.addQueryParameter("start", FormatUtil.toRFC3339Format(start)); + } + + if (end != null) { + urlBuilder.addQueryParameter("end", FormatUtil.toRFC3339Format(end)); + } + + if (limit != null) { + urlBuilder.addQueryParameter("limit", limit.toString()); + } + + if (pageToken != null) { + urlBuilder.addQueryParameter("page_token", pageToken); + } + + Request request = alpacaClient.requestBuilder(urlBuilder.build()) + .get() + .build(); + return alpacaClient.requestObject(request, QuotesResponse.class); + } + + /** + * Gets the latest {@link Quote} for the requested security. + * + * @param symbol the symbol to query for + * @param exchange the {@link Exchange} to filter the latest {@link Quote} by. + * + * @return the {@link LatestQuoteResponse} + * + * @throws AlpacaClientException thrown for {@link AlpacaClientException}s + */ + public LatestQuoteResponse getLatestQuote(String symbol, Exchange exchange) throws AlpacaClientException { + checkNotNull(symbol); + checkNotNull(exchange); + + HttpUrl.Builder urlBuilder = alpacaClient.urlBuilder() + .addPathSegment(endpointPathSegment) + .addPathSegment(symbol) + .addPathSegment("quotes") + .addPathSegment("latest"); + + urlBuilder.addQueryParameter("exchange", exchange.toString()); + + Request request = alpacaClient.requestBuilder(urlBuilder.build()) + .get() + .build(); + return alpacaClient.requestObject(request, LatestQuoteResponse.class); + } + + /** + * Gets {@link Bar} aggregate historical data for the requested crypto. + * + * @param symbol the symbol to query for + * @param exchanges a {@link Collection} of {@link Exchange}s to filter by. null for all + * exchanges. + * @param start filter data equal to or after this {@link ZonedDateTime}. Fractions of a second are + * not accepted. null for now. + * @param limit number of data points to return. Must be in range 1-10000, defaults to 1000 if + * null is given + * @param pageToken pagination token to continue from + * @param barTimePeriodDuration the duration for the given barTimePeriod parameter. e.g. for + * 15Min bars, you would supply 15 for this parameter and + * {@link BarTimePeriod#MINUTE} for the barTimePeriod parameter. + * @param barTimePeriod the {@link BarTimePeriod} e.g. for 15Min bars, you would supply {@link + * BarTimePeriod#MINUTE} for this parameter and 15 for the + * barTimePeriodDuration parameter. + * + * @return the {@link BarsResponse} + * + * @throws AlpacaClientException thrown for {@link AlpacaClientException}s + */ + public BarsResponse getBars(String symbol, Collection exchanges, ZonedDateTime start, Integer limit, + String pageToken, int barTimePeriodDuration, BarTimePeriod barTimePeriod) throws AlpacaClientException { + checkNotNull(symbol); + checkNotNull(barTimePeriod); + + HttpUrl.Builder urlBuilder = alpacaClient.urlBuilder() + .addPathSegment(endpointPathSegment) + .addPathSegment(symbol) + .addPathSegment("bars"); + + if (exchanges != null) { + urlBuilder.addQueryParameter("exchanges", exchanges.stream() + .map(Exchange::value) + .collect(Collectors.joining(","))); + } + + if (start != null) { + urlBuilder.addQueryParameter("start", FormatUtil.toRFC3339Format(start)); + } + + if (limit != null) { + urlBuilder.addQueryParameter("limit", limit.toString()); + } + + if (pageToken != null) { + urlBuilder.addQueryParameter("page_token", pageToken); + } + + urlBuilder.addQueryParameter("timeframe", barTimePeriodDuration + barTimePeriod.toString()); + + Request request = alpacaClient.requestBuilder(urlBuilder.build()) + .get() + .build(); + return alpacaClient.requestObject(request, BarsResponse.class); + } + + /** + * Returns the XBBO for a crypto symbol that calculates the Best Bid and Offer across multiple exchanges. + * + * @param symbol the symbol to query for + * @param exchanges a {@link Collection} of {@link Exchange}s to filter by. If null, then only the + * exchanges that can be traded on Alpaca are included in the calculation. + * + * @return the {@link LatestQuoteResponse} + * + * @throws AlpacaClientException thrown for {@link AlpacaClientException}s + */ + public XbboResponse getXBBO(String symbol, Collection exchanges) throws AlpacaClientException { + checkNotNull(symbol); + + HttpUrl.Builder urlBuilder = alpacaClient.urlBuilder() + .addPathSegment(endpointPathSegment) + .addPathSegment(symbol) + .addPathSegment("xbbo") + .addPathSegment("latest"); + + if (exchanges != null) { + urlBuilder.addQueryParameter("exchanges", exchanges.stream() + .map(Exchange::value) + .collect(Collectors.joining(","))); + } + + Request request = alpacaClient.requestBuilder(urlBuilder.build()) + .get() + .build(); + return alpacaClient.requestObject(request, XbboResponse.class); + } +} diff --git a/src/main/java/net/jacobpeterson/alpaca/rest/endpoint/MarketDataEndpoint.java b/src/main/java/net/jacobpeterson/alpaca/rest/endpoint/marketdata/StockMarketDataEndpoint.java similarity index 86% rename from src/main/java/net/jacobpeterson/alpaca/rest/endpoint/MarketDataEndpoint.java rename to src/main/java/net/jacobpeterson/alpaca/rest/endpoint/marketdata/StockMarketDataEndpoint.java index 45fc4a08..f203a5d2 100644 --- a/src/main/java/net/jacobpeterson/alpaca/rest/endpoint/MarketDataEndpoint.java +++ b/src/main/java/net/jacobpeterson/alpaca/rest/endpoint/marketdata/StockMarketDataEndpoint.java @@ -1,19 +1,20 @@ -package net.jacobpeterson.alpaca.rest.endpoint; +package net.jacobpeterson.alpaca.rest.endpoint.marketdata; import com.google.gson.reflect.TypeToken; -import net.jacobpeterson.alpaca.model.endpoint.marketdata.historical.bar.Bar; -import net.jacobpeterson.alpaca.model.endpoint.marketdata.historical.bar.BarsResponse; -import net.jacobpeterson.alpaca.model.endpoint.marketdata.historical.bar.enums.BarAdjustment; -import net.jacobpeterson.alpaca.model.endpoint.marketdata.historical.bar.enums.BarTimePeriod; -import net.jacobpeterson.alpaca.model.endpoint.marketdata.historical.quote.LatestQuoteResponse; -import net.jacobpeterson.alpaca.model.endpoint.marketdata.historical.quote.Quote; -import net.jacobpeterson.alpaca.model.endpoint.marketdata.historical.quote.QuotesResponse; -import net.jacobpeterson.alpaca.model.endpoint.marketdata.historical.snapshot.Snapshot; -import net.jacobpeterson.alpaca.model.endpoint.marketdata.historical.trade.LatestTradeResponse; -import net.jacobpeterson.alpaca.model.endpoint.marketdata.historical.trade.Trade; -import net.jacobpeterson.alpaca.model.endpoint.marketdata.historical.trade.TradesResponse; +import net.jacobpeterson.alpaca.model.endpoint.marketdata.common.enums.BarTimePeriod; +import net.jacobpeterson.alpaca.model.endpoint.marketdata.stock.historical.bar.Bar; +import net.jacobpeterson.alpaca.model.endpoint.marketdata.stock.historical.bar.BarsResponse; +import net.jacobpeterson.alpaca.model.endpoint.marketdata.stock.historical.bar.enums.BarAdjustment; +import net.jacobpeterson.alpaca.model.endpoint.marketdata.stock.historical.quote.LatestQuoteResponse; +import net.jacobpeterson.alpaca.model.endpoint.marketdata.stock.historical.quote.Quote; +import net.jacobpeterson.alpaca.model.endpoint.marketdata.stock.historical.quote.QuotesResponse; +import net.jacobpeterson.alpaca.model.endpoint.marketdata.stock.historical.snapshot.Snapshot; +import net.jacobpeterson.alpaca.model.endpoint.marketdata.stock.historical.trade.LatestTradeResponse; +import net.jacobpeterson.alpaca.model.endpoint.marketdata.stock.historical.trade.Trade; +import net.jacobpeterson.alpaca.model.endpoint.marketdata.stock.historical.trade.TradesResponse; import net.jacobpeterson.alpaca.rest.AlpacaClient; import net.jacobpeterson.alpaca.rest.AlpacaClientException; +import net.jacobpeterson.alpaca.rest.endpoint.AlpacaEndpoint; import net.jacobpeterson.alpaca.util.format.FormatUtil; import okhttp3.HttpUrl; import okhttp3.Request; @@ -31,14 +32,14 @@ * Historical * Market Data API v2. */ -public class MarketDataEndpoint extends AlpacaEndpoint { +public class StockMarketDataEndpoint extends AlpacaEndpoint { /** - * Instantiates a new {@link MarketDataEndpoint}. + * Instantiates a new {@link StockMarketDataEndpoint}. * * @param alpacaClient the {@link AlpacaClient} */ - public MarketDataEndpoint(AlpacaClient alpacaClient) { + public StockMarketDataEndpoint(AlpacaClient alpacaClient) { super(alpacaClient, "stocks"); } @@ -87,7 +88,7 @@ public TradesResponse getTrades(String symbol, ZonedDateTime start, ZonedDateTim } /** - * The Latest Trade API provides the latest {@link Trade} data for a given ticker symbol. + * Gets the latest {@link Trade} for the requested security. * * @param symbol the symbol to query for * @@ -155,7 +156,7 @@ public QuotesResponse getQuotes(String symbol, ZonedDateTime start, ZonedDateTim } /** - * The Latest Quote API provides the latest quote data for a given ticker symbol. + * Gets the latest {@link Quote} for the requested security. * * @param symbol the symbol to query for * @@ -217,7 +218,6 @@ public BarsResponse getBars(String symbol, ZonedDateTime start, ZonedDateTime en urlBuilder.addQueryParameter("start", FormatUtil.toRFC3339Format(start)); urlBuilder.addQueryParameter("end", FormatUtil.toRFC3339Format(end)); - urlBuilder.addQueryParameter("timeframe", barTimePeriodDuration + barTimePeriod.toString()); if (limit != null) { urlBuilder.addQueryParameter("limit", limit.toString()); @@ -227,6 +227,8 @@ public BarsResponse getBars(String symbol, ZonedDateTime start, ZonedDateTime en urlBuilder.addQueryParameter("page_token", pageToken); } + urlBuilder.addQueryParameter("timeframe", barTimePeriodDuration + barTimePeriod.toString()); + if (barAdjustment != null) { urlBuilder.addQueryParameter("adjustment", barAdjustment.toString()); } @@ -238,8 +240,7 @@ public BarsResponse getBars(String symbol, ZonedDateTime start, ZonedDateTime en } /** - * The Snapshot API for one ticker provides the latest trade, latest quote, minute bar daily bar, and previous daily - * bar data for a given ticker symbol + * Gets {@link Snapshot}s of the requested securities. * * @param symbols a {@link Collection} of symbols to query for * @@ -264,8 +265,7 @@ public Map getSnapshots(Collection symbols) throws Alp } /** - * The Snapshot API for one ticker provides the latest trade, latest quote, minute bar daily bar and previous daily - * bar data for a given ticker symbol + * Gets a {@link Snapshot} of the requested security. * * @param symbol the symbol to query for * diff --git a/src/main/java/net/jacobpeterson/alpaca/websocket/AlpacaWebsocket.java b/src/main/java/net/jacobpeterson/alpaca/websocket/AlpacaWebsocket.java index 232d23f2..18387bcd 100644 --- a/src/main/java/net/jacobpeterson/alpaca/websocket/AlpacaWebsocket.java +++ b/src/main/java/net/jacobpeterson/alpaca/websocket/AlpacaWebsocket.java @@ -275,38 +275,18 @@ public void setListener(L listener) { this.listener = listener; } - /** - * Gets {@link #websocketStateListener}. - * - * @return the {@link WebsocketStateListener} - */ public WebsocketStateListener getWebsocketStateListener() { return websocketStateListener; } - /** - * Sets {@link #websocketStateListener}. - * - * @param websocketStateListener an {@link WebsocketStateListener} - */ public void setWebsocketStateListener(WebsocketStateListener websocketStateListener) { this.websocketStateListener = websocketStateListener; } - /** - * Gets {@link #automaticallyReconnect}. - * - * @return a boolean - */ public boolean doesAutomaticallyReconnect() { return automaticallyReconnect; } - /** - * Sets {@link #automaticallyReconnect}. - * - * @param automaticallyReconnect the boolean - */ public void setAutomaticallyReconnect(boolean automaticallyReconnect) { this.automaticallyReconnect = automaticallyReconnect; } diff --git a/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/MarketDataListener.java b/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/MarketDataListener.java index c1138209..09bf4266 100644 --- a/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/MarketDataListener.java +++ b/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/MarketDataListener.java @@ -1,7 +1,7 @@ package net.jacobpeterson.alpaca.websocket.marketdata; -import net.jacobpeterson.alpaca.model.endpoint.marketdata.realtime.MarketDataMessage; -import net.jacobpeterson.alpaca.model.endpoint.marketdata.realtime.enums.MarketDataMessageType; +import net.jacobpeterson.alpaca.model.endpoint.marketdata.stock.realtime.MarketDataMessage; +import net.jacobpeterson.alpaca.model.endpoint.marketdata.stock.realtime.enums.MarketDataMessageType; import net.jacobpeterson.alpaca.websocket.AlpacaWebsocketMessageListener; /** diff --git a/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/MarketDataWebsocket.java b/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/MarketDataWebsocket.java index 48953560..28cce551 100644 --- a/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/MarketDataWebsocket.java +++ b/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/MarketDataWebsocket.java @@ -5,14 +5,14 @@ import com.google.gson.JsonElement; import com.google.gson.JsonObject; import com.google.gson.JsonParser; -import net.jacobpeterson.alpaca.model.endpoint.marketdata.realtime.MarketDataMessage; -import net.jacobpeterson.alpaca.model.endpoint.marketdata.realtime.bar.BarMessage; -import net.jacobpeterson.alpaca.model.endpoint.marketdata.realtime.control.ErrorMessage; -import net.jacobpeterson.alpaca.model.endpoint.marketdata.realtime.control.SubscriptionsMessage; -import net.jacobpeterson.alpaca.model.endpoint.marketdata.realtime.control.SuccessMessage; -import net.jacobpeterson.alpaca.model.endpoint.marketdata.realtime.enums.MarketDataMessageType; -import net.jacobpeterson.alpaca.model.endpoint.marketdata.realtime.quote.QuoteMessage; -import net.jacobpeterson.alpaca.model.endpoint.marketdata.realtime.trade.TradeMessage; +import net.jacobpeterson.alpaca.model.endpoint.marketdata.stock.realtime.MarketDataMessage; +import net.jacobpeterson.alpaca.model.endpoint.marketdata.stock.realtime.bar.BarMessage; +import net.jacobpeterson.alpaca.model.endpoint.marketdata.stock.realtime.control.ErrorMessage; +import net.jacobpeterson.alpaca.model.endpoint.marketdata.stock.realtime.control.SubscriptionsMessage; +import net.jacobpeterson.alpaca.model.endpoint.marketdata.stock.realtime.control.SuccessMessage; +import net.jacobpeterson.alpaca.model.endpoint.marketdata.stock.realtime.enums.MarketDataMessageType; +import net.jacobpeterson.alpaca.model.endpoint.marketdata.stock.realtime.quote.QuoteMessage; +import net.jacobpeterson.alpaca.model.endpoint.marketdata.stock.realtime.trade.TradeMessage; import net.jacobpeterson.alpaca.model.properties.DataAPIType; import net.jacobpeterson.alpaca.websocket.AlpacaWebsocket; import okhttp3.HttpUrl; diff --git a/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/MarketDataWebsocketInterface.java b/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/MarketDataWebsocketInterface.java index 9ac90112..2eb52e63 100644 --- a/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/MarketDataWebsocketInterface.java +++ b/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/MarketDataWebsocketInterface.java @@ -1,10 +1,10 @@ package net.jacobpeterson.alpaca.websocket.marketdata; -import net.jacobpeterson.alpaca.model.endpoint.marketdata.realtime.MarketDataMessage; -import net.jacobpeterson.alpaca.model.endpoint.marketdata.realtime.control.ErrorMessage; -import net.jacobpeterson.alpaca.model.endpoint.marketdata.realtime.control.SubscriptionsMessage; -import net.jacobpeterson.alpaca.model.endpoint.marketdata.realtime.control.SuccessMessage; -import net.jacobpeterson.alpaca.model.endpoint.marketdata.realtime.enums.MarketDataMessageType; +import net.jacobpeterson.alpaca.model.endpoint.marketdata.stock.realtime.MarketDataMessage; +import net.jacobpeterson.alpaca.model.endpoint.marketdata.stock.realtime.control.ErrorMessage; +import net.jacobpeterson.alpaca.model.endpoint.marketdata.stock.realtime.control.SubscriptionsMessage; +import net.jacobpeterson.alpaca.model.endpoint.marketdata.stock.realtime.control.SuccessMessage; +import net.jacobpeterson.alpaca.model.endpoint.marketdata.stock.realtime.enums.MarketDataMessageType; import net.jacobpeterson.alpaca.websocket.AlpacaWebsocketInterface; import java.util.Collection; From 95b36855c9a95e783f37196984da04d7b56880a9 Mon Sep 17 00:00:00 2001 From: Jacob Peterson <14127217+Petersoj@users.noreply.github.com> Date: Tue, 21 Dec 2021 18:21:33 -0700 Subject: [PATCH 171/271] Refactor, abstract models and endpoints, implement realtime crypto --- README.md | 2 +- schema_json/endpoint/account/account.json | 4 +- .../endpoint/{asset => assets}/asset.json | 8 +-- .../{asset => assets}/enums/asset_class.json | 0 .../{asset => assets}/enums/asset_status.json | 0 .../{stock => common}/historical/bar/bar.json | 10 ++- .../bar}/enums/bar_time_period.json | 0 .../common/historical/quote/quote.json | 21 ++++++ .../common/historical/trade/trade.json | 21 ++++++ .../realtime/bar/bar_message.json | 9 +-- .../realtime/control/error_message.json | 4 +- .../control/subscriptions_message.json | 4 +- .../realtime/control/success_message.json | 4 +- .../enums/market_data_message_type.json | 2 +- .../realtime/market_data_message.json | 6 +- .../common/realtime/quote/quote_message.json | 24 +++++++ .../realtime/symbol_message.json | 4 +- .../common/realtime/trade/trade_message.json | 24 +++++++ .../enums/exchange.json | 2 +- .../trade => common}/enums/taker_side.json | 2 +- .../crypto/historical/bar/bar.json | 51 -------------- .../crypto/historical/bar/crypto_bar.json | 19 +++++ ...esponse.json => crypto_bars_response.json} | 6 +- .../crypto/historical/quote/crypto_quote.json | 24 +++++++ ...ponse.json => crypto_quotes_response.json} | 6 +- ...json => latest_crypto_quote_response.json} | 6 +- .../crypto/historical/quote/quote.json | 36 ---------- .../crypto/historical/trade/crypto_trade.json | 24 +++++++ ...ponse.json => crypto_trades_response.json} | 6 +- ...json => latest_crypto_trade_response.json} | 6 +- .../crypto/historical/trade/trade.json | 36 ---------- .../crypto/historical/xbbo/xbbo.json | 6 +- .../crypto/historical/xbbo/xbbo_response.json | 2 +- .../realtime/bar/crypto_bar_message.json | 29 ++++++++ .../realtime/quote/crypto_quote_message.json | 24 +++++++ .../realtime/trade/crypto_trade_message.json | 24 +++++++ .../historical/bar/enums/bar_adjustment.json | 2 +- .../stock/historical/bar/stock_bar.json | 14 ++++ ...response.json => stock_bars_response.json} | 6 +- ....json => latest_stock_quote_response.json} | 6 +- .../quote/{quote.json => stock_quote.json} | 20 ++---- ...sponse.json => stock_quotes_response.json} | 6 +- .../stock/historical/snapshot/snapshot.json | 22 +++--- ....json => latest_stock_trade_response.json} | 6 +- .../trade/{trade.json => stock_trade.json} | 20 ++---- ...sponse.json => stock_trades_response.json} | 6 +- .../stock/realtime/bar/stock_bar_message.json | 14 ++++ ..._message.json => stock_quote_message.json} | 19 +---- ..._message.json => stock_trade_message.json} | 19 +---- .../{order => orders}/cancelled_order.json | 4 +- .../enums/current_order_status.json | 0 .../{order => orders}/enums/order_class.json | 0 .../{order => orders}/enums/order_side.json | 0 .../{order => orders}/enums/order_status.json | 0 .../enums/order_time_in_force.json | 0 .../{order => orders}/enums/order_type.json | 0 .../endpoint/{order => orders}/order.json | 24 +++---- .../close_position_order.json | 4 +- .../{position => positions}/position.json | 0 .../streaming/trade/trade_update.json | 4 +- schema_json/endpoint/watchlist/watchlist.json | 2 +- .../net/jacobpeterson/alpaca/AlpacaAPI.java | 50 ++++++++----- .../{ => account}/AccountEndpoint.java | 3 +- .../AccountActivitiesEndpoint.java | 3 +- .../AccountConfigurationEndpoint.java | 3 +- .../endpoint/{ => assets}/AssetsEndpoint.java | 9 +-- .../{ => calendar}/CalendarEndpoint.java | 3 +- .../endpoint/{ => clock}/ClockEndpoint.java | 3 +- .../CryptoMarketDataEndpoint.java | 70 +++++++++---------- .../{ => stock}/StockMarketDataEndpoint.java | 60 ++++++++-------- .../endpoint/{ => orders}/OrdersEndpoint.java | 19 ++--- .../PortfolioHistoryEndpoint.java | 3 +- .../{ => positions}/PositionsEndpoint.java | 11 +-- .../{ => watchlist}/WatchlistEndpoint.java | 5 +- .../marketdata/MarketDataListener.java | 4 +- .../marketdata/MarketDataWebsocket.java | 70 ++++++++++++------- .../MarketDataWebsocketInterface.java | 10 +-- .../crypto/CryptoMarketDataWebsocket.java | 27 +++++++ .../stock/StockMarketDataWebsocket.java | 30 ++++++++ .../alpaca/test/live/AlpacaAPITest.java | 14 ++-- 80 files changed, 612 insertions(+), 439 deletions(-) rename schema_json/endpoint/{asset => assets}/asset.json (93%) rename schema_json/endpoint/{asset => assets}/enums/asset_class.json (100%) rename schema_json/endpoint/{asset => assets}/enums/asset_status.json (100%) rename schema_json/endpoint/market_data/{stock => common}/historical/bar/bar.json (83%) rename schema_json/endpoint/market_data/common/{ => historical/bar}/enums/bar_time_period.json (100%) create mode 100644 schema_json/endpoint/market_data/common/historical/quote/quote.json create mode 100644 schema_json/endpoint/market_data/common/historical/trade/trade.json rename schema_json/endpoint/market_data/{stock => common}/realtime/bar/bar_message.json (77%) rename schema_json/endpoint/market_data/{stock => common}/realtime/control/error_message.json (76%) rename schema_json/endpoint/market_data/{stock => common}/realtime/control/subscriptions_message.json (84%) rename schema_json/endpoint/market_data/{stock => common}/realtime/control/success_message.json (71%) rename schema_json/endpoint/market_data/{stock => common}/realtime/enums/market_data_message_type.json (83%) rename schema_json/endpoint/market_data/{stock => common}/realtime/market_data_message.json (59%) create mode 100644 schema_json/endpoint/market_data/common/realtime/quote/quote_message.json rename schema_json/endpoint/market_data/{stock => common}/realtime/symbol_message.json (71%) create mode 100644 schema_json/endpoint/market_data/common/realtime/trade/trade_message.json rename schema_json/endpoint/market_data/crypto/{historical => common}/enums/exchange.json (74%) rename schema_json/endpoint/market_data/crypto/{historical/trade => common}/enums/taker_side.json (69%) delete mode 100644 schema_json/endpoint/market_data/crypto/historical/bar/bar.json create mode 100644 schema_json/endpoint/market_data/crypto/historical/bar/crypto_bar.json rename schema_json/endpoint/market_data/crypto/historical/bar/{bars_response.json => crypto_bars_response.json} (76%) create mode 100644 schema_json/endpoint/market_data/crypto/historical/quote/crypto_quote.json rename schema_json/endpoint/market_data/crypto/historical/quote/{quotes_response.json => crypto_quotes_response.json} (75%) rename schema_json/endpoint/market_data/crypto/historical/quote/{latest_quote_response.json => latest_crypto_quote_response.json} (64%) delete mode 100644 schema_json/endpoint/market_data/crypto/historical/quote/quote.json create mode 100644 schema_json/endpoint/market_data/crypto/historical/trade/crypto_trade.json rename schema_json/endpoint/market_data/crypto/historical/trade/{trades_response.json => crypto_trades_response.json} (75%) rename schema_json/endpoint/market_data/crypto/historical/trade/{latest_trade_response.json => latest_crypto_trade_response.json} (64%) delete mode 100644 schema_json/endpoint/market_data/crypto/historical/trade/trade.json create mode 100644 schema_json/endpoint/market_data/crypto/realtime/bar/crypto_bar_message.json create mode 100644 schema_json/endpoint/market_data/crypto/realtime/quote/crypto_quote_message.json create mode 100644 schema_json/endpoint/market_data/crypto/realtime/trade/crypto_trade_message.json create mode 100644 schema_json/endpoint/market_data/stock/historical/bar/stock_bar.json rename schema_json/endpoint/market_data/stock/historical/bar/{bars_response.json => stock_bars_response.json} (85%) rename schema_json/endpoint/market_data/stock/historical/quote/{latest_quote_response.json => latest_stock_quote_response.json} (76%) rename schema_json/endpoint/market_data/stock/historical/quote/{quote.json => stock_quote.json} (66%) rename schema_json/endpoint/market_data/stock/historical/quote/{quotes_response.json => stock_quotes_response.json} (84%) rename schema_json/endpoint/market_data/stock/historical/trade/{latest_trade_response.json => latest_stock_trade_response.json} (76%) rename schema_json/endpoint/market_data/stock/historical/trade/{trade.json => stock_trade.json} (63%) rename schema_json/endpoint/market_data/stock/historical/trade/{trades_response.json => stock_trades_response.json} (84%) create mode 100644 schema_json/endpoint/market_data/stock/realtime/bar/stock_bar_message.json rename schema_json/endpoint/market_data/stock/realtime/quote/{quote_message.json => stock_quote_message.json} (65%) rename schema_json/endpoint/market_data/stock/realtime/trade/{trade_message.json => stock_trade_message.json} (59%) rename schema_json/endpoint/{order => orders}/cancelled_order.json (92%) rename schema_json/endpoint/{order => orders}/enums/current_order_status.json (100%) rename schema_json/endpoint/{order => orders}/enums/order_class.json (100%) rename schema_json/endpoint/{order => orders}/enums/order_side.json (100%) rename schema_json/endpoint/{order => orders}/enums/order_status.json (100%) rename schema_json/endpoint/{order => orders}/enums/order_time_in_force.json (100%) rename schema_json/endpoint/{order => orders}/enums/order_type.json (100%) rename schema_json/endpoint/{order => orders}/order.json (89%) rename schema_json/endpoint/{position => positions}/close_position_order.json (91%) rename schema_json/endpoint/{position => positions}/position.json (100%) rename src/main/java/net/jacobpeterson/alpaca/rest/endpoint/{ => account}/AccountEndpoint.java (90%) rename src/main/java/net/jacobpeterson/alpaca/rest/endpoint/{ => accountactivities}/AccountActivitiesEndpoint.java (97%) rename src/main/java/net/jacobpeterson/alpaca/rest/endpoint/{ => accountconfiguration}/AccountConfigurationEndpoint.java (94%) rename src/main/java/net/jacobpeterson/alpaca/rest/endpoint/{ => assets}/AssetsEndpoint.java (88%) rename src/main/java/net/jacobpeterson/alpaca/rest/endpoint/{ => calendar}/CalendarEndpoint.java (94%) rename src/main/java/net/jacobpeterson/alpaca/rest/endpoint/{ => clock}/ClockEndpoint.java (91%) rename src/main/java/net/jacobpeterson/alpaca/rest/endpoint/marketdata/{ => crypto}/CryptoMarketDataEndpoint.java (83%) rename src/main/java/net/jacobpeterson/alpaca/rest/endpoint/marketdata/{ => stock}/StockMarketDataEndpoint.java (84%) rename src/main/java/net/jacobpeterson/alpaca/rest/endpoint/{ => orders}/OrdersEndpoint.java (98%) rename src/main/java/net/jacobpeterson/alpaca/rest/endpoint/{ => portfoliohistory}/PortfolioHistoryEndpoint.java (97%) rename src/main/java/net/jacobpeterson/alpaca/rest/endpoint/{ => positions}/PositionsEndpoint.java (92%) rename src/main/java/net/jacobpeterson/alpaca/rest/endpoint/{ => watchlist}/WatchlistEndpoint.java (97%) create mode 100644 src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/crypto/CryptoMarketDataWebsocket.java create mode 100644 src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/stock/StockMarketDataWebsocket.java diff --git a/README.md b/README.md index a0947ec0..ffbea854 100644 --- a/README.md +++ b/README.md @@ -101,7 +101,7 @@ try { ``` ## [`CryptoMarketDataEndpoint`](src/main/java/net/jacobpeterson/alpaca/rest/endpoint/marketdata/CryptoMarketDataEndpoint.java) -Alpaca provides cryptocurrency data from multiple venues/exchanges, namely: Coinbase, ErisX, and FTX. Note that it does not route orders to all venues. +Alpaca provides cryptocurrency data from multiple venues/exchanges, namely: Coinbase, ErisX, and FTX. Example usage: ```java diff --git a/schema_json/endpoint/account/account.json b/schema_json/endpoint/account/account.json index a6e6d506..7e558fe2 100644 --- a/schema_json/endpoint/account/account.json +++ b/schema_json/endpoint/account/account.json @@ -56,11 +56,11 @@ }, "long_market_value": { "existingJavaType": "java.lang.String", - "title": "Real-time MtM value of all long positions held in the account." + "title": "Realtime MtM value of all long positions held in the account." }, "short_market_value": { "existingJavaType": "java.lang.String", - "title": "Real-time MtM value of all short positions held in the account." + "title": "Realtime MtM value of all short positions held in the account." }, "equity": { "existingJavaType": "java.lang.String", diff --git a/schema_json/endpoint/asset/asset.json b/schema_json/endpoint/assets/asset.json similarity index 93% rename from schema_json/endpoint/asset/asset.json rename to schema_json/endpoint/assets/asset.json index f5ab8b9c..fa65e096 100644 --- a/schema_json/endpoint/asset/asset.json +++ b/schema_json/endpoint/assets/asset.json @@ -7,9 +7,9 @@ "title": "Asset ID." }, "class": { - "existingJavaType": "net.jacobpeterson.alpaca.model.endpoint.asset.enums.AssetClass", + "existingJavaType": "net.jacobpeterson.alpaca.model.endpoint.assets.enums.AssetClass", "javaName": "assetClass", - "title": "The {@link net.jacobpeterson.alpaca.model.endpoint.asset.enums.AssetClass}." + "title": "The {@link net.jacobpeterson.alpaca.model.endpoint.assets.enums.AssetClass}." }, "exchange": { "existingJavaType": "java.lang.String", @@ -24,8 +24,8 @@ "title": "The official name of the asset." }, "status": { - "existingJavaType": "net.jacobpeterson.alpaca.model.endpoint.asset.enums.AssetStatus", - "title": "The {@link net.jacobpeterson.alpaca.model.endpoint.asset.enums.AssetStatus}." + "existingJavaType": "net.jacobpeterson.alpaca.model.endpoint.assets.enums.AssetStatus", + "title": "The {@link net.jacobpeterson.alpaca.model.endpoint.assets.enums.AssetStatus}." }, "tradable": { "existingJavaType": "java.lang.Boolean", diff --git a/schema_json/endpoint/asset/enums/asset_class.json b/schema_json/endpoint/assets/enums/asset_class.json similarity index 100% rename from schema_json/endpoint/asset/enums/asset_class.json rename to schema_json/endpoint/assets/enums/asset_class.json diff --git a/schema_json/endpoint/asset/enums/asset_status.json b/schema_json/endpoint/assets/enums/asset_status.json similarity index 100% rename from schema_json/endpoint/asset/enums/asset_status.json rename to schema_json/endpoint/assets/enums/asset_status.json diff --git a/schema_json/endpoint/market_data/stock/historical/bar/bar.json b/schema_json/endpoint/market_data/common/historical/bar/bar.json similarity index 83% rename from schema_json/endpoint/market_data/stock/historical/bar/bar.json rename to schema_json/endpoint/market_data/common/historical/bar/bar.json index 55423fde..b0284b57 100644 --- a/schema_json/endpoint/market_data/stock/historical/bar/bar.json +++ b/schema_json/endpoint/market_data/common/historical/bar/bar.json @@ -1,6 +1,9 @@ { "type": "object", - "title": "See Historical Data.", + "extends": { + "existingJavaType": "net.jacobpeterson.alpaca.model.endpoint.marketdata.common.realtime.quote.QuoteMessage" + }, + "title": "See Market Data.", "properties": { "t": { "existingJavaType": "java.time.ZonedDateTime", @@ -27,11 +30,6 @@ "javaName": "close", "title": "Close price." }, - "v": { - "existingJavaType": "java.lang.Long", - "javaName": "volume", - "title": "Volume." - }, "n": { "existingJavaType": "java.lang.Long", "javaName": "tradeCount", diff --git a/schema_json/endpoint/market_data/common/enums/bar_time_period.json b/schema_json/endpoint/market_data/common/historical/bar/enums/bar_time_period.json similarity index 100% rename from schema_json/endpoint/market_data/common/enums/bar_time_period.json rename to schema_json/endpoint/market_data/common/historical/bar/enums/bar_time_period.json diff --git a/schema_json/endpoint/market_data/common/historical/quote/quote.json b/schema_json/endpoint/market_data/common/historical/quote/quote.json new file mode 100644 index 00000000..ed75e88f --- /dev/null +++ b/schema_json/endpoint/market_data/common/historical/quote/quote.json @@ -0,0 +1,21 @@ +{ + "type": "object", + "title": "See Market Data.", + "properties": { + "t": { + "existingJavaType": "java.time.ZonedDateTime", + "javaName": "timestamp", + "title": "Timestamp with nanosecond precision." + }, + "ap": { + "existingJavaType": "java.lang.Double", + "javaName": "askPrice", + "title": "Ask price." + }, + "bp": { + "existingJavaType": "java.lang.Double", + "javaName": "bidPrice", + "title": "Bid price." + } + } +} diff --git a/schema_json/endpoint/market_data/common/historical/trade/trade.json b/schema_json/endpoint/market_data/common/historical/trade/trade.json new file mode 100644 index 00000000..1cacafa5 --- /dev/null +++ b/schema_json/endpoint/market_data/common/historical/trade/trade.json @@ -0,0 +1,21 @@ +{ + "type": "object", + "title": "See Market Data.", + "properties": { + "t": { + "existingJavaType": "java.time.ZonedDateTime", + "javaName": "timestamp", + "title": "Timestamp with nanosecond precision." + }, + "p": { + "existingJavaType": "java.lang.Double", + "javaName": "price", + "title": "Trade price." + }, + "i": { + "existingJavaType": "java.lang.Long", + "javaName": "tradeID", + "title": "Trade ID." + } + } +} diff --git a/schema_json/endpoint/market_data/stock/realtime/bar/bar_message.json b/schema_json/endpoint/market_data/common/realtime/bar/bar_message.json similarity index 77% rename from schema_json/endpoint/market_data/stock/realtime/bar/bar_message.json rename to schema_json/endpoint/market_data/common/realtime/bar/bar_message.json index 7abf464d..fcf5f6c6 100644 --- a/schema_json/endpoint/market_data/stock/realtime/bar/bar_message.json +++ b/schema_json/endpoint/market_data/common/realtime/bar/bar_message.json @@ -1,9 +1,9 @@ { "type": "object", "extends": { - "existingJavaType": "net.jacobpeterson.alpaca.model.endpoint.marketdata.stock.realtime.SymbolMessage" + "existingJavaType": "net.jacobpeterson.alpaca.model.endpoint.marketdata.common.realtime.SymbolMessage" }, - "title": "See Real-time Data.", + "title": "See Market Data.", "properties": { "o": { "existingJavaType": "java.lang.Double", @@ -25,11 +25,6 @@ "javaName": "close", "title": "Close price." }, - "v": { - "existingJavaType": "java.lang.Long", - "javaName": "volume", - "title": "Volume." - }, "t": { "existingJavaType": "java.time.ZonedDateTime", "javaName": "timestamp", diff --git a/schema_json/endpoint/market_data/stock/realtime/control/error_message.json b/schema_json/endpoint/market_data/common/realtime/control/error_message.json similarity index 76% rename from schema_json/endpoint/market_data/stock/realtime/control/error_message.json rename to schema_json/endpoint/market_data/common/realtime/control/error_message.json index 1ef275ab..39b9e7f2 100644 --- a/schema_json/endpoint/market_data/stock/realtime/control/error_message.json +++ b/schema_json/endpoint/market_data/common/realtime/control/error_message.json @@ -1,9 +1,9 @@ { "type": "object", "extends": { - "existingJavaType": "net.jacobpeterson.alpaca.model.endpoint.marketdata.stock.realtime.MarketDataMessage" + "existingJavaType": "net.jacobpeterson.alpaca.model.endpoint.marketdata.common.realtime.MarketDataMessage" }, - "title": "See Real-time Data.", + "title": "See Market Data.", "properties": { "code": { "existingJavaType": "java.lang.Integer", diff --git a/schema_json/endpoint/market_data/stock/realtime/control/subscriptions_message.json b/schema_json/endpoint/market_data/common/realtime/control/subscriptions_message.json similarity index 84% rename from schema_json/endpoint/market_data/stock/realtime/control/subscriptions_message.json rename to schema_json/endpoint/market_data/common/realtime/control/subscriptions_message.json index 76bfc479..c302083a 100644 --- a/schema_json/endpoint/market_data/stock/realtime/control/subscriptions_message.json +++ b/schema_json/endpoint/market_data/common/realtime/control/subscriptions_message.json @@ -1,9 +1,9 @@ { "type": "object", "extends": { - "existingJavaType": "net.jacobpeterson.alpaca.model.endpoint.marketdata.stock.realtime.MarketDataMessage" + "existingJavaType": "net.jacobpeterson.alpaca.model.endpoint.marketdata.common.realtime.MarketDataMessage" }, - "title": "See Real-time Data.", + "title": "See Market Data.", "properties": { "trades": { "existingJavaType": "java.util.ArrayList", diff --git a/schema_json/endpoint/market_data/stock/realtime/control/success_message.json b/schema_json/endpoint/market_data/common/realtime/control/success_message.json similarity index 71% rename from schema_json/endpoint/market_data/stock/realtime/control/success_message.json rename to schema_json/endpoint/market_data/common/realtime/control/success_message.json index d7225e02..2edd335b 100644 --- a/schema_json/endpoint/market_data/stock/realtime/control/success_message.json +++ b/schema_json/endpoint/market_data/common/realtime/control/success_message.json @@ -1,9 +1,9 @@ { "type": "object", "extends": { - "existingJavaType": "net.jacobpeterson.alpaca.model.endpoint.marketdata.stock.realtime.MarketDataMessage" + "existingJavaType": "net.jacobpeterson.alpaca.model.endpoint.marketdata.common.realtime.MarketDataMessage" }, - "title": "See Real-time Data.", + "title": "See Market Data.", "properties": { "msg": { "existingJavaType": "java.lang.String", diff --git a/schema_json/endpoint/market_data/stock/realtime/enums/market_data_message_type.json b/schema_json/endpoint/market_data/common/realtime/enums/market_data_message_type.json similarity index 83% rename from schema_json/endpoint/market_data/stock/realtime/enums/market_data_message_type.json rename to schema_json/endpoint/market_data/common/realtime/enums/market_data_message_type.json index 40368ec9..f55a5190 100644 --- a/schema_json/endpoint/market_data/stock/realtime/enums/market_data_message_type.json +++ b/schema_json/endpoint/market_data/common/realtime/enums/market_data_message_type.json @@ -1,6 +1,6 @@ { "type": "string", - "title": "See Real-time Data.", + "title": "See Market Data.", "enum": [ "success", "error", diff --git a/schema_json/endpoint/market_data/stock/realtime/market_data_message.json b/schema_json/endpoint/market_data/common/realtime/market_data_message.json similarity index 59% rename from schema_json/endpoint/market_data/stock/realtime/market_data_message.json rename to schema_json/endpoint/market_data/common/realtime/market_data_message.json index 5ee64bef..d41cd481 100644 --- a/schema_json/endpoint/market_data/stock/realtime/market_data_message.json +++ b/schema_json/endpoint/market_data/common/realtime/market_data_message.json @@ -1,11 +1,11 @@ { "type": "object", - "title": "See Real-time Data.", + "title": "See Market Data.", "properties": { "T": { - "existingJavaType": "net.jacobpeterson.alpaca.model.endpoint.marketdata.stock.realtime.enums.MarketDataMessageType", + "existingJavaType": "net.jacobpeterson.alpaca.model.endpoint.marketdata.common.realtime.enums.MarketDataMessageType", "javaName": "messageType", - "title": "The {@link net.jacobpeterson.alpaca.model.endpoint.marketdata.stock.realtime.enums.MarketDataMessageType}." + "title": "The {@link net.jacobpeterson.alpaca.model.endpoint.marketdata.common.realtime.enums.MarketDataMessageType}." } } } diff --git a/schema_json/endpoint/market_data/common/realtime/quote/quote_message.json b/schema_json/endpoint/market_data/common/realtime/quote/quote_message.json new file mode 100644 index 00000000..d6bdaabc --- /dev/null +++ b/schema_json/endpoint/market_data/common/realtime/quote/quote_message.json @@ -0,0 +1,24 @@ +{ + "type": "object", + "extends": { + "existingJavaType": "net.jacobpeterson.alpaca.model.endpoint.marketdata.common.realtime.SymbolMessage" + }, + "title": "See Market Data.", + "properties": { + "ap": { + "existingJavaType": "java.lang.Double", + "javaName": "askPrice", + "title": "Ask price." + }, + "bp": { + "existingJavaType": "java.lang.Double", + "javaName": "bidPrice", + "title": "Bid price." + }, + "t": { + "existingJavaType": "java.time.ZonedDateTime", + "javaName": "timestamp", + "title": "Timestamp with nanosecond precision." + } + } +} diff --git a/schema_json/endpoint/market_data/stock/realtime/symbol_message.json b/schema_json/endpoint/market_data/common/realtime/symbol_message.json similarity index 71% rename from schema_json/endpoint/market_data/stock/realtime/symbol_message.json rename to schema_json/endpoint/market_data/common/realtime/symbol_message.json index acc6327c..a6f40f69 100644 --- a/schema_json/endpoint/market_data/stock/realtime/symbol_message.json +++ b/schema_json/endpoint/market_data/common/realtime/symbol_message.json @@ -1,9 +1,9 @@ { "type": "object", "extends": { - "existingJavaType": "net.jacobpeterson.alpaca.model.endpoint.marketdata.stock.realtime.MarketDataMessage" + "existingJavaType": "net.jacobpeterson.alpaca.model.endpoint.marketdata.common.realtime.MarketDataMessage" }, - "title": "See Real-time Data.", + "title": "See Market Data.", "properties": { "S": { "existingJavaType": "java.lang.String", diff --git a/schema_json/endpoint/market_data/common/realtime/trade/trade_message.json b/schema_json/endpoint/market_data/common/realtime/trade/trade_message.json new file mode 100644 index 00000000..ca01450f --- /dev/null +++ b/schema_json/endpoint/market_data/common/realtime/trade/trade_message.json @@ -0,0 +1,24 @@ +{ + "type": "object", + "extends": { + "existingJavaType": "net.jacobpeterson.alpaca.model.endpoint.marketdata.common.realtime.SymbolMessage" + }, + "title": "See Market Data.", + "properties": { + "i": { + "existingJavaType": "java.lang.Integer", + "javaName": "tradeID", + "title": "Trade ID." + }, + "p": { + "existingJavaType": "java.lang.Double", + "javaName": "price", + "title": "Trade price." + }, + "t": { + "existingJavaType": "java.time.ZonedDateTime", + "javaName": "timestamp", + "title": "Timestamp with nanosecond precision." + } + } +} diff --git a/schema_json/endpoint/market_data/crypto/historical/enums/exchange.json b/schema_json/endpoint/market_data/crypto/common/enums/exchange.json similarity index 74% rename from schema_json/endpoint/market_data/crypto/historical/enums/exchange.json rename to schema_json/endpoint/market_data/crypto/common/enums/exchange.json index 1ea41bd6..ff9d94e5 100644 --- a/schema_json/endpoint/market_data/crypto/historical/enums/exchange.json +++ b/schema_json/endpoint/market_data/crypto/common/enums/exchange.json @@ -1,6 +1,6 @@ { "type": "string", - "title": "See Crypto Historical Data.", + "title": "See Crypto Data.", "enum": [ "ERSX", "FTX", diff --git a/schema_json/endpoint/market_data/crypto/historical/trade/enums/taker_side.json b/schema_json/endpoint/market_data/crypto/common/enums/taker_side.json similarity index 69% rename from schema_json/endpoint/market_data/crypto/historical/trade/enums/taker_side.json rename to schema_json/endpoint/market_data/crypto/common/enums/taker_side.json index 9604bc54..8c8e4adf 100644 --- a/schema_json/endpoint/market_data/crypto/historical/trade/enums/taker_side.json +++ b/schema_json/endpoint/market_data/crypto/common/enums/taker_side.json @@ -1,6 +1,6 @@ { "type": "string", - "title": "See Crypto Historical Data.", + "title": "See Crypto Data.", "enum": [ "B", "S" diff --git a/schema_json/endpoint/market_data/crypto/historical/bar/bar.json b/schema_json/endpoint/market_data/crypto/historical/bar/bar.json deleted file mode 100644 index c677ba92..00000000 --- a/schema_json/endpoint/market_data/crypto/historical/bar/bar.json +++ /dev/null @@ -1,51 +0,0 @@ -{ - "type": "object", - "title": "See Crypto Historical Data.", - "properties": { - "t": { - "existingJavaType": "java.time.ZonedDateTime", - "javaName": "timestamp", - "title": "Timestamp with nanosecond precision." - }, - "x": { - "existingJavaType": "net.jacobpeterson.alpaca.model.endpoint.marketdata.crypto.historical.enums.Exchange", - "javaName": "exchange", - "title": "Exchange." - }, - "o": { - "existingJavaType": "java.lang.Double", - "javaName": "open", - "title": "Open price." - }, - "h": { - "existingJavaType": "java.lang.Double", - "javaName": "high", - "title": "High price." - }, - "l": { - "existingJavaType": "java.lang.Double", - "javaName": "low", - "title": "Low price." - }, - "c": { - "existingJavaType": "java.lang.Double", - "javaName": "close", - "title": "Close price." - }, - "v": { - "existingJavaType": "java.lang.Double", - "javaName": "volume", - "title": "Volume." - }, - "n": { - "existingJavaType": "java.lang.Long", - "javaName": "tradeCount", - "title": "Trade count." - }, - "vw": { - "existingJavaType": "java.lang.Double", - "javaName": "vwap", - "title": "VWAP (Volume Weighted Average Price)." - } - } -} diff --git a/schema_json/endpoint/market_data/crypto/historical/bar/crypto_bar.json b/schema_json/endpoint/market_data/crypto/historical/bar/crypto_bar.json new file mode 100644 index 00000000..e23c3ec6 --- /dev/null +++ b/schema_json/endpoint/market_data/crypto/historical/bar/crypto_bar.json @@ -0,0 +1,19 @@ +{ + "type": "object", + "extends": { + "existingJavaType": "net.jacobpeterson.alpaca.model.endpoint.marketdata.common.historical.bar.Bar" + }, + "title": "See Historical Crypto Data.", + "properties": { + "x": { + "existingJavaType": "net.jacobpeterson.alpaca.model.endpoint.marketdata.crypto.common.enums.Exchange", + "javaName": "exchange", + "title": "Exchange." + }, + "v": { + "existingJavaType": "java.lang.Double", + "javaName": "volume", + "title": "Volume." + } + } +} diff --git a/schema_json/endpoint/market_data/crypto/historical/bar/bars_response.json b/schema_json/endpoint/market_data/crypto/historical/bar/crypto_bars_response.json similarity index 76% rename from schema_json/endpoint/market_data/crypto/historical/bar/bars_response.json rename to schema_json/endpoint/market_data/crypto/historical/bar/crypto_bars_response.json index 21f5cddb..677e6633 100644 --- a/schema_json/endpoint/market_data/crypto/historical/bar/bars_response.json +++ b/schema_json/endpoint/market_data/crypto/historical/bar/crypto_bars_response.json @@ -1,10 +1,10 @@ { "type": "object", - "title": "See Historical Data.", + "title": "See Historical Crypto Data.", "properties": { "bars": { - "existingJavaType": "java.util.ArrayList", - "title": "The {@link java.util.ArrayList} of {@link net.jacobpeterson.alpaca.model.endpoint.marketdata.crypto.historical.bar.Bar}s." + "existingJavaType": "java.util.ArrayList", + "title": "The {@link java.util.ArrayList} of {@link net.jacobpeterson.alpaca.model.endpoint.marketdata.crypto.historical.bar.CryptoBar}s." }, "symbol": { "existingJavaType": "java.lang.String", diff --git a/schema_json/endpoint/market_data/crypto/historical/quote/crypto_quote.json b/schema_json/endpoint/market_data/crypto/historical/quote/crypto_quote.json new file mode 100644 index 00000000..099df001 --- /dev/null +++ b/schema_json/endpoint/market_data/crypto/historical/quote/crypto_quote.json @@ -0,0 +1,24 @@ +{ + "type": "object", + "extends": { + "existingJavaType": "net.jacobpeterson.alpaca.model.endpoint.marketdata.common.historical.quote.Quote" + }, + "title": "See Historical Crypto Data.", + "properties": { + "x": { + "existingJavaType": "net.jacobpeterson.alpaca.model.endpoint.marketdata.crypto.common.enums.Exchange", + "javaName": "exchange", + "title": "Exchange." + }, + "as": { + "existingJavaType": "java.lang.Double", + "javaName": "askSize", + "title": "Ask size." + }, + "bs": { + "existingJavaType": "java.lang.Double", + "javaName": "bidSize", + "title": "Bid size." + } + } +} diff --git a/schema_json/endpoint/market_data/crypto/historical/quote/quotes_response.json b/schema_json/endpoint/market_data/crypto/historical/quote/crypto_quotes_response.json similarity index 75% rename from schema_json/endpoint/market_data/crypto/historical/quote/quotes_response.json rename to schema_json/endpoint/market_data/crypto/historical/quote/crypto_quotes_response.json index 212635c3..e8a67bfd 100644 --- a/schema_json/endpoint/market_data/crypto/historical/quote/quotes_response.json +++ b/schema_json/endpoint/market_data/crypto/historical/quote/crypto_quotes_response.json @@ -1,10 +1,10 @@ { "type": "object", - "title": "See Crypto Historical Data.", + "title": "See Historical Crypto Data.", "properties": { "quotes": { - "existingJavaType": "java.util.ArrayList", - "title": "The {@link java.util.ArrayList} of {@link net.jacobpeterson.alpaca.model.endpoint.marketdata.crypto.historical.quote.Quote}s." + "existingJavaType": "java.util.ArrayList", + "title": "The {@link java.util.ArrayList} of {@link net.jacobpeterson.alpaca.model.endpoint.marketdata.crypto.historical.quote.CryptoQuote}s." }, "symbol": { "existingJavaType": "java.lang.String", diff --git a/schema_json/endpoint/market_data/crypto/historical/quote/latest_quote_response.json b/schema_json/endpoint/market_data/crypto/historical/quote/latest_crypto_quote_response.json similarity index 64% rename from schema_json/endpoint/market_data/crypto/historical/quote/latest_quote_response.json rename to schema_json/endpoint/market_data/crypto/historical/quote/latest_crypto_quote_response.json index 044d252f..64bf1d75 100644 --- a/schema_json/endpoint/market_data/crypto/historical/quote/latest_quote_response.json +++ b/schema_json/endpoint/market_data/crypto/historical/quote/latest_crypto_quote_response.json @@ -1,14 +1,14 @@ { "type": "object", - "title": "See Crypto Historical Data.", + "title": "See Historical Crypto Data.", "properties": { "symbol": { "existingJavaType": "java.lang.String", "title": "Symbol that was queried." }, "quote": { - "existingJavaType": "net.jacobpeterson.alpaca.model.endpoint.marketdata.crypto.historical.quote.Quote", - "title": "The latest {@link net.jacobpeterson.alpaca.model.endpoint.marketdata.crypto.historical.quote.Quote}." + "existingJavaType": "net.jacobpeterson.alpaca.model.endpoint.marketdata.crypto.historical.quote.CryptoQuote", + "title": "The latest {@link net.jacobpeterson.alpaca.model.endpoint.marketdata.crypto.historical.quote.CryptoQuote}." } } } diff --git a/schema_json/endpoint/market_data/crypto/historical/quote/quote.json b/schema_json/endpoint/market_data/crypto/historical/quote/quote.json deleted file mode 100644 index ecca5a55..00000000 --- a/schema_json/endpoint/market_data/crypto/historical/quote/quote.json +++ /dev/null @@ -1,36 +0,0 @@ -{ - "type": "object", - "title": "See Crypto Historical Data.", - "properties": { - "t": { - "existingJavaType": "java.time.ZonedDateTime", - "javaName": "timestamp", - "title": "Timestamp with nanosecond precision." - }, - "x": { - "existingJavaType": "net.jacobpeterson.alpaca.model.endpoint.marketdata.crypto.historical.enums.Exchange", - "javaName": "exchange", - "title": "Exchange." - }, - "ap": { - "existingJavaType": "java.lang.Double", - "javaName": "askPrice", - "title": "Ask price." - }, - "as": { - "existingJavaType": "java.lang.Double", - "javaName": "askSize", - "title": "Ask size." - }, - "bp": { - "existingJavaType": "java.lang.Double", - "javaName": "bidPrice", - "title": "Bid price." - }, - "bs": { - "existingJavaType": "java.lang.Double", - "javaName": "bidSize", - "title": "Bid size." - } - } -} diff --git a/schema_json/endpoint/market_data/crypto/historical/trade/crypto_trade.json b/schema_json/endpoint/market_data/crypto/historical/trade/crypto_trade.json new file mode 100644 index 00000000..0b0c1b53 --- /dev/null +++ b/schema_json/endpoint/market_data/crypto/historical/trade/crypto_trade.json @@ -0,0 +1,24 @@ +{ + "type": "object", + "extends": { + "existingJavaType": "net.jacobpeterson.alpaca.model.endpoint.marketdata.common.historical.trade.Trade" + }, + "title": "See Historical Crypto Data.", + "properties": { + "x": { + "existingJavaType": "net.jacobpeterson.alpaca.model.endpoint.marketdata.crypto.common.enums.Exchange", + "javaName": "exchange", + "title": "Exchange where the trade happened." + }, + "s": { + "existingJavaType": "java.lang.Double", + "javaName": "size", + "title": "Trade size." + }, + "tks": { + "existingJavaType": "net.jacobpeterson.alpaca.model.endpoint.marketdata.crypto.common.enums.TakerSide", + "javaName": "takerSide", + "title": "The {@link net.jacobpeterson.alpaca.model.endpoint.marketdata.crypto.common.enums.TakerSide} of the trade." + } + } +} diff --git a/schema_json/endpoint/market_data/crypto/historical/trade/trades_response.json b/schema_json/endpoint/market_data/crypto/historical/trade/crypto_trades_response.json similarity index 75% rename from schema_json/endpoint/market_data/crypto/historical/trade/trades_response.json rename to schema_json/endpoint/market_data/crypto/historical/trade/crypto_trades_response.json index 68a770a5..a512c775 100644 --- a/schema_json/endpoint/market_data/crypto/historical/trade/trades_response.json +++ b/schema_json/endpoint/market_data/crypto/historical/trade/crypto_trades_response.json @@ -1,10 +1,10 @@ { "type": "object", - "title": "See Crypto Historical Data.", + "title": "See Historical Crypto Data.", "properties": { "trades": { - "existingJavaType": "java.util.ArrayList", - "title": "The {@link java.util.ArrayList} of {@link net.jacobpeterson.alpaca.model.endpoint.marketdata.crypto.historical.trade.Trade}s." + "existingJavaType": "java.util.ArrayList", + "title": "The {@link java.util.ArrayList} of {@link net.jacobpeterson.alpaca.model.endpoint.marketdata.crypto.historical.trade.CryptoTrade}s." }, "symbol": { "existingJavaType": "java.lang.String", diff --git a/schema_json/endpoint/market_data/crypto/historical/trade/latest_trade_response.json b/schema_json/endpoint/market_data/crypto/historical/trade/latest_crypto_trade_response.json similarity index 64% rename from schema_json/endpoint/market_data/crypto/historical/trade/latest_trade_response.json rename to schema_json/endpoint/market_data/crypto/historical/trade/latest_crypto_trade_response.json index 089f9528..512c6a18 100644 --- a/schema_json/endpoint/market_data/crypto/historical/trade/latest_trade_response.json +++ b/schema_json/endpoint/market_data/crypto/historical/trade/latest_crypto_trade_response.json @@ -1,14 +1,14 @@ { "type": "object", - "title": "See Crypto Historical Data.", + "title": "See Historical Crypto Data.", "properties": { "symbol": { "existingJavaType": "java.lang.String", "title": "Symbol that was queried." }, "trade": { - "existingJavaType": "net.jacobpeterson.alpaca.model.endpoint.marketdata.crypto.historical.trade.Trade", - "title": "The latest {@link net.jacobpeterson.alpaca.model.endpoint.marketdata.crypto.historical.trade.Trade}." + "existingJavaType": "net.jacobpeterson.alpaca.model.endpoint.marketdata.crypto.historical.trade.CryptoTrade", + "title": "The latest {@link net.jacobpeterson.alpaca.model.endpoint.marketdata.crypto.historical.trade.CryptoTrade}." } } } diff --git a/schema_json/endpoint/market_data/crypto/historical/trade/trade.json b/schema_json/endpoint/market_data/crypto/historical/trade/trade.json deleted file mode 100644 index 56fef69b..00000000 --- a/schema_json/endpoint/market_data/crypto/historical/trade/trade.json +++ /dev/null @@ -1,36 +0,0 @@ -{ - "type": "object", - "title": "See Crypto Historical Data.", - "properties": { - "t": { - "existingJavaType": "java.time.ZonedDateTime", - "javaName": "timestamp", - "title": "Timestamp with nanosecond precision." - }, - "x": { - "existingJavaType": "net.jacobpeterson.alpaca.model.endpoint.marketdata.crypto.historical.enums.Exchange", - "javaName": "exchange", - "title": "Exchange where the trade happened." - }, - "p": { - "existingJavaType": "java.lang.Double", - "javaName": "price", - "title": "Trade price." - }, - "s": { - "existingJavaType": "java.lang.Double", - "javaName": "size", - "title": "Trade size." - }, - "tks": { - "existingJavaType": "net.jacobpeterson.alpaca.model.endpoint.marketdata.crypto.historical.trade.enums.TakerSide", - "javaName": "takerSide", - "title": "The {@link net.jacobpeterson.alpaca.model.endpoint.marketdata.crypto.historical.trade.enums.TakerSide} of the trade." - }, - "i": { - "existingJavaType": "java.lang.Long", - "javaName": "tradeID", - "title": "Trade ID." - } - } -} diff --git a/schema_json/endpoint/market_data/crypto/historical/xbbo/xbbo.json b/schema_json/endpoint/market_data/crypto/historical/xbbo/xbbo.json index 9646a1ee..d279722f 100644 --- a/schema_json/endpoint/market_data/crypto/historical/xbbo/xbbo.json +++ b/schema_json/endpoint/market_data/crypto/historical/xbbo/xbbo.json @@ -1,6 +1,6 @@ { "type": "object", - "title": "See Crypto Historical Data.", + "title": "See Historical Crypto Data.", "properties": { "t": { "existingJavaType": "java.time.ZonedDateTime", @@ -8,7 +8,7 @@ "title": "Timestamp with nanosecond precision." }, "ax": { - "existingJavaType": "net.jacobpeterson.alpaca.model.endpoint.marketdata.crypto.historical.enums.Exchange", + "existingJavaType": "net.jacobpeterson.alpaca.model.endpoint.marketdata.crypto.common.enums.Exchange", "javaName": "askExchange", "title": "Ask Exchange." }, @@ -23,7 +23,7 @@ "title": "Ask size." }, "bx": { - "existingJavaType": "net.jacobpeterson.alpaca.model.endpoint.marketdata.crypto.historical.enums.Exchange", + "existingJavaType": "net.jacobpeterson.alpaca.model.endpoint.marketdata.crypto.common.enums.Exchange", "javaName": "bidExchange", "title": "Bid Exchange." }, diff --git a/schema_json/endpoint/market_data/crypto/historical/xbbo/xbbo_response.json b/schema_json/endpoint/market_data/crypto/historical/xbbo/xbbo_response.json index b3bbe427..2fdc8f28 100644 --- a/schema_json/endpoint/market_data/crypto/historical/xbbo/xbbo_response.json +++ b/schema_json/endpoint/market_data/crypto/historical/xbbo/xbbo_response.json @@ -1,6 +1,6 @@ { "type": "object", - "title": "See Crypto Historical Data.", + "title": "See Historical Crypto Data.", "properties": { "symbol": { "existingJavaType": "java.lang.String", diff --git a/schema_json/endpoint/market_data/crypto/realtime/bar/crypto_bar_message.json b/schema_json/endpoint/market_data/crypto/realtime/bar/crypto_bar_message.json new file mode 100644 index 00000000..3ef4ac16 --- /dev/null +++ b/schema_json/endpoint/market_data/crypto/realtime/bar/crypto_bar_message.json @@ -0,0 +1,29 @@ +{ + "type": "object", + "extends": { + "existingJavaType": "net.jacobpeterson.alpaca.model.endpoint.marketdata.common.realtime.bar.BarMessage" + }, + "title": "See Realtime Crypto Market Data.", + "properties": { + "x": { + "existingJavaType": "net.jacobpeterson.alpaca.model.endpoint.marketdata.crypto.common.enums.Exchange", + "javaName": "exchange", + "title": "Exchange." + }, + "v": { + "existingJavaType": "java.lang.Double", + "javaName": "volume", + "title": "Volume." + }, + "n": { + "existingJavaType": "java.lang.Long", + "javaName": "tradeCount", + "title": "Trade count." + }, + "vw": { + "existingJavaType": "java.lang.Double", + "javaName": "vwap", + "title": "VWAP (Volume Weighted Average Price)." + } + } +} diff --git a/schema_json/endpoint/market_data/crypto/realtime/quote/crypto_quote_message.json b/schema_json/endpoint/market_data/crypto/realtime/quote/crypto_quote_message.json new file mode 100644 index 00000000..9d9a2618 --- /dev/null +++ b/schema_json/endpoint/market_data/crypto/realtime/quote/crypto_quote_message.json @@ -0,0 +1,24 @@ +{ + "type": "object", + "extends": { + "existingJavaType": "net.jacobpeterson.alpaca.model.endpoint.marketdata.common.realtime.quote.QuoteMessage" + }, + "title": "See Realtime Crypto Market Data.", + "properties": { + "x": { + "existingJavaType": "net.jacobpeterson.alpaca.model.endpoint.marketdata.crypto.common.enums.Exchange", + "javaName": "exchange", + "title": "Exchange." + }, + "as": { + "existingJavaType": "java.lang.Double", + "javaName": "askSize", + "title": "Ask size." + }, + "bs": { + "existingJavaType": "java.lang.Double", + "javaName": "bidSize", + "title": "Bid size." + } + } +} diff --git a/schema_json/endpoint/market_data/crypto/realtime/trade/crypto_trade_message.json b/schema_json/endpoint/market_data/crypto/realtime/trade/crypto_trade_message.json new file mode 100644 index 00000000..ec4f5227 --- /dev/null +++ b/schema_json/endpoint/market_data/crypto/realtime/trade/crypto_trade_message.json @@ -0,0 +1,24 @@ +{ + "type": "object", + "extends": { + "existingJavaType": "net.jacobpeterson.alpaca.model.endpoint.marketdata.common.realtime.trade.TradeMessage" + }, + "title": "See Realtime Crypto Market Data.", + "properties": { + "x": { + "existingJavaType": "net.jacobpeterson.alpaca.model.endpoint.marketdata.crypto.common.enums.Exchange", + "javaName": "exchange", + "title": "Exchange where the trade happened." + }, + "s": { + "existingJavaType": "java.lang.Double", + "javaName": "size", + "title": "Trade size." + }, + "tks": { + "existingJavaType": "net.jacobpeterson.alpaca.model.endpoint.marketdata.crypto.common.enums.TakerSide", + "javaName": "takerSide", + "title": "The {@link net.jacobpeterson.alpaca.model.endpoint.marketdata.crypto.common.enums.TakerSide} of the trade." + } + } +} diff --git a/schema_json/endpoint/market_data/stock/historical/bar/enums/bar_adjustment.json b/schema_json/endpoint/market_data/stock/historical/bar/enums/bar_adjustment.json index 4f28b6ed..d42b31b2 100644 --- a/schema_json/endpoint/market_data/stock/historical/bar/enums/bar_adjustment.json +++ b/schema_json/endpoint/market_data/stock/historical/bar/enums/bar_adjustment.json @@ -1,6 +1,6 @@ { "type": "string", - "title": "See Historical Data.", + "title": "See Historical Stock Data.", "enum": [ "raw", "split", diff --git a/schema_json/endpoint/market_data/stock/historical/bar/stock_bar.json b/schema_json/endpoint/market_data/stock/historical/bar/stock_bar.json new file mode 100644 index 00000000..ae851453 --- /dev/null +++ b/schema_json/endpoint/market_data/stock/historical/bar/stock_bar.json @@ -0,0 +1,14 @@ +{ + "type": "object", + "extends": { + "existingJavaType": "net.jacobpeterson.alpaca.model.endpoint.marketdata.common.historical.bar.Bar" + }, + "title": "See Historical Stock Data.", + "properties": { + "v": { + "existingJavaType": "java.lang.Long", + "javaName": "volume", + "title": "Volume." + } + } +} diff --git a/schema_json/endpoint/market_data/stock/historical/bar/bars_response.json b/schema_json/endpoint/market_data/stock/historical/bar/stock_bars_response.json similarity index 85% rename from schema_json/endpoint/market_data/stock/historical/bar/bars_response.json rename to schema_json/endpoint/market_data/stock/historical/bar/stock_bars_response.json index ebd21e82..b23db483 100644 --- a/schema_json/endpoint/market_data/stock/historical/bar/bars_response.json +++ b/schema_json/endpoint/market_data/stock/historical/bar/stock_bars_response.json @@ -1,10 +1,10 @@ { "type": "object", - "title": "See Historical Data.", + "title": "See Historical Stock Data.", "properties": { "bars": { - "existingJavaType": "java.util.ArrayList", - "title": "The {@link java.util.ArrayList} of {@link net.jacobpeterson.alpaca.model.endpoint.marketdata.stock.historical.bar.Bar}s." + "existingJavaType": "java.util.ArrayList", + "title": "The {@link java.util.ArrayList} of {@link net.jacobpeterson.alpaca.model.endpoint.marketdata.stock.historical.bar.StockBar}s." }, "symbol": { "existingJavaType": "java.lang.String", diff --git a/schema_json/endpoint/market_data/stock/historical/quote/latest_quote_response.json b/schema_json/endpoint/market_data/stock/historical/quote/latest_stock_quote_response.json similarity index 76% rename from schema_json/endpoint/market_data/stock/historical/quote/latest_quote_response.json rename to schema_json/endpoint/market_data/stock/historical/quote/latest_stock_quote_response.json index 1250c5f8..4a9a3992 100644 --- a/schema_json/endpoint/market_data/stock/historical/quote/latest_quote_response.json +++ b/schema_json/endpoint/market_data/stock/historical/quote/latest_stock_quote_response.json @@ -1,14 +1,14 @@ { "type": "object", - "title": "See Historical Data.", + "title": "See Historical Stock Data.", "properties": { "symbol": { "existingJavaType": "java.lang.String", "title": "Symbol that was queried." }, "quote": { - "existingJavaType": "net.jacobpeterson.alpaca.model.endpoint.marketdata.stock.historical.quote.Quote", - "title": "The latest {@link net.jacobpeterson.alpaca.model.endpoint.marketdata.stock.historical.quote.Quote}." + "existingJavaType": "net.jacobpeterson.alpaca.model.endpoint.marketdata.stock.historical.quote.StockQuote", + "title": "The latest {@link net.jacobpeterson.alpaca.model.endpoint.marketdata.stock.historical.quote.StockQuote}." } } } diff --git a/schema_json/endpoint/market_data/stock/historical/quote/quote.json b/schema_json/endpoint/market_data/stock/historical/quote/stock_quote.json similarity index 66% rename from schema_json/endpoint/market_data/stock/historical/quote/quote.json rename to schema_json/endpoint/market_data/stock/historical/quote/stock_quote.json index 059c9746..164f093f 100644 --- a/schema_json/endpoint/market_data/stock/historical/quote/quote.json +++ b/schema_json/endpoint/market_data/stock/historical/quote/stock_quote.json @@ -1,22 +1,15 @@ { "type": "object", - "title": "See Historical Data.", + "extends": { + "existingJavaType": "net.jacobpeterson.alpaca.model.endpoint.marketdata.common.historical.quote.Quote" + }, + "title": "See Historical Stock Data.", "properties": { - "t": { - "existingJavaType": "java.time.ZonedDateTime", - "javaName": "timestamp", - "title": "Timestamp with nanosecond precision." - }, "ax": { "existingJavaType": "java.lang.String", "javaName": "askExchange", "title": "Ask exchange." }, - "ap": { - "existingJavaType": "java.lang.Double", - "javaName": "askPrice", - "title": "Ask price." - }, "as": { "existingJavaType": "java.lang.Integer", "javaName": "askSize", @@ -27,11 +20,6 @@ "javaName": "bidExchange", "title": "Bid exchange." }, - "bp": { - "existingJavaType": "java.lang.Double", - "javaName": "bidPrice", - "title": "Bid price." - }, "bs": { "existingJavaType": "java.lang.Integer", "javaName": "bidSize", diff --git a/schema_json/endpoint/market_data/stock/historical/quote/quotes_response.json b/schema_json/endpoint/market_data/stock/historical/quote/stock_quotes_response.json similarity index 84% rename from schema_json/endpoint/market_data/stock/historical/quote/quotes_response.json rename to schema_json/endpoint/market_data/stock/historical/quote/stock_quotes_response.json index 6ba96682..418dcf6b 100644 --- a/schema_json/endpoint/market_data/stock/historical/quote/quotes_response.json +++ b/schema_json/endpoint/market_data/stock/historical/quote/stock_quotes_response.json @@ -1,10 +1,10 @@ { "type": "object", - "title": "See Historical Data.", + "title": "See Historical Stock Data.", "properties": { "quotes": { - "existingJavaType": "java.util.ArrayList", - "title": "The {@link java.util.ArrayList} of {@link net.jacobpeterson.alpaca.model.endpoint.marketdata.stock.historical.quote.Quote}s." + "existingJavaType": "java.util.ArrayList", + "title": "The {@link java.util.ArrayList} of {@link net.jacobpeterson.alpaca.model.endpoint.marketdata.stock.historical.quote.StockQuote}s." }, "symbol": { "existingJavaType": "java.lang.String", diff --git a/schema_json/endpoint/market_data/stock/historical/snapshot/snapshot.json b/schema_json/endpoint/market_data/stock/historical/snapshot/snapshot.json index f42b18af..9617b3de 100644 --- a/schema_json/endpoint/market_data/stock/historical/snapshot/snapshot.json +++ b/schema_json/endpoint/market_data/stock/historical/snapshot/snapshot.json @@ -1,26 +1,26 @@ { "type": "object", - "title": "See Historical Data.", + "title": "See Historical Stock Data.", "properties": { "latestTrade": { - "existingJavaType": "net.jacobpeterson.alpaca.model.endpoint.marketdata.stock.historical.trade.Trade", - "title": "The latest {@link net.jacobpeterson.alpaca.model.endpoint.marketdata.stock.historical.trade.Trade}." + "existingJavaType": "net.jacobpeterson.alpaca.model.endpoint.marketdata.stock.historical.trade.StockTrade", + "title": "The latest {@link net.jacobpeterson.alpaca.model.endpoint.marketdata.stock.historical.trade.StockTrade}." }, "latestQuote": { - "existingJavaType": "net.jacobpeterson.alpaca.model.endpoint.marketdata.stock.historical.quote.Quote", - "title": "The latest {@link net.jacobpeterson.alpaca.model.endpoint.marketdata.stock.historical.quote.Quote}." + "existingJavaType": "net.jacobpeterson.alpaca.model.endpoint.marketdata.stock.historical.quote.StockQuote", + "title": "The latest {@link net.jacobpeterson.alpaca.model.endpoint.marketdata.stock.historical.quote.StockQuote}." }, "minuteBar": { - "existingJavaType": "net.jacobpeterson.alpaca.model.endpoint.marketdata.stock.historical.bar.Bar", - "title": "The minute {@link net.jacobpeterson.alpaca.model.endpoint.marketdata.stock.historical.bar.Bar}." + "existingJavaType": "net.jacobpeterson.alpaca.model.endpoint.marketdata.stock.historical.bar.StockBar", + "title": "The minute {@link net.jacobpeterson.alpaca.model.endpoint.marketdata.stock.historical.bar.StockBar}." }, "dailyBar": { - "existingJavaType": "net.jacobpeterson.alpaca.model.endpoint.marketdata.stock.historical.bar.Bar", - "title": "The daily {@link net.jacobpeterson.alpaca.model.endpoint.marketdata.stock.historical.bar.Bar}." + "existingJavaType": "net.jacobpeterson.alpaca.model.endpoint.marketdata.stock.historical.bar.StockBar", + "title": "The daily {@link net.jacobpeterson.alpaca.model.endpoint.marketdata.stock.historical.bar.StockBar}." }, "prevDailyBar": { - "existingJavaType": "net.jacobpeterson.alpaca.model.endpoint.marketdata.stock.historical.bar.Bar", - "title": "The previous daily {@link net.jacobpeterson.alpaca.model.endpoint.marketdata.stock.historical.bar.Bar}." + "existingJavaType": "net.jacobpeterson.alpaca.model.endpoint.marketdata.stock.historical.bar.StockBar", + "title": "The previous daily {@link net.jacobpeterson.alpaca.model.endpoint.marketdata.stock.historical.bar.StockBar}." } } } diff --git a/schema_json/endpoint/market_data/stock/historical/trade/latest_trade_response.json b/schema_json/endpoint/market_data/stock/historical/trade/latest_stock_trade_response.json similarity index 76% rename from schema_json/endpoint/market_data/stock/historical/trade/latest_trade_response.json rename to schema_json/endpoint/market_data/stock/historical/trade/latest_stock_trade_response.json index f5be1b9d..b4765b53 100644 --- a/schema_json/endpoint/market_data/stock/historical/trade/latest_trade_response.json +++ b/schema_json/endpoint/market_data/stock/historical/trade/latest_stock_trade_response.json @@ -1,14 +1,14 @@ { "type": "object", - "title": "See Historical Data.", + "title": "See Historical Stock Data.", "properties": { "symbol": { "existingJavaType": "java.lang.String", "title": "Symbol that was queried." }, "trade": { - "existingJavaType": "net.jacobpeterson.alpaca.model.endpoint.marketdata.stock.historical.trade.Trade", - "title": "The latest {@link net.jacobpeterson.alpaca.model.endpoint.marketdata.stock.historical.trade.Trade}." + "existingJavaType": "net.jacobpeterson.alpaca.model.endpoint.marketdata.stock.historical.trade.StockTrade", + "title": "The latest {@link net.jacobpeterson.alpaca.model.endpoint.marketdata.stock.historical.trade.StockTrade}." } } } diff --git a/schema_json/endpoint/market_data/stock/historical/trade/trade.json b/schema_json/endpoint/market_data/stock/historical/trade/stock_trade.json similarity index 63% rename from schema_json/endpoint/market_data/stock/historical/trade/trade.json rename to schema_json/endpoint/market_data/stock/historical/trade/stock_trade.json index f7ef48ca..ea3e96cf 100644 --- a/schema_json/endpoint/market_data/stock/historical/trade/trade.json +++ b/schema_json/endpoint/market_data/stock/historical/trade/stock_trade.json @@ -1,22 +1,15 @@ { "type": "object", - "title": "See Historical Data.", + "extends": { + "existingJavaType": "net.jacobpeterson.alpaca.model.endpoint.marketdata.common.historical.trade.Trade" + }, + "title": "See Historical Stock Data.", "properties": { - "t": { - "existingJavaType": "java.time.ZonedDateTime", - "javaName": "timestamp", - "title": "Timestamp with nanosecond precision." - }, "x": { "existingJavaType": "java.lang.String", "javaName": "exchange", "title": "Exchange where the trade happened." }, - "p": { - "existingJavaType": "java.lang.Double", - "javaName": "price", - "title": "Trade price." - }, "s": { "existingJavaType": "java.lang.Integer", "javaName": "size", @@ -27,11 +20,6 @@ "javaName": "conditions", "title": "The {@link java.util.ArrayList} of trade conditions." }, - "i": { - "existingJavaType": "java.lang.Long", - "javaName": "tradeID", - "title": "Trade ID." - }, "z": { "existingJavaType": "java.lang.String", "javaName": "tape", diff --git a/schema_json/endpoint/market_data/stock/historical/trade/trades_response.json b/schema_json/endpoint/market_data/stock/historical/trade/stock_trades_response.json similarity index 84% rename from schema_json/endpoint/market_data/stock/historical/trade/trades_response.json rename to schema_json/endpoint/market_data/stock/historical/trade/stock_trades_response.json index 503b6d9f..9b08b1da 100644 --- a/schema_json/endpoint/market_data/stock/historical/trade/trades_response.json +++ b/schema_json/endpoint/market_data/stock/historical/trade/stock_trades_response.json @@ -1,10 +1,10 @@ { "type": "object", - "title": "See Historical Data.", + "title": "See Historical Stock Data.", "properties": { "trades": { - "existingJavaType": "java.util.ArrayList", - "title": "The {@link java.util.ArrayList} of {@link net.jacobpeterson.alpaca.model.endpoint.marketdata.stock.historical.trade.Trade}s." + "existingJavaType": "java.util.ArrayList", + "title": "The {@link java.util.ArrayList} of {@link net.jacobpeterson.alpaca.model.endpoint.marketdata.stock.historical.trade.StockTrade}s." }, "symbol": { "existingJavaType": "java.lang.String", diff --git a/schema_json/endpoint/market_data/stock/realtime/bar/stock_bar_message.json b/schema_json/endpoint/market_data/stock/realtime/bar/stock_bar_message.json new file mode 100644 index 00000000..6bfbf62a --- /dev/null +++ b/schema_json/endpoint/market_data/stock/realtime/bar/stock_bar_message.json @@ -0,0 +1,14 @@ +{ + "type": "object", + "extends": { + "existingJavaType": "net.jacobpeterson.alpaca.model.endpoint.marketdata.common.realtime.bar.BarMessage" + }, + "title": "See Realtime Stock Market Data.", + "properties": { + "v": { + "existingJavaType": "java.lang.Long", + "javaName": "volume", + "title": "Volume." + } + } +} diff --git a/schema_json/endpoint/market_data/stock/realtime/quote/quote_message.json b/schema_json/endpoint/market_data/stock/realtime/quote/stock_quote_message.json similarity index 65% rename from schema_json/endpoint/market_data/stock/realtime/quote/quote_message.json rename to schema_json/endpoint/market_data/stock/realtime/quote/stock_quote_message.json index 3c7c650c..2633ccde 100644 --- a/schema_json/endpoint/market_data/stock/realtime/quote/quote_message.json +++ b/schema_json/endpoint/market_data/stock/realtime/quote/stock_quote_message.json @@ -1,20 +1,15 @@ { "type": "object", "extends": { - "existingJavaType": "net.jacobpeterson.alpaca.model.endpoint.marketdata.stock.realtime.SymbolMessage" + "existingJavaType": "net.jacobpeterson.alpaca.model.endpoint.marketdata.common.realtime.quote.QuoteMessage" }, - "title": "See Real-time Data.", + "title": "See Realtime Stock Market Data.", "properties": { "ax": { "existingJavaType": "java.lang.String", "javaName": "askExchange", "title": "Ask exchange code." }, - "ap": { - "existingJavaType": "java.lang.Double", - "javaName": "askPrice", - "title": "Ask price." - }, "as": { "existingJavaType": "java.lang.Integer", "javaName": "askSize", @@ -25,21 +20,11 @@ "javaName": "bidExchange", "title": "Bid exchange code." }, - "bp": { - "existingJavaType": "java.lang.Double", - "javaName": "bidPrice", - "title": "Bid price." - }, "bs": { "existingJavaType": "java.lang.Integer", "javaName": "bidSize", "title": "Bid size." }, - "t": { - "existingJavaType": "java.time.ZonedDateTime", - "javaName": "timestamp", - "title": "Timestamp with nanosecond precision." - }, "c": { "existingJavaType": "java.util.ArrayList", "javaName": "conditions", diff --git a/schema_json/endpoint/market_data/stock/realtime/trade/trade_message.json b/schema_json/endpoint/market_data/stock/realtime/trade/stock_trade_message.json similarity index 59% rename from schema_json/endpoint/market_data/stock/realtime/trade/trade_message.json rename to schema_json/endpoint/market_data/stock/realtime/trade/stock_trade_message.json index e72f5859..c3b0bc51 100644 --- a/schema_json/endpoint/market_data/stock/realtime/trade/trade_message.json +++ b/schema_json/endpoint/market_data/stock/realtime/trade/stock_trade_message.json @@ -1,35 +1,20 @@ { "type": "object", "extends": { - "existingJavaType": "net.jacobpeterson.alpaca.model.endpoint.marketdata.stock.realtime.SymbolMessage" + "existingJavaType": "net.jacobpeterson.alpaca.model.endpoint.marketdata.common.realtime.trade.TradeMessage" }, - "title": "See Real-time Data.", + "title": "See Realtime Stock Market Data.", "properties": { - "i": { - "existingJavaType": "java.lang.Integer", - "javaName": "tradeID", - "title": "Trade ID." - }, "x": { "existingJavaType": "java.lang.String", "javaName": "exchange", "title": "Exchange code where the trade occurred." }, - "p": { - "existingJavaType": "java.lang.Double", - "javaName": "price", - "title": "Trade price." - }, "s": { "existingJavaType": "java.lang.Integer", "javaName": "size", "title": "Trade size." }, - "t": { - "existingJavaType": "java.time.ZonedDateTime", - "javaName": "timestamp", - "title": "Timestamp with nanosecond precision." - }, "c": { "existingJavaType": "java.util.ArrayList", "javaName": "conditions", diff --git a/schema_json/endpoint/order/cancelled_order.json b/schema_json/endpoint/orders/cancelled_order.json similarity index 92% rename from schema_json/endpoint/order/cancelled_order.json rename to schema_json/endpoint/orders/cancelled_order.json index be79774c..cd34b921 100644 --- a/schema_json/endpoint/order/cancelled_order.json +++ b/schema_json/endpoint/orders/cancelled_order.json @@ -11,9 +11,9 @@ "title": "The HTTP status code of the cancelled order." }, "body": { - "existingJavaType": "net.jacobpeterson.alpaca.model.endpoint.order.Order", + "existingJavaType": "net.jacobpeterson.alpaca.model.endpoint.orders.Order", "javaName": "order", - "title": "The cancelled {@link net.jacobpeterson.alpaca.model.endpoint.order.Order}." + "title": "The cancelled {@link net.jacobpeterson.alpaca.model.endpoint.orders.Order}." } } } diff --git a/schema_json/endpoint/order/enums/current_order_status.json b/schema_json/endpoint/orders/enums/current_order_status.json similarity index 100% rename from schema_json/endpoint/order/enums/current_order_status.json rename to schema_json/endpoint/orders/enums/current_order_status.json diff --git a/schema_json/endpoint/order/enums/order_class.json b/schema_json/endpoint/orders/enums/order_class.json similarity index 100% rename from schema_json/endpoint/order/enums/order_class.json rename to schema_json/endpoint/orders/enums/order_class.json diff --git a/schema_json/endpoint/order/enums/order_side.json b/schema_json/endpoint/orders/enums/order_side.json similarity index 100% rename from schema_json/endpoint/order/enums/order_side.json rename to schema_json/endpoint/orders/enums/order_side.json diff --git a/schema_json/endpoint/order/enums/order_status.json b/schema_json/endpoint/orders/enums/order_status.json similarity index 100% rename from schema_json/endpoint/order/enums/order_status.json rename to schema_json/endpoint/orders/enums/order_status.json diff --git a/schema_json/endpoint/order/enums/order_time_in_force.json b/schema_json/endpoint/orders/enums/order_time_in_force.json similarity index 100% rename from schema_json/endpoint/order/enums/order_time_in_force.json rename to schema_json/endpoint/orders/enums/order_time_in_force.json diff --git a/schema_json/endpoint/order/enums/order_type.json b/schema_json/endpoint/orders/enums/order_type.json similarity index 100% rename from schema_json/endpoint/order/enums/order_type.json rename to schema_json/endpoint/orders/enums/order_type.json diff --git a/schema_json/endpoint/order/order.json b/schema_json/endpoint/orders/order.json similarity index 89% rename from schema_json/endpoint/order/order.json rename to schema_json/endpoint/orders/order.json index c0af5330..6a6f85fd 100644 --- a/schema_json/endpoint/order/order.json +++ b/schema_json/endpoint/orders/order.json @@ -82,20 +82,20 @@ "title": "Filled average price." }, "order_class": { - "existingJavaType": "net.jacobpeterson.alpaca.model.endpoint.order.enums.OrderClass", - "title": "The {@link net.jacobpeterson.alpaca.model.endpoint.order.enums.OrderClass}. For details of non-simple order classes, please see Bracket Order Overview." + "existingJavaType": "net.jacobpeterson.alpaca.model.endpoint.orders.enums.OrderClass", + "title": "The {@link net.jacobpeterson.alpaca.model.endpoint.orders.enums.OrderClass}. For details of non-simple order classes, please see Bracket Order Overview." }, "type": { - "existingJavaType": "net.jacobpeterson.alpaca.model.endpoint.order.enums.OrderType", - "title": "The {@link net.jacobpeterson.alpaca.model.endpoint.order.enums.OrderType}." + "existingJavaType": "net.jacobpeterson.alpaca.model.endpoint.orders.enums.OrderType", + "title": "The {@link net.jacobpeterson.alpaca.model.endpoint.orders.enums.OrderType}." }, "side": { - "existingJavaType": "net.jacobpeterson.alpaca.model.endpoint.order.enums.OrderSide", - "title": "The {@link net.jacobpeterson.alpaca.model.endpoint.order.enums.OrderSide}." + "existingJavaType": "net.jacobpeterson.alpaca.model.endpoint.orders.enums.OrderSide", + "title": "The {@link net.jacobpeterson.alpaca.model.endpoint.orders.enums.OrderSide}." }, "time_in_force": { - "existingJavaType": "net.jacobpeterson.alpaca.model.endpoint.order.enums.OrderTimeInForce", - "title": "The {@link net.jacobpeterson.alpaca.model.endpoint.order.enums.OrderTimeInForce}." + "existingJavaType": "net.jacobpeterson.alpaca.model.endpoint.orders.enums.OrderTimeInForce", + "title": "The {@link net.jacobpeterson.alpaca.model.endpoint.orders.enums.OrderTimeInForce}." }, "limit_price": { "existingJavaType": "java.lang.String", @@ -106,16 +106,16 @@ "title": "Stop price." }, "status": { - "existingJavaType": "net.jacobpeterson.alpaca.model.endpoint.order.enums.OrderStatus", - "title": "The {@link net.jacobpeterson.alpaca.model.endpoint.order.enums.OrderStatus}." + "existingJavaType": "net.jacobpeterson.alpaca.model.endpoint.orders.enums.OrderStatus", + "title": "The {@link net.jacobpeterson.alpaca.model.endpoint.orders.enums.OrderStatus}." }, "extended_hours": { "existingJavaType": "java.lang.Boolean", "title": "If true, eligible for execution outside regular trading hours." }, "legs": { - "existingJavaType": "java.util.ArrayList", - "title": "When querying non-simple order_class orders in a nested style, an {@link java.util.ArrayList} of {@link net.jacobpeterson.alpaca.model.endpoint.order.Order} entities associated with this order. Otherwise, null." + "existingJavaType": "java.util.ArrayList", + "title": "When querying non-simple order_class orders in a nested style, an {@link java.util.ArrayList} of {@link net.jacobpeterson.alpaca.model.endpoint.orders.Order} entities associated with this order. Otherwise, null." }, "trail_percent": { "existingJavaType": "java.lang.String", diff --git a/schema_json/endpoint/position/close_position_order.json b/schema_json/endpoint/positions/close_position_order.json similarity index 91% rename from schema_json/endpoint/position/close_position_order.json rename to schema_json/endpoint/positions/close_position_order.json index 5ce79575..3ffba883 100644 --- a/schema_json/endpoint/position/close_position_order.json +++ b/schema_json/endpoint/positions/close_position_order.json @@ -11,9 +11,9 @@ "title": "The HTTP status code of the position-closing order." }, "body": { - "existingJavaType": "net.jacobpeterson.alpaca.model.endpoint.order.Order", + "existingJavaType": "net.jacobpeterson.alpaca.model.endpoint.orders.Order", "javaName": "order", - "title": "The position-closing {@link net.jacobpeterson.alpaca.model.endpoint.order.Order}." + "title": "The position-closing {@link net.jacobpeterson.alpaca.model.endpoint.orders.Order}." } } } diff --git a/schema_json/endpoint/position/position.json b/schema_json/endpoint/positions/position.json similarity index 100% rename from schema_json/endpoint/position/position.json rename to schema_json/endpoint/positions/position.json diff --git a/schema_json/endpoint/streaming/trade/trade_update.json b/schema_json/endpoint/streaming/trade/trade_update.json index 339ea01c..32f6c324 100644 --- a/schema_json/endpoint/streaming/trade/trade_update.json +++ b/schema_json/endpoint/streaming/trade/trade_update.json @@ -20,8 +20,8 @@ "title": "The position quantity." }, "order": { - "existingJavaType": "net.jacobpeterson.alpaca.model.endpoint.order.Order", - "title": "The {@link net.jacobpeterson.alpaca.model.endpoint.order.Order}." + "existingJavaType": "net.jacobpeterson.alpaca.model.endpoint.orders.Order", + "title": "The {@link net.jacobpeterson.alpaca.model.endpoint.orders.Order}." } } } diff --git a/schema_json/endpoint/watchlist/watchlist.json b/schema_json/endpoint/watchlist/watchlist.json index 0cfa52a0..7c7e8602 100644 --- a/schema_json/endpoint/watchlist/watchlist.json +++ b/schema_json/endpoint/watchlist/watchlist.json @@ -23,7 +23,7 @@ "title": "Account ID." }, "assets": { - "existingJavaType": "java.util.ArrayList", + "existingJavaType": "java.util.ArrayList", "title": "The content of this watchlist, in the order as registered by the client." } } diff --git a/src/main/java/net/jacobpeterson/alpaca/AlpacaAPI.java b/src/main/java/net/jacobpeterson/alpaca/AlpacaAPI.java index 67813131..63980318 100644 --- a/src/main/java/net/jacobpeterson/alpaca/AlpacaAPI.java +++ b/src/main/java/net/jacobpeterson/alpaca/AlpacaAPI.java @@ -5,22 +5,23 @@ import net.jacobpeterson.alpaca.model.properties.EndpointAPIType; import net.jacobpeterson.alpaca.properties.AlpacaProperties; import net.jacobpeterson.alpaca.rest.AlpacaClient; -import net.jacobpeterson.alpaca.rest.endpoint.AccountActivitiesEndpoint; -import net.jacobpeterson.alpaca.rest.endpoint.AccountConfigurationEndpoint; -import net.jacobpeterson.alpaca.rest.endpoint.AccountEndpoint; +import net.jacobpeterson.alpaca.rest.endpoint.accountactivities.AccountActivitiesEndpoint; +import net.jacobpeterson.alpaca.rest.endpoint.accountconfiguration.AccountConfigurationEndpoint; +import net.jacobpeterson.alpaca.rest.endpoint.account.AccountEndpoint; import net.jacobpeterson.alpaca.rest.endpoint.AlpacaEndpoint; -import net.jacobpeterson.alpaca.rest.endpoint.AssetsEndpoint; -import net.jacobpeterson.alpaca.rest.endpoint.CalendarEndpoint; -import net.jacobpeterson.alpaca.rest.endpoint.ClockEndpoint; -import net.jacobpeterson.alpaca.rest.endpoint.OrdersEndpoint; -import net.jacobpeterson.alpaca.rest.endpoint.PortfolioHistoryEndpoint; -import net.jacobpeterson.alpaca.rest.endpoint.PositionsEndpoint; -import net.jacobpeterson.alpaca.rest.endpoint.WatchlistEndpoint; -import net.jacobpeterson.alpaca.rest.endpoint.marketdata.CryptoMarketDataEndpoint; -import net.jacobpeterson.alpaca.rest.endpoint.marketdata.StockMarketDataEndpoint; +import net.jacobpeterson.alpaca.rest.endpoint.assets.AssetsEndpoint; +import net.jacobpeterson.alpaca.rest.endpoint.calendar.CalendarEndpoint; +import net.jacobpeterson.alpaca.rest.endpoint.clock.ClockEndpoint; +import net.jacobpeterson.alpaca.rest.endpoint.orders.OrdersEndpoint; +import net.jacobpeterson.alpaca.rest.endpoint.portfoliohistory.PortfolioHistoryEndpoint; +import net.jacobpeterson.alpaca.rest.endpoint.positions.PositionsEndpoint; +import net.jacobpeterson.alpaca.rest.endpoint.watchlist.WatchlistEndpoint; +import net.jacobpeterson.alpaca.rest.endpoint.marketdata.crypto.CryptoMarketDataEndpoint; +import net.jacobpeterson.alpaca.rest.endpoint.marketdata.stock.StockMarketDataEndpoint; import net.jacobpeterson.alpaca.websocket.AlpacaWebsocket; -import net.jacobpeterson.alpaca.websocket.marketdata.MarketDataWebsocket; import net.jacobpeterson.alpaca.websocket.marketdata.MarketDataWebsocketInterface; +import net.jacobpeterson.alpaca.websocket.marketdata.crypto.CryptoMarketDataWebsocket; +import net.jacobpeterson.alpaca.websocket.marketdata.stock.StockMarketDataWebsocket; import net.jacobpeterson.alpaca.websocket.streaming.StreamingWebsocket; import net.jacobpeterson.alpaca.websocket.streaming.StreamingWebsocketInterface; import okhttp3.OkHttpClient; @@ -63,7 +64,8 @@ public class AlpacaAPI { private final AccountActivitiesEndpoint accountActivitiesEndpoint; private final PortfolioHistoryEndpoint portfolioHistoryEndpoint; private final StreamingWebsocket streamingWebsocket; - private final MarketDataWebsocket marketDataWebsocket; + private final CryptoMarketDataWebsocket cryptoMarketDataWebsocket; + private final StockMarketDataWebsocket stockMarketDataWebsocket; /** * Instantiates a new {@link AlpacaAPI} using properties specified in alpaca.properties file (or their @@ -178,9 +180,12 @@ public AlpacaAPI(OkHttpClient okHttpClient, String keyID, String secretKey, Stri accountConfigurationEndpoint = new AccountConfigurationEndpoint(brokerClient); accountActivitiesEndpoint = new AccountActivitiesEndpoint(brokerClient); portfolioHistoryEndpoint = new PortfolioHistoryEndpoint(brokerClient); + streamingWebsocket = new StreamingWebsocket(okHttpClient, brokerHostSubdomain, keyID, secretKey, oAuthToken); - marketDataWebsocket = stockDataClient == null ? null : - new MarketDataWebsocket(okHttpClient, dataAPIType, keyID, secretKey); + cryptoMarketDataWebsocket = cryptoDataClient == null ? null : + new CryptoMarketDataWebsocket(okHttpClient, keyID, secretKey); + stockMarketDataWebsocket = stockDataClient == null ? null : + new StockMarketDataWebsocket(okHttpClient, dataAPIType, keyID, secretKey); } /** @@ -275,10 +280,17 @@ public StreamingWebsocketInterface streaming() { } /** - * @return the {@link MarketDataWebsocketInterface} + * @return the Crypto {@link MarketDataWebsocketInterface} + */ + public MarketDataWebsocketInterface cryptoMarketDataStreaming() { + return cryptoMarketDataWebsocket; + } + + /** + * @return the Stock {@link MarketDataWebsocketInterface} */ - public MarketDataWebsocketInterface marketDataStreaming() { - return marketDataWebsocket; + public MarketDataWebsocketInterface stockMarketDataStreaming() { + return stockMarketDataWebsocket; } public OkHttpClient getOkHttpClient() { diff --git a/src/main/java/net/jacobpeterson/alpaca/rest/endpoint/AccountEndpoint.java b/src/main/java/net/jacobpeterson/alpaca/rest/endpoint/account/AccountEndpoint.java similarity index 90% rename from src/main/java/net/jacobpeterson/alpaca/rest/endpoint/AccountEndpoint.java rename to src/main/java/net/jacobpeterson/alpaca/rest/endpoint/account/AccountEndpoint.java index ff57ff67..8054ab4f 100644 --- a/src/main/java/net/jacobpeterson/alpaca/rest/endpoint/AccountEndpoint.java +++ b/src/main/java/net/jacobpeterson/alpaca/rest/endpoint/account/AccountEndpoint.java @@ -1,8 +1,9 @@ -package net.jacobpeterson.alpaca.rest.endpoint; +package net.jacobpeterson.alpaca.rest.endpoint.account; import net.jacobpeterson.alpaca.model.endpoint.account.Account; import net.jacobpeterson.alpaca.rest.AlpacaClient; import net.jacobpeterson.alpaca.rest.AlpacaClientException; +import net.jacobpeterson.alpaca.rest.endpoint.AlpacaEndpoint; import okhttp3.HttpUrl; import okhttp3.Request; diff --git a/src/main/java/net/jacobpeterson/alpaca/rest/endpoint/AccountActivitiesEndpoint.java b/src/main/java/net/jacobpeterson/alpaca/rest/endpoint/accountactivities/AccountActivitiesEndpoint.java similarity index 97% rename from src/main/java/net/jacobpeterson/alpaca/rest/endpoint/AccountActivitiesEndpoint.java rename to src/main/java/net/jacobpeterson/alpaca/rest/endpoint/accountactivities/AccountActivitiesEndpoint.java index adbe8414..c56375bf 100644 --- a/src/main/java/net/jacobpeterson/alpaca/rest/endpoint/AccountActivitiesEndpoint.java +++ b/src/main/java/net/jacobpeterson/alpaca/rest/endpoint/accountactivities/AccountActivitiesEndpoint.java @@ -1,4 +1,4 @@ -package net.jacobpeterson.alpaca.rest.endpoint; +package net.jacobpeterson.alpaca.rest.endpoint.accountactivities; import com.google.gson.JsonArray; import com.google.gson.JsonElement; @@ -10,6 +10,7 @@ import net.jacobpeterson.alpaca.model.endpoint.common.enums.SortDirection; import net.jacobpeterson.alpaca.rest.AlpacaClient; import net.jacobpeterson.alpaca.rest.AlpacaClientException; +import net.jacobpeterson.alpaca.rest.endpoint.AlpacaEndpoint; import okhttp3.HttpUrl; import okhttp3.Request; import org.slf4j.Logger; diff --git a/src/main/java/net/jacobpeterson/alpaca/rest/endpoint/AccountConfigurationEndpoint.java b/src/main/java/net/jacobpeterson/alpaca/rest/endpoint/accountconfiguration/AccountConfigurationEndpoint.java similarity index 94% rename from src/main/java/net/jacobpeterson/alpaca/rest/endpoint/AccountConfigurationEndpoint.java rename to src/main/java/net/jacobpeterson/alpaca/rest/endpoint/accountconfiguration/AccountConfigurationEndpoint.java index 2d7c5de0..a319e650 100644 --- a/src/main/java/net/jacobpeterson/alpaca/rest/endpoint/AccountConfigurationEndpoint.java +++ b/src/main/java/net/jacobpeterson/alpaca/rest/endpoint/accountconfiguration/AccountConfigurationEndpoint.java @@ -1,8 +1,9 @@ -package net.jacobpeterson.alpaca.rest.endpoint; +package net.jacobpeterson.alpaca.rest.endpoint.accountconfiguration; import net.jacobpeterson.alpaca.model.endpoint.accountconfiguration.AccountConfiguration; import net.jacobpeterson.alpaca.rest.AlpacaClient; import net.jacobpeterson.alpaca.rest.AlpacaClientException; +import net.jacobpeterson.alpaca.rest.endpoint.AlpacaEndpoint; import net.jacobpeterson.alpaca.util.okhttp.JSONBodyBuilder; import okhttp3.HttpUrl; import okhttp3.Request; diff --git a/src/main/java/net/jacobpeterson/alpaca/rest/endpoint/AssetsEndpoint.java b/src/main/java/net/jacobpeterson/alpaca/rest/endpoint/assets/AssetsEndpoint.java similarity index 88% rename from src/main/java/net/jacobpeterson/alpaca/rest/endpoint/AssetsEndpoint.java rename to src/main/java/net/jacobpeterson/alpaca/rest/endpoint/assets/AssetsEndpoint.java index 6cbf3298..bfcfaffe 100644 --- a/src/main/java/net/jacobpeterson/alpaca/rest/endpoint/AssetsEndpoint.java +++ b/src/main/java/net/jacobpeterson/alpaca/rest/endpoint/assets/AssetsEndpoint.java @@ -1,11 +1,12 @@ -package net.jacobpeterson.alpaca.rest.endpoint; +package net.jacobpeterson.alpaca.rest.endpoint.assets; import com.google.gson.reflect.TypeToken; -import net.jacobpeterson.alpaca.model.endpoint.asset.Asset; -import net.jacobpeterson.alpaca.model.endpoint.asset.enums.AssetClass; -import net.jacobpeterson.alpaca.model.endpoint.asset.enums.AssetStatus; +import net.jacobpeterson.alpaca.model.endpoint.assets.Asset; +import net.jacobpeterson.alpaca.model.endpoint.assets.enums.AssetClass; +import net.jacobpeterson.alpaca.model.endpoint.assets.enums.AssetStatus; import net.jacobpeterson.alpaca.rest.AlpacaClient; import net.jacobpeterson.alpaca.rest.AlpacaClientException; +import net.jacobpeterson.alpaca.rest.endpoint.AlpacaEndpoint; import okhttp3.HttpUrl; import okhttp3.Request; diff --git a/src/main/java/net/jacobpeterson/alpaca/rest/endpoint/CalendarEndpoint.java b/src/main/java/net/jacobpeterson/alpaca/rest/endpoint/calendar/CalendarEndpoint.java similarity index 94% rename from src/main/java/net/jacobpeterson/alpaca/rest/endpoint/CalendarEndpoint.java rename to src/main/java/net/jacobpeterson/alpaca/rest/endpoint/calendar/CalendarEndpoint.java index 03bb2641..58ad41af 100644 --- a/src/main/java/net/jacobpeterson/alpaca/rest/endpoint/CalendarEndpoint.java +++ b/src/main/java/net/jacobpeterson/alpaca/rest/endpoint/calendar/CalendarEndpoint.java @@ -1,9 +1,10 @@ -package net.jacobpeterson.alpaca.rest.endpoint; +package net.jacobpeterson.alpaca.rest.endpoint.calendar; import com.google.gson.reflect.TypeToken; import net.jacobpeterson.alpaca.model.endpoint.calendar.Calendar; import net.jacobpeterson.alpaca.rest.AlpacaClient; import net.jacobpeterson.alpaca.rest.AlpacaClientException; +import net.jacobpeterson.alpaca.rest.endpoint.AlpacaEndpoint; import okhttp3.HttpUrl; import okhttp3.Request; diff --git a/src/main/java/net/jacobpeterson/alpaca/rest/endpoint/ClockEndpoint.java b/src/main/java/net/jacobpeterson/alpaca/rest/endpoint/clock/ClockEndpoint.java similarity index 91% rename from src/main/java/net/jacobpeterson/alpaca/rest/endpoint/ClockEndpoint.java rename to src/main/java/net/jacobpeterson/alpaca/rest/endpoint/clock/ClockEndpoint.java index 6e5e9ae4..368edc13 100644 --- a/src/main/java/net/jacobpeterson/alpaca/rest/endpoint/ClockEndpoint.java +++ b/src/main/java/net/jacobpeterson/alpaca/rest/endpoint/clock/ClockEndpoint.java @@ -1,8 +1,9 @@ -package net.jacobpeterson.alpaca.rest.endpoint; +package net.jacobpeterson.alpaca.rest.endpoint.clock; import net.jacobpeterson.alpaca.model.endpoint.clock.Clock; import net.jacobpeterson.alpaca.rest.AlpacaClient; import net.jacobpeterson.alpaca.rest.AlpacaClientException; +import net.jacobpeterson.alpaca.rest.endpoint.AlpacaEndpoint; import okhttp3.HttpUrl; import okhttp3.Request; diff --git a/src/main/java/net/jacobpeterson/alpaca/rest/endpoint/marketdata/CryptoMarketDataEndpoint.java b/src/main/java/net/jacobpeterson/alpaca/rest/endpoint/marketdata/crypto/CryptoMarketDataEndpoint.java similarity index 83% rename from src/main/java/net/jacobpeterson/alpaca/rest/endpoint/marketdata/CryptoMarketDataEndpoint.java rename to src/main/java/net/jacobpeterson/alpaca/rest/endpoint/marketdata/crypto/CryptoMarketDataEndpoint.java index c044d1f8..b3136b17 100644 --- a/src/main/java/net/jacobpeterson/alpaca/rest/endpoint/marketdata/CryptoMarketDataEndpoint.java +++ b/src/main/java/net/jacobpeterson/alpaca/rest/endpoint/marketdata/crypto/CryptoMarketDataEndpoint.java @@ -1,15 +1,15 @@ -package net.jacobpeterson.alpaca.rest.endpoint.marketdata; - -import net.jacobpeterson.alpaca.model.endpoint.marketdata.common.enums.BarTimePeriod; -import net.jacobpeterson.alpaca.model.endpoint.marketdata.crypto.historical.bar.Bar; -import net.jacobpeterson.alpaca.model.endpoint.marketdata.crypto.historical.bar.BarsResponse; -import net.jacobpeterson.alpaca.model.endpoint.marketdata.crypto.historical.enums.Exchange; -import net.jacobpeterson.alpaca.model.endpoint.marketdata.crypto.historical.quote.LatestQuoteResponse; -import net.jacobpeterson.alpaca.model.endpoint.marketdata.crypto.historical.quote.Quote; -import net.jacobpeterson.alpaca.model.endpoint.marketdata.crypto.historical.quote.QuotesResponse; -import net.jacobpeterson.alpaca.model.endpoint.marketdata.crypto.historical.trade.LatestTradeResponse; -import net.jacobpeterson.alpaca.model.endpoint.marketdata.crypto.historical.trade.Trade; -import net.jacobpeterson.alpaca.model.endpoint.marketdata.crypto.historical.trade.TradesResponse; +package net.jacobpeterson.alpaca.rest.endpoint.marketdata.crypto; + +import net.jacobpeterson.alpaca.model.endpoint.marketdata.common.historical.bar.enums.BarTimePeriod; +import net.jacobpeterson.alpaca.model.endpoint.marketdata.crypto.common.enums.Exchange; +import net.jacobpeterson.alpaca.model.endpoint.marketdata.crypto.historical.bar.CryptoBar; +import net.jacobpeterson.alpaca.model.endpoint.marketdata.crypto.historical.bar.CryptoBarsResponse; +import net.jacobpeterson.alpaca.model.endpoint.marketdata.crypto.historical.quote.CryptoQuote; +import net.jacobpeterson.alpaca.model.endpoint.marketdata.crypto.historical.quote.CryptoQuotesResponse; +import net.jacobpeterson.alpaca.model.endpoint.marketdata.crypto.historical.quote.LatestCryptoQuoteResponse; +import net.jacobpeterson.alpaca.model.endpoint.marketdata.crypto.historical.trade.CryptoTrade; +import net.jacobpeterson.alpaca.model.endpoint.marketdata.crypto.historical.trade.CryptoTradesResponse; +import net.jacobpeterson.alpaca.model.endpoint.marketdata.crypto.historical.trade.LatestCryptoTradeResponse; import net.jacobpeterson.alpaca.model.endpoint.marketdata.crypto.historical.xbbo.XbboResponse; import net.jacobpeterson.alpaca.rest.AlpacaClient; import net.jacobpeterson.alpaca.rest.AlpacaClientException; @@ -41,7 +41,7 @@ public CryptoMarketDataEndpoint(AlpacaClient alpacaClient) { } /** - * Gets {@link Trade} historical data for the requested crypto symbol. + * Gets {@link CryptoTrade} historical data for the requested crypto symbol. * * @param symbol the symbol to query for * @param exchanges a {@link Collection} of {@link Exchange}s to filter by. null for all exchanges. @@ -53,11 +53,11 @@ public CryptoMarketDataEndpoint(AlpacaClient alpacaClient) { * is given * @param pageToken pagination token to continue from * - * @return the {@link TradesResponse} + * @return the {@link CryptoTradesResponse} * * @throws AlpacaClientException thrown for {@link AlpacaClientException}s */ - public TradesResponse getTrades(String symbol, Collection exchanges, ZonedDateTime start, + public CryptoTradesResponse getTrades(String symbol, Collection exchanges, ZonedDateTime start, ZonedDateTime end, Integer limit, String pageToken) throws AlpacaClientException { checkNotNull(symbol); @@ -91,20 +91,20 @@ public TradesResponse getTrades(String symbol, Collection exchanges, Z Request request = alpacaClient.requestBuilder(urlBuilder.build()) .get() .build(); - return alpacaClient.requestObject(request, TradesResponse.class); + return alpacaClient.requestObject(request, CryptoTradesResponse.class); } /** - * Gets the latest {@link Trade} for the requested security. + * Gets the latest {@link CryptoTrade} for the requested security. * * @param symbol the symbol to query for - * @param exchange the {@link Exchange} to filter the latest {@link Trade} by. + * @param exchange the {@link Exchange} to filter the latest {@link CryptoTrade} by. * - * @return the {@link LatestTradeResponse} + * @return the {@link LatestCryptoTradeResponse} * * @throws AlpacaClientException thrown for {@link AlpacaClientException}s */ - public LatestTradeResponse getLatestTrade(String symbol, Exchange exchange) throws AlpacaClientException { + public LatestCryptoTradeResponse getLatestTrade(String symbol, Exchange exchange) throws AlpacaClientException { checkNotNull(symbol); checkNotNull(exchange); @@ -119,11 +119,11 @@ public LatestTradeResponse getLatestTrade(String symbol, Exchange exchange) thro Request request = alpacaClient.requestBuilder(urlBuilder.build()) .get() .build(); - return alpacaClient.requestObject(request, LatestTradeResponse.class); + return alpacaClient.requestObject(request, LatestCryptoTradeResponse.class); } /** - * Gets {@link Quote} historical data for the requested crypto symbol. + * Gets {@link CryptoQuote} historical data for the requested crypto symbol. * * @param symbol the symbol to query for * @param exchanges a {@link Collection} of {@link Exchange}s to filter by. null for all exchanges. @@ -135,11 +135,11 @@ public LatestTradeResponse getLatestTrade(String symbol, Exchange exchange) thro * is given * @param pageToken pagination token to continue from * - * @return the {@link QuotesResponse} + * @return the {@link CryptoQuotesResponse} * * @throws AlpacaClientException thrown for {@link AlpacaClientException}s */ - public QuotesResponse getQuotes(String symbol, Collection exchanges, ZonedDateTime start, + public CryptoQuotesResponse getQuotes(String symbol, Collection exchanges, ZonedDateTime start, ZonedDateTime end, Integer limit, String pageToken) throws AlpacaClientException { checkNotNull(symbol); @@ -173,20 +173,20 @@ public QuotesResponse getQuotes(String symbol, Collection exchanges, Z Request request = alpacaClient.requestBuilder(urlBuilder.build()) .get() .build(); - return alpacaClient.requestObject(request, QuotesResponse.class); + return alpacaClient.requestObject(request, CryptoQuotesResponse.class); } /** - * Gets the latest {@link Quote} for the requested security. + * Gets the latest {@link CryptoQuote} for the requested security. * * @param symbol the symbol to query for - * @param exchange the {@link Exchange} to filter the latest {@link Quote} by. + * @param exchange the {@link Exchange} to filter the latest {@link CryptoQuote} by. * - * @return the {@link LatestQuoteResponse} + * @return the {@link LatestCryptoQuoteResponse} * * @throws AlpacaClientException thrown for {@link AlpacaClientException}s */ - public LatestQuoteResponse getLatestQuote(String symbol, Exchange exchange) throws AlpacaClientException { + public LatestCryptoQuoteResponse getLatestQuote(String symbol, Exchange exchange) throws AlpacaClientException { checkNotNull(symbol); checkNotNull(exchange); @@ -201,11 +201,11 @@ public LatestQuoteResponse getLatestQuote(String symbol, Exchange exchange) thro Request request = alpacaClient.requestBuilder(urlBuilder.build()) .get() .build(); - return alpacaClient.requestObject(request, LatestQuoteResponse.class); + return alpacaClient.requestObject(request, LatestCryptoQuoteResponse.class); } /** - * Gets {@link Bar} aggregate historical data for the requested crypto. + * Gets {@link CryptoBar} aggregate historical data for the requested crypto. * * @param symbol the symbol to query for * @param exchanges a {@link Collection} of {@link Exchange}s to filter by. null for all @@ -222,11 +222,11 @@ public LatestQuoteResponse getLatestQuote(String symbol, Exchange exchange) thro * BarTimePeriod#MINUTE} for this parameter and 15 for the * barTimePeriodDuration parameter. * - * @return the {@link BarsResponse} + * @return the {@link CryptoBarsResponse} * * @throws AlpacaClientException thrown for {@link AlpacaClientException}s */ - public BarsResponse getBars(String symbol, Collection exchanges, ZonedDateTime start, Integer limit, + public CryptoBarsResponse getBars(String symbol, Collection exchanges, ZonedDateTime start, Integer limit, String pageToken, int barTimePeriodDuration, BarTimePeriod barTimePeriod) throws AlpacaClientException { checkNotNull(symbol); checkNotNull(barTimePeriod); @@ -259,7 +259,7 @@ public BarsResponse getBars(String symbol, Collection exchanges, Zoned Request request = alpacaClient.requestBuilder(urlBuilder.build()) .get() .build(); - return alpacaClient.requestObject(request, BarsResponse.class); + return alpacaClient.requestObject(request, CryptoBarsResponse.class); } /** @@ -269,7 +269,7 @@ public BarsResponse getBars(String symbol, Collection exchanges, Zoned * @param exchanges a {@link Collection} of {@link Exchange}s to filter by. If null, then only the * exchanges that can be traded on Alpaca are included in the calculation. * - * @return the {@link LatestQuoteResponse} + * @return the {@link XbboResponse} * * @throws AlpacaClientException thrown for {@link AlpacaClientException}s */ diff --git a/src/main/java/net/jacobpeterson/alpaca/rest/endpoint/marketdata/StockMarketDataEndpoint.java b/src/main/java/net/jacobpeterson/alpaca/rest/endpoint/marketdata/stock/StockMarketDataEndpoint.java similarity index 84% rename from src/main/java/net/jacobpeterson/alpaca/rest/endpoint/marketdata/StockMarketDataEndpoint.java rename to src/main/java/net/jacobpeterson/alpaca/rest/endpoint/marketdata/stock/StockMarketDataEndpoint.java index f203a5d2..fe8a846e 100644 --- a/src/main/java/net/jacobpeterson/alpaca/rest/endpoint/marketdata/StockMarketDataEndpoint.java +++ b/src/main/java/net/jacobpeterson/alpaca/rest/endpoint/marketdata/stock/StockMarketDataEndpoint.java @@ -1,17 +1,17 @@ -package net.jacobpeterson.alpaca.rest.endpoint.marketdata; +package net.jacobpeterson.alpaca.rest.endpoint.marketdata.stock; import com.google.gson.reflect.TypeToken; -import net.jacobpeterson.alpaca.model.endpoint.marketdata.common.enums.BarTimePeriod; -import net.jacobpeterson.alpaca.model.endpoint.marketdata.stock.historical.bar.Bar; -import net.jacobpeterson.alpaca.model.endpoint.marketdata.stock.historical.bar.BarsResponse; +import net.jacobpeterson.alpaca.model.endpoint.marketdata.common.historical.bar.enums.BarTimePeriod; +import net.jacobpeterson.alpaca.model.endpoint.marketdata.stock.historical.bar.StockBar; +import net.jacobpeterson.alpaca.model.endpoint.marketdata.stock.historical.bar.StockBarsResponse; import net.jacobpeterson.alpaca.model.endpoint.marketdata.stock.historical.bar.enums.BarAdjustment; -import net.jacobpeterson.alpaca.model.endpoint.marketdata.stock.historical.quote.LatestQuoteResponse; -import net.jacobpeterson.alpaca.model.endpoint.marketdata.stock.historical.quote.Quote; -import net.jacobpeterson.alpaca.model.endpoint.marketdata.stock.historical.quote.QuotesResponse; +import net.jacobpeterson.alpaca.model.endpoint.marketdata.stock.historical.quote.LatestStockQuoteResponse; +import net.jacobpeterson.alpaca.model.endpoint.marketdata.stock.historical.quote.StockQuote; +import net.jacobpeterson.alpaca.model.endpoint.marketdata.stock.historical.quote.StockQuotesResponse; import net.jacobpeterson.alpaca.model.endpoint.marketdata.stock.historical.snapshot.Snapshot; -import net.jacobpeterson.alpaca.model.endpoint.marketdata.stock.historical.trade.LatestTradeResponse; -import net.jacobpeterson.alpaca.model.endpoint.marketdata.stock.historical.trade.Trade; -import net.jacobpeterson.alpaca.model.endpoint.marketdata.stock.historical.trade.TradesResponse; +import net.jacobpeterson.alpaca.model.endpoint.marketdata.stock.historical.trade.LatestStockTradeResponse; +import net.jacobpeterson.alpaca.model.endpoint.marketdata.stock.historical.trade.StockTrade; +import net.jacobpeterson.alpaca.model.endpoint.marketdata.stock.historical.trade.StockTradesResponse; import net.jacobpeterson.alpaca.rest.AlpacaClient; import net.jacobpeterson.alpaca.rest.AlpacaClientException; import net.jacobpeterson.alpaca.rest.endpoint.AlpacaEndpoint; @@ -44,7 +44,7 @@ public StockMarketDataEndpoint(AlpacaClient alpacaClient) { } /** - * Gets {@link Trade} historical data for the requested security. + * Gets {@link StockTrade} historical data for the requested security. * * @param symbol the symbol to query for * @param start filter data equal to or after this {@link ZonedDateTime}. Fractions of a second are not @@ -55,11 +55,11 @@ public StockMarketDataEndpoint(AlpacaClient alpacaClient) { * is given * @param pageToken pagination token to continue from * - * @return the {@link TradesResponse} + * @return the {@link StockTradesResponse} * * @throws AlpacaClientException thrown for {@link AlpacaClientException}s */ - public TradesResponse getTrades(String symbol, ZonedDateTime start, ZonedDateTime end, Integer limit, + public StockTradesResponse getTrades(String symbol, ZonedDateTime start, ZonedDateTime end, Integer limit, String pageToken) throws AlpacaClientException { checkNotNull(symbol); checkNotNull(start); @@ -84,19 +84,19 @@ public TradesResponse getTrades(String symbol, ZonedDateTime start, ZonedDateTim Request request = alpacaClient.requestBuilder(urlBuilder.build()) .get() .build(); - return alpacaClient.requestObject(request, TradesResponse.class); + return alpacaClient.requestObject(request, StockTradesResponse.class); } /** - * Gets the latest {@link Trade} for the requested security. + * Gets the latest {@link StockTrade} for the requested security. * * @param symbol the symbol to query for * - * @return the {@link LatestTradeResponse} + * @return the {@link LatestStockTradeResponse} * * @throws AlpacaClientException thrown for {@link AlpacaClientException}s */ - public LatestTradeResponse getLatestTrade(String symbol) throws AlpacaClientException { + public LatestStockTradeResponse getLatestTrade(String symbol) throws AlpacaClientException { checkNotNull(symbol); HttpUrl.Builder urlBuilder = alpacaClient.urlBuilder() @@ -108,11 +108,11 @@ public LatestTradeResponse getLatestTrade(String symbol) throws AlpacaClientExce Request request = alpacaClient.requestBuilder(urlBuilder.build()) .get() .build(); - return alpacaClient.requestObject(request, LatestTradeResponse.class); + return alpacaClient.requestObject(request, LatestStockTradeResponse.class); } /** - * Gets {@link Quote} (NBBO or National Best Bid and Offer) historical data for the requested security. + * Gets {@link StockQuote} (NBBO or National Best Bid and Offer) historical data for the requested security. * * @param symbol the symbol to query for * @param start filter data equal to or after this {@link ZonedDateTime}. Fractions of a second are not @@ -123,11 +123,11 @@ public LatestTradeResponse getLatestTrade(String symbol) throws AlpacaClientExce * is given * @param pageToken pagination token to continue from * - * @return the {@link QuotesResponse} + * @return the {@link StockQuotesResponse} * * @throws AlpacaClientException thrown for {@link AlpacaClientException}s */ - public QuotesResponse getQuotes(String symbol, ZonedDateTime start, ZonedDateTime end, Integer limit, + public StockQuotesResponse getQuotes(String symbol, ZonedDateTime start, ZonedDateTime end, Integer limit, String pageToken) throws AlpacaClientException { checkNotNull(symbol); checkNotNull(start); @@ -152,19 +152,19 @@ public QuotesResponse getQuotes(String symbol, ZonedDateTime start, ZonedDateTim Request request = alpacaClient.requestBuilder(urlBuilder.build()) .get() .build(); - return alpacaClient.requestObject(request, QuotesResponse.class); + return alpacaClient.requestObject(request, StockQuotesResponse.class); } /** - * Gets the latest {@link Quote} for the requested security. + * Gets the latest {@link StockQuote} for the requested security. * * @param symbol the symbol to query for * - * @return the {@link LatestQuoteResponse} + * @return the {@link LatestStockQuoteResponse} * * @throws AlpacaClientException thrown for {@link AlpacaClientException}s */ - public LatestQuoteResponse getLatestQuote(String symbol) throws AlpacaClientException { + public LatestStockQuoteResponse getLatestQuote(String symbol) throws AlpacaClientException { checkNotNull(symbol); HttpUrl.Builder urlBuilder = alpacaClient.urlBuilder() @@ -176,11 +176,11 @@ public LatestQuoteResponse getLatestQuote(String symbol) throws AlpacaClientExce Request request = alpacaClient.requestBuilder(urlBuilder.build()) .get() .build(); - return alpacaClient.requestObject(request, LatestQuoteResponse.class); + return alpacaClient.requestObject(request, LatestStockQuoteResponse.class); } /** - * Gets {@link Bar} aggregate historical data for the requested security. + * Gets {@link StockBar} aggregate historical data for the requested security. * * @param symbol the symbol to query for * @param start filter data equal to or after this {@link ZonedDateTime}. Fractions of a second are @@ -199,11 +199,11 @@ public LatestQuoteResponse getLatestQuote(String symbol) throws AlpacaClientExce * @param barAdjustment specifies the corporate action adjustment for the stocks. Default value is {@link * BarAdjustment#RAW}. * - * @return the {@link BarsResponse} + * @return the {@link StockBarsResponse} * * @throws AlpacaClientException thrown for {@link AlpacaClientException}s */ - public BarsResponse getBars(String symbol, ZonedDateTime start, ZonedDateTime end, Integer limit, + public StockBarsResponse getBars(String symbol, ZonedDateTime start, ZonedDateTime end, Integer limit, String pageToken, int barTimePeriodDuration, BarTimePeriod barTimePeriod, BarAdjustment barAdjustment) throws AlpacaClientException { checkNotNull(symbol); @@ -236,7 +236,7 @@ public BarsResponse getBars(String symbol, ZonedDateTime start, ZonedDateTime en Request request = alpacaClient.requestBuilder(urlBuilder.build()) .get() .build(); - return alpacaClient.requestObject(request, BarsResponse.class); + return alpacaClient.requestObject(request, StockBarsResponse.class); } /** diff --git a/src/main/java/net/jacobpeterson/alpaca/rest/endpoint/OrdersEndpoint.java b/src/main/java/net/jacobpeterson/alpaca/rest/endpoint/orders/OrdersEndpoint.java similarity index 98% rename from src/main/java/net/jacobpeterson/alpaca/rest/endpoint/OrdersEndpoint.java rename to src/main/java/net/jacobpeterson/alpaca/rest/endpoint/orders/OrdersEndpoint.java index 8715d1fc..90159bb4 100644 --- a/src/main/java/net/jacobpeterson/alpaca/rest/endpoint/OrdersEndpoint.java +++ b/src/main/java/net/jacobpeterson/alpaca/rest/endpoint/orders/OrdersEndpoint.java @@ -1,19 +1,20 @@ -package net.jacobpeterson.alpaca.rest.endpoint; +package net.jacobpeterson.alpaca.rest.endpoint.orders; import com.google.gson.JsonObject; import com.google.gson.JsonPrimitive; import com.google.gson.reflect.TypeToken; import net.jacobpeterson.alpaca.model.endpoint.common.enums.SortDirection; -import net.jacobpeterson.alpaca.model.endpoint.order.CancelledOrder; -import net.jacobpeterson.alpaca.model.endpoint.order.Order; -import net.jacobpeterson.alpaca.model.endpoint.order.enums.CurrentOrderStatus; -import net.jacobpeterson.alpaca.model.endpoint.order.enums.OrderClass; -import net.jacobpeterson.alpaca.model.endpoint.order.enums.OrderSide; -import net.jacobpeterson.alpaca.model.endpoint.order.enums.OrderStatus; -import net.jacobpeterson.alpaca.model.endpoint.order.enums.OrderTimeInForce; -import net.jacobpeterson.alpaca.model.endpoint.order.enums.OrderType; +import net.jacobpeterson.alpaca.model.endpoint.orders.CancelledOrder; +import net.jacobpeterson.alpaca.model.endpoint.orders.Order; +import net.jacobpeterson.alpaca.model.endpoint.orders.enums.CurrentOrderStatus; +import net.jacobpeterson.alpaca.model.endpoint.orders.enums.OrderClass; +import net.jacobpeterson.alpaca.model.endpoint.orders.enums.OrderSide; +import net.jacobpeterson.alpaca.model.endpoint.orders.enums.OrderStatus; +import net.jacobpeterson.alpaca.model.endpoint.orders.enums.OrderTimeInForce; +import net.jacobpeterson.alpaca.model.endpoint.orders.enums.OrderType; import net.jacobpeterson.alpaca.rest.AlpacaClient; import net.jacobpeterson.alpaca.rest.AlpacaClientException; +import net.jacobpeterson.alpaca.rest.endpoint.AlpacaEndpoint; import net.jacobpeterson.alpaca.util.format.FormatUtil; import net.jacobpeterson.alpaca.util.okhttp.JSONBodyBuilder; import okhttp3.HttpUrl; diff --git a/src/main/java/net/jacobpeterson/alpaca/rest/endpoint/PortfolioHistoryEndpoint.java b/src/main/java/net/jacobpeterson/alpaca/rest/endpoint/portfoliohistory/PortfolioHistoryEndpoint.java similarity index 97% rename from src/main/java/net/jacobpeterson/alpaca/rest/endpoint/PortfolioHistoryEndpoint.java rename to src/main/java/net/jacobpeterson/alpaca/rest/endpoint/portfoliohistory/PortfolioHistoryEndpoint.java index 1d01c19d..722299f0 100644 --- a/src/main/java/net/jacobpeterson/alpaca/rest/endpoint/PortfolioHistoryEndpoint.java +++ b/src/main/java/net/jacobpeterson/alpaca/rest/endpoint/portfoliohistory/PortfolioHistoryEndpoint.java @@ -1,4 +1,4 @@ -package net.jacobpeterson.alpaca.rest.endpoint; +package net.jacobpeterson.alpaca.rest.endpoint.portfoliohistory; import net.jacobpeterson.alpaca.model.endpoint.portfoliohistory.PortfolioHistory; import net.jacobpeterson.alpaca.model.endpoint.portfoliohistory.PortfolioHistoryDataPoint; @@ -7,6 +7,7 @@ import net.jacobpeterson.alpaca.model.endpoint.portfoliohistory.enums.PortfolioTimeFrame; import net.jacobpeterson.alpaca.rest.AlpacaClient; import net.jacobpeterson.alpaca.rest.AlpacaClientException; +import net.jacobpeterson.alpaca.rest.endpoint.AlpacaEndpoint; import net.jacobpeterson.alpaca.util.format.FormatUtil; import okhttp3.HttpUrl; import okhttp3.Request; diff --git a/src/main/java/net/jacobpeterson/alpaca/rest/endpoint/PositionsEndpoint.java b/src/main/java/net/jacobpeterson/alpaca/rest/endpoint/positions/PositionsEndpoint.java similarity index 92% rename from src/main/java/net/jacobpeterson/alpaca/rest/endpoint/PositionsEndpoint.java rename to src/main/java/net/jacobpeterson/alpaca/rest/endpoint/positions/PositionsEndpoint.java index 26d72861..cccfdfb8 100644 --- a/src/main/java/net/jacobpeterson/alpaca/rest/endpoint/PositionsEndpoint.java +++ b/src/main/java/net/jacobpeterson/alpaca/rest/endpoint/positions/PositionsEndpoint.java @@ -1,12 +1,13 @@ -package net.jacobpeterson.alpaca.rest.endpoint; +package net.jacobpeterson.alpaca.rest.endpoint.positions; import com.google.gson.reflect.TypeToken; -import net.jacobpeterson.alpaca.model.endpoint.asset.Asset; -import net.jacobpeterson.alpaca.model.endpoint.order.Order; -import net.jacobpeterson.alpaca.model.endpoint.position.ClosePositionOrder; -import net.jacobpeterson.alpaca.model.endpoint.position.Position; +import net.jacobpeterson.alpaca.model.endpoint.assets.Asset; +import net.jacobpeterson.alpaca.model.endpoint.orders.Order; +import net.jacobpeterson.alpaca.model.endpoint.positions.ClosePositionOrder; +import net.jacobpeterson.alpaca.model.endpoint.positions.Position; import net.jacobpeterson.alpaca.rest.AlpacaClient; import net.jacobpeterson.alpaca.rest.AlpacaClientException; +import net.jacobpeterson.alpaca.rest.endpoint.AlpacaEndpoint; import okhttp3.HttpUrl; import okhttp3.Request; diff --git a/src/main/java/net/jacobpeterson/alpaca/rest/endpoint/WatchlistEndpoint.java b/src/main/java/net/jacobpeterson/alpaca/rest/endpoint/watchlist/WatchlistEndpoint.java similarity index 97% rename from src/main/java/net/jacobpeterson/alpaca/rest/endpoint/WatchlistEndpoint.java rename to src/main/java/net/jacobpeterson/alpaca/rest/endpoint/watchlist/WatchlistEndpoint.java index aeadb183..8f8762eb 100644 --- a/src/main/java/net/jacobpeterson/alpaca/rest/endpoint/WatchlistEndpoint.java +++ b/src/main/java/net/jacobpeterson/alpaca/rest/endpoint/watchlist/WatchlistEndpoint.java @@ -1,11 +1,12 @@ -package net.jacobpeterson.alpaca.rest.endpoint; +package net.jacobpeterson.alpaca.rest.endpoint.watchlist; import com.google.gson.JsonArray; import com.google.gson.reflect.TypeToken; -import net.jacobpeterson.alpaca.model.endpoint.asset.Asset; +import net.jacobpeterson.alpaca.model.endpoint.assets.Asset; import net.jacobpeterson.alpaca.model.endpoint.watchlist.Watchlist; import net.jacobpeterson.alpaca.rest.AlpacaClient; import net.jacobpeterson.alpaca.rest.AlpacaClientException; +import net.jacobpeterson.alpaca.rest.endpoint.AlpacaEndpoint; import net.jacobpeterson.alpaca.util.okhttp.JSONBodyBuilder; import okhttp3.HttpUrl; import okhttp3.Request; diff --git a/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/MarketDataListener.java b/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/MarketDataListener.java index 09bf4266..0f760d7e 100644 --- a/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/MarketDataListener.java +++ b/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/MarketDataListener.java @@ -1,7 +1,7 @@ package net.jacobpeterson.alpaca.websocket.marketdata; -import net.jacobpeterson.alpaca.model.endpoint.marketdata.stock.realtime.MarketDataMessage; -import net.jacobpeterson.alpaca.model.endpoint.marketdata.stock.realtime.enums.MarketDataMessageType; +import net.jacobpeterson.alpaca.model.endpoint.marketdata.common.realtime.MarketDataMessage; +import net.jacobpeterson.alpaca.model.endpoint.marketdata.common.realtime.enums.MarketDataMessageType; import net.jacobpeterson.alpaca.websocket.AlpacaWebsocketMessageListener; /** diff --git a/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/MarketDataWebsocket.java b/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/MarketDataWebsocket.java index 28cce551..836a452a 100644 --- a/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/MarketDataWebsocket.java +++ b/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/MarketDataWebsocket.java @@ -5,15 +5,14 @@ import com.google.gson.JsonElement; import com.google.gson.JsonObject; import com.google.gson.JsonParser; -import net.jacobpeterson.alpaca.model.endpoint.marketdata.stock.realtime.MarketDataMessage; -import net.jacobpeterson.alpaca.model.endpoint.marketdata.stock.realtime.bar.BarMessage; -import net.jacobpeterson.alpaca.model.endpoint.marketdata.stock.realtime.control.ErrorMessage; -import net.jacobpeterson.alpaca.model.endpoint.marketdata.stock.realtime.control.SubscriptionsMessage; -import net.jacobpeterson.alpaca.model.endpoint.marketdata.stock.realtime.control.SuccessMessage; -import net.jacobpeterson.alpaca.model.endpoint.marketdata.stock.realtime.enums.MarketDataMessageType; -import net.jacobpeterson.alpaca.model.endpoint.marketdata.stock.realtime.quote.QuoteMessage; -import net.jacobpeterson.alpaca.model.endpoint.marketdata.stock.realtime.trade.TradeMessage; -import net.jacobpeterson.alpaca.model.properties.DataAPIType; +import net.jacobpeterson.alpaca.model.endpoint.marketdata.common.realtime.MarketDataMessage; +import net.jacobpeterson.alpaca.model.endpoint.marketdata.common.realtime.bar.BarMessage; +import net.jacobpeterson.alpaca.model.endpoint.marketdata.common.realtime.control.ErrorMessage; +import net.jacobpeterson.alpaca.model.endpoint.marketdata.common.realtime.control.SubscriptionsMessage; +import net.jacobpeterson.alpaca.model.endpoint.marketdata.common.realtime.control.SuccessMessage; +import net.jacobpeterson.alpaca.model.endpoint.marketdata.common.realtime.enums.MarketDataMessageType; +import net.jacobpeterson.alpaca.model.endpoint.marketdata.common.realtime.quote.QuoteMessage; +import net.jacobpeterson.alpaca.model.endpoint.marketdata.common.realtime.trade.TradeMessage; import net.jacobpeterson.alpaca.websocket.AlpacaWebsocket; import okhttp3.HttpUrl; import okhttp3.OkHttpClient; @@ -22,6 +21,7 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import java.lang.reflect.Type; import java.util.Arrays; import java.util.Collection; import java.util.HashSet; @@ -37,10 +37,11 @@ /** * {@link MarketDataWebsocket} is an {@link AlpacaWebsocket} implementation and provides the {@link * MarketDataWebsocketInterface} interface for - * Real-time - * Market Data + * Realtime Market Data for both crypto + * and stocks. */ -public class MarketDataWebsocket extends AlpacaWebsocket +public abstract class MarketDataWebsocket + extends AlpacaWebsocket implements MarketDataWebsocketInterface { private static final Logger LOGGER = LoggerFactory.getLogger(MarketDataWebsocket.class); @@ -55,18 +56,17 @@ public class MarketDataWebsocket extends AlpacaWebsocketdataAPIType
. + * Creates a {@link HttpUrl} for {@link MarketDataWebsocket} with the given websocketURLPathSegments. * - * @param dataAPIType the {@link DataAPIType} + * @param websocketURLPathSegments the websocket URL path segments * * @return a {@link HttpUrl} */ - private static HttpUrl createWebsocketURL(DataAPIType dataAPIType) { + private static HttpUrl createWebsocketURL(String websocketURLPathSegments) { return new HttpUrl.Builder() .scheme("https") // HttpUrl.Builder doesn't recognize "wss" scheme, but "https" works fine .host("stream.data.alpaca.markets") - .addPathSegment("v2") - .addPathSegment(dataAPIType.toString()) + .addPathSegments(websocketURLPathSegments) .build(); } @@ -74,23 +74,37 @@ private static HttpUrl createWebsocketURL(DataAPIType dataAPIType) { private final Set subscribedTrades; private final Set subscribedQuotes; private final Set subscribedBars; + private final Type tradeClassType; + private final Type quoteClassType; + private final Type barClassType; /** * Instantiates a new {@link MarketDataWebsocket}. * - * @param okHttpClient the {@link OkHttpClient} - * @param dataAPIType the {@link DataAPIType} - * @param keyID the key ID - * @param secretKey the secret key + * @param okHttpClient the {@link OkHttpClient} + * @param websocketURLPathSegments the websocket URL path segments + * @param websocketMarketDataTypeName the websocket market data type name {@link String} + * @param keyID the key ID + * @param secretKey the secret key + * @param tradeClass the {@link TradeMessage} {@link Class} to deserialize data into + * @param quoteClass the {@link QuoteMessage} {@link Class} to deserialize data into + * @param barClass the {@link BarMessage} {@link Class} to deserialize data into */ - public MarketDataWebsocket(OkHttpClient okHttpClient, DataAPIType dataAPIType, - String keyID, String secretKey) { - super(okHttpClient, createWebsocketURL(dataAPIType), "Market Data", keyID, secretKey, null); + public MarketDataWebsocket(OkHttpClient okHttpClient, String websocketURLPathSegments, + String websocketMarketDataTypeName, String keyID, String secretKey, + Class tradeClass, Class quoteClass, + Class barClass) { + super(okHttpClient, createWebsocketURL(websocketURLPathSegments), websocketMarketDataTypeName + " Market Data", + keyID, secretKey, null); listenedMarketDataMessageTypes = new HashSet<>(); subscribedTrades = new HashSet<>(); subscribedQuotes = new HashSet<>(); subscribedBars = new HashSet<>(); + + this.tradeClassType = tradeClass; + this.quoteClassType = quoteClass; + this.barClassType = barClass; } @Override @@ -139,6 +153,8 @@ protected void sendAuthenticationMessage() { // This websocket uses string frames and not binary frames. @Override public void onMessage(@NotNull WebSocket webSocket, @NotNull String message) { + LOGGER.trace("{}", message); + JsonElement messageElement = JsonParser.parseString(message); checkState(messageElement instanceof JsonArray, "Message must be a JsonArray! Received: %s", messageElement); @@ -198,13 +214,13 @@ public void onMessage(@NotNull WebSocket webSocket, @NotNull String message) { subscribedBars); break; case TRADE: - marketDataMessage = GSON.fromJson(messageObject, TradeMessage.class); + marketDataMessage = GSON.fromJson(messageObject, tradeClassType); break; case QUOTE: - marketDataMessage = GSON.fromJson(messageObject, QuoteMessage.class); + marketDataMessage = GSON.fromJson(messageObject, quoteClassType); break; case BAR: - marketDataMessage = GSON.fromJson(messageObject, BarMessage.class); + marketDataMessage = GSON.fromJson(messageObject, barClassType); break; default: LOGGER.error("Message type {} not implemented!", marketDataMessageType); diff --git a/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/MarketDataWebsocketInterface.java b/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/MarketDataWebsocketInterface.java index 2eb52e63..5ffcaf3c 100644 --- a/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/MarketDataWebsocketInterface.java +++ b/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/MarketDataWebsocketInterface.java @@ -1,10 +1,10 @@ package net.jacobpeterson.alpaca.websocket.marketdata; -import net.jacobpeterson.alpaca.model.endpoint.marketdata.stock.realtime.MarketDataMessage; -import net.jacobpeterson.alpaca.model.endpoint.marketdata.stock.realtime.control.ErrorMessage; -import net.jacobpeterson.alpaca.model.endpoint.marketdata.stock.realtime.control.SubscriptionsMessage; -import net.jacobpeterson.alpaca.model.endpoint.marketdata.stock.realtime.control.SuccessMessage; -import net.jacobpeterson.alpaca.model.endpoint.marketdata.stock.realtime.enums.MarketDataMessageType; +import net.jacobpeterson.alpaca.model.endpoint.marketdata.common.realtime.MarketDataMessage; +import net.jacobpeterson.alpaca.model.endpoint.marketdata.common.realtime.control.ErrorMessage; +import net.jacobpeterson.alpaca.model.endpoint.marketdata.common.realtime.control.SubscriptionsMessage; +import net.jacobpeterson.alpaca.model.endpoint.marketdata.common.realtime.control.SuccessMessage; +import net.jacobpeterson.alpaca.model.endpoint.marketdata.common.realtime.enums.MarketDataMessageType; import net.jacobpeterson.alpaca.websocket.AlpacaWebsocketInterface; import java.util.Collection; diff --git a/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/crypto/CryptoMarketDataWebsocket.java b/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/crypto/CryptoMarketDataWebsocket.java new file mode 100644 index 00000000..fa61d1ec --- /dev/null +++ b/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/crypto/CryptoMarketDataWebsocket.java @@ -0,0 +1,27 @@ +package net.jacobpeterson.alpaca.websocket.marketdata.crypto; + +import net.jacobpeterson.alpaca.model.endpoint.marketdata.crypto.realtime.bar.CryptoBarMessage; +import net.jacobpeterson.alpaca.model.endpoint.marketdata.crypto.realtime.quote.CryptoQuoteMessage; +import net.jacobpeterson.alpaca.model.endpoint.marketdata.crypto.realtime.trade.CryptoTradeMessage; +import net.jacobpeterson.alpaca.websocket.marketdata.MarketDataWebsocket; +import okhttp3.OkHttpClient; + +/** + * {@link CryptoMarketDataWebsocket} is a {@link MarketDataWebsocket} for + * Realtime + * Crypto Market Data + */ +public class CryptoMarketDataWebsocket extends MarketDataWebsocket { + + /** + * Instantiates a new {@link CryptoMarketDataWebsocket}. + * + * @param okHttpClient the {@link OkHttpClient} + * @param keyID the key ID + * @param secretKey the secret key + */ + public CryptoMarketDataWebsocket(OkHttpClient okHttpClient, String keyID, String secretKey) { + super(okHttpClient, "v1beta1/crypto", "Crypto", keyID, secretKey, CryptoTradeMessage.class, + CryptoQuoteMessage.class, CryptoBarMessage.class); + } +} diff --git a/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/stock/StockMarketDataWebsocket.java b/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/stock/StockMarketDataWebsocket.java new file mode 100644 index 00000000..4b897f0d --- /dev/null +++ b/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/stock/StockMarketDataWebsocket.java @@ -0,0 +1,30 @@ +package net.jacobpeterson.alpaca.websocket.marketdata.stock; + +import net.jacobpeterson.alpaca.model.endpoint.marketdata.stock.realtime.bar.StockBarMessage; +import net.jacobpeterson.alpaca.model.endpoint.marketdata.stock.realtime.quote.StockQuoteMessage; +import net.jacobpeterson.alpaca.model.endpoint.marketdata.stock.realtime.trade.StockTradeMessage; +import net.jacobpeterson.alpaca.model.properties.DataAPIType; +import net.jacobpeterson.alpaca.websocket.marketdata.MarketDataWebsocket; +import okhttp3.OkHttpClient; + +/** + * {@link StockMarketDataWebsocket} is a {@link MarketDataWebsocket} for + * Realtime + * Stock Market Data + */ +public class StockMarketDataWebsocket extends MarketDataWebsocket { + + /** + * Instantiates a new {@link StockMarketDataWebsocket}. + * + * @param okHttpClient the {@link OkHttpClient} + * @param dataAPIType the {@link DataAPIType} + * @param keyID the key ID + * @param secretKey the secret key + */ + public StockMarketDataWebsocket(OkHttpClient okHttpClient, DataAPIType dataAPIType, + String keyID, String secretKey) { + super(okHttpClient, "v2/" + dataAPIType.toString(), "Stock", keyID, secretKey, StockTradeMessage.class, + StockQuoteMessage.class, StockBarMessage.class); + } +} diff --git a/src/test/java/net/jacobpeterson/alpaca/test/live/AlpacaAPITest.java b/src/test/java/net/jacobpeterson/alpaca/test/live/AlpacaAPITest.java index dafd1016..f9760235 100644 --- a/src/test/java/net/jacobpeterson/alpaca/test/live/AlpacaAPITest.java +++ b/src/test/java/net/jacobpeterson/alpaca/test/live/AlpacaAPITest.java @@ -11,14 +11,14 @@ import net.jacobpeterson.alpaca.model.endpoint.accountconfiguration.enums.TradeConfirmEmail; import net.jacobpeterson.alpaca.model.endpoint.clock.Clock; import net.jacobpeterson.alpaca.model.endpoint.common.enums.SortDirection; -import net.jacobpeterson.alpaca.model.endpoint.order.Order; -import net.jacobpeterson.alpaca.model.endpoint.order.enums.CurrentOrderStatus; +import net.jacobpeterson.alpaca.model.endpoint.orders.Order; +import net.jacobpeterson.alpaca.model.endpoint.orders.enums.CurrentOrderStatus; import net.jacobpeterson.alpaca.rest.AlpacaClientException; -import net.jacobpeterson.alpaca.rest.endpoint.AccountActivitiesEndpoint; -import net.jacobpeterson.alpaca.rest.endpoint.AccountConfigurationEndpoint; -import net.jacobpeterson.alpaca.rest.endpoint.AccountEndpoint; -import net.jacobpeterson.alpaca.rest.endpoint.ClockEndpoint; -import net.jacobpeterson.alpaca.rest.endpoint.OrdersEndpoint; +import net.jacobpeterson.alpaca.rest.endpoint.account.AccountEndpoint; +import net.jacobpeterson.alpaca.rest.endpoint.accountactivities.AccountActivitiesEndpoint; +import net.jacobpeterson.alpaca.rest.endpoint.accountconfiguration.AccountConfigurationEndpoint; +import net.jacobpeterson.alpaca.rest.endpoint.clock.ClockEndpoint; +import net.jacobpeterson.alpaca.rest.endpoint.orders.OrdersEndpoint; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.BeforeEach; From e39ce37b58f1d40ded1ad6a6fb220978469bfd40 Mon Sep 17 00:00:00 2001 From: Jacob Peterson <14127217+Petersoj@users.noreply.github.com> Date: Wed, 22 Dec 2021 13:22:34 -0700 Subject: [PATCH 172/271] Update README, fix some market_data schema JSONs --- README.md | 68 ++++++++++++------- .../common/historical/bar/bar.json | 3 - .../common/realtime/bar/bar_message.json | 10 +++ .../realtime/bar/crypto_bar_message.json | 10 --- .../marketdata/MarketDataWebsocket.java | 2 - .../streaming/StreamingWebsocket.java | 6 +- 6 files changed, 54 insertions(+), 45 deletions(-) diff --git a/README.md b/README.md index ffbea854..59df6b86 100644 --- a/README.md +++ b/README.md @@ -86,7 +86,7 @@ See the [OkHttpClient Documentation](https://square.github.io/okhttp/4.x/okhttp/ ## [`AlpacaClientException`](src/main/java/net/jacobpeterson/alpaca/rest/AlpacaClientException.java) [`AlpacaClientException`](src/main/java/net/jacobpeterson/alpaca/rest/AlpacaClientException.java) is thrown anytime an exception occurs when using various [`AlpacaEndpoints`](src/main/java/net/jacobpeterson/alpaca/rest/endpoint/AlpacaEndpoint.java). It should be caught and handled within your trading algorithm application accordingly. -## [`AccountEndpoint`](src/main/java/net/jacobpeterson/alpaca/rest/endpoint/AccountEndpoint.java) +## [`AccountEndpoint`](src/main/java/net/jacobpeterson/alpaca/rest/endpoint/account/AccountEndpoint.java) The Account API serves important information related to an account, including account status, funds available for trade, funds available for withdrawal, and various flags relevant to an account's ability to trade. Example usage: @@ -100,14 +100,14 @@ try { } ``` -## [`CryptoMarketDataEndpoint`](src/main/java/net/jacobpeterson/alpaca/rest/endpoint/marketdata/CryptoMarketDataEndpoint.java) +## [`CryptoMarketDataEndpoint`](src/main/java/net/jacobpeterson/alpaca/rest/endpoint/marketdata/crypto/CryptoMarketDataEndpoint.java) Alpaca provides cryptocurrency data from multiple venues/exchanges, namely: Coinbase, ErisX, and FTX. Example usage: ```java try { // Get BTCUSD 50 one-hour bars starting on 12/18/2021 from Coinbase and print them out - BarsResponse btcBarsResponse = alpacaAPI.cryptoMarketData().getBars( + CryptoBarsResponse btcBarsResponse = alpacaAPI.cryptoMarketData().getBars( "BTCUSD", Arrays.asList(Exchange.COINBASE), ZonedDateTime.of(2021, 12, 18, 0, 0, 0, 0, ZoneId.of("America/New_York")), @@ -127,7 +127,7 @@ try { } ``` -## [`StockMarketDataEndpoint`](src/main/java/net/jacobpeterson/alpaca/rest/endpoint/marketdata/StockMarketDataEndpoint.java) +## [`StockMarketDataEndpoint`](src/main/java/net/jacobpeterson/alpaca/rest/endpoint/marketdata/stock/StockMarketDataEndpoint.java) The Data API v2 provides market data through an easy-to-use API for historical stock market data. Example usage: @@ -135,7 +135,7 @@ Example usage: try { // Get AAPL one hour, split-adjusted bars from 7/6/2021 market open // to 7/8/2021 market close and print them out - BarsResponse aaplBarsResponse = alpacaAPI.stockMarketData().getBars( + StockBarsResponse aaplBarsResponse = alpacaAPI.stockMarketData().getBars( "AAPL", ZonedDateTime.of(2021, 7, 6, 9, 30, 0, 0, ZoneId.of("America/New_York")), ZonedDateTime.of(2021, 7, 8, 12 + 4, 0, 0, 0, ZoneId.of("America/New_York")), @@ -147,7 +147,7 @@ try { aaplBarsResponse.getBars().forEach(System.out::println); // Get AAPL first 10 trades on 7/8/2021 at market open and print them out - TradesResponse aaplTradesResponse = alpacaAPI.stockMarketData().getTrades( + StockTradesResponse aaplTradesResponse = alpacaAPI.stockMarketData().getTrades( "AAPL", ZonedDateTime.of(2021, 7, 8, 9, 30, 0, 0, ZoneId.of("America/New_York")), ZonedDateTime.of(2021, 7, 8, 9, 31, 0, 0, ZoneId.of("America/New_York")), @@ -169,7 +169,7 @@ try { } ``` -## [`OrdersEndpoint`](src/main/java/net/jacobpeterson/alpaca/rest/endpoint/OrdersEndpoint.java) +## [`OrdersEndpoint`](src/main/java/net/jacobpeterson/alpaca/rest/endpoint/orders/OrdersEndpoint.java) The Orders API allows a user to monitor, place, and cancel their orders with Alpaca. Example usage: @@ -229,7 +229,7 @@ try { } ``` -## [`PositionsEndpoint`](src/main/java/net/jacobpeterson/alpaca/rest/endpoint/PositionsEndpoint.java) +## [`PositionsEndpoint`](src/main/java/net/jacobpeterson/alpaca/rest/endpoint/positions/PositionsEndpoint.java) The Positions API provides information about an account's current open positions. Example usage: @@ -250,7 +250,7 @@ try { } ``` -## [`AssetsEndpoint`](src/main/java/net/jacobpeterson/alpaca/rest/endpoint/AssetsEndpoint.java) +## [`AssetsEndpoint`](src/main/java/net/jacobpeterson/alpaca/rest/endpoint/assets/AssetsEndpoint.java) The Assets API serves as the master list of assets available for trade and data consumption from Alpaca. Example usage: @@ -270,7 +270,7 @@ try { } ``` -## [`WatchlistEndpoint`](src/main/java/net/jacobpeterson/alpaca/rest/endpoint/WatchlistEndpoint.java) +## [`WatchlistEndpoint`](src/main/java/net/jacobpeterson/alpaca/rest/endpoint/watchlist/WatchlistEndpoint.java) The Watchlist API provides CRUD operation for the account's watchlist. Example usage: @@ -304,7 +304,7 @@ try { } ``` -## [`CalendarEndpoint`](src/main/java/net/jacobpeterson/alpaca/rest/endpoint/CalendarEndpoint.java) +## [`CalendarEndpoint`](src/main/java/net/jacobpeterson/alpaca/rest/endpoint/calendar/CalendarEndpoint.java) The calendar API serves the full list of market days from 1970 to 2029. Example usage: @@ -320,7 +320,7 @@ try { } ``` -## [`ClockEndpoint`](src/main/java/net/jacobpeterson/alpaca/rest/endpoint/ClockEndpoint.java) +## [`ClockEndpoint`](src/main/java/net/jacobpeterson/alpaca/rest/endpoint/clock/ClockEndpoint.java) The clock API serves the current market timestamp, whether the market is currently open, as well as the times of the next market open and close. Example usage: @@ -334,7 +334,7 @@ try { } ``` -## [`AccountConfigurationEndpoint`](src/main/java/net/jacobpeterson/alpaca/rest/endpoint/AccountConfigurationEndpoint.java) +## [`AccountConfigurationEndpoint`](src/main/java/net/jacobpeterson/alpaca/rest/endpoint/accountconfiguration/AccountConfigurationEndpoint.java) The Account Configuration API provides custom configurations about your trading account settings. These configurations control various allow you to modify settings to suit your trading needs. Example usage: @@ -349,7 +349,7 @@ try { } ``` -## [`AccountActivitiesEndpoint`](src/main/java/net/jacobpeterson/alpaca/rest/endpoint/AccountActivitiesEndpoint.java) +## [`AccountActivitiesEndpoint`](src/main/java/net/jacobpeterson/alpaca/rest/endpoint/accountactivities/AccountActivitiesEndpoint.java) The Account Activities API provides access to a historical record of transaction activities that have impacted your account. Example usage: @@ -376,7 +376,7 @@ try { } ``` -## [`PortfolioHistoryEndpoint`](src/main/java/net/jacobpeterson/alpaca/rest/endpoint/PortfolioHistoryEndpoint.java) +## [`PortfolioHistoryEndpoint`](src/main/java/net/jacobpeterson/alpaca/rest/endpoint/portfoliohistory/PortfolioHistoryEndpoint.java) The Portfolio History API returns the timeseries data for equity and profit loss information of the account. Example usage: @@ -399,7 +399,7 @@ try { ``` ## [`StreamingWebsocket`](src/main/java/net/jacobpeterson/alpaca/websocket/streaming/StreamingWebsocket.java) -Alpaca offers WebSocket streaming of order updates. +Alpaca offers websocket streaming of order updates. Example usage: ```java @@ -433,7 +433,7 @@ Thread.sleep(5000); alpacaAPI.streaming().disconnect(); ``` -## [`MarketDataWebsocket`](src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/MarketDataWebsocket.java) +## [`StockMarketDataWebsocket`](src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/stock/StockMarketDataWebsocket.java) Alpaca's Data API v2 provides websocket streaming for trades, quotes, and minute bars. This helps receive the most up-to-date market information that could help your trading strategy to act upon certain market movement. Example usage: @@ -441,27 +441,27 @@ Example usage: // Add a 'MarketDataListener' that simply prints market data information MarketDataListener marketDataListener = (messageType, message) -> System.out.printf("%s: %s\n", messageType.name(), message); -alpacaAPI.marketDataStreaming().setListener(marketDataListener); +alpacaAPI.stockMarketDataStreaming().setListener(marketDataListener); // Listen to 'SubscriptionsMessage', 'SuccessMessage', and 'ErrorMessage' control messages // that contain information about the stream's current state. Note that these are subscribed // to before the websocket is connected since these messages usually are sent -// upon websocket connection. -alpacaAPI.marketDataStreaming().subscribeToControl( +// upon websocket connection. +alpacaAPI.stockMarketDataStreaming().subscribeToControl( MarketDataMessageType.SUCCESS, MarketDataMessageType.SUBSCRIPTION, MarketDataMessageType.ERROR); // Connect the websocket and confirm authentication -alpacaAPI.marketDataStreaming().connect(); -alpacaAPI.marketDataStreaming().waitForAuthorization(5, TimeUnit.SECONDS); -if (!alpacaAPI.marketDataStreaming().isValid()) { +alpacaAPI.stockMarketDataStreaming().connect(); +alpacaAPI.stockMarketDataStreaming().waitForAuthorization(5, TimeUnit.SECONDS); +if (!alpacaAPI.stockMarketDataStreaming().isValid()) { System.out.println("Websocket not valid!"); return; } -// Listen to the AAPL and TSLA trades and all ('*') bars. -alpacaAPI.marketDataStreaming().subscribe( +// Listen to AAPL and TSLA trades and all bars via the wildcard operator ('*'). +alpacaAPI.stockMarketDataStreaming().subscribe( Arrays.asList("AAPL", "TSLA"), null, Arrays.asList("*")); @@ -470,7 +470,23 @@ alpacaAPI.marketDataStreaming().subscribe( Thread.sleep(5000); // Manually disconnect the websocket -alpacaAPI.marketDataStreaming().disconnect(); +alpacaAPI.stockMarketDataStreaming().disconnect(); +``` + +## [`CryptoMarketDataWebsocket`](src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/crypto/CryptoMarketDataWebsocket.java) +Alpaca also offers cryptocurrency websocket streaming for trades, quotes, and minute bars. + +The usage is identical to the [`StockMarketDataWebsocket`](https://github.com/Petersoj/alpaca-java/#stockmarketdatawebsocket) usage except that accessing the websocket instance via the `AlpacaAPI` instance is done using: `alpacaAPI.cryptoMarketDataStreaming()` instead of `alpacaAPI.stockMarketDataStreaming()`. + +Example usage: +```java +// The 'CryptoMarketDataWebsocket' setup is identical to the 'StockMarketDataWebsocket' setup + +// Listen to BTCUSD and ETHUSD trades and all bars via the wildcard operator ('*'). +alpacaAPI.cryptoMarketDataStreaming().subscribe( + Arrays.asList("BTCUSD", "ETHUSD"), + null, + Arrays.asList("*")); ``` # Building diff --git a/schema_json/endpoint/market_data/common/historical/bar/bar.json b/schema_json/endpoint/market_data/common/historical/bar/bar.json index b0284b57..b2f3b42f 100644 --- a/schema_json/endpoint/market_data/common/historical/bar/bar.json +++ b/schema_json/endpoint/market_data/common/historical/bar/bar.json @@ -1,8 +1,5 @@ { "type": "object", - "extends": { - "existingJavaType": "net.jacobpeterson.alpaca.model.endpoint.marketdata.common.realtime.quote.QuoteMessage" - }, "title": "See Market Data.", "properties": { "t": { diff --git a/schema_json/endpoint/market_data/common/realtime/bar/bar_message.json b/schema_json/endpoint/market_data/common/realtime/bar/bar_message.json index fcf5f6c6..9df2a564 100644 --- a/schema_json/endpoint/market_data/common/realtime/bar/bar_message.json +++ b/schema_json/endpoint/market_data/common/realtime/bar/bar_message.json @@ -29,6 +29,16 @@ "existingJavaType": "java.time.ZonedDateTime", "javaName": "timestamp", "title": "The timestamp." + }, + "n": { + "existingJavaType": "java.lang.Long", + "javaName": "tradeCount", + "title": "Trade count." + }, + "vw": { + "existingJavaType": "java.lang.Double", + "javaName": "vwap", + "title": "VWAP (Volume Weighted Average Price)." } } } diff --git a/schema_json/endpoint/market_data/crypto/realtime/bar/crypto_bar_message.json b/schema_json/endpoint/market_data/crypto/realtime/bar/crypto_bar_message.json index 3ef4ac16..8886e9d4 100644 --- a/schema_json/endpoint/market_data/crypto/realtime/bar/crypto_bar_message.json +++ b/schema_json/endpoint/market_data/crypto/realtime/bar/crypto_bar_message.json @@ -14,16 +14,6 @@ "existingJavaType": "java.lang.Double", "javaName": "volume", "title": "Volume." - }, - "n": { - "existingJavaType": "java.lang.Long", - "javaName": "tradeCount", - "title": "Trade count." - }, - "vw": { - "existingJavaType": "java.lang.Double", - "javaName": "vwap", - "title": "VWAP (Volume Weighted Average Price)." } } } diff --git a/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/MarketDataWebsocket.java b/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/MarketDataWebsocket.java index 836a452a..9241f066 100644 --- a/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/MarketDataWebsocket.java +++ b/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/MarketDataWebsocket.java @@ -172,7 +172,6 @@ public void onMessage(@NotNull WebSocket webSocket, @NotNull String message) { switch (marketDataMessageType) { case SUCCESS: marketDataMessage = GSON.fromJson(messageObject, SuccessMessage.class); - LOGGER.debug("{}", marketDataMessage); if (isSuccessMessageAuthenticated((SuccessMessage) marketDataMessage)) { LOGGER.info("{} websocket authenticated.", websocketName); @@ -202,7 +201,6 @@ public void onMessage(@NotNull WebSocket webSocket, @NotNull String message) { break; case SUBSCRIPTION: marketDataMessage = GSON.fromJson(messageObject, SubscriptionsMessage.class); - LOGGER.debug("{}", marketDataMessage); // Update 'listenedMarketDataMessageTypes' and the associated subscribed symbols lists SubscriptionsMessage subscriptionsMessage = (SubscriptionsMessage) marketDataMessage; diff --git a/src/main/java/net/jacobpeterson/alpaca/websocket/streaming/StreamingWebsocket.java b/src/main/java/net/jacobpeterson/alpaca/websocket/streaming/StreamingWebsocket.java index 57ede8a4..cfbf2106 100644 --- a/src/main/java/net/jacobpeterson/alpaca/websocket/streaming/StreamingWebsocket.java +++ b/src/main/java/net/jacobpeterson/alpaca/websocket/streaming/StreamingWebsocket.java @@ -136,7 +136,8 @@ protected void sendAuthenticationMessage() { // This websocket uses binary frames and not text frames. @Override public void onMessage(@NotNull WebSocket webSocket, @NotNull ByteString byteString) { - String message = byteString.utf8(); + final String message = byteString.utf8(); + LOGGER.trace("{}", message); JsonElement messageElement = JsonParser.parseString(message); checkState(messageElement instanceof JsonObject, "Message must be a JsonObject! Received: %s", messageElement); @@ -160,7 +161,6 @@ public void onMessage(@NotNull WebSocket webSocket, @NotNull ByteString byteStri LOGGER.error("{} websocket not authenticated! Received: {}.", websocketName, streamingMessage); } else { LOGGER.info("{} websocket authenticated.", websocketName); - LOGGER.debug("{}", streamingMessage); } if (authenticationMessageFuture != null) { @@ -169,7 +169,6 @@ public void onMessage(@NotNull WebSocket webSocket, @NotNull ByteString byteStri break; case LISTENING: streamingMessage = GSON.fromJson(messageObject, ListeningMessage.class); - LOGGER.debug("{}", streamingMessage); // Remove all 'StreamingMessageType's that are no longer listened to and add new ones List currentTypes = ((ListeningMessage) streamingMessage).getData().getStreams(); @@ -180,7 +179,6 @@ public void onMessage(@NotNull WebSocket webSocket, @NotNull ByteString byteStri break; case TRADE_UPDATES: streamingMessage = GSON.fromJson(messageObject, TradeUpdateMessage.class); - LOGGER.debug("{}", streamingMessage); break; default: throw new UnsupportedOperationException(); From 24065531f43cdc8d97486226b9211f67de83970c Mon Sep 17 00:00:00 2001 From: Jacob Peterson <14127217+Petersoj@users.noreply.github.com> Date: Wed, 22 Dec 2021 13:23:45 -0700 Subject: [PATCH 173/271] Update to 9.0.0 --- README.md | 4 ++-- build.gradle | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 59df6b86..0b17443a 100644 --- a/README.md +++ b/README.md @@ -17,7 +17,7 @@ If you are using Gradle as your build tool, add the following dependency to your ``` dependencies { - implementation group: 'net.jacobpeterson', name: 'alpaca-java', version: '8.3.3' + implementation group: 'net.jacobpeterson', name: 'alpaca-java', version: '9.0.0' } ``` @@ -27,7 +27,7 @@ If you are using Maven as your build tool, add the following dependency to your net.jacobpeterson alpaca-java - 8.3.3 + 9.0.0 compile ``` diff --git a/build.gradle b/build.gradle index 88519ffb..fcbdf568 100644 --- a/build.gradle +++ b/build.gradle @@ -30,7 +30,7 @@ tasks.getByName("generateJsonSchema2Pojo").enabled(false) // Disable default 'js archivesBaseName = 'alpaca-java' group = 'net.jacobpeterson' -version = '8.3.3-SNAPSHOT' +version = '9.0.0-SNAPSHOT' repositories { mavenCentral() From 1da8305d39d994e8e97a3180c8fb3c9812171db8 Mon Sep 17 00:00:00 2001 From: Jacob Peterson <14127217+Petersoj@users.noreply.github.com> Date: Mon, 27 Dec 2021 00:18:18 -0700 Subject: [PATCH 174/271] Add multi-symbol getBars method, make some inline instantiations static --- README.md | 5 +- .../stock/historical/bar/enums/bar_feed.json | 8 ++ .../bar/multi_stock_bars_response.json | 14 +++ .../alpaca/rest/AlpacaClient.java | 3 + .../rest/endpoint/assets/AssetsEndpoint.java | 5 +- .../endpoint/calendar/CalendarEndpoint.java | 5 +- .../stock/StockMarketDataEndpoint.java | 89 +++++++++++++++++-- .../rest/endpoint/orders/OrdersEndpoint.java | 13 ++- .../endpoint/positions/PositionsEndpoint.java | 11 ++- .../endpoint/watchlist/WatchlistEndpoint.java | 10 ++- 10 files changed, 144 insertions(+), 19 deletions(-) create mode 100644 schema_json/endpoint/market_data/stock/historical/bar/enums/bar_feed.json create mode 100644 schema_json/endpoint/market_data/stock/historical/bar/multi_stock_bars_response.json diff --git a/README.md b/README.md index 0b17443a..197064ec 100644 --- a/README.md +++ b/README.md @@ -134,7 +134,7 @@ Example usage: ```java try { // Get AAPL one hour, split-adjusted bars from 7/6/2021 market open - // to 7/8/2021 market close and print them out + // to 7/8/2021 market close from the SIP feed and print them out StockBarsResponse aaplBarsResponse = alpacaAPI.stockMarketData().getBars( "AAPL", ZonedDateTime.of(2021, 7, 6, 9, 30, 0, 0, ZoneId.of("America/New_York")), @@ -143,7 +143,8 @@ try { null, 1, BarTimePeriod.HOUR, - BarAdjustment.SPLIT); + BarAdjustment.SPLIT, + BarFeed.SIP); aaplBarsResponse.getBars().forEach(System.out::println); // Get AAPL first 10 trades on 7/8/2021 at market open and print them out diff --git a/schema_json/endpoint/market_data/stock/historical/bar/enums/bar_feed.json b/schema_json/endpoint/market_data/stock/historical/bar/enums/bar_feed.json new file mode 100644 index 00000000..87d8a1a4 --- /dev/null +++ b/schema_json/endpoint/market_data/stock/historical/bar/enums/bar_feed.json @@ -0,0 +1,8 @@ +{ + "type": "string", + "title": "See Historical Stock Data.", + "enum": [ + "IEX", + "SIP" + ] +} diff --git a/schema_json/endpoint/market_data/stock/historical/bar/multi_stock_bars_response.json b/schema_json/endpoint/market_data/stock/historical/bar/multi_stock_bars_response.json new file mode 100644 index 00000000..65028159 --- /dev/null +++ b/schema_json/endpoint/market_data/stock/historical/bar/multi_stock_bars_response.json @@ -0,0 +1,14 @@ +{ + "type": "object", + "title": "See Historical Stock Data.", + "properties": { + "bars": { + "existingJavaType": "java.util.HashMap>", + "title": "The {@link net.jacobpeterson.alpaca.model.endpoint.marketdata.stock.historical.bar.StockBar}s of {@link java.lang.String} symbols {@link java.util.HashMap}." + }, + "next_page_token": { + "existingJavaType": "java.lang.String", + "title": "Token that can be used to query the next page." + } + } +} diff --git a/src/main/java/net/jacobpeterson/alpaca/rest/AlpacaClient.java b/src/main/java/net/jacobpeterson/alpaca/rest/AlpacaClient.java index 12cb6a6f..7fe5cc27 100644 --- a/src/main/java/net/jacobpeterson/alpaca/rest/AlpacaClient.java +++ b/src/main/java/net/jacobpeterson/alpaca/rest/AlpacaClient.java @@ -25,6 +25,9 @@ */ public class AlpacaClient { + public static final Predicate STATUS_CODE_200_OR_207 = (code) -> code == 200 || code == 207; + public static final Predicate STATUS_CODE_200_OR_204 = (code) -> code == 200 || code == 204; + private final OkHttpClient okHttpClient; private final HttpUrl baseURL; private final Headers requestHeaders; diff --git a/src/main/java/net/jacobpeterson/alpaca/rest/endpoint/assets/AssetsEndpoint.java b/src/main/java/net/jacobpeterson/alpaca/rest/endpoint/assets/AssetsEndpoint.java index bfcfaffe..82763589 100644 --- a/src/main/java/net/jacobpeterson/alpaca/rest/endpoint/assets/AssetsEndpoint.java +++ b/src/main/java/net/jacobpeterson/alpaca/rest/endpoint/assets/AssetsEndpoint.java @@ -10,6 +10,7 @@ import okhttp3.HttpUrl; import okhttp3.Request; +import java.lang.reflect.Type; import java.util.ArrayList; import java.util.List; @@ -20,6 +21,8 @@ */ public class AssetsEndpoint extends AlpacaEndpoint { + private static final Type ASSET_ARRAYLIST_TYPE = new TypeToken>() {}.getType(); + /** * Instantiates a new {@link AssetsEndpoint}. * @@ -54,7 +57,7 @@ public List get(AssetStatus assetStatus, AssetClass assetClass) throws Al Request request = alpacaClient.requestBuilder(urlBuilder.build()) .get() .build(); - return alpacaClient.requestObject(request, new TypeToken>() {}.getType()); + return alpacaClient.requestObject(request, ASSET_ARRAYLIST_TYPE); } /** diff --git a/src/main/java/net/jacobpeterson/alpaca/rest/endpoint/calendar/CalendarEndpoint.java b/src/main/java/net/jacobpeterson/alpaca/rest/endpoint/calendar/CalendarEndpoint.java index 58ad41af..cc1cb5d2 100644 --- a/src/main/java/net/jacobpeterson/alpaca/rest/endpoint/calendar/CalendarEndpoint.java +++ b/src/main/java/net/jacobpeterson/alpaca/rest/endpoint/calendar/CalendarEndpoint.java @@ -8,6 +8,7 @@ import okhttp3.HttpUrl; import okhttp3.Request; +import java.lang.reflect.Type; import java.time.LocalDate; import java.time.format.DateTimeFormatter; import java.util.ArrayList; @@ -18,6 +19,8 @@ */ public class CalendarEndpoint extends AlpacaEndpoint { + private static final Type CALENDAR_ARRAYLIST_TYPE = new TypeToken>() {}.getType(); + /** * Instantiates a new {@link CalendarEndpoint}. * @@ -63,6 +66,6 @@ public List get(LocalDate start, LocalDate end) throws AlpacaClientExc Request request = alpacaClient.requestBuilder(urlBuilder.build()) .get() .build(); - return alpacaClient.requestObject(request, new TypeToken>() {}.getType()); + return alpacaClient.requestObject(request, CALENDAR_ARRAYLIST_TYPE); } } diff --git a/src/main/java/net/jacobpeterson/alpaca/rest/endpoint/marketdata/stock/StockMarketDataEndpoint.java b/src/main/java/net/jacobpeterson/alpaca/rest/endpoint/marketdata/stock/StockMarketDataEndpoint.java index fe8a846e..d47b167d 100644 --- a/src/main/java/net/jacobpeterson/alpaca/rest/endpoint/marketdata/stock/StockMarketDataEndpoint.java +++ b/src/main/java/net/jacobpeterson/alpaca/rest/endpoint/marketdata/stock/StockMarketDataEndpoint.java @@ -2,9 +2,11 @@ import com.google.gson.reflect.TypeToken; import net.jacobpeterson.alpaca.model.endpoint.marketdata.common.historical.bar.enums.BarTimePeriod; +import net.jacobpeterson.alpaca.model.endpoint.marketdata.stock.historical.bar.MultiStockBarsResponse; import net.jacobpeterson.alpaca.model.endpoint.marketdata.stock.historical.bar.StockBar; import net.jacobpeterson.alpaca.model.endpoint.marketdata.stock.historical.bar.StockBarsResponse; import net.jacobpeterson.alpaca.model.endpoint.marketdata.stock.historical.bar.enums.BarAdjustment; +import net.jacobpeterson.alpaca.model.endpoint.marketdata.stock.historical.bar.enums.BarFeed; import net.jacobpeterson.alpaca.model.endpoint.marketdata.stock.historical.quote.LatestStockQuoteResponse; import net.jacobpeterson.alpaca.model.endpoint.marketdata.stock.historical.quote.StockQuote; import net.jacobpeterson.alpaca.model.endpoint.marketdata.stock.historical.quote.StockQuotesResponse; @@ -19,6 +21,7 @@ import okhttp3.HttpUrl; import okhttp3.Request; +import java.lang.reflect.Type; import java.time.ZonedDateTime; import java.util.Collection; import java.util.HashMap; @@ -34,6 +37,9 @@ */ public class StockMarketDataEndpoint extends AlpacaEndpoint { + private static final Type SNAPSHOTS_OF_STRINGS_HASHMAP_TYPE = + new TypeToken>() {}.getType(); + /** * Instantiates a new {@link StockMarketDataEndpoint}. * @@ -193,19 +199,21 @@ public LatestStockQuoteResponse getLatestQuote(String symbol) throws AlpacaClien * @param barTimePeriodDuration the duration for the given barTimePeriod parameter. e.g. for * 15Min bars, you would supply 15 for this parameter and * {@link BarTimePeriod#MINUTE} for the barTimePeriod parameter. - * @param barTimePeriod the {@link BarTimePeriod} e.g. for 15Min bars, you would supply {@link + * @param barTimePeriod the {@link BarTimePeriod}. e.g. for 15Min bars, you would supply {@link * BarTimePeriod#MINUTE} for this parameter and 15 for the * barTimePeriodDuration parameter. * @param barAdjustment specifies the corporate action adjustment for the stocks. Default value is {@link - * BarAdjustment#RAW}. + * BarAdjustment#RAW} + * @param barFeed defaults to {@link BarFeed#IEX} for Free users and {@link BarFeed#SIP} for users + * with an Unlimited subscription * * @return the {@link StockBarsResponse} * * @throws AlpacaClientException thrown for {@link AlpacaClientException}s */ public StockBarsResponse getBars(String symbol, ZonedDateTime start, ZonedDateTime end, Integer limit, - String pageToken, int barTimePeriodDuration, BarTimePeriod barTimePeriod, BarAdjustment barAdjustment) - throws AlpacaClientException { + String pageToken, int barTimePeriodDuration, BarTimePeriod barTimePeriod, BarAdjustment barAdjustment, + BarFeed barFeed) throws AlpacaClientException { checkNotNull(symbol); checkNotNull(start); checkNotNull(end); @@ -233,12 +241,83 @@ public StockBarsResponse getBars(String symbol, ZonedDateTime start, ZonedDateTi urlBuilder.addQueryParameter("adjustment", barAdjustment.toString()); } + if (barFeed != null) { + urlBuilder.addQueryParameter("feed", barFeed.toString()); + } + Request request = alpacaClient.requestBuilder(urlBuilder.build()) .get() .build(); return alpacaClient.requestObject(request, StockBarsResponse.class); } + /** + * Gets {@link StockBar} aggregate historical data for the requested securities. + * + * @param symbols a {@link Collection} of symbols to query for + * @param start filter data equal to or after this {@link ZonedDateTime}. Fractions of a second are + * not accepted. + * @param end filter data equal to or before this {@link ZonedDateTime}. Fractions of a second are + * not accepted. + * @param limit number of data points to return. Must be in range 1-10000, defaults to 1000 if + * null is given + * @param pageToken pagination token to continue from + * @param barTimePeriodDuration the duration for the given barTimePeriod parameter. e.g. for + * 15Min bars, you would supply 15 for this parameter and + * {@link BarTimePeriod#MINUTE} for the barTimePeriod parameter. + * @param barTimePeriod the {@link BarTimePeriod}. e.g. for 15Min bars, you would supply {@link + * BarTimePeriod#MINUTE} for this parameter and 15 for the + * barTimePeriodDuration parameter. + * @param barAdjustment specifies the corporate action adjustment for the stocks. Default value is {@link + * BarAdjustment#RAW} + * @param barFeed defaults to {@link BarFeed#IEX} for Free users and {@link BarFeed#SIP} for users + * with an Unlimited subscription + * + * @return the {@link MultiStockBarsResponse} + * + * @throws AlpacaClientException thrown for {@link AlpacaClientException}s + */ + public MultiStockBarsResponse getBars(Collection symbols, ZonedDateTime start, ZonedDateTime end, + Integer limit, String pageToken, int barTimePeriodDuration, BarTimePeriod barTimePeriod, + BarAdjustment barAdjustment, BarFeed barFeed) throws AlpacaClientException { + checkNotNull(symbols); + checkArgument(!symbols.isEmpty(), "'symbols' cannot be empty!"); + checkNotNull(start); + checkNotNull(end); + checkNotNull(barTimePeriod); + + HttpUrl.Builder urlBuilder = alpacaClient.urlBuilder() + .addPathSegment(endpointPathSegment) + .addPathSegment("bars"); + + urlBuilder.addQueryParameter("symbols", String.join(",", symbols)); + urlBuilder.addQueryParameter("start", FormatUtil.toRFC3339Format(start)); + urlBuilder.addQueryParameter("end", FormatUtil.toRFC3339Format(end)); + + if (limit != null) { + urlBuilder.addQueryParameter("limit", limit.toString()); + } + + if (pageToken != null) { + urlBuilder.addQueryParameter("page_token", pageToken); + } + + urlBuilder.addQueryParameter("timeframe", barTimePeriodDuration + barTimePeriod.toString()); + + if (barAdjustment != null) { + urlBuilder.addQueryParameter("adjustment", barAdjustment.toString()); + } + + if (barFeed != null) { + urlBuilder.addQueryParameter("feed", barFeed.toString()); + } + + Request request = alpacaClient.requestBuilder(urlBuilder.build()) + .get() + .build(); + return alpacaClient.requestObject(request, MultiStockBarsResponse.class); + } + /** * Gets {@link Snapshot}s of the requested securities. * @@ -261,7 +340,7 @@ public Map getSnapshots(Collection symbols) throws Alp Request request = alpacaClient.requestBuilder(urlBuilder.build()) .get() .build(); - return alpacaClient.requestObject(request, new TypeToken>() {}.getType()); + return alpacaClient.requestObject(request, SNAPSHOTS_OF_STRINGS_HASHMAP_TYPE); } /** diff --git a/src/main/java/net/jacobpeterson/alpaca/rest/endpoint/orders/OrdersEndpoint.java b/src/main/java/net/jacobpeterson/alpaca/rest/endpoint/orders/OrdersEndpoint.java index 90159bb4..c177db71 100644 --- a/src/main/java/net/jacobpeterson/alpaca/rest/endpoint/orders/OrdersEndpoint.java +++ b/src/main/java/net/jacobpeterson/alpaca/rest/endpoint/orders/OrdersEndpoint.java @@ -20,6 +20,7 @@ import okhttp3.HttpUrl; import okhttp3.Request; +import java.lang.reflect.Type; import java.time.ZonedDateTime; import java.time.format.DateTimeFormatter; import java.util.ArrayList; @@ -28,12 +29,17 @@ import static com.google.common.base.Preconditions.checkNotNull; import static com.google.common.base.Preconditions.checkState; +import static net.jacobpeterson.alpaca.rest.AlpacaClient.STATUS_CODE_200_OR_204; +import static net.jacobpeterson.alpaca.rest.AlpacaClient.STATUS_CODE_200_OR_207; /** * {@link AlpacaEndpoint} for Orders. */ public class OrdersEndpoint extends AlpacaEndpoint { + private static final Type ORDER_ARRAYLIST_TYPE = new TypeToken>() {}.getType(); + private static final Type CANCELLED_ORDER_ARRAYLIST_TYPE = new TypeToken>() {}.getType(); + /** * Instantiates a new {@link OrdersEndpoint}. * @@ -97,7 +103,7 @@ public List get(CurrentOrderStatus status, Integer limit, ZonedDateTime a Request request = alpacaClient.requestBuilder(urlBuilder.build()) .get() .build(); - return alpacaClient.requestObject(request, new TypeToken>() {}.getType()); + return alpacaClient.requestObject(request, ORDER_ARRAYLIST_TYPE); } /** @@ -615,8 +621,7 @@ public List cancelAll() throws AlpacaClientException { Request request = alpacaClient.requestBuilder(urlBuilder.build()) .delete() .build(); - return alpacaClient.requestObject(request, (code) -> code == 200 || code == 207, - new TypeToken>() {}.getType()); + return alpacaClient.requestObject(request, STATUS_CODE_200_OR_207, CANCELLED_ORDER_ARRAYLIST_TYPE); } /** @@ -637,6 +642,6 @@ public void cancel(String orderID) throws AlpacaClientException { Request request = alpacaClient.requestBuilder(urlBuilder.build()) .delete() .build(); - alpacaClient.requestVoid(request, (code) -> code == 200 || code == 204); + alpacaClient.requestVoid(request, STATUS_CODE_200_OR_204); } } diff --git a/src/main/java/net/jacobpeterson/alpaca/rest/endpoint/positions/PositionsEndpoint.java b/src/main/java/net/jacobpeterson/alpaca/rest/endpoint/positions/PositionsEndpoint.java index cccfdfb8..4ea3b096 100644 --- a/src/main/java/net/jacobpeterson/alpaca/rest/endpoint/positions/PositionsEndpoint.java +++ b/src/main/java/net/jacobpeterson/alpaca/rest/endpoint/positions/PositionsEndpoint.java @@ -11,16 +11,22 @@ import okhttp3.HttpUrl; import okhttp3.Request; +import java.lang.reflect.Type; import java.util.ArrayList; import java.util.List; import static com.google.common.base.Preconditions.checkNotNull; +import static net.jacobpeterson.alpaca.rest.AlpacaClient.STATUS_CODE_200_OR_207; /** * {@link AlpacaEndpoint} for Positions. */ public class PositionsEndpoint extends AlpacaEndpoint { + private static final Type POSITION_ARRAYLIST_TYPE = new TypeToken>() {}.getType(); + private static final Type CLOSED_POSITION_ORDER_ARRAYLIST_TYPE = + new TypeToken>() {}.getType(); + /** * Instantiates a new {@link PositionsEndpoint}. * @@ -43,7 +49,7 @@ public List get() throws AlpacaClientException { Request request = alpacaClient.requestBuilder(urlBuilder.build()) .get() .build(); - return alpacaClient.requestObject(request, new TypeToken>() {}.getType()); + return alpacaClient.requestObject(request, POSITION_ARRAYLIST_TYPE); } /** @@ -89,8 +95,7 @@ public List closeAll(Boolean cancelOrders) throws AlpacaClie Request request = alpacaClient.requestBuilder(urlBuilder.build()) .delete() .build(); - return alpacaClient.requestObject(request, (code) -> code == 200 || code == 207, - new TypeToken>() {}.getType()); + return alpacaClient.requestObject(request, STATUS_CODE_200_OR_207, CLOSED_POSITION_ORDER_ARRAYLIST_TYPE); } /** diff --git a/src/main/java/net/jacobpeterson/alpaca/rest/endpoint/watchlist/WatchlistEndpoint.java b/src/main/java/net/jacobpeterson/alpaca/rest/endpoint/watchlist/WatchlistEndpoint.java index 8f8762eb..605d651b 100644 --- a/src/main/java/net/jacobpeterson/alpaca/rest/endpoint/watchlist/WatchlistEndpoint.java +++ b/src/main/java/net/jacobpeterson/alpaca/rest/endpoint/watchlist/WatchlistEndpoint.java @@ -11,12 +11,14 @@ import okhttp3.HttpUrl; import okhttp3.Request; +import java.lang.reflect.Type; import java.util.ArrayList; import java.util.Arrays; import java.util.List; import static com.google.common.base.Preconditions.checkArgument; import static com.google.common.base.Preconditions.checkNotNull; +import static net.jacobpeterson.alpaca.rest.AlpacaClient.STATUS_CODE_200_OR_204; /** * {@link AlpacaEndpoint} for Watchlists @@ -24,6 +26,8 @@ */ public class WatchlistEndpoint extends AlpacaEndpoint { + private static final Type WATCHLIST_ARRAYLIST_TYPE = new TypeToken>() {}.getType(); + /** * Instantiates a new {@link WatchlistEndpoint}. * @@ -46,7 +50,7 @@ public List get() throws AlpacaClientException { Request request = alpacaClient.requestBuilder(urlBuilder.build()) .get() .build(); - return alpacaClient.requestObject(request, new TypeToken>() {}.getType()); + return alpacaClient.requestObject(request, WATCHLIST_ARRAYLIST_TYPE); } /** @@ -183,7 +187,7 @@ public void delete(String watchlistID) throws AlpacaClientException { Request request = alpacaClient.requestBuilder(urlBuilder.build()) .delete() .build(); - alpacaClient.requestVoid(request, (code) -> code == 200 || code == 204); + alpacaClient.requestVoid(request, STATUS_CODE_200_OR_204); } /** @@ -207,6 +211,6 @@ public Watchlist removeSymbol(String watchlistID, String symbol) throws AlpacaCl Request request = alpacaClient.requestBuilder(urlBuilder.build()) .delete() .build(); - return alpacaClient.requestObject(request, (code) -> code == 200 || code == 204, Watchlist.class); + return alpacaClient.requestObject(request, STATUS_CODE_200_OR_204, Watchlist.class); } } From d3acf99dd783f2d4c2c34bcc354e83bad7fd5696 Mon Sep 17 00:00:00 2001 From: Jacob Peterson <14127217+Petersoj@users.noreply.github.com> Date: Mon, 27 Dec 2021 00:18:57 -0700 Subject: [PATCH 175/271] Update to 9.1.0 --- README.md | 4 ++-- build.gradle | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 197064ec..e1a3d562 100644 --- a/README.md +++ b/README.md @@ -17,7 +17,7 @@ If you are using Gradle as your build tool, add the following dependency to your ``` dependencies { - implementation group: 'net.jacobpeterson', name: 'alpaca-java', version: '9.0.0' + implementation group: 'net.jacobpeterson', name: 'alpaca-java', version: '9.1.0' } ``` @@ -27,7 +27,7 @@ If you are using Maven as your build tool, add the following dependency to your net.jacobpeterson alpaca-java - 9.0.0 + 9.1.0 compile ``` diff --git a/build.gradle b/build.gradle index fcbdf568..e9552238 100644 --- a/build.gradle +++ b/build.gradle @@ -30,7 +30,7 @@ tasks.getByName("generateJsonSchema2Pojo").enabled(false) // Disable default 'js archivesBaseName = 'alpaca-java' group = 'net.jacobpeterson' -version = '9.0.0-SNAPSHOT' +version = '9.1.0-SNAPSHOT' repositories { mavenCentral() From 0e15c393deef5e87198b223355324464510b1714 Mon Sep 17 00:00:00 2001 From: Jacob Peterson <14127217+Petersoj@users.noreply.github.com> Date: Thu, 10 Mar 2022 22:13:39 -0700 Subject: [PATCH 176/271] Update SLF4j version, generateBuilders in POJOs, update a Javadoc --- README.md | 12 ++++++------ build.gradle | 7 +++++-- .../stock/historical/bar/stock_bars_response.json | 2 +- 3 files changed, 12 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index e1a3d562..396ee4cc 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,7 @@ Javadocs Build Status CodeCov badge - GitHub + GitHub

# Overview @@ -51,7 +51,7 @@ For logging, this library uses [SLF4j](http://www.slf4j.org/) which serves as an Note that the examples below are not exhaustive. Refer to the [Javadoc](https://javadoc.io/doc/net.jacobpeterson/alpaca-java) for all classes and method signatures. ## [`AlpacaAPI`](src/main/java/net/jacobpeterson/alpaca/AlpacaAPI.java) -The [`AlpacaAPI`](src/main/java/net/jacobpeterson/alpaca/AlpacaAPI.java) class contains several instances of various [`AlpacaEndpoints`](src/main/java/net/jacobpeterson/alpaca/rest/endpoint/AlpacaEndpoint.java) and [`AlpacaWebsockets`](src/main/java/net/jacobpeterson/alpaca/websocket/AlpacaWebsocket.java) to interface with Alpaca. You will generally only need one instance of this class in your application. Note that many methods inside the various [`AlpacaEndpoints`](src/main/java/net/jacobpeterson/alpaca/rest/endpoint/AlpacaEndpoint.java) allow `null` to be passed in as a parameter if it is optional. +The [`AlpacaAPI`](src/main/java/net/jacobpeterson/alpaca/AlpacaAPI.java) class contains several instances of various [`AlpacaEndpoints`](src/main/java/net/jacobpeterson/alpaca/rest/endpoint/AlpacaEndpoint.java) and [`AlpacaWebsockets`](src/main/java/net/jacobpeterson/alpaca/websocket/AlpacaWebsocket.java) to interface with Alpaca. You will generally only need one instance of this class in your application. Note that many methods inside the various [`AlpacaEndpoints`](src/main/java/net/jacobpeterson/alpaca/rest/endpoint/AlpacaEndpoint.java) allow `null` to be passed in as a parameter if it is optional. The Alpaca API specification is located [here](https://docs.alpaca.markets/api-documentation/api-v2/) and the [`AlpacaAPI`](src/main/java/net/jacobpeterson/alpaca/AlpacaAPI.java) Javadoc is located [here](https://javadoc.io/doc/net.jacobpeterson/alpaca-java/latest/net/jacobpeterson/alpaca/AlpacaAPI.html). @@ -409,10 +409,10 @@ StreamingListener streamingListener = (messageType, message) -> System.out.printf("%s: %s\n", messageType.name(), message); alpacaAPI.streaming().setListener(streamingListener); -// Listen 'AuthorizationMessage' and 'ListeningMessage' messages that contain +// Listen 'AuthorizationMessage' and 'ListeningMessage' messages that contain // information about the stream's current state. Note that these are subscribed // to before the websocket is connected since these messages usually are sent -// upon websocket connection. +// upon websocket connection. alpacaAPI.streaming().streams(StreamingMessageType.AUTHORIZATION, StreamingMessageType.LISTENING); @@ -481,7 +481,7 @@ The usage is identical to the [`StockMarketDataWebsocket`](https://github.com/Pe Example usage: ```java -// The 'CryptoMarketDataWebsocket' setup is identical to the 'StockMarketDataWebsocket' setup +// The 'CryptoMarketDataWebsocket' setup is identical to the 'StockMarketDataWebsocket' setup // Listen to BTCUSD and ETHUSD trades and all bars via the wildcard operator ('*'). alpacaAPI.cryptoMarketDataStreaming().subscribe( @@ -523,4 +523,4 @@ Note that the live tests will modify your account minimally. It's meant to test # Contributing Contributions are welcome! -If you are creating a Pull Request, be sure to create a new branch in your forked repository for your feature or bug fix instead of committing directly to the `master` branch in your fork. +If you are creating a Pull Request, be sure to create a new branch in your forked repository for your feature or bug fix instead of committing directly to the `master` branch in your fork. diff --git a/build.gradle b/build.gradle index e9552238..bfd19fbd 100644 --- a/build.gradle +++ b/build.gradle @@ -26,7 +26,6 @@ plugins { } apply plugin: 'jsonschema2pojo' -tasks.getByName("generateJsonSchema2Pojo").enabled(false) // Disable default 'jsonSchema2Pojo' task archivesBaseName = 'alpaca-java' group = 'net.jacobpeterson' @@ -38,7 +37,7 @@ repositories { dependencies { // Logging framework - implementation group: 'org.slf4j', name: 'slf4j-api', version: '1.7.31' + implementation group: 'org.slf4j', name: 'slf4j-api', version: '1.7.32' // This is a bridge for the Apache Jakarta Commons Logging library used in dependencies to actually use SLF4j instead implementation group: 'org.slf4j', name: 'jcl-over-slf4j', version: '1.7.31' @@ -178,6 +177,9 @@ processResources { // START POJO generation // +// Disable default 'jsonSchema2Pojo' task +tasks.getByName("generateJsonSchema2Pojo").enabled(false) + // The generated POJOs will be in a package structure analogous to the path in the 'schema_json/' directory // See: https://github.com/joelittlejohn/jsonschema2pojo/wiki/Reference @@ -240,6 +242,7 @@ jsonSchema2Pojo { includeGetters = true includeSetters = true includeCopyConstructor = true + generateBuilders = true } // diff --git a/schema_json/endpoint/market_data/stock/historical/bar/stock_bars_response.json b/schema_json/endpoint/market_data/stock/historical/bar/stock_bars_response.json index b23db483..5add43d2 100644 --- a/schema_json/endpoint/market_data/stock/historical/bar/stock_bars_response.json +++ b/schema_json/endpoint/market_data/stock/historical/bar/stock_bars_response.json @@ -4,7 +4,7 @@ "properties": { "bars": { "existingJavaType": "java.util.ArrayList", - "title": "The {@link java.util.ArrayList} of {@link net.jacobpeterson.alpaca.model.endpoint.marketdata.stock.historical.bar.StockBar}s." + "title": "The {@link java.util.ArrayList} of {@link net.jacobpeterson.alpaca.model.endpoint.marketdata.stock.historical.bar.StockBar}s. This may be null if no bars exist." }, "symbol": { "existingJavaType": "java.lang.String", From 04545575ea122a1b5ace2077b02a94e9d1664b98 Mon Sep 17 00:00:00 2001 From: jessethouin Date: Thu, 21 Apr 2022 16:18:38 -0400 Subject: [PATCH 177/271] Updated requestLimitOrder to take Double type quantity for fractional limit orders. --- .../alpaca/rest/endpoint/orders/OrdersEndpoint.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/net/jacobpeterson/alpaca/rest/endpoint/orders/OrdersEndpoint.java b/src/main/java/net/jacobpeterson/alpaca/rest/endpoint/orders/OrdersEndpoint.java index c177db71..3212bf75 100644 --- a/src/main/java/net/jacobpeterson/alpaca/rest/endpoint/orders/OrdersEndpoint.java +++ b/src/main/java/net/jacobpeterson/alpaca/rest/endpoint/orders/OrdersEndpoint.java @@ -285,9 +285,9 @@ public Order requestNotionalMarketOrder(String symbol, Double notional, OrderSid * * @throws AlpacaClientException thrown for {@link AlpacaClientException}s */ - public Order requestLimitOrder(String symbol, Integer quantity, OrderSide side, OrderTimeInForce timeInForce, + public Order requestLimitOrder(String symbol, Double quantity, OrderSide side, OrderTimeInForce timeInForce, Double limitPrice, Boolean extendedHours) throws AlpacaClientException { - return requestOrder(symbol, quantity.doubleValue(), null, side, OrderType.LIMIT, timeInForce, limitPrice, + return requestOrder(symbol, quantity, null, side, OrderType.LIMIT, timeInForce, limitPrice, null, null, null, extendedHours, null, OrderClass.SIMPLE, null, null, null); } From aac872e095e3af3deafe3cdd46043ca5ba2c3cb1 Mon Sep 17 00:00:00 2001 From: Dario Arena Date: Fri, 3 Jun 2022 13:24:38 +0200 Subject: [PATCH 178/271] #121 - use double type quantity in PositionsEndpoint.close() --- .../alpaca/rest/endpoint/positions/PositionsEndpoint.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/net/jacobpeterson/alpaca/rest/endpoint/positions/PositionsEndpoint.java b/src/main/java/net/jacobpeterson/alpaca/rest/endpoint/positions/PositionsEndpoint.java index 4ea3b096..fc8eda81 100644 --- a/src/main/java/net/jacobpeterson/alpaca/rest/endpoint/positions/PositionsEndpoint.java +++ b/src/main/java/net/jacobpeterson/alpaca/rest/endpoint/positions/PositionsEndpoint.java @@ -113,7 +113,7 @@ public List closeAll(Boolean cancelOrders) throws AlpacaClie * * @throws AlpacaClientException thrown for {@link AlpacaClientException}s */ - public Order close(String symbolOrAssetID, Integer quantity, Double percentage) throws AlpacaClientException { + public Order close(String symbolOrAssetID, Double quantity, Double percentage) throws AlpacaClientException { checkNotNull(symbolOrAssetID); HttpUrl.Builder urlBuilder = alpacaClient.urlBuilder() From 43206bdc629be8a33bd28b4dc967d0f0ff63fa08 Mon Sep 17 00:00:00 2001 From: Jacob Peterson <14127217+Petersoj@users.noreply.github.com> Date: Mon, 20 Jun 2022 09:37:36 -0600 Subject: [PATCH 179/271] Update dependency versions --- build.gradle | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/build.gradle b/build.gradle index bfd19fbd..80c4aa13 100644 --- a/build.gradle +++ b/build.gradle @@ -37,22 +37,22 @@ repositories { dependencies { // Logging framework - implementation group: 'org.slf4j', name: 'slf4j-api', version: '1.7.32' + implementation group: 'org.slf4j', name: 'slf4j-api', version: '1.7.36' // This is a bridge for the Apache Jakarta Commons Logging library used in dependencies to actually use SLF4j instead - implementation group: 'org.slf4j', name: 'jcl-over-slf4j', version: '1.7.31' + implementation group: 'org.slf4j', name: 'jcl-over-slf4j', version: '1.7.36' // Guava and GSON - implementation group: 'com.google.guava', name: 'guava', version: '30.1.1-jre' - implementation group: 'com.google.code.gson', name: 'gson', version: '2.8.7' + implementation group: 'com.google.guava', name: 'guava', version: '31.1-jre' + implementation group: 'com.google.code.gson', name: 'gson', version: '2.9.0' // OkHttp - implementation group: 'com.squareup.okhttp3', name: 'okhttp', version: '4.9.1' + implementation group: 'com.squareup.okhttp3', name: 'okhttp', version: '4.10.0' implementation group: 'com.github.devcsrj', name: 'slf4j-okhttp3-logging-interceptor', version: '1.0.1' // Unit test dependencies - testImplementation group: 'org.slf4j', name: 'slf4j-simple', version: '1.7.30' - testImplementation group: 'org.junit.jupiter', name: 'junit-jupiter', version: '5.7.0' - testImplementation group: 'org.mockito', name: 'mockito-core', version: '3.7.0' + testImplementation group: 'org.slf4j', name: 'slf4j-simple', version: '1.7.36' + testImplementation group: 'org.junit.jupiter', name: 'junit-jupiter', version: '5.8.2' + testImplementation group: 'org.mockito', name: 'mockito-core', version: '4.6.1' } // Exclude any SLF4j-implementation transitive dependencies so users can use a logging framework of their choice From 4b9970ed0d50df1e18c0980dcf44d3f8797fdc00 Mon Sep 17 00:00:00 2001 From: Jacob Peterson <14127217+Petersoj@users.noreply.github.com> Date: Tue, 9 Aug 2022 18:09:04 -0600 Subject: [PATCH 180/271] Update to 9.1.1 --- README.md | 4 ++-- build.gradle | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 396ee4cc..e3f98186 100644 --- a/README.md +++ b/README.md @@ -17,7 +17,7 @@ If you are using Gradle as your build tool, add the following dependency to your ``` dependencies { - implementation group: 'net.jacobpeterson', name: 'alpaca-java', version: '9.1.0' + implementation group: 'net.jacobpeterson', name: 'alpaca-java', version: '9.1.1' } ``` @@ -27,7 +27,7 @@ If you are using Maven as your build tool, add the following dependency to your net.jacobpeterson alpaca-java - 9.1.0 + 9.1.1 compile ``` diff --git a/build.gradle b/build.gradle index 80c4aa13..692c91f3 100644 --- a/build.gradle +++ b/build.gradle @@ -29,7 +29,7 @@ apply plugin: 'jsonschema2pojo' archivesBaseName = 'alpaca-java' group = 'net.jacobpeterson' -version = '9.1.0-SNAPSHOT' +version = '9.1.1-SNAPSHOT' repositories { mavenCentral() From f058227f9d6da94c0c70ddfc9d348d8ee76c004c Mon Sep 17 00:00:00 2001 From: Jacob Peterson <14127217+Petersoj@users.noreply.github.com> Date: Tue, 16 Aug 2022 22:47:36 -0600 Subject: [PATCH 181/271] Fix FTX crypto exchange enum name --- .../endpoint/market_data/crypto/common/enums/exchange.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/schema_json/endpoint/market_data/crypto/common/enums/exchange.json b/schema_json/endpoint/market_data/crypto/common/enums/exchange.json index ff9d94e5..b17a92f8 100644 --- a/schema_json/endpoint/market_data/crypto/common/enums/exchange.json +++ b/schema_json/endpoint/market_data/crypto/common/enums/exchange.json @@ -3,7 +3,7 @@ "title": "See Crypto Data.", "enum": [ "ERSX", - "FTX", + "FTXU", "CBSE" ], "javaEnums": [ From becccd909f93a1bc58ca6326feae5e7bb56fe629 Mon Sep 17 00:00:00 2001 From: Jacob Peterson <14127217+Petersoj@users.noreply.github.com> Date: Tue, 16 Aug 2022 22:48:12 -0600 Subject: [PATCH 182/271] Update to 9.1.2 --- README.md | 4 ++-- build.gradle | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index e3f98186..057e7f0e 100644 --- a/README.md +++ b/README.md @@ -17,7 +17,7 @@ If you are using Gradle as your build tool, add the following dependency to your ``` dependencies { - implementation group: 'net.jacobpeterson', name: 'alpaca-java', version: '9.1.1' + implementation group: 'net.jacobpeterson', name: 'alpaca-java', version: '9.1.2' } ``` @@ -27,7 +27,7 @@ If you are using Maven as your build tool, add the following dependency to your net.jacobpeterson alpaca-java - 9.1.1 + 9.1.2 compile ``` diff --git a/build.gradle b/build.gradle index 692c91f3..bacd0079 100644 --- a/build.gradle +++ b/build.gradle @@ -29,7 +29,7 @@ apply plugin: 'jsonschema2pojo' archivesBaseName = 'alpaca-java' group = 'net.jacobpeterson' -version = '9.1.1-SNAPSHOT' +version = '9.1.2-SNAPSHOT' repositories { mavenCentral() From aad1af49b522ebe0d683a0e8cbc8f65140b06755 Mon Sep 17 00:00:00 2001 From: Kurru Date: Fri, 9 Dec 2022 12:10:46 -0800 Subject: [PATCH 183/271] Add nullable annotations on various fields/methods in AlpacaClientException --- .../alpaca/rest/AlpacaClientException.java | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/src/main/java/net/jacobpeterson/alpaca/rest/AlpacaClientException.java b/src/main/java/net/jacobpeterson/alpaca/rest/AlpacaClientException.java index 2114b1e8..8e34795c 100644 --- a/src/main/java/net/jacobpeterson/alpaca/rest/AlpacaClientException.java +++ b/src/main/java/net/jacobpeterson/alpaca/rest/AlpacaClientException.java @@ -3,6 +3,9 @@ import com.google.gson.JsonElement; import com.google.gson.JsonObject; import com.google.gson.JsonParser; + +import javax.annotation.Nullable; + import okhttp3.Response; import okhttp3.ResponseBody; @@ -11,10 +14,15 @@ public class AlpacaClientException extends Exception { private static final String CODE_KEY = "code"; private static final String MESSAGE_KEY = "message"; + @Nullable private String responseBody; + @Nullable private Integer responseStatusCode; + @Nullable private String responseStatusMessage; + @Nullable private Integer apiResponseCode; + @Nullable private String apiResponseMessage; /** @@ -120,18 +128,22 @@ private String parseAPIErrorResponse() { return messageBuilder.toString(); } + @Nullable public Integer getResponseStatusCode() { return responseStatusCode; } + @Nullable public String getResponseStatusMessage() { return responseStatusMessage; } + @Nullable public Integer getAPIResponseCode() { return apiResponseCode; } + @Nullable public String getAPIResponseMessage() { return apiResponseMessage; } From 00c3bea9e44de3eb4cb687eb989add1f5191b95a Mon Sep 17 00:00:00 2001 From: Jacob Peterson <14127217+Petersoj@users.noreply.github.com> Date: Tue, 13 Dec 2022 20:27:31 -0700 Subject: [PATCH 184/271] Remove redundant 'Nullable' annotation from private fields --- .../alpaca/rest/AlpacaClientException.java | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/src/main/java/net/jacobpeterson/alpaca/rest/AlpacaClientException.java b/src/main/java/net/jacobpeterson/alpaca/rest/AlpacaClientException.java index 8e34795c..4c902b05 100644 --- a/src/main/java/net/jacobpeterson/alpaca/rest/AlpacaClientException.java +++ b/src/main/java/net/jacobpeterson/alpaca/rest/AlpacaClientException.java @@ -3,26 +3,20 @@ import com.google.gson.JsonElement; import com.google.gson.JsonObject; import com.google.gson.JsonParser; - -import javax.annotation.Nullable; - import okhttp3.Response; import okhttp3.ResponseBody; +import javax.annotation.Nullable; + public class AlpacaClientException extends Exception { private static final String CODE_KEY = "code"; private static final String MESSAGE_KEY = "message"; - @Nullable private String responseBody; - @Nullable private Integer responseStatusCode; - @Nullable private String responseStatusMessage; - @Nullable private Integer apiResponseCode; - @Nullable private String apiResponseMessage; /** From 32e6f149f9ba212ea207edda415aab17b6263204 Mon Sep 17 00:00:00 2001 From: Doug Graham Date: Wed, 15 Mar 2023 08:28:19 -0500 Subject: [PATCH 185/271] This should fix Issue #131 --- schema_json/endpoint/positions/position.json | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/schema_json/endpoint/positions/position.json b/schema_json/endpoint/positions/position.json index 7ebb619f..1217ef51 100644 --- a/schema_json/endpoint/positions/position.json +++ b/schema_json/endpoint/positions/position.json @@ -28,6 +28,11 @@ "javaName": "quantity", "title": "The number of shares." }, + "qty_available": { + "existingJavaType": "java.lang.String", + "javaName": "quantity", + "title": "Total number of shares available minus open orders." + }, "side": { "existingJavaType": "java.lang.String", "title": "\"long\"." From b171b097f2117f1cfbc71ba9f9725cdca1f8ba67 Mon Sep 17 00:00:00 2001 From: Doug Graham Date: Wed, 15 Mar 2023 08:32:32 -0500 Subject: [PATCH 186/271] Needs a unique POJO name --- schema_json/endpoint/positions/position.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/schema_json/endpoint/positions/position.json b/schema_json/endpoint/positions/position.json index 1217ef51..8ae71b33 100644 --- a/schema_json/endpoint/positions/position.json +++ b/schema_json/endpoint/positions/position.json @@ -30,7 +30,7 @@ }, "qty_available": { "existingJavaType": "java.lang.String", - "javaName": "quantity", + "javaName": "quantityAvailable", "title": "Total number of shares available minus open orders." }, "side": { From c23c6e7d05416f7f022d8f144872e2aeee89491e Mon Sep 17 00:00:00 2001 From: Jacob Peterson <14127217+Petersoj@users.noreply.github.com> Date: Mon, 20 Mar 2023 09:13:29 -0600 Subject: [PATCH 187/271] Update to 9.1.3 --- README.md | 4 ++-- build.gradle | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 057e7f0e..98b9acf0 100644 --- a/README.md +++ b/README.md @@ -17,7 +17,7 @@ If you are using Gradle as your build tool, add the following dependency to your ``` dependencies { - implementation group: 'net.jacobpeterson', name: 'alpaca-java', version: '9.1.2' + implementation group: 'net.jacobpeterson', name: 'alpaca-java', version: '9.1.3' } ``` @@ -27,7 +27,7 @@ If you are using Maven as your build tool, add the following dependency to your net.jacobpeterson alpaca-java - 9.1.2 + 9.1.3 compile ``` diff --git a/build.gradle b/build.gradle index bacd0079..c7ec09bf 100644 --- a/build.gradle +++ b/build.gradle @@ -29,7 +29,7 @@ apply plugin: 'jsonschema2pojo' archivesBaseName = 'alpaca-java' group = 'net.jacobpeterson' -version = '9.1.2-SNAPSHOT' +version = '9.1.3-SNAPSHOT' repositories { mavenCentral() From 60f2deb5a5d9a7dea89bdbec091cedd9c94f677a Mon Sep 17 00:00:00 2001 From: Balaji Kandavel Date: Wed, 10 May 2023 23:09:49 -0400 Subject: [PATCH 188/271] Add week and month timeframes https://alpaca.markets/docs/api-references/market-data-api/stock-pricing-data/historical/#bars --- .../common/historical/bar/enums/bar_time_period.json | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/schema_json/endpoint/market_data/common/historical/bar/enums/bar_time_period.json b/schema_json/endpoint/market_data/common/historical/bar/enums/bar_time_period.json index cccf0508..0305b24e 100644 --- a/schema_json/endpoint/market_data/common/historical/bar/enums/bar_time_period.json +++ b/schema_json/endpoint/market_data/common/historical/bar/enums/bar_time_period.json @@ -4,7 +4,9 @@ "enum": [ "Min", "Hour", - "Day" + "Day", + "Week", + "Month" ], "javaEnums": [ { @@ -15,6 +17,12 @@ }, { "name": "DAY" + }, + { + "name": "WEEK" + }, + { + "name": "MONTH" } ] } From a5638b3800e495866a7fde5d8a656bde2017712b Mon Sep 17 00:00:00 2001 From: Jacob Peterson <14127217+Petersoj@users.noreply.github.com> Date: Fri, 26 May 2023 08:55:11 -0600 Subject: [PATCH 189/271] Update to 9.1.4 --- README.md | 4 ++-- build.gradle | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 98b9acf0..a29ffc56 100644 --- a/README.md +++ b/README.md @@ -17,7 +17,7 @@ If you are using Gradle as your build tool, add the following dependency to your ``` dependencies { - implementation group: 'net.jacobpeterson', name: 'alpaca-java', version: '9.1.3' + implementation group: 'net.jacobpeterson', name: 'alpaca-java', version: '9.1.4' } ``` @@ -27,7 +27,7 @@ If you are using Maven as your build tool, add the following dependency to your net.jacobpeterson alpaca-java - 9.1.3 + 9.1.4 compile ``` diff --git a/build.gradle b/build.gradle index c7ec09bf..588393d3 100644 --- a/build.gradle +++ b/build.gradle @@ -29,7 +29,7 @@ apply plugin: 'jsonschema2pojo' archivesBaseName = 'alpaca-java' group = 'net.jacobpeterson' -version = '9.1.3-SNAPSHOT' +version = '9.1.4-SNAPSHOT' repositories { mavenCentral() From 1403348b3b74fe87c2769af2f187588631edda27 Mon Sep 17 00:00:00 2001 From: Jesse Thouin Date: Fri, 7 Jul 2023 15:26:25 -0400 Subject: [PATCH 190/271] Updated to use v1beta3 version of Alpaca APIs. --- .../crypto/common/enums/exchange.json | 20 ---- .../crypto/historical/bar/crypto_bar.json | 7 +- .../historical/bar/crypto_bars_response.json | 8 +- .../crypto/historical/quote/crypto_quote.json | 7 +- .../quote/crypto_quotes_response.json | 8 +- .../quote/latest_crypto_quote_response.json | 2 +- .../crypto/historical/trade/crypto_trade.json | 7 +- .../trade/crypto_trades_response.json | 8 +- .../trade/latest_crypto_trade_response.json | 2 +- .../crypto/historical/xbbo/xbbo.json | 41 ------- .../crypto/historical/xbbo/xbbo_response.json | 14 --- .../realtime/bar/crypto_bar_message.json | 5 - .../realtime/quote/crypto_quote_message.json | 5 - .../realtime/trade/crypto_trade_message.json | 5 - .../net/jacobpeterson/alpaca/AlpacaAPI.java | 4 +- .../crypto/CryptoMarketDataEndpoint.java | 107 +++++------------- .../crypto/CryptoMarketDataWebsocket.java | 4 +- 17 files changed, 42 insertions(+), 212 deletions(-) delete mode 100644 schema_json/endpoint/market_data/crypto/common/enums/exchange.json delete mode 100644 schema_json/endpoint/market_data/crypto/historical/xbbo/xbbo.json delete mode 100644 schema_json/endpoint/market_data/crypto/historical/xbbo/xbbo_response.json diff --git a/schema_json/endpoint/market_data/crypto/common/enums/exchange.json b/schema_json/endpoint/market_data/crypto/common/enums/exchange.json deleted file mode 100644 index b17a92f8..00000000 --- a/schema_json/endpoint/market_data/crypto/common/enums/exchange.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "type": "string", - "title": "See Crypto Data.", - "enum": [ - "ERSX", - "FTXU", - "CBSE" - ], - "javaEnums": [ - { - "name": "ERISX" - }, - { - "name": "FTX" - }, - { - "name": "COINBASE" - } - ] -} diff --git a/schema_json/endpoint/market_data/crypto/historical/bar/crypto_bar.json b/schema_json/endpoint/market_data/crypto/historical/bar/crypto_bar.json index e23c3ec6..1fbf8b5d 100644 --- a/schema_json/endpoint/market_data/crypto/historical/bar/crypto_bar.json +++ b/schema_json/endpoint/market_data/crypto/historical/bar/crypto_bar.json @@ -3,13 +3,8 @@ "extends": { "existingJavaType": "net.jacobpeterson.alpaca.model.endpoint.marketdata.common.historical.bar.Bar" }, - "title": "See Historical Crypto Data.", + "title": "See Historical Crypto Data.", "properties": { - "x": { - "existingJavaType": "net.jacobpeterson.alpaca.model.endpoint.marketdata.crypto.common.enums.Exchange", - "javaName": "exchange", - "title": "Exchange." - }, "v": { "existingJavaType": "java.lang.Double", "javaName": "volume", diff --git a/schema_json/endpoint/market_data/crypto/historical/bar/crypto_bars_response.json b/schema_json/endpoint/market_data/crypto/historical/bar/crypto_bars_response.json index 677e6633..8461d5cc 100644 --- a/schema_json/endpoint/market_data/crypto/historical/bar/crypto_bars_response.json +++ b/schema_json/endpoint/market_data/crypto/historical/bar/crypto_bars_response.json @@ -1,15 +1,11 @@ { "type": "object", - "title": "See Historical Crypto Data.", + "title": "See Historical Crypto Data.", "properties": { "bars": { - "existingJavaType": "java.util.ArrayList", + "existingJavaType": "java.util.Map>", "title": "The {@link java.util.ArrayList} of {@link net.jacobpeterson.alpaca.model.endpoint.marketdata.crypto.historical.bar.CryptoBar}s." }, - "symbol": { - "existingJavaType": "java.lang.String", - "title": "Symbol that was queried." - }, "next_page_token": { "existingJavaType": "java.lang.String", "title": "Token that can be used to query the next page." diff --git a/schema_json/endpoint/market_data/crypto/historical/quote/crypto_quote.json b/schema_json/endpoint/market_data/crypto/historical/quote/crypto_quote.json index 099df001..97e50e0b 100644 --- a/schema_json/endpoint/market_data/crypto/historical/quote/crypto_quote.json +++ b/schema_json/endpoint/market_data/crypto/historical/quote/crypto_quote.json @@ -3,13 +3,8 @@ "extends": { "existingJavaType": "net.jacobpeterson.alpaca.model.endpoint.marketdata.common.historical.quote.Quote" }, - "title": "See Historical Crypto Data.", + "title": "See Historical Crypto Data.", "properties": { - "x": { - "existingJavaType": "net.jacobpeterson.alpaca.model.endpoint.marketdata.crypto.common.enums.Exchange", - "javaName": "exchange", - "title": "Exchange." - }, "as": { "existingJavaType": "java.lang.Double", "javaName": "askSize", diff --git a/schema_json/endpoint/market_data/crypto/historical/quote/crypto_quotes_response.json b/schema_json/endpoint/market_data/crypto/historical/quote/crypto_quotes_response.json index e8a67bfd..a5010f8c 100644 --- a/schema_json/endpoint/market_data/crypto/historical/quote/crypto_quotes_response.json +++ b/schema_json/endpoint/market_data/crypto/historical/quote/crypto_quotes_response.json @@ -1,15 +1,11 @@ { "type": "object", - "title": "See Historical Crypto Data.", + "title": "See Historical Crypto Data.", "properties": { "quotes": { - "existingJavaType": "java.util.ArrayList", + "existingJavaType": "java.util.Map>", "title": "The {@link java.util.ArrayList} of {@link net.jacobpeterson.alpaca.model.endpoint.marketdata.crypto.historical.quote.CryptoQuote}s." }, - "symbol": { - "existingJavaType": "java.lang.String", - "title": "Symbol that was queried." - }, "next_page_token": { "existingJavaType": "java.lang.String", "title": "Token that can be used to query the next page." diff --git a/schema_json/endpoint/market_data/crypto/historical/quote/latest_crypto_quote_response.json b/schema_json/endpoint/market_data/crypto/historical/quote/latest_crypto_quote_response.json index 64bf1d75..6db62c25 100644 --- a/schema_json/endpoint/market_data/crypto/historical/quote/latest_crypto_quote_response.json +++ b/schema_json/endpoint/market_data/crypto/historical/quote/latest_crypto_quote_response.json @@ -1,6 +1,6 @@ { "type": "object", - "title": "See Historical Crypto Data.", + "title": "See Historical Crypto Data.", "properties": { "symbol": { "existingJavaType": "java.lang.String", diff --git a/schema_json/endpoint/market_data/crypto/historical/trade/crypto_trade.json b/schema_json/endpoint/market_data/crypto/historical/trade/crypto_trade.json index 0b0c1b53..8e5bf0da 100644 --- a/schema_json/endpoint/market_data/crypto/historical/trade/crypto_trade.json +++ b/schema_json/endpoint/market_data/crypto/historical/trade/crypto_trade.json @@ -3,13 +3,8 @@ "extends": { "existingJavaType": "net.jacobpeterson.alpaca.model.endpoint.marketdata.common.historical.trade.Trade" }, - "title": "See Historical Crypto Data.", + "title": "See Historical Crypto Data.", "properties": { - "x": { - "existingJavaType": "net.jacobpeterson.alpaca.model.endpoint.marketdata.crypto.common.enums.Exchange", - "javaName": "exchange", - "title": "Exchange where the trade happened." - }, "s": { "existingJavaType": "java.lang.Double", "javaName": "size", diff --git a/schema_json/endpoint/market_data/crypto/historical/trade/crypto_trades_response.json b/schema_json/endpoint/market_data/crypto/historical/trade/crypto_trades_response.json index a512c775..0a007487 100644 --- a/schema_json/endpoint/market_data/crypto/historical/trade/crypto_trades_response.json +++ b/schema_json/endpoint/market_data/crypto/historical/trade/crypto_trades_response.json @@ -1,15 +1,11 @@ { "type": "object", - "title": "See Historical Crypto Data.", + "title": "See Historical Crypto Data.", "properties": { "trades": { - "existingJavaType": "java.util.ArrayList", + "existingJavaType": "java.util.Map>", "title": "The {@link java.util.ArrayList} of {@link net.jacobpeterson.alpaca.model.endpoint.marketdata.crypto.historical.trade.CryptoTrade}s." }, - "symbol": { - "existingJavaType": "java.lang.String", - "title": "Symbol that was queried." - }, "next_page_token": { "existingJavaType": "java.lang.String", "title": "Token that can be used to query the next page." diff --git a/schema_json/endpoint/market_data/crypto/historical/trade/latest_crypto_trade_response.json b/schema_json/endpoint/market_data/crypto/historical/trade/latest_crypto_trade_response.json index 512c6a18..b49e4ef7 100644 --- a/schema_json/endpoint/market_data/crypto/historical/trade/latest_crypto_trade_response.json +++ b/schema_json/endpoint/market_data/crypto/historical/trade/latest_crypto_trade_response.json @@ -1,6 +1,6 @@ { "type": "object", - "title": "See Historical Crypto Data.", + "title": "See Historical Crypto Data.", "properties": { "symbol": { "existingJavaType": "java.lang.String", diff --git a/schema_json/endpoint/market_data/crypto/historical/xbbo/xbbo.json b/schema_json/endpoint/market_data/crypto/historical/xbbo/xbbo.json deleted file mode 100644 index d279722f..00000000 --- a/schema_json/endpoint/market_data/crypto/historical/xbbo/xbbo.json +++ /dev/null @@ -1,41 +0,0 @@ -{ - "type": "object", - "title": "See Historical Crypto Data.", - "properties": { - "t": { - "existingJavaType": "java.time.ZonedDateTime", - "javaName": "timestamp", - "title": "Timestamp with nanosecond precision." - }, - "ax": { - "existingJavaType": "net.jacobpeterson.alpaca.model.endpoint.marketdata.crypto.common.enums.Exchange", - "javaName": "askExchange", - "title": "Ask Exchange." - }, - "ap": { - "existingJavaType": "java.lang.Double", - "javaName": "askPrice", - "title": "Ask price." - }, - "as": { - "existingJavaType": "java.lang.Double", - "javaName": "askSize", - "title": "Ask size." - }, - "bx": { - "existingJavaType": "net.jacobpeterson.alpaca.model.endpoint.marketdata.crypto.common.enums.Exchange", - "javaName": "bidExchange", - "title": "Bid Exchange." - }, - "bp": { - "existingJavaType": "java.lang.Double", - "javaName": "bidPrice", - "title": "Bid price." - }, - "bs": { - "existingJavaType": "java.lang.Double", - "javaName": "bidSize", - "title": "Bid size." - } - } -} diff --git a/schema_json/endpoint/market_data/crypto/historical/xbbo/xbbo_response.json b/schema_json/endpoint/market_data/crypto/historical/xbbo/xbbo_response.json deleted file mode 100644 index 2fdc8f28..00000000 --- a/schema_json/endpoint/market_data/crypto/historical/xbbo/xbbo_response.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "type": "object", - "title": "See Historical Crypto Data.", - "properties": { - "symbol": { - "existingJavaType": "java.lang.String", - "title": "Symbol that was queried." - }, - "xbbo": { - "existingJavaType": "net.jacobpeterson.alpaca.model.endpoint.marketdata.crypto.historical.xbbo.Xbbo", - "title": "The latest {@link net.jacobpeterson.alpaca.model.endpoint.marketdata.crypto.historical.xbbo.Xbbo}." - } - } -} diff --git a/schema_json/endpoint/market_data/crypto/realtime/bar/crypto_bar_message.json b/schema_json/endpoint/market_data/crypto/realtime/bar/crypto_bar_message.json index 8886e9d4..82cfe47f 100644 --- a/schema_json/endpoint/market_data/crypto/realtime/bar/crypto_bar_message.json +++ b/schema_json/endpoint/market_data/crypto/realtime/bar/crypto_bar_message.json @@ -5,11 +5,6 @@ }, "title": "See Realtime Crypto Market Data.", "properties": { - "x": { - "existingJavaType": "net.jacobpeterson.alpaca.model.endpoint.marketdata.crypto.common.enums.Exchange", - "javaName": "exchange", - "title": "Exchange." - }, "v": { "existingJavaType": "java.lang.Double", "javaName": "volume", diff --git a/schema_json/endpoint/market_data/crypto/realtime/quote/crypto_quote_message.json b/schema_json/endpoint/market_data/crypto/realtime/quote/crypto_quote_message.json index 9d9a2618..1eac99c5 100644 --- a/schema_json/endpoint/market_data/crypto/realtime/quote/crypto_quote_message.json +++ b/schema_json/endpoint/market_data/crypto/realtime/quote/crypto_quote_message.json @@ -5,11 +5,6 @@ }, "title": "See Realtime Crypto Market Data.", "properties": { - "x": { - "existingJavaType": "net.jacobpeterson.alpaca.model.endpoint.marketdata.crypto.common.enums.Exchange", - "javaName": "exchange", - "title": "Exchange." - }, "as": { "existingJavaType": "java.lang.Double", "javaName": "askSize", diff --git a/schema_json/endpoint/market_data/crypto/realtime/trade/crypto_trade_message.json b/schema_json/endpoint/market_data/crypto/realtime/trade/crypto_trade_message.json index ec4f5227..09ee5a87 100644 --- a/schema_json/endpoint/market_data/crypto/realtime/trade/crypto_trade_message.json +++ b/schema_json/endpoint/market_data/crypto/realtime/trade/crypto_trade_message.json @@ -5,11 +5,6 @@ }, "title": "See Realtime Crypto Market Data.", "properties": { - "x": { - "existingJavaType": "net.jacobpeterson.alpaca.model.endpoint.marketdata.crypto.common.enums.Exchange", - "javaName": "exchange", - "title": "Exchange where the trade happened." - }, "s": { "existingJavaType": "java.lang.Double", "javaName": "size", diff --git a/src/main/java/net/jacobpeterson/alpaca/AlpacaAPI.java b/src/main/java/net/jacobpeterson/alpaca/AlpacaAPI.java index 63980318..c71f496b 100644 --- a/src/main/java/net/jacobpeterson/alpaca/AlpacaAPI.java +++ b/src/main/java/net/jacobpeterson/alpaca/AlpacaAPI.java @@ -44,7 +44,7 @@ public class AlpacaAPI { private static final Logger LOGGER = LoggerFactory.getLogger(AlpacaAPI.class); private static final String VERSION_2_PATH_SEGMENT = "v2"; - private static final String VERSION_1_BETA_1_PATH_SEGMENT = "v1beta1"; + private static final String VERSION_1_BETA_3_PATH_SEGMENT = "v1beta3"; private final OkHttpClient okHttpClient; private final AlpacaClient brokerClient; @@ -160,7 +160,7 @@ public AlpacaAPI(OkHttpClient okHttpClient, String keyID, String secretKey, Stri if (oAuthToken == null) { brokerClient = new AlpacaClient(okHttpClient, keyID, secretKey, brokerHostSubdomain, VERSION_2_PATH_SEGMENT); - cryptoDataClient = new AlpacaClient(okHttpClient, keyID, secretKey, "data", VERSION_1_BETA_1_PATH_SEGMENT); + cryptoDataClient = new AlpacaClient(okHttpClient, keyID, secretKey, "data", VERSION_1_BETA_3_PATH_SEGMENT); stockDataClient = new AlpacaClient(okHttpClient, keyID, secretKey, "data", VERSION_2_PATH_SEGMENT); } else { brokerClient = new AlpacaClient(okHttpClient, oAuthToken, brokerHostSubdomain, VERSION_2_PATH_SEGMENT); diff --git a/src/main/java/net/jacobpeterson/alpaca/rest/endpoint/marketdata/crypto/CryptoMarketDataEndpoint.java b/src/main/java/net/jacobpeterson/alpaca/rest/endpoint/marketdata/crypto/CryptoMarketDataEndpoint.java index b3136b17..0062d3fe 100644 --- a/src/main/java/net/jacobpeterson/alpaca/rest/endpoint/marketdata/crypto/CryptoMarketDataEndpoint.java +++ b/src/main/java/net/jacobpeterson/alpaca/rest/endpoint/marketdata/crypto/CryptoMarketDataEndpoint.java @@ -1,7 +1,6 @@ package net.jacobpeterson.alpaca.rest.endpoint.marketdata.crypto; import net.jacobpeterson.alpaca.model.endpoint.marketdata.common.historical.bar.enums.BarTimePeriod; -import net.jacobpeterson.alpaca.model.endpoint.marketdata.crypto.common.enums.Exchange; import net.jacobpeterson.alpaca.model.endpoint.marketdata.crypto.historical.bar.CryptoBar; import net.jacobpeterson.alpaca.model.endpoint.marketdata.crypto.historical.bar.CryptoBarsResponse; import net.jacobpeterson.alpaca.model.endpoint.marketdata.crypto.historical.quote.CryptoQuote; @@ -10,7 +9,6 @@ import net.jacobpeterson.alpaca.model.endpoint.marketdata.crypto.historical.trade.CryptoTrade; import net.jacobpeterson.alpaca.model.endpoint.marketdata.crypto.historical.trade.CryptoTradesResponse; import net.jacobpeterson.alpaca.model.endpoint.marketdata.crypto.historical.trade.LatestCryptoTradeResponse; -import net.jacobpeterson.alpaca.model.endpoint.marketdata.crypto.historical.xbbo.XbboResponse; import net.jacobpeterson.alpaca.rest.AlpacaClient; import net.jacobpeterson.alpaca.rest.AlpacaClientException; import net.jacobpeterson.alpaca.rest.endpoint.AlpacaEndpoint; @@ -26,10 +24,11 @@ /** * {@link AlpacaEndpoint} for - * Historical + * Historical * Crypto Market Data API. */ public class CryptoMarketDataEndpoint extends AlpacaEndpoint { + public static final String LOCALE = "us"; /** * Instantiates a new {@link CryptoMarketDataEndpoint}. @@ -43,8 +42,7 @@ public CryptoMarketDataEndpoint(AlpacaClient alpacaClient) { /** * Gets {@link CryptoTrade} historical data for the requested crypto symbol. * - * @param symbol the symbol to query for - * @param exchanges a {@link Collection} of {@link Exchange}s to filter by. null for all exchanges. + * @param symbols a {@link Collection} of symbols to query for * @param start filter data equal to or after this {@link ZonedDateTime}. Fractions of a second are not * accepted. null for the current day in Central Time. * @param end filter data equal to or before this {@link ZonedDateTime}. Fractions of a second are not @@ -57,20 +55,16 @@ public CryptoMarketDataEndpoint(AlpacaClient alpacaClient) { * * @throws AlpacaClientException thrown for {@link AlpacaClientException}s */ - public CryptoTradesResponse getTrades(String symbol, Collection exchanges, ZonedDateTime start, + public CryptoTradesResponse getTrades(Collection symbols, ZonedDateTime start, ZonedDateTime end, Integer limit, String pageToken) throws AlpacaClientException { - checkNotNull(symbol); + checkNotNull(symbols); HttpUrl.Builder urlBuilder = alpacaClient.urlBuilder() .addPathSegment(endpointPathSegment) - .addPathSegment(symbol) + .addPathSegment(LOCALE) .addPathSegment("trades"); - if (exchanges != null) { - urlBuilder.addQueryParameter("exchanges", exchanges.stream() - .map(Exchange::value) - .collect(Collectors.joining(","))); - } + urlBuilder.addQueryParameter("symbols", String.join(",", symbols)); if (start != null) { urlBuilder.addQueryParameter("start", FormatUtil.toRFC3339Format(start)); @@ -97,24 +91,22 @@ public CryptoTradesResponse getTrades(String symbol, Collection exchan /** * Gets the latest {@link CryptoTrade} for the requested security. * - * @param symbol the symbol to query for - * @param exchange the {@link Exchange} to filter the latest {@link CryptoTrade} by. + * @param symbols a {@link Collection} of symbols to query for * * @return the {@link LatestCryptoTradeResponse} * * @throws AlpacaClientException thrown for {@link AlpacaClientException}s */ - public LatestCryptoTradeResponse getLatestTrade(String symbol, Exchange exchange) throws AlpacaClientException { - checkNotNull(symbol); - checkNotNull(exchange); + public LatestCryptoTradeResponse getLatestTrade(Collection symbols) throws AlpacaClientException { + checkNotNull(symbols); HttpUrl.Builder urlBuilder = alpacaClient.urlBuilder() .addPathSegment(endpointPathSegment) - .addPathSegment(symbol) + .addPathSegment(LOCALE) .addPathSegment("trades") .addPathSegment("latest"); - urlBuilder.addQueryParameter("exchange", exchange.toString()); + urlBuilder.addQueryParameter("symbols", String.join(",", symbols)); Request request = alpacaClient.requestBuilder(urlBuilder.build()) .get() @@ -125,8 +117,7 @@ public LatestCryptoTradeResponse getLatestTrade(String symbol, Exchange exchange /** * Gets {@link CryptoQuote} historical data for the requested crypto symbol. * - * @param symbol the symbol to query for - * @param exchanges a {@link Collection} of {@link Exchange}s to filter by. null for all exchanges. + * @param symbols a {@link Collection} of symbols to query for * @param start filter data equal to or after this {@link ZonedDateTime}. Fractions of a second are not * accepted. null for the current day in Central Time. * @param end filter data equal to or before this {@link ZonedDateTime}. Fractions of a second are not @@ -139,20 +130,16 @@ public LatestCryptoTradeResponse getLatestTrade(String symbol, Exchange exchange * * @throws AlpacaClientException thrown for {@link AlpacaClientException}s */ - public CryptoQuotesResponse getQuotes(String symbol, Collection exchanges, ZonedDateTime start, + public CryptoQuotesResponse getQuotes(Collection symbols, ZonedDateTime start, ZonedDateTime end, Integer limit, String pageToken) throws AlpacaClientException { - checkNotNull(symbol); + checkNotNull(symbols); HttpUrl.Builder urlBuilder = alpacaClient.urlBuilder() .addPathSegment(endpointPathSegment) - .addPathSegment(symbol) + .addPathSegment(LOCALE) .addPathSegment("quotes"); - if (exchanges != null) { - urlBuilder.addQueryParameter("exchanges", exchanges.stream() - .map(Exchange::value) - .collect(Collectors.joining(","))); - } + urlBuilder.addQueryParameter("symbols", String.join(",", symbols)); if (start != null) { urlBuilder.addQueryParameter("start", FormatUtil.toRFC3339Format(start)); @@ -179,24 +166,22 @@ public CryptoQuotesResponse getQuotes(String symbol, Collection exchan /** * Gets the latest {@link CryptoQuote} for the requested security. * - * @param symbol the symbol to query for - * @param exchange the {@link Exchange} to filter the latest {@link CryptoQuote} by. + * @param symbols a {@link Collection} of symbols to query for * * @return the {@link LatestCryptoQuoteResponse} * * @throws AlpacaClientException thrown for {@link AlpacaClientException}s */ - public LatestCryptoQuoteResponse getLatestQuote(String symbol, Exchange exchange) throws AlpacaClientException { - checkNotNull(symbol); - checkNotNull(exchange); + public LatestCryptoQuoteResponse getLatestQuote(Collection symbols) throws AlpacaClientException { + checkNotNull(symbols); HttpUrl.Builder urlBuilder = alpacaClient.urlBuilder() .addPathSegment(endpointPathSegment) - .addPathSegment(symbol) + .addPathSegment(LOCALE) .addPathSegment("quotes") .addPathSegment("latest"); - urlBuilder.addQueryParameter("exchange", exchange.toString()); + urlBuilder.addQueryParameter("symbols", String.join(",", symbols)); Request request = alpacaClient.requestBuilder(urlBuilder.build()) .get() @@ -207,9 +192,7 @@ public LatestCryptoQuoteResponse getLatestQuote(String symbol, Exchange exchange /** * Gets {@link CryptoBar} aggregate historical data for the requested crypto. * - * @param symbol the symbol to query for - * @param exchanges a {@link Collection} of {@link Exchange}s to filter by. null for all - * exchanges. + * @param symbols a {@link Collection} of symbols to query for * @param start filter data equal to or after this {@link ZonedDateTime}. Fractions of a second are * not accepted. null for now. * @param limit number of data points to return. Must be in range 1-10000, defaults to 1000 if @@ -226,21 +209,17 @@ public LatestCryptoQuoteResponse getLatestQuote(String symbol, Exchange exchange * * @throws AlpacaClientException thrown for {@link AlpacaClientException}s */ - public CryptoBarsResponse getBars(String symbol, Collection exchanges, ZonedDateTime start, Integer limit, + public CryptoBarsResponse getBars(Collection symbols, ZonedDateTime start, Integer limit, String pageToken, int barTimePeriodDuration, BarTimePeriod barTimePeriod) throws AlpacaClientException { - checkNotNull(symbol); + checkNotNull(symbols); checkNotNull(barTimePeriod); HttpUrl.Builder urlBuilder = alpacaClient.urlBuilder() .addPathSegment(endpointPathSegment) - .addPathSegment(symbol) + .addPathSegment(LOCALE) .addPathSegment("bars"); - if (exchanges != null) { - urlBuilder.addQueryParameter("exchanges", exchanges.stream() - .map(Exchange::value) - .collect(Collectors.joining(","))); - } + urlBuilder.addQueryParameter("symbols", String.join(",", symbols)); if (start != null) { urlBuilder.addQueryParameter("start", FormatUtil.toRFC3339Format(start)); @@ -261,36 +240,4 @@ public CryptoBarsResponse getBars(String symbol, Collection exchanges, .build(); return alpacaClient.requestObject(request, CryptoBarsResponse.class); } - - /** - * Returns the XBBO for a crypto symbol that calculates the Best Bid and Offer across multiple exchanges. - * - * @param symbol the symbol to query for - * @param exchanges a {@link Collection} of {@link Exchange}s to filter by. If null, then only the - * exchanges that can be traded on Alpaca are included in the calculation. - * - * @return the {@link XbboResponse} - * - * @throws AlpacaClientException thrown for {@link AlpacaClientException}s - */ - public XbboResponse getXBBO(String symbol, Collection exchanges) throws AlpacaClientException { - checkNotNull(symbol); - - HttpUrl.Builder urlBuilder = alpacaClient.urlBuilder() - .addPathSegment(endpointPathSegment) - .addPathSegment(symbol) - .addPathSegment("xbbo") - .addPathSegment("latest"); - - if (exchanges != null) { - urlBuilder.addQueryParameter("exchanges", exchanges.stream() - .map(Exchange::value) - .collect(Collectors.joining(","))); - } - - Request request = alpacaClient.requestBuilder(urlBuilder.build()) - .get() - .build(); - return alpacaClient.requestObject(request, XbboResponse.class); - } } diff --git a/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/crypto/CryptoMarketDataWebsocket.java b/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/crypto/CryptoMarketDataWebsocket.java index fa61d1ec..27168332 100644 --- a/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/crypto/CryptoMarketDataWebsocket.java +++ b/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/crypto/CryptoMarketDataWebsocket.java @@ -8,7 +8,7 @@ /** * {@link CryptoMarketDataWebsocket} is a {@link MarketDataWebsocket} for - * Realtime + * Realtime * Crypto Market Data */ public class CryptoMarketDataWebsocket extends MarketDataWebsocket { @@ -21,7 +21,7 @@ public class CryptoMarketDataWebsocket extends MarketDataWebsocket { * @param secretKey the secret key */ public CryptoMarketDataWebsocket(OkHttpClient okHttpClient, String keyID, String secretKey) { - super(okHttpClient, "v1beta1/crypto", "Crypto", keyID, secretKey, CryptoTradeMessage.class, + super(okHttpClient, "v1beta3/crypto/us", "Crypto", keyID, secretKey, CryptoTradeMessage.class, CryptoQuoteMessage.class, CryptoBarMessage.class); } } From 4aab2d5f98adde2957f4bf3845426d20ff16d098 Mon Sep 17 00:00:00 2001 From: Jesse Thouin Date: Tue, 11 Jul 2023 11:39:41 -0400 Subject: [PATCH 191/271] Further updates to match v1beta3 crypto APIs. --- .../bar/latest_crypto_bars_response.json | 10 ++ .../orderbook/crypto_orderbook.json | 21 +++ .../orderbook/crypto_orderbook_entry.json | 16 ++ .../latest_crypto_orderbooks_response.json | 10 ++ .../quote/latest_crypto_quote_response.json | 14 -- .../quote/latest_crypto_quotes_response.json | 10 ++ .../historical/snapshot/crypto_snapshot.json | 31 ++++ .../snapshot/crypto_snapshots_response.json | 10 ++ .../trade/latest_crypto_trade_response.json | 14 -- .../trade/latest_crypto_trades_response.json | 10 ++ .../crypto/CryptoMarketDataEndpoint.java | 147 +++++++++++++++--- 11 files changed, 247 insertions(+), 46 deletions(-) create mode 100644 schema_json/endpoint/market_data/crypto/historical/bar/latest_crypto_bars_response.json create mode 100644 schema_json/endpoint/market_data/crypto/historical/orderbook/crypto_orderbook.json create mode 100644 schema_json/endpoint/market_data/crypto/historical/orderbook/crypto_orderbook_entry.json create mode 100644 schema_json/endpoint/market_data/crypto/historical/orderbook/latest_crypto_orderbooks_response.json delete mode 100644 schema_json/endpoint/market_data/crypto/historical/quote/latest_crypto_quote_response.json create mode 100644 schema_json/endpoint/market_data/crypto/historical/quote/latest_crypto_quotes_response.json create mode 100644 schema_json/endpoint/market_data/crypto/historical/snapshot/crypto_snapshot.json create mode 100644 schema_json/endpoint/market_data/crypto/historical/snapshot/crypto_snapshots_response.json delete mode 100644 schema_json/endpoint/market_data/crypto/historical/trade/latest_crypto_trade_response.json create mode 100644 schema_json/endpoint/market_data/crypto/historical/trade/latest_crypto_trades_response.json diff --git a/schema_json/endpoint/market_data/crypto/historical/bar/latest_crypto_bars_response.json b/schema_json/endpoint/market_data/crypto/historical/bar/latest_crypto_bars_response.json new file mode 100644 index 00000000..b502ddc3 --- /dev/null +++ b/schema_json/endpoint/market_data/crypto/historical/bar/latest_crypto_bars_response.json @@ -0,0 +1,10 @@ +{ + "type": "object", + "title": "See Historical Crypto Data.", + "properties": { + "bars": { + "existingJavaType": "java.util.Map", + "title": "The {@link java.util.Map} of {@link net.jacobpeterson.alpaca.model.endpoint.marketdata.crypto.historical.bar.CryptoBar}s." + } + } +} diff --git a/schema_json/endpoint/market_data/crypto/historical/orderbook/crypto_orderbook.json b/schema_json/endpoint/market_data/crypto/historical/orderbook/crypto_orderbook.json new file mode 100644 index 00000000..04409010 --- /dev/null +++ b/schema_json/endpoint/market_data/crypto/historical/orderbook/crypto_orderbook.json @@ -0,0 +1,21 @@ +{ + "type": "object", + "title": "See Latest Orderbook.", + "properties": { + "t": { + "existingJavaType": "java.time.ZonedDateTime", + "javaName": "timestamp", + "title": "Timestamp in RFC-3339 format with nanosecond precision." + }, + "b": { + "existingJavaType": "java.util.ArrayList", + "javaName": "bidSide", + "title": "An {@link java.util.ArrayList} of {@link net.jacobpeterson.alpaca.model.endpoint.marketdata.crypto.historical.orderbook.CryptoOrderbookEntry}s for the buy/bid side." + }, + "a": { + "existingJavaType": "java.util.ArrayList", + "javaName": "askSide", + "title": "An {@link java.util.ArrayList} of {@link net.jacobpeterson.alpaca.model.endpoint.marketdata.crypto.historical.orderbook.CryptoOrderbookEntry}s for the ask/sell side." + } + } +} diff --git a/schema_json/endpoint/market_data/crypto/historical/orderbook/crypto_orderbook_entry.json b/schema_json/endpoint/market_data/crypto/historical/orderbook/crypto_orderbook_entry.json new file mode 100644 index 00000000..5387ecc7 --- /dev/null +++ b/schema_json/endpoint/market_data/crypto/historical/orderbook/crypto_orderbook_entry.json @@ -0,0 +1,16 @@ +{ + "type": "object", + "title": "See Latest Orderbook.", + "properties": { + "p": { + "existingJavaType": "java.lang.Double", + "javaName": "price", + "title": "Price." + }, + "s": { + "existingJavaType": "java.lang.Double", + "javaName": "size", + "title": "Size." + } + } +} diff --git a/schema_json/endpoint/market_data/crypto/historical/orderbook/latest_crypto_orderbooks_response.json b/schema_json/endpoint/market_data/crypto/historical/orderbook/latest_crypto_orderbooks_response.json new file mode 100644 index 00000000..31c605d3 --- /dev/null +++ b/schema_json/endpoint/market_data/crypto/historical/orderbook/latest_crypto_orderbooks_response.json @@ -0,0 +1,10 @@ +{ + "type": "object", + "title": "See Historical Crypto Data.", + "properties": { + "orderbooks": { + "existingJavaType": "java.util.Map", + "title": "The {@link java.util.Map} of {@link net.jacobpeterson.alpaca.model.endpoint.marketdata.crypto.historical.orderbook.CryptoOrderbook}s." + } + } +} diff --git a/schema_json/endpoint/market_data/crypto/historical/quote/latest_crypto_quote_response.json b/schema_json/endpoint/market_data/crypto/historical/quote/latest_crypto_quote_response.json deleted file mode 100644 index 6db62c25..00000000 --- a/schema_json/endpoint/market_data/crypto/historical/quote/latest_crypto_quote_response.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "type": "object", - "title": "See Historical Crypto Data.", - "properties": { - "symbol": { - "existingJavaType": "java.lang.String", - "title": "Symbol that was queried." - }, - "quote": { - "existingJavaType": "net.jacobpeterson.alpaca.model.endpoint.marketdata.crypto.historical.quote.CryptoQuote", - "title": "The latest {@link net.jacobpeterson.alpaca.model.endpoint.marketdata.crypto.historical.quote.CryptoQuote}." - } - } -} diff --git a/schema_json/endpoint/market_data/crypto/historical/quote/latest_crypto_quotes_response.json b/schema_json/endpoint/market_data/crypto/historical/quote/latest_crypto_quotes_response.json new file mode 100644 index 00000000..995ccd02 --- /dev/null +++ b/schema_json/endpoint/market_data/crypto/historical/quote/latest_crypto_quotes_response.json @@ -0,0 +1,10 @@ +{ + "type": "object", + "title": "See Historical Crypto Data.", + "properties": { + "quotes": { + "existingJavaType": "java.util.Map", + "title": "The {@link java.util.Map} of {@link net.jacobpeterson.alpaca.model.endpoint.marketdata.crypto.historical.quote.CryptoQuote}s." + } + } +} diff --git a/schema_json/endpoint/market_data/crypto/historical/snapshot/crypto_snapshot.json b/schema_json/endpoint/market_data/crypto/historical/snapshot/crypto_snapshot.json new file mode 100644 index 00000000..9ff6ab0d --- /dev/null +++ b/schema_json/endpoint/market_data/crypto/historical/snapshot/crypto_snapshot.json @@ -0,0 +1,31 @@ +{ + "type": "object", + "title": "See Snapshots.", + "properties": { + "dailyBar": { + "existingJavaType": "net.jacobpeterson.alpaca.model.endpoint.marketdata.crypto.historical.bar.CryptoBar", + "javaName": "dailyBar", + "title": "OHLC aggregate of all the trades in a given interval." + }, + "latestQuote": { + "existingJavaType": "net.jacobpeterson.alpaca.model.endpoint.marketdata.crypto.historical.quote.CryptoQuote", + "javaName": "latestQuote", + "title": "The best bid and ask information for a given security." + }, + "latestTrade": { + "existingJavaType": "net.jacobpeterson.alpaca.model.endpoint.marketdata.crypto.historical.trade.CryptoTrade", + "javaName": "latestTrade", + "title": "A crypto trade" + }, + "minuteBar": { + "existingJavaType": "net.jacobpeterson.alpaca.model.endpoint.marketdata.crypto.historical.bar.CryptoBar", + "javaName": "minuteBar", + "title": "OHLC aggregate of all the trades in a given interval." + }, + "prevDailyBar": { + "existingJavaType": "net.jacobpeterson.alpaca.model.endpoint.marketdata.crypto.historical.bar.CryptoBar", + "javaName": "prevDailyBar", + "title": "OHLC aggregate of all the trades in a given interval." + } + } +} diff --git a/schema_json/endpoint/market_data/crypto/historical/snapshot/crypto_snapshots_response.json b/schema_json/endpoint/market_data/crypto/historical/snapshot/crypto_snapshots_response.json new file mode 100644 index 00000000..3ec3f3b8 --- /dev/null +++ b/schema_json/endpoint/market_data/crypto/historical/snapshot/crypto_snapshots_response.json @@ -0,0 +1,10 @@ +{ + "type": "object", + "title": "See Historical Crypto Data.", + "properties": { + "snapshots": { + "existingJavaType": "java.util.Map", + "title": "The {@link java.util.Map} of {@link net.jacobpeterson.alpaca.model.endpoint.marketdata.crypto.historical.snapshot.CryptoSnapshot}s." + } + } +} diff --git a/schema_json/endpoint/market_data/crypto/historical/trade/latest_crypto_trade_response.json b/schema_json/endpoint/market_data/crypto/historical/trade/latest_crypto_trade_response.json deleted file mode 100644 index b49e4ef7..00000000 --- a/schema_json/endpoint/market_data/crypto/historical/trade/latest_crypto_trade_response.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "type": "object", - "title": "See Historical Crypto Data.", - "properties": { - "symbol": { - "existingJavaType": "java.lang.String", - "title": "Symbol that was queried." - }, - "trade": { - "existingJavaType": "net.jacobpeterson.alpaca.model.endpoint.marketdata.crypto.historical.trade.CryptoTrade", - "title": "The latest {@link net.jacobpeterson.alpaca.model.endpoint.marketdata.crypto.historical.trade.CryptoTrade}." - } - } -} diff --git a/schema_json/endpoint/market_data/crypto/historical/trade/latest_crypto_trades_response.json b/schema_json/endpoint/market_data/crypto/historical/trade/latest_crypto_trades_response.json new file mode 100644 index 00000000..4e6749c2 --- /dev/null +++ b/schema_json/endpoint/market_data/crypto/historical/trade/latest_crypto_trades_response.json @@ -0,0 +1,10 @@ +{ + "type": "object", + "title": "See Historical Crypto Data.", + "properties": { + "trades": { + "existingJavaType": "java.util.Map", + "title": "The {@link java.util.Map} of {@link net.jacobpeterson.alpaca.model.endpoint.marketdata.crypto.historical.trade.CryptoTrade}s." + } + } +} diff --git a/src/main/java/net/jacobpeterson/alpaca/rest/endpoint/marketdata/crypto/CryptoMarketDataEndpoint.java b/src/main/java/net/jacobpeterson/alpaca/rest/endpoint/marketdata/crypto/CryptoMarketDataEndpoint.java index 0062d3fe..bebe7a87 100644 --- a/src/main/java/net/jacobpeterson/alpaca/rest/endpoint/marketdata/crypto/CryptoMarketDataEndpoint.java +++ b/src/main/java/net/jacobpeterson/alpaca/rest/endpoint/marketdata/crypto/CryptoMarketDataEndpoint.java @@ -3,12 +3,17 @@ import net.jacobpeterson.alpaca.model.endpoint.marketdata.common.historical.bar.enums.BarTimePeriod; import net.jacobpeterson.alpaca.model.endpoint.marketdata.crypto.historical.bar.CryptoBar; import net.jacobpeterson.alpaca.model.endpoint.marketdata.crypto.historical.bar.CryptoBarsResponse; +import net.jacobpeterson.alpaca.model.endpoint.marketdata.crypto.historical.bar.LatestCryptoBarsResponse; +import net.jacobpeterson.alpaca.model.endpoint.marketdata.crypto.historical.orderbook.CryptoOrderbook; +import net.jacobpeterson.alpaca.model.endpoint.marketdata.crypto.historical.orderbook.LatestCryptoOrderbooksResponse; import net.jacobpeterson.alpaca.model.endpoint.marketdata.crypto.historical.quote.CryptoQuote; import net.jacobpeterson.alpaca.model.endpoint.marketdata.crypto.historical.quote.CryptoQuotesResponse; -import net.jacobpeterson.alpaca.model.endpoint.marketdata.crypto.historical.quote.LatestCryptoQuoteResponse; +import net.jacobpeterson.alpaca.model.endpoint.marketdata.crypto.historical.quote.LatestCryptoQuotesResponse; +import net.jacobpeterson.alpaca.model.endpoint.marketdata.crypto.historical.snapshot.CryptoSnapshot; +import net.jacobpeterson.alpaca.model.endpoint.marketdata.crypto.historical.snapshot.CryptoSnapshotsResponse; import net.jacobpeterson.alpaca.model.endpoint.marketdata.crypto.historical.trade.CryptoTrade; import net.jacobpeterson.alpaca.model.endpoint.marketdata.crypto.historical.trade.CryptoTradesResponse; -import net.jacobpeterson.alpaca.model.endpoint.marketdata.crypto.historical.trade.LatestCryptoTradeResponse; +import net.jacobpeterson.alpaca.model.endpoint.marketdata.crypto.historical.trade.LatestCryptoTradesResponse; import net.jacobpeterson.alpaca.rest.AlpacaClient; import net.jacobpeterson.alpaca.rest.AlpacaClientException; import net.jacobpeterson.alpaca.rest.endpoint.AlpacaEndpoint; @@ -18,7 +23,6 @@ import java.time.ZonedDateTime; import java.util.Collection; -import java.util.stream.Collectors; import static com.google.common.base.Preconditions.checkNotNull; @@ -40,7 +44,7 @@ public CryptoMarketDataEndpoint(AlpacaClient alpacaClient) { } /** - * Gets {@link CryptoTrade} historical data for the requested crypto symbol. + * Gets {@link CryptoTrade} historical data for the requested crypto symbols. * * @param symbols a {@link Collection} of symbols to query for * @param start filter data equal to or after this {@link ZonedDateTime}. Fractions of a second are not @@ -58,6 +62,9 @@ public CryptoMarketDataEndpoint(AlpacaClient alpacaClient) { public CryptoTradesResponse getTrades(Collection symbols, ZonedDateTime start, ZonedDateTime end, Integer limit, String pageToken) throws AlpacaClientException { checkNotNull(symbols); + if (symbols.isEmpty()) { + throw new AlpacaClientException("Collection symbols must not be empty."); + } HttpUrl.Builder urlBuilder = alpacaClient.urlBuilder() .addPathSegment(endpointPathSegment) @@ -89,33 +96,65 @@ public CryptoTradesResponse getTrades(Collection symbols, ZonedDateTime } /** - * Gets the latest {@link CryptoTrade} for the requested security. + * Gets the latest {@link CryptoTrade}s for the requested securities. + * + * @param symbols a {@link Collection} of symbols to query for + * + * @return the {@link LatestCryptoTradesResponse} + * + * @throws AlpacaClientException thrown for {@link AlpacaClientException}s + */ + public LatestCryptoTradesResponse getLatestTrades(Collection symbols) throws AlpacaClientException { + checkNotNull(symbols); + if (symbols.isEmpty()) { + throw new AlpacaClientException("Collection symbols must not be empty."); + } + + HttpUrl.Builder urlBuilder = alpacaClient.urlBuilder() + .addPathSegment(endpointPathSegment) + .addPathSegment(LOCALE) + .addPathSegment("latest") + .addPathSegment("trades"); + + urlBuilder.addQueryParameter("symbols", String.join(",", symbols)); + + Request request = alpacaClient.requestBuilder(urlBuilder.build()) + .get() + .build(); + return alpacaClient.requestObject(request, LatestCryptoTradesResponse.class); + } + + /** + * Gets the latest {@link CryptoBar}s for the requested securities. * * @param symbols a {@link Collection} of symbols to query for * - * @return the {@link LatestCryptoTradeResponse} + * @return the {@link LatestCryptoBarsResponse} * * @throws AlpacaClientException thrown for {@link AlpacaClientException}s */ - public LatestCryptoTradeResponse getLatestTrade(Collection symbols) throws AlpacaClientException { + public LatestCryptoBarsResponse getLatestBars(Collection symbols) throws AlpacaClientException { checkNotNull(symbols); + if (symbols.isEmpty()) { + throw new AlpacaClientException("Collection symbols must not be empty."); + } HttpUrl.Builder urlBuilder = alpacaClient.urlBuilder() .addPathSegment(endpointPathSegment) .addPathSegment(LOCALE) - .addPathSegment("trades") - .addPathSegment("latest"); + .addPathSegment("latest") + .addPathSegment("bars"); urlBuilder.addQueryParameter("symbols", String.join(",", symbols)); Request request = alpacaClient.requestBuilder(urlBuilder.build()) .get() .build(); - return alpacaClient.requestObject(request, LatestCryptoTradeResponse.class); + return alpacaClient.requestObject(request, LatestCryptoBarsResponse.class); } /** - * Gets {@link CryptoQuote} historical data for the requested crypto symbol. + * Gets {@link CryptoQuote} historical data for the requested crypto symbols. * * @param symbols a {@link Collection} of symbols to query for * @param start filter data equal to or after this {@link ZonedDateTime}. Fractions of a second are not @@ -133,6 +172,9 @@ public LatestCryptoTradeResponse getLatestTrade(Collection symbols) thro public CryptoQuotesResponse getQuotes(Collection symbols, ZonedDateTime start, ZonedDateTime end, Integer limit, String pageToken) throws AlpacaClientException { checkNotNull(symbols); + if (symbols.isEmpty()) { + throw new AlpacaClientException("Collection symbols must not be empty."); + } HttpUrl.Builder urlBuilder = alpacaClient.urlBuilder() .addPathSegment(endpointPathSegment) @@ -164,29 +206,89 @@ public CryptoQuotesResponse getQuotes(Collection symbols, ZonedDateTime } /** - * Gets the latest {@link CryptoQuote} for the requested security. + * Gets the latest {@link CryptoQuote}s for the requested securities. + * + * @param symbols a {@link Collection} of symbols to query for + * + * @return the {@link LatestCryptoQuotesResponse} + * + * @throws AlpacaClientException thrown for {@link AlpacaClientException}s + */ + public LatestCryptoQuotesResponse getLatestQuotes(Collection symbols) throws AlpacaClientException { + checkNotNull(symbols); + if (symbols.isEmpty()) { + throw new AlpacaClientException("Collection symbols must not be empty."); + } + + HttpUrl.Builder urlBuilder = alpacaClient.urlBuilder() + .addPathSegment(endpointPathSegment) + .addPathSegment(LOCALE) + .addPathSegment("latest") + .addPathSegment("quotes"); + + urlBuilder.addQueryParameter("symbols", String.join(",", symbols)); + + Request request = alpacaClient.requestBuilder(urlBuilder.build()) + .get() + .build(); + return alpacaClient.requestObject(request, LatestCryptoQuotesResponse.class); + } + + /** + * Gets the latest {@link CryptoSnapshot} for the requested securities. + * + * @param symbols a {@link Collection} of symbols to query for + * + * @return the {@link CryptoSnapshotsResponse} + * + * @throws AlpacaClientException thrown for {@link AlpacaClientException}s + */ + public CryptoSnapshotsResponse getSnapshots(Collection symbols) throws AlpacaClientException { + checkNotNull(symbols); + if (symbols.isEmpty()) { + throw new AlpacaClientException("Collection symbols must not be empty."); + } + + HttpUrl.Builder urlBuilder = alpacaClient.urlBuilder() + .addPathSegment(endpointPathSegment) + .addPathSegment(LOCALE) + .addPathSegment("snapshots"); + + urlBuilder.addQueryParameter("symbols", String.join(",", symbols)); + + Request request = alpacaClient.requestBuilder(urlBuilder.build()) + .get() + .build(); + return alpacaClient.requestObject(request, CryptoSnapshotsResponse.class); + } + + /** + * Gets the latest {@link CryptoOrderbook}s for the requested securities. * * @param symbols a {@link Collection} of symbols to query for * - * @return the {@link LatestCryptoQuoteResponse} + * @return the {@link LatestCryptoOrderbooksResponse} * * @throws AlpacaClientException thrown for {@link AlpacaClientException}s */ - public LatestCryptoQuoteResponse getLatestQuote(Collection symbols) throws AlpacaClientException { + public LatestCryptoOrderbooksResponse getLatestOrderbooks(Collection symbols) throws AlpacaClientException { checkNotNull(symbols); + if (symbols.isEmpty()) { + throw new AlpacaClientException("Collection symbols must not be empty."); + } HttpUrl.Builder urlBuilder = alpacaClient.urlBuilder() .addPathSegment(endpointPathSegment) .addPathSegment(LOCALE) - .addPathSegment("quotes") - .addPathSegment("latest"); + .addPathSegment("latest") + .addPathSegment("orderbooks"); urlBuilder.addQueryParameter("symbols", String.join(",", symbols)); Request request = alpacaClient.requestBuilder(urlBuilder.build()) .get() .build(); - return alpacaClient.requestObject(request, LatestCryptoQuoteResponse.class); + return alpacaClient.requestObject(request, LatestCryptoOrderbooksResponse.class); } /** @@ -195,6 +297,8 @@ public LatestCryptoQuoteResponse getLatestQuote(Collection symbols) thro * @param symbols a {@link Collection} of symbols to query for * @param start filter data equal to or after this {@link ZonedDateTime}. Fractions of a second are * not accepted. null for now. + * @param end filter data equal to or before this {@link ZonedDateTime}. Fractions of a second are + * not accepted. null for now. * @param limit number of data points to return. Must be in range 1-10000, defaults to 1000 if * null is given * @param pageToken pagination token to continue from @@ -209,9 +313,12 @@ public LatestCryptoQuoteResponse getLatestQuote(Collection symbols) thro * * @throws AlpacaClientException thrown for {@link AlpacaClientException}s */ - public CryptoBarsResponse getBars(Collection symbols, ZonedDateTime start, Integer limit, + public CryptoBarsResponse getBars(Collection symbols, ZonedDateTime start, ZonedDateTime end, Integer limit, String pageToken, int barTimePeriodDuration, BarTimePeriod barTimePeriod) throws AlpacaClientException { checkNotNull(symbols); + if (symbols.isEmpty()) { + throw new AlpacaClientException("Collection symbols must not be empty."); + } checkNotNull(barTimePeriod); HttpUrl.Builder urlBuilder = alpacaClient.urlBuilder() @@ -225,6 +332,10 @@ public CryptoBarsResponse getBars(Collection symbols, ZonedDateTime star urlBuilder.addQueryParameter("start", FormatUtil.toRFC3339Format(start)); } + if (end != null) { + urlBuilder.addQueryParameter("end", FormatUtil.toRFC3339Format(end)); + } + if (limit != null) { urlBuilder.addQueryParameter("limit", limit.toString()); } From eb7f551f547bbaf6015797b2972aa2bed1fdb50c Mon Sep 17 00:00:00 2001 From: Jesse Thouin Date: Thu, 13 Jul 2023 16:54:02 -0400 Subject: [PATCH 192/271] Removed getQuotes as it doesn't have a matching API call with Alpaca. --- .../quote/crypto_quotes_response.json | 14 ----- .../crypto/CryptoMarketDataEndpoint.java | 53 ------------------- 2 files changed, 67 deletions(-) delete mode 100644 schema_json/endpoint/market_data/crypto/historical/quote/crypto_quotes_response.json diff --git a/schema_json/endpoint/market_data/crypto/historical/quote/crypto_quotes_response.json b/schema_json/endpoint/market_data/crypto/historical/quote/crypto_quotes_response.json deleted file mode 100644 index a5010f8c..00000000 --- a/schema_json/endpoint/market_data/crypto/historical/quote/crypto_quotes_response.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "type": "object", - "title": "See Historical Crypto Data.", - "properties": { - "quotes": { - "existingJavaType": "java.util.Map>", - "title": "The {@link java.util.ArrayList} of {@link net.jacobpeterson.alpaca.model.endpoint.marketdata.crypto.historical.quote.CryptoQuote}s." - }, - "next_page_token": { - "existingJavaType": "java.lang.String", - "title": "Token that can be used to query the next page." - } - } -} diff --git a/src/main/java/net/jacobpeterson/alpaca/rest/endpoint/marketdata/crypto/CryptoMarketDataEndpoint.java b/src/main/java/net/jacobpeterson/alpaca/rest/endpoint/marketdata/crypto/CryptoMarketDataEndpoint.java index bebe7a87..c1a8947f 100644 --- a/src/main/java/net/jacobpeterson/alpaca/rest/endpoint/marketdata/crypto/CryptoMarketDataEndpoint.java +++ b/src/main/java/net/jacobpeterson/alpaca/rest/endpoint/marketdata/crypto/CryptoMarketDataEndpoint.java @@ -7,7 +7,6 @@ import net.jacobpeterson.alpaca.model.endpoint.marketdata.crypto.historical.orderbook.CryptoOrderbook; import net.jacobpeterson.alpaca.model.endpoint.marketdata.crypto.historical.orderbook.LatestCryptoOrderbooksResponse; import net.jacobpeterson.alpaca.model.endpoint.marketdata.crypto.historical.quote.CryptoQuote; -import net.jacobpeterson.alpaca.model.endpoint.marketdata.crypto.historical.quote.CryptoQuotesResponse; import net.jacobpeterson.alpaca.model.endpoint.marketdata.crypto.historical.quote.LatestCryptoQuotesResponse; import net.jacobpeterson.alpaca.model.endpoint.marketdata.crypto.historical.snapshot.CryptoSnapshot; import net.jacobpeterson.alpaca.model.endpoint.marketdata.crypto.historical.snapshot.CryptoSnapshotsResponse; @@ -153,58 +152,6 @@ public LatestCryptoBarsResponse getLatestBars(Collection symbols) throws return alpacaClient.requestObject(request, LatestCryptoBarsResponse.class); } - /** - * Gets {@link CryptoQuote} historical data for the requested crypto symbols. - * - * @param symbols a {@link Collection} of symbols to query for - * @param start filter data equal to or after this {@link ZonedDateTime}. Fractions of a second are not - * accepted. null for the current day in Central Time. - * @param end filter data equal to or before this {@link ZonedDateTime}. Fractions of a second are not - * accepted. null for now. - * @param limit number of data points to return. Must be in range 1-10000, defaults to 1000 if null - * is given - * @param pageToken pagination token to continue from - * - * @return the {@link CryptoQuotesResponse} - * - * @throws AlpacaClientException thrown for {@link AlpacaClientException}s - */ - public CryptoQuotesResponse getQuotes(Collection symbols, ZonedDateTime start, - ZonedDateTime end, Integer limit, String pageToken) throws AlpacaClientException { - checkNotNull(symbols); - if (symbols.isEmpty()) { - throw new AlpacaClientException("Collection symbols must not be empty."); - } - - HttpUrl.Builder urlBuilder = alpacaClient.urlBuilder() - .addPathSegment(endpointPathSegment) - .addPathSegment(LOCALE) - .addPathSegment("quotes"); - - urlBuilder.addQueryParameter("symbols", String.join(",", symbols)); - - if (start != null) { - urlBuilder.addQueryParameter("start", FormatUtil.toRFC3339Format(start)); - } - - if (end != null) { - urlBuilder.addQueryParameter("end", FormatUtil.toRFC3339Format(end)); - } - - if (limit != null) { - urlBuilder.addQueryParameter("limit", limit.toString()); - } - - if (pageToken != null) { - urlBuilder.addQueryParameter("page_token", pageToken); - } - - Request request = alpacaClient.requestBuilder(urlBuilder.build()) - .get() - .build(); - return alpacaClient.requestObject(request, CryptoQuotesResponse.class); - } - /** * Gets the latest {@link CryptoQuote}s for the requested securities. * From 6fdb6fcab02b68a0c54eeeabaa95c5d651b75ce7 Mon Sep 17 00:00:00 2001 From: Jesse Thouin Date: Sat, 15 Jul 2023 12:50:29 -0400 Subject: [PATCH 193/271] Alpaca removed OrderTimeInForce.DAY as a valid time in force for crypto orders. --- .../alpaca/rest/endpoint/orders/OrdersEndpoint.java | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/main/java/net/jacobpeterson/alpaca/rest/endpoint/orders/OrdersEndpoint.java b/src/main/java/net/jacobpeterson/alpaca/rest/endpoint/orders/OrdersEndpoint.java index 3212bf75..d2d355dd 100644 --- a/src/main/java/net/jacobpeterson/alpaca/rest/endpoint/orders/OrdersEndpoint.java +++ b/src/main/java/net/jacobpeterson/alpaca/rest/endpoint/orders/OrdersEndpoint.java @@ -242,7 +242,7 @@ public Order requestMarketOrder(String symbol, Integer quantity, OrderSide side, * most likely method of filling an order. Market orders fill nearly instantaneously. This method calls {@link * #requestOrder(String, Double, Double, OrderSide, OrderType, OrderTimeInForce, Double, Double, Double, Double, * Boolean, String, OrderClass, Double, Double, Double)} with {@link OrderType#MARKET} and {@link - * OrderTimeInForce#DAY} with a fractional quantity so check the Javadoc for that method for parameter details. + * OrderTimeInForce#GOOD_UNTIL_CANCELLED} with a fractional quantity so check the Javadoc for that method for parameter details. * * @return the requested {@link Order} * @@ -250,7 +250,7 @@ public Order requestMarketOrder(String symbol, Integer quantity, OrderSide side, */ public Order requestFractionalMarketOrder(String symbol, Double quantity, OrderSide side) throws AlpacaClientException { - return requestOrder(symbol, quantity, null, side, OrderType.MARKET, OrderTimeInForce.DAY, null, null, null, + return requestOrder(symbol, quantity, null, side, OrderType.MARKET, OrderTimeInForce.GOOD_UNTIL_CANCELLED, null, null, null, null, null, null, null, null, null, null); } @@ -259,7 +259,7 @@ public Order requestFractionalMarketOrder(String symbol, Double quantity, OrderS * most likely method of filling an order. Market orders fill nearly instantaneously. This method calls {@link * #requestOrder(String, Double, Double, OrderSide, OrderType, OrderTimeInForce, Double, Double, Double, Double, * Boolean, String, OrderClass, Double, Double, Double)} with {@link OrderType#MARKET} and {@link - * OrderTimeInForce#DAY} with a notional dollar amount so check the Javadoc for that method for the parameter + * OrderTimeInForce#GOOD_UNTIL_CANCELLED} with a notional dollar amount so check the Javadoc for that method for the parameter * details. * * @return the requested {@link Order} @@ -268,7 +268,7 @@ public Order requestFractionalMarketOrder(String symbol, Double quantity, OrderS */ public Order requestNotionalMarketOrder(String symbol, Double notional, OrderSide side) throws AlpacaClientException { - return requestOrder(symbol, null, notional, side, OrderType.MARKET, OrderTimeInForce.DAY, null, null, null, + return requestOrder(symbol, null, notional, side, OrderType.MARKET, OrderTimeInForce.GOOD_UNTIL_CANCELLED, null, null, null, null, null, null, null, null, null, null); } From 9ca1bcd9dec7f7fab6dd58da1ab4bc8d3722dd73 Mon Sep 17 00:00:00 2001 From: Daniel Mejer Date: Mon, 7 Aug 2023 18:13:31 +0200 Subject: [PATCH 194/271] Add builder to AlpacaAPI --- README.md | 6 +++ .../net/jacobpeterson/alpaca/AlpacaAPI.java | 53 +++++++++++++++++++ 2 files changed, 59 insertions(+) diff --git a/README.md b/README.md index a29ffc56..aff83e53 100644 --- a/README.md +++ b/README.md @@ -73,6 +73,12 @@ AlpacaAPI alpacaAPI = new AlpacaAPI(keyID, secretKey, EndpointAPIType.LIVE, Data // This constructor is for OAuth tokens String oAuthToken = ""; AlpacaAPI alpacaAPI = new AlpacaAPI(oAuthToken); + +// The following approach uses builder pattern. Attributes not provided explicitly are retrieved from 'alpaca.properties' file +AlpacaAPI alpacaAPI = AlpacaAPI.builder() + .withEndpointAPIType(EndpointAPIType.PAPER) + .build(); + ``` Note that this library uses [OkHttp](https://square.github.io/okhttp/) as its HTTP client library which creates background threads to service requests. These threads persist even if the main thread exists so if you want to destroy these threads when you're done using [`AlpacaAPI`](src/main/java/net/jacobpeterson/alpaca/AlpacaAPI.java) so your program can exit without calling `System.exit()`, use the following snippet: diff --git a/src/main/java/net/jacobpeterson/alpaca/AlpacaAPI.java b/src/main/java/net/jacobpeterson/alpaca/AlpacaAPI.java index 63980318..60b47293 100644 --- a/src/main/java/net/jacobpeterson/alpaca/AlpacaAPI.java +++ b/src/main/java/net/jacobpeterson/alpaca/AlpacaAPI.java @@ -78,6 +78,17 @@ public AlpacaAPI() { AlpacaProperties.DATA_API_TYPE); } + /** + * Instantiates a new {@link AlpacaAPI} using properties specified in Builder, otherwise from + * alpaca.properties file (or their associated defaults). + */ + private AlpacaAPI(Builder builder) { + this(builder.keyID, + builder.secretKey, + builder.endpointAPIType, + builder.dataAPIType); + } + /** * Instantiates a new {@link AlpacaAPI}. * @@ -308,4 +319,46 @@ public AlpacaClient getCryptoDataClient() { public AlpacaClient getStockDataClient() { return stockDataClient; } + + public static Builder builder() { + return new Builder(); + } + + public static final class Builder { + private String keyID; + private String secretKey; + private EndpointAPIType endpointAPIType; + private DataAPIType dataAPIType; + + private Builder() { + this.keyID = AlpacaProperties.KEY_ID; + this.secretKey = AlpacaProperties.SECRET_KEY; + this.endpointAPIType = AlpacaProperties.ENDPOINT_API_TYPE; + this.dataAPIType = AlpacaProperties.DATA_API_TYPE; + } + + public Builder withKeyID(String val) { + keyID = val; + return this; + } + + public Builder withSecretKey(String val) { + secretKey = val; + return this; + } + + public Builder withEndpointAPIType(EndpointAPIType val) { + endpointAPIType = val; + return this; + } + + public Builder withDataAPIType(DataAPIType val) { + dataAPIType = val; + return this; + } + + public AlpacaAPI build() { + return new AlpacaAPI(this); + } + } } From 854a5ddf55af940d406b60d0828986984e52ca56 Mon Sep 17 00:00:00 2001 From: Jacob Peterson <14127217+Petersoj@users.noreply.github.com> Date: Sun, 20 Aug 2023 16:19:10 -0600 Subject: [PATCH 195/271] Minor documentation updates --- README.md | 2 +- build.gradle | 1 + .../net/jacobpeterson/alpaca/AlpacaAPI.java | 23 +++++++++++++------ 3 files changed, 18 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index aff83e53..a6c51d49 100644 --- a/README.md +++ b/README.md @@ -74,7 +74,7 @@ AlpacaAPI alpacaAPI = new AlpacaAPI(keyID, secretKey, EndpointAPIType.LIVE, Data String oAuthToken = ""; AlpacaAPI alpacaAPI = new AlpacaAPI(oAuthToken); -// The following approach uses builder pattern. Attributes not provided explicitly are retrieved from 'alpaca.properties' file +// This approach uses a builder pattern. Attributes not provided explicitly are retrieved from 'alpaca.properties' file AlpacaAPI alpacaAPI = AlpacaAPI.builder() .withEndpointAPIType(EndpointAPIType.PAPER) .build(); diff --git a/build.gradle b/build.gradle index 588393d3..a40691fa 100644 --- a/build.gradle +++ b/build.gradle @@ -237,6 +237,7 @@ jsonSchema2Pojo { annotationStyle = 'gson' sourceType = 'jsonschema' customDateTimePattern = "yyyy-MM-ddTHH:mm:ssZ" + includeGeneratedAnnotation = false includeConstructors = true serializable = true includeGetters = true diff --git a/src/main/java/net/jacobpeterson/alpaca/AlpacaAPI.java b/src/main/java/net/jacobpeterson/alpaca/AlpacaAPI.java index 60b47293..c043b78f 100644 --- a/src/main/java/net/jacobpeterson/alpaca/AlpacaAPI.java +++ b/src/main/java/net/jacobpeterson/alpaca/AlpacaAPI.java @@ -5,19 +5,19 @@ import net.jacobpeterson.alpaca.model.properties.EndpointAPIType; import net.jacobpeterson.alpaca.properties.AlpacaProperties; import net.jacobpeterson.alpaca.rest.AlpacaClient; +import net.jacobpeterson.alpaca.rest.endpoint.AlpacaEndpoint; +import net.jacobpeterson.alpaca.rest.endpoint.account.AccountEndpoint; import net.jacobpeterson.alpaca.rest.endpoint.accountactivities.AccountActivitiesEndpoint; import net.jacobpeterson.alpaca.rest.endpoint.accountconfiguration.AccountConfigurationEndpoint; -import net.jacobpeterson.alpaca.rest.endpoint.account.AccountEndpoint; -import net.jacobpeterson.alpaca.rest.endpoint.AlpacaEndpoint; import net.jacobpeterson.alpaca.rest.endpoint.assets.AssetsEndpoint; import net.jacobpeterson.alpaca.rest.endpoint.calendar.CalendarEndpoint; import net.jacobpeterson.alpaca.rest.endpoint.clock.ClockEndpoint; +import net.jacobpeterson.alpaca.rest.endpoint.marketdata.crypto.CryptoMarketDataEndpoint; +import net.jacobpeterson.alpaca.rest.endpoint.marketdata.stock.StockMarketDataEndpoint; import net.jacobpeterson.alpaca.rest.endpoint.orders.OrdersEndpoint; import net.jacobpeterson.alpaca.rest.endpoint.portfoliohistory.PortfolioHistoryEndpoint; import net.jacobpeterson.alpaca.rest.endpoint.positions.PositionsEndpoint; import net.jacobpeterson.alpaca.rest.endpoint.watchlist.WatchlistEndpoint; -import net.jacobpeterson.alpaca.rest.endpoint.marketdata.crypto.CryptoMarketDataEndpoint; -import net.jacobpeterson.alpaca.rest.endpoint.marketdata.stock.StockMarketDataEndpoint; import net.jacobpeterson.alpaca.websocket.AlpacaWebsocket; import net.jacobpeterson.alpaca.websocket.marketdata.MarketDataWebsocketInterface; import net.jacobpeterson.alpaca.websocket.marketdata.crypto.CryptoMarketDataWebsocket; @@ -32,8 +32,8 @@ import static com.google.common.base.Preconditions.checkNotNull; /** - * The {@link AlpacaAPI} class contains several instances of various {@link AlpacaEndpoint}s and {@link - * AlpacaWebsocket}s to interface with Alpaca. You will generally only need one instance of this class in your + * The {@link AlpacaAPI} class contains several instances of various {@link AlpacaEndpoint}s and + * {@link AlpacaWebsocket}s to interface with Alpaca. You will generally only need one instance of this class in your * application. Note that many methods inside the various {@link AlpacaEndpoint}s allow null to be passed * in as a parameter if it is optional. * @@ -79,7 +79,7 @@ public AlpacaAPI() { } /** - * Instantiates a new {@link AlpacaAPI} using properties specified in Builder, otherwise from + * Instantiates a new {@link AlpacaAPI} using properties specified in the given {@link Builder}, otherwise from * alpaca.properties file (or their associated defaults). */ private AlpacaAPI(Builder builder) { @@ -320,11 +320,20 @@ public AlpacaClient getStockDataClient() { return stockDataClient; } + /** + * Creates a {@link Builder} for {@link AlpacaAPI}. + * + * @return a {@link Builder} + */ public static Builder builder() { return new Builder(); } + /** + * A builder for {@link AlpacaAPI} + */ public static final class Builder { + private String keyID; private String secretKey; private EndpointAPIType endpointAPIType; From 9d253ff4f5acf7d2ff65cbfe903720e19299fd1d Mon Sep 17 00:00:00 2001 From: Jacob Peterson <14127217+Petersoj@users.noreply.github.com> Date: Sun, 20 Aug 2023 16:33:26 -0600 Subject: [PATCH 196/271] Auto-format --- .../net/jacobpeterson/alpaca/AlpacaAPI.java | 12 +- .../alpaca/rest/AlpacaClient.java | 16 +- .../crypto/CryptoMarketDataEndpoint.java | 15 +- .../stock/StockMarketDataEndpoint.java | 16 +- .../rest/endpoint/orders/OrdersEndpoint.java | 154 +++++++++--------- .../marketdata/MarketDataWebsocket.java | 8 +- .../streaming/StreamingWebsocket.java | 4 +- src/main/resources/alpaca.default.properties | 1 - .../alpaca/test/live/AlpacaAPITest.java | 9 +- 9 files changed, 120 insertions(+), 115 deletions(-) diff --git a/src/main/java/net/jacobpeterson/alpaca/AlpacaAPI.java b/src/main/java/net/jacobpeterson/alpaca/AlpacaAPI.java index c71f496b..6ae47c47 100644 --- a/src/main/java/net/jacobpeterson/alpaca/AlpacaAPI.java +++ b/src/main/java/net/jacobpeterson/alpaca/AlpacaAPI.java @@ -5,19 +5,19 @@ import net.jacobpeterson.alpaca.model.properties.EndpointAPIType; import net.jacobpeterson.alpaca.properties.AlpacaProperties; import net.jacobpeterson.alpaca.rest.AlpacaClient; +import net.jacobpeterson.alpaca.rest.endpoint.AlpacaEndpoint; +import net.jacobpeterson.alpaca.rest.endpoint.account.AccountEndpoint; import net.jacobpeterson.alpaca.rest.endpoint.accountactivities.AccountActivitiesEndpoint; import net.jacobpeterson.alpaca.rest.endpoint.accountconfiguration.AccountConfigurationEndpoint; -import net.jacobpeterson.alpaca.rest.endpoint.account.AccountEndpoint; -import net.jacobpeterson.alpaca.rest.endpoint.AlpacaEndpoint; import net.jacobpeterson.alpaca.rest.endpoint.assets.AssetsEndpoint; import net.jacobpeterson.alpaca.rest.endpoint.calendar.CalendarEndpoint; import net.jacobpeterson.alpaca.rest.endpoint.clock.ClockEndpoint; +import net.jacobpeterson.alpaca.rest.endpoint.marketdata.crypto.CryptoMarketDataEndpoint; +import net.jacobpeterson.alpaca.rest.endpoint.marketdata.stock.StockMarketDataEndpoint; import net.jacobpeterson.alpaca.rest.endpoint.orders.OrdersEndpoint; import net.jacobpeterson.alpaca.rest.endpoint.portfoliohistory.PortfolioHistoryEndpoint; import net.jacobpeterson.alpaca.rest.endpoint.positions.PositionsEndpoint; import net.jacobpeterson.alpaca.rest.endpoint.watchlist.WatchlistEndpoint; -import net.jacobpeterson.alpaca.rest.endpoint.marketdata.crypto.CryptoMarketDataEndpoint; -import net.jacobpeterson.alpaca.rest.endpoint.marketdata.stock.StockMarketDataEndpoint; import net.jacobpeterson.alpaca.websocket.AlpacaWebsocket; import net.jacobpeterson.alpaca.websocket.marketdata.MarketDataWebsocketInterface; import net.jacobpeterson.alpaca.websocket.marketdata.crypto.CryptoMarketDataWebsocket; @@ -32,8 +32,8 @@ import static com.google.common.base.Preconditions.checkNotNull; /** - * The {@link AlpacaAPI} class contains several instances of various {@link AlpacaEndpoint}s and {@link - * AlpacaWebsocket}s to interface with Alpaca. You will generally only need one instance of this class in your + * The {@link AlpacaAPI} class contains several instances of various {@link AlpacaEndpoint}s and + * {@link AlpacaWebsocket}s to interface with Alpaca. You will generally only need one instance of this class in your * application. Note that many methods inside the various {@link AlpacaEndpoint}s allow null to be passed * in as a parameter if it is optional. * diff --git a/src/main/java/net/jacobpeterson/alpaca/rest/AlpacaClient.java b/src/main/java/net/jacobpeterson/alpaca/rest/AlpacaClient.java index 7fe5cc27..dc11df3f 100644 --- a/src/main/java/net/jacobpeterson/alpaca/rest/AlpacaClient.java +++ b/src/main/java/net/jacobpeterson/alpaca/rest/AlpacaClient.java @@ -127,8 +127,8 @@ public T requestObject(Request request, Type type) throws AlpacaClientExcept * * @param the type of object * @param request the {@link Request} - * @param isSuccessCode throws {@link AlpacaClientException} if passed in {@link Response#code()} to the {@link - * Predicate#test(Object)} returns false + * @param isSuccessCode throws {@link AlpacaClientException} if passed in {@link Response#code()} to the + * {@link Predicate#test(Object)} returns false * @param type the object {@link Type} * * @return the requested object @@ -151,8 +151,8 @@ public JsonElement requestJSON(Request request) throws AlpacaClientException { * Requests a {@link JsonElement} given a {@link Request}. * * @param request the {@link Request} - * @param isSuccessCode throws {@link AlpacaClientException} if passed in {@link Response#code()} to the {@link - * Predicate#test(Object)} returns false + * @param isSuccessCode throws {@link AlpacaClientException} if passed in {@link Response#code()} to the + * {@link Predicate#test(Object)} returns false * * @return the {@link JsonElement} * @@ -178,8 +178,8 @@ public JsonElement requestJSON(Request request, Predicate isSuccessCode * Sends a {@link Request} and ignores any {@link ResponseBody}. * * @param request the {@link Request} - * @param isSuccessCode throws {@link AlpacaClientException} if passed in {@link Response#code()} to the {@link - * Predicate#test(Object)} returns false + * @param isSuccessCode throws {@link AlpacaClientException} if passed in {@link Response#code()} to the + * {@link Predicate#test(Object)} returns false * * @throws AlpacaClientException thrown for {@link AlpacaClientException}s */ @@ -193,8 +193,8 @@ public void requestVoid(Request request, Predicate isSuccessCode) throw * Sends a {@link Request} and returns a {@link Response}. * * @param request the {@link Request} - * @param isSuccessCode throws {@link AlpacaClientException} if passed in {@link Response#code()} to the {@link - * Predicate#test(Object)} returns false + * @param isSuccessCode throws {@link AlpacaClientException} if passed in {@link Response#code()} to the + * {@link Predicate#test(Object)} returns false * * @return the {@link Response}. Be sure to call {@link Response#close()} after use. * diff --git a/src/main/java/net/jacobpeterson/alpaca/rest/endpoint/marketdata/crypto/CryptoMarketDataEndpoint.java b/src/main/java/net/jacobpeterson/alpaca/rest/endpoint/marketdata/crypto/CryptoMarketDataEndpoint.java index c1a8947f..f429a32c 100644 --- a/src/main/java/net/jacobpeterson/alpaca/rest/endpoint/marketdata/crypto/CryptoMarketDataEndpoint.java +++ b/src/main/java/net/jacobpeterson/alpaca/rest/endpoint/marketdata/crypto/CryptoMarketDataEndpoint.java @@ -31,6 +31,7 @@ * Crypto Market Data API. */ public class CryptoMarketDataEndpoint extends AlpacaEndpoint { + public static final String LOCALE = "us"; /** @@ -97,7 +98,7 @@ public CryptoTradesResponse getTrades(Collection symbols, ZonedDateTime /** * Gets the latest {@link CryptoTrade}s for the requested securities. * - * @param symbols a {@link Collection} of symbols to query for + * @param symbols a {@link Collection} of symbols to query for * * @return the {@link LatestCryptoTradesResponse} * @@ -126,7 +127,7 @@ public LatestCryptoTradesResponse getLatestTrades(Collection symbols) th /** * Gets the latest {@link CryptoBar}s for the requested securities. * - * @param symbols a {@link Collection} of symbols to query for + * @param symbols a {@link Collection} of symbols to query for * * @return the {@link LatestCryptoBarsResponse} * @@ -155,7 +156,7 @@ public LatestCryptoBarsResponse getLatestBars(Collection symbols) throws /** * Gets the latest {@link CryptoQuote}s for the requested securities. * - * @param symbols a {@link Collection} of symbols to query for + * @param symbols a {@link Collection} of symbols to query for * * @return the {@link LatestCryptoQuotesResponse} * @@ -184,7 +185,7 @@ public LatestCryptoQuotesResponse getLatestQuotes(Collection symbols) th /** * Gets the latest {@link CryptoSnapshot} for the requested securities. * - * @param symbols a {@link Collection} of symbols to query for + * @param symbols a {@link Collection} of symbols to query for * * @return the {@link CryptoSnapshotsResponse} * @@ -212,7 +213,7 @@ public CryptoSnapshotsResponse getSnapshots(Collection symbols) throws A /** * Gets the latest {@link CryptoOrderbook}s for the requested securities. * - * @param symbols a {@link Collection} of symbols to query for + * @param symbols a {@link Collection} of symbols to query for * * @return the {@link LatestCryptoOrderbooksResponse} * @@ -252,8 +253,8 @@ public LatestCryptoOrderbooksResponse getLatestOrderbooks(Collection sym * @param barTimePeriodDuration the duration for the given barTimePeriod parameter. e.g. for * 15Min bars, you would supply 15 for this parameter and * {@link BarTimePeriod#MINUTE} for the barTimePeriod parameter. - * @param barTimePeriod the {@link BarTimePeriod} e.g. for 15Min bars, you would supply {@link - * BarTimePeriod#MINUTE} for this parameter and 15 for the + * @param barTimePeriod the {@link BarTimePeriod} e.g. for 15Min bars, you would supply + * {@link BarTimePeriod#MINUTE} for this parameter and 15 for the * barTimePeriodDuration parameter. * * @return the {@link CryptoBarsResponse} diff --git a/src/main/java/net/jacobpeterson/alpaca/rest/endpoint/marketdata/stock/StockMarketDataEndpoint.java b/src/main/java/net/jacobpeterson/alpaca/rest/endpoint/marketdata/stock/StockMarketDataEndpoint.java index d47b167d..3111dde7 100644 --- a/src/main/java/net/jacobpeterson/alpaca/rest/endpoint/marketdata/stock/StockMarketDataEndpoint.java +++ b/src/main/java/net/jacobpeterson/alpaca/rest/endpoint/marketdata/stock/StockMarketDataEndpoint.java @@ -199,11 +199,11 @@ public LatestStockQuoteResponse getLatestQuote(String symbol) throws AlpacaClien * @param barTimePeriodDuration the duration for the given barTimePeriod parameter. e.g. for * 15Min bars, you would supply 15 for this parameter and * {@link BarTimePeriod#MINUTE} for the barTimePeriod parameter. - * @param barTimePeriod the {@link BarTimePeriod}. e.g. for 15Min bars, you would supply {@link - * BarTimePeriod#MINUTE} for this parameter and 15 for the + * @param barTimePeriod the {@link BarTimePeriod}. e.g. for 15Min bars, you would supply + * {@link BarTimePeriod#MINUTE} for this parameter and 15 for the * barTimePeriodDuration parameter. - * @param barAdjustment specifies the corporate action adjustment for the stocks. Default value is {@link - * BarAdjustment#RAW} + * @param barAdjustment specifies the corporate action adjustment for the stocks. Default value is + * {@link BarAdjustment#RAW} * @param barFeed defaults to {@link BarFeed#IEX} for Free users and {@link BarFeed#SIP} for users * with an Unlimited subscription * @@ -265,11 +265,11 @@ public StockBarsResponse getBars(String symbol, ZonedDateTime start, ZonedDateTi * @param barTimePeriodDuration the duration for the given barTimePeriod parameter. e.g. for * 15Min bars, you would supply 15 for this parameter and * {@link BarTimePeriod#MINUTE} for the barTimePeriod parameter. - * @param barTimePeriod the {@link BarTimePeriod}. e.g. for 15Min bars, you would supply {@link - * BarTimePeriod#MINUTE} for this parameter and 15 for the + * @param barTimePeriod the {@link BarTimePeriod}. e.g. for 15Min bars, you would supply + * {@link BarTimePeriod#MINUTE} for this parameter and 15 for the * barTimePeriodDuration parameter. - * @param barAdjustment specifies the corporate action adjustment for the stocks. Default value is {@link - * BarAdjustment#RAW} + * @param barAdjustment specifies the corporate action adjustment for the stocks. Default value is + * {@link BarAdjustment#RAW} * @param barFeed defaults to {@link BarFeed#IEX} for Free users and {@link BarFeed#SIP} for users * with an Unlimited subscription * diff --git a/src/main/java/net/jacobpeterson/alpaca/rest/endpoint/orders/OrdersEndpoint.java b/src/main/java/net/jacobpeterson/alpaca/rest/endpoint/orders/OrdersEndpoint.java index d2d355dd..0ad4a66e 100644 --- a/src/main/java/net/jacobpeterson/alpaca/rest/endpoint/orders/OrdersEndpoint.java +++ b/src/main/java/net/jacobpeterson/alpaca/rest/endpoint/orders/OrdersEndpoint.java @@ -56,8 +56,8 @@ public OrdersEndpoint(AlpacaClient alpacaClient) { * @param limit the maximum number of orders in response. Defaults to 50 and max is 500. * @param after the response will include only ones submitted after this timestamp (exclusive) * @param until the response will include only ones submitted until this timestamp (exclusive) - * @param direction the chronological order of response based on the submission time. Defaults to {@link - * SortDirection#DESCENDING}. + * @param direction the chronological order of response based on the submission time. Defaults to + * {@link SortDirection#DESCENDING}. * @param nested if true, the result will roll up multi-leg orders under the legs field of primary order. * @param symbols a {@link Collection} of symbols to filter by (e.g. "AAPL,TSLA,MSFT"). A currency pair is * required for crypto orders (e.g. "BTCUSD,BCHUSD,LTCUSD,ETCUSD"). null for no @@ -120,19 +120,19 @@ public List get(CurrentOrderStatus status, Integer limit, ZonedDateTime a * @param timeInForce the {@link OrderTimeInForce} * @param limitPrice required if type is {@link OrderType#LIMIT} or {@link OrderType#STOP_LIMIT} * @param stopPrice required if type is {@link OrderType#STOP} or {@link OrderType#STOP_LIMIT} - * @param trailPrice this or trail_percent is required if type is {@link - * OrderType#TRAILING_STOP} - * @param trailPercent this or trail_price is required if type is {@link - * OrderType#TRAILING_STOP} + * @param trailPrice this or trail_percent is required if type is + * {@link OrderType#TRAILING_STOP} + * @param trailPercent this or trail_price is required if type is + * {@link OrderType#TRAILING_STOP} * @param extendedHours (default) false. If true, order will be eligible to execute in premarket/afterhours. * Only works with type {@link OrderType#LIMIT} and {@link OrderTimeInForce#DAY}. * @param clientOrderId a unique identifier for the order. Automatically generated if null. * @param orderClass the {@link OrderClass}. For details of non-simple order classes, please see "Bracket * Order Overview" on the Alpaca Docs. - * @param takeProfitLimitPrice additional parameter for take-profit leg of advanced orders. Required for {@link - * OrderClass#BRACKET}. - * @param stopLossStopPrice additional parameters for stop-loss leg of advanced orders. Required for {@link - * OrderClass#BRACKET}. + * @param takeProfitLimitPrice additional parameter for take-profit leg of advanced orders. Required for + * {@link OrderClass#BRACKET}. + * @param stopLossStopPrice additional parameters for stop-loss leg of advanced orders. Required for + * {@link OrderClass#BRACKET}. * @param stopLossLimitPrice additional parameters for stop-loss leg of advanced orders. The stop-loss order * becomes a stop-limit order if specified. * @@ -222,10 +222,10 @@ public Order requestOrder(String symbol, Double quantity, Double notional, Order /** * A market order is a request to buy or sell a security at the currently available market price. It provides the - * most likely method of filling an order. Market orders fill nearly instantaneously. This method calls {@link - * #requestOrder(String, Double, Double, OrderSide, OrderType, OrderTimeInForce, Double, Double, Double, Double, - * Boolean, String, OrderClass, Double, Double, Double)} with {@link OrderType#MARKET} so check the Javadoc for that - * method for parameter details. + * most likely method of filling an order. Market orders fill nearly instantaneously. This method calls + * {@link #requestOrder(String, Double, Double, OrderSide, OrderType, OrderTimeInForce, Double, Double, Double, + * Double, Boolean, String, OrderClass, Double, Double, Double)} with {@link OrderType#MARKET} so check the Javadoc + * for that method for parameter details. * * @return the requested {@link Order} * @@ -239,10 +239,11 @@ public Order requestMarketOrder(String symbol, Integer quantity, OrderSide side, /** * A market order is a request to buy or sell a security at the currently available market price. It provides the - * most likely method of filling an order. Market orders fill nearly instantaneously. This method calls {@link - * #requestOrder(String, Double, Double, OrderSide, OrderType, OrderTimeInForce, Double, Double, Double, Double, - * Boolean, String, OrderClass, Double, Double, Double)} with {@link OrderType#MARKET} and {@link - * OrderTimeInForce#GOOD_UNTIL_CANCELLED} with a fractional quantity so check the Javadoc for that method for parameter details. + * most likely method of filling an order. Market orders fill nearly instantaneously. This method calls + * {@link #requestOrder(String, Double, Double, OrderSide, OrderType, OrderTimeInForce, Double, Double, Double, + * Double, Boolean, String, OrderClass, Double, Double, Double)} with {@link OrderType#MARKET} and + * {@link OrderTimeInForce#GOOD_UNTIL_CANCELLED} with a fractional quantity so check the Javadoc for that method for + * parameter details. * * @return the requested {@link Order} * @@ -250,17 +251,18 @@ public Order requestMarketOrder(String symbol, Integer quantity, OrderSide side, */ public Order requestFractionalMarketOrder(String symbol, Double quantity, OrderSide side) throws AlpacaClientException { - return requestOrder(symbol, quantity, null, side, OrderType.MARKET, OrderTimeInForce.GOOD_UNTIL_CANCELLED, null, null, null, + return requestOrder(symbol, quantity, null, side, OrderType.MARKET, OrderTimeInForce.GOOD_UNTIL_CANCELLED, null, + null, null, null, null, null, null, null, null, null); } /** * A market order is a request to buy or sell a security at the currently available market price. It provides the - * most likely method of filling an order. Market orders fill nearly instantaneously. This method calls {@link - * #requestOrder(String, Double, Double, OrderSide, OrderType, OrderTimeInForce, Double, Double, Double, Double, - * Boolean, String, OrderClass, Double, Double, Double)} with {@link OrderType#MARKET} and {@link - * OrderTimeInForce#GOOD_UNTIL_CANCELLED} with a notional dollar amount so check the Javadoc for that method for the parameter - * details. + * most likely method of filling an order. Market orders fill nearly instantaneously. This method calls + * {@link #requestOrder(String, Double, Double, OrderSide, OrderType, OrderTimeInForce, Double, Double, Double, + * Double, Boolean, String, OrderClass, Double, Double, Double)} with {@link OrderType#MARKET} and + * {@link OrderTimeInForce#GOOD_UNTIL_CANCELLED} with a notional dollar amount so check the Javadoc for that method + * for the parameter details. * * @return the requested {@link Order} * @@ -268,7 +270,8 @@ public Order requestFractionalMarketOrder(String symbol, Double quantity, OrderS */ public Order requestNotionalMarketOrder(String symbol, Double notional, OrderSide side) throws AlpacaClientException { - return requestOrder(symbol, null, notional, side, OrderType.MARKET, OrderTimeInForce.GOOD_UNTIL_CANCELLED, null, null, null, + return requestOrder(symbol, null, notional, side, OrderType.MARKET, OrderTimeInForce.GOOD_UNTIL_CANCELLED, null, + null, null, null, null, null, null, null, null, null); } @@ -276,10 +279,10 @@ public Order requestNotionalMarketOrder(String symbol, Double notional, OrderSid * A limit order is an order to buy or sell at a specified price or better. A buy limit order (a limit order to buy) * is executed at the specified limit price or lower (i.e., better). Conversely, a sell limit order (a limit order * to sell) is executed at the specified limit price or higher (better). Unlike a market order, you have to specify - * the limit price parameter when submitting your order. This method calls {@link #requestOrder(String, Double, - * Double, OrderSide, OrderType, OrderTimeInForce, Double, Double, Double, Double, Boolean, String, OrderClass, - * Double, Double, Double)} with {@link OrderType#LIMIT} so check the Javadoc for that method for the parameter - * details. + * the limit price parameter when submitting your order. This method calls + * {@link #requestOrder(String, Double, Double, OrderSide, OrderType, OrderTimeInForce, Double, Double, Double, + * Double, Boolean, String, OrderClass, Double, Double, Double)} with {@link OrderType#LIMIT} so check the Javadoc + * for that method for the parameter details. * * @return the requested {@link Order} * @@ -296,10 +299,10 @@ public Order requestLimitOrder(String symbol, Double quantity, OrderSide side, O * ensuring a higher probability of achieving a predetermined entry or exit price. Once the market price crosses the * specified stop price, the stop order becomes a market order. Alpaca converts buy stop orders into stop limit * orders with a limit price that is 4% higher than a stop price < $50 (or 2.5% higher than a stop price >= - * $50). Sell stop orders are not converted into stop limit orders. This method calls {@link #requestOrder(String, - * Double, Double, OrderSide, OrderType, OrderTimeInForce, Double, Double, Double, Double, Boolean, String, - * OrderClass, Double, Double, Double)} with {@link OrderType#STOP} so check the Javadoc for that method for - * parameter details. + * $50). Sell stop orders are not converted into stop limit orders. This method calls + * {@link #requestOrder(String, Double, Double, OrderSide, OrderType, OrderTimeInForce, Double, Double, Double, + * Double, Boolean, String, OrderClass, Double, Double, Double)} with {@link OrderType#STOP} so check the Javadoc + * for that method for parameter details. * * @return the requested {@link Order} * @@ -315,10 +318,10 @@ public Order requestStopOrder(String symbol, Integer quantity, OrderSide side, O * A stop-limit order is a conditional trade over a set time frame that combines the features of a stop order with * those of a limit order and is used to mitigate risk. The stop-limit order will be executed at a specified limit * price, or better, after a given stop price has been reached. Once the stop price is reached, the stop-limit order - * becomes a limit order to buy or sell at the limit price or better. This method calls {@link #requestOrder(String, - * Double, Double, OrderSide, OrderType, OrderTimeInForce, Double, Double, Double, Double, Boolean, String, - * OrderClass, Double, Double, Double)} with {@link OrderType#STOP_LIMIT} so check the Javadoc for that method for - * parameter details. + * becomes a limit order to buy or sell at the limit price or better. This method calls + * {@link #requestOrder(String, Double, Double, OrderSide, OrderType, OrderTimeInForce, Double, Double, Double, + * Double, Boolean, String, OrderClass, Double, Double, Double)} with {@link OrderType#STOP_LIMIT} so check the + * Javadoc for that method for parameter details. * * @return the requested {@link Order} * @@ -332,10 +335,10 @@ public Order requestStopLimitOrder(String symbol, Integer quantity, OrderSide si /** * A bracket order is a chain of three orders that can be used to manage your position entry and exit. It is a - * common use case of an OTOCO (One Triggers OCO {One Cancels Other}) order. This method calls {@link - * #requestOrder(String, Double, Double, OrderSide, OrderType, OrderTimeInForce, Double, Double, Double, Double, - * Boolean, String, OrderClass, Double, Double, Double)} with {@link OrderType#MARKET} and with parameters for a - * bracket order so check the Javadoc for that method for parameter details. + * common use case of an OTOCO (One Triggers OCO {One Cancels Other}) order. This method calls + * {@link #requestOrder(String, Double, Double, OrderSide, OrderType, OrderTimeInForce, Double, Double, Double, + * Double, Boolean, String, OrderClass, Double, Double, Double)} with {@link OrderType#MARKET} and with parameters + * for a bracket order so check the Javadoc for that method for parameter details. * * @return the requested {@link Order} * @@ -351,10 +354,10 @@ public Order requestMarketBracketOrder(String symbol, Integer quantity, OrderSid /** * A bracket order is a chain of three orders that can be used to manage your position entry and exit. It is a - * common use case of an OTOCO (One Triggers OCO {One Cancels Other}) order. This method calls {@link - * #requestOrder(String, Double, Double, OrderSide, OrderType, OrderTimeInForce, Double, Double, Double, Double, - * Boolean, String, OrderClass, Double, Double, Double)} with {@link OrderType#LIMIT} and with parameters for a - * bracket order so check the Javadoc for that method for parameter details. + * common use case of an OTOCO (One Triggers OCO {One Cancels Other}) order. This method calls + * {@link #requestOrder(String, Double, Double, OrderSide, OrderType, OrderTimeInForce, Double, Double, Double, + * Double, Boolean, String, OrderClass, Double, Double, Double)} with {@link OrderType#LIMIT} and with parameters + * for a bracket order so check the Javadoc for that method for parameter details. * * @return the requested {@link Order} * @@ -372,10 +375,10 @@ public Order requestLimitBracketOrder(String symbol, Integer quantity, OrderSide * OCO (One-Cancels-Other) is another type of advanced order type. This is a set of two orders with the same side * (buy/buy or sell/sell) and currently only exit order is supported. In other words, this is the second part of the * bracket orders where the entry order is already filled, and you can submit the take-profit and stop-loss in one - * order submission. This method calls {@link #requestOrder(String, Double, Double, OrderSide, OrderType, - * OrderTimeInForce, Double, Double, Double, Double, Boolean, String, OrderClass, Double, Double, Double)} with - * parameters for a {@link OrderClass#ONE_CANCELS_OTHER} so check the Javadoc for that method for the parameter - * details. + * order submission. This method calls + * {@link #requestOrder(String, Double, Double, OrderSide, OrderType, OrderTimeInForce, Double, Double, Double, + * Double, Boolean, String, OrderClass, Double, Double, Double)} with parameters for a + * {@link OrderClass#ONE_CANCELS_OTHER} so check the Javadoc for that method for the parameter details. * * @return the requested {@link Order} * @@ -391,10 +394,10 @@ public Order requestOCOOrder(String symbol, Integer quantity, OrderSide side, /** * OTO (One-Triggers-Other) is a variant of bracket order. It takes one of the take-profit or stop-loss order in - * addition to the entry order. This method calls {@link #requestOrder(String, Double, Double, OrderSide, OrderType, - * OrderTimeInForce, Double, Double, Double, Double, Boolean, String, OrderClass, Double, Double, Double)} with - * {@link OrderType#LIMIT} and with parameters for a {@link OrderClass#ONE_CANCELS_OTHER} so check the Javadoc for - * that method for parameter details. + * addition to the entry order. This method calls + * {@link #requestOrder(String, Double, Double, OrderSide, OrderType, OrderTimeInForce, Double, Double, Double, + * Double, Boolean, String, OrderClass, Double, Double, Double)} with {@link OrderType#LIMIT} and with parameters + * for a {@link OrderClass#ONE_CANCELS_OTHER} so check the Javadoc for that method for parameter details. * * @return the requested {@link Order} * @@ -410,10 +413,10 @@ public Order requestOTOMarketOrder(String symbol, Integer quantity, OrderSide si /** * OTO (One-Triggers-Other) is a variant of bracket order. It takes one of the take-profit or stop-loss order in - * addition to the entry order. This method calls {@link #requestOrder(String, Double, Double, OrderSide, OrderType, - * OrderTimeInForce, Double, Double, Double, Double, Boolean, String, OrderClass, Double, Double, Double)} with - * {@link OrderType#LIMIT} and with parameters for a {@link OrderClass#ONE_CANCELS_OTHER} so check the Javadoc for - * that method for parameter details. + * addition to the entry order. This method calls + * {@link #requestOrder(String, Double, Double, OrderSide, OrderType, OrderTimeInForce, Double, Double, Double, + * Double, Boolean, String, OrderClass, Double, Double, Double)} with {@link OrderType#LIMIT} and with parameters + * for a {@link OrderClass#ONE_CANCELS_OTHER} so check the Javadoc for that method for parameter details. * * @return the requested {@link Order} * @@ -429,10 +432,10 @@ public Order requestOTOLimitOrder(String symbol, Integer quantity, OrderSide sid /** * OTO (One-Triggers-Other) is a variant of bracket order. It takes one of the take-profit or stop-loss order in - * addition to the entry order. This method calls {@link #requestOrder(String, Double, Double, OrderSide, OrderType, - * OrderTimeInForce, Double, Double, Double, Double, Boolean, String, OrderClass, Double, Double, Double)} with - * {@link OrderType#STOP} and with parameters for a {@link OrderClass#ONE_CANCELS_OTHER} so check the Javadoc for - * that method for parameter details. + * addition to the entry order. This method calls + * {@link #requestOrder(String, Double, Double, OrderSide, OrderType, OrderTimeInForce, Double, Double, Double, + * Double, Boolean, String, OrderClass, Double, Double, Double)} with {@link OrderType#STOP} and with parameters for + * a {@link OrderClass#ONE_CANCELS_OTHER} so check the Javadoc for that method for parameter details. * * @return the requested {@link Order} * @@ -448,10 +451,11 @@ public Order requestOTOStopOrder(String symbol, Integer quantity, OrderSide side /** * OTO (One-Triggers-Other) is a variant of bracket order. It takes one of the take-profit or stop-loss order in - * addition to the entry order. This method calls {@link #requestOrder(String, Double, Double, OrderSide, OrderType, - * OrderTimeInForce, Double, Double, Double, Double, Boolean, String, OrderClass, Double, Double, Double)} with - * {@link OrderType#STOP_LIMIT} and with parameters for a {@link OrderClass#ONE_CANCELS_OTHER} so check the Javadoc - * for that method for parameter details. + * addition to the entry order. This method calls + * {@link #requestOrder(String, Double, Double, OrderSide, OrderType, OrderTimeInForce, Double, Double, Double, + * Double, Boolean, String, OrderClass, Double, Double, Double)} with {@link OrderType#STOP_LIMIT} and with + * parameters for a {@link OrderClass#ONE_CANCELS_OTHER} so check the Javadoc for that method for parameter + * details. * * @return the requested {@link Order} * @@ -468,10 +472,10 @@ public Order requestOTOStopLimitOrder(String symbol, Integer quantity, OrderSide /** * Trailing stop orders allow you to continuously and automatically keep updating the stop price threshold based on - * the stock price movement. This method calls {@link #requestOrder(String, Double, Double, OrderSide, OrderType, - * OrderTimeInForce, Double, Double, Double, Double, Boolean, String, OrderClass, Double, Double, Double)} with - * {@link OrderType#TRAILING_STOP} and with parameters for a {@link OrderType#TRAILING_STOP} so check the Javadoc - * for that method for parameter details. + * the stock price movement. This method calls + * {@link #requestOrder(String, Double, Double, OrderSide, OrderType, OrderTimeInForce, Double, Double, Double, + * Double, Boolean, String, OrderClass, Double, Double, Double)} with {@link OrderType#TRAILING_STOP} and with + * parameters for a {@link OrderType#TRAILING_STOP} so check the Javadoc for that method for parameter details. * * @return the requested {@link Order} * @@ -485,10 +489,10 @@ public Order requestTrailingStopPriceOrder(String symbol, Integer quantity, Orde /** * Trailing stop orders allow you to continuously and automatically keep updating the stop price threshold based on - * the stock price movement. This method calls {@link #requestOrder(String, Double, Double, OrderSide, OrderType, - * OrderTimeInForce, Double, Double, Double, Double, Boolean, String, OrderClass, Double, Double, Double)} with - * {@link OrderType#TRAILING_STOP} and with parameters for a {@link OrderType#TRAILING_STOP} so check the Javadoc - * for that method for parameter details. + * the stock price movement. This method calls + * {@link #requestOrder(String, Double, Double, OrderSide, OrderType, OrderTimeInForce, Double, Double, Double, + * Double, Boolean, String, OrderClass, Double, Double, Double)} with {@link OrderType#TRAILING_STOP} and with + * parameters for a {@link OrderType#TRAILING_STOP} so check the Javadoc for that method for parameter details. * * @return the requested {@link Order} * @@ -625,8 +629,8 @@ public List cancelAll() throws AlpacaClientException { } /** - * Attempts to cancel an open {@link Order}. If the {@link Order} is no longer cancelable (example: {@link - * Order#getStatus()} == {@link OrderStatus#FILLED}), the server will respond with status + * Attempts to cancel an open {@link Order}. If the {@link Order} is no longer cancelable (example: + * {@link Order#getStatus()} == {@link OrderStatus#FILLED}), the server will respond with status * 422, and reject the request. * * @param orderID the {@link Order#getId()} diff --git a/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/MarketDataWebsocket.java b/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/MarketDataWebsocket.java index 9241f066..9c924c11 100644 --- a/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/MarketDataWebsocket.java +++ b/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/MarketDataWebsocket.java @@ -35,8 +35,8 @@ import static net.jacobpeterson.alpaca.util.gson.GsonUtil.GSON; /** - * {@link MarketDataWebsocket} is an {@link AlpacaWebsocket} implementation and provides the {@link - * MarketDataWebsocketInterface} interface for + * {@link MarketDataWebsocket} is an {@link AlpacaWebsocket} implementation and provides the + * {@link MarketDataWebsocketInterface} interface for * Realtime Market Data for both crypto * and stocks. */ @@ -243,8 +243,8 @@ private boolean isSuccessMessageAuthenticated(SuccessMessage successMessage) { } /** - * Handles a {@link SubscriptionsMessage} for updating {@link #listenedMarketDataMessageTypes} and returns a {@link - * List} of currently subscribe symbols or null. + * Handles a {@link SubscriptionsMessage} for updating {@link #listenedMarketDataMessageTypes} and returns a + * {@link List} of currently subscribe symbols or null. * * @param marketDataMessageType the {@link MarketDataMessageType} * @param newSubscribedSymbols the new subscribed symbols from {@link SubscriptionsMessage} diff --git a/src/main/java/net/jacobpeterson/alpaca/websocket/streaming/StreamingWebsocket.java b/src/main/java/net/jacobpeterson/alpaca/websocket/streaming/StreamingWebsocket.java index cfbf2106..92a15c0c 100644 --- a/src/main/java/net/jacobpeterson/alpaca/websocket/streaming/StreamingWebsocket.java +++ b/src/main/java/net/jacobpeterson/alpaca/websocket/streaming/StreamingWebsocket.java @@ -35,8 +35,8 @@ import static net.jacobpeterson.alpaca.util.gson.GsonUtil.GSON; /** - * {@link StreamingWebsocket} is an {@link AlpacaWebsocket} implementation and provides the {@link - * StreamingWebsocketInterface} interface for + * {@link StreamingWebsocket} is an {@link AlpacaWebsocket} implementation and provides the + * {@link StreamingWebsocketInterface} interface for * Streaming */ public class StreamingWebsocket extends AlpacaWebsocket diff --git a/src/main/resources/alpaca.default.properties b/src/main/resources/alpaca.default.properties index 0cef7e43..7da46f1c 100644 --- a/src/main/resources/alpaca.default.properties +++ b/src/main/resources/alpaca.default.properties @@ -4,7 +4,6 @@ #endpoint_api_type = #data_api_type = #user_agent = - #Defaults: endpoint_api_type=paper data_api_type=iex diff --git a/src/test/java/net/jacobpeterson/alpaca/test/live/AlpacaAPITest.java b/src/test/java/net/jacobpeterson/alpaca/test/live/AlpacaAPITest.java index f9760235..9e6d6136 100644 --- a/src/test/java/net/jacobpeterson/alpaca/test/live/AlpacaAPITest.java +++ b/src/test/java/net/jacobpeterson/alpaca/test/live/AlpacaAPITest.java @@ -15,7 +15,6 @@ import net.jacobpeterson.alpaca.model.endpoint.orders.enums.CurrentOrderStatus; import net.jacobpeterson.alpaca.rest.AlpacaClientException; import net.jacobpeterson.alpaca.rest.endpoint.account.AccountEndpoint; -import net.jacobpeterson.alpaca.rest.endpoint.accountactivities.AccountActivitiesEndpoint; import net.jacobpeterson.alpaca.rest.endpoint.accountconfiguration.AccountConfigurationEndpoint; import net.jacobpeterson.alpaca.rest.endpoint.clock.ClockEndpoint; import net.jacobpeterson.alpaca.rest.endpoint.orders.OrdersEndpoint; @@ -156,8 +155,9 @@ public void testAccountEndpointGet() throws AlpacaClientException, NumberFormatE } /** - * Tests @{@link AccountActivitiesEndpoint#get(ZonedDateTime, ZonedDateTime, ZonedDateTime, SortDirection, Integer, - * String, ActivityType...)} one {@link AccountActivity} exists until now. + * Tests + * @{@link AccountActivitiesEndpoint#get(ZonedDateTime, ZonedDateTime, ZonedDateTime, SortDirection, Integer, + *String, ActivityType...)} one {@link AccountActivity} exists until now. * * @throws AlpacaClientException thrown for {@link AlpacaClientException}s */ @@ -246,7 +246,8 @@ public void testAccountConfigurationEndpointSet() throws AlpacaClientException { } /** - * Test {@link OrdersEndpoint#get(CurrentOrderStatus, Integer, ZonedDateTime, ZonedDateTime, SortDirection, Boolean, + * Test + * {@link OrdersEndpoint#get(CurrentOrderStatus, Integer, ZonedDateTime, ZonedDateTime, SortDirection, Boolean, * Collection)} one {@link Order} exists until now. * * @throws AlpacaClientException thrown for {@link AlpacaClientException}s From cf282160700a5edf396a3ac0de0d32a7e8d88e19 Mon Sep 17 00:00:00 2001 From: Jacob Peterson <14127217+Petersoj@users.noreply.github.com> Date: Sun, 20 Aug 2023 16:36:10 -0600 Subject: [PATCH 197/271] Update `.editorconfig` --- .editorconfig | 1067 +++++++++++++++++++++++++++++++++++-------------- README.md | 1 - 2 files changed, 776 insertions(+), 292 deletions(-) diff --git a/.editorconfig b/.editorconfig index 604f1553..818a4c9e 100644 --- a/.editorconfig +++ b/.editorconfig @@ -11,8 +11,33 @@ ij_formatter_off_tag = @formatter:off ij_formatter_on_tag = @formatter:on ij_formatter_tags_enabled = true ij_smart_tabs = false +ij_visual_guides = ij_wrap_on_typing = false +[*.css] +ij_css_align_closing_brace_with_properties = false +ij_css_blank_lines_around_nested_selector = 1 +ij_css_blank_lines_between_blocks = 1 +ij_css_block_comment_add_space = false +ij_css_brace_placement = end_of_line +ij_css_enforce_quotes_on_format = false +ij_css_hex_color_long_format = false +ij_css_hex_color_lower_case = false +ij_css_hex_color_short_format = false +ij_css_hex_color_upper_case = false +ij_css_keep_blank_lines_in_code = 2 +ij_css_keep_indents_on_empty_lines = false +ij_css_keep_single_line_blocks = false +ij_css_properties_order = font,font-family,font-size,font-weight,font-style,font-variant,font-size-adjust,font-stretch,line-height,position,z-index,top,right,bottom,left,display,visibility,float,clear,overflow,overflow-x,overflow-y,clip,zoom,align-content,align-items,align-self,flex,flex-flow,flex-basis,flex-direction,flex-grow,flex-shrink,flex-wrap,justify-content,order,box-sizing,width,min-width,max-width,height,min-height,max-height,margin,margin-top,margin-right,margin-bottom,margin-left,padding,padding-top,padding-right,padding-bottom,padding-left,table-layout,empty-cells,caption-side,border-spacing,border-collapse,list-style,list-style-position,list-style-type,list-style-image,content,quotes,counter-reset,counter-increment,resize,cursor,user-select,nav-index,nav-up,nav-right,nav-down,nav-left,transition,transition-delay,transition-timing-function,transition-duration,transition-property,transform,transform-origin,animation,animation-name,animation-duration,animation-play-state,animation-timing-function,animation-delay,animation-iteration-count,animation-direction,text-align,text-align-last,vertical-align,white-space,text-decoration,text-emphasis,text-emphasis-color,text-emphasis-style,text-emphasis-position,text-indent,text-justify,letter-spacing,word-spacing,text-outline,text-transform,text-wrap,text-overflow,text-overflow-ellipsis,text-overflow-mode,word-wrap,word-break,tab-size,hyphens,pointer-events,opacity,color,border,border-width,border-style,border-color,border-top,border-top-width,border-top-style,border-top-color,border-right,border-right-width,border-right-style,border-right-color,border-bottom,border-bottom-width,border-bottom-style,border-bottom-color,border-left,border-left-width,border-left-style,border-left-color,border-radius,border-top-left-radius,border-top-right-radius,border-bottom-right-radius,border-bottom-left-radius,border-image,border-image-source,border-image-slice,border-image-width,border-image-outset,border-image-repeat,outline,outline-width,outline-style,outline-color,outline-offset,background,background-color,background-image,background-repeat,background-attachment,background-position,background-position-x,background-position-y,background-clip,background-origin,background-size,box-decoration-break,box-shadow,text-shadow +ij_css_space_after_colon = true +ij_css_space_before_opening_brace = true +ij_css_use_double_quotes = true +ij_css_value_alignment = do_not_align + +[*.feature] +indent_size = 2 +ij_gherkin_keep_indents_on_empty_lines = false + [*.java] ij_java_align_consecutive_assignments = false ij_java_align_consecutive_variable_declarations = false @@ -22,17 +47,21 @@ ij_java_align_multiline_array_initializer_expression = false ij_java_align_multiline_assignment = false ij_java_align_multiline_binary_operation = false ij_java_align_multiline_chained_methods = false +ij_java_align_multiline_deconstruction_list_components = false ij_java_align_multiline_extends_list = false ij_java_align_multiline_for = false ij_java_align_multiline_method_parentheses = false ij_java_align_multiline_parameters = false ij_java_align_multiline_parameters_in_calls = false ij_java_align_multiline_parenthesized_expression = false +ij_java_align_multiline_records = false ij_java_align_multiline_resources = false ij_java_align_multiline_ternary_operation = false +ij_java_align_multiline_text_blocks = false ij_java_align_multiline_throws_list = false ij_java_align_subsequent_simple_methods = false ij_java_align_throws_keyword = false +ij_java_align_types_in_multi_catch = false ij_java_annotation_parameter_wrap = normal ij_java_array_initializer_new_line_after_left_brace = false ij_java_array_initializer_right_brace_on_new_line = false @@ -57,18 +86,22 @@ ij_java_blank_lines_before_imports = 1 ij_java_blank_lines_before_method_body = 0 ij_java_blank_lines_before_package = 0 ij_java_block_brace_style = end_of_line +ij_java_block_comment_add_space = false ij_java_block_comment_at_first_column = true +ij_java_builder_methods = ij_java_call_parameters_new_line_after_left_paren = false ij_java_call_parameters_right_paren_on_new_line = false ij_java_call_parameters_wrap = normal ij_java_case_statement_on_separate_line = true ij_java_catch_on_new_line = false -ij_java_class_annotation_wrap = split_into_lines +ij_java_class_annotation_wrap = normal ij_java_class_brace_style = end_of_line -ij_java_class_count_to_use_import_on_demand = 20 -ij_java_class_names_in_javadoc = 1 +ij_java_class_count_to_use_import_on_demand = 999 +ij_java_class_names_in_javadoc = 3 +ij_java_deconstruction_list_wrap = normal ij_java_do_not_indent_top_level_class_members = false ij_java_do_not_wrap_after_single_annotation = false +ij_java_do_not_wrap_after_single_annotation_in_parameter = false ij_java_do_while_brace_force = always ij_java_doc_add_blank_line_after_description = true ij_java_doc_add_blank_line_after_param_comments = true @@ -76,7 +109,7 @@ ij_java_doc_add_blank_line_after_return = true ij_java_doc_add_p_tag_on_empty_lines = true ij_java_doc_align_exception_comments = true ij_java_doc_align_param_comments = true -ij_java_doc_do_not_wrap_if_one_line = true +ij_java_doc_do_not_wrap_if_one_line = false ij_java_doc_enable_formatting = true ij_java_doc_enable_leading_asterisks = true ij_java_doc_indent_on_continuation = false @@ -89,10 +122,31 @@ ij_java_doc_param_description_on_new_line = false ij_java_doc_preserve_line_breaks = false ij_java_doc_use_throws_not_exception_tag = true ij_java_else_on_new_line = false -ij_java_enum_constants_wrap = normal +ij_java_entity_dd_prefix = +ij_java_entity_dd_suffix = EJB +ij_java_entity_eb_prefix = +ij_java_entity_eb_suffix = Bean +ij_java_entity_hi_prefix = +ij_java_entity_hi_suffix = Home +ij_java_entity_lhi_prefix = Local +ij_java_entity_lhi_suffix = Home +ij_java_entity_li_prefix = Local +ij_java_entity_li_suffix = +ij_java_entity_pk_class = java.lang.String +ij_java_entity_ri_prefix = +ij_java_entity_ri_suffix = +ij_java_entity_vo_prefix = +ij_java_entity_vo_suffix = VO +ij_java_enum_constants_wrap = split_into_lines ij_java_extends_keyword_wrap = normal ij_java_extends_list_wrap = normal -ij_java_field_annotation_wrap = split_into_lines +ij_java_field_annotation_wrap = normal +ij_java_field_name_prefix = +ij_java_field_name_suffix = +ij_java_filter_class_prefix = +ij_java_filter_class_suffix = +ij_java_filter_dd_prefix = +ij_java_filter_dd_suffix = ij_java_finally_on_new_line = false ij_java_for_brace_force = always ij_java_for_statement_new_line_after_left_paren = false @@ -109,6 +163,7 @@ ij_java_keep_blank_lines_before_right_brace = 1 ij_java_keep_blank_lines_between_package_declaration_and_header = 1 ij_java_keep_blank_lines_in_code = 1 ij_java_keep_blank_lines_in_declarations = 1 +ij_java_keep_builder_methods_indents = false ij_java_keep_control_statement_in_one_line = true ij_java_keep_first_column_comment = true ij_java_keep_indents_on_empty_lines = false @@ -118,24 +173,44 @@ ij_java_keep_simple_blocks_in_one_line = true ij_java_keep_simple_classes_in_one_line = true ij_java_keep_simple_lambdas_in_one_line = true ij_java_keep_simple_methods_in_one_line = true +ij_java_label_indent_absolute = false +ij_java_label_indent_size = 0 ij_java_lambda_brace_style = end_of_line ij_java_layout_static_imports_separately = true -ij_java_line_comment_add_space = true -ij_java_line_comment_at_first_column = false -ij_java_method_annotation_wrap = split_into_lines +ij_java_line_comment_add_space = false +ij_java_line_comment_add_space_on_reformat = false +ij_java_line_comment_at_first_column = true +ij_java_listener_class_prefix = +ij_java_listener_class_suffix = +ij_java_local_variable_name_prefix = +ij_java_local_variable_name_suffix = +ij_java_message_dd_prefix = +ij_java_message_dd_suffix = EJB +ij_java_message_eb_prefix = +ij_java_message_eb_suffix = Bean +ij_java_method_annotation_wrap = normal ij_java_method_brace_style = end_of_line ij_java_method_call_chain_wrap = normal ij_java_method_parameters_new_line_after_left_paren = false ij_java_method_parameters_right_paren_on_new_line = false ij_java_method_parameters_wrap = normal ij_java_modifier_list_wrap = false -ij_java_names_count_to_use_import_on_demand = 20 +ij_java_multi_catch_types_wrap = normal +ij_java_names_count_to_use_import_on_demand = 999 +ij_java_new_line_after_lparen_in_annotation = false +ij_java_new_line_after_lparen_in_deconstruction_pattern = false +ij_java_new_line_after_lparen_in_record_header = false +ij_java_packages_to_use_import_on_demand = ij_java_parameter_annotation_wrap = normal +ij_java_parameter_name_prefix = +ij_java_parameter_name_suffix = ij_java_parentheses_expression_new_line_after_left_paren = false ij_java_parentheses_expression_right_paren_on_new_line = false ij_java_place_assignment_sign_on_next_line = false ij_java_prefer_longer_names = true ij_java_prefer_parameters_wrap = false +ij_java_record_components_wrap = normal +ij_java_repeat_annotations = ij_java_repeat_synchronized = true ij_java_replace_instanceof_and_cast = false ij_java_replace_null_check = true @@ -143,6 +218,27 @@ ij_java_replace_sum_lambda_with_method_ref = true ij_java_resource_list_new_line_after_left_paren = false ij_java_resource_list_right_paren_on_new_line = false ij_java_resource_list_wrap = normal +ij_java_rparen_on_new_line_in_annotation = false +ij_java_rparen_on_new_line_in_deconstruction_pattern = false +ij_java_rparen_on_new_line_in_record_header = false +ij_java_servlet_class_prefix = +ij_java_servlet_class_suffix = +ij_java_servlet_dd_prefix = +ij_java_servlet_dd_suffix = +ij_java_session_dd_prefix = +ij_java_session_dd_suffix = EJB +ij_java_session_eb_prefix = +ij_java_session_eb_suffix = Bean +ij_java_session_hi_prefix = +ij_java_session_hi_suffix = Home +ij_java_session_lhi_prefix = Local +ij_java_session_lhi_suffix = Home +ij_java_session_li_prefix = Local +ij_java_session_li_suffix = +ij_java_session_ri_prefix = +ij_java_session_ri_suffix = +ij_java_session_si_prefix = +ij_java_session_si_suffix = Service ij_java_space_after_closing_angle_bracket_in_type_argument = false ij_java_space_after_colon = true ij_java_space_after_comma = true @@ -160,6 +256,7 @@ ij_java_space_before_class_left_brace = true ij_java_space_before_colon = true ij_java_space_before_colon_in_foreach = true ij_java_space_before_comma = false +ij_java_space_before_deconstruction_list = false ij_java_space_before_do_left_brace = true ij_java_space_before_else_keyword = true ij_java_space_before_else_left_brace = true @@ -190,6 +287,7 @@ ij_java_space_within_empty_array_initializer_braces = false ij_java_space_within_empty_method_call_parentheses = false ij_java_space_within_empty_method_parentheses = false ij_java_spaces_around_additive_operators = true +ij_java_spaces_around_annotation_eq = true ij_java_spaces_around_assignment_operators = true ij_java_spaces_around_bitwise_operators = true ij_java_spaces_around_equality_operators = true @@ -208,24 +306,31 @@ ij_java_spaces_within_braces = false ij_java_spaces_within_brackets = false ij_java_spaces_within_cast_parentheses = false ij_java_spaces_within_catch_parentheses = false +ij_java_spaces_within_deconstruction_list = false ij_java_spaces_within_for_parentheses = false ij_java_spaces_within_if_parentheses = false ij_java_spaces_within_method_call_parentheses = false ij_java_spaces_within_method_parentheses = false ij_java_spaces_within_parentheses = false +ij_java_spaces_within_record_header = false ij_java_spaces_within_switch_parentheses = false ij_java_spaces_within_synchronized_parentheses = false ij_java_spaces_within_try_parentheses = false ij_java_spaces_within_while_parentheses = false ij_java_special_else_if_treatment = true +ij_java_static_field_name_prefix = +ij_java_static_field_name_suffix = +ij_java_subclass_name_prefix = ij_java_subclass_name_suffix = Impl ij_java_ternary_operation_signs_on_next_line = false ij_java_ternary_operation_wrap = normal +ij_java_test_name_prefix = ij_java_test_name_suffix = Test ij_java_throws_keyword_wrap = normal ij_java_throws_list_wrap = normal ij_java_use_external_annotations = false ij_java_use_fq_class_names = false +ij_java_use_relative_indents = false ij_java_use_single_class_imports = true ij_java_variable_annotation_wrap = normal ij_java_visibility = public @@ -233,209 +338,99 @@ ij_java_while_brace_force = always ij_java_while_on_new_line = false ij_java_wrap_comments = true ij_java_wrap_first_method_in_call_chain = false -ij_java_wrap_long_lines = true +ij_java_wrap_long_lines = false -[*.json] +[*.less] indent_size = 2 -ij_json_keep_blank_lines_in_code = 0 -ij_json_keep_indents_on_empty_lines = false -ij_json_keep_line_breaks = true -ij_json_space_after_colon = true -ij_json_space_after_comma = true -ij_json_space_before_colon = true -ij_json_space_before_comma = false -ij_json_spaces_within_braces = false -ij_json_spaces_within_brackets = false -ij_json_wrap_long_lines = false +ij_less_align_closing_brace_with_properties = false +ij_less_blank_lines_around_nested_selector = 1 +ij_less_blank_lines_between_blocks = 1 +ij_less_block_comment_add_space = false +ij_less_brace_placement = 0 +ij_less_enforce_quotes_on_format = false +ij_less_hex_color_long_format = false +ij_less_hex_color_lower_case = false +ij_less_hex_color_short_format = false +ij_less_hex_color_upper_case = false +ij_less_keep_blank_lines_in_code = 2 +ij_less_keep_indents_on_empty_lines = false +ij_less_keep_single_line_blocks = false +ij_less_line_comment_add_space = false +ij_less_line_comment_at_first_column = false +ij_less_properties_order = font,font-family,font-size,font-weight,font-style,font-variant,font-size-adjust,font-stretch,line-height,position,z-index,top,right,bottom,left,display,visibility,float,clear,overflow,overflow-x,overflow-y,clip,zoom,align-content,align-items,align-self,flex,flex-flow,flex-basis,flex-direction,flex-grow,flex-shrink,flex-wrap,justify-content,order,box-sizing,width,min-width,max-width,height,min-height,max-height,margin,margin-top,margin-right,margin-bottom,margin-left,padding,padding-top,padding-right,padding-bottom,padding-left,table-layout,empty-cells,caption-side,border-spacing,border-collapse,list-style,list-style-position,list-style-type,list-style-image,content,quotes,counter-reset,counter-increment,resize,cursor,user-select,nav-index,nav-up,nav-right,nav-down,nav-left,transition,transition-delay,transition-timing-function,transition-duration,transition-property,transform,transform-origin,animation,animation-name,animation-duration,animation-play-state,animation-timing-function,animation-delay,animation-iteration-count,animation-direction,text-align,text-align-last,vertical-align,white-space,text-decoration,text-emphasis,text-emphasis-color,text-emphasis-style,text-emphasis-position,text-indent,text-justify,letter-spacing,word-spacing,text-outline,text-transform,text-wrap,text-overflow,text-overflow-ellipsis,text-overflow-mode,word-wrap,word-break,tab-size,hyphens,pointer-events,opacity,color,border,border-width,border-style,border-color,border-top,border-top-width,border-top-style,border-top-color,border-right,border-right-width,border-right-style,border-right-color,border-bottom,border-bottom-width,border-bottom-style,border-bottom-color,border-left,border-left-width,border-left-style,border-left-color,border-radius,border-top-left-radius,border-top-right-radius,border-bottom-right-radius,border-bottom-left-radius,border-image,border-image-source,border-image-slice,border-image-width,border-image-outset,border-image-repeat,outline,outline-width,outline-style,outline-color,outline-offset,background,background-color,background-image,background-repeat,background-attachment,background-position,background-position-x,background-position-y,background-clip,background-origin,background-size,box-decoration-break,box-shadow,text-shadow +ij_less_space_after_colon = true +ij_less_space_before_opening_brace = true +ij_less_use_double_quotes = true +ij_less_value_alignment = 0 -[*.properties] -ij_properties_align_group_field_declarations = false +[*.proto] +indent_size = 2 +tab_width = 2 +ij_continuation_indent_size = 4 +ij_protobuf_keep_blank_lines_in_code = 2 +ij_protobuf_keep_indents_on_empty_lines = false +ij_protobuf_keep_line_breaks = true +ij_protobuf_space_after_comma = true +ij_protobuf_space_before_comma = false +ij_protobuf_spaces_around_assignment_operators = true +ij_protobuf_spaces_within_braces = false +ij_protobuf_spaces_within_brackets = false -[*.scala] +[*.sass] +indent_size = 2 +ij_sass_align_closing_brace_with_properties = false +ij_sass_blank_lines_around_nested_selector = 1 +ij_sass_blank_lines_between_blocks = 1 +ij_sass_brace_placement = 0 +ij_sass_enforce_quotes_on_format = false +ij_sass_hex_color_long_format = false +ij_sass_hex_color_lower_case = false +ij_sass_hex_color_short_format = false +ij_sass_hex_color_upper_case = false +ij_sass_keep_blank_lines_in_code = 2 +ij_sass_keep_indents_on_empty_lines = false +ij_sass_keep_single_line_blocks = false +ij_sass_line_comment_add_space = false +ij_sass_line_comment_at_first_column = false +ij_sass_properties_order = font,font-family,font-size,font-weight,font-style,font-variant,font-size-adjust,font-stretch,line-height,position,z-index,top,right,bottom,left,display,visibility,float,clear,overflow,overflow-x,overflow-y,clip,zoom,align-content,align-items,align-self,flex,flex-flow,flex-basis,flex-direction,flex-grow,flex-shrink,flex-wrap,justify-content,order,box-sizing,width,min-width,max-width,height,min-height,max-height,margin,margin-top,margin-right,margin-bottom,margin-left,padding,padding-top,padding-right,padding-bottom,padding-left,table-layout,empty-cells,caption-side,border-spacing,border-collapse,list-style,list-style-position,list-style-type,list-style-image,content,quotes,counter-reset,counter-increment,resize,cursor,user-select,nav-index,nav-up,nav-right,nav-down,nav-left,transition,transition-delay,transition-timing-function,transition-duration,transition-property,transform,transform-origin,animation,animation-name,animation-duration,animation-play-state,animation-timing-function,animation-delay,animation-iteration-count,animation-direction,text-align,text-align-last,vertical-align,white-space,text-decoration,text-emphasis,text-emphasis-color,text-emphasis-style,text-emphasis-position,text-indent,text-justify,letter-spacing,word-spacing,text-outline,text-transform,text-wrap,text-overflow,text-overflow-ellipsis,text-overflow-mode,word-wrap,word-break,tab-size,hyphens,pointer-events,opacity,color,border,border-width,border-style,border-color,border-top,border-top-width,border-top-style,border-top-color,border-right,border-right-width,border-right-style,border-right-color,border-bottom,border-bottom-width,border-bottom-style,border-bottom-color,border-left,border-left-width,border-left-style,border-left-color,border-radius,border-top-left-radius,border-top-right-radius,border-bottom-right-radius,border-bottom-left-radius,border-image,border-image-source,border-image-slice,border-image-width,border-image-outset,border-image-repeat,outline,outline-width,outline-style,outline-color,outline-offset,background,background-color,background-image,background-repeat,background-attachment,background-position,background-position-x,background-position-y,background-clip,background-origin,background-size,box-decoration-break,box-shadow,text-shadow +ij_sass_space_after_colon = true +ij_sass_space_before_opening_brace = true +ij_sass_use_double_quotes = true +ij_sass_value_alignment = 0 + +[*.scss] +indent_size = 2 +ij_scss_align_closing_brace_with_properties = false +ij_scss_blank_lines_around_nested_selector = 1 +ij_scss_blank_lines_between_blocks = 1 +ij_scss_block_comment_add_space = false +ij_scss_brace_placement = 0 +ij_scss_enforce_quotes_on_format = false +ij_scss_hex_color_long_format = false +ij_scss_hex_color_lower_case = false +ij_scss_hex_color_short_format = false +ij_scss_hex_color_upper_case = false +ij_scss_keep_blank_lines_in_code = 2 +ij_scss_keep_indents_on_empty_lines = false +ij_scss_keep_single_line_blocks = false +ij_scss_line_comment_add_space = false +ij_scss_line_comment_at_first_column = false +ij_scss_properties_order = font,font-family,font-size,font-weight,font-style,font-variant,font-size-adjust,font-stretch,line-height,position,z-index,top,right,bottom,left,display,visibility,float,clear,overflow,overflow-x,overflow-y,clip,zoom,align-content,align-items,align-self,flex,flex-flow,flex-basis,flex-direction,flex-grow,flex-shrink,flex-wrap,justify-content,order,box-sizing,width,min-width,max-width,height,min-height,max-height,margin,margin-top,margin-right,margin-bottom,margin-left,padding,padding-top,padding-right,padding-bottom,padding-left,table-layout,empty-cells,caption-side,border-spacing,border-collapse,list-style,list-style-position,list-style-type,list-style-image,content,quotes,counter-reset,counter-increment,resize,cursor,user-select,nav-index,nav-up,nav-right,nav-down,nav-left,transition,transition-delay,transition-timing-function,transition-duration,transition-property,transform,transform-origin,animation,animation-name,animation-duration,animation-play-state,animation-timing-function,animation-delay,animation-iteration-count,animation-direction,text-align,text-align-last,vertical-align,white-space,text-decoration,text-emphasis,text-emphasis-color,text-emphasis-style,text-emphasis-position,text-indent,text-justify,letter-spacing,word-spacing,text-outline,text-transform,text-wrap,text-overflow,text-overflow-ellipsis,text-overflow-mode,word-wrap,word-break,tab-size,hyphens,pointer-events,opacity,color,border,border-width,border-style,border-color,border-top,border-top-width,border-top-style,border-top-color,border-right,border-right-width,border-right-style,border-right-color,border-bottom,border-bottom-width,border-bottom-style,border-bottom-color,border-left,border-left-width,border-left-style,border-left-color,border-radius,border-top-left-radius,border-top-right-radius,border-bottom-right-radius,border-bottom-left-radius,border-image,border-image-source,border-image-slice,border-image-width,border-image-outset,border-image-repeat,outline,outline-width,outline-style,outline-color,outline-offset,background,background-color,background-image,background-repeat,background-attachment,background-position,background-position-x,background-position-y,background-clip,background-origin,background-size,box-decoration-break,box-shadow,text-shadow +ij_scss_space_after_colon = true +ij_scss_space_before_opening_brace = true +ij_scss_use_double_quotes = true +ij_scss_value_alignment = 0 + +[*.vue] indent_size = 2 tab_width = 2 -ij_continuation_indent_size = 2 -ij_scala_align_composite_pattern = true -ij_scala_align_extends_with = 0 -ij_scala_align_group_field_declarations = false -ij_scala_align_if_else = false -ij_scala_align_in_columns_case_branch = false -ij_scala_align_multiline_binary_operation = false -ij_scala_align_multiline_chained_methods = false -ij_scala_align_multiline_for = true -ij_scala_align_multiline_parameters = true -ij_scala_align_multiline_parameters_in_calls = false -ij_scala_align_multiline_parenthesized_expression = false -ij_scala_align_tuple_elements = false -ij_scala_align_types_in_multiline_declarations = false -ij_scala_all_other_imports = all other imports -ij_scala_alternate_continuation_indent_for_params = 4 -ij_scala_binary_operation_wrap = off -ij_scala_blank_line = _______ blank line _______ -ij_scala_blank_lines_after_anonymous_class_header = 0 -ij_scala_blank_lines_after_class_header = 0 -ij_scala_blank_lines_after_imports = 1 -ij_scala_blank_lines_after_package = 1 -ij_scala_blank_lines_around_class = 1 -ij_scala_blank_lines_around_field = 0 -ij_scala_blank_lines_around_field_in_inner_scopes = 0 -ij_scala_blank_lines_around_field_in_interface = 0 -ij_scala_blank_lines_around_method = 1 -ij_scala_blank_lines_around_method_in_inner_scopes = 1 -ij_scala_blank_lines_around_method_in_interface = 1 -ij_scala_blank_lines_before_imports = 1 -ij_scala_blank_lines_before_method_body = 0 -ij_scala_blank_lines_before_package = 0 -ij_scala_block_brace_style = end_of_line -ij_scala_block_comment_at_first_column = true -ij_scala_call_parameters_new_line_after_lparen = 0 -ij_scala_call_parameters_right_paren_on_new_line = false -ij_scala_call_parameters_wrap = off -ij_scala_case_clause_brace_force = never -ij_scala_catch_on_new_line = false -ij_scala_class_annotation_wrap = split_into_lines -ij_scala_class_brace_style = end_of_line -ij_scala_closure_brace_force = never -ij_scala_do_not_align_block_expr_params = true -ij_scala_do_not_indent_case_clause_body = false -ij_scala_do_not_indent_tuples_close_brace = true -ij_scala_do_while_brace_force = never -ij_scala_else_on_new_line = false -ij_scala_enable_scaladoc_formatting = true -ij_scala_enforce_functional_syntax_for_unit = true -ij_scala_exclude_prefix = exclude: -ij_scala_extends_keyword_wrap = off -ij_scala_extends_list_wrap = off -ij_scala_field_annotation_wrap = split_into_lines -ij_scala_finally_brace_force = never -ij_scala_finally_on_new_line = false -ij_scala_for_brace_force = never -ij_scala_for_statement_wrap = off -ij_scala_formatter = 0 -ij_scala_if_brace_force = never -ij_scala_indent_braced_function_args = true -ij_scala_indent_case_from_switch = true -ij_scala_indent_first_parameter = true -ij_scala_indent_first_parameter_clause = false -ij_scala_indent_type_arguments = true -ij_scala_indent_type_parameters = true -ij_scala_insert_whitespaces_in_simple_one_line_method = true -ij_scala_keep_blank_lines_before_right_brace = 2 -ij_scala_keep_blank_lines_in_code = 2 -ij_scala_keep_blank_lines_in_declarations = 2 -ij_scala_keep_comments_on_same_line = true -ij_scala_keep_first_column_comment = false -ij_scala_keep_indents_on_empty_lines = false -ij_scala_keep_line_breaks = true -ij_scala_keep_one_line_lambdas_in_arg_list = false -ij_scala_keep_simple_blocks_in_one_line = false -ij_scala_keep_simple_methods_in_one_line = false -ij_scala_keep_xml_formatting = false -ij_scala_line_comment_at_first_column = true -ij_scala_method_annotation_wrap = split_into_lines -ij_scala_method_brace_force = never -ij_scala_method_brace_style = end_of_line -ij_scala_method_call_chain_wrap = off -ij_scala_method_parameters_new_line_after_left_paren = false -ij_scala_method_parameters_right_paren_on_new_line = false -ij_scala_method_parameters_wrap = off -ij_scala_modifier_list_wrap = false -ij_scala_multiline_string_align_dangling_closing_quotes = false -ij_scala_multiline_string_closing_quotes_on_new_line = true -ij_scala_multiline_string_insert_margin_on_enter = true -ij_scala_multiline_string_margin_char = | -ij_scala_multiline_string_margin_indent = 2 -ij_scala_multiline_string_opening_quotes_on_new_line = true -ij_scala_multiline_string_process_margin_on_copy_paste = true -ij_scala_newline_after_annotations = false -ij_scala_not_continuation_indent_for_params = false -ij_scala_parameter_annotation_wrap = off -ij_scala_parentheses_expression_new_line_after_left_paren = false -ij_scala_parentheses_expression_right_paren_on_new_line = false -ij_scala_place_closure_parameters_on_new_line = false -ij_scala_place_self_type_on_new_line = true -ij_scala_prefer_parameters_wrap = false -ij_scala_preserve_space_after_method_declaration_name = false -ij_scala_reformat_on_compile = false -ij_scala_replace_case_arrow_with_unicode_char = false -ij_scala_replace_for_generator_arrow_with_unicode_char = false -ij_scala_replace_lambda_with_greek_letter = false -ij_scala_replace_map_arrow_with_unicode_char = false -ij_scala_scalafmt_reformat_on_files_save = false -ij_scala_scalafmt_show_invalid_code_warnings = true -ij_scala_scalafmt_use_intellij_formatter_for_range_format = true -ij_scala_sd_align_exception_comments = true -ij_scala_sd_align_other_tags_comments = true -ij_scala_sd_align_parameters_comments = true -ij_scala_sd_align_return_comments = true -ij_scala_sd_blank_line_after_parameters_comments = false -ij_scala_sd_blank_line_after_return_comments = false -ij_scala_sd_blank_line_before_parameters = false -ij_scala_sd_blank_line_before_tags = true -ij_scala_sd_blank_line_between_parameters = false -ij_scala_sd_keep_blank_lines_between_tags = false -ij_scala_sd_preserve_spaces_in_tags = false -ij_scala_space_after_comma = true -ij_scala_space_after_for_semicolon = true -ij_scala_space_after_modifiers_constructor = false -ij_scala_space_after_type_colon = true -ij_scala_space_before_brace_method_call = true -ij_scala_space_before_class_left_brace = true -ij_scala_space_before_infix_like_method_parentheses = false -ij_scala_space_before_infix_method_call_parentheses = false -ij_scala_space_before_infix_operator_like_method_call_parentheses = true -ij_scala_space_before_method_call_parentheses = false -ij_scala_space_before_method_left_brace = true -ij_scala_space_before_method_parentheses = false -ij_scala_space_before_type_colon = false -ij_scala_space_before_type_parameter_in_def_list = false -ij_scala_space_before_type_parameter_list = false -ij_scala_space_inside_closure_braces = true -ij_scala_space_inside_self_type_braces = true -ij_scala_space_within_empty_method_call_parentheses = false -ij_scala_spaces_around_at_in_patterns = false -ij_scala_spaces_in_imports = false -ij_scala_spaces_in_one_line_blocks = false -ij_scala_spaces_within_brackets = false -ij_scala_spaces_within_for_parentheses = false -ij_scala_spaces_within_if_parentheses = false -ij_scala_spaces_within_method_call_parentheses = false -ij_scala_spaces_within_method_parentheses = false -ij_scala_spaces_within_parentheses = false -ij_scala_spaces_within_while_parentheses = false -ij_scala_special_else_if_treatment = true -ij_scala_trailing_comma_arg_list_enabled = true -ij_scala_trailing_comma_import_selector_enabled = false -ij_scala_trailing_comma_mode = trailing_comma_keep -ij_scala_trailing_comma_params_enabled = true -ij_scala_trailing_comma_pattern_arg_list_enabled = false -ij_scala_trailing_comma_tuple_enabled = false -ij_scala_trailing_comma_tuple_type_enabled = false -ij_scala_trailing_comma_type_params_enabled = false -ij_scala_try_brace_force = never -ij_scala_type_annotation_exclude_constant = true -ij_scala_type_annotation_exclude_in_dialect_sources = true -ij_scala_type_annotation_exclude_in_test_sources = false -ij_scala_type_annotation_exclude_member_of_anonymous_class = false -ij_scala_type_annotation_exclude_member_of_private_class = false -ij_scala_type_annotation_exclude_when_type_is_stable = true -ij_scala_type_annotation_function_parameter = false -ij_scala_type_annotation_implicit_modifier = true -ij_scala_type_annotation_local_definition = false -ij_scala_type_annotation_private_member = false -ij_scala_type_annotation_protected_member = true -ij_scala_type_annotation_public_member = true -ij_scala_type_annotation_structural_type = true -ij_scala_type_annotation_underscore_parameter = false -ij_scala_type_annotation_unit_type = true -ij_scala_use_alternate_continuation_indent_for_params = false -ij_scala_use_scaladoc2_formatting = false -ij_scala_variable_annotation_wrap = off -ij_scala_while_brace_force = never -ij_scala_while_on_new_line = false -ij_scala_wrap_before_with_keyword = false -ij_scala_wrap_first_method_in_call_chain = false -ij_scala_wrap_long_lines = false +ij_continuation_indent_size = 4 +ij_vue_indent_children_of_top_level = template +ij_vue_interpolation_new_line_after_start_delimiter = true +ij_vue_interpolation_new_line_before_end_delimiter = true +ij_vue_interpolation_wrap = off +ij_vue_keep_indents_on_empty_lines = false +ij_vue_spaces_within_interpolation_expressions = true [.editorconfig] ij_editorconfig_align_group_field_declarations = false @@ -445,27 +440,402 @@ ij_editorconfig_space_before_colon = false ij_editorconfig_space_before_comma = false ij_editorconfig_spaces_around_assignment_operators = true -[{*.gant,*.groovy,*.gradle,*.gdsl,*.gy}] +[{*.ant,*.fxml,*.jhm,*.jnlp,*.jrxml,*.pom,*.rng,*.tld,*.wadl,*.wsdl,*.xml,*.xsd,*.xsl,*.xslt,*.xul}] +ij_xml_align_attributes = true +ij_xml_align_text = false +ij_xml_attribute_wrap = normal +ij_xml_block_comment_add_space = false +ij_xml_block_comment_at_first_column = true +ij_xml_keep_blank_lines = 2 +ij_xml_keep_indents_on_empty_lines = false +ij_xml_keep_line_breaks = true +ij_xml_keep_line_breaks_in_text = true +ij_xml_keep_whitespaces = false +ij_xml_keep_whitespaces_around_cdata = preserve +ij_xml_keep_whitespaces_inside_cdata = false +ij_xml_line_comment_at_first_column = true +ij_xml_space_after_tag_name = false +ij_xml_space_around_equals_in_attribute = false +ij_xml_space_inside_empty_tag = false +ij_xml_text_wrap = normal +ij_xml_use_custom_settings = false + +[{*.ats,*.cts,*.mts,*.ts}] +ij_typescript_align_imports = false +ij_typescript_align_multiline_array_initializer_expression = false +ij_typescript_align_multiline_binary_operation = false +ij_typescript_align_multiline_chained_methods = false +ij_typescript_align_multiline_extends_list = false +ij_typescript_align_multiline_for = false +ij_typescript_align_multiline_parameters = false +ij_typescript_align_multiline_parameters_in_calls = false +ij_typescript_align_multiline_ternary_operation = false +ij_typescript_align_object_properties = 0 +ij_typescript_align_union_types = false +ij_typescript_align_var_statements = 0 +ij_typescript_array_initializer_new_line_after_left_brace = false +ij_typescript_array_initializer_right_brace_on_new_line = false +ij_typescript_array_initializer_wrap = normal +ij_typescript_assignment_wrap = normal +ij_typescript_binary_operation_sign_on_next_line = false +ij_typescript_binary_operation_wrap = normal +ij_typescript_blacklist_imports = rxjs/Rx,node_modules/**,**/node_modules/**,@angular/material,@angular/material/typings/** +ij_typescript_blank_lines_after_imports = 1 +ij_typescript_blank_lines_around_class = 1 +ij_typescript_blank_lines_around_field = 0 +ij_typescript_blank_lines_around_field_in_interface = 0 +ij_typescript_blank_lines_around_function = 1 +ij_typescript_blank_lines_around_method = 1 +ij_typescript_blank_lines_around_method_in_interface = 1 +ij_typescript_block_brace_style = end_of_line +ij_typescript_block_comment_add_space = false +ij_typescript_block_comment_at_first_column = true +ij_typescript_call_parameters_new_line_after_left_paren = false +ij_typescript_call_parameters_right_paren_on_new_line = false +ij_typescript_call_parameters_wrap = normal +ij_typescript_catch_on_new_line = false +ij_typescript_chained_call_dot_on_new_line = true +ij_typescript_class_brace_style = end_of_line +ij_typescript_comma_on_new_line = false +ij_typescript_do_while_brace_force = always +ij_typescript_else_on_new_line = false +ij_typescript_enforce_trailing_comma = remove +ij_typescript_enum_constants_wrap = split_into_lines +ij_typescript_extends_keyword_wrap = normal +ij_typescript_extends_list_wrap = normal +ij_typescript_field_prefix = _ +ij_typescript_file_name_style = relaxed +ij_typescript_finally_on_new_line = false +ij_typescript_for_brace_force = always +ij_typescript_for_statement_new_line_after_left_paren = false +ij_typescript_for_statement_right_paren_on_new_line = false +ij_typescript_for_statement_wrap = split_into_lines +ij_typescript_force_quote_style = true +ij_typescript_force_semicolon_style = true +ij_typescript_function_expression_brace_style = end_of_line +ij_typescript_if_brace_force = always +ij_typescript_import_merge_members = global +ij_typescript_import_prefer_absolute_path = global +ij_typescript_import_sort_members = true +ij_typescript_import_sort_module_name = false +ij_typescript_import_use_node_resolution = true +ij_typescript_imports_wrap = normal +ij_typescript_indent_case_from_switch = true +ij_typescript_indent_chained_calls = true +ij_typescript_indent_package_children = 0 +ij_typescript_jsdoc_include_types = false +ij_typescript_jsx_attribute_value = braces +ij_typescript_keep_blank_lines_in_code = 1 +ij_typescript_keep_first_column_comment = true +ij_typescript_keep_indents_on_empty_lines = false +ij_typescript_keep_line_breaks = true +ij_typescript_keep_simple_blocks_in_one_line = true +ij_typescript_keep_simple_methods_in_one_line = true +ij_typescript_line_comment_add_space = true +ij_typescript_line_comment_at_first_column = false +ij_typescript_method_brace_style = end_of_line +ij_typescript_method_call_chain_wrap = normal +ij_typescript_method_parameters_new_line_after_left_paren = false +ij_typescript_method_parameters_right_paren_on_new_line = false +ij_typescript_method_parameters_wrap = normal +ij_typescript_object_literal_wrap = on_every_item +ij_typescript_object_types_wrap = on_every_item +ij_typescript_parentheses_expression_new_line_after_left_paren = false +ij_typescript_parentheses_expression_right_paren_on_new_line = false +ij_typescript_place_assignment_sign_on_next_line = false +ij_typescript_prefer_as_type_cast = false +ij_typescript_prefer_explicit_types_function_expression_returns = false +ij_typescript_prefer_explicit_types_function_returns = false +ij_typescript_prefer_explicit_types_vars_fields = false +ij_typescript_prefer_parameters_wrap = false +ij_typescript_property_prefix = +ij_typescript_reformat_c_style_comments = false +ij_typescript_space_after_colon = true +ij_typescript_space_after_comma = true +ij_typescript_space_after_dots_in_rest_parameter = false +ij_typescript_space_after_generator_mult = true +ij_typescript_space_after_property_colon = true +ij_typescript_space_after_quest = true +ij_typescript_space_after_type_colon = true +ij_typescript_space_after_unary_not = false +ij_typescript_space_before_async_arrow_lparen = true +ij_typescript_space_before_catch_keyword = true +ij_typescript_space_before_catch_left_brace = true +ij_typescript_space_before_catch_parentheses = true +ij_typescript_space_before_class_lbrace = true +ij_typescript_space_before_class_left_brace = true +ij_typescript_space_before_colon = true +ij_typescript_space_before_comma = false +ij_typescript_space_before_do_left_brace = true +ij_typescript_space_before_else_keyword = true +ij_typescript_space_before_else_left_brace = true +ij_typescript_space_before_finally_keyword = true +ij_typescript_space_before_finally_left_brace = true +ij_typescript_space_before_for_left_brace = true +ij_typescript_space_before_for_parentheses = true +ij_typescript_space_before_for_semicolon = false +ij_typescript_space_before_function_left_parenth = true +ij_typescript_space_before_generator_mult = false +ij_typescript_space_before_if_left_brace = true +ij_typescript_space_before_if_parentheses = true +ij_typescript_space_before_method_call_parentheses = false +ij_typescript_space_before_method_left_brace = true +ij_typescript_space_before_method_parentheses = false +ij_typescript_space_before_property_colon = false +ij_typescript_space_before_quest = true +ij_typescript_space_before_switch_left_brace = true +ij_typescript_space_before_switch_parentheses = true +ij_typescript_space_before_try_left_brace = true +ij_typescript_space_before_type_colon = false +ij_typescript_space_before_unary_not = false +ij_typescript_space_before_while_keyword = true +ij_typescript_space_before_while_left_brace = true +ij_typescript_space_before_while_parentheses = true +ij_typescript_spaces_around_additive_operators = true +ij_typescript_spaces_around_arrow_function_operator = true +ij_typescript_spaces_around_assignment_operators = true +ij_typescript_spaces_around_bitwise_operators = true +ij_typescript_spaces_around_equality_operators = true +ij_typescript_spaces_around_logical_operators = true +ij_typescript_spaces_around_multiplicative_operators = true +ij_typescript_spaces_around_relational_operators = true +ij_typescript_spaces_around_shift_operators = true +ij_typescript_spaces_around_unary_operator = false +ij_typescript_spaces_within_array_initializer_brackets = false +ij_typescript_spaces_within_brackets = false +ij_typescript_spaces_within_catch_parentheses = false +ij_typescript_spaces_within_for_parentheses = false +ij_typescript_spaces_within_if_parentheses = false +ij_typescript_spaces_within_imports = false +ij_typescript_spaces_within_interpolation_expressions = false +ij_typescript_spaces_within_method_call_parentheses = false +ij_typescript_spaces_within_method_parentheses = false +ij_typescript_spaces_within_object_literal_braces = false +ij_typescript_spaces_within_object_type_braces = true +ij_typescript_spaces_within_parentheses = false +ij_typescript_spaces_within_switch_parentheses = false +ij_typescript_spaces_within_type_assertion = false +ij_typescript_spaces_within_union_types = true +ij_typescript_spaces_within_while_parentheses = false +ij_typescript_special_else_if_treatment = true +ij_typescript_ternary_operation_signs_on_next_line = false +ij_typescript_ternary_operation_wrap = normal +ij_typescript_union_types_wrap = on_every_item +ij_typescript_use_chained_calls_group_indents = false +ij_typescript_use_double_quotes = true +ij_typescript_use_explicit_js_extension = auto +ij_typescript_use_path_mapping = always +ij_typescript_use_public_modifier = false +ij_typescript_use_semicolon_after_statement = true +ij_typescript_var_declaration_wrap = normal +ij_typescript_while_brace_force = always +ij_typescript_while_on_new_line = false +ij_typescript_wrap_comments = true + +[{*.bash,*.sh,*.zsh}] +indent_size = 2 +tab_width = 2 +ij_shell_binary_ops_start_line = false +ij_shell_keep_column_alignment_padding = false +ij_shell_minify_program = false +ij_shell_redirect_followed_by_space = false +ij_shell_switch_cases_indented = false +ij_shell_use_unix_line_separator = true + +[{*.cjs,*.js}] +ij_javascript_align_imports = false +ij_javascript_align_multiline_array_initializer_expression = false +ij_javascript_align_multiline_binary_operation = false +ij_javascript_align_multiline_chained_methods = false +ij_javascript_align_multiline_extends_list = false +ij_javascript_align_multiline_for = false +ij_javascript_align_multiline_parameters = false +ij_javascript_align_multiline_parameters_in_calls = false +ij_javascript_align_multiline_ternary_operation = false +ij_javascript_align_object_properties = 0 +ij_javascript_align_union_types = false +ij_javascript_align_var_statements = 0 +ij_javascript_array_initializer_new_line_after_left_brace = false +ij_javascript_array_initializer_right_brace_on_new_line = false +ij_javascript_array_initializer_wrap = normal +ij_javascript_assignment_wrap = normal +ij_javascript_binary_operation_sign_on_next_line = false +ij_javascript_binary_operation_wrap = normal +ij_javascript_blacklist_imports = rxjs/Rx,node_modules/**,**/node_modules/**,@angular/material,@angular/material/typings/** +ij_javascript_blank_lines_after_imports = 1 +ij_javascript_blank_lines_around_class = 1 +ij_javascript_blank_lines_around_field = 0 +ij_javascript_blank_lines_around_function = 1 +ij_javascript_blank_lines_around_method = 1 +ij_javascript_block_brace_style = end_of_line +ij_javascript_block_comment_add_space = false +ij_javascript_block_comment_at_first_column = true +ij_javascript_call_parameters_new_line_after_left_paren = false +ij_javascript_call_parameters_right_paren_on_new_line = false +ij_javascript_call_parameters_wrap = normal +ij_javascript_catch_on_new_line = false +ij_javascript_chained_call_dot_on_new_line = true +ij_javascript_class_brace_style = end_of_line +ij_javascript_comma_on_new_line = false +ij_javascript_do_while_brace_force = always +ij_javascript_else_on_new_line = false +ij_javascript_enforce_trailing_comma = remove +ij_javascript_extends_keyword_wrap = normal +ij_javascript_extends_list_wrap = normal +ij_javascript_field_prefix = _ +ij_javascript_file_name_style = relaxed +ij_javascript_finally_on_new_line = false +ij_javascript_for_brace_force = always +ij_javascript_for_statement_new_line_after_left_paren = false +ij_javascript_for_statement_right_paren_on_new_line = false +ij_javascript_for_statement_wrap = normal +ij_javascript_force_quote_style = true +ij_javascript_force_semicolon_style = true +ij_javascript_function_expression_brace_style = end_of_line +ij_javascript_if_brace_force = always +ij_javascript_import_merge_members = global +ij_javascript_import_prefer_absolute_path = global +ij_javascript_import_sort_members = true +ij_javascript_import_sort_module_name = false +ij_javascript_import_use_node_resolution = true +ij_javascript_imports_wrap = normal +ij_javascript_indent_case_from_switch = true +ij_javascript_indent_chained_calls = true +ij_javascript_indent_package_children = 0 +ij_javascript_jsx_attribute_value = braces +ij_javascript_keep_blank_lines_in_code = 1 +ij_javascript_keep_first_column_comment = true +ij_javascript_keep_indents_on_empty_lines = false +ij_javascript_keep_line_breaks = true +ij_javascript_keep_simple_blocks_in_one_line = true +ij_javascript_keep_simple_methods_in_one_line = true +ij_javascript_line_comment_add_space = true +ij_javascript_line_comment_at_first_column = false +ij_javascript_method_brace_style = end_of_line +ij_javascript_method_call_chain_wrap = normal +ij_javascript_method_parameters_new_line_after_left_paren = false +ij_javascript_method_parameters_right_paren_on_new_line = false +ij_javascript_method_parameters_wrap = normal +ij_javascript_object_literal_wrap = on_every_item +ij_javascript_object_types_wrap = on_every_item +ij_javascript_parentheses_expression_new_line_after_left_paren = false +ij_javascript_parentheses_expression_right_paren_on_new_line = false +ij_javascript_place_assignment_sign_on_next_line = false +ij_javascript_prefer_as_type_cast = false +ij_javascript_prefer_explicit_types_function_expression_returns = false +ij_javascript_prefer_explicit_types_function_returns = false +ij_javascript_prefer_explicit_types_vars_fields = false +ij_javascript_prefer_parameters_wrap = false +ij_javascript_property_prefix = +ij_javascript_reformat_c_style_comments = false +ij_javascript_space_after_colon = true +ij_javascript_space_after_comma = true +ij_javascript_space_after_dots_in_rest_parameter = false +ij_javascript_space_after_generator_mult = true +ij_javascript_space_after_property_colon = true +ij_javascript_space_after_quest = true +ij_javascript_space_after_type_colon = true +ij_javascript_space_after_unary_not = false +ij_javascript_space_before_async_arrow_lparen = true +ij_javascript_space_before_catch_keyword = true +ij_javascript_space_before_catch_left_brace = true +ij_javascript_space_before_catch_parentheses = true +ij_javascript_space_before_class_lbrace = true +ij_javascript_space_before_class_left_brace = true +ij_javascript_space_before_colon = true +ij_javascript_space_before_comma = false +ij_javascript_space_before_do_left_brace = true +ij_javascript_space_before_else_keyword = true +ij_javascript_space_before_else_left_brace = true +ij_javascript_space_before_finally_keyword = true +ij_javascript_space_before_finally_left_brace = true +ij_javascript_space_before_for_left_brace = true +ij_javascript_space_before_for_parentheses = true +ij_javascript_space_before_for_semicolon = false +ij_javascript_space_before_function_left_parenth = true +ij_javascript_space_before_generator_mult = false +ij_javascript_space_before_if_left_brace = true +ij_javascript_space_before_if_parentheses = true +ij_javascript_space_before_method_call_parentheses = false +ij_javascript_space_before_method_left_brace = true +ij_javascript_space_before_method_parentheses = false +ij_javascript_space_before_property_colon = false +ij_javascript_space_before_quest = true +ij_javascript_space_before_switch_left_brace = true +ij_javascript_space_before_switch_parentheses = true +ij_javascript_space_before_try_left_brace = true +ij_javascript_space_before_type_colon = false +ij_javascript_space_before_unary_not = false +ij_javascript_space_before_while_keyword = true +ij_javascript_space_before_while_left_brace = true +ij_javascript_space_before_while_parentheses = true +ij_javascript_spaces_around_additive_operators = true +ij_javascript_spaces_around_arrow_function_operator = true +ij_javascript_spaces_around_assignment_operators = true +ij_javascript_spaces_around_bitwise_operators = true +ij_javascript_spaces_around_equality_operators = true +ij_javascript_spaces_around_logical_operators = true +ij_javascript_spaces_around_multiplicative_operators = true +ij_javascript_spaces_around_relational_operators = true +ij_javascript_spaces_around_shift_operators = true +ij_javascript_spaces_around_unary_operator = false +ij_javascript_spaces_within_array_initializer_brackets = false +ij_javascript_spaces_within_brackets = false +ij_javascript_spaces_within_catch_parentheses = false +ij_javascript_spaces_within_for_parentheses = false +ij_javascript_spaces_within_if_parentheses = false +ij_javascript_spaces_within_imports = false +ij_javascript_spaces_within_interpolation_expressions = false +ij_javascript_spaces_within_method_call_parentheses = false +ij_javascript_spaces_within_method_parentheses = false +ij_javascript_spaces_within_object_literal_braces = false +ij_javascript_spaces_within_object_type_braces = true +ij_javascript_spaces_within_parentheses = false +ij_javascript_spaces_within_switch_parentheses = false +ij_javascript_spaces_within_type_assertion = false +ij_javascript_spaces_within_union_types = true +ij_javascript_spaces_within_while_parentheses = false +ij_javascript_special_else_if_treatment = true +ij_javascript_ternary_operation_signs_on_next_line = false +ij_javascript_ternary_operation_wrap = normal +ij_javascript_union_types_wrap = on_every_item +ij_javascript_use_chained_calls_group_indents = false +ij_javascript_use_double_quotes = true +ij_javascript_use_explicit_js_extension = auto +ij_javascript_use_path_mapping = always +ij_javascript_use_public_modifier = false +ij_javascript_use_semicolon_after_statement = true +ij_javascript_var_declaration_wrap = normal +ij_javascript_while_brace_force = always +ij_javascript_while_on_new_line = false +ij_javascript_wrap_comments = true + +[{*.ft,*.vm,*.vsl}] +ij_vtl_keep_indents_on_empty_lines = false + +[{*.gant,*.groovy,*.gy}] ij_groovy_align_group_field_declarations = false ij_groovy_align_multiline_array_initializer_expression = false ij_groovy_align_multiline_assignment = false ij_groovy_align_multiline_binary_operation = false ij_groovy_align_multiline_chained_methods = false ij_groovy_align_multiline_extends_list = false -ij_groovy_align_multiline_for = true +ij_groovy_align_multiline_for = false +ij_groovy_align_multiline_list_or_map = false ij_groovy_align_multiline_method_parentheses = false -ij_groovy_align_multiline_parameters = true +ij_groovy_align_multiline_parameters = false ij_groovy_align_multiline_parameters_in_calls = false -ij_groovy_align_multiline_resources = true +ij_groovy_align_multiline_resources = false ij_groovy_align_multiline_ternary_operation = false ij_groovy_align_multiline_throws_list = false +ij_groovy_align_named_args_in_map = false ij_groovy_align_throws_keyword = false ij_groovy_array_initializer_new_line_after_left_brace = false ij_groovy_array_initializer_right_brace_on_new_line = false -ij_groovy_array_initializer_wrap = off -ij_groovy_assert_statement_wrap = off -ij_groovy_assignment_wrap = off -ij_groovy_binary_operation_wrap = off +ij_groovy_array_initializer_wrap = normal +ij_groovy_assert_statement_wrap = normal +ij_groovy_assignment_wrap = normal +ij_groovy_binary_operation_wrap = normal ij_groovy_blank_lines_after_class_header = 0 ij_groovy_blank_lines_after_imports = 1 ij_groovy_blank_lines_after_package = 1 @@ -478,55 +848,75 @@ ij_groovy_blank_lines_before_imports = 1 ij_groovy_blank_lines_before_method_body = 0 ij_groovy_blank_lines_before_package = 0 ij_groovy_block_brace_style = end_of_line +ij_groovy_block_comment_add_space = false ij_groovy_block_comment_at_first_column = true ij_groovy_call_parameters_new_line_after_left_paren = false ij_groovy_call_parameters_right_paren_on_new_line = false -ij_groovy_call_parameters_wrap = off +ij_groovy_call_parameters_wrap = normal ij_groovy_catch_on_new_line = false ij_groovy_class_annotation_wrap = split_into_lines ij_groovy_class_brace_style = end_of_line -ij_groovy_do_while_brace_force = never +ij_groovy_class_count_to_use_import_on_demand = 999 +ij_groovy_do_while_brace_force = always ij_groovy_else_on_new_line = false -ij_groovy_enum_constants_wrap = off -ij_groovy_extends_keyword_wrap = off -ij_groovy_extends_list_wrap = off +ij_groovy_enable_groovydoc_formatting = true +ij_groovy_enum_constants_wrap = split_into_lines +ij_groovy_extends_keyword_wrap = normal +ij_groovy_extends_list_wrap = normal ij_groovy_field_annotation_wrap = split_into_lines ij_groovy_finally_on_new_line = false -ij_groovy_for_brace_force = never +ij_groovy_for_brace_force = always ij_groovy_for_statement_new_line_after_left_paren = false ij_groovy_for_statement_right_paren_on_new_line = false -ij_groovy_for_statement_wrap = off -ij_groovy_if_brace_force = never +ij_groovy_for_statement_wrap = normal +ij_groovy_ginq_general_clause_wrap_policy = 2 +ij_groovy_ginq_having_wrap_policy = 1 +ij_groovy_ginq_indent_having_clause = true +ij_groovy_ginq_indent_on_clause = true +ij_groovy_ginq_on_wrap_policy = 1 +ij_groovy_ginq_space_after_keyword = true +ij_groovy_if_brace_force = always +ij_groovy_import_annotation_wrap = 2 +ij_groovy_imports_layout = *,|,javax.**,java.**,|,$* ij_groovy_indent_case_from_switch = true -ij_groovy_keep_blank_lines_before_right_brace = 2 -ij_groovy_keep_blank_lines_in_code = 2 -ij_groovy_keep_blank_lines_in_declarations = 2 +ij_groovy_indent_label_blocks = true +ij_groovy_insert_inner_class_imports = false +ij_groovy_keep_blank_lines_before_right_brace = 1 +ij_groovy_keep_blank_lines_in_code = 1 +ij_groovy_keep_blank_lines_in_declarations = 1 ij_groovy_keep_control_statement_in_one_line = true ij_groovy_keep_first_column_comment = true ij_groovy_keep_indents_on_empty_lines = false ij_groovy_keep_line_breaks = true -ij_groovy_keep_multiple_expressions_in_one_line = false -ij_groovy_keep_simple_blocks_in_one_line = false +ij_groovy_keep_multiple_expressions_in_one_line = true +ij_groovy_keep_simple_blocks_in_one_line = true ij_groovy_keep_simple_classes_in_one_line = true ij_groovy_keep_simple_lambdas_in_one_line = true ij_groovy_keep_simple_methods_in_one_line = true +ij_groovy_label_indent_absolute = false +ij_groovy_label_indent_size = 0 ij_groovy_lambda_brace_style = end_of_line +ij_groovy_layout_static_imports_separately = true ij_groovy_line_comment_add_space = false +ij_groovy_line_comment_add_space_on_reformat = false ij_groovy_line_comment_at_first_column = true ij_groovy_method_annotation_wrap = split_into_lines ij_groovy_method_brace_style = end_of_line -ij_groovy_method_call_chain_wrap = off +ij_groovy_method_call_chain_wrap = normal ij_groovy_method_parameters_new_line_after_left_paren = false ij_groovy_method_parameters_right_paren_on_new_line = false -ij_groovy_method_parameters_wrap = off +ij_groovy_method_parameters_wrap = normal ij_groovy_modifier_list_wrap = false -ij_groovy_parameter_annotation_wrap = off +ij_groovy_names_count_to_use_import_on_demand = 999 +ij_groovy_packages_to_use_import_on_demand = * +ij_groovy_parameter_annotation_wrap = normal ij_groovy_parentheses_expression_new_line_after_left_paren = false ij_groovy_parentheses_expression_right_paren_on_new_line = false ij_groovy_prefer_parameters_wrap = false ij_groovy_resource_list_new_line_after_left_paren = false ij_groovy_resource_list_right_paren_on_new_line = false -ij_groovy_resource_list_wrap = off +ij_groovy_resource_list_wrap = normal +ij_groovy_space_after_assert_separator = true ij_groovy_space_after_colon = true ij_groovy_space_after_comma = true ij_groovy_space_after_comma_in_type_arguments = true @@ -535,10 +925,12 @@ ij_groovy_space_after_quest = true ij_groovy_space_after_type_cast = true ij_groovy_space_before_annotation_parameter_list = false ij_groovy_space_before_array_initializer_left_brace = false +ij_groovy_space_before_assert_separator = false ij_groovy_space_before_catch_keyword = true ij_groovy_space_before_catch_left_brace = true ij_groovy_space_before_catch_parentheses = true ij_groovy_space_before_class_left_brace = true +ij_groovy_space_before_closure_left_brace = true ij_groovy_space_before_colon = true ij_groovy_space_before_comma = false ij_groovy_space_before_do_left_brace = true @@ -555,6 +947,7 @@ ij_groovy_space_before_method_call_parentheses = false ij_groovy_space_before_method_left_brace = true ij_groovy_space_before_method_parentheses = false ij_groovy_space_before_quest = true +ij_groovy_space_before_record_parentheses = false ij_groovy_space_before_switch_left_brace = true ij_groovy_space_before_switch_parentheses = true ij_groovy_space_before_synchronized_left_brace = true @@ -564,6 +957,8 @@ ij_groovy_space_before_try_parentheses = true ij_groovy_space_before_while_keyword = true ij_groovy_space_before_while_left_brace = true ij_groovy_space_before_while_parentheses = true +ij_groovy_space_in_named_argument = true +ij_groovy_space_in_named_argument_before_colon = false ij_groovy_space_within_empty_array_initializer_braces = false ij_groovy_space_within_empty_method_call_parentheses = false ij_groovy_spaces_around_additive_operators = true @@ -573,6 +968,7 @@ ij_groovy_spaces_around_equality_operators = true ij_groovy_spaces_around_lambda_arrow = true ij_groovy_spaces_around_logical_operators = true ij_groovy_spaces_around_multiplicative_operators = true +ij_groovy_spaces_around_regex_operators = true ij_groovy_spaces_around_relational_operators = true ij_groovy_spaces_around_shift_operators = true ij_groovy_spaces_within_annotation_parentheses = false @@ -582,42 +978,110 @@ ij_groovy_spaces_within_brackets = false ij_groovy_spaces_within_cast_parentheses = false ij_groovy_spaces_within_catch_parentheses = false ij_groovy_spaces_within_for_parentheses = false +ij_groovy_spaces_within_gstring_injection_braces = false ij_groovy_spaces_within_if_parentheses = false +ij_groovy_spaces_within_list_or_map = false ij_groovy_spaces_within_method_call_parentheses = false ij_groovy_spaces_within_method_parentheses = false ij_groovy_spaces_within_parentheses = false ij_groovy_spaces_within_switch_parentheses = false ij_groovy_spaces_within_synchronized_parentheses = false ij_groovy_spaces_within_try_parentheses = false +ij_groovy_spaces_within_tuple_expression = false ij_groovy_spaces_within_while_parentheses = false ij_groovy_special_else_if_treatment = true -ij_groovy_ternary_operation_wrap = off -ij_groovy_throws_keyword_wrap = off -ij_groovy_throws_list_wrap = off -ij_groovy_variable_annotation_wrap = off -ij_groovy_while_brace_force = never +ij_groovy_ternary_operation_wrap = normal +ij_groovy_throws_keyword_wrap = normal +ij_groovy_throws_list_wrap = normal +ij_groovy_use_flying_geese_braces = false +ij_groovy_use_fq_class_names = false +ij_groovy_use_fq_class_names_in_javadoc = false +ij_groovy_use_relative_indents = false +ij_groovy_use_single_class_imports = true +ij_groovy_variable_annotation_wrap = normal +ij_groovy_while_brace_force = always ij_groovy_while_on_new_line = false +ij_groovy_wrap_chain_calls_after_dot = false ij_groovy_wrap_long_lines = false -[{*.jhm,*.xslt,*.xul,*.tagx,*.rng,*.xsl,*.xsd,*.jspx,*.ant,*.xml,*.tld,*.fxml,*.wsdl,*.jrxml,*.jnlp,*.pom}] -ij_xml_block_comment_at_first_column = true -ij_xml_keep_indents_on_empty_lines = false -ij_xml_line_comment_at_first_column = true +[{*.har,*.jsb2,*.jsb3,*.json,.babelrc,.eslintrc,.prettierrc,.stylelintrc,bowerrc,jest.config}] +indent_size = 2 +ij_json_array_wrapping = split_into_lines +ij_json_keep_blank_lines_in_code = 0 +ij_json_keep_indents_on_empty_lines = false +ij_json_keep_line_breaks = false +ij_json_keep_trailing_comma = false +ij_json_object_wrapping = split_into_lines +ij_json_property_alignment = do_not_align +ij_json_space_after_colon = true +ij_json_space_after_comma = true +ij_json_space_before_colon = false +ij_json_space_before_comma = false +ij_json_spaces_within_braces = false +ij_json_spaces_within_brackets = false +ij_json_wrap_long_lines = false + +[{*.htm,*.html,*.ng,*.peb,*.sht,*.shtm,*.shtml}] +ij_html_add_new_line_before_tags = body,div,p,form,h1,h2,h3 +ij_html_align_attributes = true +ij_html_align_text = false +ij_html_attribute_wrap = normal +ij_html_block_comment_add_space = false +ij_html_block_comment_at_first_column = true +ij_html_do_not_align_children_of_min_lines = 0 +ij_html_do_not_break_if_inline_tags = title,h1,h2,h3,h4,h5,h6,p +ij_html_do_not_indent_children_of_tags = html,body,thead,tbody,tfoot +ij_html_enforce_quotes = true +ij_html_inline_tags = a,abbr,acronym,b,basefont,bdo,big,br,cite,cite,code,dfn,em,font,i,img,input,kbd,label,q,s,samp,select,small,span,strike,strong,sub,sup,textarea,tt,u,var +ij_html_keep_blank_lines = 1 +ij_html_keep_indents_on_empty_lines = false +ij_html_keep_line_breaks = true +ij_html_keep_line_breaks_in_text = true +ij_html_keep_whitespaces = false +ij_html_keep_whitespaces_inside = span,pre,textarea +ij_html_line_comment_at_first_column = true +ij_html_new_line_after_last_attribute = never +ij_html_new_line_before_first_attribute = never +ij_html_quote_style = double +ij_html_remove_new_line_before_tags = br +ij_html_space_after_tag_name = false +ij_html_space_around_equality_in_attribute = false +ij_html_space_inside_empty_tag = false +ij_html_text_wrap = normal + +[{*.http,*.rest}] +indent_size = 0 +ij_continuation_indent_size = 4 +ij_http-request_call_parameters_wrap = normal +ij_http-request_method_parameters_wrap = split_into_lines +ij_http-request_space_before_comma = true +ij_http-request_spaces_around_assignment_operators = true + +[{*.jsf,*.jsp,*.jspf,*.tag,*.tagf,*.xjsp}] +ij_jsp_jsp_prefer_comma_separated_import_list = false +ij_jsp_keep_indents_on_empty_lines = false + +[{*.jspx,*.tagx}] +ij_jspx_keep_indents_on_empty_lines = false [{*.kt,*.kts}] ij_kotlin_align_in_columns_case_branch = false ij_kotlin_align_multiline_binary_operation = false ij_kotlin_align_multiline_extends_list = false ij_kotlin_align_multiline_method_parentheses = false -ij_kotlin_align_multiline_parameters = true +ij_kotlin_align_multiline_parameters = false ij_kotlin_align_multiline_parameters_in_calls = false -ij_kotlin_assignment_wrap = off +ij_kotlin_allow_trailing_comma = false +ij_kotlin_allow_trailing_comma_on_call_site = false +ij_kotlin_assignment_wrap = normal ij_kotlin_blank_lines_after_class_header = 0 ij_kotlin_blank_lines_around_block_when_branches = 0 +ij_kotlin_blank_lines_before_declaration_with_comment_or_annotation_on_separate_line = 1 +ij_kotlin_block_comment_add_space = false ij_kotlin_block_comment_at_first_column = true ij_kotlin_call_parameters_new_line_after_left_paren = false ij_kotlin_call_parameters_right_paren_on_new_line = false -ij_kotlin_call_parameters_wrap = off +ij_kotlin_call_parameters_wrap = normal ij_kotlin_catch_on_new_line = false ij_kotlin_class_annotation_wrap = split_into_lines ij_kotlin_continuation_indent_for_chained_calls = true @@ -628,30 +1092,34 @@ ij_kotlin_continuation_indent_in_if_conditions = true ij_kotlin_continuation_indent_in_parameter_lists = true ij_kotlin_continuation_indent_in_supertype_lists = true ij_kotlin_else_on_new_line = false -ij_kotlin_enum_constants_wrap = off -ij_kotlin_extends_list_wrap = off +ij_kotlin_enum_constants_wrap = split_into_lines +ij_kotlin_extends_list_wrap = normal ij_kotlin_field_annotation_wrap = split_into_lines ij_kotlin_finally_on_new_line = false ij_kotlin_if_rparen_on_new_line = false ij_kotlin_import_nested_classes = false +ij_kotlin_imports_layout = *,java.**,javax.**,kotlin.**,^ ij_kotlin_insert_whitespaces_in_simple_one_line_method = true -ij_kotlin_keep_blank_lines_before_right_brace = 2 -ij_kotlin_keep_blank_lines_in_code = 2 -ij_kotlin_keep_blank_lines_in_declarations = 2 +ij_kotlin_keep_blank_lines_before_right_brace = 1 +ij_kotlin_keep_blank_lines_in_code = 1 +ij_kotlin_keep_blank_lines_in_declarations = 1 ij_kotlin_keep_first_column_comment = true ij_kotlin_keep_indents_on_empty_lines = false ij_kotlin_keep_line_breaks = true ij_kotlin_lbrace_on_next_line = false +ij_kotlin_line_break_after_multiline_when_entry = true ij_kotlin_line_comment_add_space = false +ij_kotlin_line_comment_add_space_on_reformat = false ij_kotlin_line_comment_at_first_column = true ij_kotlin_method_annotation_wrap = split_into_lines -ij_kotlin_method_call_chain_wrap = off +ij_kotlin_method_call_chain_wrap = normal ij_kotlin_method_parameters_new_line_after_left_paren = false ij_kotlin_method_parameters_right_paren_on_new_line = false -ij_kotlin_method_parameters_wrap = off -ij_kotlin_name_count_to_use_star_import = 5 -ij_kotlin_name_count_to_use_star_import_for_members = 3 -ij_kotlin_parameter_annotation_wrap = off +ij_kotlin_method_parameters_wrap = normal +ij_kotlin_name_count_to_use_star_import = 2147483647 +ij_kotlin_name_count_to_use_star_import_for_members = 2147483647 +ij_kotlin_packages_to_use_import_on_demand = +ij_kotlin_parameter_annotation_wrap = normal ij_kotlin_space_after_comma = true ij_kotlin_space_after_extend_colon = true ij_kotlin_space_after_type_colon = true @@ -674,48 +1142,65 @@ ij_kotlin_spaces_around_range = false ij_kotlin_spaces_around_relational_operators = true ij_kotlin_spaces_around_unary_operator = false ij_kotlin_spaces_around_when_arrow = true -ij_kotlin_variable_annotation_wrap = off +ij_kotlin_variable_annotation_wrap = normal ij_kotlin_while_on_new_line = false ij_kotlin_wrap_elvis_expressions = 1 -ij_kotlin_wrap_expression_body_functions = 0 +ij_kotlin_wrap_expression_body_functions = 1 ij_kotlin_wrap_first_method_in_call_chain = false -[{*.shtm,*.htm,*.sht,*.shtml,*.html}] -ij_html_add_new_line_before_tags = body,div,p,form,h1,h2,h3 -ij_html_align_attributes = true -ij_html_align_text = false -ij_html_attribute_wrap = normal -ij_html_block_comment_at_first_column = true -ij_html_do_not_align_children_of_min_lines = 0 -ij_html_do_not_break_if_inline_tags = title,h1,h2,h3,h4,h5,h6,p -ij_html_do_not_indent_children_of_tags = html,body,thead,tbody,tfoot -ij_html_enforce_quotes = false -ij_html_inline_tags = a,abbr,acronym,b,basefont,bdo,big,br,cite,cite,code,dfn,em,font,i,img,input,kbd,label,q,s,samp,select,small,span,strike,strong,sub,sup,textarea,tt,u,var -ij_html_keep_blank_lines = 2 -ij_html_keep_indents_on_empty_lines = false -ij_html_keep_line_breaks = true -ij_html_keep_line_breaks_in_text = true -ij_html_keep_whitespaces = false -ij_html_keep_whitespaces_inside = span,pre,textarea -ij_html_line_comment_at_first_column = true -ij_html_new_line_after_last_attribute = never -ij_html_new_line_before_first_attribute = never -ij_html_quote_style = double -ij_html_remove_new_line_before_tags = br -ij_html_space_after_tag_name = false -ij_html_space_around_equality_in_attribute = false -ij_html_space_inside_empty_tag = false -ij_html_text_wrap = normal +[{*.markdown,*.md}] +ij_markdown_force_one_space_after_blockquote_symbol = true +ij_markdown_force_one_space_after_header_symbol = true +ij_markdown_force_one_space_after_list_bullet = true +ij_markdown_force_one_space_between_words = true +ij_markdown_format_tables = true +ij_markdown_insert_quote_arrows_on_wrap = true +ij_markdown_keep_indents_on_empty_lines = false +ij_markdown_keep_line_breaks_inside_text_blocks = true +ij_markdown_max_lines_around_block_elements = 1 +ij_markdown_max_lines_around_header = 1 +ij_markdown_max_lines_between_paragraphs = 1 +ij_markdown_min_lines_around_block_elements = 1 +ij_markdown_min_lines_around_header = 1 +ij_markdown_min_lines_between_paragraphs = 1 +ij_markdown_wrap_text_if_long = true +ij_markdown_wrap_text_inside_blockquotes = true -[{*.yml,*.yaml}] +[{*.pb,*.textproto}] indent_size = 2 -ij_continuation_indent_size = 2 +tab_width = 2 +ij_continuation_indent_size = 4 +ij_prototext_keep_blank_lines_in_code = 2 +ij_prototext_keep_indents_on_empty_lines = false +ij_prototext_keep_line_breaks = true +ij_prototext_space_after_colon = true +ij_prototext_space_after_comma = true +ij_prototext_space_before_colon = false +ij_prototext_space_before_comma = false +ij_prototext_spaces_within_braces = true +ij_prototext_spaces_within_brackets = false + +[{*.properties,spring.handlers,spring.schemas}] +ij_properties_align_group_field_declarations = false +ij_properties_keep_blank_lines = false +ij_properties_key_value_delimiter = equals +ij_properties_spaces_around_key_value_delimiter = false + +[{*.qute.htm,*.qute.html,*.qute.json,*.qute.txt,*.qute.yaml,*.qute.yml}] +ij_qute_keep_indents_on_empty_lines = false + +[{*.toml,Cargo.lock,Cargo.toml.orig,Gopkg.lock,Pipfile,poetry.lock}] +ij_toml_keep_indents_on_empty_lines = false + +[{*.yaml,*.yml}] +indent_size = 2 +ij_yaml_align_values_properties = do_not_align +ij_yaml_autoinsert_sequence_marker = true +ij_yaml_block_mapping_on_new_line = false +ij_yaml_indent_sequence_value = true ij_yaml_keep_indents_on_empty_lines = false ij_yaml_keep_line_breaks = true - -[{*.zsh,*.bash,*.sh}] -ij_shell_binary_ops_start_line = false -ij_shell_keep_column_alignment_padding = false -ij_shell_minify_program = false -ij_shell_redirect_followed_by_space = false -ij_shell_switch_cases_indented = false +ij_yaml_sequence_on_new_line = false +ij_yaml_space_before_colon = false +ij_yaml_spaces_within_braces = true +ij_yaml_spaces_within_brackets = true diff --git a/README.md b/README.md index a6c51d49..0ec10a33 100644 --- a/README.md +++ b/README.md @@ -78,7 +78,6 @@ AlpacaAPI alpacaAPI = new AlpacaAPI(oAuthToken); AlpacaAPI alpacaAPI = AlpacaAPI.builder() .withEndpointAPIType(EndpointAPIType.PAPER) .build(); - ``` Note that this library uses [OkHttp](https://square.github.io/okhttp/) as its HTTP client library which creates background threads to service requests. These threads persist even if the main thread exists so if you want to destroy these threads when you're done using [`AlpacaAPI`](src/main/java/net/jacobpeterson/alpaca/AlpacaAPI.java) so your program can exit without calling `System.exit()`, use the following snippet: From 92656f777c005302bb3245bd0a38e6ecfa6918ca Mon Sep 17 00:00:00 2001 From: Jacob Peterson <14127217+Petersoj@users.noreply.github.com> Date: Sun, 20 Aug 2023 16:37:54 -0600 Subject: [PATCH 198/271] Update to 9.2.0 --- README.md | 4 ++-- build.gradle | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 0ec10a33..82630ee6 100644 --- a/README.md +++ b/README.md @@ -17,7 +17,7 @@ If you are using Gradle as your build tool, add the following dependency to your ``` dependencies { - implementation group: 'net.jacobpeterson', name: 'alpaca-java', version: '9.1.4' + implementation group: 'net.jacobpeterson', name: 'alpaca-java', version: '9.2.0' } ``` @@ -27,7 +27,7 @@ If you are using Maven as your build tool, add the following dependency to your net.jacobpeterson alpaca-java - 9.1.4 + 9.2.0 compile ``` diff --git a/build.gradle b/build.gradle index a40691fa..a861d055 100644 --- a/build.gradle +++ b/build.gradle @@ -29,7 +29,7 @@ apply plugin: 'jsonschema2pojo' archivesBaseName = 'alpaca-java' group = 'net.jacobpeterson' -version = '9.1.4-SNAPSHOT' +version = '9.2.0-SNAPSHOT' repositories { mavenCentral() From 5d1f23126fb83cec4630630698fcba92fc367d56 Mon Sep 17 00:00:00 2001 From: Jc Date: Tue, 7 Nov 2023 16:19:17 -0500 Subject: [PATCH 199/271] Fix --- .../alpaca/rest/endpoint/orders/OrdersEndpoint.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/net/jacobpeterson/alpaca/rest/endpoint/orders/OrdersEndpoint.java b/src/main/java/net/jacobpeterson/alpaca/rest/endpoint/orders/OrdersEndpoint.java index 0ad4a66e..b2a0563d 100644 --- a/src/main/java/net/jacobpeterson/alpaca/rest/endpoint/orders/OrdersEndpoint.java +++ b/src/main/java/net/jacobpeterson/alpaca/rest/endpoint/orders/OrdersEndpoint.java @@ -251,7 +251,7 @@ public Order requestMarketOrder(String symbol, Integer quantity, OrderSide side, */ public Order requestFractionalMarketOrder(String symbol, Double quantity, OrderSide side) throws AlpacaClientException { - return requestOrder(symbol, quantity, null, side, OrderType.MARKET, OrderTimeInForce.GOOD_UNTIL_CANCELLED, null, + return requestOrder(symbol, quantity, null, side, OrderType.MARKET, OrderTimeInForce.DAY, null, null, null, null, null, null, null, null, null, null); } From 8d9ca1434d3bb5f29ca68eac16267d788021140e Mon Sep 17 00:00:00 2001 From: gretard Date: Tue, 2 Jan 2024 21:00:58 +0200 Subject: [PATCH 200/271] Add corporate actions endpoint for #118 --- build.gradle | 1 + .../common/announcement.json | 53 ++++++++ .../net/jacobpeterson/alpaca/AlpacaAPI.java | 9 ++ .../CorporateActionsEndpoint.java | 121 ++++++++++++++++++ .../alpaca/test/mock/AlpacaAPITest.java | 13 ++ .../CorporateActionsEndpointTest.java | 104 +++++++++++++++ 6 files changed, 301 insertions(+) create mode 100644 schema_json/endpoint/corporate_actions/common/announcement.json create mode 100644 src/main/java/net/jacobpeterson/alpaca/rest/endpoint/corporateactions/CorporateActionsEndpoint.java create mode 100644 src/test/java/net/jacobpeterson/alpaca/test/mock/corporateactions/CorporateActionsEndpointTest.java diff --git a/build.gradle b/build.gradle index a861d055..7c65cc3d 100644 --- a/build.gradle +++ b/build.gradle @@ -53,6 +53,7 @@ dependencies { testImplementation group: 'org.slf4j', name: 'slf4j-simple', version: '1.7.36' testImplementation group: 'org.junit.jupiter', name: 'junit-jupiter', version: '5.8.2' testImplementation group: 'org.mockito', name: 'mockito-core', version: '4.6.1' + testImplementation group: 'com.github.gmazzo.okhttp.mock', name: 'mock-client', version: '2.0.0' } // Exclude any SLF4j-implementation transitive dependencies so users can use a logging framework of their choice diff --git a/schema_json/endpoint/corporate_actions/common/announcement.json b/schema_json/endpoint/corporate_actions/common/announcement.json new file mode 100644 index 00000000..14c8e66e --- /dev/null +++ b/schema_json/endpoint/corporate_actions/common/announcement.json @@ -0,0 +1,53 @@ +{ + "type": "object", + "title": "See Corporate announcement.", + "properties": { + "id": { + "existingJavaType": "java.lang.String" + }, + "corporate_actions_id": { + "existingJavaType": "java.lang.String" + }, + "ca_type": { + "existingJavaType": "java.lang.String" + }, + "ca_sub_type": { + "existingJavaType": "java.lang.String" + }, + "initiating_symbol": { + "existingJavaType": "java.lang.String" + }, + "initiating_original_cusip": { + "existingJavaType": "java.lang.String" + }, + "target_symbol": { + "target_symbol": "java.lang.String" + }, + "target_original_cusip": { + "target_original_cusip": "java.lang.String" + }, + "declaration_date": { + "declaration_date": "java.lang.String" + }, + "expiration_date": { + "declaration_date": "java.lang.String" + }, + "record_date": { + "declaration_date": "java.lang.String" + }, + "payable_date": { + "declaration_date": "java.lang.String" + }, + "cash": { + "declaration_date": "java.lang.String" + }, + "old_rate": { + "declaration_date": "java.lang.String" + }, + "new_rate": { + "declaration_date": "java.lang.String" + } + + + } +} diff --git a/src/main/java/net/jacobpeterson/alpaca/AlpacaAPI.java b/src/main/java/net/jacobpeterson/alpaca/AlpacaAPI.java index d1e75376..922ddd10 100644 --- a/src/main/java/net/jacobpeterson/alpaca/AlpacaAPI.java +++ b/src/main/java/net/jacobpeterson/alpaca/AlpacaAPI.java @@ -12,6 +12,7 @@ import net.jacobpeterson.alpaca.rest.endpoint.assets.AssetsEndpoint; import net.jacobpeterson.alpaca.rest.endpoint.calendar.CalendarEndpoint; import net.jacobpeterson.alpaca.rest.endpoint.clock.ClockEndpoint; +import net.jacobpeterson.alpaca.rest.endpoint.corporateactions.CorporateActionsEndpoint; import net.jacobpeterson.alpaca.rest.endpoint.marketdata.crypto.CryptoMarketDataEndpoint; import net.jacobpeterson.alpaca.rest.endpoint.marketdata.stock.StockMarketDataEndpoint; import net.jacobpeterson.alpaca.rest.endpoint.orders.OrdersEndpoint; @@ -67,6 +68,7 @@ public class AlpacaAPI { private final CryptoMarketDataWebsocket cryptoMarketDataWebsocket; private final StockMarketDataWebsocket stockMarketDataWebsocket; + private final CorporateActionsEndpoint corporateActionsEndpoint; /** * Instantiates a new {@link AlpacaAPI} using properties specified in alpaca.properties file (or their * associated defaults). @@ -197,6 +199,7 @@ public AlpacaAPI(OkHttpClient okHttpClient, String keyID, String secretKey, Stri new CryptoMarketDataWebsocket(okHttpClient, keyID, secretKey); stockMarketDataWebsocket = stockDataClient == null ? null : new StockMarketDataWebsocket(okHttpClient, dataAPIType, keyID, secretKey); + corporateActionsEndpoint = new CorporateActionsEndpoint(brokerClient); } /** @@ -320,6 +323,12 @@ public AlpacaClient getStockDataClient() { return stockDataClient; } + /** + * @return the {@link CorporateActionsEndpoint} + */ + public CorporateActionsEndpoint corporateActions() { + return corporateActionsEndpoint; + } /** * Creates a {@link Builder} for {@link AlpacaAPI}. * diff --git a/src/main/java/net/jacobpeterson/alpaca/rest/endpoint/corporateactions/CorporateActionsEndpoint.java b/src/main/java/net/jacobpeterson/alpaca/rest/endpoint/corporateactions/CorporateActionsEndpoint.java new file mode 100644 index 00000000..f467c4f0 --- /dev/null +++ b/src/main/java/net/jacobpeterson/alpaca/rest/endpoint/corporateactions/CorporateActionsEndpoint.java @@ -0,0 +1,121 @@ +package net.jacobpeterson.alpaca.rest.endpoint.corporateactions; + +import com.google.gson.reflect.TypeToken; +import net.jacobpeterson.alpaca.model.endpoint.corporateactions.common.Announcement; +import net.jacobpeterson.alpaca.rest.AlpacaClient; +import net.jacobpeterson.alpaca.rest.AlpacaClientException; +import net.jacobpeterson.alpaca.rest.endpoint.AlpacaEndpoint; +import okhttp3.HttpUrl; +import okhttp3.Request; + +import java.lang.reflect.Type; +import java.time.LocalDate; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +import static com.google.common.base.Preconditions.checkNotNull; + +/** + * {@link AlpacaEndpoint} for + * Corporate actions + * Endpoint. + */ +public class CorporateActionsEndpoint extends AlpacaEndpoint { + + private static final Type ANNOUNCEMENT_ARRAYLIST_TYPE = new TypeToken>() {}.getType(); + + private static final List ALL_CA_TYPES = Arrays.asList("dividend", "merger", "spinoff", "split"); + + /** + * Instantiates a new {@link AlpacaEndpoint}. + * + * @param alpacaClient the {@link AlpacaClient} + */ + public CorporateActionsEndpoint(AlpacaClient alpacaClient) { + super(alpacaClient, "corporate_actions/announcements"); + } + + /** + * Gets a {@link Announcement} of the requested id. + * @param id - announcement id to query for + * @return the {@link Announcement} + * @throws AlpacaClientException thrown for {@link AlpacaClientException}s + */ + public Announcement getAnnouncement(String id) throws AlpacaClientException { + checkNotNull(id); + HttpUrl.Builder urlBuilder = alpacaClient.urlBuilder() + .addPathSegment(endpointPathSegment) + .addPathSegment(id); + Request request = alpacaClient.requestBuilder(urlBuilder.build()) + .get() + .build(); + return alpacaClient.requestObject(request, Announcement.class); + + } + + /** + * Gets list of all {@link Announcement} within last 90 days + * @return a {@link List} of {@link Announcement} + * @throws AlpacaClientException thrown for {@link AlpacaClientException}s + */ + public List getAnnouncements() throws AlpacaClientException { + return this.getAnnouncements(ALL_CA_TYPES, LocalDate.now().minusDays(90), LocalDate.now(), null, null, null); + } + + /** + * Gets list of all {@link Announcement} within last 90 days for specific symbol + * @param symbol the symbol to query for + * @return a {@link List} of {@link Announcement} + * @throws AlpacaClientException thrown for {@link AlpacaClientException}s + */ + public List getAnnouncements(String symbol) throws AlpacaClientException { + return this.getAnnouncements(ALL_CA_TYPES, LocalDate.now().minusDays(90), LocalDate.now(), symbol, null, null); + } + + /** + * Gets list of all {@link Announcement} for specific query + * @param caTypes - a list of Dividend, Merger, Spinoff, or Split + * @param since - the start (inclusive) of the date range when searching corporate action announcements. This should follow the YYYY-MM-DD format. The date range is limited to 90 days + * @param until - the end (inclusive) of the date range when searching corporate action announcements. This should follow the YYYY-MM-DD format. The date range is limited to 90 days + * @param symbol - the symbol of the company initiating the announcement + * @param cusip - the CUSIP of the company initiating the announcement + * @param dateType - one of declaration_date, ex_date, record_date, or payable_date + * @return a {@link List} of {@link Announcement} + * @throws AlpacaClientException thrown for {@link AlpacaClientException}s + */ + public List getAnnouncements( + List caTypes, LocalDate since, LocalDate until, + String symbol, + String cusip, + String dateType + ) + throws AlpacaClientException { + HttpUrl.Builder urlBuilder = alpacaClient.urlBuilder() + .addPathSegment(endpointPathSegment); + checkNotNull(caTypes); + checkNotNull(since); + checkNotNull(until); + + + urlBuilder.addQueryParameter("ca_types", String.join(",", caTypes)); + + urlBuilder.addQueryParameter("since", since.toString()); + urlBuilder.addQueryParameter("until", until.toString()); + + if (symbol != null) { + urlBuilder.addQueryParameter("symbol", symbol); + } + if (cusip != null) { + urlBuilder.addQueryParameter("cusip", cusip); + } + if (dateType !=null) { + urlBuilder.addQueryParameter("date_type", dateType); + } + Request request = alpacaClient.requestBuilder(urlBuilder.build()) + .get() + .build(); + return alpacaClient.requestObject(request, ANNOUNCEMENT_ARRAYLIST_TYPE); + + } +} diff --git a/src/test/java/net/jacobpeterson/alpaca/test/mock/AlpacaAPITest.java b/src/test/java/net/jacobpeterson/alpaca/test/mock/AlpacaAPITest.java index d5f4f320..0fbc722a 100644 --- a/src/test/java/net/jacobpeterson/alpaca/test/mock/AlpacaAPITest.java +++ b/src/test/java/net/jacobpeterson/alpaca/test/mock/AlpacaAPITest.java @@ -6,6 +6,8 @@ import okhttp3.OkHttpClient; import org.junit.jupiter.api.Test; +import static org.junit.jupiter.api.Assertions.assertNotNull; + /** * {@link AlpacaAPITest} tests {@link AlpacaAPI} using mocked objects with Mockito. */ @@ -71,4 +73,15 @@ public void testAlpacaAPIConstructor_okHttpClient_keyID_secret_oAuthToken_endpoi new AlpacaAPI(okHttpClient, null, null, oAuthToken, EndpointAPIType.PAPER, DataAPIType.IEX); new AlpacaAPI(okHttpClient, keyID, secret, null, EndpointAPIType.LIVE, DataAPIType.SIP); } + + @Test + public void testAlpacaAPIConstructor_corporateActionsEndpoint() { + OkHttpClient okHttpClient = new OkHttpClient(); + String keyID = "ABCDEFGHIJKLM"; + String secret = "NOPQURSTUVWXYZ"; + + AlpacaAPI api = new AlpacaAPI(okHttpClient, keyID, secret, null, EndpointAPIType.LIVE, DataAPIType.SIP); + + assertNotNull(api.corporateActions()); + } } diff --git a/src/test/java/net/jacobpeterson/alpaca/test/mock/corporateactions/CorporateActionsEndpointTest.java b/src/test/java/net/jacobpeterson/alpaca/test/mock/corporateactions/CorporateActionsEndpointTest.java new file mode 100644 index 00000000..fd2dc0ae --- /dev/null +++ b/src/test/java/net/jacobpeterson/alpaca/test/mock/corporateactions/CorporateActionsEndpointTest.java @@ -0,0 +1,104 @@ +package net.jacobpeterson.alpaca.test.mock.corporateactions; + +import net.jacobpeterson.alpaca.model.endpoint.corporateactions.common.Announcement; +import net.jacobpeterson.alpaca.rest.AlpacaClient; +import net.jacobpeterson.alpaca.rest.AlpacaClientException; +import net.jacobpeterson.alpaca.rest.endpoint.corporateactions.CorporateActionsEndpoint; +import okhttp3.MediaType; +import okhttp3.OkHttpClient; +import okhttp3.ResponseBody; +import okhttp3.mock.MockInterceptor; +import org.junit.jupiter.api.Test; + +import java.time.LocalDate; +import java.util.ArrayList; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertThrows; + +public class CorporateActionsEndpointTest { + + @Test + void getAnnouncements_givenSymbol_shouldReturnData() throws AlpacaClientException { + CorporateActionsEndpoint newsEndpoint = setupEndpoint(mockMultipleAnnouncementsResponse()); + + java.util.List result = newsEndpoint.getAnnouncements("K"); + + assertNotNull(result); + assertEquals(3, result.size()); + + } + + @Test + void getAnnouncements_shouldReturnData() throws AlpacaClientException { + CorporateActionsEndpoint newsEndpoint = setupEndpoint(mockMultipleAnnouncementsResponse()); + + java.util.List result = newsEndpoint.getAnnouncements(); + + assertNotNull(result); + assertEquals(3, result.size()); + + } + + @Test + void getAnnouncements_validation_caTypes() { + CorporateActionsEndpoint newsEndpoint = setupEndpoint(mockMultipleAnnouncementsResponse()); + + assertThrows(Exception.class, () -> newsEndpoint.getAnnouncements(null, LocalDate.now(), LocalDate.now(), null, null, null)); + } + + @Test + void getAnnouncements_validation_since() { + CorporateActionsEndpoint newsEndpoint = setupEndpoint(mockMultipleAnnouncementsResponse()); + + assertThrows(Exception.class, () -> newsEndpoint.getAnnouncements(new ArrayList<>(), null, LocalDate.now(), null, null, null)); + } + + @Test + void getAnnouncements_validation_until() { + CorporateActionsEndpoint newsEndpoint = setupEndpoint(mockMultipleAnnouncementsResponse()); + + assertThrows(Exception.class, () -> newsEndpoint.getAnnouncements(new ArrayList<>(), LocalDate.now(), null, null, null, null)); + } + + @Test + void getAnnouncement_validation_id() { + CorporateActionsEndpoint newsEndpoint = setupEndpoint(mockSingleAnnouncementResponse()); + + assertThrows(Exception.class, () -> newsEndpoint.getAnnouncement(null)); + } + + @Test + void getAnnouncement_shouldReturnData() throws AlpacaClientException { + CorporateActionsEndpoint newsEndpoint = setupEndpoint(mockSingleAnnouncementResponse()); + + Announcement announcement = newsEndpoint.getAnnouncement("1"); + + assertNotNull(announcement); + assertEquals(announcement.getId(), "cd0cb768-889f-47a1-a94b-1438017dad77"); + } + + private static CorporateActionsEndpoint setupEndpoint(String response) { + MockInterceptor interceptor = new MockInterceptor(); + interceptor.addRule() + .respond(200) + .body(ResponseBody.create(response, MediaType.parse("application/json"))); + + OkHttpClient client = new OkHttpClient.Builder() + .addInterceptor(interceptor) + .build(); + + AlpacaClient api = new AlpacaClient(client, "token", "test", "/v1"); + + return new CorporateActionsEndpoint(api); + } + + private static String mockMultipleAnnouncementsResponse() { + return "[{\"id\":\"cd0cb768-889f-47a1-a94b-1438017dad77\",\"corporate_action_id\":\"2642471\",\"ca_type\":\"spinoff\",\"ca_sub_type\":\"spinoff\",\"initiating_symbol\":\"KLG\",\"initiating_original_cusip\":\"92942W107\",\"target_symbol\":\"K\",\"target_original_cusip\":\"487836108\",\"effective_date\":\"2023-09-08\",\"ex_date\":\"2023-10-02\",\"record_date\":\"2023-09-21\",\"payable_date\":\"2023-10-02\",\"cash\":\"0\",\"old_rate\":\"1\",\"new_rate\":\"0.25\"},{\"id\":\"3a7fe233-7766-4f70-915f-9d1a5258eaa9\",\"corporate_action_id\":\"487836108_AD23\",\"ca_type\":\"dividend\",\"ca_sub_type\":\"cash\",\"initiating_symbol\":\"K\",\"initiating_original_cusip\":\"487836108\",\"target_symbol\":\"K\",\"target_original_cusip\":\"487836108\",\"declaration_date\":\"2023-09-19\",\"effective_date\":\"2023-09-21\",\"ex_date\":\"2023-10-02\",\"record_date\":\"2023-09-21\",\"payable_date\":\"2023-10-02\",\"cash\":\"0\",\"old_rate\":\"1\",\"new_rate\":\"1\"},{\"id\":\"ddf64bcb-fa76-4472-a98b-237f220ea38b\",\"corporate_action_id\":\"487836108_AE23\",\"ca_type\":\"dividend\",\"ca_sub_type\":\"cash\",\"initiating_symbol\":\"K\",\"initiating_original_cusip\":\"487836108\",\"target_symbol\":\"K\",\"target_original_cusip\":\"487836108\",\"declaration_date\":\"2023-11-29\",\"effective_date\":\"2023-12-01\",\"ex_date\":\"2023-11-30\",\"record_date\":\"2023-12-01\",\"payable_date\":\"2023-12-15\",\"cash\":\"0.56\",\"old_rate\":\"1\",\"new_rate\":\"1\"}]"; + } + + private static String mockSingleAnnouncementResponse() { + return "{\"id\":\"cd0cb768-889f-47a1-a94b-1438017dad77\",\"corporate_action_id\":\"2642471\",\"ca_type\":\"spinoff\",\"ca_sub_type\":\"spinoff\",\"initiating_symbol\":\"KLG\",\"initiating_original_cusip\":\"92942W107\",\"target_symbol\":\"K\",\"target_original_cusip\":\"487836108\",\"effective_date\":\"2023-09-08\",\"ex_date\":\"2023-10-02\",\"record_date\":\"2023-09-21\",\"payable_date\":\"2023-10-02\",\"cash\":\"0\",\"old_rate\":\"1\",\"new_rate\":\"0.25\"}"; + } +} From 6d5a0436da2de9d6ebadeb5fad76f820a6ea0545 Mon Sep 17 00:00:00 2001 From: gretard Date: Mon, 8 Jan 2024 20:23:20 +0200 Subject: [PATCH 201/271] Add news actions endpoints for #118 --- README.md | 33 +++++ .../control/subscriptions_message.json | 4 + .../enums/market_data_message_type.json | 6 +- .../market_data/news/common/image.json | 13 ++ .../market_data/news/common/news_article.json | 39 ++++++ .../market_data/news/news_response.json | 12 ++ .../news/realtime/news_message.json | 40 ++++++ .../net/jacobpeterson/alpaca/AlpacaAPI.java | 32 ++++- .../marketdata/news/NewsEndpoint.java | 120 ++++++++++++++++++ .../marketdata/MarketDataWebsocket.java | 32 +++-- .../MarketDataWebsocketInterface.java | 11 +- .../news/NewsMarketDataWebsocket.java | 15 +++ .../CorporateActionsEndpointTest.java | 11 +- .../marketdata/news/NewsEndpointTest.java | 111 ++++++++++++++++ 14 files changed, 461 insertions(+), 18 deletions(-) create mode 100644 schema_json/endpoint/market_data/news/common/image.json create mode 100644 schema_json/endpoint/market_data/news/common/news_article.json create mode 100644 schema_json/endpoint/market_data/news/news_response.json create mode 100644 schema_json/endpoint/market_data/news/realtime/news_message.json create mode 100644 src/main/java/net/jacobpeterson/alpaca/rest/endpoint/marketdata/news/NewsEndpoint.java create mode 100644 src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/news/NewsMarketDataWebsocket.java create mode 100644 src/test/java/net/jacobpeterson/alpaca/test/mock/marketdata/news/NewsEndpointTest.java diff --git a/README.md b/README.md index 82630ee6..c483ee2d 100644 --- a/README.md +++ b/README.md @@ -495,6 +495,39 @@ alpacaAPI.cryptoMarketDataStreaming().subscribe( Arrays.asList("*")); ``` +## [`NewsMarketDataWebsocket`](src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/news/NewsMarketDataWebsocket.java) +Alpaca also offers news websocket streaming for symbols. + +The usage is identical to the [`StockMarketDataWebsocket`](https://github.com/Petersoj/alpaca-java/#stockmarketdatawebsocket) usage except that accessing the websocket instance via the `AlpacaAPI` instance is done using: `alpacaAPI.newsMarketDataStreaming()` instead of `alpacaAPI.stockMarketDataStreaming()`. + +Example usage: +```java +MarketDataListener marketDataListener = (messageType, message) -> + System.out.printf("%s: %s\n", messageType.name(), message); + +MarketDataWebsocketInterface newsWebsocket = alpacaAPI.newsMarketDataStreaming(); + +newsWebsocket.setListener(marketDataListener); +newsWebsocket.subscribeToControl( + MarketDataMessageType.SUCCESS, + MarketDataMessageType.SUBSCRIPTION, + MarketDataMessageType.ERROR); + +newsWebsocket.connect(); +newsWebsocket.waitForAuthorization(5, TimeUnit.SECONDS); +if (!newsWebsocket.isValid()) { + System.out.println("Websocket not valid!"); + return; +} +newsWebsocket.subscribe(null, null, null, Arrays.asList("*")); + +// Wait a few seconds +Thread.sleep(5000); + +// Manually disconnect the websocket +newsWebsocket.disconnect(); +``` + # Building To build this project yourself, clone this repository and run: ``` diff --git a/schema_json/endpoint/market_data/common/realtime/control/subscriptions_message.json b/schema_json/endpoint/market_data/common/realtime/control/subscriptions_message.json index c302083a..b44cbb85 100644 --- a/schema_json/endpoint/market_data/common/realtime/control/subscriptions_message.json +++ b/schema_json/endpoint/market_data/common/realtime/control/subscriptions_message.json @@ -16,6 +16,10 @@ "bars": { "existingJavaType": "java.util.ArrayList", "title": "The {@link java.util.ArrayList} of subscribed bars." + }, + "news": { + "existingJavaType": "java.util.ArrayList", + "title": "The {@link java.util.ArrayList} of subscribed news." } } } diff --git a/schema_json/endpoint/market_data/common/realtime/enums/market_data_message_type.json b/schema_json/endpoint/market_data/common/realtime/enums/market_data_message_type.json index f55a5190..6999050d 100644 --- a/schema_json/endpoint/market_data/common/realtime/enums/market_data_message_type.json +++ b/schema_json/endpoint/market_data/common/realtime/enums/market_data_message_type.json @@ -7,7 +7,8 @@ "subscription", "t", "q", - "b" + "b", + "n" ], "javaEnums": [ { @@ -27,6 +28,9 @@ }, { "name": "BAR" + }, + { + "name": "NEWS" } ] } diff --git a/schema_json/endpoint/market_data/news/common/image.json b/schema_json/endpoint/market_data/news/common/image.json new file mode 100644 index 00000000..2f3bf607 --- /dev/null +++ b/schema_json/endpoint/market_data/news/common/image.json @@ -0,0 +1,13 @@ +{ + "type": "object", + "title": "See News.", + "properties": { + "size": { + "existingJavaType": "java.lang.String" + }, + "url": { + "existingJavaType": "java.lang.String" + } + } + } + \ No newline at end of file diff --git a/schema_json/endpoint/market_data/news/common/news_article.json b/schema_json/endpoint/market_data/news/common/news_article.json new file mode 100644 index 00000000..ee80a6d7 --- /dev/null +++ b/schema_json/endpoint/market_data/news/common/news_article.json @@ -0,0 +1,39 @@ +{ + "type": "object", + "title": "See News.", + "properties": { + "author": { + "existingJavaType": "java.lang.String" + }, + "content": { + "existingJavaType": "java.lang.String" + }, + "created_at": { + "existingJavaType": "java.time.ZonedDateTime" + }, + "headline": { + "existingJavaType": "java.lang.String" + }, + "id": { + "existingJavaType": "java.lang.Long" + }, + "images": { + "existingJavaType": "java.util.ArrayList" + }, + "source": { + "existingJavaType": "java.lang.String" + }, + "summary": { + "existingJavaType": "java.lang.String" + }, + "symbols": { + "existingJavaType": "java.util.ArrayList" + }, + "updated_at": { + "existingJavaType": "java.time.ZonedDateTime" + }, + "url": { + "existingJavaType": "java.lang.String" + } + } + } diff --git a/schema_json/endpoint/market_data/news/news_response.json b/schema_json/endpoint/market_data/news/news_response.json new file mode 100644 index 00000000..b25ed7f9 --- /dev/null +++ b/schema_json/endpoint/market_data/news/news_response.json @@ -0,0 +1,12 @@ +{ + "type": "object", + "title": "See News.", + "properties": { + "news": { + "existingJavaType": "java.util.ArrayList" + }, + "next_page_token": { + "existingJavaType": "java.lang.String" + } + } +} diff --git a/schema_json/endpoint/market_data/news/realtime/news_message.json b/schema_json/endpoint/market_data/news/realtime/news_message.json new file mode 100644 index 00000000..bee011fe --- /dev/null +++ b/schema_json/endpoint/market_data/news/realtime/news_message.json @@ -0,0 +1,40 @@ +{ + "type": "object", + "title": "See News.", + "extends": { + "existingJavaType": "net.jacobpeterson.alpaca.model.endpoint.marketdata.common.realtime.MarketDataMessage" + }, + "properties": { + "headline": { + "existingJavaType": "java.lang.String" + }, + "created_at": { + "existingJavaType": "java.time.ZonedDateTime" + }, + "updated_at": { + "existingJavaType": "java.time.ZonedDateTime" + }, + "author": { + "existingJavaType": "java.lang.String" + }, + "summary": { + "existingJavaType": "java.lang.String" + }, + "content": { + "existingJavaType": "java.lang.String" + }, + + "url": { + "existingJavaType": "java.lang.String" + }, + "symbols": { + "existingJavaType": "java.util.ArrayList" + }, + "source": { + "existingJavaType": "java.lang.String" + }, + "id": { + "existingJavaType": "java.lang.Long" + } + } + } diff --git a/src/main/java/net/jacobpeterson/alpaca/AlpacaAPI.java b/src/main/java/net/jacobpeterson/alpaca/AlpacaAPI.java index 922ddd10..4292e85d 100644 --- a/src/main/java/net/jacobpeterson/alpaca/AlpacaAPI.java +++ b/src/main/java/net/jacobpeterson/alpaca/AlpacaAPI.java @@ -23,6 +23,8 @@ import net.jacobpeterson.alpaca.websocket.marketdata.MarketDataWebsocketInterface; import net.jacobpeterson.alpaca.websocket.marketdata.crypto.CryptoMarketDataWebsocket; import net.jacobpeterson.alpaca.websocket.marketdata.stock.StockMarketDataWebsocket; +import net.jacobpeterson.alpaca.rest.endpoint.marketdata.news.NewsEndpoint; +import net.jacobpeterson.alpaca.websocket.marketdata.news.NewsMarketDataWebsocket; import net.jacobpeterson.alpaca.websocket.streaming.StreamingWebsocket; import net.jacobpeterson.alpaca.websocket.streaming.StreamingWebsocketInterface; import okhttp3.OkHttpClient; @@ -46,11 +48,13 @@ public class AlpacaAPI { private static final String VERSION_2_PATH_SEGMENT = "v2"; private static final String VERSION_1_BETA_3_PATH_SEGMENT = "v1beta3"; + private static final String VERSION_1_BETA_1_PATH_SEGMENT = "v1beta1"; private final OkHttpClient okHttpClient; private final AlpacaClient brokerClient; private final AlpacaClient cryptoDataClient; private final AlpacaClient stockDataClient; + private final AlpacaClient newsDataClient; // Ordering of fields/methods below are analogous to the ordering in the Alpaca documentation private final AccountEndpoint accountEndpoint; private final CryptoMarketDataEndpoint cryptoMarketDataEndpoint; @@ -67,8 +71,10 @@ public class AlpacaAPI { private final StreamingWebsocket streamingWebsocket; private final CryptoMarketDataWebsocket cryptoMarketDataWebsocket; private final StockMarketDataWebsocket stockMarketDataWebsocket; - private final CorporateActionsEndpoint corporateActionsEndpoint; + private final NewsMarketDataWebsocket newsMarketDataWebsocket; + private final NewsEndpoint newsEndpoint; + /** * Instantiates a new {@link AlpacaAPI} using properties specified in alpaca.properties file (or their * associated defaults). @@ -175,10 +181,12 @@ public AlpacaAPI(OkHttpClient okHttpClient, String keyID, String secretKey, Stri brokerHostSubdomain, VERSION_2_PATH_SEGMENT); cryptoDataClient = new AlpacaClient(okHttpClient, keyID, secretKey, "data", VERSION_1_BETA_3_PATH_SEGMENT); stockDataClient = new AlpacaClient(okHttpClient, keyID, secretKey, "data", VERSION_2_PATH_SEGMENT); + newsDataClient = new AlpacaClient(okHttpClient, keyID, secretKey, "data", VERSION_1_BETA_1_PATH_SEGMENT); } else { brokerClient = new AlpacaClient(okHttpClient, oAuthToken, brokerHostSubdomain, VERSION_2_PATH_SEGMENT); cryptoDataClient = null; stockDataClient = null; + newsDataClient = null; } accountEndpoint = new AccountEndpoint(brokerClient); @@ -200,6 +208,8 @@ public AlpacaAPI(OkHttpClient okHttpClient, String keyID, String secretKey, Stri stockMarketDataWebsocket = stockDataClient == null ? null : new StockMarketDataWebsocket(okHttpClient, dataAPIType, keyID, secretKey); corporateActionsEndpoint = new CorporateActionsEndpoint(brokerClient); + newsEndpoint = newsDataClient == null ? null : new NewsEndpoint(newsDataClient); + newsMarketDataWebsocket = newsDataClient == null ? null : new NewsMarketDataWebsocket(okHttpClient, keyID, secretKey); } /** @@ -323,12 +333,32 @@ public AlpacaClient getStockDataClient() { return stockDataClient; } + public AlpacaClient getNewsDataClient() { + return newsDataClient; + } + /** * @return the {@link CorporateActionsEndpoint} */ public CorporateActionsEndpoint corporateActions() { return corporateActionsEndpoint; } + + /** + * @return the {@link NewsEndpoint} + */ + public NewsEndpoint newsEndpoint() { + return newsEndpoint; + } + + /** + * @return the News {@link MarketDataWebsocketInterface} + */ + public MarketDataWebsocketInterface newsMarketDataStreaming() { + return newsMarketDataWebsocket; + } + + /** * Creates a {@link Builder} for {@link AlpacaAPI}. * diff --git a/src/main/java/net/jacobpeterson/alpaca/rest/endpoint/marketdata/news/NewsEndpoint.java b/src/main/java/net/jacobpeterson/alpaca/rest/endpoint/marketdata/news/NewsEndpoint.java new file mode 100644 index 00000000..88ee1b7c --- /dev/null +++ b/src/main/java/net/jacobpeterson/alpaca/rest/endpoint/marketdata/news/NewsEndpoint.java @@ -0,0 +1,120 @@ +package net.jacobpeterson.alpaca.rest.endpoint.marketdata.news; + +import net.jacobpeterson.alpaca.model.endpoint.common.enums.SortDirection; +import net.jacobpeterson.alpaca.model.endpoint.marketdata.news.NewsResponse; +import net.jacobpeterson.alpaca.rest.AlpacaClient; +import net.jacobpeterson.alpaca.rest.AlpacaClientException; +import net.jacobpeterson.alpaca.rest.endpoint.AlpacaEndpoint; +import okhttp3.HttpUrl; +import okhttp3.Request; + +import java.time.LocalDate; +import java.util.List; + +import static com.google.common.base.Preconditions.checkNotNull; + +/** + * {@link AlpacaEndpoint} for + * News + * Endpoint. + */ +public class NewsEndpoint extends AlpacaEndpoint { + + /** + * Instantiates a new {@link AlpacaEndpoint}. + * + * @param alpacaClient the {@link AlpacaClient} + */ + public NewsEndpoint(AlpacaClient alpacaClient) { + super(alpacaClient, "news"); + } + + /** + * Gets latest list of news {@link NewsResponse}. + * @return the {@link NewsResponse} + * @throws AlpacaClientException thrown for {@link AlpacaClientException} + */ + public NewsResponse getLatestNews() throws AlpacaClientException { + return this.getLatestNews(null, null, null, null, null, null, null, null); + } + + /** + * Gets latest list of news {@link NewsResponse} for specific symbols. + * @param symbols list of symbols to query news for + * @return the {@link NewsResponse} + * @throws AlpacaClientException thrown for {@link AlpacaClientException}s + */ + public NewsResponse getLatestNews(List symbols) throws AlpacaClientException { + checkNotNull(symbols); + return this.getLatestNews(null, null, null, symbols, null, null, null, null); + } + + /** + * Gets latest list of news {@link NewsResponse} for specified token. + * @param pageToken the ID of the end of your current page of results. (See the section on paging.) + * @return the {@link NewsResponse} + * @throws AlpacaClientException thrown for {@link AlpacaClientException}s + */ + public NewsResponse getLatestNewsPaged(String pageToken) throws AlpacaClientException { + checkNotNull(pageToken); + return this.getLatestNews(null, null, null, null, null, null, null, pageToken); + } + + /** + * Gets latest list of news {@link NewsResponse} + * @param start The inclusive start of the interval + * @param end the inclusive end of the interval + * @param sortDirection sort articles by updated date + * @param symbols list of symbols to query news for + * @param limit Limit of news items to be returned for given page + * @param includeContent Boolean indicator to include content for news articles (if available) + * @param excludeContentless Boolean indicator to exclude news articles that do not contain content + * @param pageToken the ID of the end of your current page of results. (See the section on paging.) + * @return the {@link NewsResponse} + * @throws AlpacaClientException thrown for {@link AlpacaClientException}s + */ + public NewsResponse getLatestNews( + LocalDate start, LocalDate end, + SortDirection sortDirection, + List symbols, + Integer limit, + Boolean includeContent, + Boolean excludeContentless, + String pageToken) throws AlpacaClientException { + + HttpUrl.Builder urlBuilder = alpacaClient.urlBuilder() + .addPathSegment(endpointPathSegment); + + if (start != null) { + urlBuilder.addQueryParameter("start", start.toString()); + } + if (end != null) { + urlBuilder.addQueryParameter("end", end.toString()); + } + if (sortDirection != null) { + urlBuilder.addQueryParameter("sort", sortDirection.value()); + } + if (symbols != null && !symbols.isEmpty()) { + urlBuilder.addQueryParameter("symbols", String.join(",", symbols)); + } + if (limit != null) { + urlBuilder.addQueryParameter("limit", Integer.toString(limit)); + } + if (includeContent != null) { + urlBuilder.addQueryParameter("include_content", Boolean.toString(includeContent)); + } + if (excludeContentless != null) { + urlBuilder.addQueryParameter("exclude_contentless", Boolean.toString(excludeContentless)); + } + if (pageToken != null) { + urlBuilder.addQueryParameter("page_token", pageToken); + } + + Request request = alpacaClient.requestBuilder(urlBuilder.build()) + .get() + .build(); + + return alpacaClient.requestObject(request, NewsResponse.class); + + } +} diff --git a/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/MarketDataWebsocket.java b/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/MarketDataWebsocket.java index 9c924c11..48c297f9 100644 --- a/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/MarketDataWebsocket.java +++ b/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/MarketDataWebsocket.java @@ -13,6 +13,8 @@ import net.jacobpeterson.alpaca.model.endpoint.marketdata.common.realtime.enums.MarketDataMessageType; import net.jacobpeterson.alpaca.model.endpoint.marketdata.common.realtime.quote.QuoteMessage; import net.jacobpeterson.alpaca.model.endpoint.marketdata.common.realtime.trade.TradeMessage; +import net.jacobpeterson.alpaca.model.endpoint.marketdata.news.common.NewsArticle; +import net.jacobpeterson.alpaca.model.endpoint.marketdata.news.realtime.NewsMessage; import net.jacobpeterson.alpaca.websocket.AlpacaWebsocket; import okhttp3.HttpUrl; import okhttp3.OkHttpClient; @@ -53,7 +55,8 @@ public abstract class MarketDataWebsocket private static final List SUBSCRIBABLE_MARKET_DATA_MESSAGE_TYPES = Arrays.asList( MarketDataMessageType.TRADE, MarketDataMessageType.QUOTE, - MarketDataMessageType.BAR); + MarketDataMessageType.BAR, + MarketDataMessageType.NEWS); /** * Creates a {@link HttpUrl} for {@link MarketDataWebsocket} with the given websocketURLPathSegments. @@ -74,9 +77,11 @@ private static HttpUrl createWebsocketURL(String websocketURLPathSegments) { private final Set subscribedTrades; private final Set subscribedQuotes; private final Set subscribedBars; + private final Set subscribedNews; private final Type tradeClassType; private final Type quoteClassType; private final Type barClassType; + private final Type newsClassType = NewsMessage.class; /** * Instantiates a new {@link MarketDataWebsocket}. @@ -101,7 +106,7 @@ public MarketDataWebsocket(OkHttpClient okHttpClient, String websocketURLPathSeg subscribedTrades = new HashSet<>(); subscribedQuotes = new HashSet<>(); subscribedBars = new HashSet<>(); - + subscribedNews = new HashSet<>(); this.tradeClassType = tradeClass; this.quoteClassType = quoteClass; this.barClassType = barClass; @@ -124,7 +129,7 @@ protected void onReconnection() { sendAuthenticationMessage(); if (waitForAuthorization(5, TimeUnit.SECONDS)) { subscribeToControl(Iterables.toArray(listenedMarketDataMessageTypes, MarketDataMessageType.class)); - subscribe(subscribedTrades, subscribedQuotes, subscribedBars); + subscribe(subscribedTrades, subscribedQuotes, subscribedBars, subscribedNews); } } @@ -210,6 +215,8 @@ public void onMessage(@NotNull WebSocket webSocket, @NotNull String message) { subscribedQuotes); handleSubscriptionMessageList(MarketDataMessageType.BAR, subscriptionsMessage.getBars(), subscribedBars); + handleSubscriptionMessageList(MarketDataMessageType.NEWS, subscriptionsMessage.getNews(), + subscribedNews); break; case TRADE: marketDataMessage = GSON.fromJson(messageObject, tradeClassType); @@ -220,6 +227,9 @@ public void onMessage(@NotNull WebSocket webSocket, @NotNull String message) { case BAR: marketDataMessage = GSON.fromJson(messageObject, barClassType); break; + case NEWS: + marketDataMessage = GSON.fromJson(messageObject, newsClassType); + break; default: LOGGER.error("Message type {} not implemented!", marketDataMessageType); continue; @@ -286,14 +296,14 @@ public void subscribeToControl(MarketDataMessageType... marketDataMessageTypes) @Override public void subscribe(Collection tradeSymbols, Collection quoteSymbols, - Collection barSymbols) { - sendSubscriptionUpdate(tradeSymbols, quoteSymbols, barSymbols, true); + Collection barSymbols, Collection newsSymbols) { + sendSubscriptionUpdate(tradeSymbols, quoteSymbols, barSymbols, newsSymbols, true); } @Override public void unsubscribe(Collection tradeSymbols, Collection quoteSymbols, - Collection barSymbols) { - sendSubscriptionUpdate(tradeSymbols, quoteSymbols, barSymbols, false); + Collection barSymbols, Collection newsSymbols) { + sendSubscriptionUpdate(tradeSymbols, quoteSymbols, barSymbols, newsSymbols, false); } /** @@ -305,7 +315,7 @@ public void unsubscribe(Collection tradeSymbols, Collection quot * @param subscribe true to subscribe, false to unsubscribe */ private void sendSubscriptionUpdate(Collection tradeSymbols, Collection quoteSymbols, - Collection barSymbols, boolean subscribe) { + Collection barSymbols, Collection newsSymbols, boolean subscribe) { if (!isConnected()) { throw new IllegalStateException("This websocket must be connected before sending subscription updates!"); } @@ -325,6 +335,7 @@ private void sendSubscriptionUpdate(Collection tradeSymbols, Collection< addSubscriptionUpdateList(subscriptionUpdateObject, "trades", tradeSymbols); addSubscriptionUpdateList(subscriptionUpdateObject, "quotes", quoteSymbols); addSubscriptionUpdateList(subscriptionUpdateObject, "bars", barSymbols); + addSubscriptionUpdateList(subscriptionUpdateObject, "news", newsSymbols); boolean updateExists = subscriptionUpdateObject.size() > 1; if (updateExists) { @@ -368,4 +379,9 @@ public Collection subscribedQuotes() { public Collection subscribedBars() { return new HashSet<>(subscribedBars); } + + @Override + public Collection subscribedNews() { + return new HashSet<>(subscribedNews); + } } diff --git a/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/MarketDataWebsocketInterface.java b/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/MarketDataWebsocketInterface.java index 5ffcaf3c..d4f7700c 100644 --- a/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/MarketDataWebsocketInterface.java +++ b/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/MarketDataWebsocketInterface.java @@ -33,9 +33,9 @@ public interface MarketDataWebsocketInterface extends AlpacaWebsocketInterfacenull for no change * @param barSymbols a {@link Collection} of symbols to subscribe to bars or null for no change * - * @see #unsubscribe(Collection, Collection, Collection) + * @see #unsubscribe(Collection, Collection, Collection, Collection) */ - void subscribe(Collection tradeSymbols, Collection quoteSymbols, Collection barSymbols); + void subscribe(Collection tradeSymbols, Collection quoteSymbols, Collection barSymbols, Collection newsSymbols); /** * Unsubscribes from trades, quotes, or bars according to the given {@link Collection} of symbols. @@ -49,9 +49,9 @@ public interface MarketDataWebsocketInterface extends AlpacaWebsocketInterfacenull for no change * - * @see #subscribe(Collection, Collection, Collection) + * @see #subscribe(Collection, Collection, Collection, Collection) */ - void unsubscribe(Collection tradeSymbols, Collection quoteSymbols, Collection barSymbols); + void unsubscribe(Collection tradeSymbols, Collection quoteSymbols, Collection barSymbols, Collection newsSymbols); /** * Gets all the currently subscribed control {@link MarketDataMessageType}s. @@ -80,4 +80,7 @@ public interface MarketDataWebsocketInterface extends AlpacaWebsocketInterface subscribedBars(); + + Collection subscribedNews(); + } diff --git a/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/news/NewsMarketDataWebsocket.java b/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/news/NewsMarketDataWebsocket.java new file mode 100644 index 00000000..9d2c2620 --- /dev/null +++ b/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/news/NewsMarketDataWebsocket.java @@ -0,0 +1,15 @@ +package net.jacobpeterson.alpaca.websocket.marketdata.news; + +import net.jacobpeterson.alpaca.model.endpoint.marketdata.common.realtime.bar.BarMessage; +import net.jacobpeterson.alpaca.model.endpoint.marketdata.common.realtime.quote.QuoteMessage; +import net.jacobpeterson.alpaca.model.endpoint.marketdata.common.realtime.trade.TradeMessage; +import net.jacobpeterson.alpaca.websocket.marketdata.MarketDataWebsocket; +import okhttp3.OkHttpClient; + +public class NewsMarketDataWebsocket extends MarketDataWebsocket { + public NewsMarketDataWebsocket(OkHttpClient okHttpClient, + String keyID, String secretKey) { + super(okHttpClient, "v1beta1/news", "News", keyID, secretKey, TradeMessage.class, + QuoteMessage.class, BarMessage.class); + } +} diff --git a/src/test/java/net/jacobpeterson/alpaca/test/mock/corporateactions/CorporateActionsEndpointTest.java b/src/test/java/net/jacobpeterson/alpaca/test/mock/corporateactions/CorporateActionsEndpointTest.java index fd2dc0ae..c895ed03 100644 --- a/src/test/java/net/jacobpeterson/alpaca/test/mock/corporateactions/CorporateActionsEndpointTest.java +++ b/src/test/java/net/jacobpeterson/alpaca/test/mock/corporateactions/CorporateActionsEndpointTest.java @@ -45,21 +45,24 @@ void getAnnouncements_shouldReturnData() throws AlpacaClientException { void getAnnouncements_validation_caTypes() { CorporateActionsEndpoint newsEndpoint = setupEndpoint(mockMultipleAnnouncementsResponse()); - assertThrows(Exception.class, () -> newsEndpoint.getAnnouncements(null, LocalDate.now(), LocalDate.now(), null, null, null)); + assertThrows(Exception.class, + () -> newsEndpoint.getAnnouncements(null, LocalDate.now(), LocalDate.now(), null, null, null)); } @Test void getAnnouncements_validation_since() { CorporateActionsEndpoint newsEndpoint = setupEndpoint(mockMultipleAnnouncementsResponse()); - assertThrows(Exception.class, () -> newsEndpoint.getAnnouncements(new ArrayList<>(), null, LocalDate.now(), null, null, null)); + assertThrows(Exception.class, + () -> newsEndpoint.getAnnouncements(new ArrayList<>(), null, LocalDate.now(), null, null, null)); } @Test void getAnnouncements_validation_until() { CorporateActionsEndpoint newsEndpoint = setupEndpoint(mockMultipleAnnouncementsResponse()); - assertThrows(Exception.class, () -> newsEndpoint.getAnnouncements(new ArrayList<>(), LocalDate.now(), null, null, null, null)); + assertThrows(Exception.class, + () -> newsEndpoint.getAnnouncements(new ArrayList<>(), LocalDate.now(), null, null, null, null)); } @Test @@ -76,7 +79,7 @@ void getAnnouncement_shouldReturnData() throws AlpacaClientException { Announcement announcement = newsEndpoint.getAnnouncement("1"); assertNotNull(announcement); - assertEquals(announcement.getId(), "cd0cb768-889f-47a1-a94b-1438017dad77"); + assertEquals("cd0cb768-889f-47a1-a94b-1438017dad77", announcement.getId()); } private static CorporateActionsEndpoint setupEndpoint(String response) { diff --git a/src/test/java/net/jacobpeterson/alpaca/test/mock/marketdata/news/NewsEndpointTest.java b/src/test/java/net/jacobpeterson/alpaca/test/mock/marketdata/news/NewsEndpointTest.java new file mode 100644 index 00000000..ef4b553f --- /dev/null +++ b/src/test/java/net/jacobpeterson/alpaca/test/mock/marketdata/news/NewsEndpointTest.java @@ -0,0 +1,111 @@ +package net.jacobpeterson.alpaca.test.mock.marketdata.news; + +import net.jacobpeterson.alpaca.model.endpoint.marketdata.news.NewsResponse; +import net.jacobpeterson.alpaca.rest.AlpacaClient; +import net.jacobpeterson.alpaca.rest.AlpacaClientException; +import net.jacobpeterson.alpaca.rest.endpoint.marketdata.news.NewsEndpoint; +import okhttp3.MediaType; +import okhttp3.OkHttpClient; +import okhttp3.ResponseBody; +import okhttp3.mock.MockInterceptor; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertThrows; + +class NewsEndpointTest { + + @Test + void getLatestNews_validation_page() { + NewsEndpoint newsEndpoint = setupEndpoint(mockResponse()); + + assertThrows(Exception.class, () -> newsEndpoint.getLatestNewsPaged(null)); + } + + @Test + void getLatestNews_validation_symbols() { + NewsEndpoint newsEndpoint = setupEndpoint(mockResponse()); + + assertThrows(Exception.class, () -> newsEndpoint.getLatestNews(null)); + } + + @Test + void getLatestNews_shouldReturnData() throws AlpacaClientException { + NewsEndpoint newsEndpoint = setupEndpoint(mockResponse()); + NewsResponse response = newsEndpoint.getLatestNews(null, null, null, null, null, null, null, null); + + assertNotNull(response); + assertEquals("MTcwNDczNjg2NTAwMDAwMDAwMHwzNjU0MDM2Mw==", response.getNextPageToken()); + assertEquals(1, response.getNews().size()); + assertEquals(3, response.getNews().get(0).getImages().size()); + + } + + @Test + void getLatestNews_shouldReturnLatestData() throws AlpacaClientException { + NewsEndpoint newsEndpoint = setupEndpoint(mockResponse()); + NewsResponse response = newsEndpoint.getLatestNews(); + + assertNotNull(response); + assertEquals("MTcwNDczNjg2NTAwMDAwMDAwMHwzNjU0MDM2Mw==", response.getNextPageToken()); + assertEquals(1, response.getNews().size()); + assertEquals(3, response.getNews().get(0).getImages().size()); + + } + + private static NewsEndpoint setupEndpoint(String response) { + MockInterceptor interceptor = new MockInterceptor(); + interceptor.addRule() + .respond(200) + .body(ResponseBody.create(response, MediaType.parse("application/json"))); + + OkHttpClient client = new OkHttpClient.Builder() + .addInterceptor(interceptor) + .build(); + + AlpacaClient api = new AlpacaClient(client, "token", "test", "/v1"); + + return new NewsEndpoint(api); + } + + private static String mockResponse() { + return "{\n" + + " \"news\": [\n" + + " {\n" + + " \"author\": \"Khyathi Dalal\",\n" + + " \"content\": \"\",\n" + + " \"created_at\": \"2024-01-08T18:07:34Z\",\n" + + " \"headline\": \"Solana Drops 9.9%, Solana Memecoins Tank Over 20%: Is This The End Of The Dogecoin Killers?\",\n" + + " \"id\": 36536785,\n" + + " \"images\": [\n" + + " {\n" + + " \"size\": \"large\",\n" + + " \"url\": \"https://cdn.benzinga.com/files/imagecache/2048x1536xUP/images/story/2024/01/08/solana_coin_shutter2_0.jpg\"\n" + + " },\n" + + " {\n" + + " \"size\": \"small\",\n" + + " \"url\": \"https://cdn.benzinga.com/files/imagecache/1024x768xUP/images/story/2024/01/08/solana_coin_shutter2_0.jpg\"\n" + + " },\n" + + " {\n" + + " \"size\": \"thumb\",\n" + + " \"url\": \"https://cdn.benzinga.com/files/imagecache/250x187xUP/images/story/2024/01/08/solana_coin_shutter2_0.jpg\"\n" + + " }\n" + + " ],\n" + + " \"source\": \"benzinga\",\n" + + " \"summary\": \"Solana (CRYPTO: SOL) dropped 1.4% in the past 24 hours, extending its losses over the previous seven days to 9.9%. Solana-based memecoins are particularly hard-hit by the correction, taking much heavier losses than established memecoins.\",\n" + + " \"symbols\": [\n" + + " \"BONKUSD\",\n" + + " \"DOGEUSD\",\n" + + " \"ETHUSD\",\n" + + " \"SHIBUSD\",\n" + + " \"SOLUSD\"\n" + + " ],\n" + + " \"updated_at\": \"2024-01-08T18:07:35Z\",\n" + + " \"url\": \"https://www.benzinga.com/markets/cryptocurrency/24/01/36536785/solana-drops-9-9-solana-memecoins-tank-over-20-is-this-the-end-of-the-dogecoin-killers\"\n" + + " }\n" + + " ],\n" + + " \"next_page_token\": \"MTcwNDczNjg2NTAwMDAwMDAwMHwzNjU0MDM2Mw==\"\n" + + "}"; + } +} From 2750936a15114a750ce604e741fb8c6e88d87052 Mon Sep 17 00:00:00 2001 From: Jacob Peterson <14127217+Petersoj@users.noreply.github.com> Date: Fri, 1 Mar 2024 17:16:16 -0800 Subject: [PATCH 202/271] Modify PR to account for future project structure changes Namely: moving to OpenAPI generated clients, removing unit testing for RestAPI --- build.gradle | 1 - .../alpaca/test/mock/AlpacaAPITest.java | 13 -- .../CorporateActionsEndpointTest.java | 107 ----------------- .../marketdata/news/NewsEndpointTest.java | 111 ------------------ 4 files changed, 232 deletions(-) delete mode 100644 src/test/java/net/jacobpeterson/alpaca/test/mock/corporateactions/CorporateActionsEndpointTest.java delete mode 100644 src/test/java/net/jacobpeterson/alpaca/test/mock/marketdata/news/NewsEndpointTest.java diff --git a/build.gradle b/build.gradle index 7c65cc3d..a861d055 100644 --- a/build.gradle +++ b/build.gradle @@ -53,7 +53,6 @@ dependencies { testImplementation group: 'org.slf4j', name: 'slf4j-simple', version: '1.7.36' testImplementation group: 'org.junit.jupiter', name: 'junit-jupiter', version: '5.8.2' testImplementation group: 'org.mockito', name: 'mockito-core', version: '4.6.1' - testImplementation group: 'com.github.gmazzo.okhttp.mock', name: 'mock-client', version: '2.0.0' } // Exclude any SLF4j-implementation transitive dependencies so users can use a logging framework of their choice diff --git a/src/test/java/net/jacobpeterson/alpaca/test/mock/AlpacaAPITest.java b/src/test/java/net/jacobpeterson/alpaca/test/mock/AlpacaAPITest.java index 0fbc722a..d5f4f320 100644 --- a/src/test/java/net/jacobpeterson/alpaca/test/mock/AlpacaAPITest.java +++ b/src/test/java/net/jacobpeterson/alpaca/test/mock/AlpacaAPITest.java @@ -6,8 +6,6 @@ import okhttp3.OkHttpClient; import org.junit.jupiter.api.Test; -import static org.junit.jupiter.api.Assertions.assertNotNull; - /** * {@link AlpacaAPITest} tests {@link AlpacaAPI} using mocked objects with Mockito. */ @@ -73,15 +71,4 @@ public void testAlpacaAPIConstructor_okHttpClient_keyID_secret_oAuthToken_endpoi new AlpacaAPI(okHttpClient, null, null, oAuthToken, EndpointAPIType.PAPER, DataAPIType.IEX); new AlpacaAPI(okHttpClient, keyID, secret, null, EndpointAPIType.LIVE, DataAPIType.SIP); } - - @Test - public void testAlpacaAPIConstructor_corporateActionsEndpoint() { - OkHttpClient okHttpClient = new OkHttpClient(); - String keyID = "ABCDEFGHIJKLM"; - String secret = "NOPQURSTUVWXYZ"; - - AlpacaAPI api = new AlpacaAPI(okHttpClient, keyID, secret, null, EndpointAPIType.LIVE, DataAPIType.SIP); - - assertNotNull(api.corporateActions()); - } } diff --git a/src/test/java/net/jacobpeterson/alpaca/test/mock/corporateactions/CorporateActionsEndpointTest.java b/src/test/java/net/jacobpeterson/alpaca/test/mock/corporateactions/CorporateActionsEndpointTest.java deleted file mode 100644 index c895ed03..00000000 --- a/src/test/java/net/jacobpeterson/alpaca/test/mock/corporateactions/CorporateActionsEndpointTest.java +++ /dev/null @@ -1,107 +0,0 @@ -package net.jacobpeterson.alpaca.test.mock.corporateactions; - -import net.jacobpeterson.alpaca.model.endpoint.corporateactions.common.Announcement; -import net.jacobpeterson.alpaca.rest.AlpacaClient; -import net.jacobpeterson.alpaca.rest.AlpacaClientException; -import net.jacobpeterson.alpaca.rest.endpoint.corporateactions.CorporateActionsEndpoint; -import okhttp3.MediaType; -import okhttp3.OkHttpClient; -import okhttp3.ResponseBody; -import okhttp3.mock.MockInterceptor; -import org.junit.jupiter.api.Test; - -import java.time.LocalDate; -import java.util.ArrayList; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertNotNull; -import static org.junit.jupiter.api.Assertions.assertThrows; - -public class CorporateActionsEndpointTest { - - @Test - void getAnnouncements_givenSymbol_shouldReturnData() throws AlpacaClientException { - CorporateActionsEndpoint newsEndpoint = setupEndpoint(mockMultipleAnnouncementsResponse()); - - java.util.List result = newsEndpoint.getAnnouncements("K"); - - assertNotNull(result); - assertEquals(3, result.size()); - - } - - @Test - void getAnnouncements_shouldReturnData() throws AlpacaClientException { - CorporateActionsEndpoint newsEndpoint = setupEndpoint(mockMultipleAnnouncementsResponse()); - - java.util.List result = newsEndpoint.getAnnouncements(); - - assertNotNull(result); - assertEquals(3, result.size()); - - } - - @Test - void getAnnouncements_validation_caTypes() { - CorporateActionsEndpoint newsEndpoint = setupEndpoint(mockMultipleAnnouncementsResponse()); - - assertThrows(Exception.class, - () -> newsEndpoint.getAnnouncements(null, LocalDate.now(), LocalDate.now(), null, null, null)); - } - - @Test - void getAnnouncements_validation_since() { - CorporateActionsEndpoint newsEndpoint = setupEndpoint(mockMultipleAnnouncementsResponse()); - - assertThrows(Exception.class, - () -> newsEndpoint.getAnnouncements(new ArrayList<>(), null, LocalDate.now(), null, null, null)); - } - - @Test - void getAnnouncements_validation_until() { - CorporateActionsEndpoint newsEndpoint = setupEndpoint(mockMultipleAnnouncementsResponse()); - - assertThrows(Exception.class, - () -> newsEndpoint.getAnnouncements(new ArrayList<>(), LocalDate.now(), null, null, null, null)); - } - - @Test - void getAnnouncement_validation_id() { - CorporateActionsEndpoint newsEndpoint = setupEndpoint(mockSingleAnnouncementResponse()); - - assertThrows(Exception.class, () -> newsEndpoint.getAnnouncement(null)); - } - - @Test - void getAnnouncement_shouldReturnData() throws AlpacaClientException { - CorporateActionsEndpoint newsEndpoint = setupEndpoint(mockSingleAnnouncementResponse()); - - Announcement announcement = newsEndpoint.getAnnouncement("1"); - - assertNotNull(announcement); - assertEquals("cd0cb768-889f-47a1-a94b-1438017dad77", announcement.getId()); - } - - private static CorporateActionsEndpoint setupEndpoint(String response) { - MockInterceptor interceptor = new MockInterceptor(); - interceptor.addRule() - .respond(200) - .body(ResponseBody.create(response, MediaType.parse("application/json"))); - - OkHttpClient client = new OkHttpClient.Builder() - .addInterceptor(interceptor) - .build(); - - AlpacaClient api = new AlpacaClient(client, "token", "test", "/v1"); - - return new CorporateActionsEndpoint(api); - } - - private static String mockMultipleAnnouncementsResponse() { - return "[{\"id\":\"cd0cb768-889f-47a1-a94b-1438017dad77\",\"corporate_action_id\":\"2642471\",\"ca_type\":\"spinoff\",\"ca_sub_type\":\"spinoff\",\"initiating_symbol\":\"KLG\",\"initiating_original_cusip\":\"92942W107\",\"target_symbol\":\"K\",\"target_original_cusip\":\"487836108\",\"effective_date\":\"2023-09-08\",\"ex_date\":\"2023-10-02\",\"record_date\":\"2023-09-21\",\"payable_date\":\"2023-10-02\",\"cash\":\"0\",\"old_rate\":\"1\",\"new_rate\":\"0.25\"},{\"id\":\"3a7fe233-7766-4f70-915f-9d1a5258eaa9\",\"corporate_action_id\":\"487836108_AD23\",\"ca_type\":\"dividend\",\"ca_sub_type\":\"cash\",\"initiating_symbol\":\"K\",\"initiating_original_cusip\":\"487836108\",\"target_symbol\":\"K\",\"target_original_cusip\":\"487836108\",\"declaration_date\":\"2023-09-19\",\"effective_date\":\"2023-09-21\",\"ex_date\":\"2023-10-02\",\"record_date\":\"2023-09-21\",\"payable_date\":\"2023-10-02\",\"cash\":\"0\",\"old_rate\":\"1\",\"new_rate\":\"1\"},{\"id\":\"ddf64bcb-fa76-4472-a98b-237f220ea38b\",\"corporate_action_id\":\"487836108_AE23\",\"ca_type\":\"dividend\",\"ca_sub_type\":\"cash\",\"initiating_symbol\":\"K\",\"initiating_original_cusip\":\"487836108\",\"target_symbol\":\"K\",\"target_original_cusip\":\"487836108\",\"declaration_date\":\"2023-11-29\",\"effective_date\":\"2023-12-01\",\"ex_date\":\"2023-11-30\",\"record_date\":\"2023-12-01\",\"payable_date\":\"2023-12-15\",\"cash\":\"0.56\",\"old_rate\":\"1\",\"new_rate\":\"1\"}]"; - } - - private static String mockSingleAnnouncementResponse() { - return "{\"id\":\"cd0cb768-889f-47a1-a94b-1438017dad77\",\"corporate_action_id\":\"2642471\",\"ca_type\":\"spinoff\",\"ca_sub_type\":\"spinoff\",\"initiating_symbol\":\"KLG\",\"initiating_original_cusip\":\"92942W107\",\"target_symbol\":\"K\",\"target_original_cusip\":\"487836108\",\"effective_date\":\"2023-09-08\",\"ex_date\":\"2023-10-02\",\"record_date\":\"2023-09-21\",\"payable_date\":\"2023-10-02\",\"cash\":\"0\",\"old_rate\":\"1\",\"new_rate\":\"0.25\"}"; - } -} diff --git a/src/test/java/net/jacobpeterson/alpaca/test/mock/marketdata/news/NewsEndpointTest.java b/src/test/java/net/jacobpeterson/alpaca/test/mock/marketdata/news/NewsEndpointTest.java deleted file mode 100644 index ef4b553f..00000000 --- a/src/test/java/net/jacobpeterson/alpaca/test/mock/marketdata/news/NewsEndpointTest.java +++ /dev/null @@ -1,111 +0,0 @@ -package net.jacobpeterson.alpaca.test.mock.marketdata.news; - -import net.jacobpeterson.alpaca.model.endpoint.marketdata.news.NewsResponse; -import net.jacobpeterson.alpaca.rest.AlpacaClient; -import net.jacobpeterson.alpaca.rest.AlpacaClientException; -import net.jacobpeterson.alpaca.rest.endpoint.marketdata.news.NewsEndpoint; -import okhttp3.MediaType; -import okhttp3.OkHttpClient; -import okhttp3.ResponseBody; -import okhttp3.mock.MockInterceptor; -import org.junit.jupiter.api.Test; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertNotNull; -import static org.junit.jupiter.api.Assertions.assertThrows; - -class NewsEndpointTest { - - @Test - void getLatestNews_validation_page() { - NewsEndpoint newsEndpoint = setupEndpoint(mockResponse()); - - assertThrows(Exception.class, () -> newsEndpoint.getLatestNewsPaged(null)); - } - - @Test - void getLatestNews_validation_symbols() { - NewsEndpoint newsEndpoint = setupEndpoint(mockResponse()); - - assertThrows(Exception.class, () -> newsEndpoint.getLatestNews(null)); - } - - @Test - void getLatestNews_shouldReturnData() throws AlpacaClientException { - NewsEndpoint newsEndpoint = setupEndpoint(mockResponse()); - NewsResponse response = newsEndpoint.getLatestNews(null, null, null, null, null, null, null, null); - - assertNotNull(response); - assertEquals("MTcwNDczNjg2NTAwMDAwMDAwMHwzNjU0MDM2Mw==", response.getNextPageToken()); - assertEquals(1, response.getNews().size()); - assertEquals(3, response.getNews().get(0).getImages().size()); - - } - - @Test - void getLatestNews_shouldReturnLatestData() throws AlpacaClientException { - NewsEndpoint newsEndpoint = setupEndpoint(mockResponse()); - NewsResponse response = newsEndpoint.getLatestNews(); - - assertNotNull(response); - assertEquals("MTcwNDczNjg2NTAwMDAwMDAwMHwzNjU0MDM2Mw==", response.getNextPageToken()); - assertEquals(1, response.getNews().size()); - assertEquals(3, response.getNews().get(0).getImages().size()); - - } - - private static NewsEndpoint setupEndpoint(String response) { - MockInterceptor interceptor = new MockInterceptor(); - interceptor.addRule() - .respond(200) - .body(ResponseBody.create(response, MediaType.parse("application/json"))); - - OkHttpClient client = new OkHttpClient.Builder() - .addInterceptor(interceptor) - .build(); - - AlpacaClient api = new AlpacaClient(client, "token", "test", "/v1"); - - return new NewsEndpoint(api); - } - - private static String mockResponse() { - return "{\n" + - " \"news\": [\n" + - " {\n" + - " \"author\": \"Khyathi Dalal\",\n" + - " \"content\": \"\",\n" + - " \"created_at\": \"2024-01-08T18:07:34Z\",\n" + - " \"headline\": \"Solana Drops 9.9%, Solana Memecoins Tank Over 20%: Is This The End Of The Dogecoin Killers?\",\n" + - " \"id\": 36536785,\n" + - " \"images\": [\n" + - " {\n" + - " \"size\": \"large\",\n" + - " \"url\": \"https://cdn.benzinga.com/files/imagecache/2048x1536xUP/images/story/2024/01/08/solana_coin_shutter2_0.jpg\"\n" + - " },\n" + - " {\n" + - " \"size\": \"small\",\n" + - " \"url\": \"https://cdn.benzinga.com/files/imagecache/1024x768xUP/images/story/2024/01/08/solana_coin_shutter2_0.jpg\"\n" + - " },\n" + - " {\n" + - " \"size\": \"thumb\",\n" + - " \"url\": \"https://cdn.benzinga.com/files/imagecache/250x187xUP/images/story/2024/01/08/solana_coin_shutter2_0.jpg\"\n" + - " }\n" + - " ],\n" + - " \"source\": \"benzinga\",\n" + - " \"summary\": \"Solana (CRYPTO: SOL) dropped 1.4% in the past 24 hours, extending its losses over the previous seven days to 9.9%. Solana-based memecoins are particularly hard-hit by the correction, taking much heavier losses than established memecoins.\",\n" + - " \"symbols\": [\n" + - " \"BONKUSD\",\n" + - " \"DOGEUSD\",\n" + - " \"ETHUSD\",\n" + - " \"SHIBUSD\",\n" + - " \"SOLUSD\"\n" + - " ],\n" + - " \"updated_at\": \"2024-01-08T18:07:35Z\",\n" + - " \"url\": \"https://www.benzinga.com/markets/cryptocurrency/24/01/36536785/solana-drops-9-9-solana-memecoins-tank-over-20-is-this-the-end-of-the-dogecoin-killers\"\n" + - " }\n" + - " ],\n" + - " \"next_page_token\": \"MTcwNDczNjg2NTAwMDAwMDAwMHwzNjU0MDM2Mw==\"\n" + - "}"; - } -} From 1f6e425087ea963e0497f616bd8f411b09d3f9b5 Mon Sep 17 00:00:00 2001 From: Jacob Peterson <14127217+Petersoj@users.noreply.github.com> Date: Fri, 1 Mar 2024 20:33:54 -0800 Subject: [PATCH 203/271] WIP use OpenAPI specification with generated clients --- .editorconfig | 10 +- .gitattributes | 45 +- .github/image/logo.png | Bin 0 -> 52737 bytes .gitignore | 3 +- .travis.yml | 24 - LICENSE => LICENSE.txt | 2 +- README.md | 47 +- _config.yml | 2 +- build.gradle | 376 +++++----- buildSrc/build.gradle | 29 + .../GeneratePOJOsTask.groovy | 128 ++++ .../JSONSchema2POJOAdaptedPlugin.groovy | 26 + .../JSONSchemaFileConfig.groovy | 31 + codecov.yml | 6 - gradle/wrapper/.gitignore | 1 + gradle/wrapper/gradle-wrapper.properties | 4 +- schema_json/endpoint/account/account.json | 110 --- .../account/enums/account_status.json | 44 -- .../account_activities/account_activity.json | 14 - .../enums/activity_type.json | 152 ---- .../enums/trade_activity_side.json | 8 - .../enums/trade_activity_type.json | 8 - .../non_trade_activity.json | 38 - .../account_activities/trade_activity.json | 48 -- .../account_configuration.json | 22 - .../enums/dtbp_check.json | 10 - .../enums/trade_confirm_email.json | 8 - schema_json/endpoint/assets/asset.json | 51 -- .../endpoint/assets/enums/asset_class.json | 8 - .../endpoint/assets/enums/asset_status.json | 8 - schema_json/endpoint/calendar/calendar.json | 18 - schema_json/endpoint/clock/clock.json | 22 - .../endpoint/common/enums/sort_direction.json | 16 - .../common/announcement.json | 53 -- .../endpoint/orders/cancelled_order.json | 19 - .../orders/enums/current_order_status.json | 9 - .../endpoint/orders/enums/order_class.json | 24 - .../endpoint/orders/enums/order_side.json | 8 - .../endpoint/orders/enums/order_status.json | 76 -- .../orders/enums/order_time_in_force.json | 38 - .../endpoint/orders/enums/order_type.json | 28 - schema_json/endpoint/orders/order.json | 134 ---- .../enums/portfolio_period_unit.json | 24 - .../enums/portfolio_time_frame.json | 28 - .../portfolio_history/portfolio_history.json | 18 - .../portfolio_history_data_point.json | 22 - .../portfolio_history_response.json | 31 - .../positions/close_position_order.json | 19 - schema_json/endpoint/positions/position.json | 81 --- schema_json/endpoint/watchlist/watchlist.json | 30 - .../net/jacobpeterson/alpaca/AlpacaAPI.java | 246 +------ .../common/historical/bar/bar.json | 0 .../historical/bar/enums/bar_time_period.json | 0 .../common/historical/quote/quote.json | 0 .../common/historical/trade/trade.json | 0 .../common/realtime/bar/bar_message.json | 0 .../realtime/control/error_message.json | 0 .../control/subscriptions_message.json | 0 .../realtime/control/success_message.json | 0 .../enums/market_data_message_type.json | 0 .../common/realtime/market_data_message.json | 0 .../common/realtime/quote/quote_message.json | 0 .../common/realtime/symbol_message.json | 0 .../common/realtime/trade/trade_message.json | 0 .../crypto/common/enums/taker_side.json | 0 .../crypto/historical/bar/crypto_bar.json | 0 .../historical/bar/crypto_bars_response.json | 0 .../bar/latest_crypto_bars_response.json | 0 .../orderbook/crypto_orderbook.json | 0 .../orderbook/crypto_orderbook_entry.json | 0 .../latest_crypto_orderbooks_response.json | 0 .../crypto/historical/quote/crypto_quote.json | 0 .../quote/latest_crypto_quotes_response.json | 0 .../historical/snapshot/crypto_snapshot.json | 0 .../snapshot/crypto_snapshots_response.json | 0 .../crypto/historical/trade/crypto_trade.json | 0 .../trade/crypto_trades_response.json | 0 .../trade/latest_crypto_trades_response.json | 0 .../realtime/bar/crypto_bar_message.json | 0 .../realtime/quote/crypto_quote_message.json | 0 .../realtime/trade/crypto_trade_message.json | 0 .../market_data/news/common/image.json | 0 .../market_data/news/common/news_article.json | 0 .../market_data/news/news_response.json | 0 .../news/realtime/news_message.json | 0 .../historical/bar/enums/bar_adjustment.json | 0 .../stock/historical/bar/enums/bar_feed.json | 0 .../bar/multi_stock_bars_response.json | 0 .../stock/historical/bar/stock_bar.json | 0 .../historical/bar/stock_bars_response.json | 0 .../quote/latest_stock_quote_response.json | 0 .../stock/historical/quote/stock_quote.json | 0 .../quote/stock_quotes_response.json | 0 .../stock/historical/snapshot/snapshot.json | 0 .../trade/latest_stock_trade_response.json | 0 .../stock/historical/trade/stock_trade.json | 0 .../trade/stock_trades_response.json | 0 .../stock/realtime/bar/stock_bar_message.json | 0 .../realtime/quote/stock_quote_message.json | 0 .../realtime/trade/stock_trade_message.json | 0 .../authorization/authorization_data.json | 0 .../authorization/authorization_message.json | 0 .../enums/streaming_message_type.json | 0 .../streaming/listening/listening_data.json | 0 .../listening/listening_message.json | 0 .../endpoint/streaming/streaming_message.json | 0 .../trade/enums/trade_update_event.json | 0 .../streaming/trade/trade_update.json | 4 +- .../streaming/trade/trade_update_message.json | 0 .../alpaca/properties/AlpacaProperties.java | 35 +- .../alpaca}/properties/data_api_type.json | 0 .../alpaca}/properties/endpoint_api_type.json | 0 .../alpaca/rest/AlpacaClient.java | 235 ------- .../alpaca/rest/AlpacaClientException.java | 144 ---- .../alpaca/rest/endpoint/AlpacaEndpoint.java | 23 - .../endpoint/account/AccountEndpoint.java | 39 -- .../AccountActivitiesEndpoint.java | 142 ---- .../AccountConfigurationEndpoint.java | 64 -- .../rest/endpoint/assets/AssetsEndpoint.java | 83 --- .../endpoint/calendar/CalendarEndpoint.java | 71 -- .../rest/endpoint/clock/ClockEndpoint.java | 39 -- .../CorporateActionsEndpoint.java | 121 ---- .../crypto/CryptoMarketDataEndpoint.java | 302 -------- .../marketdata/news/NewsEndpoint.java | 120 ---- .../stock/StockMarketDataEndpoint.java | 368 ---------- .../rest/endpoint/orders/OrdersEndpoint.java | 651 ------------------ .../PortfolioHistoryEndpoint.java | 103 --- .../endpoint/positions/PositionsEndpoint.java | 136 ---- .../endpoint/watchlist/WatchlistEndpoint.java | 216 ------ .../util/doc/Documentation2JSONSchema.java | 130 ---- .../alpaca/util/format/FormatUtil.java | 53 -- .../alpaca/util/okhttp/JSONBodyBuilder.java | 65 -- .../alpaca/util/properties/PropertyUtil.java | 88 ++- .../alpaca/websocket/AlpacaWebsocket.java | 3 +- .../WebsocketStateListener.java | 2 +- .../streaming/StreamingWebsocket.java | 18 +- src/main/resources/alpaca.default.properties | 12 +- .../alpaca/test/live/AlpacaAPITest.java | 289 -------- .../alpaca/test/mock/AlpacaAPITest.java | 74 -- src/test/resources/alpaca.default.properties | 5 - 140 files changed, 528 insertions(+), 5314 deletions(-) create mode 100644 .github/image/logo.png delete mode 100644 .travis.yml rename LICENSE => LICENSE.txt (96%) create mode 100644 buildSrc/build.gradle create mode 100644 buildSrc/src/main/groovy/net/jacobpeterson/alpaca/buildsrc/plugin/jsonschema2pojoadapted/GeneratePOJOsTask.groovy create mode 100644 buildSrc/src/main/groovy/net/jacobpeterson/alpaca/buildsrc/plugin/jsonschema2pojoadapted/JSONSchema2POJOAdaptedPlugin.groovy create mode 100644 buildSrc/src/main/groovy/net/jacobpeterson/alpaca/buildsrc/plugin/jsonschema2pojoadapted/JSONSchemaFileConfig.groovy delete mode 100644 codecov.yml create mode 100644 gradle/wrapper/.gitignore delete mode 100644 schema_json/endpoint/account/account.json delete mode 100644 schema_json/endpoint/account/enums/account_status.json delete mode 100644 schema_json/endpoint/account_activities/account_activity.json delete mode 100644 schema_json/endpoint/account_activities/enums/activity_type.json delete mode 100644 schema_json/endpoint/account_activities/enums/trade_activity_side.json delete mode 100644 schema_json/endpoint/account_activities/enums/trade_activity_type.json delete mode 100644 schema_json/endpoint/account_activities/non_trade_activity.json delete mode 100644 schema_json/endpoint/account_activities/trade_activity.json delete mode 100644 schema_json/endpoint/account_configuration/account_configuration.json delete mode 100644 schema_json/endpoint/account_configuration/enums/dtbp_check.json delete mode 100644 schema_json/endpoint/account_configuration/enums/trade_confirm_email.json delete mode 100644 schema_json/endpoint/assets/asset.json delete mode 100644 schema_json/endpoint/assets/enums/asset_class.json delete mode 100644 schema_json/endpoint/assets/enums/asset_status.json delete mode 100644 schema_json/endpoint/calendar/calendar.json delete mode 100644 schema_json/endpoint/clock/clock.json delete mode 100644 schema_json/endpoint/common/enums/sort_direction.json delete mode 100644 schema_json/endpoint/corporate_actions/common/announcement.json delete mode 100644 schema_json/endpoint/orders/cancelled_order.json delete mode 100644 schema_json/endpoint/orders/enums/current_order_status.json delete mode 100644 schema_json/endpoint/orders/enums/order_class.json delete mode 100644 schema_json/endpoint/orders/enums/order_side.json delete mode 100644 schema_json/endpoint/orders/enums/order_status.json delete mode 100644 schema_json/endpoint/orders/enums/order_time_in_force.json delete mode 100644 schema_json/endpoint/orders/enums/order_type.json delete mode 100644 schema_json/endpoint/orders/order.json delete mode 100644 schema_json/endpoint/portfolio_history/enums/portfolio_period_unit.json delete mode 100644 schema_json/endpoint/portfolio_history/enums/portfolio_time_frame.json delete mode 100644 schema_json/endpoint/portfolio_history/portfolio_history.json delete mode 100644 schema_json/endpoint/portfolio_history/portfolio_history_data_point.json delete mode 100644 schema_json/endpoint/portfolio_history/portfolio_history_response.json delete mode 100644 schema_json/endpoint/positions/close_position_order.json delete mode 100644 schema_json/endpoint/positions/position.json delete mode 100644 schema_json/endpoint/watchlist/watchlist.json rename {schema_json => src/main/java/net/jacobpeterson/alpaca}/endpoint/market_data/common/historical/bar/bar.json (100%) rename {schema_json => src/main/java/net/jacobpeterson/alpaca}/endpoint/market_data/common/historical/bar/enums/bar_time_period.json (100%) rename {schema_json => src/main/java/net/jacobpeterson/alpaca}/endpoint/market_data/common/historical/quote/quote.json (100%) rename {schema_json => src/main/java/net/jacobpeterson/alpaca}/endpoint/market_data/common/historical/trade/trade.json (100%) rename {schema_json => src/main/java/net/jacobpeterson/alpaca}/endpoint/market_data/common/realtime/bar/bar_message.json (100%) rename {schema_json => src/main/java/net/jacobpeterson/alpaca}/endpoint/market_data/common/realtime/control/error_message.json (100%) rename {schema_json => src/main/java/net/jacobpeterson/alpaca}/endpoint/market_data/common/realtime/control/subscriptions_message.json (100%) rename {schema_json => src/main/java/net/jacobpeterson/alpaca}/endpoint/market_data/common/realtime/control/success_message.json (100%) rename {schema_json => src/main/java/net/jacobpeterson/alpaca}/endpoint/market_data/common/realtime/enums/market_data_message_type.json (100%) rename {schema_json => src/main/java/net/jacobpeterson/alpaca}/endpoint/market_data/common/realtime/market_data_message.json (100%) rename {schema_json => src/main/java/net/jacobpeterson/alpaca}/endpoint/market_data/common/realtime/quote/quote_message.json (100%) rename {schema_json => src/main/java/net/jacobpeterson/alpaca}/endpoint/market_data/common/realtime/symbol_message.json (100%) rename {schema_json => src/main/java/net/jacobpeterson/alpaca}/endpoint/market_data/common/realtime/trade/trade_message.json (100%) rename {schema_json => src/main/java/net/jacobpeterson/alpaca}/endpoint/market_data/crypto/common/enums/taker_side.json (100%) rename {schema_json => src/main/java/net/jacobpeterson/alpaca}/endpoint/market_data/crypto/historical/bar/crypto_bar.json (100%) rename {schema_json => src/main/java/net/jacobpeterson/alpaca}/endpoint/market_data/crypto/historical/bar/crypto_bars_response.json (100%) rename {schema_json => src/main/java/net/jacobpeterson/alpaca}/endpoint/market_data/crypto/historical/bar/latest_crypto_bars_response.json (100%) rename {schema_json => src/main/java/net/jacobpeterson/alpaca}/endpoint/market_data/crypto/historical/orderbook/crypto_orderbook.json (100%) rename {schema_json => src/main/java/net/jacobpeterson/alpaca}/endpoint/market_data/crypto/historical/orderbook/crypto_orderbook_entry.json (100%) rename {schema_json => src/main/java/net/jacobpeterson/alpaca}/endpoint/market_data/crypto/historical/orderbook/latest_crypto_orderbooks_response.json (100%) rename {schema_json => src/main/java/net/jacobpeterson/alpaca}/endpoint/market_data/crypto/historical/quote/crypto_quote.json (100%) rename {schema_json => src/main/java/net/jacobpeterson/alpaca}/endpoint/market_data/crypto/historical/quote/latest_crypto_quotes_response.json (100%) rename {schema_json => src/main/java/net/jacobpeterson/alpaca}/endpoint/market_data/crypto/historical/snapshot/crypto_snapshot.json (100%) rename {schema_json => src/main/java/net/jacobpeterson/alpaca}/endpoint/market_data/crypto/historical/snapshot/crypto_snapshots_response.json (100%) rename {schema_json => src/main/java/net/jacobpeterson/alpaca}/endpoint/market_data/crypto/historical/trade/crypto_trade.json (100%) rename {schema_json => src/main/java/net/jacobpeterson/alpaca}/endpoint/market_data/crypto/historical/trade/crypto_trades_response.json (100%) rename {schema_json => src/main/java/net/jacobpeterson/alpaca}/endpoint/market_data/crypto/historical/trade/latest_crypto_trades_response.json (100%) rename {schema_json => src/main/java/net/jacobpeterson/alpaca}/endpoint/market_data/crypto/realtime/bar/crypto_bar_message.json (100%) rename {schema_json => src/main/java/net/jacobpeterson/alpaca}/endpoint/market_data/crypto/realtime/quote/crypto_quote_message.json (100%) rename {schema_json => src/main/java/net/jacobpeterson/alpaca}/endpoint/market_data/crypto/realtime/trade/crypto_trade_message.json (100%) rename {schema_json => src/main/java/net/jacobpeterson/alpaca}/endpoint/market_data/news/common/image.json (100%) rename {schema_json => src/main/java/net/jacobpeterson/alpaca}/endpoint/market_data/news/common/news_article.json (100%) rename {schema_json => src/main/java/net/jacobpeterson/alpaca}/endpoint/market_data/news/news_response.json (100%) rename {schema_json => src/main/java/net/jacobpeterson/alpaca}/endpoint/market_data/news/realtime/news_message.json (100%) rename {schema_json => src/main/java/net/jacobpeterson/alpaca}/endpoint/market_data/stock/historical/bar/enums/bar_adjustment.json (100%) rename {schema_json => src/main/java/net/jacobpeterson/alpaca}/endpoint/market_data/stock/historical/bar/enums/bar_feed.json (100%) rename {schema_json => src/main/java/net/jacobpeterson/alpaca}/endpoint/market_data/stock/historical/bar/multi_stock_bars_response.json (100%) rename {schema_json => src/main/java/net/jacobpeterson/alpaca}/endpoint/market_data/stock/historical/bar/stock_bar.json (100%) rename {schema_json => src/main/java/net/jacobpeterson/alpaca}/endpoint/market_data/stock/historical/bar/stock_bars_response.json (100%) rename {schema_json => src/main/java/net/jacobpeterson/alpaca}/endpoint/market_data/stock/historical/quote/latest_stock_quote_response.json (100%) rename {schema_json => src/main/java/net/jacobpeterson/alpaca}/endpoint/market_data/stock/historical/quote/stock_quote.json (100%) rename {schema_json => src/main/java/net/jacobpeterson/alpaca}/endpoint/market_data/stock/historical/quote/stock_quotes_response.json (100%) rename {schema_json => src/main/java/net/jacobpeterson/alpaca}/endpoint/market_data/stock/historical/snapshot/snapshot.json (100%) rename {schema_json => src/main/java/net/jacobpeterson/alpaca}/endpoint/market_data/stock/historical/trade/latest_stock_trade_response.json (100%) rename {schema_json => src/main/java/net/jacobpeterson/alpaca}/endpoint/market_data/stock/historical/trade/stock_trade.json (100%) rename {schema_json => src/main/java/net/jacobpeterson/alpaca}/endpoint/market_data/stock/historical/trade/stock_trades_response.json (100%) rename {schema_json => src/main/java/net/jacobpeterson/alpaca}/endpoint/market_data/stock/realtime/bar/stock_bar_message.json (100%) rename {schema_json => src/main/java/net/jacobpeterson/alpaca}/endpoint/market_data/stock/realtime/quote/stock_quote_message.json (100%) rename {schema_json => src/main/java/net/jacobpeterson/alpaca}/endpoint/market_data/stock/realtime/trade/stock_trade_message.json (100%) rename {schema_json => src/main/java/net/jacobpeterson/alpaca}/endpoint/streaming/authorization/authorization_data.json (100%) rename {schema_json => src/main/java/net/jacobpeterson/alpaca}/endpoint/streaming/authorization/authorization_message.json (100%) rename {schema_json => src/main/java/net/jacobpeterson/alpaca}/endpoint/streaming/enums/streaming_message_type.json (100%) rename {schema_json => src/main/java/net/jacobpeterson/alpaca}/endpoint/streaming/listening/listening_data.json (100%) rename {schema_json => src/main/java/net/jacobpeterson/alpaca}/endpoint/streaming/listening/listening_message.json (100%) rename {schema_json => src/main/java/net/jacobpeterson/alpaca}/endpoint/streaming/streaming_message.json (100%) rename {schema_json => src/main/java/net/jacobpeterson/alpaca}/endpoint/streaming/trade/enums/trade_update_event.json (100%) rename {schema_json => src/main/java/net/jacobpeterson/alpaca}/endpoint/streaming/trade/trade_update.json (82%) rename {schema_json => src/main/java/net/jacobpeterson/alpaca}/endpoint/streaming/trade/trade_update_message.json (100%) rename {schema_json => src/main/java/net/jacobpeterson/alpaca}/properties/data_api_type.json (100%) rename {schema_json => src/main/java/net/jacobpeterson/alpaca}/properties/endpoint_api_type.json (100%) delete mode 100644 src/main/java/net/jacobpeterson/alpaca/rest/AlpacaClient.java delete mode 100644 src/main/java/net/jacobpeterson/alpaca/rest/AlpacaClientException.java delete mode 100644 src/main/java/net/jacobpeterson/alpaca/rest/endpoint/AlpacaEndpoint.java delete mode 100644 src/main/java/net/jacobpeterson/alpaca/rest/endpoint/account/AccountEndpoint.java delete mode 100644 src/main/java/net/jacobpeterson/alpaca/rest/endpoint/accountactivities/AccountActivitiesEndpoint.java delete mode 100644 src/main/java/net/jacobpeterson/alpaca/rest/endpoint/accountconfiguration/AccountConfigurationEndpoint.java delete mode 100644 src/main/java/net/jacobpeterson/alpaca/rest/endpoint/assets/AssetsEndpoint.java delete mode 100644 src/main/java/net/jacobpeterson/alpaca/rest/endpoint/calendar/CalendarEndpoint.java delete mode 100644 src/main/java/net/jacobpeterson/alpaca/rest/endpoint/clock/ClockEndpoint.java delete mode 100644 src/main/java/net/jacobpeterson/alpaca/rest/endpoint/corporateactions/CorporateActionsEndpoint.java delete mode 100644 src/main/java/net/jacobpeterson/alpaca/rest/endpoint/marketdata/crypto/CryptoMarketDataEndpoint.java delete mode 100644 src/main/java/net/jacobpeterson/alpaca/rest/endpoint/marketdata/news/NewsEndpoint.java delete mode 100644 src/main/java/net/jacobpeterson/alpaca/rest/endpoint/marketdata/stock/StockMarketDataEndpoint.java delete mode 100644 src/main/java/net/jacobpeterson/alpaca/rest/endpoint/orders/OrdersEndpoint.java delete mode 100644 src/main/java/net/jacobpeterson/alpaca/rest/endpoint/portfoliohistory/PortfolioHistoryEndpoint.java delete mode 100644 src/main/java/net/jacobpeterson/alpaca/rest/endpoint/positions/PositionsEndpoint.java delete mode 100644 src/main/java/net/jacobpeterson/alpaca/rest/endpoint/watchlist/WatchlistEndpoint.java delete mode 100644 src/main/java/net/jacobpeterson/alpaca/util/doc/Documentation2JSONSchema.java delete mode 100644 src/main/java/net/jacobpeterson/alpaca/util/format/FormatUtil.java delete mode 100644 src/main/java/net/jacobpeterson/alpaca/util/okhttp/JSONBodyBuilder.java rename src/main/java/net/jacobpeterson/alpaca/{util/okhttp => websocket}/WebsocketStateListener.java (94%) delete mode 100644 src/test/java/net/jacobpeterson/alpaca/test/live/AlpacaAPITest.java delete mode 100644 src/test/java/net/jacobpeterson/alpaca/test/mock/AlpacaAPITest.java delete mode 100644 src/test/resources/alpaca.default.properties diff --git a/.editorconfig b/.editorconfig index 818a4c9e..e8ed66b1 100644 --- a/.editorconfig +++ b/.editorconfig @@ -509,7 +509,7 @@ ij_typescript_finally_on_new_line = false ij_typescript_for_brace_force = always ij_typescript_for_statement_new_line_after_left_paren = false ij_typescript_for_statement_right_paren_on_new_line = false -ij_typescript_for_statement_wrap = split_into_lines +ij_typescript_for_statement_wrap = normal ij_typescript_force_quote_style = true ij_typescript_force_semicolon_style = true ij_typescript_function_expression_brace_style = end_of_line @@ -519,7 +519,7 @@ ij_typescript_import_prefer_absolute_path = global ij_typescript_import_sort_members = true ij_typescript_import_sort_module_name = false ij_typescript_import_use_node_resolution = true -ij_typescript_imports_wrap = normal +ij_typescript_imports_wrap = on_every_item ij_typescript_indent_case_from_switch = true ij_typescript_indent_chained_calls = true ij_typescript_indent_package_children = 0 @@ -1023,7 +1023,7 @@ ij_json_wrap_long_lines = false [{*.htm,*.html,*.ng,*.peb,*.sht,*.shtm,*.shtml}] ij_html_add_new_line_before_tags = body,div,p,form,h1,h2,h3 -ij_html_align_attributes = true +ij_html_align_attributes = false ij_html_align_text = false ij_html_attribute_wrap = normal ij_html_block_comment_add_space = false @@ -1182,8 +1182,8 @@ ij_prototext_spaces_within_brackets = false [{*.properties,spring.handlers,spring.schemas}] ij_properties_align_group_field_declarations = false -ij_properties_keep_blank_lines = false -ij_properties_key_value_delimiter = equals +ij_properties_keep_blank_lines = true +ij_properties_key_value_delimiter = colon ij_properties_spaces_around_key_value_delimiter = false [{*.qute.htm,*.qute.html,*.qute.json,*.qute.txt,*.qute.yaml,*.qute.yml}] diff --git a/.gitattributes b/.gitattributes index 1730d965..710d818f 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1,46 +1,7 @@ -# https://www.gitattributes.io/api/java -# -# Handle line endings automatically for files detected as text -# and leave all files detected as binary untouched. +# Handle line endings automatically for files detected as text and leave all files detected as binary untouched. * text=auto -# -# The above will handle all files NOT found below -# -# These files are text and should be normalized (Convert crlf => lf) -*.css text -*.df text -*.htm text -*.html text -*.java text -*.js text -*.json text -*.jsp text -*.jspf text -*.jspx text -*.properties text -*.sh text -*.tld text -*.txt text -*.tag text -*.tagx text -*.xml text -*.yml text - -# Declare files that will always have CRLF line endings on checkout. +# Declare files that should always have CRLF line endings at checkout. *.sln text eol=crlf *.bat text eol=crlf - -# These files are binary and should be left untouched -# (binary is a macro for -text -diff) -*.class binary -*.dll binary -*.ear binary -*.gif binary -*.ico binary -*.jar binary -*.jpg binary -*.jpeg binary -*.png binary -*.so binary -*.war binary +*.cmd text eol=crlf diff --git a/.github/image/logo.png b/.github/image/logo.png new file mode 100644 index 0000000000000000000000000000000000000000..a9cdaa37b4314105f5b4663bc274384a7cba552d GIT binary patch literal 52737 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 GGywn&neP7p literal 0 HcmV?d00001 diff --git a/.gitignore b/.gitignore index fcbc20f3..985d4c2c 100644 --- a/.gitignore +++ b/.gitignore @@ -4,4 +4,5 @@ build/ .settings .DS_Store .idea/ -alpaca.properties +*.properties +!*.default.properties diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index d54435b1..00000000 --- a/.travis.yml +++ /dev/null @@ -1,24 +0,0 @@ -language: java -install: true -dist: trusty - -jdk: - - oraclejdk8 - -before_script: - - chmod +x gradlew - -script: - - ./gradlew build check jacocoTestReport --stacktrace - -after_success: - - bash <(curl -s https://codecov.io/bash) - -before_cache: - - rm -f $HOME/.gradle/caches/modules-2/modules-2.lock - - rm -fr $HOME/.gradle/caches/*/plugin-resolution/ - -cache: - directories: - - $HOME/.gradle/caches/ - - $HOME/.gradle/wrapper/ diff --git a/LICENSE b/LICENSE.txt similarity index 96% rename from LICENSE rename to LICENSE.txt index 706b1675..38f23352 100644 --- a/LICENSE +++ b/LICENSE.txt @@ -1,6 +1,6 @@ MIT License -Copyright (c) Jacob Peterson 2021 +Copyright (c) Jacob Peterson Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/README.md b/README.md index c483ee2d..94b7e833 100644 --- a/README.md +++ b/README.md @@ -1,14 +1,13 @@ -

Logo

+

Logo

+ GitHub Repository Maven Central Javadocs - Build Status - CodeCov badge GitHub

# Overview -This is a Java implementation for the Alpaca API. Alpaca lets you trade with algorithms, connect with apps, and build services all with a commission-free stock trading API. This library is community developed and if you have any questions, please ask them on [Github Discussions](https://github.com/Petersoj/alpaca-java/discussions), the [Alpaca Slack #dev-alpaca-java channel](https://alpaca.markets/slack), or on the [Alpaca Forums](https://forum.alpaca.markets/). +This library is a Java client implementation of the Alpaca API. Alpaca lets you trade with algorithms, connect with apps, and build services all with a commission-free trading API for stocks, crypto, and options. This library uses the [Alpaca OpenAPI Specifications](https://docs.alpaca.markets/v1.1/openapi) to generate REST API clients and implements the websocket streaming interface using [OkHttp](https://square.github.io/okhttp/). This library is community developed and if you have any questions, please ask them on [Github Discussions](https://github.com/Petersoj/alpaca-java/discussions), the [Alpaca Slack #dev-alpaca-java channel](https://alpaca.markets/slack), or on the [Alpaca Forums](https://forum.alpaca.markets/). Give this repository a star ⭐ if it helped you build a trading algorithm in Java! @@ -16,19 +15,16 @@ Give this repository a star ⭐ if it helped you build a trading algorithm in Ja If you are using Gradle as your build tool, add the following dependency to your `build.gradle` file: ``` -dependencies { - implementation group: 'net.jacobpeterson', name: 'alpaca-java', version: '9.2.0' -} +implementation group: "net.jacobpeterson.alpaca", name: "alpaca-java", version: "10.0.0" ``` If you are using Maven as your build tool, add the following dependency to your `pom.xml` file: ``` - net.jacobpeterson + net.jacobpeterson.alpaca alpaca-java - 9.2.0 - compile + 10.0.0 ``` @@ -516,8 +512,8 @@ newsWebsocket.subscribeToControl( newsWebsocket.connect(); newsWebsocket.waitForAuthorization(5, TimeUnit.SECONDS); if (!newsWebsocket.isValid()) { - System.out.println("Websocket not valid!"); - return; + System.out.println("Websocket not valid!"); + return; } newsWebsocket.subscribe(null, null, null, Arrays.asList("*")); @@ -534,31 +530,18 @@ To build this project yourself, clone this repository and run: ./gradlew build ``` -To install built artifacts to your local maven repo, run: -``` -./gradlew install -x test -``` - -# Testing - -To run mocked tests using Mockito, run: -``` -./gradlew test -``` -Note that mocked tests never send real API requests to Alpaca. Mocked tests are meant to test the basic integrity of this API locally. - -To run live endpoint tests with Alpaca Paper credentials, create the `alpaca.properties` file in `src/test/resources` with the corresponding credentials. Then run: +To install the built artifacts to your local Maven repository on your machine (the `~/.m2/` directory), run: ``` -./gradlew test -PtestPackage=live +./gradlew install ``` -Note that the live tests will modify your account minimally. It's meant to test live endpoints on a real paper account during market hours to confirm that API methods are working properly. Please read through the live endpoint tests [here](https://github.com/Petersoj/alpaca-java/tree/master/src/test/java/live/net/jacobpeterson/alpaca) before running this testing suite on your own paper account. # TODO -- Finish Unit Testing (both live and mocked) -- Use [TA4j](https://github.com/ta4j/ta4j) `Num` interface instead of `Double` for number variables so that users can use either `Double` or `BigDecimal` for performance or precision in price data. -- Add [TimeSeriesDataStore](https://github.com/Petersoj/TimeSeriesDataStore) using Alpaca Data API +- Implement Unit Testing for REST API and Websocket streaming (both live and mocked) +- Implement Broker API websockets # Contributing Contributions are welcome! -If you are creating a Pull Request, be sure to create a new branch in your forked repository for your feature or bug fix instead of committing directly to the `master` branch in your fork. +When creating a Pull Request, keep the following in mind: +1. Create a new branch in your forked repository for your feature or bug fix instead of committing directly to the `master` branch in your fork. +2. Use the `dev` branch in this repository as the base branch in your Pull Request. diff --git a/_config.yml b/_config.yml index 259a24e4..72d781d7 100644 --- a/_config.yml +++ b/_config.yml @@ -1 +1 @@ -theme: jekyll-theme-tactile \ No newline at end of file +theme: jekyll-theme-tactile diff --git a/build.gradle b/build.gradle index a861d055..844d41b9 100644 --- a/build.gradle +++ b/build.gradle @@ -1,35 +1,29 @@ -import org.jsonschema2pojo.gradle.GenerateJsonSchemaJavaTask +import org.openapitools.generator.gradle.plugin.tasks.GenerateTask -buildscript { - repositories { - mavenCentral() - } +plugins { + id "java" + id "java-library" - dependencies { - // For Maven and Maven Central support - classpath group: 'com.bmuschko', name: 'gradle-nexus-plugin', version: '2.3.1' - // For JSONSchema2POJO - classpath group: 'org.jsonschema2pojo', name: 'jsonschema2pojo-gradle-plugin', version: '1.1.1' - } -} + // Adapted JSONSchema2POJO plugin + id "jsonschema2pojo-adapted" -plugins { - id 'java' + // OpenAPI Generator + id "org.openapi.generator" version "7.3.0" - // For Maven and Maven Central support - id 'maven-publish' - id 'com.bmuschko.nexus' version '2.3.1' - id 'io.codearte.nexus-staging' version '0.11.0' + // Gradle versions plugin + id "com.github.ben-manes.versions" version "0.51.0" - // For code coverage reports - id 'jacoco' + // For Maven Central publishing + id "maven-publish" + id "signing" } -apply plugin: 'jsonschema2pojo' +final def projectGroup = "net.jacobpeterson.alpaca" +final def projectArtifactID = "alpaca-java" +final def projectVersion = "10.0.0-SNAPSHOT" -archivesBaseName = 'alpaca-java' -group = 'net.jacobpeterson' -version = '9.2.0-SNAPSHOT' +group = projectGroup +version = projectVersion repositories { mavenCentral() @@ -37,215 +31,237 @@ repositories { dependencies { // Logging framework - implementation group: 'org.slf4j', name: 'slf4j-api', version: '1.7.36' - // This is a bridge for the Apache Jakarta Commons Logging library used in dependencies to actually use SLF4j instead - implementation group: 'org.slf4j', name: 'jcl-over-slf4j', version: '1.7.36' + implementation group: "org.slf4j", name: "slf4j-api", version: "2.1.0-alpha1" + // Require dependencies that use the Apache Jakarta Commons Logging library to use SLF4j instead + implementation group: "org.slf4j", name: "jcl-over-slf4j", version: "2.1.0-alpha1" + + // Google Guava + implementation group: "com.google.guava", name: "guava", version: "33.0.0-jre" - // Guava and GSON - implementation group: 'com.google.guava', name: 'guava', version: '31.1-jre' - implementation group: 'com.google.code.gson', name: 'gson', version: '2.9.0' + // GSON + implementation group: "com.google.code.gson", name: "gson", version: "2.10.1" // OkHttp - implementation group: 'com.squareup.okhttp3', name: 'okhttp', version: '4.10.0' - implementation group: 'com.github.devcsrj', name: 'slf4j-okhttp3-logging-interceptor', version: '1.0.1' + implementation group: "com.squareup.okhttp3", name: "okhttp", version: "5.0.0-alpha.12" + implementation group: "com.squareup.okhttp3", name: "logging-interceptor", version: "5.0.0-alpha.12" - // Unit test dependencies - testImplementation group: 'org.slf4j', name: 'slf4j-simple', version: '1.7.36' - testImplementation group: 'org.junit.jupiter', name: 'junit-jupiter', version: '5.8.2' - testImplementation group: 'org.mockito', name: 'mockito-core', version: '4.6.1' + // Libraries used by the OpenAPI generated client libraries + implementation group: "jakarta.annotation", name: "jakarta.annotation-api", version: "1.3.5" + implementation group: "org.openapitools", name: "jackson-databind-nullable", version: "0.2.6" + implementation group: "io.gsonfire", name: "gson-fire", version: "1.9.0" } // Exclude any SLF4j-implementation transitive dependencies so users can use a logging framework of their choice configurations.implementation { - exclude group: 'commons-logging', module: 'commons-logging' + exclude group: "commons-logging", module: "commons-logging" } -sourceSets { - main { - java { - srcDirs 'src/main/java' - } - } - test { - java { - srcDirs 'src/test/java' - } - } +java { + sourceCompatibility = JavaVersion.VERSION_17 + targetCompatibility = JavaVersion.VERSION_17 + withJavadocJar() + withSourcesJar() } -sourceCompatibility = 1.8 -targetCompatibility = 1.8 -[compileJava, compileTestJava]*.options*.encoding = 'UTF-8' +[compileJava, compileTestJava]*.options*.encoding = "UTF-8" javadoc { - // Explicitly define source version - options.addStringOption('source', '8') - // Used to suppress Javadoc linting warnings - options.addStringOption('Xdoclint:none', '-quiet') - // Explicitly define Charset for Javadocs - options.addStringOption('charset', 'UTF-8') - // Add Java SE 8 link - options.addStringOption('link', 'https://docs.oracle.com/javase/8/docs/api/') + options.addStringOption("source", "17") + options.addStringOption("Xdoclint:none", "-quiet") // Suppress Javadoc linting warnings + options.addStringOption("charset", "UTF-8") + options.addStringOption("link", "https://docs.oracle.com/javase/8/docs/api/") } -test { - useJUnitPlatform() - - testLogging { - events "passed", "skipped", "failed" - exceptionFormat "full" - } +// +// BEGIN Alpaca OpenAPI Specification (OAS) client generation +// - doFirst { - def testPackageArgument = "testPackage" - filter.includeTestsMatching(project.hasProperty(testPackageArgument) && - project.getProperty(testPackageArgument).equals("live") ? "*.live.*" : "*.mock.*") +// These IDs come from the URLs on this page: https://docs.alpaca.markets/v1.1/openapi +final def specIDsOfFileNames = Map.of( + "trader.json", "657760422129f9005cf4bd58", + "broker.json", "657771fc2471d20070183049", + "market-data.json", "6577704d1fb6b9007032bf39") +final File specDownloadPath = new File(project.layout.buildDirectory.get().getAsFile(), + "/download/openapi/") +final File generatedClientLibrariesPath = new File(project.layout.buildDirectory.get().getAsFile(), + "/generated/openapi/") + +final def generateAllOpenAPIClientsTasks = tasks.register("generateAllOpenAPIClients") { + doLast { + fixOpenAPIGeneratedClientIssues(generatedClientLibrariesPath) } } +compileJava.dependsOn generateAllOpenAPIClientsTasks -jacocoTestReport { - reports { - xml.enabled true - html.enabled false +def downloadOpenAPISpecFilesTask = tasks.register("downloadOpenAPISpecFiles") { + configure { + outputs.dir(specDownloadPath) } -} -jacocoTestReport.dependsOn check -// -// START publishing -// -modifyPom { - project { - name = 'alpaca-java' - description = 'Java API for Alpaca' - url = 'https://github.com/Petersoj/alpaca-java' - inceptionYear = '2018' - - licenses { - license { - name = 'MIT License' - url = 'https://opensource.org/licenses/MIT' + doLast { + // Download spec files + specIDsOfFileNames.forEach { fileName, alpacaSpecID -> + final def outputFile = new File(specDownloadPath, fileName) + final def specURL = "https://docs.alpaca.markets/v1.1/openapi/${alpacaSpecID}" + outputFile.getParentFile().mkdirs() + try { + logger.info("Downloading OpenAPI spec file from: {}", specURL) + new URL(specURL).withInputStream { inputStream -> outputFile.withOutputStream { it << inputStream } } + logger.info("Successfully downloaded OpenAPI spec file to: {}", outputFile.getPath()) + } catch (IOException ioException) { + throw new RuntimeException("Could not download OpenAPI spec file!", ioException) } } - developers { - developer { - id = 'Petersoj' - name = 'Jacob Peterson' - } + // Fix an issue in the Broker spec file that uses an empty string as an object key for some reason + // TODO remove this when issue is fixed by Alpaca + final def brokerSpecFile = new File(specDownloadPath, "broker.json") + brokerSpecFile.text = brokerSpecFile.text + .replace(",\"\":{\"type\":\"string\",\"x-stoplight\":{\"id\":\"1zg5jnb6rzdoo\"}}", "") + } +} + +for (def specFileName : specIDsOfFileNames.keySet()) { + final def specName = specFileName.replace(".json", "") + final def inputSpecFile = new File(specDownloadPath, specFileName) + final def outputDirectory = new File(generatedClientLibrariesPath, specName) - developer { - id = 'mainstringargs' - name = 'main(String[] args)' + // Add source set of OpenAPI generated client library + sourceSets { + main { + java { + srcDir new File(outputDirectory, "src/main/java/").getPath() } } + } - scm { - url = "https://github.com/Petersoj/alpaca-java.git" - connection = "scm:git:https://github.com/Petersoj/alpaca-java.git" - developerConnection = "scm:git:git@github.com/Petersoj/alpaca-java.git" + // Register and configure OpenAPI generate task + final def generateTask = tasks.register("generateOpenAPIClientFor-" + specName, GenerateTask) { + dependsOn { + downloadOpenAPISpecFilesTask } - } -} -extraArchive { - sources = true - tests = true - javadoc = true -} + it.onlyIf { + !downloadOpenAPISpecFilesTask.get().state.upToDate && + downloadOpenAPISpecFilesTask.get().state.failure == null && + !downloadOpenAPISpecFilesTask.get().state.skipped && + downloadOpenAPISpecFilesTask.get().state.executed + } -nexus { - sign = true - repositoryUrl = 'https://oss.sonatype.org/service/local/staging/deploy/maven2/' - snapshotRepositoryUrl = 'https://oss.sonatype.org/content/repositories/snapshots/' -} + configure { + inputs.file(inputSpecFile) + outputs.dir(outputDirectory) + } -nexusStaging { - packageGroup = "net.jacobpeterson" + generatorName.set("java") + inputSpec.set(inputSpecFile.getPath()) + outputDir.set(outputDirectory.getPath()) + final def destinationPackage = projectGroup + ".openapi." + specName.replace("-", "") + configOptions.set(Map.of( + "artifactId", specName, + "artifactVersion", "1.0.0-SNAPSHOT", + "groupId", destinationPackage, + "invokerPackage", destinationPackage, + "apiPackage", destinationPackage + ".api", + "modelPackage", destinationPackage + ".model", + "library", "okhttp-gson", + "documentationProvider", "none")) + cleanupOutput.set(true) + validateSpec.set(false) + skipValidateSpec.set(true) + generateModelTests.set(false) + generateModelDocumentation.set(false) + generateApiTests.set(false) + generateApiDocumentation.set(false) + } + generateAllOpenAPIClientsTasks.configure { + dependsOn generateTask + } } -// Used to prevent accidental publication of Alpaca keys -processResources { - exclude('alpaca.properties') +static void fixOpenAPIGeneratedClientIssues(File generatedClientLibrariesPath) { + // Fix broker spec generated client issues with discriminators + // on some enums: https://github.com/OpenAPITools/openapi-generator/issues/806 + final def brokerTransferDataJavaFile = new File(generatedClientLibrariesPath, + "broker/src/main/java/net/jacobpeterson/alpaca/openapi/broker/model/TransferData.java") + brokerTransferDataJavaFile.text = brokerTransferDataJavaFile.text + .replace("this.transferType = this.getClass().getSimpleName();", "") + final def brokerTransferResourceJavaFile = new File(generatedClientLibrariesPath, + "broker/src/main/java/net/jacobpeterson/alpaca/openapi/broker/model/TransferResource.java") + brokerTransferResourceJavaFile.text = brokerTransferResourceJavaFile.text + .replace("this.type = this.getClass().getSimpleName();", "") } // -// END publishing +// END Alpaca OpenAPI Specification (OAS) client generation // // -// START POJO generation +// BEGIN Publishing // -// Disable default 'jsonSchema2Pojo' task -tasks.getByName("generateJsonSchema2Pojo").enabled(false) - -// The generated POJOs will be in a package structure analogous to the path in the 'schema_json/' directory -// See: https://github.com/joelittlejohn/jsonschema2pojo/wiki/Reference - -final def targetDirectoryPath = file("${project.buildDir}/generated-sources/schemajson").getPath() - -task generatePOJOs() { - final def jsonSourceDirectory = file("${project.projectDir}/schema_json").getPath() - final def jsonPackageNamePrefix = "net.jacobpeterson.alpaca.model" - - // Loop through all files in schema JSON file tree - file(jsonSourceDirectory).eachFileRecurse { jsonFile -> - if (jsonFile.getName().endsWith(".json")) { - def startPackageIndex = jsonFile.getAbsolutePath().indexOf(jsonSourceDirectory) + jsonSourceDirectory.length() - def targetPackage = jsonPackageNamePrefix + jsonFile.getParentFile().getAbsolutePath() - .substring(startPackageIndex) - .replace(File.separator, '.').replace('-', '').replace('_', '') - .toLowerCase() - - def jsonToPOJOTask = tasks.create('json2POJOTask-' + targetPackage + "." + jsonFile.getName(), - GenerateJsonSchemaJavaTask) - - // Configure this task to participate in incremental builds so that it only executes when changes occur - jsonToPOJOTask.configure { - inputs.file(jsonFile) - outputs.dir(targetDirectoryPath) +publishing { + publications { + mavenJava(MavenPublication) { + groupId = projectGroup + artifactId = projectArtifactID + version = projectVersion + + pom { + name = projectArtifactID + description = "A Java API for Alpaca, the commission free, algo friendly, stock trading broker." + url = "https://github.com/Petersoj/alpaca-java" + inceptionYear = "2018" + + licenses { + license { + name = "MIT License" + url = "https://opensource.org/licenses/MIT" + } + } + + developers { + developer { + id = "Petersoj" + name = "Jacob Peterson" + } + developer { + id = "mainstringargs" + name = "main(String[] args)" + } + } + + scm { + url = "https://github.com/Petersoj/alpaca-java.git" + connection = "scm:git:https://github.com/Petersoj/alpaca-java.git" + developerConnection = "scm:git:git@github.com/Petersoj/alpaca-java.git" + } } + } + } - jsonToPOJOTask.doFirst { - jsonToPOJOTask.configuration.source = files(jsonFile.getAbsolutePath()) - jsonToPOJOTask.configuration.targetDirectory = file(targetDirectoryPath) - jsonToPOJOTask.configuration.targetPackage = targetPackage + repositories { + maven { + name = "OSSRH" + url = projectVersion.contains("SNAPSHOT") ? "https://oss.sonatype.org/content/repositories/snapshots/" : + "https://oss.sonatype.org/service/local/staging/deploy/maven2/" + credentials { + username = project.property("nexus.username") + password = project.property("nexus.password") } - - dependsOn jsonToPOJOTask } } } -compileJava { - dependsOn generatePOJOs +signing { + sign publishing.publications.mavenJava } -sourceSets { - main { - java { - srcDir targetDirectoryPath - } - } -} - -jsonSchema2Pojo { - includeAdditionalProperties = true - targetDirectory = file(targetDirectoryPath) - propertyWordDelimiters = ['-', '_'] as char[] - annotationStyle = 'gson' - sourceType = 'jsonschema' - customDateTimePattern = "yyyy-MM-ddTHH:mm:ssZ" - includeGeneratedAnnotation = false - includeConstructors = true - serializable = true - includeGetters = true - includeSetters = true - includeCopyConstructor = true - generateBuilders = true +// Used to prevent accidental publication of Alpaca keys +processResources { + exclude("alpaca.properties") } // -// END POJO generation +// END Publishing // diff --git a/buildSrc/build.gradle b/buildSrc/build.gradle new file mode 100644 index 00000000..1ca86d32 --- /dev/null +++ b/buildSrc/build.gradle @@ -0,0 +1,29 @@ +plugins { + id "groovy-gradle-plugin" +} + +gradlePlugin { + plugins { + jsonschema2pojoAdapted { + id = "jsonschema2pojo-adapted" + implementationClass = + "net.jacobpeterson.alpaca.buildsrc.plugin.jsonschema2pojoadapted.JSONSchema2POJOAdaptedPlugin" + } + } +} + +repositories { + mavenCentral() + gradlePluginPortal() +} + +dependencies { + gradleApi() + + // Google Guava + implementation group: "com.google.guava", name: "guava", version: "33.0.0-jre" + + // JsonSchema2Pojo + implementation group: "org.jsonschema2pojo", name: "jsonschema2pojo-core", version: "1.2.1" + implementation group: "org.jsonschema2pojo", name: "jsonschema2pojo-gradle-plugin", version: "1.2.1" +} diff --git a/buildSrc/src/main/groovy/net/jacobpeterson/alpaca/buildsrc/plugin/jsonschema2pojoadapted/GeneratePOJOsTask.groovy b/buildSrc/src/main/groovy/net/jacobpeterson/alpaca/buildsrc/plugin/jsonschema2pojoadapted/GeneratePOJOsTask.groovy new file mode 100644 index 00000000..88ac5a4a --- /dev/null +++ b/buildSrc/src/main/groovy/net/jacobpeterson/alpaca/buildsrc/plugin/jsonschema2pojoadapted/GeneratePOJOsTask.groovy @@ -0,0 +1,128 @@ +package net.jacobpeterson.alpaca.buildsrc.plugin.jsonschema2pojoadapted + +import org.gradle.api.DefaultTask +import org.gradle.api.model.ReplacedBy +import org.gradle.api.tasks.Input +import org.gradle.api.tasks.Internal +import org.gradle.api.tasks.SourceSetContainer +import org.gradle.api.tasks.TaskAction +import org.jsonschema2pojo.GenerationConfig +import org.jsonschema2pojo.Jsonschema2Pojo +import org.jsonschema2pojo.gradle.GradleRuleLogger + +import java.nio.file.Files +import java.nio.file.Path + +import static JSONSchema2POJOAdaptedPlugin.EXTENSION_KEY +import static com.google.common.base.Preconditions.checkArgument + +/** + * {@link GeneratePOJOsTask} is a Gradle task that invokes jsonschema2pojo with special configurations + * and generation logic. + * TODO publish this plugin somewhere so the code doesn't have to be copied between 'cli' and 'system' Gradle projects. + * @see GenerateJsonSchemaJavaTask + */ +class GeneratePOJOsTask extends DefaultTask { + + @ReplacedBy("configurationString") + GenerationConfig configuration + + @Input + String getConfigurationString() { + configuration.toString() + } + + @Internal + List jsonSchemaFileConfigs + + GeneratePOJOsTask() { + group = "Build" + description = "Generates Java POJOs from JSON schemas." + + project.afterEvaluate { + // Validate plugin configuration + configuration = project.extensions.getByName(EXTENSION_KEY) as GenerationConfig + checkArgument(!configuration.source.hasNext(), "'source' should not be set for '${name}'.") + checkArgument(configuration.targetDirectory == null, "'targetDirectory' should not be set for '${name}'.") + + // Set custom defaults on plugin configuration + configuration.targetDirectory = project.file( + "${project.layout.buildDirectory.getAsFile().get().getPath()}/generated-sources/jsonschema2pojo") + configuration.targetPackage = configuration.targetPackage == "" ? + "${project.group}.model" : configuration.targetPackage + configuration.propertyWordDelimiters = ["-", "_"] as char[] + configuration.annotationStyle = "gson" + configuration.sourceType = "jsonschema" + configuration.customDateTimePattern = "yyyy-MM-ddTHH:mm:ssZ" + configuration.includeConstructors = true + configuration.serializable = true + configuration.includeGetters = true + configuration.includeSetters = true + configuration.includeCopyConstructor = true + configuration.generateBuilders = true + + // Get project main java source set + final def projectMainJavaSourceSet = + project.extensions.getByType(SourceSetContainer).named("main").get().java + + // Set task dependents and dependencies + project.tasks.named("compileJava").get().dependsOn(this) + dependsOn(project.tasks.named("processResources").get()) + + // Walk through source files to create a list of JSON schema files to process + jsonSchemaFileConfigs = new ArrayList<>() + projectMainJavaSourceSet.srcDirs.each { srcDir -> + if (!srcDir.exists()) { + return + } + final def sourceDirPath = srcDir.getAbsolutePath() + srcDir.eachFileRecurse { sourceFile -> + final def sourceFilePath = sourceFile.getAbsolutePath() + if (sourceFilePath.endsWith(".json")) { + def targetPackage = sourceFile.getParentFile().getAbsolutePath() + .substring(sourceDirPath.length()) + .replace(File.separator, ".").replace("-", "").replace("_", "") + .toLowerCase() + targetPackage = targetPackage.replace("${project.group}", "") + targetPackage = targetPackage.startsWith(".") ? targetPackage.substring(1) : targetPackage + targetPackage = configuration.targetPackage + + (targetPackage.isEmpty() ? "" : targetPackage) + jsonSchemaFileConfigs.add(new JSONSchemaFileConfig(sourceFile, targetPackage)) + + inputs.file(sourceFile) + outputs.file(project.file(configuration.targetDirectory.getAbsolutePath() + File.separator + + targetPackage.replace(".", File.separator) + File.separator + + sourceFile.getName().replace(".json", ".java"))) + } + } + } + + outputs.dir(configuration.targetDirectory) + outputs.cacheIf { true } + + projectMainJavaSourceSet.srcDirs(configuration.targetDirectory) + } + } + + @TaskAction + def execute() { + logger.info("Deleting existing generated POJOs...") + Files.walk(configuration.targetDirectory.toPath()) + .sorted(Comparator.reverseOrder()) + .map(Path::toFile) + .peek(file -> logger.info("Deleting file: {}", file.getPath())) + .forEach(File::delete) + logger.info("Deleted existing generated POJOs.") + + logger.info("Generating POJOs from JSON schemas...") + final def gradleRuleLogger = new GradleRuleLogger(logger) + jsonSchemaFileConfigs.forEach { jsonSchemaFileConfig -> + configuration.source = List.of(jsonSchemaFileConfig.sourceFile.toURI().toURL()) + configuration.targetPackage = jsonSchemaFileConfig.targetPackage + Jsonschema2Pojo.generate(configuration, gradleRuleLogger) + logger.info("Generated POJO for: {}", jsonSchemaFileConfig.targetPackage + "." + + jsonSchemaFileConfig.sourceFile.getName()) + } + logger.info("Generated POJOs from JSON schemas.") + } +} diff --git a/buildSrc/src/main/groovy/net/jacobpeterson/alpaca/buildsrc/plugin/jsonschema2pojoadapted/JSONSchema2POJOAdaptedPlugin.groovy b/buildSrc/src/main/groovy/net/jacobpeterson/alpaca/buildsrc/plugin/jsonschema2pojoadapted/JSONSchema2POJOAdaptedPlugin.groovy new file mode 100644 index 00000000..9d53a826 --- /dev/null +++ b/buildSrc/src/main/groovy/net/jacobpeterson/alpaca/buildsrc/plugin/jsonschema2pojoadapted/JSONSchema2POJOAdaptedPlugin.groovy @@ -0,0 +1,26 @@ +package net.jacobpeterson.alpaca.buildsrc.plugin.jsonschema2pojoadapted + +import org.gradle.api.GradleException +import org.gradle.api.Plugin +import org.gradle.api.Project +import org.jsonschema2pojo.gradle.JsonSchemaExtension + +/** + * {@link JSONSchema2POJOAdaptedPlugin} is a Gradle plugin that adapts the jsonschema2pojo Gradle plugin + * to add/change some features. + */ +class JSONSchema2POJOAdaptedPlugin implements Plugin { + + public static final EXTENSION_KEY = "jsonSchema2POJOAdaptedConfig" + + @Override + void apply(Project project) { + project.extensions.create(EXTENSION_KEY, JsonSchemaExtension) + if (project.plugins.hasPlugin("java")) { + final def generatePOJOsTask = project.tasks.register("generatePOJOs", GeneratePOJOsTask) + generatePOJOsTask.get().enabled = true + } else { + throw new GradleException("'java' Gradle plugin required.") + } + } +} diff --git a/buildSrc/src/main/groovy/net/jacobpeterson/alpaca/buildsrc/plugin/jsonschema2pojoadapted/JSONSchemaFileConfig.groovy b/buildSrc/src/main/groovy/net/jacobpeterson/alpaca/buildsrc/plugin/jsonschema2pojoadapted/JSONSchemaFileConfig.groovy new file mode 100644 index 00000000..698a30b9 --- /dev/null +++ b/buildSrc/src/main/groovy/net/jacobpeterson/alpaca/buildsrc/plugin/jsonschema2pojoadapted/JSONSchemaFileConfig.groovy @@ -0,0 +1,31 @@ +package net.jacobpeterson.alpaca.buildsrc.plugin.jsonschema2pojoadapted + +/** + * {@link JSONSchemaFileConfig} provides the configuration fields for a single JSON schema file. + */ +class JSONSchemaFileConfig { + + private File sourceFile + private String targetPackage + private String outputFilePath + + /** + * Instantiates a new {@link JSONSchemaFileConfig}. + * + * @param sourceFile the source file + * @param targetPackage the target package + */ + JSONSchemaFileConfig(File sourceFile, String targetPackage) { + this.sourceFile = sourceFile + this.targetPackage = targetPackage + this.outputFilePath = outputFilePath + } + + File getSourceFile() { + return sourceFile + } + + String getTargetPackage() { + return targetPackage + } +} diff --git a/codecov.yml b/codecov.yml deleted file mode 100644 index c749985f..00000000 --- a/codecov.yml +++ /dev/null @@ -1,6 +0,0 @@ -coverage: - status: - # Turn off Codecov status for now - project: off - patch: off -comment: false \ No newline at end of file diff --git a/gradle/wrapper/.gitignore b/gradle/wrapper/.gitignore new file mode 100644 index 00000000..351625b1 --- /dev/null +++ b/gradle/wrapper/.gitignore @@ -0,0 +1 @@ +!gradle-wrapper.properties diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 1f3fdbc5..2ea3535d 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,5 +1,7 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-6.7.1-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.6-all.zip +networkTimeout=10000 +validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/schema_json/endpoint/account/account.json b/schema_json/endpoint/account/account.json deleted file mode 100644 index 7e558fe2..00000000 --- a/schema_json/endpoint/account/account.json +++ /dev/null @@ -1,110 +0,0 @@ -{ - "type": "object", - "title": "See Account.", - "properties": { - "id": { - "existingJavaType": "java.lang.String", - "title": "Account ID." - }, - "account_number": { - "existingJavaType": "java.lang.String", - "title": "Account number." - }, - "status": { - "existingJavaType": "net.jacobpeterson.alpaca.model.endpoint.account.enums.AccountStatus", - "title": "The {@link net.jacobpeterson.alpaca.model.endpoint.account.enums.AccountStatus}." - }, - "currency": { - "existingJavaType": "java.lang.String", - "title": "\"USD\"" - }, - "cash": { - "existingJavaType": "java.lang.String", - "title": "Cash balance." - }, - "portfolio_value": { - "existingJavaType": "java.lang.String", - "title": "Total value of cash + holding positions. (This field is deprecated. It is equivalent to the equity field.)" - }, - "pattern_day_trader": { - "existingJavaType": "java.lang.Boolean", - "title": "Whether or not the account has been flagged as a pattern day trader." - }, - "trade_suspended_by_user": { - "existingJavaType": "java.lang.Boolean", - "title": "User setting. If true, the account is not allowed to place orders." - }, - "trading_blocked": { - "existingJavaType": "java.lang.Boolean", - "title": "If true, the account is not allowed to place orders." - }, - "transfers_blocked": { - "existingJavaType": "java.lang.Boolean", - "title": "If true, the account is not allowed to request money transfers." - }, - "account_blocked": { - "existingJavaType": "java.lang.Boolean", - "title": "If true, the account activity by user is prohibited." - }, - "created_at": { - "existingJavaType": "java.time.ZonedDateTime", - "title": "Timestamp this account was created at." - }, - "shorting_enabled": { - "existingJavaType": "java.lang.Boolean", - "title": "Flag to denote whether or not the account is permitted to short." - }, - "long_market_value": { - "existingJavaType": "java.lang.String", - "title": "Realtime MtM value of all long positions held in the account." - }, - "short_market_value": { - "existingJavaType": "java.lang.String", - "title": "Realtime MtM value of all short positions held in the account." - }, - "equity": { - "existingJavaType": "java.lang.String", - "title": "Cash + long_market_value + short_market_value." - }, - "last_equity": { - "existingJavaType": "java.lang.String", - "title": "Equity as of previous trading day at 16:00:00 ET." - }, - "multiplier": { - "existingJavaType": "java.lang.String", - "title": "Buying power multiplier that represents account margin classification; valid values 1 (standard limited margin account with 1x buying power), 2 (reg T margin account with 2x intraday and overnight buying power; this is the default for all non-PDT accounts with $2,000 or more equity), 4 (PDT account with 4x intraday buying power and 2x reg T overnight buying power)." - }, - "buying_power": { - "existingJavaType": "java.lang.String", - "title": "Current available $ buying power; If multiplier = 4, this is your daytrade buying power which is calculated as (last_equity - (last) maintenance_margin) * 4; If multiplier = 2, buying_power = max(equity – initial_margin,0) * 2; If multiplier = 1, buying_power = cash." - }, - "initial_margin": { - "existingJavaType": "java.lang.String", - "title": "Reg T initial margin requirement (continuously updated value)." - }, - "maintenance_margin": { - "existingJavaType": "java.lang.String", - "title": "Maintenance margin requirement (continuously updated value)." - }, - "sma": { - "existingJavaType": "java.lang.String", - "title": "Value of special memorandum account (will be used at a later date to provide additional buying_power)." - }, - "daytrade_count": { - "existingJavaType": "java.lang.Integer", - "title": "The current number of daytrades that have been made in the last 5 trading days (inclusive of today)." - }, - "last_maintenance_margin": { - "existingJavaType": "java.lang.String", - "title": "Your maintenance margin requirement on the previous trading day." - }, - "daytrading_buying_power": { - "existingJavaType": "java.lang.String", - "title": "Your buying power for day trades (continuously updated value)." - }, - "regt_buying_power": { - "existingJavaType": "java.lang.String", - "title": "Your buying power under Regulation T (your excess equity - equity minus margin value - times your margin multiplier)." - } - } -} diff --git a/schema_json/endpoint/account/enums/account_status.json b/schema_json/endpoint/account/enums/account_status.json deleted file mode 100644 index 61f7a081..00000000 --- a/schema_json/endpoint/account/enums/account_status.json +++ /dev/null @@ -1,44 +0,0 @@ -{ - "type": "string", - "title": "See Account.", - "enum": [ - "ONBOARDING", - "SUBMISSION_FAILED", - "SUBMITTED", - "ACCOUNT_UPDATED", - "APPROVAL_PENDING", - "ACTIVE", - "REJECTED", - "DISABLED", - "ACTION_REQUIRED" - ], - "javaEnums": [ - { - "description": "The account is onboarding." - }, - { - "description": "The account application submission failed for some reason." - }, - { - "description": "The account application has been submitted for review." - }, - { - "description": "The account information is being updated." - }, - { - "description": "The final account approval is pending." - }, - { - "description": "The account is active for trading." - }, - { - "description": "The account application has been rejected." - }, - { - "description": "The account has been disabled." - }, - { - "description": "The account requires manual attention." - } - ] -} diff --git a/schema_json/endpoint/account_activities/account_activity.json b/schema_json/endpoint/account_activities/account_activity.json deleted file mode 100644 index bfa36b8f..00000000 --- a/schema_json/endpoint/account_activities/account_activity.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "type": "object", - "title": "See Account Activities.", - "properties": { - "activity_type": { - "existingJavaType": "net.jacobpeterson.alpaca.model.endpoint.accountactivities.enums.ActivityType", - "title": "The {@link net.jacobpeterson.alpaca.model.endpoint.accountactivities.enums.ActivityType}." - }, - "id": { - "existingJavaType": "java.lang.String", - "title": "An ID for the activity, always in \"::\" format. Can be sent as page_token in requests to facilitate the paging of results." - } - } -} diff --git a/schema_json/endpoint/account_activities/enums/activity_type.json b/schema_json/endpoint/account_activities/enums/activity_type.json deleted file mode 100644 index 9fae432b..00000000 --- a/schema_json/endpoint/account_activities/enums/activity_type.json +++ /dev/null @@ -1,152 +0,0 @@ -{ - "type": "string", - "title": "See Account Activities.", - "enum": [ - "FILL", - "TRANS", - "MISC", - "ACATC", - "ACATS", - "CSD", - "CSR", - "CSW", - "DIV", - "DIVCGL", - "DIVCGS", - "DIVFEE", - "DIVFT", - "DIVNRA", - "DIVROC", - "DIVTW", - "DIVTXEX", - "INT", - "INTNRA", - "INTTW", - "JNL", - "JNLC", - "JNLS", - "MA", - "NC", - "OPASN", - "OPEXP", - "OPXRC", - "PTC", - "PTR", - "FEE", - "REORG", - "SC", - "SSO", - "SSP", - "SPLIT" - ], - "javaEnums": [ - { - "description": "Order fills (both partial and full fills)." - }, - { - "description": "Cash transactions (both CSD and CSW)." - }, - { - "description": "Miscellaneous or rarely used activity types (All types except those in TRANS, DIV, or FILL)." - }, - { - "description": "ACATS IN/OUT (Cash)." - }, - { - "description": "ACATS IN/OUT (Securities)." - }, - { - "description": "Cash deposit(+)." - }, - { - "description": "Cash receipt(-)." - }, - { - "description": "Cash withdrawal(-)." - }, - { - "description": "Dividends." - }, - { - "description": "Dividend (capital gain long term)." - }, - { - "description": "Dividend (capital gain short term)." - }, - { - "description": "Dividend fee." - }, - { - "description": "Dividend adjusted (Foreign Tax Withheld)." - }, - { - "description": "Dividend adjusted (NRA Withheld)." - }, - { - "description": "Dividend return of capital." - }, - { - "description": "Dividend adjusted (Tefra Withheld)." - }, - { - "description": "Dividend (tax exempt)." - }, - { - "description": "Interest (credit/margin)." - }, - { - "description": "Interest adjusted (NRA Withheld)." - }, - { - "description": "Interest adjusted (Tefra Withheld)." - }, - { - "description": "Journal entry." - }, - { - "description": "Journal entry (cash)." - }, - { - "description": "Journal entry (stock)." - }, - { - "description": "Merger/Acquisition." - }, - { - "description": "Name change." - }, - { - "description": "Option assignment." - }, - { - "description": "Option expiration." - }, - { - "description": "Option exercise." - }, - { - "description": "Pass Thru Charge." - }, - { - "description": "Pass Thru Rebate." - }, - { - "description": "REG/TAF fees." - }, - { - "description": "Reorg CA." - }, - { - "description": "Symbol change." - }, - { - "description": "Stock spinoff." - }, - { - "description": "Stock split." - }, - { - "description": "Stock split." - } - ] -} diff --git a/schema_json/endpoint/account_activities/enums/trade_activity_side.json b/schema_json/endpoint/account_activities/enums/trade_activity_side.json deleted file mode 100644 index ddcd4869..00000000 --- a/schema_json/endpoint/account_activities/enums/trade_activity_side.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "type": "string", - "title": "See Account Activities.", - "enum": [ - "buy", - "sell" - ] -} diff --git a/schema_json/endpoint/account_activities/enums/trade_activity_type.json b/schema_json/endpoint/account_activities/enums/trade_activity_type.json deleted file mode 100644 index 8b92e2b9..00000000 --- a/schema_json/endpoint/account_activities/enums/trade_activity_type.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "type": "string", - "title": "See Account Activities.", - "enum": [ - "fill", - "partial_fill" - ] -} diff --git a/schema_json/endpoint/account_activities/non_trade_activity.json b/schema_json/endpoint/account_activities/non_trade_activity.json deleted file mode 100644 index 81d8c0fa..00000000 --- a/schema_json/endpoint/account_activities/non_trade_activity.json +++ /dev/null @@ -1,38 +0,0 @@ -{ - "type": "object", - "title": "See Account Activities.", - "extends": { - "existingJavaType": "net.jacobpeterson.alpaca.model.endpoint.accountactivities.AccountActivity" - }, - "properties": { - "date": { - "existingJavaType": "java.lang.String", - "title": "The date on which the activity occurred or on which the transaction associated with the activity settled." - }, - "net_amount": { - "existingJavaType": "java.lang.String", - "title": "The net amount of money (positive or negative) associated with the activity." - }, - "symbol": { - "existingJavaType": "java.lang.String", - "title": "The symbol of the security involved with the activity. Not present for all activity types." - }, - "qty": { - "existingJavaType": "java.lang.String", - "javaName": "quantity", - "title": "For dividend activities, the number of shares that contributed to the payment. Not present for other activity types." - }, - "per_share_amount": { - "existingJavaType": "java.lang.String", - "title": "For dividend activities, the average amount paid per share. Not present for other activity types." - }, - "description": { - "existingJavaType": "java.lang.String", - "title": "Description field for transaction records (TRANS, CSR, CSD)." - }, - "status": { - "existingJavaType": "java.lang.String", - "title": "The status." - } - } -} diff --git a/schema_json/endpoint/account_activities/trade_activity.json b/schema_json/endpoint/account_activities/trade_activity.json deleted file mode 100644 index 1697ad1c..00000000 --- a/schema_json/endpoint/account_activities/trade_activity.json +++ /dev/null @@ -1,48 +0,0 @@ -{ - "type": "object", - "title": "See Account Activities.", - "extends": { - "existingJavaType": "net.jacobpeterson.alpaca.model.endpoint.accountactivities.AccountActivity" - }, - "properties": { - "cum_qty": { - "existingJavaType": "java.lang.String", - "javaName": "cumulativeQuantity", - "title": "The cumulative quantity of shares involved in the execution." - }, - "leaves_qty": { - "existingJavaType": "java.lang.String", - "javaName": "remainingQuantity", - "title": "For partially_filled orders, the quantity of shares that are left to be filled." - }, - "price": { - "existingJavaType": "java.lang.String", - "title": "The per-share price that the trade was executed at." - }, - "qty": { - "existingJavaType": "java.lang.String", - "javaName": "quantity", - "title": "The number of shares involved in the trade execution." - }, - "side": { - "existingJavaType": "net.jacobpeterson.alpaca.model.endpoint.accountactivities.enums.TradeActivitySide", - "title": "The {@link net.jacobpeterson.alpaca.model.endpoint.accountactivities.enums.TradeActivitySide}." - }, - "symbol": { - "existingJavaType": "java.lang.String", - "title": "The symbol of the security being traded." - }, - "transaction_time": { - "existingJavaType": "java.time.ZonedDateTime", - "title": "The time at which the execution occurred." - }, - "order_id": { - "existingJavaType": "java.lang.String", - "title": "The id for the order that filled. Always in GUID format." - }, - "type": { - "existingJavaType": "net.jacobpeterson.alpaca.model.endpoint.accountactivities.enums.TradeActivityType", - "title": "The {@link net.jacobpeterson.alpaca.model.endpoint.accountactivities.enums.TradeActivityType}." - } - } -} diff --git a/schema_json/endpoint/account_configuration/account_configuration.json b/schema_json/endpoint/account_configuration/account_configuration.json deleted file mode 100644 index 704ee4e1..00000000 --- a/schema_json/endpoint/account_configuration/account_configuration.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "type": "object", - "title": "See Account Configuration.", - "properties": { - "dtbp_check": { - "existingJavaType": "net.jacobpeterson.alpaca.model.endpoint.accountconfiguration.enums.DTBPCheck", - "title": "The {@link net.jacobpeterson.alpaca.model.endpoint.accountconfiguration.enums.DTBPCheck}. Controls Day Trading Margin Call (DTMC) checks." - }, - "trade_confirm_email": { - "existingJavaType": "net.jacobpeterson.alpaca.model.endpoint.accountconfiguration.enums.TradeConfirmEmail", - "title": "The {@link net.jacobpeterson.alpaca.model.endpoint.accountconfiguration.enums.TradeConfirmEmail}. If {@link net.jacobpeterson.alpaca.model.endpoint.accountconfiguration.enums.TradeConfirmEmail#NONE}, emails for order fills are not sent." - }, - "suspend_trade": { - "existingJavaType": "java.lang.Boolean", - "title": "If true, new orders are blocked." - }, - "no_shorting": { - "existingJavaType": "java.lang.Boolean", - "title": "If true, account becomes long-only mode." - } - } -} diff --git a/schema_json/endpoint/account_configuration/enums/dtbp_check.json b/schema_json/endpoint/account_configuration/enums/dtbp_check.json deleted file mode 100644 index 75940d81..00000000 --- a/schema_json/endpoint/account_configuration/enums/dtbp_check.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "type": "string", - "javaName": "DTBPCheck", - "title": "See Account Configuration.", - "enum": [ - "both", - "entry", - "exit" - ] -} diff --git a/schema_json/endpoint/account_configuration/enums/trade_confirm_email.json b/schema_json/endpoint/account_configuration/enums/trade_confirm_email.json deleted file mode 100644 index 8f28197c..00000000 --- a/schema_json/endpoint/account_configuration/enums/trade_confirm_email.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "type": "string", - "title": "See Account Configuration.", - "enum": [ - "all", - "none" - ] -} diff --git a/schema_json/endpoint/assets/asset.json b/schema_json/endpoint/assets/asset.json deleted file mode 100644 index fa65e096..00000000 --- a/schema_json/endpoint/assets/asset.json +++ /dev/null @@ -1,51 +0,0 @@ -{ - "type": "object", - "title": "See Assets.", - "properties": { - "id": { - "existingJavaType": "java.lang.String", - "title": "Asset ID." - }, - "class": { - "existingJavaType": "net.jacobpeterson.alpaca.model.endpoint.assets.enums.AssetClass", - "javaName": "assetClass", - "title": "The {@link net.jacobpeterson.alpaca.model.endpoint.assets.enums.AssetClass}." - }, - "exchange": { - "existingJavaType": "java.lang.String", - "title": "AMEX, ARCA, BATS, NYSE, NASDAQ or NYSEARCA." - }, - "symbol": { - "existingJavaType": "java.lang.String", - "title": "The symbol." - }, - "name": { - "existingJavaType": "java.lang.String", - "title": "The official name of the asset." - }, - "status": { - "existingJavaType": "net.jacobpeterson.alpaca.model.endpoint.assets.enums.AssetStatus", - "title": "The {@link net.jacobpeterson.alpaca.model.endpoint.assets.enums.AssetStatus}." - }, - "tradable": { - "existingJavaType": "java.lang.Boolean", - "title": "Asset is tradable on Alpaca or not." - }, - "marginable": { - "existingJavaType": "java.lang.Boolean", - "title": "Asset is marginable or not." - }, - "shortable": { - "existingJavaType": "java.lang.Boolean", - "title": "Asset is shortable or not." - }, - "easy_to_borrow": { - "existingJavaType": "java.lang.Boolean", - "title": "Asset is easy-to-borrow or not (filtering for easy_to_borrow = True is the best way to check whether the name is currently available to short at Alpaca)." - }, - "fractionable": { - "existingJavaType": "java.lang.Boolean", - "title": "Asset is fractionable or not." - } - } -} diff --git a/schema_json/endpoint/assets/enums/asset_class.json b/schema_json/endpoint/assets/enums/asset_class.json deleted file mode 100644 index ac36f8e7..00000000 --- a/schema_json/endpoint/assets/enums/asset_class.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "type": "string", - "title": "See Assets.", - "enum": [ - "us_equity", - "crypto" - ] -} diff --git a/schema_json/endpoint/assets/enums/asset_status.json b/schema_json/endpoint/assets/enums/asset_status.json deleted file mode 100644 index b77af48e..00000000 --- a/schema_json/endpoint/assets/enums/asset_status.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "type": "string", - "title": "See Assets.", - "enum": [ - "active", - "inactive" - ] -} diff --git a/schema_json/endpoint/calendar/calendar.json b/schema_json/endpoint/calendar/calendar.json deleted file mode 100644 index 96207ded..00000000 --- a/schema_json/endpoint/calendar/calendar.json +++ /dev/null @@ -1,18 +0,0 @@ -{ - "type": "object", - "title": "See Calendar.", - "properties": { - "date": { - "existingJavaType": "java.time.LocalDate", - "title": "Date string in \"%Y-%m-%d\" format." - }, - "open": { - "existingJavaType": "java.time.LocalTime", - "title": "The time the market opens at on this date in \"%H:%M\" format." - }, - "close": { - "existingJavaType": "java.time.LocalTime", - "title": "The time the market closes at on this date in \"%H:%M\" format." - } - } -} diff --git a/schema_json/endpoint/clock/clock.json b/schema_json/endpoint/clock/clock.json deleted file mode 100644 index 671d4a19..00000000 --- a/schema_json/endpoint/clock/clock.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "type": "object", - "title": "See Clock.", - "properties": { - "timestamp": { - "existingJavaType": "java.time.ZonedDateTime", - "title": "Current timestamp." - }, - "is_open": { - "existingJavaType": "java.lang.Boolean", - "title": "Whether or not the market is open." - }, - "next_open": { - "existingJavaType": "java.time.ZonedDateTime", - "title": "Next market open timestamp." - }, - "next_close": { - "existingJavaType": "java.time.ZonedDateTime", - "title": "Next market close timestamp." - } - } -} diff --git a/schema_json/endpoint/common/enums/sort_direction.json b/schema_json/endpoint/common/enums/sort_direction.json deleted file mode 100644 index 01e5a4eb..00000000 --- a/schema_json/endpoint/common/enums/sort_direction.json +++ /dev/null @@ -1,16 +0,0 @@ -{ - "type": "string", - "title": "Defines enums for sort directions.", - "enum": [ - "asc", - "desc" - ], - "javaEnums": [ - { - "name": "ASCENDING" - }, - { - "name": "DESCENDING" - } - ] -} diff --git a/schema_json/endpoint/corporate_actions/common/announcement.json b/schema_json/endpoint/corporate_actions/common/announcement.json deleted file mode 100644 index 14c8e66e..00000000 --- a/schema_json/endpoint/corporate_actions/common/announcement.json +++ /dev/null @@ -1,53 +0,0 @@ -{ - "type": "object", - "title": "See Corporate announcement.", - "properties": { - "id": { - "existingJavaType": "java.lang.String" - }, - "corporate_actions_id": { - "existingJavaType": "java.lang.String" - }, - "ca_type": { - "existingJavaType": "java.lang.String" - }, - "ca_sub_type": { - "existingJavaType": "java.lang.String" - }, - "initiating_symbol": { - "existingJavaType": "java.lang.String" - }, - "initiating_original_cusip": { - "existingJavaType": "java.lang.String" - }, - "target_symbol": { - "target_symbol": "java.lang.String" - }, - "target_original_cusip": { - "target_original_cusip": "java.lang.String" - }, - "declaration_date": { - "declaration_date": "java.lang.String" - }, - "expiration_date": { - "declaration_date": "java.lang.String" - }, - "record_date": { - "declaration_date": "java.lang.String" - }, - "payable_date": { - "declaration_date": "java.lang.String" - }, - "cash": { - "declaration_date": "java.lang.String" - }, - "old_rate": { - "declaration_date": "java.lang.String" - }, - "new_rate": { - "declaration_date": "java.lang.String" - } - - - } -} diff --git a/schema_json/endpoint/orders/cancelled_order.json b/schema_json/endpoint/orders/cancelled_order.json deleted file mode 100644 index cd34b921..00000000 --- a/schema_json/endpoint/orders/cancelled_order.json +++ /dev/null @@ -1,19 +0,0 @@ -{ - "type": "object", - "title": "See Orders.", - "properties": { - "id": { - "existingJavaType": "java.lang.String", - "title": "Order ID." - }, - "status": { - "existingJavaType": "java.lang.Integer", - "title": "The HTTP status code of the cancelled order." - }, - "body": { - "existingJavaType": "net.jacobpeterson.alpaca.model.endpoint.orders.Order", - "javaName": "order", - "title": "The cancelled {@link net.jacobpeterson.alpaca.model.endpoint.orders.Order}." - } - } -} diff --git a/schema_json/endpoint/orders/enums/current_order_status.json b/schema_json/endpoint/orders/enums/current_order_status.json deleted file mode 100644 index 25a5829f..00000000 --- a/schema_json/endpoint/orders/enums/current_order_status.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "type": "string", - "title": "See Orders.", - "enum": [ - "open", - "closed", - "all" - ] -} diff --git a/schema_json/endpoint/orders/enums/order_class.json b/schema_json/endpoint/orders/enums/order_class.json deleted file mode 100644 index 6baaf86c..00000000 --- a/schema_json/endpoint/orders/enums/order_class.json +++ /dev/null @@ -1,24 +0,0 @@ -{ - "type": "string", - "title": "See Orders.", - "enum": [ - "simple", - "bracket", - "oco", - "oto" - ], - "javaEnums": [ - { - "name": "SIMPLE" - }, - { - "name": "BRACKET" - }, - { - "name": "ONE_CANCELS_OTHER" - }, - { - "name": "ONE_TRIGGERS_OTHER" - } - ] -} diff --git a/schema_json/endpoint/orders/enums/order_side.json b/schema_json/endpoint/orders/enums/order_side.json deleted file mode 100644 index e8b6178f..00000000 --- a/schema_json/endpoint/orders/enums/order_side.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "type": "string", - "title": "See Orders.", - "enum": [ - "buy", - "sell" - ] -} diff --git a/schema_json/endpoint/orders/enums/order_status.json b/schema_json/endpoint/orders/enums/order_status.json deleted file mode 100644 index 5174e120..00000000 --- a/schema_json/endpoint/orders/enums/order_status.json +++ /dev/null @@ -1,76 +0,0 @@ -{ - "type": "string", - "title": "See Orders.", - "enum": [ - "new", - "partially_filled", - "filled", - "done_for_day", - "canceled", - "expired", - "replaced", - "pending_cancel", - "pending_replace", - "accepted", - "pending_new", - "accepted_for_bidding", - "stopped", - "rejected", - "suspended", - "calculated", - "held" - ], - "javaEnums": [ - { - "description": "The order has been received by Alpaca, and routed to exchanges for execution. This is the usual initial state of an order." - }, - { - "description": "The order has been partially filled." - }, - { - "description": "The order has been filled, and no further updates will occur for the order." - }, - { - "description": "The order is done executing for the day, and will not receive further updates until the next trading day." - }, - { - "description": "The order has been canceled, and no further updates will occur for the order. This can be either due to a cancel request by the user, or the order has been canceled by the exchanges due to its time-in-force." - }, - { - "description": "The order has expired, and no further updates will occur for the order." - }, - { - "description": "The order was replaced by another order, or was updated due to a market event such as corporate action." - }, - { - "description": "The order is waiting to be canceled." - }, - { - "description": "The order is waiting to be replaced by another order. The order will reject cancel request while in this state." - }, - { - "description": "The order has been received by Alpaca, but hasn’t yet been routed to the execution venue. This could be seen often out side of trading session hours." - }, - { - "description": "The order has been received by Alpaca, and routed to the exchanges, but has not yet been accepted for execution. This state only occurs on rare occasions." - }, - { - "description": "The order has been received by exchanges, and is evaluated for pricing. This state only occurs on rare occasions." - }, - { - "description": "The order has been stopped, and a trade is guaranteed for the order, usually at a stated price or better, but has not yet occurred. This state only occurs on rare occasions." - }, - { - "description": "The order has been rejected, and no further updates will occur for the order. This state occurs on rare occasions and may occur based on various conditions decided by the exchanges." - }, - { - "description": "The order has been suspended, and is not eligible for trading. This state only occurs on rare occasions." - }, - { - "description": "The order has been completed for the day (either filled or done for day), but remaining settlement calculations are still pending. This state only occurs on rare occasions." - }, - { - "description": "The order is part of a bracket leg that is waiting for the initial order to be filled." - } - ] -} diff --git a/schema_json/endpoint/orders/enums/order_time_in_force.json b/schema_json/endpoint/orders/enums/order_time_in_force.json deleted file mode 100644 index fe468cd2..00000000 --- a/schema_json/endpoint/orders/enums/order_time_in_force.json +++ /dev/null @@ -1,38 +0,0 @@ -{ - "type": "string", - "title": "See Orders.", - "enum": [ - "day", - "gtc", - "opg", - "cls", - "ioc", - "fok" - ], - "javaEnums": [ - { - "name": "DAY", - "description": "A day order is eligible for execution only on the day it is live. By default, the order is only valid during Regular Trading Hours (9:30am - 4:00pm ET). If unfilled after the closing auction, it is automatically canceled. If submitted after the close, it is queued and submitted the following trading day. However, if marked as eligible for extended hours, the order can also execute during supported extended hours." - }, - { - "name": "GOOD_UNTIL_CANCELLED", - "description": "The order is good until canceled. Non-marketable GTC limit orders are subject to price adjustments to offset corporate actions affecting the issue. We do not currently support Do Not Reduce(DNR) orders to opt out of such price adjustments." - }, - { - "name": "OPG", - "description": "Use this TIF with a market/limit order type to submit \"market on open\" (MOO) and \"limit on open\" (LOO) orders. This order is eligible to execute only in the market opening auction. Any unfilled orders after the open will be cancelled. OPG orders submitted after 9:28am but before 7:00pm ET will be rejected. OPG orders submitted after 7:00pm will be queued and routed to the following day’s opening auction. On open/on close orders are routed to the primary exchange. Such orders do not necessarily execute exactly at 9:30am / 4:00pm ET but execute per the exchange’s auction rules." - }, - { - "name": "CLS", - "description": "Use this TIF with a market/limit order type to submit \"market on close\" (MOC) and \"limit on close\" (LOC) orders. This order is eligible to execute only in the market closing auction. Any unfilled orders after the close will be cancelled. CLS orders submitted after 3:50pm but before 7:00pm ET will be rejected. CLS orders submitted after 7:00pm will be queued and routed to the following day’s closing auction. Only available with API v2." - }, - { - "name": "IMMEDIATE_OR_CANCEL", - "description": "An Immediate Or Cancel (IOC) order requires all or part of the order to be executed immediately. Any unfilled portion of the order is canceled. Only available with API v2. Most market makers who receive IOC orders will attempt to fill the order on a principal basis only, and cancel any unfilled balance. On occasion, this can result in the entire order being cancelled if the market maker does not have any existing inventory of the security in question." - }, - { - "name": "FILL_OR_KILL", - "description": "A Fill or Kill (FOK) order is only executed if the entire order quantity can be filled, otherwise the order is canceled. Only available with API v2." - } - ] -} diff --git a/schema_json/endpoint/orders/enums/order_type.json b/schema_json/endpoint/orders/enums/order_type.json deleted file mode 100644 index 32d93d2b..00000000 --- a/schema_json/endpoint/orders/enums/order_type.json +++ /dev/null @@ -1,28 +0,0 @@ -{ - "type": "string", - "title": "See Orders.", - "enum": [ - "market", - "limit", - "stop", - "stop_limit", - "trailing_stop" - ], - "javaEnums": [ - { - "description": "A market order is a request to buy or sell a security at the currently available market price. It provides the most likely method of filling an order. Market orders fill nearly instantaneously." - }, - { - "description": "A limit order is an order to buy or sell at a specified price or better. A buy limit order (a limit order to buy) is executed at the specified limit price or lower (i.e., better). Conversely, a sell limit order (a limit order to sell) is executed at the specified limit price or higher (better). Unlike a market order, you have to specify the limit price parameter when submitting your order." - }, - { - "description": "A stop (market) order is an order to buy or sell a security when its price moves past a particular point, ensuring a higher probability of achieving a predetermined entry or exit price. Once the market price crosses the specified stop price, the stop order becomes a market order. Alpaca converts buy stop orders into stop limit orders with a limit price that is 4% higher than a stop price < $50 (or 2.5% higher than a stop price >= $50). Sell stop orders are not converted into stop limit orders." - }, - { - "description": "A stop-limit order is a conditional trade over a set time frame that combines the features of a stop order with those of a limit order and is used to mitigate risk. The stop-limit order will be executed at a specified limit price, or better, after a given stop price has been reached. Once the stop price is reached, the stop-limit order becomes a limit order to buy or sell at the limit price or better" - }, - { - "description": "Trailing stop orders allow you to continuously and automatically keep updating the stop price threshold based on the stock price movement. You request a single order with a dollar offset value or percentage value as the trail and the actual stop price for this order changes as the stock price moves in your favorable way, or stay at the last level otherwise. This way, you don’t need to monitor the price movement and keep sending replace requests to update the stop price close to the latest market movement." - } - ] -} diff --git a/schema_json/endpoint/orders/order.json b/schema_json/endpoint/orders/order.json deleted file mode 100644 index 6a6f85fd..00000000 --- a/schema_json/endpoint/orders/order.json +++ /dev/null @@ -1,134 +0,0 @@ -{ - "type": "object", - "title": "See Orders.", - "properties": { - "id": { - "existingJavaType": "java.lang.String", - "title": "Order ID." - }, - "client_order_id": { - "existingJavaType": "java.lang.String", - "title": "Client unique order ID." - }, - "created_at": { - "existingJavaType": "java.time.ZonedDateTime", - "title": "Created at." - }, - "updated_at": { - "existingJavaType": "java.time.ZonedDateTime", - "title": "Updated at." - }, - "submitted_at": { - "existingJavaType": "java.time.ZonedDateTime", - "title": "Submitted at." - }, - "filled_at": { - "existingJavaType": "java.time.ZonedDateTime", - "title": "Filled at." - }, - "expired_at": { - "existingJavaType": "java.time.ZonedDateTime", - "title": "Expired at." - }, - "canceled_at": { - "existingJavaType": "java.time.ZonedDateTime", - "title": "Canceled at." - }, - "failed_at": { - "existingJavaType": "java.time.ZonedDateTime", - "title": "Failed at." - }, - "replaced_at": { - "existingJavaType": "java.time.ZonedDateTime", - "title": "Replaced at." - }, - "replaced_by": { - "existingJavaType": "java.lang.String", - "title": "The order ID that this order was replaced by." - }, - "replaces": { - "existingJavaType": "java.lang.String", - "title": "The order ID that this order replaces." - }, - "asset_id": { - "existingJavaType": "java.lang.String", - "title": "Asset ID." - }, - "symbol": { - "existingJavaType": "java.lang.String", - "title": "Asset symbol." - }, - "asset_class": { - "existingJavaType": "java.lang.String", - "title": "Asset class." - }, - "notional": { - "existingJavaType": "java.lang.String", - "title": "Ordered notional amount. If entered, qty will be null. Can take up to 9 decimal points." - }, - "qty": { - "existingJavaType": "java.lang.String", - "javaName": "quantity", - "title": "Ordered quantity. If entered, notional will be null. Can take up to 9 decimal points." - }, - "filled_qty": { - "existingJavaType": "java.lang.String", - "javaName": "filledQuantity", - "title": "Filled quantity." - }, - "filled_avg_price": { - "existingJavaType": "java.lang.String", - "javaName": "averageFillPrice", - "title": "Filled average price." - }, - "order_class": { - "existingJavaType": "net.jacobpeterson.alpaca.model.endpoint.orders.enums.OrderClass", - "title": "The {@link net.jacobpeterson.alpaca.model.endpoint.orders.enums.OrderClass}. For details of non-simple order classes, please see Bracket Order Overview." - }, - "type": { - "existingJavaType": "net.jacobpeterson.alpaca.model.endpoint.orders.enums.OrderType", - "title": "The {@link net.jacobpeterson.alpaca.model.endpoint.orders.enums.OrderType}." - }, - "side": { - "existingJavaType": "net.jacobpeterson.alpaca.model.endpoint.orders.enums.OrderSide", - "title": "The {@link net.jacobpeterson.alpaca.model.endpoint.orders.enums.OrderSide}." - }, - "time_in_force": { - "existingJavaType": "net.jacobpeterson.alpaca.model.endpoint.orders.enums.OrderTimeInForce", - "title": "The {@link net.jacobpeterson.alpaca.model.endpoint.orders.enums.OrderTimeInForce}." - }, - "limit_price": { - "existingJavaType": "java.lang.String", - "title": "Limit price." - }, - "stop_price": { - "existingJavaType": "java.lang.String", - "title": "Stop price." - }, - "status": { - "existingJavaType": "net.jacobpeterson.alpaca.model.endpoint.orders.enums.OrderStatus", - "title": "The {@link net.jacobpeterson.alpaca.model.endpoint.orders.enums.OrderStatus}." - }, - "extended_hours": { - "existingJavaType": "java.lang.Boolean", - "title": "If true, eligible for execution outside regular trading hours." - }, - "legs": { - "existingJavaType": "java.util.ArrayList", - "title": "When querying non-simple order_class orders in a nested style, an {@link java.util.ArrayList} of {@link net.jacobpeterson.alpaca.model.endpoint.orders.Order} entities associated with this order. Otherwise, null." - }, - "trail_percent": { - "existingJavaType": "java.lang.String", - "title": "The percent value away from the high water mark for trailing stop orders." - }, - "trail_price": { - "existingJavaType": "java.lang.String", - "title": "The dollar value away from the high water mark for trailing stop orders." - }, - "hwm": { - "existingJavaType": "java.lang.String", - "javaName": "highWaterMark", - "title": "High Water Mark - The highest (lowest) market price seen since the trailing stop order was submitted." - } - } -} diff --git a/schema_json/endpoint/portfolio_history/enums/portfolio_period_unit.json b/schema_json/endpoint/portfolio_history/enums/portfolio_period_unit.json deleted file mode 100644 index b55c083d..00000000 --- a/schema_json/endpoint/portfolio_history/enums/portfolio_period_unit.json +++ /dev/null @@ -1,24 +0,0 @@ -{ - "type": "string", - "title": "See Portfolio History.", - "enum": [ - "D", - "W", - "M", - "A" - ], - "javaEnums": [ - { - "name": "DAY" - }, - { - "name": "WEEK" - }, - { - "name": "MONTH" - }, - { - "name": "YEAR" - } - ] -} diff --git a/schema_json/endpoint/portfolio_history/enums/portfolio_time_frame.json b/schema_json/endpoint/portfolio_history/enums/portfolio_time_frame.json deleted file mode 100644 index 0fb614c7..00000000 --- a/schema_json/endpoint/portfolio_history/enums/portfolio_time_frame.json +++ /dev/null @@ -1,28 +0,0 @@ -{ - "type": "string", - "title": "See Portfolio History.", - "enum": [ - "1Min", - "5Min", - "15Min", - "1H", - "1D" - ], - "javaEnums": [ - { - "name": "ONE_MIN" - }, - { - "name": "FIVE_MINUTE" - }, - { - "name": "FIFTEEN_MINUTE" - }, - { - "name": "ONE_HOUR" - }, - { - "name": "ONE_DAY" - } - ] -} diff --git a/schema_json/endpoint/portfolio_history/portfolio_history.json b/schema_json/endpoint/portfolio_history/portfolio_history.json deleted file mode 100644 index f9f833b4..00000000 --- a/schema_json/endpoint/portfolio_history/portfolio_history.json +++ /dev/null @@ -1,18 +0,0 @@ -{ - "type": "object", - "title": "See Portfolio History.", - "properties": { - "data_points": { - "existingJavaType": "java.util.ArrayList", - "title": "The {@link java.util.ArrayList} of {@link net.jacobpeterson.alpaca.model.endpoint.portfoliohistory.PortfolioHistoryDataPoint}s." - }, - "base_value": { - "existingJavaType": "java.lang.Double", - "title": "Basis in dollar of the profit loss calculation." - }, - "timeframe": { - "existingJavaType": "net.jacobpeterson.alpaca.model.endpoint.portfoliohistory.enums.PortfolioTimeFrame", - "title": "Time window size of each data element." - } - } -} diff --git a/schema_json/endpoint/portfolio_history/portfolio_history_data_point.json b/schema_json/endpoint/portfolio_history/portfolio_history_data_point.json deleted file mode 100644 index 8cafa239..00000000 --- a/schema_json/endpoint/portfolio_history/portfolio_history_data_point.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "type": "object", - "title": "See Portfolio History.", - "properties": { - "timestamp": { - "existingJavaType": "java.time.ZonedDateTime", - "title": "{@link java.time.ZonedDateTime} of this data element." - }, - "equity": { - "existingJavaType": "java.lang.Double", - "title": "Equity value of the account in dollar amount as of the end of this time window." - }, - "profit_loss": { - "existingJavaType": "java.lang.Double", - "title": "Profit/loss in dollar from the base value." - }, - "profit_loss_percent": { - "existingJavaType": "java.lang.Double", - "title": "Profit/loss in percentage from the base value." - } - } -} diff --git a/schema_json/endpoint/portfolio_history/portfolio_history_response.json b/schema_json/endpoint/portfolio_history/portfolio_history_response.json deleted file mode 100644 index d3ca8177..00000000 --- a/schema_json/endpoint/portfolio_history/portfolio_history_response.json +++ /dev/null @@ -1,31 +0,0 @@ -{ - "type": "object", - "title": "See Portfolio History.", - "properties": { - "timestamp": { - "existingJavaType": "java.util.ArrayList", - "title": "Time of each data element, left-labeled (the beginning of time window)." - }, - "equity": { - "existingJavaType": "java.util.ArrayList", - "title": "Equity value of the account in dollar amount as of the end of each time window." - }, - "profit_loss": { - "existingJavaType": "java.util.ArrayList", - "title": "Profit/loss in dollar from the base value." - }, - "profit_loss_pct": { - "existingJavaType": "java.util.ArrayList", - "javaName": "profitLossPercent", - "title": "Profit/loss in percentage from the base value." - }, - "base_value": { - "existingJavaType": "java.lang.Double", - "title": "Basis in dollar of the profit loss calculation." - }, - "timeframe": { - "existingJavaType": "net.jacobpeterson.alpaca.model.endpoint.portfoliohistory.enums.PortfolioTimeFrame", - "title": "Time window size of each data element." - } - } -} diff --git a/schema_json/endpoint/positions/close_position_order.json b/schema_json/endpoint/positions/close_position_order.json deleted file mode 100644 index 3ffba883..00000000 --- a/schema_json/endpoint/positions/close_position_order.json +++ /dev/null @@ -1,19 +0,0 @@ -{ - "type": "object", - "title": "See https://alpaca.markets/docs/api-documentation/api-v2/positions/", - "properties": { - "symbol": { - "existingJavaType": "java.lang.String", - "title": "The symbol." - }, - "status": { - "existingJavaType": "java.lang.Integer", - "title": "The HTTP status code of the position-closing order." - }, - "body": { - "existingJavaType": "net.jacobpeterson.alpaca.model.endpoint.orders.Order", - "javaName": "order", - "title": "The position-closing {@link net.jacobpeterson.alpaca.model.endpoint.orders.Order}." - } - } -} diff --git a/schema_json/endpoint/positions/position.json b/schema_json/endpoint/positions/position.json deleted file mode 100644 index 8ae71b33..00000000 --- a/schema_json/endpoint/positions/position.json +++ /dev/null @@ -1,81 +0,0 @@ -{ - "type": "object", - "title": "See Positions.", - "properties": { - "asset_id": { - "existingJavaType": "java.lang.String", - "title": "Asset ID." - }, - "symbol": { - "existingJavaType": "java.lang.String", - "title": "Symbol name of the asset." - }, - "exchange": { - "existingJavaType": "java.lang.String", - "title": "Exchange name of the asset (\"ErisX\" for crypto)." - }, - "asset_class": { - "existingJavaType": "java.lang.String", - "title": "Asset class name." - }, - "avg_entry_price": { - "existingJavaType": "java.lang.String", - "javaName": "averageEntryPrice", - "title": "Average entry price of the position." - }, - "qty": { - "existingJavaType": "java.lang.String", - "javaName": "quantity", - "title": "The number of shares." - }, - "qty_available": { - "existingJavaType": "java.lang.String", - "javaName": "quantityAvailable", - "title": "Total number of shares available minus open orders." - }, - "side": { - "existingJavaType": "java.lang.String", - "title": "\"long\"." - }, - "market_value": { - "existingJavaType": "java.lang.String", - "title": "Total dollar amount of the position." - }, - "cost_basis": { - "existingJavaType": "java.lang.String", - "title": "Total cost basis in dollar." - }, - "unrealized_pl": { - "existingJavaType": "java.lang.String", - "javaName": "unrealizedProfitLoss", - "title": "Unrealized profit/loss in dollars." - }, - "unrealized_plpc": { - "existingJavaType": "java.lang.String", - "javaName": "unrealizedProfitLossPercent", - "title": "Unrealized profit/loss percent (by a factor of 1)." - }, - "unrealized_intraday_pl": { - "existingJavaType": "java.lang.String", - "javaName": "unrealizedIntradayProfitLoss", - "title": "Unrealized profit/loss in dollars for the day." - }, - "unrealized_intraday_plpc": { - "existingJavaType": "java.lang.String", - "javaName": "unrealizedIntradayProfitLossPercent", - "title": "Unrealized profit/loss percent (by a factor of 1)." - }, - "current_price": { - "existingJavaType": "java.lang.String", - "title": "Current asset price per share." - }, - "lastday_price": { - "existingJavaType": "java.lang.String", - "title": "Last day’s asset price per share based on the closing value of the last trading day." - }, - "change_today": { - "existingJavaType": "java.lang.String", - "title": "Percent change from last day price (by a factor of 1)." - } - } -} diff --git a/schema_json/endpoint/watchlist/watchlist.json b/schema_json/endpoint/watchlist/watchlist.json deleted file mode 100644 index 7c7e8602..00000000 --- a/schema_json/endpoint/watchlist/watchlist.json +++ /dev/null @@ -1,30 +0,0 @@ -{ - "type": "object", - "title": "See Watchlist.", - "properties": { - "id": { - "existingJavaType": "java.lang.String", - "title": "Watchlist ID." - }, - "created_at": { - "existingJavaType": "java.time.ZonedDateTime", - "title": "Created at." - }, - "updated_at": { - "existingJavaType": "java.time.ZonedDateTime", - "title": "Updated at." - }, - "name": { - "existingJavaType": "java.lang.String", - "title": "User-defined watchlist name (up to 64 characters)." - }, - "account_id": { - "existingJavaType": "java.lang.String", - "title": "Account ID." - }, - "assets": { - "existingJavaType": "java.util.ArrayList", - "title": "The content of this watchlist, in the order as registered by the client." - } - } -} diff --git a/src/main/java/net/jacobpeterson/alpaca/AlpacaAPI.java b/src/main/java/net/jacobpeterson/alpaca/AlpacaAPI.java index 4292e85d..bcaaec25 100644 --- a/src/main/java/net/jacobpeterson/alpaca/AlpacaAPI.java +++ b/src/main/java/net/jacobpeterson/alpaca/AlpacaAPI.java @@ -1,33 +1,16 @@ package net.jacobpeterson.alpaca; -import devcsrj.okhttp3.logging.HttpLoggingInterceptor; import net.jacobpeterson.alpaca.model.properties.DataAPIType; import net.jacobpeterson.alpaca.model.properties.EndpointAPIType; import net.jacobpeterson.alpaca.properties.AlpacaProperties; -import net.jacobpeterson.alpaca.rest.AlpacaClient; -import net.jacobpeterson.alpaca.rest.endpoint.AlpacaEndpoint; -import net.jacobpeterson.alpaca.rest.endpoint.account.AccountEndpoint; -import net.jacobpeterson.alpaca.rest.endpoint.accountactivities.AccountActivitiesEndpoint; -import net.jacobpeterson.alpaca.rest.endpoint.accountconfiguration.AccountConfigurationEndpoint; -import net.jacobpeterson.alpaca.rest.endpoint.assets.AssetsEndpoint; -import net.jacobpeterson.alpaca.rest.endpoint.calendar.CalendarEndpoint; -import net.jacobpeterson.alpaca.rest.endpoint.clock.ClockEndpoint; -import net.jacobpeterson.alpaca.rest.endpoint.corporateactions.CorporateActionsEndpoint; -import net.jacobpeterson.alpaca.rest.endpoint.marketdata.crypto.CryptoMarketDataEndpoint; -import net.jacobpeterson.alpaca.rest.endpoint.marketdata.stock.StockMarketDataEndpoint; -import net.jacobpeterson.alpaca.rest.endpoint.orders.OrdersEndpoint; -import net.jacobpeterson.alpaca.rest.endpoint.portfoliohistory.PortfolioHistoryEndpoint; -import net.jacobpeterson.alpaca.rest.endpoint.positions.PositionsEndpoint; -import net.jacobpeterson.alpaca.rest.endpoint.watchlist.WatchlistEndpoint; -import net.jacobpeterson.alpaca.websocket.AlpacaWebsocket; import net.jacobpeterson.alpaca.websocket.marketdata.MarketDataWebsocketInterface; import net.jacobpeterson.alpaca.websocket.marketdata.crypto.CryptoMarketDataWebsocket; -import net.jacobpeterson.alpaca.websocket.marketdata.stock.StockMarketDataWebsocket; -import net.jacobpeterson.alpaca.rest.endpoint.marketdata.news.NewsEndpoint; import net.jacobpeterson.alpaca.websocket.marketdata.news.NewsMarketDataWebsocket; +import net.jacobpeterson.alpaca.websocket.marketdata.stock.StockMarketDataWebsocket; import net.jacobpeterson.alpaca.websocket.streaming.StreamingWebsocket; import net.jacobpeterson.alpaca.websocket.streaming.StreamingWebsocketInterface; import okhttp3.OkHttpClient; +import okhttp3.logging.HttpLoggingInterceptor; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -35,45 +18,20 @@ import static com.google.common.base.Preconditions.checkNotNull; /** - * The {@link AlpacaAPI} class contains several instances of various {@link AlpacaEndpoint}s and - * {@link AlpacaWebsocket}s to interface with Alpaca. You will generally only need one instance of this class in your - * application. Note that many methods inside the various {@link AlpacaEndpoint}s allow null to be passed - * in as a parameter if it is optional. + * {@link AlpacaAPI} is the main class used to interface with the various Alpaca API endpoints. You will generally only + * need one instance of this class in your application. * - * @see Alpaca API Documentation + * @see Alpaca Docs */ public class AlpacaAPI { private static final Logger LOGGER = LoggerFactory.getLogger(AlpacaAPI.class); - private static final String VERSION_2_PATH_SEGMENT = "v2"; - private static final String VERSION_1_BETA_3_PATH_SEGMENT = "v1beta3"; - private static final String VERSION_1_BETA_1_PATH_SEGMENT = "v1beta1"; - private final OkHttpClient okHttpClient; - private final AlpacaClient brokerClient; - private final AlpacaClient cryptoDataClient; - private final AlpacaClient stockDataClient; - private final AlpacaClient newsDataClient; - // Ordering of fields/methods below are analogous to the ordering in the Alpaca documentation - private final AccountEndpoint accountEndpoint; - private final CryptoMarketDataEndpoint cryptoMarketDataEndpoint; - private final StockMarketDataEndpoint stockMarketDataEndpoint; - private final OrdersEndpoint ordersEndpoint; - private final PositionsEndpoint positionsEndpoint; - private final AssetsEndpoint assetsEndpoint; - private final WatchlistEndpoint watchlistEndpoint; - private final CalendarEndpoint calendarEndpoint; - private final ClockEndpoint clockEndpoint; - private final AccountConfigurationEndpoint accountConfigurationEndpoint; - private final AccountActivitiesEndpoint accountActivitiesEndpoint; - private final PortfolioHistoryEndpoint portfolioHistoryEndpoint; private final StreamingWebsocket streamingWebsocket; private final CryptoMarketDataWebsocket cryptoMarketDataWebsocket; private final StockMarketDataWebsocket stockMarketDataWebsocket; - private final CorporateActionsEndpoint corporateActionsEndpoint; private final NewsMarketDataWebsocket newsMarketDataWebsocket; - private final NewsEndpoint newsEndpoint; /** * Instantiates a new {@link AlpacaAPI} using properties specified in alpaca.properties file (or their @@ -154,146 +112,17 @@ public AlpacaAPI(OkHttpClient okHttpClient, String keyID, String secretKey, Stri if (okHttpClient == null) { OkHttpClient.Builder clientBuilder = new OkHttpClient.Builder() .cache(null); // Ensure response caching is disabled - if (LOGGER.isDebugEnabled()) { - clientBuilder.addInterceptor(new HttpLoggingInterceptor(LOGGER)); + clientBuilder.addInterceptor(new HttpLoggingInterceptor(LOGGER::debug)); } - okHttpClient = clientBuilder.build(); } - this.okHttpClient = okHttpClient; - String brokerHostSubdomain; - switch (endpointAPIType) { - case LIVE: - brokerHostSubdomain = "api"; - break; - case PAPER: - brokerHostSubdomain = "paper-api"; - break; - default: - throw new UnsupportedOperationException(); - } - - if (oAuthToken == null) { - brokerClient = new AlpacaClient(okHttpClient, keyID, secretKey, - brokerHostSubdomain, VERSION_2_PATH_SEGMENT); - cryptoDataClient = new AlpacaClient(okHttpClient, keyID, secretKey, "data", VERSION_1_BETA_3_PATH_SEGMENT); - stockDataClient = new AlpacaClient(okHttpClient, keyID, secretKey, "data", VERSION_2_PATH_SEGMENT); - newsDataClient = new AlpacaClient(okHttpClient, keyID, secretKey, "data", VERSION_1_BETA_1_PATH_SEGMENT); - } else { - brokerClient = new AlpacaClient(okHttpClient, oAuthToken, brokerHostSubdomain, VERSION_2_PATH_SEGMENT); - cryptoDataClient = null; - stockDataClient = null; - newsDataClient = null; - } - - accountEndpoint = new AccountEndpoint(brokerClient); - cryptoMarketDataEndpoint = cryptoDataClient == null ? null : new CryptoMarketDataEndpoint(cryptoDataClient); - stockMarketDataEndpoint = stockDataClient == null ? null : new StockMarketDataEndpoint(stockDataClient); - ordersEndpoint = new OrdersEndpoint(brokerClient); - positionsEndpoint = new PositionsEndpoint(brokerClient); - assetsEndpoint = new AssetsEndpoint(brokerClient); - watchlistEndpoint = new WatchlistEndpoint(brokerClient); - calendarEndpoint = new CalendarEndpoint(brokerClient); - clockEndpoint = new ClockEndpoint(brokerClient); - accountConfigurationEndpoint = new AccountConfigurationEndpoint(brokerClient); - accountActivitiesEndpoint = new AccountActivitiesEndpoint(brokerClient); - portfolioHistoryEndpoint = new PortfolioHistoryEndpoint(brokerClient); - - streamingWebsocket = new StreamingWebsocket(okHttpClient, brokerHostSubdomain, keyID, secretKey, oAuthToken); - cryptoMarketDataWebsocket = cryptoDataClient == null ? null : - new CryptoMarketDataWebsocket(okHttpClient, keyID, secretKey); - stockMarketDataWebsocket = stockDataClient == null ? null : - new StockMarketDataWebsocket(okHttpClient, dataAPIType, keyID, secretKey); - corporateActionsEndpoint = new CorporateActionsEndpoint(brokerClient); - newsEndpoint = newsDataClient == null ? null : new NewsEndpoint(newsDataClient); - newsMarketDataWebsocket = newsDataClient == null ? null : new NewsMarketDataWebsocket(okHttpClient, keyID, secretKey); - } - - /** - * @return the {@link AccountEndpoint} - */ - public AccountEndpoint account() { - return accountEndpoint; - } - - /** - * @return the {@link CryptoMarketDataEndpoint} - */ - public CryptoMarketDataEndpoint cryptoMarketData() { - return cryptoMarketDataEndpoint; - } - - /** - * @return the {@link StockMarketDataEndpoint} - */ - public StockMarketDataEndpoint stockMarketData() { - return stockMarketDataEndpoint; - } - - /** - * @return the {@link OrdersEndpoint} - */ - public OrdersEndpoint orders() { - return ordersEndpoint; - } - - /** - * @return the {@link PositionsEndpoint} - */ - public PositionsEndpoint positions() { - return positionsEndpoint; - } - - /** - * @return the {@link AssetsEndpoint} - */ - public AssetsEndpoint assets() { - return assetsEndpoint; - } - - /** - * @return the {@link WatchlistEndpoint} - */ - public WatchlistEndpoint watchlist() { - return watchlistEndpoint; - } - - /** - * @return the {@link CalendarEndpoint} - */ - public CalendarEndpoint calendar() { - return calendarEndpoint; - } - - /** - * @return the {@link ClockEndpoint} - */ - public ClockEndpoint clock() { - return clockEndpoint; - } - - /** - * @return the {@link AccountConfigurationEndpoint} - */ - public AccountConfigurationEndpoint accountConfiguration() { - return accountConfigurationEndpoint; - } - - /** - * @return the {@link AccountActivitiesEndpoint} - */ - public AccountActivitiesEndpoint accountActivities() { - return accountActivitiesEndpoint; - } - - /** - * @return the {@link PortfolioHistoryEndpoint} - */ - public PortfolioHistoryEndpoint portfolioHistory() { - return portfolioHistoryEndpoint; + streamingWebsocket = new StreamingWebsocket(okHttpClient, endpointAPIType, keyID, secretKey, oAuthToken); + cryptoMarketDataWebsocket = new CryptoMarketDataWebsocket(okHttpClient, keyID, secretKey); + stockMarketDataWebsocket = new StockMarketDataWebsocket(okHttpClient, dataAPIType, keyID, secretKey); + newsMarketDataWebsocket = new NewsMarketDataWebsocket(okHttpClient, keyID, secretKey); } /** @@ -317,40 +146,6 @@ public MarketDataWebsocketInterface stockMarketDataStreaming() { return stockMarketDataWebsocket; } - public OkHttpClient getOkHttpClient() { - return okHttpClient; - } - - public AlpacaClient getBrokerClient() { - return brokerClient; - } - - public AlpacaClient getCryptoDataClient() { - return cryptoDataClient; - } - - public AlpacaClient getStockDataClient() { - return stockDataClient; - } - - public AlpacaClient getNewsDataClient() { - return newsDataClient; - } - - /** - * @return the {@link CorporateActionsEndpoint} - */ - public CorporateActionsEndpoint corporateActions() { - return corporateActionsEndpoint; - } - - /** - * @return the {@link NewsEndpoint} - */ - public NewsEndpoint newsEndpoint() { - return newsEndpoint; - } - /** * @return the News {@link MarketDataWebsocketInterface} */ @@ -358,11 +153,14 @@ public MarketDataWebsocketInterface newsMarketDataStreaming() { return newsMarketDataWebsocket; } + public OkHttpClient getOkHttpClient() { + return okHttpClient; + } /** * Creates a {@link Builder} for {@link AlpacaAPI}. * - * @return a {@link Builder} + * @return the {@link Builder} */ public static Builder builder() { return new Builder(); @@ -385,23 +183,23 @@ private Builder() { this.dataAPIType = AlpacaProperties.DATA_API_TYPE; } - public Builder withKeyID(String val) { - keyID = val; + public Builder withKeyID(String keyID) { + this.keyID = keyID; return this; } - public Builder withSecretKey(String val) { - secretKey = val; + public Builder withSecretKey(String secretKey) { + this.secretKey = secretKey; return this; } - public Builder withEndpointAPIType(EndpointAPIType val) { - endpointAPIType = val; + public Builder withEndpointAPIType(EndpointAPIType endpointAPIType) { + this.endpointAPIType = endpointAPIType; return this; } - public Builder withDataAPIType(DataAPIType val) { - dataAPIType = val; + public Builder withDataAPIType(DataAPIType dataAPIType) { + this.dataAPIType = dataAPIType; return this; } diff --git a/schema_json/endpoint/market_data/common/historical/bar/bar.json b/src/main/java/net/jacobpeterson/alpaca/endpoint/market_data/common/historical/bar/bar.json similarity index 100% rename from schema_json/endpoint/market_data/common/historical/bar/bar.json rename to src/main/java/net/jacobpeterson/alpaca/endpoint/market_data/common/historical/bar/bar.json diff --git a/schema_json/endpoint/market_data/common/historical/bar/enums/bar_time_period.json b/src/main/java/net/jacobpeterson/alpaca/endpoint/market_data/common/historical/bar/enums/bar_time_period.json similarity index 100% rename from schema_json/endpoint/market_data/common/historical/bar/enums/bar_time_period.json rename to src/main/java/net/jacobpeterson/alpaca/endpoint/market_data/common/historical/bar/enums/bar_time_period.json diff --git a/schema_json/endpoint/market_data/common/historical/quote/quote.json b/src/main/java/net/jacobpeterson/alpaca/endpoint/market_data/common/historical/quote/quote.json similarity index 100% rename from schema_json/endpoint/market_data/common/historical/quote/quote.json rename to src/main/java/net/jacobpeterson/alpaca/endpoint/market_data/common/historical/quote/quote.json diff --git a/schema_json/endpoint/market_data/common/historical/trade/trade.json b/src/main/java/net/jacobpeterson/alpaca/endpoint/market_data/common/historical/trade/trade.json similarity index 100% rename from schema_json/endpoint/market_data/common/historical/trade/trade.json rename to src/main/java/net/jacobpeterson/alpaca/endpoint/market_data/common/historical/trade/trade.json diff --git a/schema_json/endpoint/market_data/common/realtime/bar/bar_message.json b/src/main/java/net/jacobpeterson/alpaca/endpoint/market_data/common/realtime/bar/bar_message.json similarity index 100% rename from schema_json/endpoint/market_data/common/realtime/bar/bar_message.json rename to src/main/java/net/jacobpeterson/alpaca/endpoint/market_data/common/realtime/bar/bar_message.json diff --git a/schema_json/endpoint/market_data/common/realtime/control/error_message.json b/src/main/java/net/jacobpeterson/alpaca/endpoint/market_data/common/realtime/control/error_message.json similarity index 100% rename from schema_json/endpoint/market_data/common/realtime/control/error_message.json rename to src/main/java/net/jacobpeterson/alpaca/endpoint/market_data/common/realtime/control/error_message.json diff --git a/schema_json/endpoint/market_data/common/realtime/control/subscriptions_message.json b/src/main/java/net/jacobpeterson/alpaca/endpoint/market_data/common/realtime/control/subscriptions_message.json similarity index 100% rename from schema_json/endpoint/market_data/common/realtime/control/subscriptions_message.json rename to src/main/java/net/jacobpeterson/alpaca/endpoint/market_data/common/realtime/control/subscriptions_message.json diff --git a/schema_json/endpoint/market_data/common/realtime/control/success_message.json b/src/main/java/net/jacobpeterson/alpaca/endpoint/market_data/common/realtime/control/success_message.json similarity index 100% rename from schema_json/endpoint/market_data/common/realtime/control/success_message.json rename to src/main/java/net/jacobpeterson/alpaca/endpoint/market_data/common/realtime/control/success_message.json diff --git a/schema_json/endpoint/market_data/common/realtime/enums/market_data_message_type.json b/src/main/java/net/jacobpeterson/alpaca/endpoint/market_data/common/realtime/enums/market_data_message_type.json similarity index 100% rename from schema_json/endpoint/market_data/common/realtime/enums/market_data_message_type.json rename to src/main/java/net/jacobpeterson/alpaca/endpoint/market_data/common/realtime/enums/market_data_message_type.json diff --git a/schema_json/endpoint/market_data/common/realtime/market_data_message.json b/src/main/java/net/jacobpeterson/alpaca/endpoint/market_data/common/realtime/market_data_message.json similarity index 100% rename from schema_json/endpoint/market_data/common/realtime/market_data_message.json rename to src/main/java/net/jacobpeterson/alpaca/endpoint/market_data/common/realtime/market_data_message.json diff --git a/schema_json/endpoint/market_data/common/realtime/quote/quote_message.json b/src/main/java/net/jacobpeterson/alpaca/endpoint/market_data/common/realtime/quote/quote_message.json similarity index 100% rename from schema_json/endpoint/market_data/common/realtime/quote/quote_message.json rename to src/main/java/net/jacobpeterson/alpaca/endpoint/market_data/common/realtime/quote/quote_message.json diff --git a/schema_json/endpoint/market_data/common/realtime/symbol_message.json b/src/main/java/net/jacobpeterson/alpaca/endpoint/market_data/common/realtime/symbol_message.json similarity index 100% rename from schema_json/endpoint/market_data/common/realtime/symbol_message.json rename to src/main/java/net/jacobpeterson/alpaca/endpoint/market_data/common/realtime/symbol_message.json diff --git a/schema_json/endpoint/market_data/common/realtime/trade/trade_message.json b/src/main/java/net/jacobpeterson/alpaca/endpoint/market_data/common/realtime/trade/trade_message.json similarity index 100% rename from schema_json/endpoint/market_data/common/realtime/trade/trade_message.json rename to src/main/java/net/jacobpeterson/alpaca/endpoint/market_data/common/realtime/trade/trade_message.json diff --git a/schema_json/endpoint/market_data/crypto/common/enums/taker_side.json b/src/main/java/net/jacobpeterson/alpaca/endpoint/market_data/crypto/common/enums/taker_side.json similarity index 100% rename from schema_json/endpoint/market_data/crypto/common/enums/taker_side.json rename to src/main/java/net/jacobpeterson/alpaca/endpoint/market_data/crypto/common/enums/taker_side.json diff --git a/schema_json/endpoint/market_data/crypto/historical/bar/crypto_bar.json b/src/main/java/net/jacobpeterson/alpaca/endpoint/market_data/crypto/historical/bar/crypto_bar.json similarity index 100% rename from schema_json/endpoint/market_data/crypto/historical/bar/crypto_bar.json rename to src/main/java/net/jacobpeterson/alpaca/endpoint/market_data/crypto/historical/bar/crypto_bar.json diff --git a/schema_json/endpoint/market_data/crypto/historical/bar/crypto_bars_response.json b/src/main/java/net/jacobpeterson/alpaca/endpoint/market_data/crypto/historical/bar/crypto_bars_response.json similarity index 100% rename from schema_json/endpoint/market_data/crypto/historical/bar/crypto_bars_response.json rename to src/main/java/net/jacobpeterson/alpaca/endpoint/market_data/crypto/historical/bar/crypto_bars_response.json diff --git a/schema_json/endpoint/market_data/crypto/historical/bar/latest_crypto_bars_response.json b/src/main/java/net/jacobpeterson/alpaca/endpoint/market_data/crypto/historical/bar/latest_crypto_bars_response.json similarity index 100% rename from schema_json/endpoint/market_data/crypto/historical/bar/latest_crypto_bars_response.json rename to src/main/java/net/jacobpeterson/alpaca/endpoint/market_data/crypto/historical/bar/latest_crypto_bars_response.json diff --git a/schema_json/endpoint/market_data/crypto/historical/orderbook/crypto_orderbook.json b/src/main/java/net/jacobpeterson/alpaca/endpoint/market_data/crypto/historical/orderbook/crypto_orderbook.json similarity index 100% rename from schema_json/endpoint/market_data/crypto/historical/orderbook/crypto_orderbook.json rename to src/main/java/net/jacobpeterson/alpaca/endpoint/market_data/crypto/historical/orderbook/crypto_orderbook.json diff --git a/schema_json/endpoint/market_data/crypto/historical/orderbook/crypto_orderbook_entry.json b/src/main/java/net/jacobpeterson/alpaca/endpoint/market_data/crypto/historical/orderbook/crypto_orderbook_entry.json similarity index 100% rename from schema_json/endpoint/market_data/crypto/historical/orderbook/crypto_orderbook_entry.json rename to src/main/java/net/jacobpeterson/alpaca/endpoint/market_data/crypto/historical/orderbook/crypto_orderbook_entry.json diff --git a/schema_json/endpoint/market_data/crypto/historical/orderbook/latest_crypto_orderbooks_response.json b/src/main/java/net/jacobpeterson/alpaca/endpoint/market_data/crypto/historical/orderbook/latest_crypto_orderbooks_response.json similarity index 100% rename from schema_json/endpoint/market_data/crypto/historical/orderbook/latest_crypto_orderbooks_response.json rename to src/main/java/net/jacobpeterson/alpaca/endpoint/market_data/crypto/historical/orderbook/latest_crypto_orderbooks_response.json diff --git a/schema_json/endpoint/market_data/crypto/historical/quote/crypto_quote.json b/src/main/java/net/jacobpeterson/alpaca/endpoint/market_data/crypto/historical/quote/crypto_quote.json similarity index 100% rename from schema_json/endpoint/market_data/crypto/historical/quote/crypto_quote.json rename to src/main/java/net/jacobpeterson/alpaca/endpoint/market_data/crypto/historical/quote/crypto_quote.json diff --git a/schema_json/endpoint/market_data/crypto/historical/quote/latest_crypto_quotes_response.json b/src/main/java/net/jacobpeterson/alpaca/endpoint/market_data/crypto/historical/quote/latest_crypto_quotes_response.json similarity index 100% rename from schema_json/endpoint/market_data/crypto/historical/quote/latest_crypto_quotes_response.json rename to src/main/java/net/jacobpeterson/alpaca/endpoint/market_data/crypto/historical/quote/latest_crypto_quotes_response.json diff --git a/schema_json/endpoint/market_data/crypto/historical/snapshot/crypto_snapshot.json b/src/main/java/net/jacobpeterson/alpaca/endpoint/market_data/crypto/historical/snapshot/crypto_snapshot.json similarity index 100% rename from schema_json/endpoint/market_data/crypto/historical/snapshot/crypto_snapshot.json rename to src/main/java/net/jacobpeterson/alpaca/endpoint/market_data/crypto/historical/snapshot/crypto_snapshot.json diff --git a/schema_json/endpoint/market_data/crypto/historical/snapshot/crypto_snapshots_response.json b/src/main/java/net/jacobpeterson/alpaca/endpoint/market_data/crypto/historical/snapshot/crypto_snapshots_response.json similarity index 100% rename from schema_json/endpoint/market_data/crypto/historical/snapshot/crypto_snapshots_response.json rename to src/main/java/net/jacobpeterson/alpaca/endpoint/market_data/crypto/historical/snapshot/crypto_snapshots_response.json diff --git a/schema_json/endpoint/market_data/crypto/historical/trade/crypto_trade.json b/src/main/java/net/jacobpeterson/alpaca/endpoint/market_data/crypto/historical/trade/crypto_trade.json similarity index 100% rename from schema_json/endpoint/market_data/crypto/historical/trade/crypto_trade.json rename to src/main/java/net/jacobpeterson/alpaca/endpoint/market_data/crypto/historical/trade/crypto_trade.json diff --git a/schema_json/endpoint/market_data/crypto/historical/trade/crypto_trades_response.json b/src/main/java/net/jacobpeterson/alpaca/endpoint/market_data/crypto/historical/trade/crypto_trades_response.json similarity index 100% rename from schema_json/endpoint/market_data/crypto/historical/trade/crypto_trades_response.json rename to src/main/java/net/jacobpeterson/alpaca/endpoint/market_data/crypto/historical/trade/crypto_trades_response.json diff --git a/schema_json/endpoint/market_data/crypto/historical/trade/latest_crypto_trades_response.json b/src/main/java/net/jacobpeterson/alpaca/endpoint/market_data/crypto/historical/trade/latest_crypto_trades_response.json similarity index 100% rename from schema_json/endpoint/market_data/crypto/historical/trade/latest_crypto_trades_response.json rename to src/main/java/net/jacobpeterson/alpaca/endpoint/market_data/crypto/historical/trade/latest_crypto_trades_response.json diff --git a/schema_json/endpoint/market_data/crypto/realtime/bar/crypto_bar_message.json b/src/main/java/net/jacobpeterson/alpaca/endpoint/market_data/crypto/realtime/bar/crypto_bar_message.json similarity index 100% rename from schema_json/endpoint/market_data/crypto/realtime/bar/crypto_bar_message.json rename to src/main/java/net/jacobpeterson/alpaca/endpoint/market_data/crypto/realtime/bar/crypto_bar_message.json diff --git a/schema_json/endpoint/market_data/crypto/realtime/quote/crypto_quote_message.json b/src/main/java/net/jacobpeterson/alpaca/endpoint/market_data/crypto/realtime/quote/crypto_quote_message.json similarity index 100% rename from schema_json/endpoint/market_data/crypto/realtime/quote/crypto_quote_message.json rename to src/main/java/net/jacobpeterson/alpaca/endpoint/market_data/crypto/realtime/quote/crypto_quote_message.json diff --git a/schema_json/endpoint/market_data/crypto/realtime/trade/crypto_trade_message.json b/src/main/java/net/jacobpeterson/alpaca/endpoint/market_data/crypto/realtime/trade/crypto_trade_message.json similarity index 100% rename from schema_json/endpoint/market_data/crypto/realtime/trade/crypto_trade_message.json rename to src/main/java/net/jacobpeterson/alpaca/endpoint/market_data/crypto/realtime/trade/crypto_trade_message.json diff --git a/schema_json/endpoint/market_data/news/common/image.json b/src/main/java/net/jacobpeterson/alpaca/endpoint/market_data/news/common/image.json similarity index 100% rename from schema_json/endpoint/market_data/news/common/image.json rename to src/main/java/net/jacobpeterson/alpaca/endpoint/market_data/news/common/image.json diff --git a/schema_json/endpoint/market_data/news/common/news_article.json b/src/main/java/net/jacobpeterson/alpaca/endpoint/market_data/news/common/news_article.json similarity index 100% rename from schema_json/endpoint/market_data/news/common/news_article.json rename to src/main/java/net/jacobpeterson/alpaca/endpoint/market_data/news/common/news_article.json diff --git a/schema_json/endpoint/market_data/news/news_response.json b/src/main/java/net/jacobpeterson/alpaca/endpoint/market_data/news/news_response.json similarity index 100% rename from schema_json/endpoint/market_data/news/news_response.json rename to src/main/java/net/jacobpeterson/alpaca/endpoint/market_data/news/news_response.json diff --git a/schema_json/endpoint/market_data/news/realtime/news_message.json b/src/main/java/net/jacobpeterson/alpaca/endpoint/market_data/news/realtime/news_message.json similarity index 100% rename from schema_json/endpoint/market_data/news/realtime/news_message.json rename to src/main/java/net/jacobpeterson/alpaca/endpoint/market_data/news/realtime/news_message.json diff --git a/schema_json/endpoint/market_data/stock/historical/bar/enums/bar_adjustment.json b/src/main/java/net/jacobpeterson/alpaca/endpoint/market_data/stock/historical/bar/enums/bar_adjustment.json similarity index 100% rename from schema_json/endpoint/market_data/stock/historical/bar/enums/bar_adjustment.json rename to src/main/java/net/jacobpeterson/alpaca/endpoint/market_data/stock/historical/bar/enums/bar_adjustment.json diff --git a/schema_json/endpoint/market_data/stock/historical/bar/enums/bar_feed.json b/src/main/java/net/jacobpeterson/alpaca/endpoint/market_data/stock/historical/bar/enums/bar_feed.json similarity index 100% rename from schema_json/endpoint/market_data/stock/historical/bar/enums/bar_feed.json rename to src/main/java/net/jacobpeterson/alpaca/endpoint/market_data/stock/historical/bar/enums/bar_feed.json diff --git a/schema_json/endpoint/market_data/stock/historical/bar/multi_stock_bars_response.json b/src/main/java/net/jacobpeterson/alpaca/endpoint/market_data/stock/historical/bar/multi_stock_bars_response.json similarity index 100% rename from schema_json/endpoint/market_data/stock/historical/bar/multi_stock_bars_response.json rename to src/main/java/net/jacobpeterson/alpaca/endpoint/market_data/stock/historical/bar/multi_stock_bars_response.json diff --git a/schema_json/endpoint/market_data/stock/historical/bar/stock_bar.json b/src/main/java/net/jacobpeterson/alpaca/endpoint/market_data/stock/historical/bar/stock_bar.json similarity index 100% rename from schema_json/endpoint/market_data/stock/historical/bar/stock_bar.json rename to src/main/java/net/jacobpeterson/alpaca/endpoint/market_data/stock/historical/bar/stock_bar.json diff --git a/schema_json/endpoint/market_data/stock/historical/bar/stock_bars_response.json b/src/main/java/net/jacobpeterson/alpaca/endpoint/market_data/stock/historical/bar/stock_bars_response.json similarity index 100% rename from schema_json/endpoint/market_data/stock/historical/bar/stock_bars_response.json rename to src/main/java/net/jacobpeterson/alpaca/endpoint/market_data/stock/historical/bar/stock_bars_response.json diff --git a/schema_json/endpoint/market_data/stock/historical/quote/latest_stock_quote_response.json b/src/main/java/net/jacobpeterson/alpaca/endpoint/market_data/stock/historical/quote/latest_stock_quote_response.json similarity index 100% rename from schema_json/endpoint/market_data/stock/historical/quote/latest_stock_quote_response.json rename to src/main/java/net/jacobpeterson/alpaca/endpoint/market_data/stock/historical/quote/latest_stock_quote_response.json diff --git a/schema_json/endpoint/market_data/stock/historical/quote/stock_quote.json b/src/main/java/net/jacobpeterson/alpaca/endpoint/market_data/stock/historical/quote/stock_quote.json similarity index 100% rename from schema_json/endpoint/market_data/stock/historical/quote/stock_quote.json rename to src/main/java/net/jacobpeterson/alpaca/endpoint/market_data/stock/historical/quote/stock_quote.json diff --git a/schema_json/endpoint/market_data/stock/historical/quote/stock_quotes_response.json b/src/main/java/net/jacobpeterson/alpaca/endpoint/market_data/stock/historical/quote/stock_quotes_response.json similarity index 100% rename from schema_json/endpoint/market_data/stock/historical/quote/stock_quotes_response.json rename to src/main/java/net/jacobpeterson/alpaca/endpoint/market_data/stock/historical/quote/stock_quotes_response.json diff --git a/schema_json/endpoint/market_data/stock/historical/snapshot/snapshot.json b/src/main/java/net/jacobpeterson/alpaca/endpoint/market_data/stock/historical/snapshot/snapshot.json similarity index 100% rename from schema_json/endpoint/market_data/stock/historical/snapshot/snapshot.json rename to src/main/java/net/jacobpeterson/alpaca/endpoint/market_data/stock/historical/snapshot/snapshot.json diff --git a/schema_json/endpoint/market_data/stock/historical/trade/latest_stock_trade_response.json b/src/main/java/net/jacobpeterson/alpaca/endpoint/market_data/stock/historical/trade/latest_stock_trade_response.json similarity index 100% rename from schema_json/endpoint/market_data/stock/historical/trade/latest_stock_trade_response.json rename to src/main/java/net/jacobpeterson/alpaca/endpoint/market_data/stock/historical/trade/latest_stock_trade_response.json diff --git a/schema_json/endpoint/market_data/stock/historical/trade/stock_trade.json b/src/main/java/net/jacobpeterson/alpaca/endpoint/market_data/stock/historical/trade/stock_trade.json similarity index 100% rename from schema_json/endpoint/market_data/stock/historical/trade/stock_trade.json rename to src/main/java/net/jacobpeterson/alpaca/endpoint/market_data/stock/historical/trade/stock_trade.json diff --git a/schema_json/endpoint/market_data/stock/historical/trade/stock_trades_response.json b/src/main/java/net/jacobpeterson/alpaca/endpoint/market_data/stock/historical/trade/stock_trades_response.json similarity index 100% rename from schema_json/endpoint/market_data/stock/historical/trade/stock_trades_response.json rename to src/main/java/net/jacobpeterson/alpaca/endpoint/market_data/stock/historical/trade/stock_trades_response.json diff --git a/schema_json/endpoint/market_data/stock/realtime/bar/stock_bar_message.json b/src/main/java/net/jacobpeterson/alpaca/endpoint/market_data/stock/realtime/bar/stock_bar_message.json similarity index 100% rename from schema_json/endpoint/market_data/stock/realtime/bar/stock_bar_message.json rename to src/main/java/net/jacobpeterson/alpaca/endpoint/market_data/stock/realtime/bar/stock_bar_message.json diff --git a/schema_json/endpoint/market_data/stock/realtime/quote/stock_quote_message.json b/src/main/java/net/jacobpeterson/alpaca/endpoint/market_data/stock/realtime/quote/stock_quote_message.json similarity index 100% rename from schema_json/endpoint/market_data/stock/realtime/quote/stock_quote_message.json rename to src/main/java/net/jacobpeterson/alpaca/endpoint/market_data/stock/realtime/quote/stock_quote_message.json diff --git a/schema_json/endpoint/market_data/stock/realtime/trade/stock_trade_message.json b/src/main/java/net/jacobpeterson/alpaca/endpoint/market_data/stock/realtime/trade/stock_trade_message.json similarity index 100% rename from schema_json/endpoint/market_data/stock/realtime/trade/stock_trade_message.json rename to src/main/java/net/jacobpeterson/alpaca/endpoint/market_data/stock/realtime/trade/stock_trade_message.json diff --git a/schema_json/endpoint/streaming/authorization/authorization_data.json b/src/main/java/net/jacobpeterson/alpaca/endpoint/streaming/authorization/authorization_data.json similarity index 100% rename from schema_json/endpoint/streaming/authorization/authorization_data.json rename to src/main/java/net/jacobpeterson/alpaca/endpoint/streaming/authorization/authorization_data.json diff --git a/schema_json/endpoint/streaming/authorization/authorization_message.json b/src/main/java/net/jacobpeterson/alpaca/endpoint/streaming/authorization/authorization_message.json similarity index 100% rename from schema_json/endpoint/streaming/authorization/authorization_message.json rename to src/main/java/net/jacobpeterson/alpaca/endpoint/streaming/authorization/authorization_message.json diff --git a/schema_json/endpoint/streaming/enums/streaming_message_type.json b/src/main/java/net/jacobpeterson/alpaca/endpoint/streaming/enums/streaming_message_type.json similarity index 100% rename from schema_json/endpoint/streaming/enums/streaming_message_type.json rename to src/main/java/net/jacobpeterson/alpaca/endpoint/streaming/enums/streaming_message_type.json diff --git a/schema_json/endpoint/streaming/listening/listening_data.json b/src/main/java/net/jacobpeterson/alpaca/endpoint/streaming/listening/listening_data.json similarity index 100% rename from schema_json/endpoint/streaming/listening/listening_data.json rename to src/main/java/net/jacobpeterson/alpaca/endpoint/streaming/listening/listening_data.json diff --git a/schema_json/endpoint/streaming/listening/listening_message.json b/src/main/java/net/jacobpeterson/alpaca/endpoint/streaming/listening/listening_message.json similarity index 100% rename from schema_json/endpoint/streaming/listening/listening_message.json rename to src/main/java/net/jacobpeterson/alpaca/endpoint/streaming/listening/listening_message.json diff --git a/schema_json/endpoint/streaming/streaming_message.json b/src/main/java/net/jacobpeterson/alpaca/endpoint/streaming/streaming_message.json similarity index 100% rename from schema_json/endpoint/streaming/streaming_message.json rename to src/main/java/net/jacobpeterson/alpaca/endpoint/streaming/streaming_message.json diff --git a/schema_json/endpoint/streaming/trade/enums/trade_update_event.json b/src/main/java/net/jacobpeterson/alpaca/endpoint/streaming/trade/enums/trade_update_event.json similarity index 100% rename from schema_json/endpoint/streaming/trade/enums/trade_update_event.json rename to src/main/java/net/jacobpeterson/alpaca/endpoint/streaming/trade/enums/trade_update_event.json diff --git a/schema_json/endpoint/streaming/trade/trade_update.json b/src/main/java/net/jacobpeterson/alpaca/endpoint/streaming/trade/trade_update.json similarity index 82% rename from schema_json/endpoint/streaming/trade/trade_update.json rename to src/main/java/net/jacobpeterson/alpaca/endpoint/streaming/trade/trade_update.json index 32f6c324..c0072275 100644 --- a/schema_json/endpoint/streaming/trade/trade_update.json +++ b/src/main/java/net/jacobpeterson/alpaca/endpoint/streaming/trade/trade_update.json @@ -20,8 +20,8 @@ "title": "The position quantity." }, "order": { - "existingJavaType": "net.jacobpeterson.alpaca.model.endpoint.orders.Order", - "title": "The {@link net.jacobpeterson.alpaca.model.endpoint.orders.Order}." + "existingJavaType": "net.jacobpeterson.alpaca.openapi.trader.model.Order", + "title": "The {@link net.jacobpeterson.alpaca.openapi.trader.model.Order}." } } } diff --git a/schema_json/endpoint/streaming/trade/trade_update_message.json b/src/main/java/net/jacobpeterson/alpaca/endpoint/streaming/trade/trade_update_message.json similarity index 100% rename from schema_json/endpoint/streaming/trade/trade_update_message.json rename to src/main/java/net/jacobpeterson/alpaca/endpoint/streaming/trade/trade_update_message.json diff --git a/src/main/java/net/jacobpeterson/alpaca/properties/AlpacaProperties.java b/src/main/java/net/jacobpeterson/alpaca/properties/AlpacaProperties.java index 0c121157..175a83c8 100644 --- a/src/main/java/net/jacobpeterson/alpaca/properties/AlpacaProperties.java +++ b/src/main/java/net/jacobpeterson/alpaca/properties/AlpacaProperties.java @@ -14,43 +14,16 @@ public class AlpacaProperties { private static final String ALPACA_PROPERTIES_FILE = "alpaca.properties"; private static final String ALPACA_DEFAULT_PROPERTIES_FILE = "alpaca.default.properties"; - private static final String KEY_ID_KEY = "key_id"; - /** - * The value of {@link #KEY_ID_KEY} in {@link #ALPACA_PROPERTIES_FILE}. - */ public static final String KEY_ID = getProperty( ALPACA_PROPERTIES_FILE, ALPACA_DEFAULT_PROPERTIES_FILE, - KEY_ID_KEY); - - private static final String SECRET_KEY_KEY = "secret_key"; - /** - * The value of {@link #SECRET_KEY_KEY} in {@link #ALPACA_PROPERTIES_FILE}. - */ + "key_id"); public static final String SECRET_KEY = getProperty( ALPACA_PROPERTIES_FILE, ALPACA_DEFAULT_PROPERTIES_FILE, - SECRET_KEY_KEY); - - private static final String ENDPOINT_API_TYPE_KEY = "endpoint_api_type"; - /** - * The value of {@link #ENDPOINT_API_TYPE_KEY} in {@link #ALPACA_PROPERTIES_FILE}. - */ + "secret_key"); public static final EndpointAPIType ENDPOINT_API_TYPE = EndpointAPIType.fromValue(getProperty( ALPACA_PROPERTIES_FILE, ALPACA_DEFAULT_PROPERTIES_FILE, - ENDPOINT_API_TYPE_KEY)); - - private static final String DATA_API_TYPE_KEY = "data_api_type"; - /** - * The value of {@link #DATA_API_TYPE_KEY} in {@link #ALPACA_PROPERTIES_FILE}. - */ + "endpoint_api_type")); public static final DataAPIType DATA_API_TYPE = DataAPIType.fromValue(getProperty( ALPACA_PROPERTIES_FILE, ALPACA_DEFAULT_PROPERTIES_FILE, - DATA_API_TYPE_KEY)); - - private static final String USER_AGENT_KEY = "user_agent"; - /** - * The value of {@link #USER_AGENT_KEY} in {@link #ALPACA_PROPERTIES_FILE}. - */ - public static final String USER_AGENT = getProperty( - ALPACA_PROPERTIES_FILE, ALPACA_DEFAULT_PROPERTIES_FILE, - USER_AGENT_KEY); + "data_api_type")); } diff --git a/schema_json/properties/data_api_type.json b/src/main/java/net/jacobpeterson/alpaca/properties/data_api_type.json similarity index 100% rename from schema_json/properties/data_api_type.json rename to src/main/java/net/jacobpeterson/alpaca/properties/data_api_type.json diff --git a/schema_json/properties/endpoint_api_type.json b/src/main/java/net/jacobpeterson/alpaca/properties/endpoint_api_type.json similarity index 100% rename from schema_json/properties/endpoint_api_type.json rename to src/main/java/net/jacobpeterson/alpaca/properties/endpoint_api_type.json diff --git a/src/main/java/net/jacobpeterson/alpaca/rest/AlpacaClient.java b/src/main/java/net/jacobpeterson/alpaca/rest/AlpacaClient.java deleted file mode 100644 index dc11df3f..00000000 --- a/src/main/java/net/jacobpeterson/alpaca/rest/AlpacaClient.java +++ /dev/null @@ -1,235 +0,0 @@ -package net.jacobpeterson.alpaca.rest; - -import com.google.gson.Gson; -import com.google.gson.JsonElement; -import com.google.gson.JsonParser; -import net.jacobpeterson.alpaca.properties.AlpacaProperties; -import okhttp3.Headers; -import okhttp3.HttpUrl; -import okhttp3.OkHttpClient; -import okhttp3.Request; -import okhttp3.Response; -import okhttp3.ResponseBody; - -import java.io.IOException; -import java.io.Reader; -import java.lang.reflect.Type; -import java.util.function.Predicate; - -import static com.google.common.base.Preconditions.checkNotNull; -import static com.google.common.base.Preconditions.checkState; -import static net.jacobpeterson.alpaca.util.gson.GsonUtil.GSON; - -/** - * {@link AlpacaClient} represents an HTTP RestAPI client for Alpaca. - */ -public class AlpacaClient { - - public static final Predicate STATUS_CODE_200_OR_207 = (code) -> code == 200 || code == 207; - public static final Predicate STATUS_CODE_200_OR_204 = (code) -> code == 200 || code == 204; - - private final OkHttpClient okHttpClient; - private final HttpUrl baseURL; - private final Headers requestHeaders; - - /** - * Instantiates a new {@link AlpacaClient}. - * - * @param okHttpClient the {@link OkHttpClient} - * @param keyID the key ID - * @param secretKey the secret key - * @param alpacaSubdomain the Alpaca subdomain - * @param versionPathSegment the version path segment e.g. "v2" - */ - public AlpacaClient(OkHttpClient okHttpClient, String keyID, String secretKey, String alpacaSubdomain, - String versionPathSegment) { - this(okHttpClient, keyID, secretKey, null, alpacaSubdomain, versionPathSegment); - } - - /** - * Instantiates a new {@link AlpacaClient}. - * - * @param okHttpClient the {@link OkHttpClient} - * @param oAuthToken the OAuth token - * @param alpacaSubdomain the Alpaca subdomain - * @param versionPathSegment the version path segment e.g. "v2" - */ - public AlpacaClient(OkHttpClient okHttpClient, String oAuthToken, String alpacaSubdomain, - String versionPathSegment) { - this(okHttpClient, null, null, oAuthToken, alpacaSubdomain, versionPathSegment); - } - - /** - * Instantiates a new {@link AlpacaClient}. - * - * @param okHttpClient the {@link OkHttpClient} - * @param keyID the key ID - * @param secretKey the secret key - * @param oAuthToken the OAuth token - * @param alpacaSubdomain the Alpaca subdomain - * @param versionPathSegment the version path segment e.g. "v2" - */ - protected AlpacaClient(OkHttpClient okHttpClient, String keyID, String secretKey, String oAuthToken, - String alpacaSubdomain, String versionPathSegment) { - checkNotNull(okHttpClient); - checkNotNull(alpacaSubdomain); - checkNotNull(versionPathSegment); - - this.okHttpClient = okHttpClient; - - baseURL = new HttpUrl.Builder() - .scheme("https") - .host(alpacaSubdomain + ".alpaca.markets") - .addPathSegment(versionPathSegment) - .build(); - - Headers.Builder requestHeadersBuilder = new Headers.Builder(); - requestHeadersBuilder.add("User-Agent", AlpacaProperties.USER_AGENT); - if (oAuthToken != null) { - requestHeadersBuilder.add("Authorization", "Bearer " + oAuthToken); - } else { - requestHeadersBuilder - .add("APCA-API-KEY-ID", keyID) - .add("APCA-API-SECRET-KEY", secretKey); - } - requestHeaders = requestHeadersBuilder.build(); - } - - /** - * Gets a new {@link okhttp3.HttpUrl.Builder}. - * - * @return a {@link okhttp3.HttpUrl.Builder} - */ - public HttpUrl.Builder urlBuilder() { - return baseURL.newBuilder(); - } - - /** - * Gets a new {@link okhttp3.Request.Builder} with {@link #requestHeaders}. - * - * @param httpUrl the {@link okhttp3.HttpUrl} - * - * @return a {@link okhttp3.Request.Builder} - */ - public Request.Builder requestBuilder(HttpUrl httpUrl) { - return new Request.Builder().headers(requestHeaders).url(httpUrl); - } - - /** - * Calls {@link #requestObject(Request, Predicate, Type)} with a success code of 200. - */ - public T requestObject(Request request, Type type) throws AlpacaClientException { - return requestObject(request, (code) -> code == 200, type); - } - - /** - * Requests an object given a {@link Request} that is deserialized via {@link Gson#fromJson(Reader, Type)}. - * - * @param the type of object - * @param request the {@link Request} - * @param isSuccessCode throws {@link AlpacaClientException} if passed in {@link Response#code()} to the - * {@link Predicate#test(Object)} returns false - * @param type the object {@link Type} - * - * @return the requested object - * - * @throws AlpacaClientException thrown for {@link AlpacaClientException}s - */ - public T requestObject(Request request, Predicate isSuccessCode, Type type) - throws AlpacaClientException { - return GSON.fromJson(requestJSON(request, isSuccessCode), type); - } - - /** - * Calls {@link #requestJSON(Request, Predicate)} with a success code of 200. - */ - public JsonElement requestJSON(Request request) throws AlpacaClientException { - return requestJSON(request, (code) -> code == 200); - } - - /** - * Requests a {@link JsonElement} given a {@link Request}. - * - * @param request the {@link Request} - * @param isSuccessCode throws {@link AlpacaClientException} if passed in {@link Response#code()} to the - * {@link Predicate#test(Object)} returns false - * - * @return the {@link JsonElement} - * - * @throws AlpacaClientException thrown for {@link AlpacaClientException}s - */ - public JsonElement requestJSON(Request request, Predicate isSuccessCode) throws AlpacaClientException { - try (Response response = request(request, isSuccessCode)) { - try (ResponseBody responseBody = response.body()) { - // Throw exception if 'responseBody' is ever null since we never want to use cachedResponse or - // other types of responses. - checkState(responseBody != null); - - try (Reader charStream = responseBody.charStream()) { - return JsonParser.parseReader(charStream); - } catch (IOException ioException) { - throw new AlpacaClientException(ioException); - } - } - } - } - - /** - * Sends a {@link Request} and ignores any {@link ResponseBody}. - * - * @param request the {@link Request} - * @param isSuccessCode throws {@link AlpacaClientException} if passed in {@link Response#code()} to the - * {@link Predicate#test(Object)} returns false - * - * @throws AlpacaClientException thrown for {@link AlpacaClientException}s - */ - public void requestVoid(Request request, Predicate isSuccessCode) throws AlpacaClientException { - // close() will throw an exception if cachedResponse or others were used which is fine - // since we never want to use those types of responses. - request(request, isSuccessCode).close(); - } - - /** - * Sends a {@link Request} and returns a {@link Response}. - * - * @param request the {@link Request} - * @param isSuccessCode throws {@link AlpacaClientException} if passed in {@link Response#code()} to the - * {@link Predicate#test(Object)} returns false - * - * @return the {@link Response}. Be sure to call {@link Response#close()} after use. - * - * @throws AlpacaClientException thrown for {@link AlpacaClientException}s - */ - public Response request(Request request, Predicate isSuccessCode) throws AlpacaClientException { - try { - Response response = executeRequest(request); - if (!isSuccessCode.test(response.code())) { - throw new AlpacaClientException(response); - } - return response; - } catch (IOException ioException) { - throw new AlpacaClientException(ioException); - } - } - - /** - * Executes a {@link Request}. - * - * @param request the {@link Request} - * - * @return the {@link Response}. Be sure to call {@link Response#close()} after use. - * - * @throws IOException thrown for {@link IOException}s - */ - public Response executeRequest(Request request) throws IOException { - return okHttpClient.newCall(request).execute(); - } - - public HttpUrl getBaseURL() { - return baseURL; - } - - public Headers getRequestHeaders() { - return requestHeaders; - } -} diff --git a/src/main/java/net/jacobpeterson/alpaca/rest/AlpacaClientException.java b/src/main/java/net/jacobpeterson/alpaca/rest/AlpacaClientException.java deleted file mode 100644 index 4c902b05..00000000 --- a/src/main/java/net/jacobpeterson/alpaca/rest/AlpacaClientException.java +++ /dev/null @@ -1,144 +0,0 @@ -package net.jacobpeterson.alpaca.rest; - -import com.google.gson.JsonElement; -import com.google.gson.JsonObject; -import com.google.gson.JsonParser; -import okhttp3.Response; -import okhttp3.ResponseBody; - -import javax.annotation.Nullable; - -public class AlpacaClientException extends Exception { - - private static final String CODE_KEY = "code"; - private static final String MESSAGE_KEY = "message"; - - private String responseBody; - private Integer responseStatusCode; - private String responseStatusMessage; - private Integer apiResponseCode; - private String apiResponseMessage; - - /** - * Instantiates a new {@link AlpacaClientException}. - * - * @param message the message - */ - public AlpacaClientException(String message) { - super(message); - } - - /** - * Instantiates a new {@link AlpacaClientException}. - * - * @param cause the cause {@link Throwable} - */ - public AlpacaClientException(Throwable cause) { - super(cause); - } - - /** - * Instantiates a new {@link AlpacaClientException}. - * - * @param message the message - * @param cause the cause {@link Throwable} - */ - public AlpacaClientException(String message, Throwable cause) { - super(message, cause); - } - - /** - * Instantiates a new {@link AlpacaClientException}. - * - * @param response the {@link Response} containing an API response code and message JSON - */ - protected AlpacaClientException(Response response) { - responseStatusCode = response.code(); - responseStatusMessage = response.message(); - - try (ResponseBody clientResponseBody = response.body()) { - if (clientResponseBody != null) { - this.responseBody = clientResponseBody.string(); - } - } catch (Exception ignored) {} - } - - @Override - public String getMessage() { - if (responseBody == null) { - return super.getMessage(); - } else { - return parseAPIErrorResponse(); - } - } - - /** - * Parses an API error response of the following format: - *
-     *  {
-     *     "code": 505,
-     *     "message": "Error message"
-     *  }
-     * 
- * and sets {@link #apiResponseCode} and {@link #apiResponseMessage} accordingly. - * - * @return a formatted message of the error response - */ - private String parseAPIErrorResponse() { - try { - JsonElement responseJsonElement = JsonParser.parseString(responseBody); - - if (responseJsonElement instanceof JsonObject) { - JsonObject responseJsonObject = responseJsonElement.getAsJsonObject(); - - if (responseJsonObject.has(CODE_KEY)) { - apiResponseCode = responseJsonObject.get(CODE_KEY).getAsInt(); - } - - if (responseJsonObject.has(MESSAGE_KEY)) { - apiResponseMessage = responseJsonObject.get(MESSAGE_KEY).getAsString(); - } - } - - // Just use the response JSON if the message could not be parsed. - if (apiResponseMessage == null) { - apiResponseMessage = responseJsonElement.toString(); - } - } catch (Exception ignored) {} - - // Build message - StringBuilder messageBuilder = new StringBuilder(); - messageBuilder.append("Status Code: ").append(responseStatusCode); - messageBuilder.append(", Status Message: \"").append(responseStatusMessage).append("\""); - - if (apiResponseCode != null) { - messageBuilder.append(", API Response Code: ").append(apiResponseCode); - } - - if (apiResponseMessage != null) { - messageBuilder.append(", API Response Message: \"").append(apiResponseMessage).append("\""); - } - - return messageBuilder.toString(); - } - - @Nullable - public Integer getResponseStatusCode() { - return responseStatusCode; - } - - @Nullable - public String getResponseStatusMessage() { - return responseStatusMessage; - } - - @Nullable - public Integer getAPIResponseCode() { - return apiResponseCode; - } - - @Nullable - public String getAPIResponseMessage() { - return apiResponseMessage; - } -} diff --git a/src/main/java/net/jacobpeterson/alpaca/rest/endpoint/AlpacaEndpoint.java b/src/main/java/net/jacobpeterson/alpaca/rest/endpoint/AlpacaEndpoint.java deleted file mode 100644 index dc4e1616..00000000 --- a/src/main/java/net/jacobpeterson/alpaca/rest/endpoint/AlpacaEndpoint.java +++ /dev/null @@ -1,23 +0,0 @@ -package net.jacobpeterson.alpaca.rest.endpoint; - -import net.jacobpeterson.alpaca.rest.AlpacaClient; - -/** - * {@link AlpacaEndpoint} is an abstract class representing a RestAPI endpoint. - */ -public abstract class AlpacaEndpoint { - - protected final AlpacaClient alpacaClient; - protected final String endpointPathSegment; - - /** - * Instantiates a new {@link AlpacaEndpoint}. - * - * @param alpacaClient the {@link AlpacaClient} - * @param endpointPathSegment the endpoint path segment relative to {@link AlpacaClient#getBaseURL()} - */ - public AlpacaEndpoint(AlpacaClient alpacaClient, String endpointPathSegment) { - this.alpacaClient = alpacaClient; - this.endpointPathSegment = endpointPathSegment; - } -} diff --git a/src/main/java/net/jacobpeterson/alpaca/rest/endpoint/account/AccountEndpoint.java b/src/main/java/net/jacobpeterson/alpaca/rest/endpoint/account/AccountEndpoint.java deleted file mode 100644 index 8054ab4f..00000000 --- a/src/main/java/net/jacobpeterson/alpaca/rest/endpoint/account/AccountEndpoint.java +++ /dev/null @@ -1,39 +0,0 @@ -package net.jacobpeterson.alpaca.rest.endpoint.account; - -import net.jacobpeterson.alpaca.model.endpoint.account.Account; -import net.jacobpeterson.alpaca.rest.AlpacaClient; -import net.jacobpeterson.alpaca.rest.AlpacaClientException; -import net.jacobpeterson.alpaca.rest.endpoint.AlpacaEndpoint; -import okhttp3.HttpUrl; -import okhttp3.Request; - -/** - * {@link AlpacaEndpoint} for Account. - */ -public class AccountEndpoint extends AlpacaEndpoint { - - /** - * Instantiates a new {@link AccountEndpoint}. - * - * @param alpacaClient the {@link AlpacaClient} - */ - public AccountEndpoint(AlpacaClient alpacaClient) { - super(alpacaClient, "account"); - } - - /** - * Returns the {@link Account}. - * - * @return the {@link Account} - * - * @throws AlpacaClientException thrown for {@link AlpacaClientException}s - */ - public Account get() throws AlpacaClientException { - HttpUrl.Builder urlBuilder = alpacaClient.urlBuilder() - .addPathSegment(endpointPathSegment); - Request request = alpacaClient.requestBuilder(urlBuilder.build()) - .get() - .build(); - return alpacaClient.requestObject(request, Account.class); - } -} diff --git a/src/main/java/net/jacobpeterson/alpaca/rest/endpoint/accountactivities/AccountActivitiesEndpoint.java b/src/main/java/net/jacobpeterson/alpaca/rest/endpoint/accountactivities/AccountActivitiesEndpoint.java deleted file mode 100644 index c56375bf..00000000 --- a/src/main/java/net/jacobpeterson/alpaca/rest/endpoint/accountactivities/AccountActivitiesEndpoint.java +++ /dev/null @@ -1,142 +0,0 @@ -package net.jacobpeterson.alpaca.rest.endpoint.accountactivities; - -import com.google.gson.JsonArray; -import com.google.gson.JsonElement; -import com.google.gson.JsonObject; -import net.jacobpeterson.alpaca.model.endpoint.accountactivities.AccountActivity; -import net.jacobpeterson.alpaca.model.endpoint.accountactivities.NonTradeActivity; -import net.jacobpeterson.alpaca.model.endpoint.accountactivities.TradeActivity; -import net.jacobpeterson.alpaca.model.endpoint.accountactivities.enums.ActivityType; -import net.jacobpeterson.alpaca.model.endpoint.common.enums.SortDirection; -import net.jacobpeterson.alpaca.rest.AlpacaClient; -import net.jacobpeterson.alpaca.rest.AlpacaClientException; -import net.jacobpeterson.alpaca.rest.endpoint.AlpacaEndpoint; -import okhttp3.HttpUrl; -import okhttp3.Request; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.time.ZonedDateTime; -import java.time.format.DateTimeFormatter; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; -import java.util.stream.Collectors; - -import static com.google.common.base.Preconditions.checkState; -import static net.jacobpeterson.alpaca.util.gson.GsonUtil.GSON; - -/** - * {@link AlpacaEndpoint} for - * Account - * Activities. - */ -public class AccountActivitiesEndpoint extends AlpacaEndpoint { - - private static final Logger LOGGER = LoggerFactory.getLogger(AccountActivitiesEndpoint.class); - - private static final String ACTIVITY_TYPE_FIELD = "activity_type"; - - /** - * Instantiates a new {@link AccountActivitiesEndpoint}. - * - * @param alpacaClient the {@link AlpacaClient} - */ - public AccountActivitiesEndpoint(AlpacaClient alpacaClient) { - super(alpacaClient, "account/activities"); - } - - /** - * Returns {@link AccountActivity} entries for many {@link ActivityType}s or for a specific {@link ActivityType}. - * - * @param date the date for which you want to see activities. - * @param until the response will contain only activities submitted before this date. (Cannot be used with - * date.) - * @param after the response will contain only activities submitted after this date. (Cannot be used with - * date.) - * @param sortDirection the {@link SortDirection} (defaults to {@link SortDirection#DESCENDING} if unspecified.) - * @param pageSize the maximum number of entries to return in the response. (See the section on paging.) - * @param pageToken the ID of the end of your current page of results. (See the section on paging.) - * @param activityTypes the {@link ActivityType}s (null for all {@link ActivityType}s) - * - * @return a {@link List} of {@link AccountActivity}s - * - * @throws AlpacaClientException thrown for {@link AlpacaClientException}s - */ - public List get(ZonedDateTime date, ZonedDateTime until, ZonedDateTime after, - SortDirection sortDirection, Integer pageSize, String pageToken, ActivityType... activityTypes) - throws AlpacaClientException { - HttpUrl.Builder urlBuilder = alpacaClient.urlBuilder() - .addPathSegments(endpointPathSegment); - - if (activityTypes != null) { // Check if we don't want all activity types - if (activityTypes.length == 1) { // Get one activity - urlBuilder.addPathSegment(activityTypes[0].toString()); - } else { // Get list of activities - urlBuilder.addQueryParameter("activity_types", - // Makes comma-separated list - Arrays.stream(activityTypes).map(ActivityType::toString).collect(Collectors.joining(","))); - } - } - - if (date != null) { - urlBuilder.addQueryParameter("date", date.format(DateTimeFormatter.ISO_OFFSET_DATE_TIME)); - } - - if (until != null) { - urlBuilder.addQueryParameter("until", until.format(DateTimeFormatter.ISO_OFFSET_DATE_TIME)); - } - - if (after != null) { - urlBuilder.addQueryParameter("after", after.format(DateTimeFormatter.ISO_OFFSET_DATE_TIME)); - } - - if (sortDirection != null) { - urlBuilder.addQueryParameter("direction", sortDirection.toString()); - } - - if (pageSize != null) { - urlBuilder.addQueryParameter("page_size", pageSize.toString()); - } - - if (pageToken != null) { - urlBuilder.addQueryParameter("page_token", pageToken); - } - - Request request = alpacaClient.requestBuilder(urlBuilder.build()) - .get() - .build(); - - JsonElement responseJSON = alpacaClient.requestJSON(request); - checkState(responseJSON instanceof JsonArray, "The response must be an array! Received: %s", responseJSON); - - ArrayList accountActivities = new ArrayList<>(); - for (JsonElement responseArrayElement : responseJSON.getAsJsonArray()) { - checkState(responseArrayElement instanceof JsonObject, - "All array elements must be objects! Received: %s", responseArrayElement); - - JsonObject responseArrayObject = responseArrayElement.getAsJsonObject(); - JsonElement activityTypeElement = responseArrayObject.get(ACTIVITY_TYPE_FIELD); - checkState(activityTypeElement != null, "Activity type elements must have %s field! Received: %s", - ACTIVITY_TYPE_FIELD, responseArrayElement); - - String activityType = activityTypeElement.getAsString(); - AccountActivity accountActivity; - - // A 'TradeActivity' always has 'activity_type' field as 'FILL' - if (activityType.equals(ActivityType.FILL.toString())) { - accountActivity = GSON.fromJson(responseArrayObject, TradeActivity.class); - } else { - accountActivity = GSON.fromJson(responseArrayObject, NonTradeActivity.class); - } - - if (accountActivity.getActivityType() == null) { - LOGGER.error("No ActivityType enum exists for {}. Report this!", activityType); - } - - accountActivities.add(accountActivity); - } - - return accountActivities; - } -} diff --git a/src/main/java/net/jacobpeterson/alpaca/rest/endpoint/accountconfiguration/AccountConfigurationEndpoint.java b/src/main/java/net/jacobpeterson/alpaca/rest/endpoint/accountconfiguration/AccountConfigurationEndpoint.java deleted file mode 100644 index a319e650..00000000 --- a/src/main/java/net/jacobpeterson/alpaca/rest/endpoint/accountconfiguration/AccountConfigurationEndpoint.java +++ /dev/null @@ -1,64 +0,0 @@ -package net.jacobpeterson.alpaca.rest.endpoint.accountconfiguration; - -import net.jacobpeterson.alpaca.model.endpoint.accountconfiguration.AccountConfiguration; -import net.jacobpeterson.alpaca.rest.AlpacaClient; -import net.jacobpeterson.alpaca.rest.AlpacaClientException; -import net.jacobpeterson.alpaca.rest.endpoint.AlpacaEndpoint; -import net.jacobpeterson.alpaca.util.okhttp.JSONBodyBuilder; -import okhttp3.HttpUrl; -import okhttp3.Request; - -import static com.google.common.base.Preconditions.checkNotNull; -import static net.jacobpeterson.alpaca.util.gson.GsonUtil.GSON; - -/** - * {@link AlpacaEndpoint} for - * Account Configuration. - */ -public class AccountConfigurationEndpoint extends AlpacaEndpoint { - - /** - * Instantiates a new {@link AccountConfigurationEndpoint}. - * - * @param alpacaClient the {@link AlpacaClient} - */ - public AccountConfigurationEndpoint(AlpacaClient alpacaClient) { - super(alpacaClient, "account/configurations"); - } - - /** - * Returns the {@link AccountConfiguration}. - * - * @return the {@link AccountConfiguration} - * - * @throws AlpacaClientException thrown for {@link AlpacaClientException}s - */ - public AccountConfiguration get() throws AlpacaClientException { - HttpUrl.Builder urlBuilder = alpacaClient.urlBuilder() - .addPathSegments(endpointPathSegment); - Request request = alpacaClient.requestBuilder(urlBuilder.build()) - .get() - .build(); - return alpacaClient.requestObject(request, AccountConfiguration.class); - } - - /** - * Sets the {@link AccountConfiguration}. - * - * @param accountConfiguration the {@link AccountConfiguration} - * - * @return the updated {@link AccountConfiguration} - * - * @throws AlpacaClientException thrown for {@link AlpacaClientException}s - */ - public AccountConfiguration set(AccountConfiguration accountConfiguration) throws AlpacaClientException { - checkNotNull(accountConfiguration); - - HttpUrl.Builder urlBuilder = alpacaClient.urlBuilder() - .addPathSegments(endpointPathSegment); - Request request = alpacaClient.requestBuilder(urlBuilder.build()) - .patch(new JSONBodyBuilder(GSON.toJson(accountConfiguration)).build()) - .build(); - return alpacaClient.requestObject(request, AccountConfiguration.class); - } -} diff --git a/src/main/java/net/jacobpeterson/alpaca/rest/endpoint/assets/AssetsEndpoint.java b/src/main/java/net/jacobpeterson/alpaca/rest/endpoint/assets/AssetsEndpoint.java deleted file mode 100644 index 82763589..00000000 --- a/src/main/java/net/jacobpeterson/alpaca/rest/endpoint/assets/AssetsEndpoint.java +++ /dev/null @@ -1,83 +0,0 @@ -package net.jacobpeterson.alpaca.rest.endpoint.assets; - -import com.google.gson.reflect.TypeToken; -import net.jacobpeterson.alpaca.model.endpoint.assets.Asset; -import net.jacobpeterson.alpaca.model.endpoint.assets.enums.AssetClass; -import net.jacobpeterson.alpaca.model.endpoint.assets.enums.AssetStatus; -import net.jacobpeterson.alpaca.rest.AlpacaClient; -import net.jacobpeterson.alpaca.rest.AlpacaClientException; -import net.jacobpeterson.alpaca.rest.endpoint.AlpacaEndpoint; -import okhttp3.HttpUrl; -import okhttp3.Request; - -import java.lang.reflect.Type; -import java.util.ArrayList; -import java.util.List; - -import static com.google.common.base.Preconditions.checkNotNull; - -/** - * {@link AlpacaEndpoint} for Assets. - */ -public class AssetsEndpoint extends AlpacaEndpoint { - - private static final Type ASSET_ARRAYLIST_TYPE = new TypeToken>() {}.getType(); - - /** - * Instantiates a new {@link AssetsEndpoint}. - * - * @param alpacaClient the {@link AlpacaClient} - */ - public AssetsEndpoint(AlpacaClient alpacaClient) { - super(alpacaClient, "assets"); - } - - /** - * Get a list of {@link Asset}s. - * - * @param assetStatus the {@link AssetStatus}. By default, all {@link AssetStatus}es are included. - * @param assetClass the {@link AssetClass} (null for {@link AssetClass#US_EQUITY}) - * - * @return a {@link List} of {@link Asset}s - * - * @throws AlpacaClientException thrown for {@link AlpacaClientException}s - */ - public List get(AssetStatus assetStatus, AssetClass assetClass) throws AlpacaClientException { - HttpUrl.Builder urlBuilder = alpacaClient.urlBuilder() - .addPathSegment(endpointPathSegment); - - if (assetStatus != null) { - urlBuilder.addQueryParameter("status", assetStatus.toString()); - } - - if (assetClass != null) { - urlBuilder.addQueryParameter("asset_class", assetClass.toString()); - } - - Request request = alpacaClient.requestBuilder(urlBuilder.build()) - .get() - .build(); - return alpacaClient.requestObject(request, ASSET_ARRAYLIST_TYPE); - } - - /** - * Gets an {@link Asset} by a symbol or {@link Asset#getId()}. - * - * @param symbolOrAssetID the symbol or {@link Asset#getId()} - * - * @return the {@link Asset} - * - * @throws AlpacaClientException thrown for {@link AlpacaClientException}s - */ - public Asset getBySymbol(String symbolOrAssetID) throws AlpacaClientException { - checkNotNull(symbolOrAssetID); - - HttpUrl.Builder urlBuilder = alpacaClient.urlBuilder() - .addPathSegment(endpointPathSegment) - .addPathSegment(symbolOrAssetID); - Request request = alpacaClient.requestBuilder(urlBuilder.build()) - .get() - .build(); - return alpacaClient.requestObject(request, Asset.class); - } -} diff --git a/src/main/java/net/jacobpeterson/alpaca/rest/endpoint/calendar/CalendarEndpoint.java b/src/main/java/net/jacobpeterson/alpaca/rest/endpoint/calendar/CalendarEndpoint.java deleted file mode 100644 index cc1cb5d2..00000000 --- a/src/main/java/net/jacobpeterson/alpaca/rest/endpoint/calendar/CalendarEndpoint.java +++ /dev/null @@ -1,71 +0,0 @@ -package net.jacobpeterson.alpaca.rest.endpoint.calendar; - -import com.google.gson.reflect.TypeToken; -import net.jacobpeterson.alpaca.model.endpoint.calendar.Calendar; -import net.jacobpeterson.alpaca.rest.AlpacaClient; -import net.jacobpeterson.alpaca.rest.AlpacaClientException; -import net.jacobpeterson.alpaca.rest.endpoint.AlpacaEndpoint; -import okhttp3.HttpUrl; -import okhttp3.Request; - -import java.lang.reflect.Type; -import java.time.LocalDate; -import java.time.format.DateTimeFormatter; -import java.util.ArrayList; -import java.util.List; - -/** - * {@link AlpacaEndpoint} for Calendar. - */ -public class CalendarEndpoint extends AlpacaEndpoint { - - private static final Type CALENDAR_ARRAYLIST_TYPE = new TypeToken>() {}.getType(); - - /** - * Instantiates a new {@link CalendarEndpoint}. - * - * @param alpacaClient the {@link AlpacaClient} - */ - public CalendarEndpoint(AlpacaClient alpacaClient) { - super(alpacaClient, "calendar"); - } - - /** - * Returns the market {@link Calendar} from 1970 to 2029. - * - * @return a {@link List} of {@link Calendar}s - * - * @throws AlpacaClientException thrown for {@link AlpacaClientException}s - */ - public List get() throws AlpacaClientException { - return get(null, null); - } - - /** - * Returns the market {@link Calendar}. - * - * @param start the first date to retrieve data for (inclusive) - * @param end the last date to retrieve data for (inclusive) - * - * @return a {@link List} of {@link Calendar}s - * - * @throws AlpacaClientException thrown for {@link AlpacaClientException}s - */ - public List get(LocalDate start, LocalDate end) throws AlpacaClientException { - HttpUrl.Builder urlBuilder = alpacaClient.urlBuilder() - .addPathSegment(endpointPathSegment); - - if (start != null) { - urlBuilder.addQueryParameter("start", start.format(DateTimeFormatter.ISO_DATE)); - } - - if (end != null) { - urlBuilder.addQueryParameter("end", end.format(DateTimeFormatter.ISO_DATE)); - } - - Request request = alpacaClient.requestBuilder(urlBuilder.build()) - .get() - .build(); - return alpacaClient.requestObject(request, CALENDAR_ARRAYLIST_TYPE); - } -} diff --git a/src/main/java/net/jacobpeterson/alpaca/rest/endpoint/clock/ClockEndpoint.java b/src/main/java/net/jacobpeterson/alpaca/rest/endpoint/clock/ClockEndpoint.java deleted file mode 100644 index 368edc13..00000000 --- a/src/main/java/net/jacobpeterson/alpaca/rest/endpoint/clock/ClockEndpoint.java +++ /dev/null @@ -1,39 +0,0 @@ -package net.jacobpeterson.alpaca.rest.endpoint.clock; - -import net.jacobpeterson.alpaca.model.endpoint.clock.Clock; -import net.jacobpeterson.alpaca.rest.AlpacaClient; -import net.jacobpeterson.alpaca.rest.AlpacaClientException; -import net.jacobpeterson.alpaca.rest.endpoint.AlpacaEndpoint; -import okhttp3.HttpUrl; -import okhttp3.Request; - -/** - * {@link AlpacaEndpoint} for Clock. - */ -public class ClockEndpoint extends AlpacaEndpoint { - - /** - * Instantiates a new {@link ClockEndpoint}. - * - * @param alpacaClient the {@link AlpacaClient} - */ - public ClockEndpoint(AlpacaClient alpacaClient) { - super(alpacaClient, "clock"); - } - - /** - * Returns the market {@link Clock}. - * - * @return the market {@link Clock} - * - * @throws AlpacaClientException thrown for {@link AlpacaClientException}s - */ - public Clock get() throws AlpacaClientException { - HttpUrl.Builder urlBuilder = alpacaClient.urlBuilder() - .addPathSegment(endpointPathSegment); - Request request = alpacaClient.requestBuilder(urlBuilder.build()) - .get() - .build(); - return alpacaClient.requestObject(request, Clock.class); - } -} diff --git a/src/main/java/net/jacobpeterson/alpaca/rest/endpoint/corporateactions/CorporateActionsEndpoint.java b/src/main/java/net/jacobpeterson/alpaca/rest/endpoint/corporateactions/CorporateActionsEndpoint.java deleted file mode 100644 index f467c4f0..00000000 --- a/src/main/java/net/jacobpeterson/alpaca/rest/endpoint/corporateactions/CorporateActionsEndpoint.java +++ /dev/null @@ -1,121 +0,0 @@ -package net.jacobpeterson.alpaca.rest.endpoint.corporateactions; - -import com.google.gson.reflect.TypeToken; -import net.jacobpeterson.alpaca.model.endpoint.corporateactions.common.Announcement; -import net.jacobpeterson.alpaca.rest.AlpacaClient; -import net.jacobpeterson.alpaca.rest.AlpacaClientException; -import net.jacobpeterson.alpaca.rest.endpoint.AlpacaEndpoint; -import okhttp3.HttpUrl; -import okhttp3.Request; - -import java.lang.reflect.Type; -import java.time.LocalDate; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; - -import static com.google.common.base.Preconditions.checkNotNull; - -/** - * {@link AlpacaEndpoint} for - * Corporate actions - * Endpoint. - */ -public class CorporateActionsEndpoint extends AlpacaEndpoint { - - private static final Type ANNOUNCEMENT_ARRAYLIST_TYPE = new TypeToken>() {}.getType(); - - private static final List ALL_CA_TYPES = Arrays.asList("dividend", "merger", "spinoff", "split"); - - /** - * Instantiates a new {@link AlpacaEndpoint}. - * - * @param alpacaClient the {@link AlpacaClient} - */ - public CorporateActionsEndpoint(AlpacaClient alpacaClient) { - super(alpacaClient, "corporate_actions/announcements"); - } - - /** - * Gets a {@link Announcement} of the requested id. - * @param id - announcement id to query for - * @return the {@link Announcement} - * @throws AlpacaClientException thrown for {@link AlpacaClientException}s - */ - public Announcement getAnnouncement(String id) throws AlpacaClientException { - checkNotNull(id); - HttpUrl.Builder urlBuilder = alpacaClient.urlBuilder() - .addPathSegment(endpointPathSegment) - .addPathSegment(id); - Request request = alpacaClient.requestBuilder(urlBuilder.build()) - .get() - .build(); - return alpacaClient.requestObject(request, Announcement.class); - - } - - /** - * Gets list of all {@link Announcement} within last 90 days - * @return a {@link List} of {@link Announcement} - * @throws AlpacaClientException thrown for {@link AlpacaClientException}s - */ - public List getAnnouncements() throws AlpacaClientException { - return this.getAnnouncements(ALL_CA_TYPES, LocalDate.now().minusDays(90), LocalDate.now(), null, null, null); - } - - /** - * Gets list of all {@link Announcement} within last 90 days for specific symbol - * @param symbol the symbol to query for - * @return a {@link List} of {@link Announcement} - * @throws AlpacaClientException thrown for {@link AlpacaClientException}s - */ - public List getAnnouncements(String symbol) throws AlpacaClientException { - return this.getAnnouncements(ALL_CA_TYPES, LocalDate.now().minusDays(90), LocalDate.now(), symbol, null, null); - } - - /** - * Gets list of all {@link Announcement} for specific query - * @param caTypes - a list of Dividend, Merger, Spinoff, or Split - * @param since - the start (inclusive) of the date range when searching corporate action announcements. This should follow the YYYY-MM-DD format. The date range is limited to 90 days - * @param until - the end (inclusive) of the date range when searching corporate action announcements. This should follow the YYYY-MM-DD format. The date range is limited to 90 days - * @param symbol - the symbol of the company initiating the announcement - * @param cusip - the CUSIP of the company initiating the announcement - * @param dateType - one of declaration_date, ex_date, record_date, or payable_date - * @return a {@link List} of {@link Announcement} - * @throws AlpacaClientException thrown for {@link AlpacaClientException}s - */ - public List getAnnouncements( - List caTypes, LocalDate since, LocalDate until, - String symbol, - String cusip, - String dateType - ) - throws AlpacaClientException { - HttpUrl.Builder urlBuilder = alpacaClient.urlBuilder() - .addPathSegment(endpointPathSegment); - checkNotNull(caTypes); - checkNotNull(since); - checkNotNull(until); - - - urlBuilder.addQueryParameter("ca_types", String.join(",", caTypes)); - - urlBuilder.addQueryParameter("since", since.toString()); - urlBuilder.addQueryParameter("until", until.toString()); - - if (symbol != null) { - urlBuilder.addQueryParameter("symbol", symbol); - } - if (cusip != null) { - urlBuilder.addQueryParameter("cusip", cusip); - } - if (dateType !=null) { - urlBuilder.addQueryParameter("date_type", dateType); - } - Request request = alpacaClient.requestBuilder(urlBuilder.build()) - .get() - .build(); - return alpacaClient.requestObject(request, ANNOUNCEMENT_ARRAYLIST_TYPE); - - } -} diff --git a/src/main/java/net/jacobpeterson/alpaca/rest/endpoint/marketdata/crypto/CryptoMarketDataEndpoint.java b/src/main/java/net/jacobpeterson/alpaca/rest/endpoint/marketdata/crypto/CryptoMarketDataEndpoint.java deleted file mode 100644 index f429a32c..00000000 --- a/src/main/java/net/jacobpeterson/alpaca/rest/endpoint/marketdata/crypto/CryptoMarketDataEndpoint.java +++ /dev/null @@ -1,302 +0,0 @@ -package net.jacobpeterson.alpaca.rest.endpoint.marketdata.crypto; - -import net.jacobpeterson.alpaca.model.endpoint.marketdata.common.historical.bar.enums.BarTimePeriod; -import net.jacobpeterson.alpaca.model.endpoint.marketdata.crypto.historical.bar.CryptoBar; -import net.jacobpeterson.alpaca.model.endpoint.marketdata.crypto.historical.bar.CryptoBarsResponse; -import net.jacobpeterson.alpaca.model.endpoint.marketdata.crypto.historical.bar.LatestCryptoBarsResponse; -import net.jacobpeterson.alpaca.model.endpoint.marketdata.crypto.historical.orderbook.CryptoOrderbook; -import net.jacobpeterson.alpaca.model.endpoint.marketdata.crypto.historical.orderbook.LatestCryptoOrderbooksResponse; -import net.jacobpeterson.alpaca.model.endpoint.marketdata.crypto.historical.quote.CryptoQuote; -import net.jacobpeterson.alpaca.model.endpoint.marketdata.crypto.historical.quote.LatestCryptoQuotesResponse; -import net.jacobpeterson.alpaca.model.endpoint.marketdata.crypto.historical.snapshot.CryptoSnapshot; -import net.jacobpeterson.alpaca.model.endpoint.marketdata.crypto.historical.snapshot.CryptoSnapshotsResponse; -import net.jacobpeterson.alpaca.model.endpoint.marketdata.crypto.historical.trade.CryptoTrade; -import net.jacobpeterson.alpaca.model.endpoint.marketdata.crypto.historical.trade.CryptoTradesResponse; -import net.jacobpeterson.alpaca.model.endpoint.marketdata.crypto.historical.trade.LatestCryptoTradesResponse; -import net.jacobpeterson.alpaca.rest.AlpacaClient; -import net.jacobpeterson.alpaca.rest.AlpacaClientException; -import net.jacobpeterson.alpaca.rest.endpoint.AlpacaEndpoint; -import net.jacobpeterson.alpaca.util.format.FormatUtil; -import okhttp3.HttpUrl; -import okhttp3.Request; - -import java.time.ZonedDateTime; -import java.util.Collection; - -import static com.google.common.base.Preconditions.checkNotNull; - -/** - * {@link AlpacaEndpoint} for - * Historical - * Crypto Market Data API. - */ -public class CryptoMarketDataEndpoint extends AlpacaEndpoint { - - public static final String LOCALE = "us"; - - /** - * Instantiates a new {@link CryptoMarketDataEndpoint}. - * - * @param alpacaClient the {@link AlpacaClient} - */ - public CryptoMarketDataEndpoint(AlpacaClient alpacaClient) { - super(alpacaClient, "crypto"); - } - - /** - * Gets {@link CryptoTrade} historical data for the requested crypto symbols. - * - * @param symbols a {@link Collection} of symbols to query for - * @param start filter data equal to or after this {@link ZonedDateTime}. Fractions of a second are not - * accepted. null for the current day in Central Time. - * @param end filter data equal to or before this {@link ZonedDateTime}. Fractions of a second are not - * accepted. null for now. - * @param limit number of data points to return. Must be in range 1-10000, defaults to 1000 if null - * is given - * @param pageToken pagination token to continue from - * - * @return the {@link CryptoTradesResponse} - * - * @throws AlpacaClientException thrown for {@link AlpacaClientException}s - */ - public CryptoTradesResponse getTrades(Collection symbols, ZonedDateTime start, - ZonedDateTime end, Integer limit, String pageToken) throws AlpacaClientException { - checkNotNull(symbols); - if (symbols.isEmpty()) { - throw new AlpacaClientException("Collection symbols must not be empty."); - } - - HttpUrl.Builder urlBuilder = alpacaClient.urlBuilder() - .addPathSegment(endpointPathSegment) - .addPathSegment(LOCALE) - .addPathSegment("trades"); - - urlBuilder.addQueryParameter("symbols", String.join(",", symbols)); - - if (start != null) { - urlBuilder.addQueryParameter("start", FormatUtil.toRFC3339Format(start)); - } - - if (end != null) { - urlBuilder.addQueryParameter("end", FormatUtil.toRFC3339Format(end)); - } - - if (limit != null) { - urlBuilder.addQueryParameter("limit", limit.toString()); - } - - if (pageToken != null) { - urlBuilder.addQueryParameter("page_token", pageToken); - } - - Request request = alpacaClient.requestBuilder(urlBuilder.build()) - .get() - .build(); - return alpacaClient.requestObject(request, CryptoTradesResponse.class); - } - - /** - * Gets the latest {@link CryptoTrade}s for the requested securities. - * - * @param symbols a {@link Collection} of symbols to query for - * - * @return the {@link LatestCryptoTradesResponse} - * - * @throws AlpacaClientException thrown for {@link AlpacaClientException}s - */ - public LatestCryptoTradesResponse getLatestTrades(Collection symbols) throws AlpacaClientException { - checkNotNull(symbols); - if (symbols.isEmpty()) { - throw new AlpacaClientException("Collection symbols must not be empty."); - } - - HttpUrl.Builder urlBuilder = alpacaClient.urlBuilder() - .addPathSegment(endpointPathSegment) - .addPathSegment(LOCALE) - .addPathSegment("latest") - .addPathSegment("trades"); - - urlBuilder.addQueryParameter("symbols", String.join(",", symbols)); - - Request request = alpacaClient.requestBuilder(urlBuilder.build()) - .get() - .build(); - return alpacaClient.requestObject(request, LatestCryptoTradesResponse.class); - } - - /** - * Gets the latest {@link CryptoBar}s for the requested securities. - * - * @param symbols a {@link Collection} of symbols to query for - * - * @return the {@link LatestCryptoBarsResponse} - * - * @throws AlpacaClientException thrown for {@link AlpacaClientException}s - */ - public LatestCryptoBarsResponse getLatestBars(Collection symbols) throws AlpacaClientException { - checkNotNull(symbols); - if (symbols.isEmpty()) { - throw new AlpacaClientException("Collection symbols must not be empty."); - } - - HttpUrl.Builder urlBuilder = alpacaClient.urlBuilder() - .addPathSegment(endpointPathSegment) - .addPathSegment(LOCALE) - .addPathSegment("latest") - .addPathSegment("bars"); - - urlBuilder.addQueryParameter("symbols", String.join(",", symbols)); - - Request request = alpacaClient.requestBuilder(urlBuilder.build()) - .get() - .build(); - return alpacaClient.requestObject(request, LatestCryptoBarsResponse.class); - } - - /** - * Gets the latest {@link CryptoQuote}s for the requested securities. - * - * @param symbols a {@link Collection} of symbols to query for - * - * @return the {@link LatestCryptoQuotesResponse} - * - * @throws AlpacaClientException thrown for {@link AlpacaClientException}s - */ - public LatestCryptoQuotesResponse getLatestQuotes(Collection symbols) throws AlpacaClientException { - checkNotNull(symbols); - if (symbols.isEmpty()) { - throw new AlpacaClientException("Collection symbols must not be empty."); - } - - HttpUrl.Builder urlBuilder = alpacaClient.urlBuilder() - .addPathSegment(endpointPathSegment) - .addPathSegment(LOCALE) - .addPathSegment("latest") - .addPathSegment("quotes"); - - urlBuilder.addQueryParameter("symbols", String.join(",", symbols)); - - Request request = alpacaClient.requestBuilder(urlBuilder.build()) - .get() - .build(); - return alpacaClient.requestObject(request, LatestCryptoQuotesResponse.class); - } - - /** - * Gets the latest {@link CryptoSnapshot} for the requested securities. - * - * @param symbols a {@link Collection} of symbols to query for - * - * @return the {@link CryptoSnapshotsResponse} - * - * @throws AlpacaClientException thrown for {@link AlpacaClientException}s - */ - public CryptoSnapshotsResponse getSnapshots(Collection symbols) throws AlpacaClientException { - checkNotNull(symbols); - if (symbols.isEmpty()) { - throw new AlpacaClientException("Collection symbols must not be empty."); - } - - HttpUrl.Builder urlBuilder = alpacaClient.urlBuilder() - .addPathSegment(endpointPathSegment) - .addPathSegment(LOCALE) - .addPathSegment("snapshots"); - - urlBuilder.addQueryParameter("symbols", String.join(",", symbols)); - - Request request = alpacaClient.requestBuilder(urlBuilder.build()) - .get() - .build(); - return alpacaClient.requestObject(request, CryptoSnapshotsResponse.class); - } - - /** - * Gets the latest {@link CryptoOrderbook}s for the requested securities. - * - * @param symbols a {@link Collection} of symbols to query for - * - * @return the {@link LatestCryptoOrderbooksResponse} - * - * @throws AlpacaClientException thrown for {@link AlpacaClientException}s - */ - public LatestCryptoOrderbooksResponse getLatestOrderbooks(Collection symbols) throws AlpacaClientException { - checkNotNull(symbols); - if (symbols.isEmpty()) { - throw new AlpacaClientException("Collection symbols must not be empty."); - } - - HttpUrl.Builder urlBuilder = alpacaClient.urlBuilder() - .addPathSegment(endpointPathSegment) - .addPathSegment(LOCALE) - .addPathSegment("latest") - .addPathSegment("orderbooks"); - - urlBuilder.addQueryParameter("symbols", String.join(",", symbols)); - - Request request = alpacaClient.requestBuilder(urlBuilder.build()) - .get() - .build(); - return alpacaClient.requestObject(request, LatestCryptoOrderbooksResponse.class); - } - - /** - * Gets {@link CryptoBar} aggregate historical data for the requested crypto. - * - * @param symbols a {@link Collection} of symbols to query for - * @param start filter data equal to or after this {@link ZonedDateTime}. Fractions of a second are - * not accepted. null for now. - * @param end filter data equal to or before this {@link ZonedDateTime}. Fractions of a second are - * not accepted. null for now. - * @param limit number of data points to return. Must be in range 1-10000, defaults to 1000 if - * null is given - * @param pageToken pagination token to continue from - * @param barTimePeriodDuration the duration for the given barTimePeriod parameter. e.g. for - * 15Min bars, you would supply 15 for this parameter and - * {@link BarTimePeriod#MINUTE} for the barTimePeriod parameter. - * @param barTimePeriod the {@link BarTimePeriod} e.g. for 15Min bars, you would supply - * {@link BarTimePeriod#MINUTE} for this parameter and 15 for the - * barTimePeriodDuration parameter. - * - * @return the {@link CryptoBarsResponse} - * - * @throws AlpacaClientException thrown for {@link AlpacaClientException}s - */ - public CryptoBarsResponse getBars(Collection symbols, ZonedDateTime start, ZonedDateTime end, Integer limit, - String pageToken, int barTimePeriodDuration, BarTimePeriod barTimePeriod) throws AlpacaClientException { - checkNotNull(symbols); - if (symbols.isEmpty()) { - throw new AlpacaClientException("Collection symbols must not be empty."); - } - checkNotNull(barTimePeriod); - - HttpUrl.Builder urlBuilder = alpacaClient.urlBuilder() - .addPathSegment(endpointPathSegment) - .addPathSegment(LOCALE) - .addPathSegment("bars"); - - urlBuilder.addQueryParameter("symbols", String.join(",", symbols)); - - if (start != null) { - urlBuilder.addQueryParameter("start", FormatUtil.toRFC3339Format(start)); - } - - if (end != null) { - urlBuilder.addQueryParameter("end", FormatUtil.toRFC3339Format(end)); - } - - if (limit != null) { - urlBuilder.addQueryParameter("limit", limit.toString()); - } - - if (pageToken != null) { - urlBuilder.addQueryParameter("page_token", pageToken); - } - - urlBuilder.addQueryParameter("timeframe", barTimePeriodDuration + barTimePeriod.toString()); - - Request request = alpacaClient.requestBuilder(urlBuilder.build()) - .get() - .build(); - return alpacaClient.requestObject(request, CryptoBarsResponse.class); - } -} diff --git a/src/main/java/net/jacobpeterson/alpaca/rest/endpoint/marketdata/news/NewsEndpoint.java b/src/main/java/net/jacobpeterson/alpaca/rest/endpoint/marketdata/news/NewsEndpoint.java deleted file mode 100644 index 88ee1b7c..00000000 --- a/src/main/java/net/jacobpeterson/alpaca/rest/endpoint/marketdata/news/NewsEndpoint.java +++ /dev/null @@ -1,120 +0,0 @@ -package net.jacobpeterson.alpaca.rest.endpoint.marketdata.news; - -import net.jacobpeterson.alpaca.model.endpoint.common.enums.SortDirection; -import net.jacobpeterson.alpaca.model.endpoint.marketdata.news.NewsResponse; -import net.jacobpeterson.alpaca.rest.AlpacaClient; -import net.jacobpeterson.alpaca.rest.AlpacaClientException; -import net.jacobpeterson.alpaca.rest.endpoint.AlpacaEndpoint; -import okhttp3.HttpUrl; -import okhttp3.Request; - -import java.time.LocalDate; -import java.util.List; - -import static com.google.common.base.Preconditions.checkNotNull; - -/** - * {@link AlpacaEndpoint} for - * News - * Endpoint. - */ -public class NewsEndpoint extends AlpacaEndpoint { - - /** - * Instantiates a new {@link AlpacaEndpoint}. - * - * @param alpacaClient the {@link AlpacaClient} - */ - public NewsEndpoint(AlpacaClient alpacaClient) { - super(alpacaClient, "news"); - } - - /** - * Gets latest list of news {@link NewsResponse}. - * @return the {@link NewsResponse} - * @throws AlpacaClientException thrown for {@link AlpacaClientException} - */ - public NewsResponse getLatestNews() throws AlpacaClientException { - return this.getLatestNews(null, null, null, null, null, null, null, null); - } - - /** - * Gets latest list of news {@link NewsResponse} for specific symbols. - * @param symbols list of symbols to query news for - * @return the {@link NewsResponse} - * @throws AlpacaClientException thrown for {@link AlpacaClientException}s - */ - public NewsResponse getLatestNews(List symbols) throws AlpacaClientException { - checkNotNull(symbols); - return this.getLatestNews(null, null, null, symbols, null, null, null, null); - } - - /** - * Gets latest list of news {@link NewsResponse} for specified token. - * @param pageToken the ID of the end of your current page of results. (See the section on paging.) - * @return the {@link NewsResponse} - * @throws AlpacaClientException thrown for {@link AlpacaClientException}s - */ - public NewsResponse getLatestNewsPaged(String pageToken) throws AlpacaClientException { - checkNotNull(pageToken); - return this.getLatestNews(null, null, null, null, null, null, null, pageToken); - } - - /** - * Gets latest list of news {@link NewsResponse} - * @param start The inclusive start of the interval - * @param end the inclusive end of the interval - * @param sortDirection sort articles by updated date - * @param symbols list of symbols to query news for - * @param limit Limit of news items to be returned for given page - * @param includeContent Boolean indicator to include content for news articles (if available) - * @param excludeContentless Boolean indicator to exclude news articles that do not contain content - * @param pageToken the ID of the end of your current page of results. (See the section on paging.) - * @return the {@link NewsResponse} - * @throws AlpacaClientException thrown for {@link AlpacaClientException}s - */ - public NewsResponse getLatestNews( - LocalDate start, LocalDate end, - SortDirection sortDirection, - List symbols, - Integer limit, - Boolean includeContent, - Boolean excludeContentless, - String pageToken) throws AlpacaClientException { - - HttpUrl.Builder urlBuilder = alpacaClient.urlBuilder() - .addPathSegment(endpointPathSegment); - - if (start != null) { - urlBuilder.addQueryParameter("start", start.toString()); - } - if (end != null) { - urlBuilder.addQueryParameter("end", end.toString()); - } - if (sortDirection != null) { - urlBuilder.addQueryParameter("sort", sortDirection.value()); - } - if (symbols != null && !symbols.isEmpty()) { - urlBuilder.addQueryParameter("symbols", String.join(",", symbols)); - } - if (limit != null) { - urlBuilder.addQueryParameter("limit", Integer.toString(limit)); - } - if (includeContent != null) { - urlBuilder.addQueryParameter("include_content", Boolean.toString(includeContent)); - } - if (excludeContentless != null) { - urlBuilder.addQueryParameter("exclude_contentless", Boolean.toString(excludeContentless)); - } - if (pageToken != null) { - urlBuilder.addQueryParameter("page_token", pageToken); - } - - Request request = alpacaClient.requestBuilder(urlBuilder.build()) - .get() - .build(); - - return alpacaClient.requestObject(request, NewsResponse.class); - - } -} diff --git a/src/main/java/net/jacobpeterson/alpaca/rest/endpoint/marketdata/stock/StockMarketDataEndpoint.java b/src/main/java/net/jacobpeterson/alpaca/rest/endpoint/marketdata/stock/StockMarketDataEndpoint.java deleted file mode 100644 index 3111dde7..00000000 --- a/src/main/java/net/jacobpeterson/alpaca/rest/endpoint/marketdata/stock/StockMarketDataEndpoint.java +++ /dev/null @@ -1,368 +0,0 @@ -package net.jacobpeterson.alpaca.rest.endpoint.marketdata.stock; - -import com.google.gson.reflect.TypeToken; -import net.jacobpeterson.alpaca.model.endpoint.marketdata.common.historical.bar.enums.BarTimePeriod; -import net.jacobpeterson.alpaca.model.endpoint.marketdata.stock.historical.bar.MultiStockBarsResponse; -import net.jacobpeterson.alpaca.model.endpoint.marketdata.stock.historical.bar.StockBar; -import net.jacobpeterson.alpaca.model.endpoint.marketdata.stock.historical.bar.StockBarsResponse; -import net.jacobpeterson.alpaca.model.endpoint.marketdata.stock.historical.bar.enums.BarAdjustment; -import net.jacobpeterson.alpaca.model.endpoint.marketdata.stock.historical.bar.enums.BarFeed; -import net.jacobpeterson.alpaca.model.endpoint.marketdata.stock.historical.quote.LatestStockQuoteResponse; -import net.jacobpeterson.alpaca.model.endpoint.marketdata.stock.historical.quote.StockQuote; -import net.jacobpeterson.alpaca.model.endpoint.marketdata.stock.historical.quote.StockQuotesResponse; -import net.jacobpeterson.alpaca.model.endpoint.marketdata.stock.historical.snapshot.Snapshot; -import net.jacobpeterson.alpaca.model.endpoint.marketdata.stock.historical.trade.LatestStockTradeResponse; -import net.jacobpeterson.alpaca.model.endpoint.marketdata.stock.historical.trade.StockTrade; -import net.jacobpeterson.alpaca.model.endpoint.marketdata.stock.historical.trade.StockTradesResponse; -import net.jacobpeterson.alpaca.rest.AlpacaClient; -import net.jacobpeterson.alpaca.rest.AlpacaClientException; -import net.jacobpeterson.alpaca.rest.endpoint.AlpacaEndpoint; -import net.jacobpeterson.alpaca.util.format.FormatUtil; -import okhttp3.HttpUrl; -import okhttp3.Request; - -import java.lang.reflect.Type; -import java.time.ZonedDateTime; -import java.util.Collection; -import java.util.HashMap; -import java.util.Map; - -import static com.google.common.base.Preconditions.checkArgument; -import static com.google.common.base.Preconditions.checkNotNull; - -/** - * {@link AlpacaEndpoint} for - * Historical - * Market Data API v2. - */ -public class StockMarketDataEndpoint extends AlpacaEndpoint { - - private static final Type SNAPSHOTS_OF_STRINGS_HASHMAP_TYPE = - new TypeToken>() {}.getType(); - - /** - * Instantiates a new {@link StockMarketDataEndpoint}. - * - * @param alpacaClient the {@link AlpacaClient} - */ - public StockMarketDataEndpoint(AlpacaClient alpacaClient) { - super(alpacaClient, "stocks"); - } - - /** - * Gets {@link StockTrade} historical data for the requested security. - * - * @param symbol the symbol to query for - * @param start filter data equal to or after this {@link ZonedDateTime}. Fractions of a second are not - * accepted. - * @param end filter data equal to or before this {@link ZonedDateTime}. Fractions of a second are not - * accepted. - * @param limit number of data points to return. Must be in range 1-10000, defaults to 1000 if null - * is given - * @param pageToken pagination token to continue from - * - * @return the {@link StockTradesResponse} - * - * @throws AlpacaClientException thrown for {@link AlpacaClientException}s - */ - public StockTradesResponse getTrades(String symbol, ZonedDateTime start, ZonedDateTime end, Integer limit, - String pageToken) throws AlpacaClientException { - checkNotNull(symbol); - checkNotNull(start); - checkNotNull(end); - - HttpUrl.Builder urlBuilder = alpacaClient.urlBuilder() - .addPathSegment(endpointPathSegment) - .addPathSegment(symbol) - .addPathSegment("trades"); - - urlBuilder.addQueryParameter("start", FormatUtil.toRFC3339Format(start)); - urlBuilder.addQueryParameter("end", FormatUtil.toRFC3339Format(end)); - - if (limit != null) { - urlBuilder.addQueryParameter("limit", limit.toString()); - } - - if (pageToken != null) { - urlBuilder.addQueryParameter("page_token", pageToken); - } - - Request request = alpacaClient.requestBuilder(urlBuilder.build()) - .get() - .build(); - return alpacaClient.requestObject(request, StockTradesResponse.class); - } - - /** - * Gets the latest {@link StockTrade} for the requested security. - * - * @param symbol the symbol to query for - * - * @return the {@link LatestStockTradeResponse} - * - * @throws AlpacaClientException thrown for {@link AlpacaClientException}s - */ - public LatestStockTradeResponse getLatestTrade(String symbol) throws AlpacaClientException { - checkNotNull(symbol); - - HttpUrl.Builder urlBuilder = alpacaClient.urlBuilder() - .addPathSegment(endpointPathSegment) - .addPathSegment(symbol) - .addPathSegment("trades") - .addPathSegment("latest"); - - Request request = alpacaClient.requestBuilder(urlBuilder.build()) - .get() - .build(); - return alpacaClient.requestObject(request, LatestStockTradeResponse.class); - } - - /** - * Gets {@link StockQuote} (NBBO or National Best Bid and Offer) historical data for the requested security. - * - * @param symbol the symbol to query for - * @param start filter data equal to or after this {@link ZonedDateTime}. Fractions of a second are not - * accepted. - * @param end filter data equal to or before this {@link ZonedDateTime}. Fractions of a second are not - * accepted. - * @param limit number of data points to return. Must be in range 1-10000, defaults to 1000 if null - * is given - * @param pageToken pagination token to continue from - * - * @return the {@link StockQuotesResponse} - * - * @throws AlpacaClientException thrown for {@link AlpacaClientException}s - */ - public StockQuotesResponse getQuotes(String symbol, ZonedDateTime start, ZonedDateTime end, Integer limit, - String pageToken) throws AlpacaClientException { - checkNotNull(symbol); - checkNotNull(start); - checkNotNull(end); - - HttpUrl.Builder urlBuilder = alpacaClient.urlBuilder() - .addPathSegment(endpointPathSegment) - .addPathSegment(symbol) - .addPathSegment("quotes"); - - urlBuilder.addQueryParameter("start", FormatUtil.toRFC3339Format(start)); - urlBuilder.addQueryParameter("end", FormatUtil.toRFC3339Format(end)); - - if (limit != null) { - urlBuilder.addQueryParameter("limit", limit.toString()); - } - - if (pageToken != null) { - urlBuilder.addQueryParameter("page_token", pageToken); - } - - Request request = alpacaClient.requestBuilder(urlBuilder.build()) - .get() - .build(); - return alpacaClient.requestObject(request, StockQuotesResponse.class); - } - - /** - * Gets the latest {@link StockQuote} for the requested security. - * - * @param symbol the symbol to query for - * - * @return the {@link LatestStockQuoteResponse} - * - * @throws AlpacaClientException thrown for {@link AlpacaClientException}s - */ - public LatestStockQuoteResponse getLatestQuote(String symbol) throws AlpacaClientException { - checkNotNull(symbol); - - HttpUrl.Builder urlBuilder = alpacaClient.urlBuilder() - .addPathSegment(endpointPathSegment) - .addPathSegment(symbol) - .addPathSegment("quotes") - .addPathSegment("latest"); - - Request request = alpacaClient.requestBuilder(urlBuilder.build()) - .get() - .build(); - return alpacaClient.requestObject(request, LatestStockQuoteResponse.class); - } - - /** - * Gets {@link StockBar} aggregate historical data for the requested security. - * - * @param symbol the symbol to query for - * @param start filter data equal to or after this {@link ZonedDateTime}. Fractions of a second are - * not accepted. - * @param end filter data equal to or before this {@link ZonedDateTime}. Fractions of a second are - * not accepted. - * @param limit number of data points to return. Must be in range 1-10000, defaults to 1000 if - * null is given - * @param pageToken pagination token to continue from - * @param barTimePeriodDuration the duration for the given barTimePeriod parameter. e.g. for - * 15Min bars, you would supply 15 for this parameter and - * {@link BarTimePeriod#MINUTE} for the barTimePeriod parameter. - * @param barTimePeriod the {@link BarTimePeriod}. e.g. for 15Min bars, you would supply - * {@link BarTimePeriod#MINUTE} for this parameter and 15 for the - * barTimePeriodDuration parameter. - * @param barAdjustment specifies the corporate action adjustment for the stocks. Default value is - * {@link BarAdjustment#RAW} - * @param barFeed defaults to {@link BarFeed#IEX} for Free users and {@link BarFeed#SIP} for users - * with an Unlimited subscription - * - * @return the {@link StockBarsResponse} - * - * @throws AlpacaClientException thrown for {@link AlpacaClientException}s - */ - public StockBarsResponse getBars(String symbol, ZonedDateTime start, ZonedDateTime end, Integer limit, - String pageToken, int barTimePeriodDuration, BarTimePeriod barTimePeriod, BarAdjustment barAdjustment, - BarFeed barFeed) throws AlpacaClientException { - checkNotNull(symbol); - checkNotNull(start); - checkNotNull(end); - checkNotNull(barTimePeriod); - - HttpUrl.Builder urlBuilder = alpacaClient.urlBuilder() - .addPathSegment(endpointPathSegment) - .addPathSegment(symbol) - .addPathSegment("bars"); - - urlBuilder.addQueryParameter("start", FormatUtil.toRFC3339Format(start)); - urlBuilder.addQueryParameter("end", FormatUtil.toRFC3339Format(end)); - - if (limit != null) { - urlBuilder.addQueryParameter("limit", limit.toString()); - } - - if (pageToken != null) { - urlBuilder.addQueryParameter("page_token", pageToken); - } - - urlBuilder.addQueryParameter("timeframe", barTimePeriodDuration + barTimePeriod.toString()); - - if (barAdjustment != null) { - urlBuilder.addQueryParameter("adjustment", barAdjustment.toString()); - } - - if (barFeed != null) { - urlBuilder.addQueryParameter("feed", barFeed.toString()); - } - - Request request = alpacaClient.requestBuilder(urlBuilder.build()) - .get() - .build(); - return alpacaClient.requestObject(request, StockBarsResponse.class); - } - - /** - * Gets {@link StockBar} aggregate historical data for the requested securities. - * - * @param symbols a {@link Collection} of symbols to query for - * @param start filter data equal to or after this {@link ZonedDateTime}. Fractions of a second are - * not accepted. - * @param end filter data equal to or before this {@link ZonedDateTime}. Fractions of a second are - * not accepted. - * @param limit number of data points to return. Must be in range 1-10000, defaults to 1000 if - * null is given - * @param pageToken pagination token to continue from - * @param barTimePeriodDuration the duration for the given barTimePeriod parameter. e.g. for - * 15Min bars, you would supply 15 for this parameter and - * {@link BarTimePeriod#MINUTE} for the barTimePeriod parameter. - * @param barTimePeriod the {@link BarTimePeriod}. e.g. for 15Min bars, you would supply - * {@link BarTimePeriod#MINUTE} for this parameter and 15 for the - * barTimePeriodDuration parameter. - * @param barAdjustment specifies the corporate action adjustment for the stocks. Default value is - * {@link BarAdjustment#RAW} - * @param barFeed defaults to {@link BarFeed#IEX} for Free users and {@link BarFeed#SIP} for users - * with an Unlimited subscription - * - * @return the {@link MultiStockBarsResponse} - * - * @throws AlpacaClientException thrown for {@link AlpacaClientException}s - */ - public MultiStockBarsResponse getBars(Collection symbols, ZonedDateTime start, ZonedDateTime end, - Integer limit, String pageToken, int barTimePeriodDuration, BarTimePeriod barTimePeriod, - BarAdjustment barAdjustment, BarFeed barFeed) throws AlpacaClientException { - checkNotNull(symbols); - checkArgument(!symbols.isEmpty(), "'symbols' cannot be empty!"); - checkNotNull(start); - checkNotNull(end); - checkNotNull(barTimePeriod); - - HttpUrl.Builder urlBuilder = alpacaClient.urlBuilder() - .addPathSegment(endpointPathSegment) - .addPathSegment("bars"); - - urlBuilder.addQueryParameter("symbols", String.join(",", symbols)); - urlBuilder.addQueryParameter("start", FormatUtil.toRFC3339Format(start)); - urlBuilder.addQueryParameter("end", FormatUtil.toRFC3339Format(end)); - - if (limit != null) { - urlBuilder.addQueryParameter("limit", limit.toString()); - } - - if (pageToken != null) { - urlBuilder.addQueryParameter("page_token", pageToken); - } - - urlBuilder.addQueryParameter("timeframe", barTimePeriodDuration + barTimePeriod.toString()); - - if (barAdjustment != null) { - urlBuilder.addQueryParameter("adjustment", barAdjustment.toString()); - } - - if (barFeed != null) { - urlBuilder.addQueryParameter("feed", barFeed.toString()); - } - - Request request = alpacaClient.requestBuilder(urlBuilder.build()) - .get() - .build(); - return alpacaClient.requestObject(request, MultiStockBarsResponse.class); - } - - /** - * Gets {@link Snapshot}s of the requested securities. - * - * @param symbols a {@link Collection} of symbols to query for - * - * @return a {@link Map} with they keys being the symbol and their values being the {@link Snapshot} - * - * @throws AlpacaClientException thrown for {@link AlpacaClientException}s - */ - public Map getSnapshots(Collection symbols) throws AlpacaClientException { - checkNotNull(symbols); - checkArgument(!symbols.isEmpty(), "'symbols' cannot be empty!"); - - HttpUrl.Builder urlBuilder = alpacaClient.urlBuilder() - .addPathSegment(endpointPathSegment) - .addPathSegment("snapshots"); - - urlBuilder.addQueryParameter("symbols", String.join(",", symbols)); - - Request request = alpacaClient.requestBuilder(urlBuilder.build()) - .get() - .build(); - return alpacaClient.requestObject(request, SNAPSHOTS_OF_STRINGS_HASHMAP_TYPE); - } - - /** - * Gets a {@link Snapshot} of the requested security. - * - * @param symbol the symbol to query for - * - * @return the {@link Snapshot} - * - * @throws AlpacaClientException thrown for {@link AlpacaClientException}s - */ - public Snapshot getSnapshot(String symbol) throws AlpacaClientException { - checkNotNull(symbol); - - HttpUrl.Builder urlBuilder = alpacaClient.urlBuilder() - .addPathSegment(endpointPathSegment) - .addPathSegment(symbol) - .addPathSegment("snapshot"); - - Request request = alpacaClient.requestBuilder(urlBuilder.build()) - .get() - .build(); - return alpacaClient.requestObject(request, Snapshot.class); - } -} diff --git a/src/main/java/net/jacobpeterson/alpaca/rest/endpoint/orders/OrdersEndpoint.java b/src/main/java/net/jacobpeterson/alpaca/rest/endpoint/orders/OrdersEndpoint.java deleted file mode 100644 index b2a0563d..00000000 --- a/src/main/java/net/jacobpeterson/alpaca/rest/endpoint/orders/OrdersEndpoint.java +++ /dev/null @@ -1,651 +0,0 @@ -package net.jacobpeterson.alpaca.rest.endpoint.orders; - -import com.google.gson.JsonObject; -import com.google.gson.JsonPrimitive; -import com.google.gson.reflect.TypeToken; -import net.jacobpeterson.alpaca.model.endpoint.common.enums.SortDirection; -import net.jacobpeterson.alpaca.model.endpoint.orders.CancelledOrder; -import net.jacobpeterson.alpaca.model.endpoint.orders.Order; -import net.jacobpeterson.alpaca.model.endpoint.orders.enums.CurrentOrderStatus; -import net.jacobpeterson.alpaca.model.endpoint.orders.enums.OrderClass; -import net.jacobpeterson.alpaca.model.endpoint.orders.enums.OrderSide; -import net.jacobpeterson.alpaca.model.endpoint.orders.enums.OrderStatus; -import net.jacobpeterson.alpaca.model.endpoint.orders.enums.OrderTimeInForce; -import net.jacobpeterson.alpaca.model.endpoint.orders.enums.OrderType; -import net.jacobpeterson.alpaca.rest.AlpacaClient; -import net.jacobpeterson.alpaca.rest.AlpacaClientException; -import net.jacobpeterson.alpaca.rest.endpoint.AlpacaEndpoint; -import net.jacobpeterson.alpaca.util.format.FormatUtil; -import net.jacobpeterson.alpaca.util.okhttp.JSONBodyBuilder; -import okhttp3.HttpUrl; -import okhttp3.Request; - -import java.lang.reflect.Type; -import java.time.ZonedDateTime; -import java.time.format.DateTimeFormatter; -import java.util.ArrayList; -import java.util.Collection; -import java.util.List; - -import static com.google.common.base.Preconditions.checkNotNull; -import static com.google.common.base.Preconditions.checkState; -import static net.jacobpeterson.alpaca.rest.AlpacaClient.STATUS_CODE_200_OR_204; -import static net.jacobpeterson.alpaca.rest.AlpacaClient.STATUS_CODE_200_OR_207; - -/** - * {@link AlpacaEndpoint} for Orders. - */ -public class OrdersEndpoint extends AlpacaEndpoint { - - private static final Type ORDER_ARRAYLIST_TYPE = new TypeToken>() {}.getType(); - private static final Type CANCELLED_ORDER_ARRAYLIST_TYPE = new TypeToken>() {}.getType(); - - /** - * Instantiates a new {@link OrdersEndpoint}. - * - * @param alpacaClient the {@link AlpacaClient} - */ - public OrdersEndpoint(AlpacaClient alpacaClient) { - super(alpacaClient, "orders"); - } - - /** - * Retrieves a list of {@link Order}s for the account, filtered by the supplied query parameters. - * - * @param status {@link CurrentOrderStatus} to be queried. Defaults to {@link CurrentOrderStatus#OPEN}. - * @param limit the maximum number of orders in response. Defaults to 50 and max is 500. - * @param after the response will include only ones submitted after this timestamp (exclusive) - * @param until the response will include only ones submitted until this timestamp (exclusive) - * @param direction the chronological order of response based on the submission time. Defaults to - * {@link SortDirection#DESCENDING}. - * @param nested if true, the result will roll up multi-leg orders under the legs field of primary order. - * @param symbols a {@link Collection} of symbols to filter by (e.g. "AAPL,TSLA,MSFT"). A currency pair is - * required for crypto orders (e.g. "BTCUSD,BCHUSD,LTCUSD,ETCUSD"). null for no - * filter. - * - * @return a {@link List} of {@link Order}s - * - * @throws AlpacaClientException thrown for {@link AlpacaClientException}s - */ - public List get(CurrentOrderStatus status, Integer limit, ZonedDateTime after, ZonedDateTime until, - SortDirection direction, Boolean nested, Collection symbols) throws AlpacaClientException { - HttpUrl.Builder urlBuilder = alpacaClient.urlBuilder() - .addPathSegment(endpointPathSegment); - - if (status != null) { - urlBuilder.addQueryParameter("status", status.toString()); - } - - if (limit != null) { - urlBuilder.addQueryParameter("limit", limit.toString()); - } - - if (after != null) { - urlBuilder.addQueryParameter("after", after.format(DateTimeFormatter.ISO_OFFSET_DATE_TIME)); - } - - if (until != null) { - urlBuilder.addQueryParameter("until", until.format(DateTimeFormatter.ISO_OFFSET_DATE_TIME)); - } - - if (direction != null) { - urlBuilder.addQueryParameter("direction", direction.toString()); - } - - if (nested != null) { - urlBuilder.addQueryParameter("nested", nested.toString()); - } - - if (symbols != null && !symbols.isEmpty()) { - urlBuilder.addQueryParameter("symbols", String.join(",", symbols)); - } - - Request request = alpacaClient.requestBuilder(urlBuilder.build()) - .get() - .build(); - return alpacaClient.requestObject(request, ORDER_ARRAYLIST_TYPE); - } - - /** - * Places a new order for the given account. An order request may be rejected if the account is not authorized for - * trading, or if the tradable balance is insufficient to fill the order. Note: many of the parameters for this - * method can be set to null if they aren't required for the requested {@link OrderType}. - * - * @param symbol symbol or asset ID to identify the asset to trade - * @param quantity number of shares to trade. Can be fractional. - * @param notional dollar amount to trade. Cannot work with quantity. Can only work for - * {@link OrderType#MARKET} and {@link OrderTimeInForce#DAY}. - * @param side the {@link OrderSide} - * @param type the {@link OrderType} - * @param timeInForce the {@link OrderTimeInForce} - * @param limitPrice required if type is {@link OrderType#LIMIT} or {@link OrderType#STOP_LIMIT} - * @param stopPrice required if type is {@link OrderType#STOP} or {@link OrderType#STOP_LIMIT} - * @param trailPrice this or trail_percent is required if type is - * {@link OrderType#TRAILING_STOP} - * @param trailPercent this or trail_price is required if type is - * {@link OrderType#TRAILING_STOP} - * @param extendedHours (default) false. If true, order will be eligible to execute in premarket/afterhours. - * Only works with type {@link OrderType#LIMIT} and {@link OrderTimeInForce#DAY}. - * @param clientOrderId a unique identifier for the order. Automatically generated if null. - * @param orderClass the {@link OrderClass}. For details of non-simple order classes, please see "Bracket - * Order Overview" on the Alpaca Docs. - * @param takeProfitLimitPrice additional parameter for take-profit leg of advanced orders. Required for - * {@link OrderClass#BRACKET}. - * @param stopLossStopPrice additional parameters for stop-loss leg of advanced orders. Required for - * {@link OrderClass#BRACKET}. - * @param stopLossLimitPrice additional parameters for stop-loss leg of advanced orders. The stop-loss order - * becomes a stop-limit order if specified. - * - * @return the requested {@link Order} - * - * @throws AlpacaClientException thrown for {@link AlpacaClientException}s - */ - public Order requestOrder(String symbol, Double quantity, Double notional, OrderSide side, OrderType type, - OrderTimeInForce timeInForce, Double limitPrice, Double stopPrice, Double trailPrice, Double trailPercent, - Boolean extendedHours, String clientOrderId, OrderClass orderClass, Double takeProfitLimitPrice, - Double stopLossStopPrice, Double stopLossLimitPrice) - throws AlpacaClientException { - checkNotNull(symbol); - checkState(quantity != null ^ notional != null, "Either 'quantity' or 'notional' are required."); - checkNotNull(side); - checkNotNull(type); - checkNotNull(timeInForce); - - HttpUrl.Builder urlBuilder = alpacaClient.urlBuilder() - .addPathSegment(endpointPathSegment); - - JSONBodyBuilder jsonBodyBuilder = new JSONBodyBuilder(); - jsonBodyBuilder.appendJSONBodyProperty("symbol", symbol); - jsonBodyBuilder.appendJSONBodyProperty("side", side.toString()); - jsonBodyBuilder.appendJSONBodyProperty("type", type.toString()); - jsonBodyBuilder.appendJSONBodyProperty("time_in_force", timeInForce.toString()); - - if (quantity != null) { - jsonBodyBuilder.appendJSONBodyProperty("qty", quantity.toString()); - } else { - jsonBodyBuilder.appendJSONBodyProperty("notional", notional.toString()); - } - - if (limitPrice != null) { - jsonBodyBuilder.appendJSONBodyProperty("limit_price", FormatUtil.toCurrencyFormat(limitPrice)); - } - - if (stopPrice != null) { - jsonBodyBuilder.appendJSONBodyProperty("stop_price", FormatUtil.toCurrencyFormat(stopPrice)); - } - - if (trailPrice != null) { - jsonBodyBuilder.appendJSONBodyProperty("trail_price", FormatUtil.toCurrencyFormat(trailPrice)); - } - - if (trailPercent != null) { - jsonBodyBuilder.appendJSONBodyProperty("trail_percent", FormatUtil.toCurrencyFormat(trailPercent)); - } - - if (extendedHours != null) { - jsonBodyBuilder.appendJSONBodyJSONProperty("extended_hours", new JsonPrimitive(extendedHours)); - } - - if (clientOrderId != null) { - jsonBodyBuilder.appendJSONBodyProperty("client_order_id", clientOrderId); - } - - if (orderClass != null) { - jsonBodyBuilder.appendJSONBodyProperty("order_class", orderClass.toString()); - } - - if (takeProfitLimitPrice != null) { - JsonObject takeProfitObject = new JsonObject(); - takeProfitObject.addProperty("limit_price", takeProfitLimitPrice); - jsonBodyBuilder.appendJSONBodyJSONProperty("take_profit", takeProfitObject); - } - - if (stopLossStopPrice != null || stopLossLimitPrice != null) { - JsonObject stopLossObject = new JsonObject(); - - if (stopLossStopPrice != null) { - stopLossObject.addProperty("stop_price", stopLossStopPrice); - } - - if (stopLossLimitPrice != null) { - stopLossObject.addProperty("limit_price", stopLossLimitPrice); - } - - jsonBodyBuilder.appendJSONBodyJSONProperty("stop_loss", stopLossObject); - } - - Request request = alpacaClient.requestBuilder(urlBuilder.build()) - .post(jsonBodyBuilder.build()) - .build(); - return alpacaClient.requestObject(request, Order.class); - } - - /** - * A market order is a request to buy or sell a security at the currently available market price. It provides the - * most likely method of filling an order. Market orders fill nearly instantaneously. This method calls - * {@link #requestOrder(String, Double, Double, OrderSide, OrderType, OrderTimeInForce, Double, Double, Double, - * Double, Boolean, String, OrderClass, Double, Double, Double)} with {@link OrderType#MARKET} so check the Javadoc - * for that method for parameter details. - * - * @return the requested {@link Order} - * - * @throws AlpacaClientException thrown for {@link AlpacaClientException}s - */ - public Order requestMarketOrder(String symbol, Integer quantity, OrderSide side, OrderTimeInForce timeInForce) - throws AlpacaClientException { - return requestOrder(symbol, quantity.doubleValue(), null, side, OrderType.MARKET, timeInForce, null, null, - null, null, null, null, OrderClass.SIMPLE, null, null, null); - } - - /** - * A market order is a request to buy or sell a security at the currently available market price. It provides the - * most likely method of filling an order. Market orders fill nearly instantaneously. This method calls - * {@link #requestOrder(String, Double, Double, OrderSide, OrderType, OrderTimeInForce, Double, Double, Double, - * Double, Boolean, String, OrderClass, Double, Double, Double)} with {@link OrderType#MARKET} and - * {@link OrderTimeInForce#GOOD_UNTIL_CANCELLED} with a fractional quantity so check the Javadoc for that method for - * parameter details. - * - * @return the requested {@link Order} - * - * @throws AlpacaClientException thrown for {@link AlpacaClientException}s - */ - public Order requestFractionalMarketOrder(String symbol, Double quantity, OrderSide side) - throws AlpacaClientException { - return requestOrder(symbol, quantity, null, side, OrderType.MARKET, OrderTimeInForce.DAY, null, - null, null, - null, null, null, null, null, null, null); - } - - /** - * A market order is a request to buy or sell a security at the currently available market price. It provides the - * most likely method of filling an order. Market orders fill nearly instantaneously. This method calls - * {@link #requestOrder(String, Double, Double, OrderSide, OrderType, OrderTimeInForce, Double, Double, Double, - * Double, Boolean, String, OrderClass, Double, Double, Double)} with {@link OrderType#MARKET} and - * {@link OrderTimeInForce#GOOD_UNTIL_CANCELLED} with a notional dollar amount so check the Javadoc for that method - * for the parameter details. - * - * @return the requested {@link Order} - * - * @throws AlpacaClientException thrown for {@link AlpacaClientException}s - */ - public Order requestNotionalMarketOrder(String symbol, Double notional, OrderSide side) - throws AlpacaClientException { - return requestOrder(symbol, null, notional, side, OrderType.MARKET, OrderTimeInForce.GOOD_UNTIL_CANCELLED, null, - null, null, - null, null, null, null, null, null, null); - } - - /** - * A limit order is an order to buy or sell at a specified price or better. A buy limit order (a limit order to buy) - * is executed at the specified limit price or lower (i.e., better). Conversely, a sell limit order (a limit order - * to sell) is executed at the specified limit price or higher (better). Unlike a market order, you have to specify - * the limit price parameter when submitting your order. This method calls - * {@link #requestOrder(String, Double, Double, OrderSide, OrderType, OrderTimeInForce, Double, Double, Double, - * Double, Boolean, String, OrderClass, Double, Double, Double)} with {@link OrderType#LIMIT} so check the Javadoc - * for that method for the parameter details. - * - * @return the requested {@link Order} - * - * @throws AlpacaClientException thrown for {@link AlpacaClientException}s - */ - public Order requestLimitOrder(String symbol, Double quantity, OrderSide side, OrderTimeInForce timeInForce, - Double limitPrice, Boolean extendedHours) throws AlpacaClientException { - return requestOrder(symbol, quantity, null, side, OrderType.LIMIT, timeInForce, limitPrice, - null, null, null, extendedHours, null, OrderClass.SIMPLE, null, null, null); - } - - /** - * A stop (market) order is an order to buy or sell a security when its price moves past a particular point, - * ensuring a higher probability of achieving a predetermined entry or exit price. Once the market price crosses the - * specified stop price, the stop order becomes a market order. Alpaca converts buy stop orders into stop limit - * orders with a limit price that is 4% higher than a stop price < $50 (or 2.5% higher than a stop price >= - * $50). Sell stop orders are not converted into stop limit orders. This method calls - * {@link #requestOrder(String, Double, Double, OrderSide, OrderType, OrderTimeInForce, Double, Double, Double, - * Double, Boolean, String, OrderClass, Double, Double, Double)} with {@link OrderType#STOP} so check the Javadoc - * for that method for parameter details. - * - * @return the requested {@link Order} - * - * @throws AlpacaClientException thrown for {@link AlpacaClientException}s - */ - public Order requestStopOrder(String symbol, Integer quantity, OrderSide side, OrderTimeInForce timeInForce, - Double stopPrice, Boolean extendedHours) throws AlpacaClientException { - return requestOrder(symbol, quantity.doubleValue(), null, side, OrderType.STOP, timeInForce, null, stopPrice, - null, null, extendedHours, null, OrderClass.SIMPLE, null, null, null); - } - - /** - * A stop-limit order is a conditional trade over a set time frame that combines the features of a stop order with - * those of a limit order and is used to mitigate risk. The stop-limit order will be executed at a specified limit - * price, or better, after a given stop price has been reached. Once the stop price is reached, the stop-limit order - * becomes a limit order to buy or sell at the limit price or better. This method calls - * {@link #requestOrder(String, Double, Double, OrderSide, OrderType, OrderTimeInForce, Double, Double, Double, - * Double, Boolean, String, OrderClass, Double, Double, Double)} with {@link OrderType#STOP_LIMIT} so check the - * Javadoc for that method for parameter details. - * - * @return the requested {@link Order} - * - * @throws AlpacaClientException thrown for {@link AlpacaClientException}s - */ - public Order requestStopLimitOrder(String symbol, Integer quantity, OrderSide side, OrderTimeInForce timeInForce, - Double limitPrice, Double stopPrice, Boolean extendedHours) throws AlpacaClientException { - return requestOrder(symbol, quantity.doubleValue(), null, side, OrderType.STOP_LIMIT, timeInForce, - limitPrice, stopPrice, null, null, extendedHours, null, OrderClass.SIMPLE, null, null, null); - } - - /** - * A bracket order is a chain of three orders that can be used to manage your position entry and exit. It is a - * common use case of an OTOCO (One Triggers OCO {One Cancels Other}) order. This method calls - * {@link #requestOrder(String, Double, Double, OrderSide, OrderType, OrderTimeInForce, Double, Double, Double, - * Double, Boolean, String, OrderClass, Double, Double, Double)} with {@link OrderType#MARKET} and with parameters - * for a bracket order so check the Javadoc for that method for parameter details. - * - * @return the requested {@link Order} - * - * @throws AlpacaClientException thrown for {@link AlpacaClientException}s - */ - public Order requestMarketBracketOrder(String symbol, Integer quantity, OrderSide side, - OrderTimeInForce timeInForce, Double takeProfitLimitPrice, Double stopLossStopPrice, - Double stopLossLimitPrice) throws AlpacaClientException { - return requestOrder(symbol, quantity.doubleValue(), null, side, OrderType.MARKET, timeInForce, null, null, - null, null, null, null, OrderClass.BRACKET, takeProfitLimitPrice, stopLossStopPrice, - stopLossLimitPrice); - } - - /** - * A bracket order is a chain of three orders that can be used to manage your position entry and exit. It is a - * common use case of an OTOCO (One Triggers OCO {One Cancels Other}) order. This method calls - * {@link #requestOrder(String, Double, Double, OrderSide, OrderType, OrderTimeInForce, Double, Double, Double, - * Double, Boolean, String, OrderClass, Double, Double, Double)} with {@link OrderType#LIMIT} and with parameters - * for a bracket order so check the Javadoc for that method for parameter details. - * - * @return the requested {@link Order} - * - * @throws AlpacaClientException thrown for {@link AlpacaClientException}s - */ - public Order requestLimitBracketOrder(String symbol, Integer quantity, OrderSide side, - OrderTimeInForce timeInForce, Double limitPrice, Boolean extendedHours, Double takeProfitLimitPrice, - Double stopLossStopPrice, Double stopLossLimitPrice) throws AlpacaClientException { - return requestOrder(symbol, quantity.doubleValue(), null, side, OrderType.LIMIT, timeInForce, limitPrice, - null, null, null, extendedHours, null, OrderClass.BRACKET, takeProfitLimitPrice, stopLossStopPrice, - stopLossLimitPrice); - } - - /** - * OCO (One-Cancels-Other) is another type of advanced order type. This is a set of two orders with the same side - * (buy/buy or sell/sell) and currently only exit order is supported. In other words, this is the second part of the - * bracket orders where the entry order is already filled, and you can submit the take-profit and stop-loss in one - * order submission. This method calls - * {@link #requestOrder(String, Double, Double, OrderSide, OrderType, OrderTimeInForce, Double, Double, Double, - * Double, Boolean, String, OrderClass, Double, Double, Double)} with parameters for a - * {@link OrderClass#ONE_CANCELS_OTHER} so check the Javadoc for that method for the parameter details. - * - * @return the requested {@link Order} - * - * @throws AlpacaClientException thrown for {@link AlpacaClientException}s - */ - public Order requestOCOOrder(String symbol, Integer quantity, OrderSide side, - OrderTimeInForce timeInForce, Boolean extendedHours, Double takeProfitLimitPrice, - Double stopLossStopPrice, Double stopLossLimitPrice) throws AlpacaClientException { - return requestOrder(symbol, quantity.doubleValue(), null, side, OrderType.LIMIT, timeInForce, null, null, - null, null, extendedHours, null, OrderClass.ONE_CANCELS_OTHER, takeProfitLimitPrice, stopLossStopPrice, - stopLossLimitPrice); - } - - /** - * OTO (One-Triggers-Other) is a variant of bracket order. It takes one of the take-profit or stop-loss order in - * addition to the entry order. This method calls - * {@link #requestOrder(String, Double, Double, OrderSide, OrderType, OrderTimeInForce, Double, Double, Double, - * Double, Boolean, String, OrderClass, Double, Double, Double)} with {@link OrderType#LIMIT} and with parameters - * for a {@link OrderClass#ONE_CANCELS_OTHER} so check the Javadoc for that method for parameter details. - * - * @return the requested {@link Order} - * - * @throws AlpacaClientException thrown for {@link AlpacaClientException}s - */ - public Order requestOTOMarketOrder(String symbol, Integer quantity, OrderSide side, - OrderTimeInForce timeInForce, Double takeProfitLimitPrice, Double stopLossStopPrice, - Double stopLossLimitPrice) throws AlpacaClientException { - return requestOrder(symbol, quantity.doubleValue(), null, side, OrderType.MARKET, timeInForce, null, null, - null, null, null, null, OrderClass.ONE_CANCELS_OTHER, takeProfitLimitPrice, stopLossStopPrice, - stopLossLimitPrice); - } - - /** - * OTO (One-Triggers-Other) is a variant of bracket order. It takes one of the take-profit or stop-loss order in - * addition to the entry order. This method calls - * {@link #requestOrder(String, Double, Double, OrderSide, OrderType, OrderTimeInForce, Double, Double, Double, - * Double, Boolean, String, OrderClass, Double, Double, Double)} with {@link OrderType#LIMIT} and with parameters - * for a {@link OrderClass#ONE_CANCELS_OTHER} so check the Javadoc for that method for parameter details. - * - * @return the requested {@link Order} - * - * @throws AlpacaClientException thrown for {@link AlpacaClientException}s - */ - public Order requestOTOLimitOrder(String symbol, Integer quantity, OrderSide side, - OrderTimeInForce timeInForce, Double limitPrice, Boolean extendedHours, Double takeProfitLimitPrice, - Double stopLossStopPrice, Double stopLossLimitPrice) throws AlpacaClientException { - return requestOrder(symbol, quantity.doubleValue(), null, side, OrderType.LIMIT, timeInForce, limitPrice, - null, null, null, extendedHours, null, OrderClass.ONE_CANCELS_OTHER, takeProfitLimitPrice, - stopLossStopPrice, stopLossLimitPrice); - } - - /** - * OTO (One-Triggers-Other) is a variant of bracket order. It takes one of the take-profit or stop-loss order in - * addition to the entry order. This method calls - * {@link #requestOrder(String, Double, Double, OrderSide, OrderType, OrderTimeInForce, Double, Double, Double, - * Double, Boolean, String, OrderClass, Double, Double, Double)} with {@link OrderType#STOP} and with parameters for - * a {@link OrderClass#ONE_CANCELS_OTHER} so check the Javadoc for that method for parameter details. - * - * @return the requested {@link Order} - * - * @throws AlpacaClientException thrown for {@link AlpacaClientException}s - */ - public Order requestOTOStopOrder(String symbol, Integer quantity, OrderSide side, - OrderTimeInForce timeInForce, Double stopPrice, Boolean extendedHours, Double takeProfitLimitPrice, - Double stopLossStopPrice, Double stopLossLimitPrice) throws AlpacaClientException { - return requestOrder(symbol, quantity.doubleValue(), null, side, OrderType.STOP, timeInForce, null, stopPrice, - null, null, extendedHours, null, OrderClass.ONE_CANCELS_OTHER, takeProfitLimitPrice, stopLossStopPrice, - stopLossLimitPrice); - } - - /** - * OTO (One-Triggers-Other) is a variant of bracket order. It takes one of the take-profit or stop-loss order in - * addition to the entry order. This method calls - * {@link #requestOrder(String, Double, Double, OrderSide, OrderType, OrderTimeInForce, Double, Double, Double, - * Double, Boolean, String, OrderClass, Double, Double, Double)} with {@link OrderType#STOP_LIMIT} and with - * parameters for a {@link OrderClass#ONE_CANCELS_OTHER} so check the Javadoc for that method for parameter - * details. - * - * @return the requested {@link Order} - * - * @throws AlpacaClientException thrown for {@link AlpacaClientException}s - */ - public Order requestOTOStopLimitOrder(String symbol, Integer quantity, OrderSide side, - OrderTimeInForce timeInForce, Double limitPrice, Double stopPrice, Boolean extendedHours, - Double takeProfitLimitPrice, Double stopLossStopPrice, Double stopLossLimitPrice) - throws AlpacaClientException { - return requestOrder(symbol, quantity.doubleValue(), null, side, OrderType.STOP_LIMIT, timeInForce, - limitPrice, stopPrice, null, null, extendedHours, null, OrderClass.ONE_CANCELS_OTHER, - takeProfitLimitPrice, stopLossStopPrice, stopLossLimitPrice); - } - - /** - * Trailing stop orders allow you to continuously and automatically keep updating the stop price threshold based on - * the stock price movement. This method calls - * {@link #requestOrder(String, Double, Double, OrderSide, OrderType, OrderTimeInForce, Double, Double, Double, - * Double, Boolean, String, OrderClass, Double, Double, Double)} with {@link OrderType#TRAILING_STOP} and with - * parameters for a {@link OrderType#TRAILING_STOP} so check the Javadoc for that method for parameter details. - * - * @return the requested {@link Order} - * - * @throws AlpacaClientException thrown for {@link AlpacaClientException}s - */ - public Order requestTrailingStopPriceOrder(String symbol, Integer quantity, OrderSide side, - OrderTimeInForce timeInForce, Double trailPrice, Boolean extendedHours) throws AlpacaClientException { - return requestOrder(symbol, quantity.doubleValue(), null, side, OrderType.TRAILING_STOP, timeInForce, null, - null, trailPrice, null, extendedHours, null, null, null, null, null); - } - - /** - * Trailing stop orders allow you to continuously and automatically keep updating the stop price threshold based on - * the stock price movement. This method calls - * {@link #requestOrder(String, Double, Double, OrderSide, OrderType, OrderTimeInForce, Double, Double, Double, - * Double, Boolean, String, OrderClass, Double, Double, Double)} with {@link OrderType#TRAILING_STOP} and with - * parameters for a {@link OrderType#TRAILING_STOP} so check the Javadoc for that method for parameter details. - * - * @return the requested {@link Order} - * - * @throws AlpacaClientException thrown for {@link AlpacaClientException}s - */ - public Order requestTrailingStopPercentOrder(String symbol, Integer quantity, OrderSide side, - OrderTimeInForce timeInForce, Double trailPercent, Boolean extendedHours) throws AlpacaClientException { - return requestOrder(symbol, quantity.doubleValue(), null, side, OrderType.TRAILING_STOP, timeInForce, null, - null, null, trailPercent, extendedHours, null, null, null, null, null); - } - - /** - * Retrieves a single {@link Order} for the given orderID. - * - * @param orderID the {@link Order#getId()} - * @param nested if true, the result will roll up multi-leg orders under the legs field of primary order. - * - * @return the {@link Order} - * - * @throws AlpacaClientException thrown for {@link AlpacaClientException}s - */ - public Order get(String orderID, Boolean nested) throws AlpacaClientException { - checkNotNull(orderID); - - HttpUrl.Builder urlBuilder = alpacaClient.urlBuilder() - .addPathSegment(endpointPathSegment) - .addPathSegment(orderID); - - if (nested != null) { - urlBuilder.addQueryParameter("nested", nested.toString()); - } - - Request request = alpacaClient.requestBuilder(urlBuilder.build()) - .get() - .build(); - return alpacaClient.requestObject(request, Order.class); - } - - /** - * Retrieves a single {@link Order} for the given clientOrderId. - * - * @param clientOrderID the {@link Order#getClientOrderId()} - * - * @return the {@link Order} - * - * @throws AlpacaClientException thrown for {@link AlpacaClientException}s - */ - public Order getByClientID(String clientOrderID) throws AlpacaClientException { - checkNotNull(clientOrderID); - - HttpUrl.Builder urlBuilder = alpacaClient.urlBuilder() - .addPathSegment("orders:by_client_order_id"); - urlBuilder.addQueryParameter("client_order_id", clientOrderID); - - Request request = alpacaClient.requestBuilder(urlBuilder.build()) - .get() - .build(); - return alpacaClient.requestObject(request, Order.class); - } - - /** - * Replaces a single {@link Order} with updated parameters. Each parameter overrides the corresponding attribute of - * the existing {@link Order}. The other attributes remain the same as the existing {@link Order}. - * - * @param orderID the {@link Order#getId()} - * @param quantity number of shares to trade. Can be fractional. - * @param timeInForce the {@link OrderTimeInForce} - * @param limitPrice required if type is {@link OrderType#LIMIT} or {@link OrderType#STOP_LIMIT} - * @param stopPrice required if type is {@link OrderType#STOP} or {@link OrderType#STOP_LIMIT} - * @param trail the new value of the trail_price or trail_percent value (works - * only for {@link Order#getType()} == {@link OrderType#TRAILING_STOP}) - * @param clientOrderId a unique identifier for the order. Automatically generated if null. - * - * @return a new {@link Order} - * - * @throws AlpacaClientException thrown for {@link AlpacaClientException}s - */ - public Order replace(String orderID, Integer quantity, OrderTimeInForce timeInForce, Double limitPrice, - Double stopPrice, Double trail, String clientOrderId) throws AlpacaClientException { - checkNotNull(orderID); - - HttpUrl.Builder urlBuilder = alpacaClient.urlBuilder() - .addPathSegment(endpointPathSegment) - .addPathSegment(orderID); - - JSONBodyBuilder jsonBodyBuilder = new JSONBodyBuilder(); - - if (quantity != null) { - jsonBodyBuilder.appendJSONBodyProperty("qty", quantity.toString()); - } - - if (timeInForce != null) { - jsonBodyBuilder.appendJSONBodyProperty("time_in_force", timeInForce.toString()); - } - - if (limitPrice != null) { - jsonBodyBuilder.appendJSONBodyProperty("limit_price", FormatUtil.toCurrencyFormat(limitPrice)); - } - - if (stopPrice != null) { - jsonBodyBuilder.appendJSONBodyProperty("stop_price", FormatUtil.toCurrencyFormat(stopPrice)); - } - - if (trail != null) { - jsonBodyBuilder.appendJSONBodyProperty("trail", FormatUtil.toCurrencyFormat(trail)); - } - - if (clientOrderId != null) { - jsonBodyBuilder.appendJSONBodyProperty("client_order_id", clientOrderId); - } - - Request request = alpacaClient.requestBuilder(urlBuilder.build()) - .patch(jsonBodyBuilder.build()) - .build(); - return alpacaClient.requestObject(request, Order.class); - } - - /** - * Attempts to cancel all open {@link Order}s. A response will be provided for each {@link Order} that is attempted - * to be cancelled. If an {@link Order} is no longer cancelable, the server will respond with status - * 500 and reject the request. - * - * @return a {@link List} of {@link CancelledOrder}s - * - * @throws AlpacaClientException thrown for {@link AlpacaClientException}s - */ - public List cancelAll() throws AlpacaClientException { - HttpUrl.Builder urlBuilder = alpacaClient.urlBuilder() - .addPathSegment(endpointPathSegment); - Request request = alpacaClient.requestBuilder(urlBuilder.build()) - .delete() - .build(); - return alpacaClient.requestObject(request, STATUS_CODE_200_OR_207, CANCELLED_ORDER_ARRAYLIST_TYPE); - } - - /** - * Attempts to cancel an open {@link Order}. If the {@link Order} is no longer cancelable (example: - * {@link Order#getStatus()} == {@link OrderStatus#FILLED}), the server will respond with status - * 422, and reject the request. - * - * @param orderID the {@link Order#getId()} - * - * @throws AlpacaClientException thrown for {@link AlpacaClientException}s - */ - public void cancel(String orderID) throws AlpacaClientException { - checkNotNull(orderID); - - HttpUrl.Builder urlBuilder = alpacaClient.urlBuilder() - .addPathSegment(endpointPathSegment) - .addPathSegment(orderID); - Request request = alpacaClient.requestBuilder(urlBuilder.build()) - .delete() - .build(); - alpacaClient.requestVoid(request, STATUS_CODE_200_OR_204); - } -} diff --git a/src/main/java/net/jacobpeterson/alpaca/rest/endpoint/portfoliohistory/PortfolioHistoryEndpoint.java b/src/main/java/net/jacobpeterson/alpaca/rest/endpoint/portfoliohistory/PortfolioHistoryEndpoint.java deleted file mode 100644 index 722299f0..00000000 --- a/src/main/java/net/jacobpeterson/alpaca/rest/endpoint/portfoliohistory/PortfolioHistoryEndpoint.java +++ /dev/null @@ -1,103 +0,0 @@ -package net.jacobpeterson.alpaca.rest.endpoint.portfoliohistory; - -import net.jacobpeterson.alpaca.model.endpoint.portfoliohistory.PortfolioHistory; -import net.jacobpeterson.alpaca.model.endpoint.portfoliohistory.PortfolioHistoryDataPoint; -import net.jacobpeterson.alpaca.model.endpoint.portfoliohistory.PortfolioHistoryResponse; -import net.jacobpeterson.alpaca.model.endpoint.portfoliohistory.enums.PortfolioPeriodUnit; -import net.jacobpeterson.alpaca.model.endpoint.portfoliohistory.enums.PortfolioTimeFrame; -import net.jacobpeterson.alpaca.rest.AlpacaClient; -import net.jacobpeterson.alpaca.rest.AlpacaClientException; -import net.jacobpeterson.alpaca.rest.endpoint.AlpacaEndpoint; -import net.jacobpeterson.alpaca.util.format.FormatUtil; -import okhttp3.HttpUrl; -import okhttp3.Request; - -import java.time.Instant; -import java.time.LocalDate; -import java.time.format.DateTimeFormatter; -import java.util.ArrayList; - -import static com.google.common.base.Preconditions.checkState; - -/** - * {@link AlpacaEndpoint} for - * Portfolio - * History. - */ -public class PortfolioHistoryEndpoint extends AlpacaEndpoint { - - /** - * Instantiates a new {@link PortfolioHistoryEndpoint}. - * - * @param alpacaClient the {@link AlpacaClient} - */ - public PortfolioHistoryEndpoint(AlpacaClient alpacaClient) { - super(alpacaClient, "account/portfolio/history"); - } - - /** - * Returns timeseries data about equity and profit/loss (P/L) of the account in requested timespan. - * - * @param periodLength the duration of the periodUnit. Defaults to 1. - * @param periodUnit the {@link PortfolioPeriodUnit}. Defaults to {@link PortfolioPeriodUnit#MONTH}. - * @param timeFrame the resolution of time window. If omitted, {@link PortfolioTimeFrame#ONE_MIN} for less than - * 7 days period, {@link PortfolioTimeFrame#FIFTEEN_MINUTE} for less than 30 days, or otherwise - * {@link PortfolioTimeFrame#ONE_DAY}. - * @param dateEnd the date the data is returned up to. Defaults to the current market date (rolls over at the - * market open if extended_hours is false, otherwise at 7 AM ET) - * @param extendedHours if true, include extended hours in the result. This is effective only for timeframe less - * than {@link PortfolioTimeFrame#ONE_DAY}. - * - * @return the {@link PortfolioHistory} - * - * @throws AlpacaClientException thrown for {@link AlpacaClientException}s - */ - public PortfolioHistory get(Integer periodLength, PortfolioPeriodUnit periodUnit, PortfolioTimeFrame timeFrame, - LocalDate dateEnd, Boolean extendedHours) throws AlpacaClientException { - HttpUrl.Builder urlBuilder = alpacaClient.urlBuilder() - .addPathSegments(endpointPathSegment); - - if (periodLength != null && periodUnit != null) { - urlBuilder.addQueryParameter("period", periodLength + periodUnit.toString()); - } - - if (timeFrame != null) { - urlBuilder.addQueryParameter("timeframe", timeFrame.toString()); - } - - if (dateEnd != null) { - urlBuilder.addQueryParameter("date_end", dateEnd.format(DateTimeFormatter.ISO_DATE)); - } - - if (extendedHours != null) { - urlBuilder.addQueryParameter("extended_hours", extendedHours.toString()); - } - - Request request = alpacaClient.requestBuilder(urlBuilder.build()) - .get() - .build(); - PortfolioHistoryResponse response = alpacaClient.requestObject(request, - PortfolioHistoryResponse.class); - - // Check if any response arrays differ in size - checkState(response.getTimestamp().size() == response.getEquity().size() && - response.getEquity().size() == response.getProfitLoss().size() && - response.getProfitLoss().size() == response.getProfitLossPercent().size(), - "Response arrays should not differ in size!"); - - // Add all data points into one POJO - ArrayList dataPoints = new ArrayList<>(); - for (int index = 0; index < response.getTimestamp().size(); index++) { - dataPoints.add(new PortfolioHistoryDataPoint( - Instant.ofEpochSecond(response.getTimestamp().get(index)).atZone(FormatUtil.NEW_YORK_ZONED_ID), - response.getEquity().get(index), - response.getProfitLoss().get(index), - response.getProfitLossPercent().get(index))); - } - - return new PortfolioHistory( - dataPoints, - response.getBaseValue(), - response.getTimeframe()); - } -} diff --git a/src/main/java/net/jacobpeterson/alpaca/rest/endpoint/positions/PositionsEndpoint.java b/src/main/java/net/jacobpeterson/alpaca/rest/endpoint/positions/PositionsEndpoint.java deleted file mode 100644 index fc8eda81..00000000 --- a/src/main/java/net/jacobpeterson/alpaca/rest/endpoint/positions/PositionsEndpoint.java +++ /dev/null @@ -1,136 +0,0 @@ -package net.jacobpeterson.alpaca.rest.endpoint.positions; - -import com.google.gson.reflect.TypeToken; -import net.jacobpeterson.alpaca.model.endpoint.assets.Asset; -import net.jacobpeterson.alpaca.model.endpoint.orders.Order; -import net.jacobpeterson.alpaca.model.endpoint.positions.ClosePositionOrder; -import net.jacobpeterson.alpaca.model.endpoint.positions.Position; -import net.jacobpeterson.alpaca.rest.AlpacaClient; -import net.jacobpeterson.alpaca.rest.AlpacaClientException; -import net.jacobpeterson.alpaca.rest.endpoint.AlpacaEndpoint; -import okhttp3.HttpUrl; -import okhttp3.Request; - -import java.lang.reflect.Type; -import java.util.ArrayList; -import java.util.List; - -import static com.google.common.base.Preconditions.checkNotNull; -import static net.jacobpeterson.alpaca.rest.AlpacaClient.STATUS_CODE_200_OR_207; - -/** - * {@link AlpacaEndpoint} for Positions. - */ -public class PositionsEndpoint extends AlpacaEndpoint { - - private static final Type POSITION_ARRAYLIST_TYPE = new TypeToken>() {}.getType(); - private static final Type CLOSED_POSITION_ORDER_ARRAYLIST_TYPE = - new TypeToken>() {}.getType(); - - /** - * Instantiates a new {@link PositionsEndpoint}. - * - * @param alpacaClient the {@link AlpacaClient} - */ - public PositionsEndpoint(AlpacaClient alpacaClient) { - super(alpacaClient, "positions"); - } - - /** - * Retrieves a list of the account's open {@link Position}s. - * - * @return a {@link List} of open {@link Position}s - * - * @throws AlpacaClientException thrown for {@link AlpacaClientException}s - */ - public List get() throws AlpacaClientException { - HttpUrl.Builder urlBuilder = alpacaClient.urlBuilder() - .addPathSegment(endpointPathSegment); - Request request = alpacaClient.requestBuilder(urlBuilder.build()) - .get() - .build(); - return alpacaClient.requestObject(request, POSITION_ARRAYLIST_TYPE); - } - - /** - * Retrieves the account's open {@link Position}s for the given symbol or {@link Asset#getId()}. - * - * @param symbolOrAssetID the symbol or {@link Asset#getId()} (required) - * - * @return the open {@link Position} - * - * @throws AlpacaClientException thrown for {@link AlpacaClientException}s - */ - public Position getBySymbol(String symbolOrAssetID) throws AlpacaClientException { - checkNotNull(symbolOrAssetID); - - HttpUrl.Builder urlBuilder = alpacaClient.urlBuilder() - .addPathSegment(endpointPathSegment) - .addPathSegment(symbolOrAssetID); - Request request = alpacaClient.requestBuilder(urlBuilder.build()) - .get() - .build(); - return alpacaClient.requestObject(request, Position.class); - } - - /** - * Closes (liquidates) all of the account’s open long and short {@link Position}s. A response will be provided for - * each order that is attempted to be cancelled. If an order is no longer cancelable, the server will respond with - * status 500 and reject the request. - * - * @param cancelOrders if true is specified, cancel all open orders before liquidating all positions - * - * @return a {@link List} of open {@link ClosePositionOrder}s - * - * @throws AlpacaClientException thrown for {@link AlpacaClientException}s - */ - public List closeAll(Boolean cancelOrders) throws AlpacaClientException { - HttpUrl.Builder urlBuilder = alpacaClient.urlBuilder() - .addPathSegment(endpointPathSegment); - - if (cancelOrders != null) { - urlBuilder.addQueryParameter("cancel_orders", cancelOrders.toString()); - } - - Request request = alpacaClient.requestBuilder(urlBuilder.build()) - .delete() - .build(); - return alpacaClient.requestObject(request, STATUS_CODE_200_OR_207, CLOSED_POSITION_ORDER_ARRAYLIST_TYPE); - } - - /** - * Closes (liquidates) the account’s open position for the given symbol. Works for both long and short - * positions. - * - * @param symbolOrAssetID the symbol or {@link Asset#getId()} - * @param quantity the number of shares to liquidate. Can accept up to 9 decimal points. Cannot - * work with percentage. - * @param percentage the percentage of the position to liquidate. Must be between 0 and - * 100. Would only sell fractional if position is originally fractional. Can - * accept up to 9 decimal points. Cannot work with quantity. - * - * @return a closing {@link Position} {@link Order} - * - * @throws AlpacaClientException thrown for {@link AlpacaClientException}s - */ - public Order close(String symbolOrAssetID, Double quantity, Double percentage) throws AlpacaClientException { - checkNotNull(symbolOrAssetID); - - HttpUrl.Builder urlBuilder = alpacaClient.urlBuilder() - .addPathSegment(endpointPathSegment) - .addPathSegment(symbolOrAssetID); - - if (quantity != null) { - urlBuilder.addQueryParameter("qty", quantity.toString()); - } - - if (percentage != null) { - urlBuilder.addQueryParameter("percentage", percentage.toString()); - } - - Request request = alpacaClient.requestBuilder(urlBuilder.build()) - .delete() - .build(); - return alpacaClient.requestObject(request, Order.class); - } -} diff --git a/src/main/java/net/jacobpeterson/alpaca/rest/endpoint/watchlist/WatchlistEndpoint.java b/src/main/java/net/jacobpeterson/alpaca/rest/endpoint/watchlist/WatchlistEndpoint.java deleted file mode 100644 index 605d651b..00000000 --- a/src/main/java/net/jacobpeterson/alpaca/rest/endpoint/watchlist/WatchlistEndpoint.java +++ /dev/null @@ -1,216 +0,0 @@ -package net.jacobpeterson.alpaca.rest.endpoint.watchlist; - -import com.google.gson.JsonArray; -import com.google.gson.reflect.TypeToken; -import net.jacobpeterson.alpaca.model.endpoint.assets.Asset; -import net.jacobpeterson.alpaca.model.endpoint.watchlist.Watchlist; -import net.jacobpeterson.alpaca.rest.AlpacaClient; -import net.jacobpeterson.alpaca.rest.AlpacaClientException; -import net.jacobpeterson.alpaca.rest.endpoint.AlpacaEndpoint; -import net.jacobpeterson.alpaca.util.okhttp.JSONBodyBuilder; -import okhttp3.HttpUrl; -import okhttp3.Request; - -import java.lang.reflect.Type; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; - -import static com.google.common.base.Preconditions.checkArgument; -import static com.google.common.base.Preconditions.checkNotNull; -import static net.jacobpeterson.alpaca.rest.AlpacaClient.STATUS_CODE_200_OR_204; - -/** - * {@link AlpacaEndpoint} for Watchlists - * . - */ -public class WatchlistEndpoint extends AlpacaEndpoint { - - private static final Type WATCHLIST_ARRAYLIST_TYPE = new TypeToken>() {}.getType(); - - /** - * Instantiates a new {@link WatchlistEndpoint}. - * - * @param alpacaClient the {@link AlpacaClient} - */ - public WatchlistEndpoint(AlpacaClient alpacaClient) { - super(alpacaClient, "watchlists"); - } - - /** - * Returns the list of {@link Watchlist}s registered under the account. - * - * @return a {@link List} of {@link Watchlist}s - * - * @throws AlpacaClientException thrown for {@link AlpacaClientException}s - */ - public List get() throws AlpacaClientException { - HttpUrl.Builder urlBuilder = alpacaClient.urlBuilder() - .addPathSegment(endpointPathSegment); - Request request = alpacaClient.requestBuilder(urlBuilder.build()) - .get() - .build(); - return alpacaClient.requestObject(request, WATCHLIST_ARRAYLIST_TYPE); - } - - /** - * Creates a new {@link Watchlist} with initial set of {@link Asset}s. - * - * @param name arbitrary name string, up to 64 characters - * @param symbols set of symbols {@link String}s - * - * @return the created {@link Watchlist} - * - * @throws AlpacaClientException thrown for {@link AlpacaClientException}s - */ - public Watchlist create(String name, String... symbols) throws AlpacaClientException { - checkNotNull(name); - checkArgument(name.length() <= 64, "'name' cannot be longer than 64 characters!"); - - HttpUrl.Builder urlBuilder = alpacaClient.urlBuilder() - .addPathSegment(endpointPathSegment); - - JSONBodyBuilder jsonBodyBuilder = new JSONBodyBuilder(); - jsonBodyBuilder.appendJSONBodyProperty("name", name); - - if (symbols != null && symbols.length != 0) { - JsonArray symbolsArray = new JsonArray(); - Arrays.stream(symbols).forEach(symbolsArray::add); - jsonBodyBuilder.appendJSONBodyJSONProperty("symbols", symbolsArray); - } - - Request request = alpacaClient.requestBuilder(urlBuilder.build()) - .post(jsonBodyBuilder.build()) - .build(); - return alpacaClient.requestObject(request, Watchlist.class); - } - - /** - * Returns a {@link Watchlist} identified by the ID. - * - * @param watchlistID the {@link Watchlist#getId()} - * - * @return the {@link Watchlist} - * - * @throws AlpacaClientException thrown for {@link AlpacaClientException}s - */ - public Watchlist get(String watchlistID) throws AlpacaClientException { - checkNotNull(watchlistID); - - HttpUrl.Builder urlBuilder = alpacaClient.urlBuilder() - .addPathSegment(endpointPathSegment) - .addPathSegment(watchlistID); - Request request = alpacaClient.requestBuilder(urlBuilder.build()) - .get() - .build(); - return alpacaClient.requestObject(request, Watchlist.class); - } - - /** - * Updates the name and/or content of a {@link Watchlist}. - * - * @param watchlistID the {@link Watchlist#getId()} - * @param name the new {@link Watchlist} name - * @param symbols the new list of symbol names to replace the {@link Watchlist} content - * - * @return the updated {@link Watchlist} - * - * @throws AlpacaClientException thrown for {@link AlpacaClientException}s - */ - public Watchlist update(String watchlistID, String name, String... symbols) - throws AlpacaClientException { - checkNotNull(watchlistID); - checkArgument(name == null || name.length() <= 64, "'name' cannot be longer than 64 characters!"); - - HttpUrl.Builder urlBuilder = alpacaClient.urlBuilder() - .addPathSegment(endpointPathSegment) - .addPathSegment(watchlistID); - - JSONBodyBuilder jsonBodyBuilder = new JSONBodyBuilder(); - - if (name != null) { - jsonBodyBuilder.appendJSONBodyProperty("name", name); - } - - if (symbols != null && symbols.length != 0) { - JsonArray symbolsArray = new JsonArray(); - Arrays.stream(symbols).forEach(symbolsArray::add); - jsonBodyBuilder.appendJSONBodyJSONProperty("symbols", symbolsArray); - } - - Request request = alpacaClient.requestBuilder(urlBuilder.build()) - .put(jsonBodyBuilder.build()) - .build(); - return alpacaClient.requestObject(request, Watchlist.class); - } - - /** - * Append an asset for the symbol to the end of {@link Watchlist} asset list. - * - * @param watchlistID the {@link Watchlist#getId()} - * @param symbol the symbol name to add to the {@link Watchlist} - * - * @return the updated {@link Watchlist} - * - * @throws AlpacaClientException thrown for {@link AlpacaClientException}s - */ - public Watchlist addAsset(String watchlistID, String symbol) throws AlpacaClientException { - checkNotNull(watchlistID); - checkNotNull(symbol); - - HttpUrl.Builder urlBuilder = alpacaClient.urlBuilder() - .addPathSegment(endpointPathSegment) - .addPathSegment(watchlistID); - - JSONBodyBuilder jsonBodyBuilder = new JSONBodyBuilder(); - jsonBodyBuilder.appendJSONBodyProperty("symbol", symbol); - - Request request = alpacaClient.requestBuilder(urlBuilder.build()) - .post(jsonBodyBuilder.build()) - .build(); - return alpacaClient.requestObject(request, Watchlist.class); - } - - /** - * Deletes a {@link Watchlist}. This is a permanent deletion. - * - * @param watchlistID the {@link Watchlist#getId()} - * - * @throws AlpacaClientException thrown for {@link AlpacaClientException}s - */ - public void delete(String watchlistID) throws AlpacaClientException { - checkNotNull(watchlistID); - - HttpUrl.Builder urlBuilder = alpacaClient.urlBuilder() - .addPathSegment(endpointPathSegment) - .addPathSegment(watchlistID); - Request request = alpacaClient.requestBuilder(urlBuilder.build()) - .delete() - .build(); - alpacaClient.requestVoid(request, STATUS_CODE_200_OR_204); - } - - /** - * Delete one entry for an asset by symbol name. - * - * @param watchlistID the {@link Watchlist#getId()} - * @param symbol symbol name to remove from the {@link Watchlist} content - * - * @return the updated {@link Watchlist} - * - * @throws AlpacaClientException thrown for {@link AlpacaClientException}s - */ - public Watchlist removeSymbol(String watchlistID, String symbol) throws AlpacaClientException { - checkNotNull(watchlistID); - checkNotNull(symbol); - - HttpUrl.Builder urlBuilder = alpacaClient.urlBuilder() - .addPathSegment(endpointPathSegment) - .addPathSegment(watchlistID) - .addPathSegment(symbol); - Request request = alpacaClient.requestBuilder(urlBuilder.build()) - .delete() - .build(); - return alpacaClient.requestObject(request, STATUS_CODE_200_OR_204, Watchlist.class); - } -} diff --git a/src/main/java/net/jacobpeterson/alpaca/util/doc/Documentation2JSONSchema.java b/src/main/java/net/jacobpeterson/alpaca/util/doc/Documentation2JSONSchema.java deleted file mode 100644 index ba0e2208..00000000 --- a/src/main/java/net/jacobpeterson/alpaca/util/doc/Documentation2JSONSchema.java +++ /dev/null @@ -1,130 +0,0 @@ -package net.jacobpeterson.alpaca.util.doc; - -import org.w3c.dom.Document; -import org.w3c.dom.NodeList; -import org.xml.sax.SAXException; - -import javax.xml.parsers.DocumentBuilder; -import javax.xml.parsers.DocumentBuilderFactory; -import javax.xml.parsers.ParserConfigurationException; -import javax.xml.xpath.XPath; -import javax.xml.xpath.XPathConstants; -import javax.xml.xpath.XPathExpression; -import javax.xml.xpath.XPathExpressionException; -import javax.xml.xpath.XPathFactory; -import java.io.ByteArrayInputStream; -import java.io.IOException; - -/** - * The {@link Documentation2JSONSchema} is used strictly to expedite the process of creating JSON schemas for POJO - * generation in this library using Alpaca's website documentation. - */ -public class Documentation2JSONSchema { - - /** - * This is a standalone helper method that converts properties HTML copied from the Alpaca docs page into a json - * schema property list that includes the type and the description. It's kinda hacky, but it helps in the generation - * of JSON POJOs with Javadocs. Must be in the format of a list of: - *
-     * <div class="spec-table">
-     *     <div class="spec-row">
-     *     <div class="spec-left"><span class="spec-name">id</span></div>
-     *     <div class="spec-right">
-     *         <div class="spec-type">string<uuid></div>
-     *         <div class="spec-desc">Account ID.</div>
-     *     </div>
-     *     </div>
-     *     ...
-     * </div>
-     * 
- * - * @param alpacaDocXML the alpaca doc xml - */ - public static void printAlpacaHTMLWebDocAsJSONSchema(String alpacaDocXML) - throws ParserConfigurationException, SAXException, IOException, XPathExpressionException { - String nameXPath = "//span[" + xPathClassName("spec-name") + "]"; - String typeXPath = "//div[" + xPathClassName("spec-type") + "]"; - String descriptionXPath = "//div[" + xPathClassName("spec-desc") + "]"; - - System.out.println(getSchemaFromDoc(alpacaDocXML, nameXPath, typeXPath, descriptionXPath)); - } - - /** - * Converts a generic HTML doc for Alpaca with JSON property fields for "name", "type", and "description" into - * schema JSON used for - * jsonSchema2POJO. - * - * @param docHTML the doc html - * @param nameXPath the name x path - * @param typeXPath the type x path - * @param descriptionXPath the description x path - * - * @return the schema from doc - * - * @throws ParserConfigurationException the parser configuration exception - * @throws SAXException the sax exception - * @throws IOException the io exception - * @throws XPathExpressionException the x path expression exception - */ - private static String getSchemaFromDoc(String docHTML, String nameXPath, String typeXPath, String descriptionXPath) - throws ParserConfigurationException, SAXException, IOException, XPathExpressionException { - String schemaJSON = ""; - DocumentBuilder builder = DocumentBuilderFactory.newInstance().newDocumentBuilder(); - Document document = builder.parse(new ByteArrayInputStream(docHTML.getBytes())); - XPathFactory xPathFactory = XPathFactory.newInstance(); - XPath xPath = xPathFactory.newXPath(); - - XPathExpression propertyNamesXPath = xPath.compile(nameXPath); - XPathExpression propertyTypesXPath = xPath.compile(typeXPath); - XPathExpression propertyDescriptionsXPath = xPath.compile(descriptionXPath); - - NodeList propertyNameNodes = (NodeList) propertyNamesXPath.evaluate(document, XPathConstants.NODESET); - NodeList propertyTypesNodes = (NodeList) propertyTypesXPath.evaluate(document, XPathConstants.NODESET); - NodeList propertyDescriptionsNodes = (NodeList) propertyDescriptionsXPath.evaluate(document, - XPathConstants.NODESET); - - for (int propertyIndex = 0; propertyIndex < propertyNameNodes.getLength(); propertyIndex++) { - String propertyName = propertyNameNodes.item(propertyIndex).getTextContent(); - String propertyType = propertyTypesNodes.item(propertyIndex).getTextContent(); - String propertyDescription = propertyDescriptionsNodes.item(propertyIndex).getTextContent(); - - // Property description may be null - if (propertyDescription == null || propertyDescription.isEmpty()) { - propertyDescription = propertyName; - } - - schemaJSON += "\"" + propertyName + "\": {\n"; - schemaJSON += "\"existingJavaType\": \"" + parseDocTypeToSchemaJavaType(propertyType) + "\",\n"; - schemaJSON += "\"title\": \"" + propertyDescription + "\"\n"; - schemaJSON += "},\n"; - } - return schemaJSON; - } - - /** - * @see jsonSchema2Pojo Ref - */ - private static String parseDocTypeToSchemaJavaType(String docType) { - docType = docType.toLowerCase(); - - if (docType.contains("string") || docType.contains("timestamp")) { - return "java.lang.String"; - } else if (docType.contains("boolean") || docType.contains("bool")) { - return "java.lang.Boolean"; - } else if (docType.contains("int") || docType.contains("integer")) { - return "java.lang.Integer"; - } else if (docType.contains("double") || docType.contains("float") || - docType.contains("number")) { - return "java.lang.Double"; - } else { - return "UNKNOWN DOC DATA TYPE"; - } - } - - /** - * @see https://devhints.io/xpath#class-check - */ - private static String xPathClassName(String className) { - return String.format("contains(concat(' ',normalize-space(@class),' '),' %s ')", className); - } -} diff --git a/src/main/java/net/jacobpeterson/alpaca/util/format/FormatUtil.java b/src/main/java/net/jacobpeterson/alpaca/util/format/FormatUtil.java deleted file mode 100644 index e797c294..00000000 --- a/src/main/java/net/jacobpeterson/alpaca/util/format/FormatUtil.java +++ /dev/null @@ -1,53 +0,0 @@ -package net.jacobpeterson.alpaca.util.format; - -import java.text.DecimalFormat; -import java.text.DecimalFormatSymbols; -import java.text.NumberFormat; -import java.time.ZoneId; -import java.time.ZonedDateTime; -import java.time.format.DateTimeFormatter; -import java.time.temporal.TemporalAccessor; -import java.util.Locale; - -/** - * {@link FormatUtil} is a utility class for various formatting. - */ -public class FormatUtil { - - /** - * The {@link ZoneId} for America/New_York. - */ - public static final ZoneId NEW_YORK_ZONED_ID = ZoneId.of("America/New_York"); - - // Alpaca uses the following rounding mechanics with respect to buy orders: (1) rounded down to two decimal - // places if the last trade price is over $1.00; otherwise, rounded down to four decimal places, hence the '#' in - // the 3rd and 4th least significant decimal digits. - private static final NumberFormat CURRENCY_FORMATTER = new DecimalFormat("#0.00##", - DecimalFormatSymbols.getInstance(Locale.US)); - - /** - * Formats an arbitrary number to a currency format. e.g. $123.45 - * - * @param numberToFormat the {@link Number} to format - * - * @return the formatted string - */ - public static String toCurrencyFormat(Number numberToFormat) { - return CURRENCY_FORMATTER.format(numberToFormat); - } - - /** - * Formats a {@link TemporalAccessor} using {@link DateTimeFormatter#ISO_OFFSET_DATE_TIME}. - *
- * Alpaca requires RFC 3339 formatted timestamps to be provided which includes the timezone. ISO - * 8601 is compatible with RFC 3339 which is what {@link DateTimeFormatter#ISO_OFFSET_DATE_TIME} - * uses. - * - * @param zonedDateTime the {@link ZonedDateTime} - * - * @return the formatted string - */ - public static String toRFC3339Format(ZonedDateTime zonedDateTime) { - return DateTimeFormatter.ISO_OFFSET_DATE_TIME.format(zonedDateTime); - } -} diff --git a/src/main/java/net/jacobpeterson/alpaca/util/okhttp/JSONBodyBuilder.java b/src/main/java/net/jacobpeterson/alpaca/util/okhttp/JSONBodyBuilder.java deleted file mode 100644 index 11944345..00000000 --- a/src/main/java/net/jacobpeterson/alpaca/util/okhttp/JSONBodyBuilder.java +++ /dev/null @@ -1,65 +0,0 @@ -package net.jacobpeterson.alpaca.util.okhttp; - -import com.google.gson.JsonElement; -import com.google.gson.JsonObject; -import okhttp3.MediaType; -import okhttp3.RequestBody; - -/** - * {@link JSONBodyBuilder} is builder HTTP bodies. - */ -public class JSONBodyBuilder { - - /** - * The UTF-8 JSON {@link MediaType}. - */ - public static final MediaType JSON_MEDIA_TYPE = MediaType.get("application/json; charset=UTF-8"); - - private String bodyJSON; - private JsonObject bodyJsonObject; - - /** - * Instantiates a new {@link JSONBodyBuilder}. - */ - public JSONBodyBuilder() { - bodyJsonObject = new JsonObject(); - } - - /** - * Instantiates a new {@link JSONBodyBuilder}. - * - * @param bodyJSON a JSON body {@link String} that is used instead of the internal {@link #bodyJsonObject} - */ - public JSONBodyBuilder(String bodyJSON) { - this.bodyJSON = bodyJSON; - } - - /** - * Appends a {@link String} to {@link #bodyJsonObject}. - * - * @param key the key - * @param value the value - */ - public void appendJSONBodyProperty(String key, String value) { - bodyJsonObject.addProperty(key, value); - } - - /** - * Appends a {@link JsonElement} property to {@link #bodyJsonObject}. - * - * @param key the key - * @param value the {@link JsonElement} value - */ - public void appendJSONBodyJSONProperty(String key, JsonElement value) { - bodyJsonObject.add(key, value); - } - - /** - * Builds a {@link RequestBody}. - * - * @return a {@link RequestBody} - */ - public RequestBody build() { - return RequestBody.create(bodyJSON != null ? bodyJSON : bodyJsonObject.toString(), JSON_MEDIA_TYPE); - } -} diff --git a/src/main/java/net/jacobpeterson/alpaca/util/properties/PropertyUtil.java b/src/main/java/net/jacobpeterson/alpaca/util/properties/PropertyUtil.java index 50882e47..718d02a3 100644 --- a/src/main/java/net/jacobpeterson/alpaca/util/properties/PropertyUtil.java +++ b/src/main/java/net/jacobpeterson/alpaca/util/properties/PropertyUtil.java @@ -11,9 +11,9 @@ import java.util.Properties; /** - * {@link PropertyUtil} is a util class for all things {@link Properties}. + * {@link PropertyUtil} is a utility class for all {@link Properties}-related handling. */ -public class PropertyUtil { +public final class PropertyUtil { private static final Logger LOGGER = LoggerFactory.getLogger(PropertyUtil.class); @@ -60,15 +60,13 @@ public static String getProperty(String propertyFile, String defaultPropertyFile * @return the string */ public static String getProperty(String propertyFile, String defaultPropertyFile, String key, String defaultValue) { - Properties properties; - + final Properties properties; if (!CACHED_PROPERTIES.containsKey(propertyFile)) { properties = loadPropertyFile(propertyFile, defaultPropertyFile); CACHED_PROPERTIES.put(propertyFile, properties); } else { properties = CACHED_PROPERTIES.get(propertyFile); } - return properties == null ? defaultValue : properties.getProperty(key, defaultValue); } @@ -88,53 +86,53 @@ public synchronized static Properties loadPropertyFile(String propertyFile, Stri // Load the default property file if exists Properties defaultProperties = null; - InputStream defaultPropertyStream = classLoader.getResourceAsStream(defaultPropertyFile); - - if (defaultPropertyStream != null) { - defaultProperties = new Properties(); - - // Load the properties - try { - defaultProperties.load(defaultPropertyStream); - LOGGER.debug("Loaded default properties file: {}", defaultPropertyFile); - } catch (IOException exception) { - LOGGER.error("Could not load default property file: {}\n{}", defaultPropertyFile, exception); - } - - // Close the InputStream - try { - defaultPropertyStream.close(); - } catch (IOException exception) { - LOGGER.error("Could not close default property file stream: {}\n{}", defaultPropertyFile, exception); + if (defaultPropertyFile != null) { + try (InputStream defaultPropertyStream = classLoader.getResourceAsStream(defaultPropertyFile)) { + if (defaultPropertyStream == null) { + LOGGER.warn("No default property file \"{}\" exists on the classpath.", defaultPropertyFile); + } else { + + defaultProperties = new Properties(); + try { + defaultProperties.load(defaultPropertyStream); + LOGGER.debug("Loaded default properties file: {}", defaultPropertyFile); + } catch (IOException exception) { + LOGGER.error("Could not load default property file: {}", defaultPropertyFile, exception); + } + } + } catch (Exception exception) { + LOGGER.error("Could not read default property file stream: {}", defaultPropertyFile, exception); } - } else { - LOGGER.warn("No default property file found for: {}", propertyFile); + LOGGER.warn("No default property file given for: {}", propertyFile); } - // Load the property file + // Load the property file if exists Properties properties = null; - InputStream propertyStream = classLoader.getResourceAsStream(propertyFile); - - if (propertyStream != null) { - // Add default properties if they were found - properties = defaultProperties == null ? new Properties() : new Properties(defaultProperties); - - // Load the properties - try { - properties.load(propertyStream); - LOGGER.info("Loaded properties file: {}", propertyFile); - } catch (IOException exception) { - LOGGER.error("Could not load property file: {}\n{}", propertyFile, exception); + if (propertyFile != null) { + try (InputStream propertyStream = classLoader.getResourceAsStream(propertyFile)) { + if (propertyStream == null) { + LOGGER.warn("No property file \"{}\" exists on the classpath.", propertyFile); + } else { + // Add default properties if they were found + properties = defaultProperties == null ? new Properties() : new Properties(defaultProperties); + + // Load the properties + try { + properties.load(propertyStream); + LOGGER.info("Loaded properties file: {}", propertyFile); + } catch (IOException exception) { + LOGGER.error("Could not load property file: {}", propertyFile, exception); + } + } + } catch (Exception exception) { + LOGGER.error("Could not read property file stream: {}", propertyFile, exception); } + } else if (defaultProperties == null) { + throw new IllegalStateException("No property files were found!"); + } - // Close the InputStream - try { - propertyStream.close(); - } catch (IOException exception) { - LOGGER.error("Could not close property file stream: {}\n{}", propertyFile, exception); - } - } else { + if (properties == null) { LOGGER.debug("Could not find property file: {}", propertyFile); if (defaultProperties != null) { diff --git a/src/main/java/net/jacobpeterson/alpaca/websocket/AlpacaWebsocket.java b/src/main/java/net/jacobpeterson/alpaca/websocket/AlpacaWebsocket.java index 18387bcd..59208ccc 100644 --- a/src/main/java/net/jacobpeterson/alpaca/websocket/AlpacaWebsocket.java +++ b/src/main/java/net/jacobpeterson/alpaca/websocket/AlpacaWebsocket.java @@ -1,6 +1,5 @@ package net.jacobpeterson.alpaca.websocket; -import net.jacobpeterson.alpaca.util.okhttp.WebsocketStateListener; import okhttp3.HttpUrl; import okhttp3.OkHttpClient; import okhttp3.Request; @@ -216,7 +215,7 @@ private void handleReconnectionAttempt() { } /** - * Cleans up this instances state variables. + * Cleans up this instance's state variables. */ protected void cleanupState() { websocket = null; diff --git a/src/main/java/net/jacobpeterson/alpaca/util/okhttp/WebsocketStateListener.java b/src/main/java/net/jacobpeterson/alpaca/websocket/WebsocketStateListener.java similarity index 94% rename from src/main/java/net/jacobpeterson/alpaca/util/okhttp/WebsocketStateListener.java rename to src/main/java/net/jacobpeterson/alpaca/websocket/WebsocketStateListener.java index 38f91d70..4f3713b6 100644 --- a/src/main/java/net/jacobpeterson/alpaca/util/okhttp/WebsocketStateListener.java +++ b/src/main/java/net/jacobpeterson/alpaca/websocket/WebsocketStateListener.java @@ -1,4 +1,4 @@ -package net.jacobpeterson.alpaca.util.okhttp; +package net.jacobpeterson.alpaca.websocket; import okhttp3.Response; import okhttp3.WebSocket; diff --git a/src/main/java/net/jacobpeterson/alpaca/websocket/streaming/StreamingWebsocket.java b/src/main/java/net/jacobpeterson/alpaca/websocket/streaming/StreamingWebsocket.java index 92a15c0c..2fccb63e 100644 --- a/src/main/java/net/jacobpeterson/alpaca/websocket/streaming/StreamingWebsocket.java +++ b/src/main/java/net/jacobpeterson/alpaca/websocket/streaming/StreamingWebsocket.java @@ -12,6 +12,7 @@ import net.jacobpeterson.alpaca.model.endpoint.streaming.enums.StreamingMessageType; import net.jacobpeterson.alpaca.model.endpoint.streaming.listening.ListeningMessage; import net.jacobpeterson.alpaca.model.endpoint.streaming.trade.TradeUpdateMessage; +import net.jacobpeterson.alpaca.model.properties.EndpointAPIType; import net.jacobpeterson.alpaca.websocket.AlpacaWebsocket; import okhttp3.HttpUrl; import okhttp3.OkHttpClient; @@ -50,14 +51,19 @@ public class StreamingWebsocket extends AlpacaWebsocketalpacaSubdomain
. * - * @param alpacaSubdomain the Alpaca subdomain + * @param endpointAPIType the {@link EndpointAPIType} * * @return a {@link HttpUrl} */ - private static HttpUrl createWebsocketURL(String alpacaSubdomain) { + @SuppressWarnings("UnnecessaryDefault") + private static HttpUrl createWebsocketURL(EndpointAPIType endpointAPIType) { return new HttpUrl.Builder() .scheme("https") // HttpUrl.Builder doesn't recognize "wss" scheme, but "https" works fine - .host(alpacaSubdomain + ".alpaca.markets") + .host((switch (endpointAPIType) { + case LIVE -> "api"; + case PAPER -> "paper-api"; + default -> throw new UnsupportedOperationException(); + }) + ".alpaca.markets") .addPathSegment("stream") .build(); } @@ -68,14 +74,14 @@ private static HttpUrl createWebsocketURL(String alpacaSubdomain) { * Instantiates a new {@link StreamingWebsocket}. * * @param okHttpClient the {@link OkHttpClient} - * @param alpacaSubdomain the Alpaca subdomain + * @param endpointAPIType the {@link EndpointAPIType} * @param keyID the key ID * @param secretKey the secret key * @param oAuthToken the OAuth token */ - public StreamingWebsocket(OkHttpClient okHttpClient, String alpacaSubdomain, + public StreamingWebsocket(OkHttpClient okHttpClient, EndpointAPIType endpointAPIType, String keyID, String secretKey, String oAuthToken) { - super(okHttpClient, createWebsocketURL(alpacaSubdomain), "Streaming", keyID, secretKey, oAuthToken); + super(okHttpClient, createWebsocketURL(endpointAPIType), "Streaming", keyID, secretKey, oAuthToken); listenedStreamMessageTypes = new HashSet<>(); } diff --git a/src/main/resources/alpaca.default.properties b/src/main/resources/alpaca.default.properties index 7da46f1c..e3629268 100644 --- a/src/main/resources/alpaca.default.properties +++ b/src/main/resources/alpaca.default.properties @@ -1,10 +1,2 @@ -#Example: -#key_id = -#secret_key = -#endpoint_api_type = -#data_api_type = -#user_agent = -#Defaults: -endpoint_api_type=paper -data_api_type=iex -user_agent=JavaRuntimeEnvironment/ OkHttp/4.9.1 +endpoint_api_type:paper +data_api_type:iex diff --git a/src/test/java/net/jacobpeterson/alpaca/test/live/AlpacaAPITest.java b/src/test/java/net/jacobpeterson/alpaca/test/live/AlpacaAPITest.java deleted file mode 100644 index 9e6d6136..00000000 --- a/src/test/java/net/jacobpeterson/alpaca/test/live/AlpacaAPITest.java +++ /dev/null @@ -1,289 +0,0 @@ -package net.jacobpeterson.alpaca.test.live; - -import net.jacobpeterson.alpaca.AlpacaAPI; -import net.jacobpeterson.alpaca.model.endpoint.account.Account; -import net.jacobpeterson.alpaca.model.endpoint.accountactivities.AccountActivity; -import net.jacobpeterson.alpaca.model.endpoint.accountactivities.NonTradeActivity; -import net.jacobpeterson.alpaca.model.endpoint.accountactivities.TradeActivity; -import net.jacobpeterson.alpaca.model.endpoint.accountactivities.enums.ActivityType; -import net.jacobpeterson.alpaca.model.endpoint.accountconfiguration.AccountConfiguration; -import net.jacobpeterson.alpaca.model.endpoint.accountconfiguration.enums.DTBPCheck; -import net.jacobpeterson.alpaca.model.endpoint.accountconfiguration.enums.TradeConfirmEmail; -import net.jacobpeterson.alpaca.model.endpoint.clock.Clock; -import net.jacobpeterson.alpaca.model.endpoint.common.enums.SortDirection; -import net.jacobpeterson.alpaca.model.endpoint.orders.Order; -import net.jacobpeterson.alpaca.model.endpoint.orders.enums.CurrentOrderStatus; -import net.jacobpeterson.alpaca.rest.AlpacaClientException; -import net.jacobpeterson.alpaca.rest.endpoint.account.AccountEndpoint; -import net.jacobpeterson.alpaca.rest.endpoint.accountconfiguration.AccountConfigurationEndpoint; -import net.jacobpeterson.alpaca.rest.endpoint.clock.ClockEndpoint; -import net.jacobpeterson.alpaca.rest.endpoint.orders.OrdersEndpoint; -import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.BeforeAll; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.MethodOrderer; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.TestMethodOrder; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.time.ZonedDateTime; -import java.util.Collection; -import java.util.List; - -import static org.junit.jupiter.api.Assertions.assertFalse; -import static org.junit.jupiter.api.Assertions.assertNotNull; -import static org.junit.jupiter.api.Assertions.fail; - -/** - * {@link AlpacaAPITest} tests live endpoints using Alpaca Paper credentials given in the - * alpaca.properties file on the classpath. - */ -@TestMethodOrder(MethodOrderer.OrderAnnotation.class) -public class AlpacaAPITest { - - static { - // Log trace-level - System.setProperty("org.slf4j.simpleLogger.log.net.jacobpeterson", "trace"); - } - - private static final Logger LOGGER = LoggerFactory.getLogger(AlpacaAPITest.class); - private static final int RATE_LIMIT_MILLIS = 200; // Wait 200ms between every test to prevent rate-limiting - - private static AlpacaAPI alpacaAPI; - private static boolean marketOpen; - private static AccountConfiguration accountConfiguration; - - /** - * Executed before all tests in this class. - */ - @BeforeAll - public static void beforeAll() { - alpacaAPI = new AlpacaAPI(); - marketOpen = false; - } - - /** - * Executed before each test. - */ - @BeforeEach - public void beforeEach() { - } - - /** - * Executed after each test. Note that this will {@link Thread#sleep(long)} for {@link #RATE_LIMIT_MILLIS} to - * protect against rate limiting. - */ - @AfterEach - public void afterEach() { - try { - Thread.sleep(RATE_LIMIT_MILLIS); - } catch (InterruptedException exception) { - fail("Interrupted when tearing down!"); - } - } - - /** - * Tests {@link ClockEndpoint#get()}. - * - * @throws AlpacaClientException thrown for {@link AlpacaClientException}s - */ - @Test - @org.junit.jupiter.api.Order(1) - public void testClockEndpointGet() throws AlpacaClientException { - Clock clock = alpacaAPI.clock().get(); - assertNotNull(clock); - - LOGGER.debug("{}", clock); - - assertNotNull(clock.getTimestamp()); - assertNotNull(clock.getIsOpen()); - assertNotNull(clock.getNextOpen()); - assertNotNull(clock.getNextClose()); - - marketOpen = clock.getIsOpen(); - if (marketOpen) { - LOGGER.info("Market is currently open! All live endpoints will be tested."); - } else { - LOGGER.info("Market is currently closed! Only some live endpoints will be tested."); - } - } - - /** - * Tests {@link AccountEndpoint#get()}. - * - * @throws AlpacaClientException thrown for {@link AlpacaClientException}s - * @throws NumberFormatException thrown for {@link NumberFormatException}s - */ - @Test - @SuppressWarnings("ResultOfMethodCallIgnored") - public void testAccountEndpointGet() throws AlpacaClientException, NumberFormatException { - Account account = alpacaAPI.account().get(); - assertNotNull(account); - - LOGGER.debug("{}", account); - - // Assert basic data integrity and not null - Double.parseDouble(account.getCash()); - Double.parseDouble(account.getPortfolioValue()); - Double.parseDouble(account.getLongMarketValue()); - Double.parseDouble(account.getShortMarketValue()); - Double.parseDouble(account.getEquity()); - Double.parseDouble(account.getLastEquity()); - Double.parseDouble(account.getBuyingPower()); - Double.parseDouble(account.getInitialMargin()); - Double.parseDouble(account.getMaintenanceMargin()); - Double.parseDouble(account.getLastMaintenanceMargin()); - Double.parseDouble(account.getDaytradingBuyingPower()); - Double.parseDouble(account.getRegtBuyingPower()); - - // Assert other data exists - assertNotNull(account.getId()); - assertNotNull(account.getAccountNumber()); - assertNotNull(account.getStatus()); - assertNotNull(account.getCurrency()); - assertNotNull(account.getPatternDayTrader()); - assertNotNull(account.getTradeSuspendedByUser()); - assertNotNull(account.getTradingBlocked()); - assertNotNull(account.getTransfersBlocked()); - assertNotNull(account.getAccountBlocked()); - assertNotNull(account.getCreatedAt()); - assertNotNull(account.getShortingEnabled()); - assertNotNull(account.getMultiplier()); - assertNotNull(account.getSma()); - assertNotNull(account.getDaytradeCount()); - } - - /** - * Tests - * @{@link AccountActivitiesEndpoint#get(ZonedDateTime, ZonedDateTime, ZonedDateTime, SortDirection, Integer, - *String, ActivityType...)} one {@link AccountActivity} exists until now. - * - * @throws AlpacaClientException thrown for {@link AlpacaClientException}s - */ - @Test - public void testAccountActivitiesEndpointGetOneActivityExistsUntilNow() throws AlpacaClientException { - List accountActivities = alpacaAPI.accountActivities().get( - null, - ZonedDateTime.now(), - null, - SortDirection.ASCENDING, - 1, - null, - (ActivityType[]) null); - assertNotNull(accountActivities); - assertFalse(accountActivities.isEmpty()); - - accountActivities.forEach(accountActivity -> LOGGER.debug(accountActivity.toString())); - - AccountActivity accountActivity = accountActivities.get(0); - if (accountActivity instanceof TradeActivity) { - TradeActivity tradeActivity = (TradeActivity) accountActivity; - assertNotNull(tradeActivity.getActivityType()); - assertNotNull(tradeActivity.getId()); - assertNotNull(tradeActivity.getCumulativeQuantity()); - assertNotNull(tradeActivity.getRemainingQuantity()); - assertNotNull(tradeActivity.getPrice()); - assertNotNull(tradeActivity.getQuantity()); - assertNotNull(tradeActivity.getSide()); - assertNotNull(tradeActivity.getSymbol()); - assertNotNull(tradeActivity.getTransactionTime()); - assertNotNull(tradeActivity.getOrderId()); - assertNotNull(tradeActivity.getType()); - } else if (accountActivity instanceof NonTradeActivity) { - NonTradeActivity nonTradeActivity = (NonTradeActivity) accountActivity; - assertNotNull(nonTradeActivity.getActivityType()); - assertNotNull(nonTradeActivity.getId()); - assertNotNull(nonTradeActivity.getDate()); - assertNotNull(nonTradeActivity.getNetAmount()); - assertNotNull(nonTradeActivity.getSymbol()); - assertNotNull(nonTradeActivity.getQuantity()); - assertNotNull(nonTradeActivity.getPerShareAmount()); - assertNotNull(nonTradeActivity.getDescription()); - } - } - - /** - * Test {@link AccountConfigurationEndpoint#get()}. - * - * @throws AlpacaClientException thrown for {@link AlpacaClientException}s - */ - @Test - @org.junit.jupiter.api.Order(1) - public void testAccountConfigurationEndpointGet() throws AlpacaClientException { - AccountConfiguration accountConfiguration = alpacaAPI.accountConfiguration().get(); - assertNotNull(accountConfiguration); - - LOGGER.debug("{}", accountConfiguration); - - assertNotNull(accountConfiguration.getDtbpCheck()); - assertNotNull(accountConfiguration.getTradeConfirmEmail()); - assertNotNull(accountConfiguration.getSuspendTrade()); - assertNotNull(accountConfiguration.getNoShorting()); - - AlpacaAPITest.accountConfiguration = accountConfiguration; - } - - /** - * Test {@link AccountConfigurationEndpoint#set(AccountConfiguration)}. - * - * @throws AlpacaClientException thrown for {@link AlpacaClientException}s - */ - @Test - @org.junit.jupiter.api.Order(2) - public void testAccountConfigurationEndpointSet() throws AlpacaClientException { - if (accountConfiguration == null) { - AccountConfiguration newAccountConfiguration = new AccountConfiguration( - DTBPCheck.BOTH, - TradeConfirmEmail.NONE, - false, - false); - LOGGER.info("Settings Account Configuration to: {}", newAccountConfiguration); - alpacaAPI.accountConfiguration().set(newAccountConfiguration); - } else { - alpacaAPI.accountConfiguration().set(accountConfiguration); - } - } - - /** - * Test - * {@link OrdersEndpoint#get(CurrentOrderStatus, Integer, ZonedDateTime, ZonedDateTime, SortDirection, Boolean, - * Collection)} one {@link Order} exists until now. - * - * @throws AlpacaClientException thrown for {@link AlpacaClientException}s - */ - @Test - public void testOrdersEndpointGetOneOrderExistsUntilNow() throws AlpacaClientException { - List orders = alpacaAPI.orders().get( - CurrentOrderStatus.ALL, - 1, - null, - ZonedDateTime.now(), - SortDirection.ASCENDING, - true, - null); - - assertNotNull(orders); - assertFalse(orders.isEmpty()); - - orders.forEach(order -> LOGGER.debug("{}", order)); - - // Assert required fields are present - Order order = orders.get(0); - assertNotNull(order.getId()); - assertNotNull(order.getClientOrderId()); - assertNotNull(order.getCreatedAt()); - assertNotNull(order.getUpdatedAt()); - assertNotNull(order.getSubmittedAt()); - assertNotNull(order.getAssetId()); - assertNotNull(order.getSymbol()); - assertNotNull(order.getAssetClass()); - assertNotNull(order.getQuantity()); - assertNotNull(order.getFilledQuantity()); - assertNotNull(order.getType()); - assertNotNull(order.getSide()); - assertNotNull(order.getTimeInForce()); - assertNotNull(order.getStatus()); - assertNotNull(order.getExtendedHours()); - } -} diff --git a/src/test/java/net/jacobpeterson/alpaca/test/mock/AlpacaAPITest.java b/src/test/java/net/jacobpeterson/alpaca/test/mock/AlpacaAPITest.java deleted file mode 100644 index d5f4f320..00000000 --- a/src/test/java/net/jacobpeterson/alpaca/test/mock/AlpacaAPITest.java +++ /dev/null @@ -1,74 +0,0 @@ -package net.jacobpeterson.alpaca.test.mock; - -import net.jacobpeterson.alpaca.AlpacaAPI; -import net.jacobpeterson.alpaca.model.properties.DataAPIType; -import net.jacobpeterson.alpaca.model.properties.EndpointAPIType; -import okhttp3.OkHttpClient; -import org.junit.jupiter.api.Test; - -/** - * {@link AlpacaAPITest} tests {@link AlpacaAPI} using mocked objects with Mockito. - */ -public class AlpacaAPITest { - - static { - System.setProperty("org.slf4j.simpleLogger.log.net.jacobpeterson.alpaca", "trace"); - } - - /** - * Tests {@link AlpacaAPI#AlpacaAPI()}. - */ - @Test - public void testAlpacaAPIConstructor_Default() { - new AlpacaAPI(); - } - - /** - * Tests {@link AlpacaAPI#AlpacaAPI(String, String)}. - */ - @Test - public void testAlpacaAPIConstructor_keyID_secret() { - String keyID = "ABCDEFGHIJKLM"; - String secret = "NOPQURSTUVWXYZ"; - - new AlpacaAPI(keyID, secret); - } - - /** - * Tests {@link AlpacaAPI#AlpacaAPI(String, String, EndpointAPIType, DataAPIType)}. - */ - @Test - public void testAlpacaAPIConstructor_keyID_secret_endpointAPIType_dataAPIType() { - String keyID = "ABCDEFGHIJKLM"; - String secret = "NOPQURSTUVWXYZ"; - - new AlpacaAPI(keyID, secret, EndpointAPIType.PAPER, DataAPIType.IEX); - new AlpacaAPI(keyID, secret, EndpointAPIType.LIVE, DataAPIType.SIP); - } - - /** - * Tests {@link AlpacaAPI#AlpacaAPI(String)}. - */ - @Test - public void testAlpacaAPIConstructor_oAuthToken() { - String oAuthToken = "ABCDEFGHIJKLMNOPQURSTUVWXYZ"; - - new AlpacaAPI(oAuthToken); - } - - /** - * Tests {@link AlpacaAPI#AlpacaAPI(OkHttpClient, String, String, String, EndpointAPIType, DataAPIType)}. - */ - @Test - public void testAlpacaAPIConstructor_okHttpClient_keyID_secret_oAuthToken_endpointAPIType_dataAPIType() { - OkHttpClient okHttpClient = new OkHttpClient(); - String keyID = "ABCDEFGHIJKLM"; - String secret = "NOPQURSTUVWXYZ"; - String oAuthToken = "ABCDEFGHIJKLMNOPQURSTUVWXYZ"; - - new AlpacaAPI(okHttpClient, null, null, oAuthToken, EndpointAPIType.PAPER, DataAPIType.IEX); - new AlpacaAPI(okHttpClient, keyID, secret, null, EndpointAPIType.LIVE, DataAPIType.SIP); - new AlpacaAPI(okHttpClient, null, null, oAuthToken, EndpointAPIType.PAPER, DataAPIType.IEX); - new AlpacaAPI(okHttpClient, keyID, secret, null, EndpointAPIType.LIVE, DataAPIType.SIP); - } -} diff --git a/src/test/resources/alpaca.default.properties b/src/test/resources/alpaca.default.properties deleted file mode 100644 index 58a3702f..00000000 --- a/src/test/resources/alpaca.default.properties +++ /dev/null @@ -1,5 +0,0 @@ -key_id=ABCDEFGHIJKLM -secret_key=NOPQURSTUVWXYZ -endpoint_api_type=paper -data_api_type=iex -user_agent=JavaRuntimeEnvironment/ OkHttp/4.9.1 From 0383b926bd61028ad396e510ea319d47fa2fc91b Mon Sep 17 00:00:00 2001 From: Jacob Peterson <14127217+Petersoj@users.noreply.github.com> Date: Sat, 2 Mar 2024 13:51:46 -0800 Subject: [PATCH 204/271] Fix JSONSchema2POJOAdaptedPlugin --- .../GeneratePOJOsTask.groovy | 113 ++++++++---------- .../JSONSchema2POJOAdaptedPlugin.groovy | 4 +- 2 files changed, 55 insertions(+), 62 deletions(-) diff --git a/buildSrc/src/main/groovy/net/jacobpeterson/alpaca/buildsrc/plugin/jsonschema2pojoadapted/GeneratePOJOsTask.groovy b/buildSrc/src/main/groovy/net/jacobpeterson/alpaca/buildsrc/plugin/jsonschema2pojoadapted/GeneratePOJOsTask.groovy index 88ac5a4a..650a7c7b 100644 --- a/buildSrc/src/main/groovy/net/jacobpeterson/alpaca/buildsrc/plugin/jsonschema2pojoadapted/GeneratePOJOsTask.groovy +++ b/buildSrc/src/main/groovy/net/jacobpeterson/alpaca/buildsrc/plugin/jsonschema2pojoadapted/GeneratePOJOsTask.groovy @@ -19,7 +19,6 @@ import static com.google.common.base.Preconditions.checkArgument /** * {@link GeneratePOJOsTask} is a Gradle task that invokes jsonschema2pojo with special configurations * and generation logic. - * TODO publish this plugin somewhere so the code doesn't have to be copied between 'cli' and 'system' Gradle projects. * @see GenerateJsonSchemaJavaTask */ class GeneratePOJOsTask extends DefaultTask { @@ -39,69 +38,63 @@ class GeneratePOJOsTask extends DefaultTask { group = "Build" description = "Generates Java POJOs from JSON schemas." - project.afterEvaluate { - // Validate plugin configuration - configuration = project.extensions.getByName(EXTENSION_KEY) as GenerationConfig - checkArgument(!configuration.source.hasNext(), "'source' should not be set for '${name}'.") - checkArgument(configuration.targetDirectory == null, "'targetDirectory' should not be set for '${name}'.") - - // Set custom defaults on plugin configuration - configuration.targetDirectory = project.file( - "${project.layout.buildDirectory.getAsFile().get().getPath()}/generated-sources/jsonschema2pojo") - configuration.targetPackage = configuration.targetPackage == "" ? - "${project.group}.model" : configuration.targetPackage - configuration.propertyWordDelimiters = ["-", "_"] as char[] - configuration.annotationStyle = "gson" - configuration.sourceType = "jsonschema" - configuration.customDateTimePattern = "yyyy-MM-ddTHH:mm:ssZ" - configuration.includeConstructors = true - configuration.serializable = true - configuration.includeGetters = true - configuration.includeSetters = true - configuration.includeCopyConstructor = true - configuration.generateBuilders = true - - // Get project main java source set - final def projectMainJavaSourceSet = - project.extensions.getByType(SourceSetContainer).named("main").get().java - - // Set task dependents and dependencies - project.tasks.named("compileJava").get().dependsOn(this) - dependsOn(project.tasks.named("processResources").get()) - - // Walk through source files to create a list of JSON schema files to process - jsonSchemaFileConfigs = new ArrayList<>() - projectMainJavaSourceSet.srcDirs.each { srcDir -> - if (!srcDir.exists()) { - return - } - final def sourceDirPath = srcDir.getAbsolutePath() - srcDir.eachFileRecurse { sourceFile -> - final def sourceFilePath = sourceFile.getAbsolutePath() - if (sourceFilePath.endsWith(".json")) { - def targetPackage = sourceFile.getParentFile().getAbsolutePath() - .substring(sourceDirPath.length()) - .replace(File.separator, ".").replace("-", "").replace("_", "") - .toLowerCase() - targetPackage = targetPackage.replace("${project.group}", "") - targetPackage = targetPackage.startsWith(".") ? targetPackage.substring(1) : targetPackage - targetPackage = configuration.targetPackage + - (targetPackage.isEmpty() ? "" : targetPackage) - jsonSchemaFileConfigs.add(new JSONSchemaFileConfig(sourceFile, targetPackage)) - - inputs.file(sourceFile) - outputs.file(project.file(configuration.targetDirectory.getAbsolutePath() + File.separator + - targetPackage.replace(".", File.separator) + File.separator + - sourceFile.getName().replace(".json", ".java"))) - } + // Validate plugin configuration + configuration = project.extensions.getByName(EXTENSION_KEY) as GenerationConfig + checkArgument(!configuration.source.hasNext(), "'source' should not be set for '${name}'.") + checkArgument(configuration.targetDirectory == null, "'targetDirectory' should not be set for '${name}'.") + + // Set custom defaults on plugin configuration + configuration.targetDirectory = project.file( + "${project.layout.buildDirectory.getAsFile().get().getPath()}/generated-sources/jsonschema2pojo") + configuration.targetPackage = configuration.targetPackage == "" ? + "${project.group}.model" : configuration.targetPackage + configuration.propertyWordDelimiters = ["-", "_"] as char[] + configuration.annotationStyle = "gson" + configuration.sourceType = "jsonschema" + configuration.customDateTimePattern = "yyyy-MM-ddTHH:mm:ssZ" + configuration.includeConstructors = true + configuration.serializable = true + configuration.includeGetters = true + configuration.includeSetters = true + configuration.includeCopyConstructor = true + configuration.generateBuilders = true + + // Get project main java source set + final def projectMainJavaSourceSet = + project.extensions.getByType(SourceSetContainer).named("main").get().java + + // Walk through source files to create a list of JSON schema files to process + jsonSchemaFileConfigs = new ArrayList<>() + projectMainJavaSourceSet.srcDirs.each { srcDir -> + if (!srcDir.exists()) { + return + } + final def sourceDirPath = srcDir.getAbsolutePath() + srcDir.eachFileRecurse { sourceFile -> + final def sourceFilePath = sourceFile.getAbsolutePath() + if (sourceFilePath.endsWith(".json")) { + def targetPackage = sourceFile.getParentFile().getAbsolutePath() + .substring(sourceDirPath.length()) + .replace(File.separator, ".").replace("-", "").replace("_", "") + .toLowerCase() + targetPackage = targetPackage.replace("${project.group}", "") + targetPackage = targetPackage.startsWith(".") ? targetPackage.substring(1) : targetPackage + targetPackage = configuration.targetPackage + + (targetPackage.isEmpty() ? "" : targetPackage) + jsonSchemaFileConfigs.add(new JSONSchemaFileConfig(sourceFile, targetPackage)) + + inputs.file(sourceFile) + outputs.file(project.file(configuration.targetDirectory.getAbsolutePath() + File.separator + + targetPackage.replace(".", File.separator) + File.separator + + sourceFile.getName().replace(".json", ".java"))) } } + } - outputs.dir(configuration.targetDirectory) - outputs.cacheIf { true } + outputs.dir(configuration.targetDirectory) + outputs.cacheIf { true } - projectMainJavaSourceSet.srcDirs(configuration.targetDirectory) - } + projectMainJavaSourceSet.srcDirs(configuration.targetDirectory) } @TaskAction diff --git a/buildSrc/src/main/groovy/net/jacobpeterson/alpaca/buildsrc/plugin/jsonschema2pojoadapted/JSONSchema2POJOAdaptedPlugin.groovy b/buildSrc/src/main/groovy/net/jacobpeterson/alpaca/buildsrc/plugin/jsonschema2pojoadapted/JSONSchema2POJOAdaptedPlugin.groovy index 9d53a826..963e8276 100644 --- a/buildSrc/src/main/groovy/net/jacobpeterson/alpaca/buildsrc/plugin/jsonschema2pojoadapted/JSONSchema2POJOAdaptedPlugin.groovy +++ b/buildSrc/src/main/groovy/net/jacobpeterson/alpaca/buildsrc/plugin/jsonschema2pojoadapted/JSONSchema2POJOAdaptedPlugin.groovy @@ -17,8 +17,8 @@ class JSONSchema2POJOAdaptedPlugin implements Plugin { void apply(Project project) { project.extensions.create(EXTENSION_KEY, JsonSchemaExtension) if (project.plugins.hasPlugin("java")) { - final def generatePOJOsTask = project.tasks.register("generatePOJOs", GeneratePOJOsTask) - generatePOJOsTask.get().enabled = true + project.tasks.register("generatePOJOs", GeneratePOJOsTask) + project.tasks.compileJava.dependsOn "generatePOJOs" } else { throw new GradleException("'java' Gradle plugin required.") } From 73a9a0bfcaed285fbe1bc6538cb60ab1d5236569 Mon Sep 17 00:00:00 2001 From: Jacob Peterson <14127217+Petersoj@users.noreply.github.com> Date: Sat, 2 Mar 2024 13:52:37 -0800 Subject: [PATCH 205/271] WIP update `build.gradle` to modify OpenAPI clients --- build.gradle | 49 +++++++++++++++++++++++++++++++++++++++---------- 1 file changed, 39 insertions(+), 10 deletions(-) diff --git a/build.gradle b/build.gradle index 844d41b9..bb8bd333 100644 --- a/build.gradle +++ b/build.gradle @@ -1,5 +1,7 @@ import org.openapitools.generator.gradle.plugin.tasks.GenerateTask +import java.nio.file.Paths + plugins { id "java" id "java-library" @@ -62,14 +64,16 @@ java { withJavadocJar() withSourcesJar() } +javadocJar.dependsOn generatePOJOs +sourcesJar.dependsOn generatePOJOs [compileJava, compileTestJava]*.options*.encoding = "UTF-8" javadoc { options.addStringOption("source", "17") - options.addStringOption("Xdoclint:none", "-quiet") // Suppress Javadoc linting warnings options.addStringOption("charset", "UTF-8") - options.addStringOption("link", "https://docs.oracle.com/javase/8/docs/api/") + options.addStringOption("Xdoclint:none", "-quiet") // Suppress Javadoc linting warnings + options.addStringOption("link", "https://docs.oracle.com/en/java/javase/17/docs/api/") } // @@ -86,12 +90,15 @@ final File specDownloadPath = new File(project.layout.buildDirectory.get().getAs final File generatedClientLibrariesPath = new File(project.layout.buildDirectory.get().getAsFile(), "/generated/openapi/") -final def generateAllOpenAPIClientsTasks = tasks.register("generateAllOpenAPIClients") { +final def generateOpenAPIClientsTasks = tasks.register("generateOpenAPIClients") { doLast { - fixOpenAPIGeneratedClientIssues(generatedClientLibrariesPath) + fixOpenAPIGeneratedClientIssues(specIDsOfFileNames, generatedClientLibrariesPath) } } -compileJava.dependsOn generateAllOpenAPIClientsTasks +compileJava.dependsOn generateOpenAPIClientsTasks +jar.dependsOn generateOpenAPIClientsTasks +javadocJar.dependsOn generateOpenAPIClientsTasks +sourcesJar.dependsOn generateOpenAPIClientsTasks def downloadOpenAPISpecFilesTask = tasks.register("downloadOpenAPISpecFiles") { configure { @@ -165,7 +172,9 @@ for (def specFileName : specIDsOfFileNames.keySet()) { "apiPackage", destinationPackage + ".api", "modelPackage", destinationPackage + ".model", "library", "okhttp-gson", - "documentationProvider", "none")) + "documentationProvider", "none", + "useBeanValidation", "false", + "performBeanValidation", "false")) cleanupOutput.set(true) validateSpec.set(false) skipValidateSpec.set(true) @@ -174,22 +183,41 @@ for (def specFileName : specIDsOfFileNames.keySet()) { generateApiTests.set(false) generateApiDocumentation.set(false) } - generateAllOpenAPIClientsTasks.configure { + generateOpenAPIClientsTasks.configure { dependsOn generateTask } } -static void fixOpenAPIGeneratedClientIssues(File generatedClientLibrariesPath) { +static void fixOpenAPIGeneratedClientIssues(Map specIDsOfFileNames, File generatedClientLibrariesPath) { + // TODO remove these manual fixes once OpenAPI fixes these issues + + final def sourceFilesPath = "src/main/java/net/jacobpeterson/alpaca/openapi" + // Fix broker spec generated client issues with discriminators // on some enums: https://github.com/OpenAPITools/openapi-generator/issues/806 final def brokerTransferDataJavaFile = new File(generatedClientLibrariesPath, - "broker/src/main/java/net/jacobpeterson/alpaca/openapi/broker/model/TransferData.java") + "broker/${sourceFilesPath}/broker/model/TransferData.java") brokerTransferDataJavaFile.text = brokerTransferDataJavaFile.text .replace("this.transferType = this.getClass().getSimpleName();", "") final def brokerTransferResourceJavaFile = new File(generatedClientLibrariesPath, - "broker/src/main/java/net/jacobpeterson/alpaca/openapi/broker/model/TransferResource.java") + "broker/${sourceFilesPath}/broker/model/TransferResource.java") brokerTransferResourceJavaFile.text = brokerTransferResourceJavaFile.text .replace("this.type = this.getClass().getSimpleName();", "") + + // Remove the JSON validation that the generated client models always run (there isn't an option to + // disable it for some reason) + specIDsOfFileNames.keySet().stream().map { it.replace(".json", "") }.forEach { + for (File modelFile : Paths.get(generatedClientLibrariesPath.getPath(), + it, sourceFilesPath, it.replace("-", ""), "model").toFile().listFiles()) { + modelFile.text = modelFile.text + .replaceAll("/\\*\\*\\n {2}\\* Validates the JSON Element and throws an exception if issues found" + + "[\\s\\S]*?\\*/", "") + .replace("public static void validateJsonElement(JsonElement jsonElement) throws IOException {", + "public static boolean validate = false;\n public static void " + + "validateJsonElement(JsonElement jsonElement) throws IOException {\n" + + " if (!validate) return;") + } + } } // @@ -206,6 +234,7 @@ publishing { groupId = projectGroup artifactId = projectArtifactID version = projectVersion + from(components["java"]) pom { name = projectArtifactID From a4a9e650cced0bc44c61f42bd1718f16c79f5918 Mon Sep 17 00:00:00 2001 From: Jacob Peterson <14127217+Petersoj@users.noreply.github.com> Date: Sat, 2 Mar 2024 21:27:45 -0800 Subject: [PATCH 206/271] WIP implement wrapper classes for OpenAPI generate clients --- build.gradle | 17 +- .../net/jacobpeterson/alpaca/AlpacaAPI.java | 291 ++++++++----- .../jacobpeterson/alpaca/AlpacaBrokerAPI.java | 304 ++++++++++++++ .../alpaca/AlpacaMarketDataAPI.java | 152 +++++++ .../jacobpeterson/alpaca/AlpacaTraderAPI.java | 212 ++++++++++ .../alpaca/properties/AlpacaProperties.java | 29 -- .../alpaca/properties/data_api_type.json | 9 - .../alpaca/properties/endpoint_api_type.json | 9 - .../alpaca/util/apikey/APIKeyUtil.java | 23 ++ .../apitype/broker_api_endpoint_type.json | 8 + .../apitype/market_data_api_source_type.json | 8 + .../apitype/trader_api_endpoint_type.json | 8 + .../alpaca/util/gson/GsonUtil.java | 28 -- .../util/gson/adapters/LocalDateAdapter.java | 31 -- .../util/gson/adapters/LocalTimeAdapter.java | 31 -- .../gson/adapters/ZonedDateTimeAdapter.java | 31 -- .../alpaca/util/properties/PropertyUtil.java | 146 ------- .../alpaca/websocket/AlpacaWebsocket.java | 292 ------------- .../websocket/AlpacaWebsocketInterface.java | 81 ---- .../AlpacaWebsocketMessageListener.java | 20 - .../websocket/WebsocketStateListener.java | 32 -- .../marketdata/MarketDataListener.java | 11 - .../marketdata/MarketDataWebsocket.java | 387 ------------------ .../MarketDataWebsocketInterface.java | 86 ---- .../crypto/CryptoMarketDataWebsocket.java | 27 -- .../news/NewsMarketDataWebsocket.java | 15 - .../stock/StockMarketDataWebsocket.java | 30 -- .../streaming/StreamingListener.java | 11 - .../streaming/StreamingWebsocket.java | 257 ------------ .../StreamingWebsocketInterface.java | 26 -- src/main/resources/alpaca.default.properties | 2 - 31 files changed, 914 insertions(+), 1700 deletions(-) create mode 100644 src/main/java/net/jacobpeterson/alpaca/AlpacaBrokerAPI.java create mode 100644 src/main/java/net/jacobpeterson/alpaca/AlpacaMarketDataAPI.java create mode 100644 src/main/java/net/jacobpeterson/alpaca/AlpacaTraderAPI.java delete mode 100644 src/main/java/net/jacobpeterson/alpaca/properties/AlpacaProperties.java delete mode 100644 src/main/java/net/jacobpeterson/alpaca/properties/data_api_type.json delete mode 100644 src/main/java/net/jacobpeterson/alpaca/properties/endpoint_api_type.json create mode 100644 src/main/java/net/jacobpeterson/alpaca/util/apikey/APIKeyUtil.java create mode 100644 src/main/java/net/jacobpeterson/alpaca/util/apitype/broker_api_endpoint_type.json create mode 100644 src/main/java/net/jacobpeterson/alpaca/util/apitype/market_data_api_source_type.json create mode 100644 src/main/java/net/jacobpeterson/alpaca/util/apitype/trader_api_endpoint_type.json delete mode 100644 src/main/java/net/jacobpeterson/alpaca/util/gson/GsonUtil.java delete mode 100644 src/main/java/net/jacobpeterson/alpaca/util/gson/adapters/LocalDateAdapter.java delete mode 100644 src/main/java/net/jacobpeterson/alpaca/util/gson/adapters/LocalTimeAdapter.java delete mode 100644 src/main/java/net/jacobpeterson/alpaca/util/gson/adapters/ZonedDateTimeAdapter.java delete mode 100644 src/main/java/net/jacobpeterson/alpaca/util/properties/PropertyUtil.java delete mode 100644 src/main/java/net/jacobpeterson/alpaca/websocket/AlpacaWebsocket.java delete mode 100644 src/main/java/net/jacobpeterson/alpaca/websocket/AlpacaWebsocketInterface.java delete mode 100644 src/main/java/net/jacobpeterson/alpaca/websocket/AlpacaWebsocketMessageListener.java delete mode 100644 src/main/java/net/jacobpeterson/alpaca/websocket/WebsocketStateListener.java delete mode 100644 src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/MarketDataListener.java delete mode 100644 src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/MarketDataWebsocket.java delete mode 100644 src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/MarketDataWebsocketInterface.java delete mode 100644 src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/crypto/CryptoMarketDataWebsocket.java delete mode 100644 src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/news/NewsMarketDataWebsocket.java delete mode 100644 src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/stock/StockMarketDataWebsocket.java delete mode 100644 src/main/java/net/jacobpeterson/alpaca/websocket/streaming/StreamingListener.java delete mode 100644 src/main/java/net/jacobpeterson/alpaca/websocket/streaming/StreamingWebsocket.java delete mode 100644 src/main/java/net/jacobpeterson/alpaca/websocket/streaming/StreamingWebsocketInterface.java delete mode 100644 src/main/resources/alpaca.default.properties diff --git a/build.gradle b/build.gradle index bb8bd333..b13ea565 100644 --- a/build.gradle +++ b/build.gradle @@ -43,6 +43,9 @@ dependencies { // GSON implementation group: "com.google.code.gson", name: "gson", version: "2.10.1" + // Jetbrains Annotations + implementation group: "org.jetbrains", name: "annotations", version: "24.1.0" + // OkHttp implementation group: "com.squareup.okhttp3", name: "okhttp", version: "5.0.0-alpha.12" implementation group: "com.squareup.okhttp3", name: "logging-interceptor", version: "5.0.0-alpha.12" @@ -213,11 +216,21 @@ static void fixOpenAPIGeneratedClientIssues(Map specIDsOfFileNam .replaceAll("/\\*\\*\\n {2}\\* Validates the JSON Element and throws an exception if issues found" + "[\\s\\S]*?\\*/", "") .replace("public static void validateJsonElement(JsonElement jsonElement) throws IOException {", - "public static boolean validate = false;\n public static void " + + "public static boolean validate = false;\n public static void " + "validateJsonElement(JsonElement jsonElement) throws IOException {\n" + - " if (!validate) return;") + " if (!validate) return;") } } + + // Remove the necessary authentication header logic from the generate client's ApiClient + specIDsOfFileNames.keySet().stream().map { it.replace(".json", "") }.forEach { + final def apiClientFile = Paths.get(generatedClientLibrariesPath.getPath(), it, sourceFilesPath, + it.replace("-", ""), "ApiClient.java").toFile() + apiClientFile.text = apiClientFile.text + .replaceAll("// Setup authentications \\(key: authentication name, value: authentication\\)" + + "[\\s\\S]*?authentications\\);", "") + .replaceAll("for \\(String authName : authNames\\)[\\s\\S]*?uri\\);\\n. {7}}", "") + } } // diff --git a/src/main/java/net/jacobpeterson/alpaca/AlpacaAPI.java b/src/main/java/net/jacobpeterson/alpaca/AlpacaAPI.java index bcaaec25..577324ec 100644 --- a/src/main/java/net/jacobpeterson/alpaca/AlpacaAPI.java +++ b/src/main/java/net/jacobpeterson/alpaca/AlpacaAPI.java @@ -1,25 +1,24 @@ package net.jacobpeterson.alpaca; -import net.jacobpeterson.alpaca.model.properties.DataAPIType; -import net.jacobpeterson.alpaca.model.properties.EndpointAPIType; -import net.jacobpeterson.alpaca.properties.AlpacaProperties; -import net.jacobpeterson.alpaca.websocket.marketdata.MarketDataWebsocketInterface; -import net.jacobpeterson.alpaca.websocket.marketdata.crypto.CryptoMarketDataWebsocket; -import net.jacobpeterson.alpaca.websocket.marketdata.news.NewsMarketDataWebsocket; -import net.jacobpeterson.alpaca.websocket.marketdata.stock.StockMarketDataWebsocket; -import net.jacobpeterson.alpaca.websocket.streaming.StreamingWebsocket; -import net.jacobpeterson.alpaca.websocket.streaming.StreamingWebsocketInterface; +import net.jacobpeterson.alpaca.model.util.apitype.BrokerAPIEndpointType; +import net.jacobpeterson.alpaca.model.util.apitype.MarketDataAPISourceType; +import net.jacobpeterson.alpaca.model.util.apitype.TraderAPIEndpointType; import okhttp3.OkHttpClient; import okhttp3.logging.HttpLoggingInterceptor; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import static com.google.common.base.Preconditions.checkArgument; -import static com.google.common.base.Preconditions.checkNotNull; +import static net.jacobpeterson.alpaca.model.util.apitype.BrokerAPIEndpointType.SANDBOX; +import static net.jacobpeterson.alpaca.model.util.apitype.MarketDataAPISourceType.IEX; +import static net.jacobpeterson.alpaca.model.util.apitype.TraderAPIEndpointType.PAPER; /** - * {@link AlpacaAPI} is the main class used to interface with the various Alpaca API endpoints. You will generally only - * need one instance of this class in your application. + * {@link AlpacaAPI} is the main class used to interface with the various Alpaca API endpoints. If you are using the + * Trading or Market Data APIs for a single Alpaca account or if you are using the Broker API, you will generally only + * need one instance of this class. However, if you are using the Trading API with OAuth to act on behalf of an Alpaca + * account, this class is optimized so that it can be instantiated quickly, especially when an existing + * {@link OkHttpClient} is given in the constructor. Additionally, all API endpoint instances are instantiated lazily. + * This class is thread-safe. * * @see Alpaca Docs */ @@ -27,134 +26,186 @@ public class AlpacaAPI { private static final Logger LOGGER = LoggerFactory.getLogger(AlpacaAPI.class); + private final String traderKeyID; + private final String traderSecretKey; + private final String traderOAuthToken; + private final TraderAPIEndpointType traderAPIEndpointType; + private final MarketDataAPISourceType marketDataAPISourceType; + private final String brokerAPIKey; + private final String brokerAPISecret; + private final BrokerAPIEndpointType brokerAPIEndpointType; private final OkHttpClient okHttpClient; - private final StreamingWebsocket streamingWebsocket; - private final CryptoMarketDataWebsocket cryptoMarketDataWebsocket; - private final StockMarketDataWebsocket stockMarketDataWebsocket; - private final NewsMarketDataWebsocket newsMarketDataWebsocket; + + private AlpacaTraderAPI trader; + private AlpacaMarketDataAPI marketData; + private AlpacaBrokerAPI broker; /** - * Instantiates a new {@link AlpacaAPI} using properties specified in alpaca.properties file (or their - * associated defaults). + * Instantiates a new {@link AlpacaAPI}. Use this constructor if you are using the Trading or Market Data APIs for a + * single Alpaca account. + * + * @param traderKeyID the Trader key ID + * @param traderSecretKey the Trader secret key + * @param traderAPIEndpointType the {@link TraderAPIEndpointType} + * @param marketDataAPISourceType the {@link MarketDataAPISourceType} */ - public AlpacaAPI() { - this(AlpacaProperties.KEY_ID, - AlpacaProperties.SECRET_KEY, - AlpacaProperties.ENDPOINT_API_TYPE, - AlpacaProperties.DATA_API_TYPE); + public AlpacaAPI(String traderKeyID, String traderSecretKey, TraderAPIEndpointType traderAPIEndpointType, + MarketDataAPISourceType marketDataAPISourceType) { + this(traderKeyID, traderSecretKey, null, traderAPIEndpointType, marketDataAPISourceType, null, null, null, + null); } /** - * Instantiates a new {@link AlpacaAPI} using properties specified in the given {@link Builder}, otherwise from - * alpaca.properties file (or their associated defaults). + * Instantiates a new {@link AlpacaAPI}. Use this constructor if you are using the Trading or Market Data APIs for a + * single Alpaca account and a custom {@link OkHttpClient} instance. + * + * @param traderKeyID the Trader key ID + * @param traderSecretKey the Trader secret key + * @param traderAPIEndpointType the {@link TraderAPIEndpointType} + * @param marketDataAPISourceType the {@link MarketDataAPISourceType} + * @param okHttpClient an existing {@link OkHttpClient} or null to create a new default + * instance */ - private AlpacaAPI(Builder builder) { - this(builder.keyID, - builder.secretKey, - builder.endpointAPIType, - builder.dataAPIType); + public AlpacaAPI(String traderKeyID, String traderSecretKey, TraderAPIEndpointType traderAPIEndpointType, + MarketDataAPISourceType marketDataAPISourceType, OkHttpClient okHttpClient) { + this(traderKeyID, traderSecretKey, null, traderAPIEndpointType, marketDataAPISourceType, null, null, null, + okHttpClient); } /** - * Instantiates a new {@link AlpacaAPI}. + * Instantiates a new {@link AlpacaAPI}. Use this constructor if you are using the Trading API with OAuth to act on + * behalf of an Alpaca account. * - * @param keyID the key ID - * @param secretKey the secret key + * @param traderOAuthToken the Trader OAuth token + * @param traderAPIEndpointType the {@link TraderAPIEndpointType} */ - public AlpacaAPI(String keyID, String secretKey) { - this(null, keyID, secretKey, null, - AlpacaProperties.ENDPOINT_API_TYPE, - AlpacaProperties.DATA_API_TYPE); + public AlpacaAPI(String traderOAuthToken, TraderAPIEndpointType traderAPIEndpointType) { + this(null, null, traderOAuthToken, traderAPIEndpointType, null, null, null, null, null); } /** - * Instantiates a new {@link AlpacaAPI}. + * Instantiates a new {@link AlpacaAPI}. Use this constructor if you are using the Trading API with OAuth to act on + * behalf of an Alpaca account and a custom {@link OkHttpClient} instance. * - * @param keyID the key ID - * @param secretKey the secret key - * @param endpointAPIType the {@link EndpointAPIType} - * @param dataAPIType the {@link DataAPIType} + * @param traderOAuthToken the Trader OAuth token + * @param traderAPIEndpointType the {@link TraderAPIEndpointType} + * @param okHttpClient an existing {@link OkHttpClient} or null to create a new default + * instance */ - public AlpacaAPI(String keyID, String secretKey, EndpointAPIType endpointAPIType, DataAPIType dataAPIType) { - this(null, keyID, secretKey, null, endpointAPIType, dataAPIType); + public AlpacaAPI(String traderOAuthToken, TraderAPIEndpointType traderAPIEndpointType, OkHttpClient okHttpClient) { + this(null, null, traderOAuthToken, traderAPIEndpointType, null, null, null, null, okHttpClient); } /** - * Instantiates a new {@link AlpacaAPI}. + * Instantiates a new {@link AlpacaAPI}. Use this constructor if you are using the Broker API. + * + * @param brokerAPIKey the Broker API key + * @param brokerAPISecret the Broker API secret + * @param brokerAPIEndpointType the {@link BrokerAPIEndpointType} + */ + public AlpacaAPI(String brokerAPIKey, String brokerAPISecret, BrokerAPIEndpointType brokerAPIEndpointType) { + this(null, null, null, null, null, brokerAPIKey, brokerAPISecret, brokerAPIEndpointType, null); + } + + /** + * Instantiates a new {@link AlpacaAPI}. Use this constructor if you are using the Broker API and a custom + * {@link OkHttpClient} instance. * - * @param oAuthToken the OAuth token. Note that the Data API v2 does not work with OAuth tokens. + * @param brokerAPIKey the Broker API key + * @param brokerAPISecret the Broker API secret + * @param brokerAPIEndpointType the {@link BrokerAPIEndpointType} + * @param okHttpClient an existing {@link OkHttpClient} or null to create a new default + * instance */ - public AlpacaAPI(String oAuthToken) { - this(null, null, null, oAuthToken, - AlpacaProperties.ENDPOINT_API_TYPE, - AlpacaProperties.DATA_API_TYPE); + public AlpacaAPI(String brokerAPIKey, String brokerAPISecret, BrokerAPIEndpointType brokerAPIEndpointType, + OkHttpClient okHttpClient) { + this(null, null, null, null, null, brokerAPIKey, brokerAPISecret, brokerAPIEndpointType, okHttpClient); } /** * Instantiates a new {@link AlpacaAPI}. * - * @param okHttpClient the {@link OkHttpClient} or null to create a default instance - * @param keyID the key ID - * @param secretKey the secret key - * @param oAuthToken the OAuth token - * @param endpointAPIType the {@link EndpointAPIType} - * @param dataAPIType the {@link DataAPIType} + * @param traderKeyID the Trader key ID + * @param traderSecretKey the Trader secret key + * @param traderOAuthToken the Trader OAuth token + * @param traderAPIEndpointType the {@link TraderAPIEndpointType} + * @param marketDataAPISourceType the {@link MarketDataAPISourceType} + * @param brokerAPIKey the Broker API key + * @param brokerAPISecret the Broker API secret + * @param brokerAPIEndpointType the {@link BrokerAPIEndpointType} + * @param okHttpClient an existing {@link OkHttpClient} or null to create a new default + * instance */ - public AlpacaAPI(OkHttpClient okHttpClient, String keyID, String secretKey, String oAuthToken, - EndpointAPIType endpointAPIType, DataAPIType dataAPIType) { - checkArgument((keyID != null && secretKey != null) ^ oAuthToken != null, - "You must specify a (KeyID (%s) and Secret Key (%s)) or an OAuthToken (%s)!", - keyID, secretKey, oAuthToken); - checkNotNull(endpointAPIType); - checkNotNull(dataAPIType); - - // Create default 'okHttpClient' + public AlpacaAPI(String traderKeyID, String traderSecretKey, + String traderOAuthToken, TraderAPIEndpointType traderAPIEndpointType, + MarketDataAPISourceType marketDataAPISourceType, + String brokerAPIKey, String brokerAPISecret, BrokerAPIEndpointType brokerAPIEndpointType, + OkHttpClient okHttpClient) { + this.traderKeyID = traderKeyID; + this.traderSecretKey = traderSecretKey; + this.traderOAuthToken = traderOAuthToken; + this.traderAPIEndpointType = traderAPIEndpointType != null ? traderAPIEndpointType : PAPER; + this.marketDataAPISourceType = marketDataAPISourceType != null ? marketDataAPISourceType : IEX; + this.brokerAPIKey = brokerAPIKey; + this.brokerAPISecret = brokerAPISecret; + this.brokerAPIEndpointType = brokerAPIEndpointType != null ? brokerAPIEndpointType : SANDBOX; + + // Create default OkHttpClient instance if (okHttpClient == null) { - OkHttpClient.Builder clientBuilder = new OkHttpClient.Builder() - .cache(null); // Ensure response caching is disabled + OkHttpClient.Builder clientBuilder = new OkHttpClient.Builder(); if (LOGGER.isDebugEnabled()) { clientBuilder.addInterceptor(new HttpLoggingInterceptor(LOGGER::debug)); } okHttpClient = clientBuilder.build(); } this.okHttpClient = okHttpClient; - - streamingWebsocket = new StreamingWebsocket(okHttpClient, endpointAPIType, keyID, secretKey, oAuthToken); - cryptoMarketDataWebsocket = new CryptoMarketDataWebsocket(okHttpClient, keyID, secretKey); - stockMarketDataWebsocket = new StockMarketDataWebsocket(okHttpClient, dataAPIType, keyID, secretKey); - newsMarketDataWebsocket = new NewsMarketDataWebsocket(okHttpClient, keyID, secretKey); } /** - * @return the {@link StreamingWebsocketInterface} + * Gets the {@link OkHttpClient}. + * + * @return the {@link OkHttpClient} */ - public StreamingWebsocketInterface streaming() { - return streamingWebsocket; + public OkHttpClient getOkHttpClient() { + return okHttpClient; } /** - * @return the Crypto {@link MarketDataWebsocketInterface} + * Gets the {@link AlpacaTraderAPI}. Lazily instantiated. + * + * @return the {@link AlpacaTraderAPI} */ - public MarketDataWebsocketInterface cryptoMarketDataStreaming() { - return cryptoMarketDataWebsocket; + public synchronized AlpacaTraderAPI trader() { + if (trader == null) { + trader = new AlpacaTraderAPI(traderKeyID, traderSecretKey, traderOAuthToken, traderAPIEndpointType, + okHttpClient); + } + return trader; } /** - * @return the Stock {@link MarketDataWebsocketInterface} + * Gets the {@link AlpacaMarketDataAPI}. Lazily instantiated. + * + * @return the {@link AlpacaMarketDataAPI} */ - public MarketDataWebsocketInterface stockMarketDataStreaming() { - return stockMarketDataWebsocket; + public synchronized AlpacaMarketDataAPI marketData() { + if (marketData == null) { + marketData = new AlpacaMarketDataAPI(traderKeyID, traderSecretKey, brokerAPIKey, brokerAPISecret, + okHttpClient); + } + return marketData; } /** - * @return the News {@link MarketDataWebsocketInterface} + * Gets the {@link AlpacaBrokerAPI}. Lazily instantiated. + * + * @return the {@link AlpacaBrokerAPI} */ - public MarketDataWebsocketInterface newsMarketDataStreaming() { - return newsMarketDataWebsocket; - } - - public OkHttpClient getOkHttpClient() { - return okHttpClient; + public synchronized AlpacaBrokerAPI broker() { + if (broker == null) { + broker = new AlpacaBrokerAPI(brokerAPIKey, brokerAPISecret, brokerAPIEndpointType, okHttpClient); + } + return broker; } /** @@ -171,40 +222,66 @@ public static Builder builder() { */ public static final class Builder { - private String keyID; - private String secretKey; - private EndpointAPIType endpointAPIType; - private DataAPIType dataAPIType; + private String traderKeyID; + private String traderSecretKey; + private String traderOAuthToken; + private TraderAPIEndpointType traderAPIEndpointType; + private MarketDataAPISourceType marketDataAPISourceType; + private String brokerAPIKey; + private String brokerAPISecret; + private BrokerAPIEndpointType brokerAPIEndpointType; + private OkHttpClient okHttpClient; + + private Builder() {} + + public Builder withTraderKeyID(String traderKeyID) { + this.traderKeyID = traderKeyID; + return this; + } + + public Builder withTraderSecretKey(String traderSecretKey) { + this.traderSecretKey = traderSecretKey; + return this; + } + + public Builder withTraderOAuthToken(String traderOAuthToken) { + this.traderOAuthToken = traderOAuthToken; + return this; + } + + public Builder withTraderAPIEndpointType(TraderAPIEndpointType traderAPIEndpointType) { + this.traderAPIEndpointType = traderAPIEndpointType; + return this; + } - private Builder() { - this.keyID = AlpacaProperties.KEY_ID; - this.secretKey = AlpacaProperties.SECRET_KEY; - this.endpointAPIType = AlpacaProperties.ENDPOINT_API_TYPE; - this.dataAPIType = AlpacaProperties.DATA_API_TYPE; + public Builder withMarketDataAPISourceType(MarketDataAPISourceType marketDataAPISourceType) { + this.marketDataAPISourceType = marketDataAPISourceType; + return this; } - public Builder withKeyID(String keyID) { - this.keyID = keyID; + public Builder withBrokerAPIKey(String brokerAPIKey) { + this.brokerAPIKey = brokerAPIKey; return this; } - public Builder withSecretKey(String secretKey) { - this.secretKey = secretKey; + public Builder withBrokerAPISecret(String brokerAPISecret) { + this.brokerAPISecret = brokerAPISecret; return this; } - public Builder withEndpointAPIType(EndpointAPIType endpointAPIType) { - this.endpointAPIType = endpointAPIType; + public Builder withBrokerAPIEndpointType(BrokerAPIEndpointType brokerAPIEndpointType) { + this.brokerAPIEndpointType = brokerAPIEndpointType; return this; } - public Builder withDataAPIType(DataAPIType dataAPIType) { - this.dataAPIType = dataAPIType; + public Builder withOkHttpClient(OkHttpClient okHttpClient) { + this.okHttpClient = okHttpClient; return this; } public AlpacaAPI build() { - return new AlpacaAPI(this); + return new AlpacaAPI(traderKeyID, traderSecretKey, traderOAuthToken, traderAPIEndpointType, + marketDataAPISourceType, brokerAPIKey, brokerAPISecret, brokerAPIEndpointType, okHttpClient); } } } diff --git a/src/main/java/net/jacobpeterson/alpaca/AlpacaBrokerAPI.java b/src/main/java/net/jacobpeterson/alpaca/AlpacaBrokerAPI.java new file mode 100644 index 00000000..7d9851b5 --- /dev/null +++ b/src/main/java/net/jacobpeterson/alpaca/AlpacaBrokerAPI.java @@ -0,0 +1,304 @@ +package net.jacobpeterson.alpaca; + +import net.jacobpeterson.alpaca.model.util.apitype.BrokerAPIEndpointType; +import net.jacobpeterson.alpaca.openapi.broker.ApiClient; +import net.jacobpeterson.alpaca.openapi.broker.api.AccountsApi; +import net.jacobpeterson.alpaca.openapi.broker.api.AssetsApi; +import net.jacobpeterson.alpaca.openapi.broker.api.CalendarApi; +import net.jacobpeterson.alpaca.openapi.broker.api.ClockApi; +import net.jacobpeterson.alpaca.openapi.broker.api.CorporateActionsApi; +import net.jacobpeterson.alpaca.openapi.broker.api.CountryInfoApi; +import net.jacobpeterson.alpaca.openapi.broker.api.DocumentsApi; +import net.jacobpeterson.alpaca.openapi.broker.api.EventsApi; +import net.jacobpeterson.alpaca.openapi.broker.api.FundingApi; +import net.jacobpeterson.alpaca.openapi.broker.api.FundingWalletsApi; +import net.jacobpeterson.alpaca.openapi.broker.api.JournalsApi; +import net.jacobpeterson.alpaca.openapi.broker.api.KycApi; +import net.jacobpeterson.alpaca.openapi.broker.api.LogosApi; +import net.jacobpeterson.alpaca.openapi.broker.api.OAuthApi; +import net.jacobpeterson.alpaca.openapi.broker.api.RebalancingApi; +import net.jacobpeterson.alpaca.openapi.broker.api.ReportingApi; +import net.jacobpeterson.alpaca.openapi.broker.api.TradingApi; +import net.jacobpeterson.alpaca.openapi.broker.api.WatchlistApi; +import okhttp3.OkHttpClient; + +import static com.google.common.base.Preconditions.checkNotNull; +import static net.jacobpeterson.alpaca.util.apikey.APIKeyUtil.createBrokerAPIAuthKey; + +/** + * {@link AlpacaBrokerAPI} is the class used to interface with the Alpaca Broker API endpoints. This class is + * thread-safe. + */ +public class AlpacaBrokerAPI { + + private final ApiClient apiClient; + private AccountsApi accounts; + private AssetsApi assets; + private CalendarApi calendar; + private ClockApi clock; + private CorporateActionsApi corporateActions; + private CountryInfoApi countryInfo; + private DocumentsApi documents; + private EventsApi events; + private FundingApi funding; + private FundingWalletsApi fundingWallets; + private JournalsApi journals; + private KycApi kyc; + private LogosApi logos; + private OAuthApi oAuth; + private RebalancingApi rebalancing; + private ReportingApi reporting; + private TradingApi trading; + private WatchlistApi watchlist; + + /** + * Instantiates a new {@link AlpacaBrokerAPI}. + * + * @param brokerAPIKey the Broker API key + * @param brokerAPISecret the Broker API secret + * @param brokerAPIEndpointType the {@link BrokerAPIEndpointType} + * @param okHttpClient an existing {@link OkHttpClient} or null to create a new default + * instance + */ + @SuppressWarnings("UnnecessaryDefault") + AlpacaBrokerAPI(String brokerAPIKey, String brokerAPISecret, BrokerAPIEndpointType brokerAPIEndpointType, + OkHttpClient okHttpClient) { + checkNotNull(brokerAPIKey); + checkNotNull(brokerAPISecret); + checkNotNull(brokerAPIEndpointType); + checkNotNull(okHttpClient); + + apiClient = new ApiClient(okHttpClient); + apiClient.setServerIndex(switch (brokerAPIEndpointType) { + case SANDBOX -> 0; + case PRODUCTION -> 1; + default -> throw new UnsupportedOperationException(); + }); + apiClient.addDefaultHeader("Authorization", createBrokerAPIAuthKey(brokerAPIKey, brokerAPISecret)); + } + + /** + * Gets the internal {@link ApiClient}. + * + * @return the {@link ApiClient} + */ + public ApiClient getInternalAPIClient() { + return apiClient; + } + + /** + * Gets the {@link AccountsApi}. Lazily instantiated. + * + * @return the {@link AccountsApi} + */ + public synchronized AccountsApi accounts() { + if (accounts == null) { + accounts = new AccountsApi(apiClient); + } + return accounts; + } + + /** + * Gets the {@link AssetsApi}. Lazily instantiated. + * + * @return the {@link AssetsApi} + */ + public synchronized AssetsApi assets() { + if (assets == null) { + assets = new AssetsApi(apiClient); + } + return assets; + } + + /** + * Gets the {@link CalendarApi}. Lazily instantiated. + * + * @return the {@link CalendarApi} + */ + public synchronized CalendarApi calendar() { + if (calendar == null) { + calendar = new CalendarApi(apiClient); + } + return calendar; + } + + /** + * Gets the {@link ClockApi}. Lazily instantiated. + * + * @return the {@link ClockApi} + */ + public synchronized ClockApi clock() { + if (clock == null) { + clock = new ClockApi(apiClient); + } + return clock; + } + + /** + * Gets the {@link CorporateActionsApi}. Lazily instantiated. + * + * @return the {@link CorporateActionsApi} + */ + public synchronized CorporateActionsApi corporateActions() { + if (corporateActions == null) { + corporateActions = new CorporateActionsApi(apiClient); + } + return corporateActions; + } + + /** + * Gets the {@link CountryInfoApi}. Lazily instantiated. + * + * @return the {@link CountryInfoApi} + */ + public synchronized CountryInfoApi countryInfo() { + if (countryInfo == null) { + countryInfo = new CountryInfoApi(apiClient); + } + return countryInfo; + } + + /** + * Gets the {@link DocumentsApi}. Lazily instantiated. + * + * @return the {@link DocumentsApi} + */ + public synchronized DocumentsApi documents() { + if (documents == null) { + documents = new DocumentsApi(apiClient); + } + return documents; + } + + /** + * Gets the {@link EventsApi}. Lazily instantiated. + * + * @return the {@link EventsApi} + */ + public synchronized EventsApi events() { + if (events == null) { + events = new EventsApi(apiClient); + } + return events; + } + + /** + * Gets the {@link FundingApi}. Lazily instantiated. + * + * @return the {@link FundingApi} + */ + public synchronized FundingApi funding() { + if (funding == null) { + funding = new FundingApi(apiClient); + } + return funding; + } + + /** + * Gets the {@link FundingWalletsApi}. Lazily instantiated. + * + * @return the {@link FundingWalletsApi} + */ + public synchronized FundingWalletsApi fundingWallets() { + if (fundingWallets == null) { + fundingWallets = new FundingWalletsApi(apiClient); + } + return fundingWallets; + } + + /** + * Gets the {@link JournalsApi}. Lazily instantiated. + * + * @return the {@link JournalsApi} + */ + public synchronized JournalsApi journals() { + if (journals == null) { + journals = new JournalsApi(apiClient); + } + return journals; + } + + /** + * Gets the {@link KycApi}. Lazily instantiated. + * + * @return the {@link KycApi} + */ + public synchronized KycApi kyc() { + if (kyc == null) { + kyc = new KycApi(apiClient); + } + return kyc; + } + + /** + * Gets the {@link LogosApi}. Lazily instantiated. + * + * @return the {@link LogosApi} + */ + public synchronized LogosApi logos() { + if (logos == null) { + logos = new LogosApi(apiClient); + } + return logos; + } + + /** + * Gets the {@link OAuthApi}. Lazily instantiated. + * + * @return the {@link OAuthApi} + */ + public synchronized OAuthApi oAuth() { + if (oAuth == null) { + oAuth = new OAuthApi(apiClient); + } + return oAuth; + } + + /** + * Gets the {@link RebalancingApi}. Lazily instantiated. + * + * @return the {@link RebalancingApi} + */ + public synchronized RebalancingApi rebalancing() { + if (rebalancing == null) { + rebalancing = new RebalancingApi(apiClient); + } + return rebalancing; + } + + /** + * Gets the {@link ReportingApi}. Lazily instantiated. + * + * @return the {@link ReportingApi} + */ + public synchronized ReportingApi reporting() { + if (reporting == null) { + reporting = new ReportingApi(apiClient); + } + return reporting; + } + + /** + * Gets the {@link TradingApi}. Lazily instantiated. + * + * @return the {@link TradingApi} + */ + public synchronized TradingApi trading() { + if (trading == null) { + trading = new TradingApi(apiClient); + } + return trading; + } + + /** + * Gets the {@link WatchlistApi}. Lazily instantiated. + * + * @return the {@link WatchlistApi} + */ + public synchronized WatchlistApi watchlist() { + if (watchlist == null) { + watchlist = new WatchlistApi(apiClient); + } + return watchlist; + } +} diff --git a/src/main/java/net/jacobpeterson/alpaca/AlpacaMarketDataAPI.java b/src/main/java/net/jacobpeterson/alpaca/AlpacaMarketDataAPI.java new file mode 100644 index 00000000..1538581a --- /dev/null +++ b/src/main/java/net/jacobpeterson/alpaca/AlpacaMarketDataAPI.java @@ -0,0 +1,152 @@ +package net.jacobpeterson.alpaca; + +import net.jacobpeterson.alpaca.openapi.marketdata.ApiClient; +import net.jacobpeterson.alpaca.openapi.marketdata.api.CorporateActionsApi; +import net.jacobpeterson.alpaca.openapi.marketdata.api.CryptoApi; +import net.jacobpeterson.alpaca.openapi.marketdata.api.ForexApi; +import net.jacobpeterson.alpaca.openapi.marketdata.api.LogosApi; +import net.jacobpeterson.alpaca.openapi.marketdata.api.NewsApi; +import net.jacobpeterson.alpaca.openapi.marketdata.api.OptionApi; +import net.jacobpeterson.alpaca.openapi.marketdata.api.StockApi; +import okhttp3.OkHttpClient; + +import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.base.Preconditions.checkNotNull; +import static net.jacobpeterson.alpaca.util.apikey.APIKeyUtil.createBrokerAPIAuthKey; + +/** + * {@link AlpacaMarketDataAPI} is the class used to interface with the Alpaca Market Data API endpoints. This class is + * thread-safe. + */ +public class AlpacaMarketDataAPI { + + private final ApiClient apiClient; + private CorporateActionsApi corporateActions; + private CryptoApi crypto; + private ForexApi forex; + private LogosApi logos; + private NewsApi news; + private OptionApi option; + private StockApi stock; + + /** + * Instantiates a new {@link AlpacaMarketDataAPI}. + * + * @param traderKeyID the Trader key ID + * @param traderSecretKey the Trader secret key + * @param brokerAPIKey the Broker API key + * @param brokerAPISecret the Broker API secret + * @param okHttpClient an existing {@link OkHttpClient} or null to create a new default instance + */ + AlpacaMarketDataAPI(String traderKeyID, String traderSecretKey, + String brokerAPIKey, String brokerAPISecret, OkHttpClient okHttpClient) { + checkArgument((traderKeyID != null && traderSecretKey != null) ^ + (brokerAPIKey != null && brokerAPISecret != null), + "You must specify a (trader key ID and secret key) or an (broker API key and secret)!"); + checkNotNull(okHttpClient); + + final boolean traderKeysGiven = traderKeyID != null && traderSecretKey != null; + + apiClient = new ApiClient(okHttpClient); + apiClient.setServerIndex(traderKeysGiven ? 0 : 1); + if (traderKeysGiven) { + apiClient.addDefaultHeader("APCA-API-KEY-ID", traderKeyID); + apiClient.addDefaultHeader("APCA-API-SECRET-KEY", traderSecretKey); + } else { + apiClient.addDefaultHeader("Authorization", createBrokerAPIAuthKey(brokerAPIKey, brokerAPISecret)); + } + } + + /** + * Gets the internal {@link ApiClient}. + * + * @return the {@link ApiClient} + */ + public ApiClient getInternalAPIClient() { + return apiClient; + } + + /** + * Gets the {@link CorporateActionsApi}. Lazily instantiated. + * + * @return the {@link CorporateActionsApi} + */ + public synchronized CorporateActionsApi corporateActions() { + if (corporateActions == null) { + corporateActions = new CorporateActionsApi(apiClient); + } + return corporateActions; + } + + /** + * Gets the {@link CryptoApi}. Lazily instantiated. + * + * @return the {@link CryptoApi} + */ + public synchronized CryptoApi crypto() { + if (crypto == null) { + crypto = new CryptoApi(apiClient); + } + return crypto; + } + + /** + * Gets the {@link ForexApi}. Lazily instantiated. + * + * @return the {@link ForexApi} + */ + public synchronized ForexApi forex() { + if (forex == null) { + forex = new ForexApi(apiClient); + } + return forex; + } + + /** + * Gets the {@link LogosApi}. Lazily instantiated. + * + * @return the {@link LogosApi} + */ + public synchronized LogosApi logos() { + if (logos == null) { + logos = new LogosApi(apiClient); + } + return logos; + } + + /** + * Gets the {@link NewsApi}. Lazily instantiated. + * + * @return the {@link NewsApi} + */ + public synchronized NewsApi news() { + if (news == null) { + news = new NewsApi(apiClient); + } + return news; + } + + /** + * Gets the {@link OptionApi}. Lazily instantiated. + * + * @return the {@link OptionApi} + */ + public synchronized OptionApi option() { + if (option == null) { + option = new OptionApi(apiClient); + } + return option; + } + + /** + * Gets the {@link StockApi}. Lazily instantiated. + * + * @return the {@link StockApi} + */ + public synchronized StockApi stock() { + if (stock == null) { + stock = new StockApi(apiClient); + } + return stock; + } +} diff --git a/src/main/java/net/jacobpeterson/alpaca/AlpacaTraderAPI.java b/src/main/java/net/jacobpeterson/alpaca/AlpacaTraderAPI.java new file mode 100644 index 00000000..07485e9c --- /dev/null +++ b/src/main/java/net/jacobpeterson/alpaca/AlpacaTraderAPI.java @@ -0,0 +1,212 @@ +package net.jacobpeterson.alpaca; + +import net.jacobpeterson.alpaca.model.util.apitype.TraderAPIEndpointType; +import net.jacobpeterson.alpaca.openapi.trader.ApiClient; +import net.jacobpeterson.alpaca.openapi.trader.api.AccountActivitiesApi; +import net.jacobpeterson.alpaca.openapi.trader.api.AccountConfigurationsApi; +import net.jacobpeterson.alpaca.openapi.trader.api.AccountsApi; +import net.jacobpeterson.alpaca.openapi.trader.api.AssetsApi; +import net.jacobpeterson.alpaca.openapi.trader.api.CalendarApi; +import net.jacobpeterson.alpaca.openapi.trader.api.ClockApi; +import net.jacobpeterson.alpaca.openapi.trader.api.CorporateActionsApi; +import net.jacobpeterson.alpaca.openapi.trader.api.OrdersApi; +import net.jacobpeterson.alpaca.openapi.trader.api.PortfolioHistoryApi; +import net.jacobpeterson.alpaca.openapi.trader.api.PositionsApi; +import net.jacobpeterson.alpaca.openapi.trader.api.WatchlistsApi; +import okhttp3.OkHttpClient; + +import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.base.Preconditions.checkNotNull; + +/** + * {@link AlpacaTraderAPI} is the class used to interface with the Alpaca Trader API endpoints. This class is + * thread-safe. + */ +public class AlpacaTraderAPI { + + private final ApiClient apiClient; + private AccountActivitiesApi accountActivities; + private AccountConfigurationsApi accountConfigurations; + private AccountsApi accounts; + private AssetsApi assets; + private CalendarApi calendar; + private ClockApi clock; + private CorporateActionsApi traderCorporateActions; + private OrdersApi orders; + private PortfolioHistoryApi portfolioHistory; + private PositionsApi positions; + private WatchlistsApi watchlists; + + /** + * Instantiates a new {@link AlpacaTraderAPI}. + * + * @param traderKeyID the Trader key ID + * @param traderSecretKey the Trader secret key + * @param traderOAuthToken the Trader OAuth token + * @param traderAPIEndpointType the {@link TraderAPIEndpointType} + * @param okHttpClient an existing {@link OkHttpClient} or null to create a new default + * instance + */ + @SuppressWarnings("UnnecessaryDefault") + AlpacaTraderAPI(String traderKeyID, String traderSecretKey, String traderOAuthToken, + TraderAPIEndpointType traderAPIEndpointType, OkHttpClient okHttpClient) { + checkArgument((traderKeyID != null && traderSecretKey != null) ^ (traderOAuthToken != null), + "You must specify a (trader key ID and secret key) or an (OAuth token)!"); + checkNotNull(traderAPIEndpointType); + checkNotNull(okHttpClient); + + apiClient = new ApiClient(okHttpClient); + apiClient.setServerIndex(switch (traderAPIEndpointType) { + case PAPER -> 0; + case LIVE -> 1; + default -> throw new UnsupportedOperationException(); + }); + if (traderKeyID != null && traderSecretKey != null) { + apiClient.addDefaultHeader("APCA-API-KEY-ID", traderKeyID); + apiClient.addDefaultHeader("APCA-API-SECRET-KEY", traderSecretKey); + } else { + apiClient.addDefaultHeader("Authorization", "Bearer " + traderOAuthToken); + } + } + + /** + * Gets the internal {@link ApiClient}. + * + * @return the {@link ApiClient} + */ + public ApiClient getInternalAPIClient() { + return apiClient; + } + + /** + * Gets the {@link AccountActivitiesApi}. Lazily instantiated. + * + * @return the {@link AccountActivitiesApi} + */ + public synchronized AccountActivitiesApi accountActivities() { + if (accountActivities == null) { + accountActivities = new AccountActivitiesApi(apiClient); + } + return accountActivities; + } + + /** + * Gets the {@link AccountConfigurationsApi}. Lazily instantiated. + * + * @return the {@link AccountConfigurationsApi} + */ + public synchronized AccountConfigurationsApi accountConfigurations() { + if (accountConfigurations == null) { + accountConfigurations = new AccountConfigurationsApi(apiClient); + } + return accountConfigurations; + } + + /** + * Gets the {@link AccountsApi}. Lazily instantiated. + * + * @return {@link AccountsApi} + */ + public synchronized AccountsApi accounts() { + if (accounts == null) { + accounts = new AccountsApi(apiClient); + } + return accounts; + } + + /** + * Gets the {@link AssetsApi}. Lazily instantiated. + * + * @return {@link AssetsApi} + */ + public synchronized AssetsApi assets() { + if (assets == null) { + assets = new AssetsApi(apiClient); + } + return assets; + } + + /** + * Gets the {@link CalendarApi}. Lazily instantiated. + * + * @return {@link CalendarApi} + */ + public synchronized CalendarApi calendar() { + if (calendar == null) { + calendar = new CalendarApi(apiClient); + } + return calendar; + } + + /** + * Gets the {@link ClockApi}. Lazily instantiated. + * + * @return {@link ClockApi} + */ + public synchronized ClockApi clock() { + if (clock == null) { + clock = new ClockApi(apiClient); + } + return clock; + } + + /** + * Gets the {@link CorporateActionsApi}. Lazily instantiated. + * + * @return the {@link CorporateActionsApi} + */ + public synchronized CorporateActionsApi traderCorporateActions() { + if (traderCorporateActions == null) { + traderCorporateActions = new CorporateActionsApi(apiClient); + } + return traderCorporateActions; + } + + /** + * Gets the {@link OrdersApi}. Lazily instantiated. + * + * @return {@link OrdersApi} + */ + public synchronized OrdersApi orders() { + if (orders == null) { + orders = new OrdersApi(apiClient); + } + return orders; + } + + /** + * Gets the {@link PortfolioHistoryApi}. Lazily instantiated. + * + * @return {@link PortfolioHistoryApi} + */ + public synchronized PortfolioHistoryApi portfolioHistory() { + if (portfolioHistory == null) { + portfolioHistory = new PortfolioHistoryApi(apiClient); + } + return portfolioHistory; + } + + /** + * Gets the {@link PositionsApi}. Lazily instantiated. + * + * @return {@link PositionsApi} + */ + public synchronized PositionsApi positions() { + if (positions == null) { + positions = new PositionsApi(apiClient); + } + return positions; + } + + /** + * Gets the {@link WatchlistsApi}. Lazily instantiated. + * + * @return {@link WatchlistsApi} + */ + public synchronized WatchlistsApi watchlists() { + if (watchlists == null) { + watchlists = new WatchlistsApi(apiClient); + } + return watchlists; + } +} diff --git a/src/main/java/net/jacobpeterson/alpaca/properties/AlpacaProperties.java b/src/main/java/net/jacobpeterson/alpaca/properties/AlpacaProperties.java deleted file mode 100644 index 175a83c8..00000000 --- a/src/main/java/net/jacobpeterson/alpaca/properties/AlpacaProperties.java +++ /dev/null @@ -1,29 +0,0 @@ -package net.jacobpeterson.alpaca.properties; - -import net.jacobpeterson.alpaca.AlpacaAPI; -import net.jacobpeterson.alpaca.model.properties.DataAPIType; -import net.jacobpeterson.alpaca.model.properties.EndpointAPIType; - -import static net.jacobpeterson.alpaca.util.properties.PropertyUtil.getProperty; - -/** - * {@link AlpacaProperties} defines properties for {@link AlpacaAPI}. - */ -public class AlpacaProperties { - - private static final String ALPACA_PROPERTIES_FILE = "alpaca.properties"; - private static final String ALPACA_DEFAULT_PROPERTIES_FILE = "alpaca.default.properties"; - - public static final String KEY_ID = getProperty( - ALPACA_PROPERTIES_FILE, ALPACA_DEFAULT_PROPERTIES_FILE, - "key_id"); - public static final String SECRET_KEY = getProperty( - ALPACA_PROPERTIES_FILE, ALPACA_DEFAULT_PROPERTIES_FILE, - "secret_key"); - public static final EndpointAPIType ENDPOINT_API_TYPE = EndpointAPIType.fromValue(getProperty( - ALPACA_PROPERTIES_FILE, ALPACA_DEFAULT_PROPERTIES_FILE, - "endpoint_api_type")); - public static final DataAPIType DATA_API_TYPE = DataAPIType.fromValue(getProperty( - ALPACA_PROPERTIES_FILE, ALPACA_DEFAULT_PROPERTIES_FILE, - "data_api_type")); -} diff --git a/src/main/java/net/jacobpeterson/alpaca/properties/data_api_type.json b/src/main/java/net/jacobpeterson/alpaca/properties/data_api_type.json deleted file mode 100644 index 3a782d77..00000000 --- a/src/main/java/net/jacobpeterson/alpaca/properties/data_api_type.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "type": "string", - "javaName": "DataAPIType", - "title": "Defines the data APIs to use with Alpaca.", - "enum": [ - "iex", - "sip" - ] -} diff --git a/src/main/java/net/jacobpeterson/alpaca/properties/endpoint_api_type.json b/src/main/java/net/jacobpeterson/alpaca/properties/endpoint_api_type.json deleted file mode 100644 index c0eacd4c..00000000 --- a/src/main/java/net/jacobpeterson/alpaca/properties/endpoint_api_type.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "type": "string", - "javaName": "EndpointAPIType", - "title": "Defines the API endpoints to use with Alpaca.", - "enum": [ - "live", - "paper" - ] -} diff --git a/src/main/java/net/jacobpeterson/alpaca/util/apikey/APIKeyUtil.java b/src/main/java/net/jacobpeterson/alpaca/util/apikey/APIKeyUtil.java new file mode 100644 index 00000000..29de58ec --- /dev/null +++ b/src/main/java/net/jacobpeterson/alpaca/util/apikey/APIKeyUtil.java @@ -0,0 +1,23 @@ +package net.jacobpeterson.alpaca.util.apikey; + +import java.util.Base64; + +/** + * {@link APIKeyUtil} is a utility class for Alpaca API keys. + */ +public class APIKeyUtil { + + /** + * Creates a broker API auth key. + * + * @param brokerAPIKey the broker API key + * @param brokerAPISecret the broker API secret + * + * @return the key {@link String} + * + * @see Alpaca Docs + */ + public static String createBrokerAPIAuthKey(String brokerAPIKey, String brokerAPISecret) { + return Base64.getEncoder().encodeToString((brokerAPIKey + ":" + brokerAPISecret).getBytes()); + } +} diff --git a/src/main/java/net/jacobpeterson/alpaca/util/apitype/broker_api_endpoint_type.json b/src/main/java/net/jacobpeterson/alpaca/util/apitype/broker_api_endpoint_type.json new file mode 100644 index 00000000..c4dd1c66 --- /dev/null +++ b/src/main/java/net/jacobpeterson/alpaca/util/apitype/broker_api_endpoint_type.json @@ -0,0 +1,8 @@ +{ + "type": "string", + "javaName": "BrokerAPIEndpointType", + "enum": [ + "sandbox", + "production" + ] +} diff --git a/src/main/java/net/jacobpeterson/alpaca/util/apitype/market_data_api_source_type.json b/src/main/java/net/jacobpeterson/alpaca/util/apitype/market_data_api_source_type.json new file mode 100644 index 00000000..e0f74cdc --- /dev/null +++ b/src/main/java/net/jacobpeterson/alpaca/util/apitype/market_data_api_source_type.json @@ -0,0 +1,8 @@ +{ + "type": "string", + "javaName": "MarketDataAPISourceType", + "enum": [ + "iex", + "sip" + ] +} diff --git a/src/main/java/net/jacobpeterson/alpaca/util/apitype/trader_api_endpoint_type.json b/src/main/java/net/jacobpeterson/alpaca/util/apitype/trader_api_endpoint_type.json new file mode 100644 index 00000000..574d4109 --- /dev/null +++ b/src/main/java/net/jacobpeterson/alpaca/util/apitype/trader_api_endpoint_type.json @@ -0,0 +1,8 @@ +{ + "type": "string", + "javaName": "TraderAPIEndpointType", + "enum": [ + "paper", + "live" + ] +} diff --git a/src/main/java/net/jacobpeterson/alpaca/util/gson/GsonUtil.java b/src/main/java/net/jacobpeterson/alpaca/util/gson/GsonUtil.java deleted file mode 100644 index dc86e2c1..00000000 --- a/src/main/java/net/jacobpeterson/alpaca/util/gson/GsonUtil.java +++ /dev/null @@ -1,28 +0,0 @@ -package net.jacobpeterson.alpaca.util.gson; - -import com.google.gson.Gson; -import com.google.gson.GsonBuilder; -import net.jacobpeterson.alpaca.util.gson.adapters.LocalDateAdapter; -import net.jacobpeterson.alpaca.util.gson.adapters.LocalTimeAdapter; -import net.jacobpeterson.alpaca.util.gson.adapters.ZonedDateTimeAdapter; - -import java.time.LocalDate; -import java.time.LocalTime; -import java.time.ZonedDateTime; - -/** - * {@link GsonUtil} contains utility methods relating to {@link Gson}. - */ -public class GsonUtil { - - /** - * A {@link Gson} instance with registered date/time adapters. - */ - public static final Gson GSON = new GsonBuilder() - .registerTypeAdapter(ZonedDateTime.class, new ZonedDateTimeAdapter()) - .registerTypeAdapter(LocalDate.class, new LocalDateAdapter()) - .registerTypeAdapter(LocalTime.class, new LocalTimeAdapter()) - .enableComplexMapKeySerialization() - .setLenient() - .create(); -} diff --git a/src/main/java/net/jacobpeterson/alpaca/util/gson/adapters/LocalDateAdapter.java b/src/main/java/net/jacobpeterson/alpaca/util/gson/adapters/LocalDateAdapter.java deleted file mode 100644 index a9475c17..00000000 --- a/src/main/java/net/jacobpeterson/alpaca/util/gson/adapters/LocalDateAdapter.java +++ /dev/null @@ -1,31 +0,0 @@ -package net.jacobpeterson.alpaca.util.gson.adapters; - -import com.google.gson.Gson; -import com.google.gson.JsonDeserializationContext; -import com.google.gson.JsonDeserializer; -import com.google.gson.JsonElement; -import com.google.gson.JsonParseException; -import com.google.gson.JsonPrimitive; -import com.google.gson.JsonSerializationContext; -import com.google.gson.JsonSerializer; - -import java.lang.reflect.Type; -import java.time.LocalDate; -import java.time.format.DateTimeFormatter; - -/** - * {@link LocalDateAdapter} is a {@link Gson} adapter for {@link LocalDate}s. - */ -public class LocalDateAdapter implements JsonSerializer, JsonDeserializer { - - @Override - public JsonElement serialize(LocalDate src, Type typeOfSrc, JsonSerializationContext context) { - return new JsonPrimitive(src.format(DateTimeFormatter.ISO_LOCAL_DATE)); - } - - @Override - public LocalDate deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) - throws JsonParseException { - return LocalDate.parse(json.getAsJsonPrimitive().getAsString(), DateTimeFormatter.ISO_LOCAL_DATE); - } -} diff --git a/src/main/java/net/jacobpeterson/alpaca/util/gson/adapters/LocalTimeAdapter.java b/src/main/java/net/jacobpeterson/alpaca/util/gson/adapters/LocalTimeAdapter.java deleted file mode 100644 index 9d6bb735..00000000 --- a/src/main/java/net/jacobpeterson/alpaca/util/gson/adapters/LocalTimeAdapter.java +++ /dev/null @@ -1,31 +0,0 @@ -package net.jacobpeterson.alpaca.util.gson.adapters; - -import com.google.gson.Gson; -import com.google.gson.JsonDeserializationContext; -import com.google.gson.JsonDeserializer; -import com.google.gson.JsonElement; -import com.google.gson.JsonParseException; -import com.google.gson.JsonPrimitive; -import com.google.gson.JsonSerializationContext; -import com.google.gson.JsonSerializer; - -import java.lang.reflect.Type; -import java.time.LocalTime; -import java.time.format.DateTimeFormatter; - -/** - * {@link LocalTimeAdapter} is a {@link Gson} adapter for {@link LocalTime}s. - */ -public class LocalTimeAdapter implements JsonSerializer, JsonDeserializer { - - @Override - public JsonElement serialize(LocalTime localTime, Type typeOfSrc, JsonSerializationContext context) { - return new JsonPrimitive(localTime.format(DateTimeFormatter.ISO_LOCAL_TIME)); - } - - @Override - public LocalTime deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) - throws JsonParseException { - return LocalTime.parse(json.getAsJsonPrimitive().getAsString(), DateTimeFormatter.ISO_LOCAL_TIME); - } -} diff --git a/src/main/java/net/jacobpeterson/alpaca/util/gson/adapters/ZonedDateTimeAdapter.java b/src/main/java/net/jacobpeterson/alpaca/util/gson/adapters/ZonedDateTimeAdapter.java deleted file mode 100644 index e10f1be1..00000000 --- a/src/main/java/net/jacobpeterson/alpaca/util/gson/adapters/ZonedDateTimeAdapter.java +++ /dev/null @@ -1,31 +0,0 @@ -package net.jacobpeterson.alpaca.util.gson.adapters; - -import com.google.gson.Gson; -import com.google.gson.JsonDeserializationContext; -import com.google.gson.JsonDeserializer; -import com.google.gson.JsonElement; -import com.google.gson.JsonParseException; -import com.google.gson.JsonPrimitive; -import com.google.gson.JsonSerializationContext; -import com.google.gson.JsonSerializer; - -import java.lang.reflect.Type; -import java.time.ZonedDateTime; -import java.time.format.DateTimeFormatter; - -/** - * {@link ZonedDateTimeAdapter} is a {@link Gson} adapter for {@link ZonedDateTime}s. - */ -public class ZonedDateTimeAdapter implements JsonSerializer, JsonDeserializer { - - @Override - public JsonElement serialize(ZonedDateTime src, Type typeOfSrc, JsonSerializationContext context) { - return new JsonPrimitive(src.format(DateTimeFormatter.ISO_OFFSET_DATE_TIME)); - } - - @Override - public ZonedDateTime deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) - throws JsonParseException { - return ZonedDateTime.parse(json.getAsJsonPrimitive().getAsString(), DateTimeFormatter.ISO_DATE_TIME); - } -} diff --git a/src/main/java/net/jacobpeterson/alpaca/util/properties/PropertyUtil.java b/src/main/java/net/jacobpeterson/alpaca/util/properties/PropertyUtil.java deleted file mode 100644 index 718d02a3..00000000 --- a/src/main/java/net/jacobpeterson/alpaca/util/properties/PropertyUtil.java +++ /dev/null @@ -1,146 +0,0 @@ -package net.jacobpeterson.alpaca.util.properties; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.io.IOException; -import java.io.InputStream; -import java.util.Collections; -import java.util.HashMap; -import java.util.Map; -import java.util.Properties; - -/** - * {@link PropertyUtil} is a utility class for all {@link Properties}-related handling. - */ -public final class PropertyUtil { - - private static final Logger LOGGER = LoggerFactory.getLogger(PropertyUtil.class); - - public static final Map CACHED_PROPERTIES = Collections.synchronizedMap(new HashMap<>()); - - /** - * Gets a string property from a property file. Will try to IO load the properties in the property file if not - * cached already. - * - * @param propertyFile the property file - * @param key the key - * - * @return the string - */ - public static String getProperty(String propertyFile, String key) { - return getProperty(propertyFile, null, key, null); - } - - /** - * Gets a string property from a property file. Will try to IO load the properties in the property file if not - * cached already. If the desired property is not found in the propertyFile, then the - * defaultPropertyFile is searched. - * - * @param propertyFile the property file - * @param defaultPropertyFile the default property file - * @param key the key - * - * @return the string - */ - public static String getProperty(String propertyFile, String defaultPropertyFile, String key) { - return getProperty(propertyFile, defaultPropertyFile, key, null); - } - - /** - * Gets a string property from a property file. Will try to IO load the properties in the property file if not - * cached already. If the desired property is not found in the propertyFile, then the - * defaultPropertyFile is searched, and if it's not there, then defaultValue is returned. - * - * @param propertyFile the property file - * @param defaultPropertyFile the default property file - * @param key the key - * @param defaultValue the default value (if the desired property wasn't found, then this is returned) - * - * @return the string - */ - public static String getProperty(String propertyFile, String defaultPropertyFile, String key, String defaultValue) { - final Properties properties; - if (!CACHED_PROPERTIES.containsKey(propertyFile)) { - properties = loadPropertyFile(propertyFile, defaultPropertyFile); - CACHED_PROPERTIES.put(propertyFile, properties); - } else { - properties = CACHED_PROPERTIES.get(propertyFile); - } - return properties == null ? defaultValue : properties.getProperty(key, defaultValue); - } - - /** - * Loads property file {@link Properties}. - * - * @param propertyFile the property file name - * @param defaultPropertyFile the default property file name - * - * @return the properties - */ - public synchronized static Properties loadPropertyFile(String propertyFile, String defaultPropertyFile) { - ClassLoader classLoader = PropertyUtil.class.getClassLoader(); - if (classLoader == null) { - classLoader = ClassLoader.getSystemClassLoader(); - } - - // Load the default property file if exists - Properties defaultProperties = null; - if (defaultPropertyFile != null) { - try (InputStream defaultPropertyStream = classLoader.getResourceAsStream(defaultPropertyFile)) { - if (defaultPropertyStream == null) { - LOGGER.warn("No default property file \"{}\" exists on the classpath.", defaultPropertyFile); - } else { - - defaultProperties = new Properties(); - try { - defaultProperties.load(defaultPropertyStream); - LOGGER.debug("Loaded default properties file: {}", defaultPropertyFile); - } catch (IOException exception) { - LOGGER.error("Could not load default property file: {}", defaultPropertyFile, exception); - } - } - } catch (Exception exception) { - LOGGER.error("Could not read default property file stream: {}", defaultPropertyFile, exception); - } - } else { - LOGGER.warn("No default property file given for: {}", propertyFile); - } - - // Load the property file if exists - Properties properties = null; - if (propertyFile != null) { - try (InputStream propertyStream = classLoader.getResourceAsStream(propertyFile)) { - if (propertyStream == null) { - LOGGER.warn("No property file \"{}\" exists on the classpath.", propertyFile); - } else { - // Add default properties if they were found - properties = defaultProperties == null ? new Properties() : new Properties(defaultProperties); - - // Load the properties - try { - properties.load(propertyStream); - LOGGER.info("Loaded properties file: {}", propertyFile); - } catch (IOException exception) { - LOGGER.error("Could not load property file: {}", propertyFile, exception); - } - } - } catch (Exception exception) { - LOGGER.error("Could not read property file stream: {}", propertyFile, exception); - } - } else if (defaultProperties == null) { - throw new IllegalStateException("No property files were found!"); - } - - if (properties == null) { - LOGGER.debug("Could not find property file: {}", propertyFile); - - if (defaultProperties != null) { - LOGGER.info("Using default properties for: {}", propertyFile); - properties = defaultProperties; - } - } - - return properties; - } -} diff --git a/src/main/java/net/jacobpeterson/alpaca/websocket/AlpacaWebsocket.java b/src/main/java/net/jacobpeterson/alpaca/websocket/AlpacaWebsocket.java deleted file mode 100644 index 59208ccc..00000000 --- a/src/main/java/net/jacobpeterson/alpaca/websocket/AlpacaWebsocket.java +++ /dev/null @@ -1,292 +0,0 @@ -package net.jacobpeterson.alpaca.websocket; - -import okhttp3.HttpUrl; -import okhttp3.OkHttpClient; -import okhttp3.Request; -import okhttp3.Response; -import okhttp3.WebSocket; -import okhttp3.WebSocketListener; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.util.concurrent.CompletableFuture; -import java.util.concurrent.ForkJoinPool; -import java.util.concurrent.Future; - -import static com.google.common.base.Preconditions.checkNotNull; - -/** - * {@link AlpacaWebsocket} represents an abstract websocket for Alpaca. - * - * @param the {@link AlpacaWebsocketMessageListener} type parameter - * @param the 'message type' type parameter - * @param the 'message' type parameter - */ -public abstract class AlpacaWebsocket> extends WebSocketListener - implements AlpacaWebsocketInterface { - - /** - * Defines a websocket normal closure code. - * - * @see WebSocket#close(int, String) - */ - public static final int WEBSOCKET_NORMAL_CLOSURE_CODE = 1000; - - /** - * Defines a websocket normal closure message. - * - * @see WebSocket#close(int, String) - */ - public static final String WEBSOCKET_NORMAL_CLOSURE_MESSAGE = "Normal closure"; - - /** - * Defines the maximum number of reconnection attempts to be made by an {@link AlpacaWebsocket}. - */ - public static int MAX_RECONNECT_ATTEMPTS = 5; - - /** - * Defines the millisecond sleep interval between reconnection attempts made by an {@link AlpacaWebsocket}. - */ - public static int RECONNECT_SLEEP_INTERVAL = 1000; - - private static final Logger LOGGER = LoggerFactory.getLogger(AlpacaWebsocket.class); - - protected final OkHttpClient okHttpClient; - protected final HttpUrl websocketURL; - protected final String websocketName; - protected final String keyID; - protected final String secretKey; - protected final String oAuthToken; - protected final boolean useOAuth; - - protected L listener; - protected WebsocketStateListener websocketStateListener; - protected WebSocket websocket; - protected boolean connected; - protected boolean authenticated; - protected CompletableFuture authenticationMessageFuture; - protected boolean intentionalClose; - protected int reconnectAttempts; - protected boolean automaticallyReconnect; - - /** - * Instantiates a {@link AlpacaWebsocket}. - * - * @param okHttpClient the {@link OkHttpClient} - * @param websocketURL the websocket {@link HttpUrl} - * @param websocketName the websocket name - * @param keyID the key ID - * @param secretKey the secret key - * @param oAuthToken the OAuth token - */ - public AlpacaWebsocket(OkHttpClient okHttpClient, HttpUrl websocketURL, String websocketName, - String keyID, String secretKey, String oAuthToken) { - checkNotNull(okHttpClient); - checkNotNull(websocketURL); - checkNotNull(websocketName); - - this.okHttpClient = okHttpClient; - this.websocketURL = websocketURL; - this.websocketName = websocketName; - this.keyID = keyID; - this.secretKey = secretKey; - this.oAuthToken = oAuthToken; - useOAuth = oAuthToken != null; - - automaticallyReconnect = true; - } - - @Override - public void connect() { - if (!isConnected()) { - Request websocketRequest = new Request.Builder() - .url(websocketURL) - .get() - .build(); - websocket = okHttpClient.newWebSocket(websocketRequest, this); - } - } - - @Override - public void disconnect() { - if (websocket != null && isConnected()) { - intentionalClose = true; - websocket.close(WEBSOCKET_NORMAL_CLOSURE_CODE, WEBSOCKET_NORMAL_CLOSURE_MESSAGE); - } else { - cleanupState(); - } - } - - @Override - public boolean isConnected() { - return connected; - } - - @Override - public boolean isAuthenticated() { - return authenticated; - } - - @Override - public void onOpen(@NotNull WebSocket webSocket, @NotNull Response response) { - connected = true; - - LOGGER.info("{} websocket opened.", websocketName); - LOGGER.debug("{} websocket response: {}", websocketName, response); - - // Call 'onConnection' or 'onReconnection' async to avoid any potential dead-locking since this is called - // in sync with 'onMessage' in OkHttp's 'WebSocketListener' - ForkJoinPool.commonPool().execute(() -> { - if (reconnectAttempts > 0) { - reconnectAttempts = 0; - onReconnection(); - } else { - onConnection(); - } - }); - - if (websocketStateListener != null) { - websocketStateListener.onOpen(response); - } - } - - @Override - public void onClosed(@NotNull WebSocket webSocket, int code, @NotNull String reason) { - connected = false; - - if (intentionalClose) { - LOGGER.info("{} websocket closed.", websocketName); - LOGGER.debug("Close code: {}, Reason: {}", code, reason); - cleanupState(); - } else { - LOGGER.error("{} websocket closed unintentionally! Code: {}, Reason: {}", websocketName, code, reason); - handleReconnectionAttempt(); - } - - if (websocketStateListener != null) { - websocketStateListener.onClosed(code, reason); - } - } - - @Override - public void onFailure(@NotNull WebSocket webSocket, @NotNull Throwable cause, @Nullable Response response) { - LOGGER.error("{} websocket failure!", websocketName, cause); - - // A websocket failure occurs when either there is a connection failure or when the client throws - // an Exception when receiving a message. In either case, OkHttp will silently close the websocket - // connection, so try to reopen it. - connected = false; - handleReconnectionAttempt(); - - if (websocketStateListener != null) { - websocketStateListener.onFailure(cause); - } - } - - /** - * Attempts to reconnect the disconnected {@link #websocket} asynchronously. - */ - private void handleReconnectionAttempt() { - if (!automaticallyReconnect) { - return; - } - - if (reconnectAttempts < MAX_RECONNECT_ATTEMPTS) { - LOGGER.info("Attempting to reconnect {} websocket in {} milliseconds...", - websocketName, RECONNECT_SLEEP_INTERVAL); - - reconnectAttempts++; - - ForkJoinPool.commonPool().execute(() -> { - try { - Thread.sleep(RECONNECT_SLEEP_INTERVAL); - } catch (InterruptedException ignored) { - return; - } - - connect(); - }); - } else { - LOGGER.error("Exhausted {} reconnection attempts. Not attempting to reconnect.", MAX_RECONNECT_ATTEMPTS); - cleanupState(); - } - } - - /** - * Cleans up this instance's state variables. - */ - protected void cleanupState() { - websocket = null; - connected = false; - authenticated = false; - if (authenticationMessageFuture != null && !authenticationMessageFuture.isDone()) { - authenticationMessageFuture.complete(false); - } - authenticationMessageFuture = null; - intentionalClose = false; - reconnectAttempts = 0; - } - - /** - * Called asynchronously when a websocket connection is made. - */ - protected abstract void onConnection(); - - /** - * Called asynchronously when a websocket reconnection is made after unintentional disconnection. - */ - protected abstract void onReconnection(); - - /** - * Sends an authentication message to authenticate this websocket stream. - */ - protected abstract void sendAuthenticationMessage(); - - @Override - public Future getAuthorizationFuture() { - if (authenticationMessageFuture == null) { - authenticationMessageFuture = new CompletableFuture<>(); - } - - return authenticationMessageFuture; - } - - /** - * Calls the {@link AlpacaWebsocketMessageListener}. - * - * @param messageType the message type - * @param message the message - */ - protected void callListener(T messageType, M message) { - if (listener != null) { - try { - listener.onMessage(messageType, message); - } catch (Exception exception) { - LOGGER.error("{} listener threw exception!", websocketName, exception); - } - } - } - - @Override - public void setListener(L listener) { - this.listener = listener; - } - - public WebsocketStateListener getWebsocketStateListener() { - return websocketStateListener; - } - - public void setWebsocketStateListener(WebsocketStateListener websocketStateListener) { - this.websocketStateListener = websocketStateListener; - } - - public boolean doesAutomaticallyReconnect() { - return automaticallyReconnect; - } - - public void setAutomaticallyReconnect(boolean automaticallyReconnect) { - this.automaticallyReconnect = automaticallyReconnect; - } -} diff --git a/src/main/java/net/jacobpeterson/alpaca/websocket/AlpacaWebsocketInterface.java b/src/main/java/net/jacobpeterson/alpaca/websocket/AlpacaWebsocketInterface.java deleted file mode 100644 index f524e1df..00000000 --- a/src/main/java/net/jacobpeterson/alpaca/websocket/AlpacaWebsocketInterface.java +++ /dev/null @@ -1,81 +0,0 @@ -package net.jacobpeterson.alpaca.websocket; - -import java.util.concurrent.ExecutionException; -import java.util.concurrent.Future; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.TimeoutException; - -/** - * {@link AlpacaWebsocketInterface} defines an interface for Alpaca websockets. - * - * @param the {@link AlpacaWebsocketMessageListener} type parameter - */ -public interface AlpacaWebsocketInterface> { - - /** - * Connects this Websocket. - */ - void connect(); - - /** - * Disconnects this Websocket. - */ - void disconnect(); - - /** - * Returns true if this websocket is connected. - * - * @return a boolean - */ - boolean isConnected(); - - /** - * Returns true if this websocket is authenticated. - * - * @return a boolean - */ - boolean isAuthenticated(); - - /** - * True if {@link #isConnected()} and {@link #isAuthenticated()}. - * - * @return a boolean - */ - default boolean isValid() { - return isConnected() && isAuthenticated(); - } - - /** - * Gets a {@link Boolean} {@link Future} that completes when an authentication message that is received after a new - * websocket connection indicates successful authentication. - *
- * Note that if this {@link AlpacaWebsocketInterface} is already authorized, the returned {@link Future} will likely - * never complete. - * - * @return a {@link Boolean} {@link Future} - */ - Future getAuthorizationFuture(); - - /** - * Waits for {@link #getAuthorizationFuture()} to complete and returns its value, except when timeout - * time has elapsed, then this will return false. - * - * @param timeout the timeout time - * @param unit the timeout {@link TimeUnit} - * - * @return a boolean - */ - default boolean waitForAuthorization(long timeout, TimeUnit unit) { - try { - return getAuthorizationFuture().get(timeout, unit); - } catch (InterruptedException | ExecutionException | TimeoutException ignored) {} - return false; - } - - /** - * Sets the {@link AlpacaWebsocketMessageListener} for this {@link AlpacaWebsocketInterface}. - * - * @param listener the {@link AlpacaWebsocketMessageListener} - */ - void setListener(L listener); -} diff --git a/src/main/java/net/jacobpeterson/alpaca/websocket/AlpacaWebsocketMessageListener.java b/src/main/java/net/jacobpeterson/alpaca/websocket/AlpacaWebsocketMessageListener.java deleted file mode 100644 index da55535c..00000000 --- a/src/main/java/net/jacobpeterson/alpaca/websocket/AlpacaWebsocketMessageListener.java +++ /dev/null @@ -1,20 +0,0 @@ -package net.jacobpeterson.alpaca.websocket; - -/** - * {@link AlpacaWebsocketMessageListener} defines a listener interface for messages from an {@link AlpacaWebsocket} - * instances. - * - * @param the 'message type' type parameter - * @param the 'message' type parameter - */ -@FunctionalInterface -public interface AlpacaWebsocketMessageListener { - - /** - * Called when a message is received. - * - * @param messageType the message type - * @param message the message - */ - void onMessage(T messageType, M message); -} diff --git a/src/main/java/net/jacobpeterson/alpaca/websocket/WebsocketStateListener.java b/src/main/java/net/jacobpeterson/alpaca/websocket/WebsocketStateListener.java deleted file mode 100644 index 4f3713b6..00000000 --- a/src/main/java/net/jacobpeterson/alpaca/websocket/WebsocketStateListener.java +++ /dev/null @@ -1,32 +0,0 @@ -package net.jacobpeterson.alpaca.websocket; - -import okhttp3.Response; -import okhttp3.WebSocket; - -/** - * {@link WebsocketStateListener} is an interface to listen for various state changes in an {@link WebSocket} instance. - */ -public interface WebsocketStateListener { - - /** - * Called when the {@link WebSocket} is connected. - * - * @param response the HTTP websocket upgrade {@link Response} - */ - void onOpen(Response response); - - /** - * Called when the {@link WebSocket} is disconnected. - * - * @param code the code - * @param reason the reason - */ - void onClosed(int code, String reason); - - /** - * Called when a {@link WebSocket} error has occurred. - * - * @param cause the cause {@link Throwable} - */ - void onFailure(Throwable cause); -} diff --git a/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/MarketDataListener.java b/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/MarketDataListener.java deleted file mode 100644 index 0f760d7e..00000000 --- a/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/MarketDataListener.java +++ /dev/null @@ -1,11 +0,0 @@ -package net.jacobpeterson.alpaca.websocket.marketdata; - -import net.jacobpeterson.alpaca.model.endpoint.marketdata.common.realtime.MarketDataMessage; -import net.jacobpeterson.alpaca.model.endpoint.marketdata.common.realtime.enums.MarketDataMessageType; -import net.jacobpeterson.alpaca.websocket.AlpacaWebsocketMessageListener; - -/** - * {@link MarketDataListener} defines a listener interface for {@link MarketDataWebsocket} messages. - */ -public interface MarketDataListener extends AlpacaWebsocketMessageListener { -} diff --git a/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/MarketDataWebsocket.java b/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/MarketDataWebsocket.java deleted file mode 100644 index 48c297f9..00000000 --- a/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/MarketDataWebsocket.java +++ /dev/null @@ -1,387 +0,0 @@ -package net.jacobpeterson.alpaca.websocket.marketdata; - -import com.google.common.collect.Iterables; -import com.google.gson.JsonArray; -import com.google.gson.JsonElement; -import com.google.gson.JsonObject; -import com.google.gson.JsonParser; -import net.jacobpeterson.alpaca.model.endpoint.marketdata.common.realtime.MarketDataMessage; -import net.jacobpeterson.alpaca.model.endpoint.marketdata.common.realtime.bar.BarMessage; -import net.jacobpeterson.alpaca.model.endpoint.marketdata.common.realtime.control.ErrorMessage; -import net.jacobpeterson.alpaca.model.endpoint.marketdata.common.realtime.control.SubscriptionsMessage; -import net.jacobpeterson.alpaca.model.endpoint.marketdata.common.realtime.control.SuccessMessage; -import net.jacobpeterson.alpaca.model.endpoint.marketdata.common.realtime.enums.MarketDataMessageType; -import net.jacobpeterson.alpaca.model.endpoint.marketdata.common.realtime.quote.QuoteMessage; -import net.jacobpeterson.alpaca.model.endpoint.marketdata.common.realtime.trade.TradeMessage; -import net.jacobpeterson.alpaca.model.endpoint.marketdata.news.common.NewsArticle; -import net.jacobpeterson.alpaca.model.endpoint.marketdata.news.realtime.NewsMessage; -import net.jacobpeterson.alpaca.websocket.AlpacaWebsocket; -import okhttp3.HttpUrl; -import okhttp3.OkHttpClient; -import okhttp3.WebSocket; -import org.jetbrains.annotations.NotNull; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.lang.reflect.Type; -import java.util.Arrays; -import java.util.Collection; -import java.util.HashSet; -import java.util.List; -import java.util.Set; -import java.util.concurrent.TimeUnit; - -import static com.google.common.base.Preconditions.checkNotNull; -import static com.google.common.base.Preconditions.checkState; -import static com.google.common.base.Predicates.not; -import static net.jacobpeterson.alpaca.util.gson.GsonUtil.GSON; - -/** - * {@link MarketDataWebsocket} is an {@link AlpacaWebsocket} implementation and provides the - * {@link MarketDataWebsocketInterface} interface for - * Realtime Market Data for both crypto - * and stocks. - */ -public abstract class MarketDataWebsocket - extends AlpacaWebsocket - implements MarketDataWebsocketInterface { - - private static final Logger LOGGER = LoggerFactory.getLogger(MarketDataWebsocket.class); - private static final String MESSAGE_TYPE_ELEMENT_KEY = "T"; - private static final List AUTH_FAILURE_MESSAGES = Arrays.asList( - "auth failed", - "auth timeout", - "not authenticated"); - private static final List SUBSCRIBABLE_MARKET_DATA_MESSAGE_TYPES = Arrays.asList( - MarketDataMessageType.TRADE, - MarketDataMessageType.QUOTE, - MarketDataMessageType.BAR, - MarketDataMessageType.NEWS); - - /** - * Creates a {@link HttpUrl} for {@link MarketDataWebsocket} with the given websocketURLPathSegments. - * - * @param websocketURLPathSegments the websocket URL path segments - * - * @return a {@link HttpUrl} - */ - private static HttpUrl createWebsocketURL(String websocketURLPathSegments) { - return new HttpUrl.Builder() - .scheme("https") // HttpUrl.Builder doesn't recognize "wss" scheme, but "https" works fine - .host("stream.data.alpaca.markets") - .addPathSegments(websocketURLPathSegments) - .build(); - } - - private final Set listenedMarketDataMessageTypes; - private final Set subscribedTrades; - private final Set subscribedQuotes; - private final Set subscribedBars; - private final Set subscribedNews; - private final Type tradeClassType; - private final Type quoteClassType; - private final Type barClassType; - private final Type newsClassType = NewsMessage.class; - - /** - * Instantiates a new {@link MarketDataWebsocket}. - * - * @param okHttpClient the {@link OkHttpClient} - * @param websocketURLPathSegments the websocket URL path segments - * @param websocketMarketDataTypeName the websocket market data type name {@link String} - * @param keyID the key ID - * @param secretKey the secret key - * @param tradeClass the {@link TradeMessage} {@link Class} to deserialize data into - * @param quoteClass the {@link QuoteMessage} {@link Class} to deserialize data into - * @param barClass the {@link BarMessage} {@link Class} to deserialize data into - */ - public MarketDataWebsocket(OkHttpClient okHttpClient, String websocketURLPathSegments, - String websocketMarketDataTypeName, String keyID, String secretKey, - Class tradeClass, Class quoteClass, - Class barClass) { - super(okHttpClient, createWebsocketURL(websocketURLPathSegments), websocketMarketDataTypeName + " Market Data", - keyID, secretKey, null); - - listenedMarketDataMessageTypes = new HashSet<>(); - subscribedTrades = new HashSet<>(); - subscribedQuotes = new HashSet<>(); - subscribedBars = new HashSet<>(); - subscribedNews = new HashSet<>(); - this.tradeClassType = tradeClass; - this.quoteClassType = quoteClass; - this.barClassType = barClass; - } - - @Override - protected void cleanupState() { - super.cleanupState(); - - listenedMarketDataMessageTypes.clear(); - } - - @Override - protected void onConnection() { - sendAuthenticationMessage(); - } - - @Override - protected void onReconnection() { - sendAuthenticationMessage(); - if (waitForAuthorization(5, TimeUnit.SECONDS)) { - subscribeToControl(Iterables.toArray(listenedMarketDataMessageTypes, MarketDataMessageType.class)); - subscribe(subscribedTrades, subscribedQuotes, subscribedBars, subscribedNews); - } - } - - @Override - protected void sendAuthenticationMessage() { - // Ensures that 'authenticationMessageFuture' exists - getAuthorizationFuture(); - - /* Format of message is: - * { - * "action": "auth", - * "key": "{APCA-API-KEY-ID}", - * "secret": "{APCA-API-SECRET-KEY}" - * } - */ - - JsonObject authObject = new JsonObject(); - authObject.addProperty("action", "auth"); - authObject.addProperty("key", keyID); - authObject.addProperty("secret", secretKey); - - LOGGER.info("{} websocket sending authentication message...", websocketName); - websocket.send(authObject.toString()); - } - - // This websocket uses string frames and not binary frames. - @Override - public void onMessage(@NotNull WebSocket webSocket, @NotNull String message) { - LOGGER.trace("{}", message); - - JsonElement messageElement = JsonParser.parseString(message); - checkState(messageElement instanceof JsonArray, "Message must be a JsonArray! Received: %s", messageElement); - - JsonArray messageArray = messageElement.getAsJsonArray(); - for (JsonElement arrayElement : messageArray) { - checkState(arrayElement instanceof JsonObject, - "'arrayElement' must be a JsonObject! Received: %s", arrayElement); - - JsonObject messageObject = arrayElement.getAsJsonObject(); - MarketDataMessageType marketDataMessageType = GSON.fromJson( - messageObject.get(MESSAGE_TYPE_ELEMENT_KEY), MarketDataMessageType.class); - checkNotNull(marketDataMessageType, "MarketDataMessageType not found in message: %s", messageObject); - - MarketDataMessage marketDataMessage; - switch (marketDataMessageType) { - case SUCCESS: - marketDataMessage = GSON.fromJson(messageObject, SuccessMessage.class); - - if (isSuccessMessageAuthenticated((SuccessMessage) marketDataMessage)) { - LOGGER.info("{} websocket authenticated.", websocketName); - - authenticated = true; - - if (authenticationMessageFuture != null) { - authenticationMessageFuture.complete(true); - } - } - break; - case ERROR: - marketDataMessage = GSON.fromJson(messageObject, ErrorMessage.class); - - if (isErrorMessageAuthFailure((ErrorMessage) marketDataMessage) && - authenticationMessageFuture != null) { - LOGGER.error("{} websocket not authenticated! Received: {}.", websocketName, marketDataMessage); - - authenticated = false; - - if (authenticationMessageFuture != null) { - authenticationMessageFuture.complete(false); - } - } else { - LOGGER.error("{} websocket error message: {}", websocketName, marketDataMessage); - } - break; - case SUBSCRIPTION: - marketDataMessage = GSON.fromJson(messageObject, SubscriptionsMessage.class); - - // Update 'listenedMarketDataMessageTypes' and the associated subscribed symbols lists - SubscriptionsMessage subscriptionsMessage = (SubscriptionsMessage) marketDataMessage; - handleSubscriptionMessageList(MarketDataMessageType.TRADE, subscriptionsMessage.getTrades(), - subscribedTrades); - handleSubscriptionMessageList(MarketDataMessageType.QUOTE, subscriptionsMessage.getQuotes(), - subscribedQuotes); - handleSubscriptionMessageList(MarketDataMessageType.BAR, subscriptionsMessage.getBars(), - subscribedBars); - handleSubscriptionMessageList(MarketDataMessageType.NEWS, subscriptionsMessage.getNews(), - subscribedNews); - break; - case TRADE: - marketDataMessage = GSON.fromJson(messageObject, tradeClassType); - break; - case QUOTE: - marketDataMessage = GSON.fromJson(messageObject, quoteClassType); - break; - case BAR: - marketDataMessage = GSON.fromJson(messageObject, barClassType); - break; - case NEWS: - marketDataMessage = GSON.fromJson(messageObject, newsClassType); - break; - default: - LOGGER.error("Message type {} not implemented!", marketDataMessageType); - continue; - } - - if (listenedMarketDataMessageTypes.contains(marketDataMessageType)) { - callListener(marketDataMessageType, marketDataMessage); - } - } - } - - /** - * Returns true if {@link SuccessMessage#getMessage()} equals "authenticated". - * - * @param successMessage the {@link SuccessMessage} - * - * @return a boolean - */ - private boolean isSuccessMessageAuthenticated(SuccessMessage successMessage) { - return successMessage.getMessage().equalsIgnoreCase("authenticated"); - } - - /** - * Handles a {@link SubscriptionsMessage} for updating {@link #listenedMarketDataMessageTypes} and returns a - * {@link List} of currently subscribe symbols or null. - * - * @param marketDataMessageType the {@link MarketDataMessageType} - * @param newSubscribedSymbols the new subscribed symbols from {@link SubscriptionsMessage} - * @param currentSubscribeSymbols the currently subscribe symbols {@link Set} - */ - private void handleSubscriptionMessageList(MarketDataMessageType marketDataMessageType, - Collection newSubscribedSymbols, Set currentSubscribeSymbols) { - if (newSubscribedSymbols != null && !newSubscribedSymbols.isEmpty()) { - listenedMarketDataMessageTypes.add(marketDataMessageType); - currentSubscribeSymbols.clear(); - currentSubscribeSymbols.addAll(newSubscribedSymbols); - } else { - listenedMarketDataMessageTypes.remove(marketDataMessageType); - currentSubscribeSymbols.clear(); - } - } - - /** - * Returns true if {@link ErrorMessage#getMessage()} is any of {@link #AUTH_FAILURE_MESSAGES}. - * - * @param errorMessage the {@link ErrorMessage} - * - * @return a boolean - */ - private boolean isErrorMessageAuthFailure(ErrorMessage errorMessage) { - return AUTH_FAILURE_MESSAGES.contains(errorMessage.getMessage().toLowerCase()); - } - - @Override - public void subscribeToControl(MarketDataMessageType... marketDataMessageTypes) { - if (marketDataMessageTypes == null) { - return; - } - - Arrays.stream(marketDataMessageTypes) - .filter(not(SUBSCRIBABLE_MARKET_DATA_MESSAGE_TYPES::contains)) - .forEach(listenedMarketDataMessageTypes::add); - } - - @Override - public void subscribe(Collection tradeSymbols, Collection quoteSymbols, - Collection barSymbols, Collection newsSymbols) { - sendSubscriptionUpdate(tradeSymbols, quoteSymbols, barSymbols, newsSymbols, true); - } - - @Override - public void unsubscribe(Collection tradeSymbols, Collection quoteSymbols, - Collection barSymbols, Collection newsSymbols) { - sendSubscriptionUpdate(tradeSymbols, quoteSymbols, barSymbols, newsSymbols, false); - } - - /** - * Sends a subscription update request. - * - * @param tradeSymbols a {@link Collection} of symbols to update for trades or null for no change - * @param quoteSymbols a {@link Collection} of symbols to update for quotes or null for no change - * @param barSymbols a {@link Collection} of symbols to update for bars or null for no change - * @param subscribe true to subscribe, false to unsubscribe - */ - private void sendSubscriptionUpdate(Collection tradeSymbols, Collection quoteSymbols, - Collection barSymbols, Collection newsSymbols, boolean subscribe) { - if (!isConnected()) { - throw new IllegalStateException("This websocket must be connected before sending subscription updates!"); - } - - /* Format of message is: - * { - * "action": "subscribe", - * "trades": ["AAPL"], - * "quotes": ["AMD", "CLDR"], - * "bars": ["*"] - * } - */ - - JsonObject subscriptionUpdateObject = new JsonObject(); - subscriptionUpdateObject.addProperty("action", subscribe ? "subscribe" : "unsubscribe"); - - addSubscriptionUpdateList(subscriptionUpdateObject, "trades", tradeSymbols); - addSubscriptionUpdateList(subscriptionUpdateObject, "quotes", quoteSymbols); - addSubscriptionUpdateList(subscriptionUpdateObject, "bars", barSymbols); - addSubscriptionUpdateList(subscriptionUpdateObject, "news", newsSymbols); - - boolean updateExists = subscriptionUpdateObject.size() > 1; - if (updateExists) { - websocket.send(subscriptionUpdateObject.toString()); - LOGGER.info("Requested subscriptions update: {}.", subscriptionUpdateObject); - } - } - - /** - * Adds symbols to subscriptionUpdateObject with elementKey if not empty. - * - * @param subscriptionUpdateObject the subscription update {@link JsonObject} - * @param elementKey the element key - * @param symbols the symbols {@link Collection} - */ - private void addSubscriptionUpdateList(JsonObject subscriptionUpdateObject, String elementKey, - Collection symbols) { - if (symbols != null && !symbols.isEmpty()) { - JsonArray symbolArray = new JsonArray(); - symbols.forEach(symbolArray::add); - subscriptionUpdateObject.add(elementKey, symbolArray); - } - } - - @Override - public Collection subscribedControls() { - return new HashSet<>(listenedMarketDataMessageTypes); - } - - @Override - public Collection subscribedTrades() { - return new HashSet<>(subscribedTrades); - } - - @Override - public Collection subscribedQuotes() { - return new HashSet<>(subscribedQuotes); - } - - @Override - public Collection subscribedBars() { - return new HashSet<>(subscribedBars); - } - - @Override - public Collection subscribedNews() { - return new HashSet<>(subscribedNews); - } -} diff --git a/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/MarketDataWebsocketInterface.java b/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/MarketDataWebsocketInterface.java deleted file mode 100644 index d4f7700c..00000000 --- a/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/MarketDataWebsocketInterface.java +++ /dev/null @@ -1,86 +0,0 @@ -package net.jacobpeterson.alpaca.websocket.marketdata; - -import net.jacobpeterson.alpaca.model.endpoint.marketdata.common.realtime.MarketDataMessage; -import net.jacobpeterson.alpaca.model.endpoint.marketdata.common.realtime.control.ErrorMessage; -import net.jacobpeterson.alpaca.model.endpoint.marketdata.common.realtime.control.SubscriptionsMessage; -import net.jacobpeterson.alpaca.model.endpoint.marketdata.common.realtime.control.SuccessMessage; -import net.jacobpeterson.alpaca.model.endpoint.marketdata.common.realtime.enums.MarketDataMessageType; -import net.jacobpeterson.alpaca.websocket.AlpacaWebsocketInterface; - -import java.util.Collection; - -/** - * {@link MarketDataWebsocketInterface} is an {@link AlpacaWebsocketInterface} for a {@link MarketDataWebsocket}. - */ -public interface MarketDataWebsocketInterface extends AlpacaWebsocketInterface { - - /** - * Subscribe to a specific control {@link MarketDataMessage} which contain information about the stream's current - * state. That is, an {@link ErrorMessage}, {@link SubscriptionsMessage}, or {@link SuccessMessage}. - * - * @param marketDataMessageTypes array containing any of the following: {@link MarketDataMessageType#SUCCESS}, - * {@link MarketDataMessageType#ERROR}, or {@link MarketDataMessageType#SUBSCRIPTION} - */ - void subscribeToControl(MarketDataMessageType... marketDataMessageTypes); - - /** - * Subscribes to trades, quotes, or bars according to the given {@link Collection} of symbols. - *
- * Note that any one of the given {@link Collection}s can contain the wildcard character e.g. "*" to subscribe to - * ALL available symbols. - * - * @param tradeSymbols a {@link Collection} of symbols to subscribe to trades or null for no change - * @param quoteSymbols a {@link Collection} of symbols to subscribe to quotes or null for no change - * @param barSymbols a {@link Collection} of symbols to subscribe to bars or null for no change - * - * @see #unsubscribe(Collection, Collection, Collection, Collection) - */ - void subscribe(Collection tradeSymbols, Collection quoteSymbols, Collection barSymbols, Collection newsSymbols); - - /** - * Unsubscribes from trades, quotes, or bars according to the given {@link Collection} of symbols. - *
- * Note that any one of the given {@link Collection}s can contain the wildcard character (e.g. "*") to unsubscribe - * from a previously subscribed wildcard. - * - * @param tradeSymbols a {@link Collection} of symbols to unsubscribe from trades or null for no - * change - * @param quoteSymbols a {@link Collection} of symbols to unsubscribe from quotes or null for no - * change - * @param barSymbols a {@link Collection} of symbols to unsubscribe from bars or null for no change - * - * @see #subscribe(Collection, Collection, Collection, Collection) - */ - void unsubscribe(Collection tradeSymbols, Collection quoteSymbols, Collection barSymbols, Collection newsSymbols); - - /** - * Gets all the currently subscribed control {@link MarketDataMessageType}s. - * - * @return a {@link Collection} of {@link MarketDataMessageType}s - */ - Collection subscribedControls(); - - /** - * Gets all the currently subscribed symbols for trade updates. - * - * @return a {@link Collection} of {@link String}s - */ - Collection subscribedTrades(); - - /** - * Gets all the currently subscribed symbols for quote updates. - * - * @return a {@link Collection} of {@link String}s - */ - Collection subscribedQuotes(); - - /** - * Gets all the currently subscribed symbols for bar updates. - * - * @return a {@link Collection} of {@link String}s - */ - Collection subscribedBars(); - - Collection subscribedNews(); - -} diff --git a/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/crypto/CryptoMarketDataWebsocket.java b/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/crypto/CryptoMarketDataWebsocket.java deleted file mode 100644 index 27168332..00000000 --- a/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/crypto/CryptoMarketDataWebsocket.java +++ /dev/null @@ -1,27 +0,0 @@ -package net.jacobpeterson.alpaca.websocket.marketdata.crypto; - -import net.jacobpeterson.alpaca.model.endpoint.marketdata.crypto.realtime.bar.CryptoBarMessage; -import net.jacobpeterson.alpaca.model.endpoint.marketdata.crypto.realtime.quote.CryptoQuoteMessage; -import net.jacobpeterson.alpaca.model.endpoint.marketdata.crypto.realtime.trade.CryptoTradeMessage; -import net.jacobpeterson.alpaca.websocket.marketdata.MarketDataWebsocket; -import okhttp3.OkHttpClient; - -/** - * {@link CryptoMarketDataWebsocket} is a {@link MarketDataWebsocket} for - * Realtime - * Crypto Market Data - */ -public class CryptoMarketDataWebsocket extends MarketDataWebsocket { - - /** - * Instantiates a new {@link CryptoMarketDataWebsocket}. - * - * @param okHttpClient the {@link OkHttpClient} - * @param keyID the key ID - * @param secretKey the secret key - */ - public CryptoMarketDataWebsocket(OkHttpClient okHttpClient, String keyID, String secretKey) { - super(okHttpClient, "v1beta3/crypto/us", "Crypto", keyID, secretKey, CryptoTradeMessage.class, - CryptoQuoteMessage.class, CryptoBarMessage.class); - } -} diff --git a/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/news/NewsMarketDataWebsocket.java b/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/news/NewsMarketDataWebsocket.java deleted file mode 100644 index 9d2c2620..00000000 --- a/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/news/NewsMarketDataWebsocket.java +++ /dev/null @@ -1,15 +0,0 @@ -package net.jacobpeterson.alpaca.websocket.marketdata.news; - -import net.jacobpeterson.alpaca.model.endpoint.marketdata.common.realtime.bar.BarMessage; -import net.jacobpeterson.alpaca.model.endpoint.marketdata.common.realtime.quote.QuoteMessage; -import net.jacobpeterson.alpaca.model.endpoint.marketdata.common.realtime.trade.TradeMessage; -import net.jacobpeterson.alpaca.websocket.marketdata.MarketDataWebsocket; -import okhttp3.OkHttpClient; - -public class NewsMarketDataWebsocket extends MarketDataWebsocket { - public NewsMarketDataWebsocket(OkHttpClient okHttpClient, - String keyID, String secretKey) { - super(okHttpClient, "v1beta1/news", "News", keyID, secretKey, TradeMessage.class, - QuoteMessage.class, BarMessage.class); - } -} diff --git a/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/stock/StockMarketDataWebsocket.java b/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/stock/StockMarketDataWebsocket.java deleted file mode 100644 index 4b897f0d..00000000 --- a/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/stock/StockMarketDataWebsocket.java +++ /dev/null @@ -1,30 +0,0 @@ -package net.jacobpeterson.alpaca.websocket.marketdata.stock; - -import net.jacobpeterson.alpaca.model.endpoint.marketdata.stock.realtime.bar.StockBarMessage; -import net.jacobpeterson.alpaca.model.endpoint.marketdata.stock.realtime.quote.StockQuoteMessage; -import net.jacobpeterson.alpaca.model.endpoint.marketdata.stock.realtime.trade.StockTradeMessage; -import net.jacobpeterson.alpaca.model.properties.DataAPIType; -import net.jacobpeterson.alpaca.websocket.marketdata.MarketDataWebsocket; -import okhttp3.OkHttpClient; - -/** - * {@link StockMarketDataWebsocket} is a {@link MarketDataWebsocket} for - * Realtime - * Stock Market Data - */ -public class StockMarketDataWebsocket extends MarketDataWebsocket { - - /** - * Instantiates a new {@link StockMarketDataWebsocket}. - * - * @param okHttpClient the {@link OkHttpClient} - * @param dataAPIType the {@link DataAPIType} - * @param keyID the key ID - * @param secretKey the secret key - */ - public StockMarketDataWebsocket(OkHttpClient okHttpClient, DataAPIType dataAPIType, - String keyID, String secretKey) { - super(okHttpClient, "v2/" + dataAPIType.toString(), "Stock", keyID, secretKey, StockTradeMessage.class, - StockQuoteMessage.class, StockBarMessage.class); - } -} diff --git a/src/main/java/net/jacobpeterson/alpaca/websocket/streaming/StreamingListener.java b/src/main/java/net/jacobpeterson/alpaca/websocket/streaming/StreamingListener.java deleted file mode 100644 index 3ebf714d..00000000 --- a/src/main/java/net/jacobpeterson/alpaca/websocket/streaming/StreamingListener.java +++ /dev/null @@ -1,11 +0,0 @@ -package net.jacobpeterson.alpaca.websocket.streaming; - -import net.jacobpeterson.alpaca.model.endpoint.streaming.StreamingMessage; -import net.jacobpeterson.alpaca.model.endpoint.streaming.enums.StreamingMessageType; -import net.jacobpeterson.alpaca.websocket.AlpacaWebsocketMessageListener; - -/** - * {@link StreamingListener} defines a listener interface for {@link StreamingWebsocket} messages. - */ -public interface StreamingListener extends AlpacaWebsocketMessageListener { -} diff --git a/src/main/java/net/jacobpeterson/alpaca/websocket/streaming/StreamingWebsocket.java b/src/main/java/net/jacobpeterson/alpaca/websocket/streaming/StreamingWebsocket.java deleted file mode 100644 index 2fccb63e..00000000 --- a/src/main/java/net/jacobpeterson/alpaca/websocket/streaming/StreamingWebsocket.java +++ /dev/null @@ -1,257 +0,0 @@ -package net.jacobpeterson.alpaca.websocket.streaming; - -import com.google.common.collect.Iterables; -import com.google.gson.JsonArray; -import com.google.gson.JsonElement; -import com.google.gson.JsonObject; -import com.google.gson.JsonParser; -import com.google.gson.JsonPrimitive; -import net.jacobpeterson.alpaca.model.endpoint.streaming.StreamingMessage; -import net.jacobpeterson.alpaca.model.endpoint.streaming.authorization.AuthorizationData; -import net.jacobpeterson.alpaca.model.endpoint.streaming.authorization.AuthorizationMessage; -import net.jacobpeterson.alpaca.model.endpoint.streaming.enums.StreamingMessageType; -import net.jacobpeterson.alpaca.model.endpoint.streaming.listening.ListeningMessage; -import net.jacobpeterson.alpaca.model.endpoint.streaming.trade.TradeUpdateMessage; -import net.jacobpeterson.alpaca.model.properties.EndpointAPIType; -import net.jacobpeterson.alpaca.websocket.AlpacaWebsocket; -import okhttp3.HttpUrl; -import okhttp3.OkHttpClient; -import okhttp3.WebSocket; -import okio.ByteString; -import org.jetbrains.annotations.NotNull; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.util.Arrays; -import java.util.Collection; -import java.util.Collections; -import java.util.HashSet; -import java.util.List; -import java.util.Set; -import java.util.concurrent.TimeUnit; - -import static com.google.common.base.Preconditions.checkNotNull; -import static com.google.common.base.Preconditions.checkState; -import static com.google.common.base.Predicates.not; -import static net.jacobpeterson.alpaca.util.gson.GsonUtil.GSON; - -/** - * {@link StreamingWebsocket} is an {@link AlpacaWebsocket} implementation and provides the - * {@link StreamingWebsocketInterface} interface for - * Streaming - */ -public class StreamingWebsocket extends AlpacaWebsocket - implements StreamingWebsocketInterface { - - private static final Logger LOGGER = LoggerFactory.getLogger(StreamingWebsocket.class); - private static final String STREAM_ELEMENT_KEY = "stream"; - private static final List SUBSCRIBABLE_STREAMING_MESSAGE_TYPES = Collections.singletonList( - StreamingMessageType.TRADE_UPDATES); - - /** - * Creates a {@link HttpUrl} for {@link StreamingWebsocket} with the given alpacaSubdomain. - * - * @param endpointAPIType the {@link EndpointAPIType} - * - * @return a {@link HttpUrl} - */ - @SuppressWarnings("UnnecessaryDefault") - private static HttpUrl createWebsocketURL(EndpointAPIType endpointAPIType) { - return new HttpUrl.Builder() - .scheme("https") // HttpUrl.Builder doesn't recognize "wss" scheme, but "https" works fine - .host((switch (endpointAPIType) { - case LIVE -> "api"; - case PAPER -> "paper-api"; - default -> throw new UnsupportedOperationException(); - }) + ".alpaca.markets") - .addPathSegment("stream") - .build(); - } - - private final Set listenedStreamMessageTypes; - - /** - * Instantiates a new {@link StreamingWebsocket}. - * - * @param okHttpClient the {@link OkHttpClient} - * @param endpointAPIType the {@link EndpointAPIType} - * @param keyID the key ID - * @param secretKey the secret key - * @param oAuthToken the OAuth token - */ - public StreamingWebsocket(OkHttpClient okHttpClient, EndpointAPIType endpointAPIType, - String keyID, String secretKey, String oAuthToken) { - super(okHttpClient, createWebsocketURL(endpointAPIType), "Streaming", keyID, secretKey, oAuthToken); - - listenedStreamMessageTypes = new HashSet<>(); - } - - @Override - protected void cleanupState() { - super.cleanupState(); - - listenedStreamMessageTypes.clear(); - } - - @Override - protected void onConnection() { - sendAuthenticationMessage(); - } - - @Override - protected void onReconnection() { - sendAuthenticationMessage(); - if (waitForAuthorization(5, TimeUnit.SECONDS)) { - streams(Iterables.toArray(listenedStreamMessageTypes, StreamingMessageType.class)); - } - } - - @Override - protected void sendAuthenticationMessage() { - // Ensures that 'authenticationMessageFuture' exists - getAuthorizationFuture(); - - /* Authentication message format: - * { - * "action": "authenticate", - * "data": { - * "key_id": "{YOUR_API_KEY_ID}", - * "secret_key": "{YOUR_API_SECRET_KEY}" - * } - * } - */ - - JsonObject authObject = new JsonObject(); - authObject.addProperty("action", "authenticate"); - - JsonObject authData = new JsonObject(); - - if (useOAuth) { - authData.addProperty("oauth_token", oAuthToken); - } else { - authData.addProperty("key_id", keyID); - authData.addProperty("secret_key", secretKey); - } - - authObject.add("data", authData); - - LOGGER.info("{} websocket sending authentication message...", websocketName); - websocket.send(authObject.toString()); - } - - // This websocket uses binary frames and not text frames. - @Override - public void onMessage(@NotNull WebSocket webSocket, @NotNull ByteString byteString) { - final String message = byteString.utf8(); - LOGGER.trace("{}", message); - - JsonElement messageElement = JsonParser.parseString(message); - checkState(messageElement instanceof JsonObject, "Message must be a JsonObject! Received: %s", messageElement); - - JsonObject messageObject = messageElement.getAsJsonObject(); - JsonElement streamElement = messageObject.get(STREAM_ELEMENT_KEY); - checkState(streamElement instanceof JsonPrimitive, - "Message must contain %s element! Received: %s", STREAM_ELEMENT_KEY, messageElement); - - StreamingMessageType streamingMessageType = GSON.fromJson(streamElement, StreamingMessageType.class); - checkNotNull(streamingMessageType, "StreamingMessageType not found in message: %s", messageObject); - - StreamingMessage streamingMessage; - switch (streamingMessageType) { - case AUTHORIZATION: - streamingMessage = GSON.fromJson(messageObject, AuthorizationMessage.class); - - authenticated = isAuthorizationMessageSuccess((AuthorizationMessage) streamingMessage); - - if (!authenticated) { - LOGGER.error("{} websocket not authenticated! Received: {}.", websocketName, streamingMessage); - } else { - LOGGER.info("{} websocket authenticated.", websocketName); - } - - if (authenticationMessageFuture != null) { - authenticationMessageFuture.complete(authenticated); - } - break; - case LISTENING: - streamingMessage = GSON.fromJson(messageObject, ListeningMessage.class); - - // Remove all 'StreamingMessageType's that are no longer listened to and add new ones - List currentTypes = ((ListeningMessage) streamingMessage).getData().getStreams(); - currentTypes.stream() - .filter(not(listenedStreamMessageTypes::contains)) - .forEach(listenedStreamMessageTypes::remove); - listenedStreamMessageTypes.addAll(currentTypes); - break; - case TRADE_UPDATES: - streamingMessage = GSON.fromJson(messageObject, TradeUpdateMessage.class); - break; - default: - throw new UnsupportedOperationException(); - } - - if (listenedStreamMessageTypes.contains(streamingMessageType)) { - callListener(streamingMessageType, streamingMessage); - } - } - - /** - * Returns true if {@link AuthorizationMessage} states success. - * - * @param authorizationMessage the {@link AuthorizationMessage} - * - * @return a boolean - */ - private boolean isAuthorizationMessageSuccess(AuthorizationMessage authorizationMessage) { - AuthorizationData authorizationData = authorizationMessage.getData(); - return authorizationData.getStatus().equalsIgnoreCase("authorized") && - authorizationData.getAction().equalsIgnoreCase("authenticate"); - } - - @Override - public void streams(StreamingMessageType... streamingMessageTypes) { - if (streamingMessageTypes == null || streamingMessageTypes.length == 0) { - return; - } - - // Add all non-subscribable 'streamingMessageTypes' before connecting or sending websocket message - Arrays.stream(streamingMessageTypes) - .filter(not(SUBSCRIBABLE_STREAMING_MESSAGE_TYPES::contains)) - .forEach(listenedStreamMessageTypes::add); - - // Stream request format: - // { - // "action": "listen", - // "data": { - // "streams": ["trade_updates"] - // } - // } - - JsonObject requestObject = new JsonObject(); - requestObject.addProperty("action", "listen"); - - JsonArray streamsArray = new JsonArray(); - Arrays.stream(streamingMessageTypes) - .filter(SUBSCRIBABLE_STREAMING_MESSAGE_TYPES::contains) - .forEach((type) -> streamsArray.add(type.toString())); - - if (streamsArray.isEmpty()) { - return; - } else if (!isConnected()) { - throw new IllegalStateException("This websocket must be connected before subscribing to streams!"); - } - - JsonObject dataObject = new JsonObject(); - dataObject.add("streams", streamsArray); - - requestObject.add("data", dataObject); - - websocket.send(requestObject.toString()); - LOGGER.info("Requested streams: {}.", streamsArray); - } - - @Override - public Collection streams() { - return new HashSet<>(listenedStreamMessageTypes); - } -} diff --git a/src/main/java/net/jacobpeterson/alpaca/websocket/streaming/StreamingWebsocketInterface.java b/src/main/java/net/jacobpeterson/alpaca/websocket/streaming/StreamingWebsocketInterface.java deleted file mode 100644 index 44f0042e..00000000 --- a/src/main/java/net/jacobpeterson/alpaca/websocket/streaming/StreamingWebsocketInterface.java +++ /dev/null @@ -1,26 +0,0 @@ -package net.jacobpeterson.alpaca.websocket.streaming; - -import net.jacobpeterson.alpaca.model.endpoint.streaming.enums.StreamingMessageType; -import net.jacobpeterson.alpaca.websocket.AlpacaWebsocketInterface; - -import java.util.Collection; - -/** - * {@link StreamingWebsocketInterface} is an {@link AlpacaWebsocketInterface} for a {@link StreamingWebsocket}. - */ -public interface StreamingWebsocketInterface extends AlpacaWebsocketInterface { - - /** - * Sets the {@link StreamingMessageType}s for this stream to the given streamingMessageTypes. - * - * @param streamingMessageTypes the {@link StreamingMessageType}s - */ - void streams(StreamingMessageType... streamingMessageTypes); - - /** - * Gets all the currently subscribed {@link StreamingMessageType}s. - * - * @return a {@link Collection} of {@link StreamingMessageType} - */ - Collection streams(); -} diff --git a/src/main/resources/alpaca.default.properties b/src/main/resources/alpaca.default.properties deleted file mode 100644 index e3629268..00000000 --- a/src/main/resources/alpaca.default.properties +++ /dev/null @@ -1,2 +0,0 @@ -endpoint_api_type:paper -data_api_type:iex From a084b1b5ebfc4bc4557148611c859a62f0284c95 Mon Sep 17 00:00:00 2001 From: Jacob Peterson <14127217+Petersoj@users.noreply.github.com> Date: Sun, 3 Mar 2024 14:30:43 -0800 Subject: [PATCH 207/271] WIP implement Broker 'events' API SSE capability --- .gitignore | 2 - README.md | 27 +-- build.gradle | 126 ++++++------ gradle/wrapper/.gitignore | 1 - .../net/jacobpeterson/alpaca/AlpacaAPI.java | 76 ++++---- .../jacobpeterson/alpaca/AlpacaBrokerAPI.java | 19 +- .../alpaca/AlpacaMarketDataAPI.java | 3 +- .../alpaca/rest/broker/EventsApiSSE.java | 180 ++++++++++++++++++ ...> market_data_api_stream_source_type.json} | 2 +- .../alpaca/util/sse/SSEListener.java | 38 ++++ .../alpaca/util/sse/SSEListenerAdapter.java | 33 ++++ .../alpaca/util/sse/SSERequest.java | 36 ++++ 12 files changed, 416 insertions(+), 127 deletions(-) delete mode 100644 gradle/wrapper/.gitignore create mode 100644 src/main/java/net/jacobpeterson/alpaca/rest/broker/EventsApiSSE.java rename src/main/java/net/jacobpeterson/alpaca/util/apitype/{market_data_api_source_type.json => market_data_api_stream_source_type.json} (56%) create mode 100644 src/main/java/net/jacobpeterson/alpaca/util/sse/SSEListener.java create mode 100644 src/main/java/net/jacobpeterson/alpaca/util/sse/SSEListenerAdapter.java create mode 100644 src/main/java/net/jacobpeterson/alpaca/util/sse/SSERequest.java diff --git a/.gitignore b/.gitignore index 985d4c2c..12a5664a 100644 --- a/.gitignore +++ b/.gitignore @@ -4,5 +4,3 @@ build/ .settings .DS_Store .idea/ -*.properties -!*.default.properties diff --git a/README.md b/README.md index 94b7e833..c5ceae7f 100644 --- a/README.md +++ b/README.md @@ -7,7 +7,7 @@

# Overview -This library is a Java client implementation of the Alpaca API. Alpaca lets you trade with algorithms, connect with apps, and build services all with a commission-free trading API for stocks, crypto, and options. This library uses the [Alpaca OpenAPI Specifications](https://docs.alpaca.markets/v1.1/openapi) to generate REST API clients and implements the websocket streaming interface using [OkHttp](https://square.github.io/okhttp/). This library is community developed and if you have any questions, please ask them on [Github Discussions](https://github.com/Petersoj/alpaca-java/discussions), the [Alpaca Slack #dev-alpaca-java channel](https://alpaca.markets/slack), or on the [Alpaca Forums](https://forum.alpaca.markets/). +This library is a Java client implementation of the Alpaca API. Alpaca lets you trade with algorithms, connect with apps, and build services all with a commission-free trading API for stocks, crypto, and options. This library uses the [Alpaca OpenAPI Specifications](https://docs.alpaca.markets/v1.1/openapi) to generate clients for the REST API with the [OkHttp](https://square.github.io/okhttp/) library, but implements the websocket and SSE streaming interface using a custom implementation with [OkHttp](https://square.github.io/okhttp/) library. This library is community developed and if you have any questions, please ask them on [Github Discussions](https://github.com/Petersoj/alpaca-java/discussions), the [Alpaca Slack #dev-alpaca-java channel](https://alpaca.markets/slack), or on the [Alpaca Forums](https://forum.alpaca.markets/). Give this repository a star ⭐ if it helped you build a trading algorithm in Java! @@ -28,17 +28,7 @@ If you are using Maven as your build tool, add the following dependency to your ``` -Note that you don't have to use the Maven Central artifacts and instead can just install a clone of this project to your local Maven repository as shown in the [Building](#building) section. - -# Configuration -Creating an `alpaca.properties` file on the classpath (e.g. in `src/main/resources/alpaca.properties`) with the following format allows you to easily load properties using the `AlpacaAPI` default constructor: -``` -key_id = -secret_key = -endpoint_api_type = -data_api_type = -``` -The default values for `alpaca.properties` can be found [here](src/main/resources/alpaca.default.properties). +Note that you don't have to use the Maven Central artifacts. Instead, you can clone this repository, build this project, and install the artifacts to your local Maven repository as shown in the [Building](#building) section. # Logger For logging, this library uses [SLF4j](http://www.slf4j.org/) which serves as an interface for various logging frameworks. This enables you to use whatever logging framework you would like. However, if you do not add a logging framework as a dependency in your project, the console will output a message stating that SLF4j is defaulting to a no-operation (NOP) logger implementation. To enable logging, add a logging framework of your choice as a dependency to your project such as [Logback](http://logback.qos.ch/), [Log4j 2](http://logging.apache.org/log4j/2.x/index.html), [SLF4j-simple](http://www.slf4j.org/manual.html), or [Apache Commons Logging](https://commons.apache.org/proper/commons-logging/). @@ -76,11 +66,7 @@ AlpacaAPI alpacaAPI = AlpacaAPI.builder() .build(); ``` -Note that this library uses [OkHttp](https://square.github.io/okhttp/) as its HTTP client library which creates background threads to service requests. These threads persist even if the main thread exists so if you want to destroy these threads when you're done using [`AlpacaAPI`](src/main/java/net/jacobpeterson/alpaca/AlpacaAPI.java) so your program can exit without calling `System.exit()`, use the following snippet: -```java -alpacaAPI.getOkHttpClient().dispatcher().executorService().shutdown(); -alpacaAPI.getOkHttpClient().connectionPool().evictAll(); -``` +Note that this library uses [OkHttp](https://square.github.io/okhttp/) as its HTTP client library which creates background threads to service requests via a connection pool. These threads persist even if the main thread exits, so if you want to destroy these threads when you're done using [`AlpacaAPI`](src/main/java/net/jacobpeterson/alpaca/AlpacaAPI.java), call `alpacaAPI.closeOkHttpClient();`. See the [OkHttpClient Documentation](https://square.github.io/okhttp/4.x/okhttp/okhttp3/-ok-http-client/#shutdown-isnt-necessary) for more information. @@ -532,16 +518,15 @@ To build this project yourself, clone this repository and run: To install the built artifacts to your local Maven repository on your machine (the `~/.m2/` directory), run: ``` -./gradlew install +./gradlew publishToMavenLocal ``` # TODO - Implement Unit Testing for REST API and Websocket streaming (both live and mocked) -- Implement Broker API websockets # Contributing Contributions are welcome! -When creating a Pull Request, keep the following in mind: +Do the following before starting your Pull Request: 1. Create a new branch in your forked repository for your feature or bug fix instead of committing directly to the `master` branch in your fork. -2. Use the `dev` branch in this repository as the base branch in your Pull Request. +2. Use the `dev` branch as the base branch in your Pull Request. diff --git a/build.gradle b/build.gradle index b13ea565..1c8e27b1 100644 --- a/build.gradle +++ b/build.gradle @@ -48,6 +48,7 @@ dependencies { // OkHttp implementation group: "com.squareup.okhttp3", name: "okhttp", version: "5.0.0-alpha.12" + implementation group: "com.squareup.okhttp3", name: "okhttp-sse", version: "5.0.0-alpha.12" implementation group: "com.squareup.okhttp3", name: "logging-interceptor", version: "5.0.0-alpha.12" // Libraries used by the OpenAPI generated client libraries @@ -69,6 +70,9 @@ java { } javadocJar.dependsOn generatePOJOs sourcesJar.dependsOn generatePOJOs +jar.dependsOn compileJava +javadocJar.dependsOn compileJava +sourcesJar.dependsOn compileJava [compileJava, compileTestJava]*.options*.encoding = "UTF-8" @@ -93,19 +97,10 @@ final File specDownloadPath = new File(project.layout.buildDirectory.get().getAs final File generatedClientLibrariesPath = new File(project.layout.buildDirectory.get().getAsFile(), "/generated/openapi/") -final def generateOpenAPIClientsTasks = tasks.register("generateOpenAPIClients") { - doLast { - fixOpenAPIGeneratedClientIssues(specIDsOfFileNames, generatedClientLibrariesPath) - } -} -compileJava.dependsOn generateOpenAPIClientsTasks -jar.dependsOn generateOpenAPIClientsTasks -javadocJar.dependsOn generateOpenAPIClientsTasks -sourcesJar.dependsOn generateOpenAPIClientsTasks - def downloadOpenAPISpecFilesTask = tasks.register("downloadOpenAPISpecFiles") { configure { outputs.dir(specDownloadPath) + outputs.cacheIf { true } } doLast { @@ -131,6 +126,16 @@ def downloadOpenAPISpecFilesTask = tasks.register("downloadOpenAPISpecFiles") { } } +final def generateOpenAPIClientsTask = tasks.register("generateOpenAPIClients") { + it.onlyIf { + !downloadOpenAPISpecFilesTask.get().state.upToDate && + downloadOpenAPISpecFilesTask.get().state.failure == null && + !downloadOpenAPISpecFilesTask.get().state.skipped && + downloadOpenAPISpecFilesTask.get().state.executed + } +} +compileJava.dependsOn generateOpenAPIClientsTask + for (def specFileName : specIDsOfFileNames.keySet()) { final def specName = specFileName.replace(".json", "") final def inputSpecFile = new File(specDownloadPath, specFileName) @@ -151,16 +156,10 @@ for (def specFileName : specIDsOfFileNames.keySet()) { downloadOpenAPISpecFilesTask } - it.onlyIf { - !downloadOpenAPISpecFilesTask.get().state.upToDate && - downloadOpenAPISpecFilesTask.get().state.failure == null && - !downloadOpenAPISpecFilesTask.get().state.skipped && - downloadOpenAPISpecFilesTask.get().state.executed - } - configure { inputs.file(inputSpecFile) outputs.dir(outputDirectory) + outputs.cacheIf { true } } generatorName.set("java") @@ -186,52 +185,64 @@ for (def specFileName : specIDsOfFileNames.keySet()) { generateApiTests.set(false) generateApiDocumentation.set(false) } - generateOpenAPIClientsTasks.configure { + generateOpenAPIClientsTask.configure { dependsOn generateTask } } -static void fixOpenAPIGeneratedClientIssues(Map specIDsOfFileNames, File generatedClientLibrariesPath) { - // TODO remove these manual fixes once OpenAPI fixes these issues - - final def sourceFilesPath = "src/main/java/net/jacobpeterson/alpaca/openapi" - - // Fix broker spec generated client issues with discriminators - // on some enums: https://github.com/OpenAPITools/openapi-generator/issues/806 - final def brokerTransferDataJavaFile = new File(generatedClientLibrariesPath, - "broker/${sourceFilesPath}/broker/model/TransferData.java") - brokerTransferDataJavaFile.text = brokerTransferDataJavaFile.text - .replace("this.transferType = this.getClass().getSimpleName();", "") - final def brokerTransferResourceJavaFile = new File(generatedClientLibrariesPath, - "broker/${sourceFilesPath}/broker/model/TransferResource.java") - brokerTransferResourceJavaFile.text = brokerTransferResourceJavaFile.text - .replace("this.type = this.getClass().getSimpleName();", "") - - // Remove the JSON validation that the generated client models always run (there isn't an option to - // disable it for some reason) - specIDsOfFileNames.keySet().stream().map { it.replace(".json", "") }.forEach { - for (File modelFile : Paths.get(generatedClientLibrariesPath.getPath(), - it, sourceFilesPath, it.replace("-", ""), "model").toFile().listFiles()) { - modelFile.text = modelFile.text - .replaceAll("/\\*\\*\\n {2}\\* Validates the JSON Element and throws an exception if issues found" + - "[\\s\\S]*?\\*/", "") - .replace("public static void validateJsonElement(JsonElement jsonElement) throws IOException {", - "public static boolean validate = false;\n public static void " + - "validateJsonElement(JsonElement jsonElement) throws IOException {\n" + - " if (!validate) return;") - } +final def fixOpenAPIGeneratedClientIssuesTask = tasks.register("fixOpenAPIGeneratedClientIssues") { + it.onlyIf { + !generateOpenAPIClientsTask.get().state.upToDate && + generateOpenAPIClientsTask.get().state.failure == null && + !generateOpenAPIClientsTask.get().state.skipped && + generateOpenAPIClientsTask.get().state.executed } - // Remove the necessary authentication header logic from the generate client's ApiClient - specIDsOfFileNames.keySet().stream().map { it.replace(".json", "") }.forEach { - final def apiClientFile = Paths.get(generatedClientLibrariesPath.getPath(), it, sourceFilesPath, - it.replace("-", ""), "ApiClient.java").toFile() - apiClientFile.text = apiClientFile.text - .replaceAll("// Setup authentications \\(key: authentication name, value: authentication\\)" + - "[\\s\\S]*?authentications\\);", "") - .replaceAll("for \\(String authName : authNames\\)[\\s\\S]*?uri\\);\\n. {7}}", "") + doLast { + // TODO remove these manual fixes once OpenAPI fixes these issues + + final def sourceFilesPath = "src/main/java/net/jacobpeterson/alpaca/openapi" + + // Fix broker spec generated client issues with discriminators + // on some enums: https://github.com/OpenAPITools/openapi-generator/issues/806 + final def brokerTransferDataJavaFile = new File(generatedClientLibrariesPath, + "broker/${sourceFilesPath}/broker/model/TransferData.java") + brokerTransferDataJavaFile.text = brokerTransferDataJavaFile.text + .replace("this.transferType = this.getClass().getSimpleName();", "") + final def brokerTransferResourceJavaFile = new File(generatedClientLibrariesPath, + "broker/${sourceFilesPath}/broker/model/TransferResource.java") + brokerTransferResourceJavaFile.text = brokerTransferResourceJavaFile.text + .replace("this.type = this.getClass().getSimpleName();", "") + + // Remove the JSON validation that the generated client models always run (there isn't an option to + // disable it for some reason) + specIDsOfFileNames.keySet().stream().map { it.replace(".json", "") }.forEach { + for (File modelFile : Paths.get(generatedClientLibrariesPath.getPath(), + it, sourceFilesPath, it.replace("-", ""), "model").toFile().listFiles()) { + modelFile.text = modelFile.text + .replaceAll("/\\*\\*\\n {2}\\* Validates the JSON Element and throws an exception if issues " + + "found[\\s\\S]*?\\*/", "") + .replace("public static void validateJsonElement(JsonElement jsonElement) throws IOException {", + "public static boolean validate = false;\n public static void " + + "validateJsonElement(JsonElement jsonElement) throws IOException {\n" + + " if (!validate) return;") + } + } + + // Remove the necessary authentication header logic from the generate client's ApiClient + specIDsOfFileNames.keySet().stream().map { it.replace(".json", "") }.forEach { + final def apiClientFile = Paths.get(generatedClientLibrariesPath.getPath(), it, sourceFilesPath, + it.replace("-", ""), "ApiClient.java").toFile() + apiClientFile.text = apiClientFile.text + .replaceAll("// Setup authentications \\(key: authentication name, value: authentication\\)" + + "[\\s\\S]*?authentications\\);", "") + .replaceAll("for \\(String authName : authNames\\)[\\s\\S]*?uri\\);\\n. {7}}", "") + } } } +generateOpenAPIClientsTask.configure { + finalizedBy fixOpenAPIGeneratedClientIssuesTask +} // // END Alpaca OpenAPI Specification (OAS) client generation @@ -299,11 +310,6 @@ signing { sign publishing.publications.mavenJava } -// Used to prevent accidental publication of Alpaca keys -processResources { - exclude("alpaca.properties") -} - // // END Publishing // diff --git a/gradle/wrapper/.gitignore b/gradle/wrapper/.gitignore deleted file mode 100644 index 351625b1..00000000 --- a/gradle/wrapper/.gitignore +++ /dev/null @@ -1 +0,0 @@ -!gradle-wrapper.properties diff --git a/src/main/java/net/jacobpeterson/alpaca/AlpacaAPI.java b/src/main/java/net/jacobpeterson/alpaca/AlpacaAPI.java index 577324ec..6e6f2da3 100644 --- a/src/main/java/net/jacobpeterson/alpaca/AlpacaAPI.java +++ b/src/main/java/net/jacobpeterson/alpaca/AlpacaAPI.java @@ -1,7 +1,7 @@ package net.jacobpeterson.alpaca; import net.jacobpeterson.alpaca.model.util.apitype.BrokerAPIEndpointType; -import net.jacobpeterson.alpaca.model.util.apitype.MarketDataAPISourceType; +import net.jacobpeterson.alpaca.model.util.apitype.MarketDataAPIStreamSourceType; import net.jacobpeterson.alpaca.model.util.apitype.TraderAPIEndpointType; import okhttp3.OkHttpClient; import okhttp3.logging.HttpLoggingInterceptor; @@ -9,8 +9,9 @@ import org.slf4j.LoggerFactory; import static net.jacobpeterson.alpaca.model.util.apitype.BrokerAPIEndpointType.SANDBOX; -import static net.jacobpeterson.alpaca.model.util.apitype.MarketDataAPISourceType.IEX; +import static net.jacobpeterson.alpaca.model.util.apitype.MarketDataAPIStreamSourceType.IEX; import static net.jacobpeterson.alpaca.model.util.apitype.TraderAPIEndpointType.PAPER; +import static okhttp3.logging.HttpLoggingInterceptor.Level.BODY; /** * {@link AlpacaAPI} is the main class used to interface with the various Alpaca API endpoints. If you are using the @@ -30,7 +31,7 @@ public class AlpacaAPI { private final String traderSecretKey; private final String traderOAuthToken; private final TraderAPIEndpointType traderAPIEndpointType; - private final MarketDataAPISourceType marketDataAPISourceType; + private final MarketDataAPIStreamSourceType marketDataAPIStreamSourceType; private final String brokerAPIKey; private final String brokerAPISecret; private final BrokerAPIEndpointType brokerAPIEndpointType; @@ -44,14 +45,14 @@ public class AlpacaAPI { * Instantiates a new {@link AlpacaAPI}. Use this constructor if you are using the Trading or Market Data APIs for a * single Alpaca account. * - * @param traderKeyID the Trader key ID - * @param traderSecretKey the Trader secret key - * @param traderAPIEndpointType the {@link TraderAPIEndpointType} - * @param marketDataAPISourceType the {@link MarketDataAPISourceType} + * @param traderKeyID the Trader key ID + * @param traderSecretKey the Trader secret key + * @param traderAPIEndpointType the {@link TraderAPIEndpointType} + * @param marketDataAPIStreamSourceType the {@link MarketDataAPIStreamSourceType} */ public AlpacaAPI(String traderKeyID, String traderSecretKey, TraderAPIEndpointType traderAPIEndpointType, - MarketDataAPISourceType marketDataAPISourceType) { - this(traderKeyID, traderSecretKey, null, traderAPIEndpointType, marketDataAPISourceType, null, null, null, + MarketDataAPIStreamSourceType marketDataAPIStreamSourceType) { + this(traderKeyID, traderSecretKey, null, traderAPIEndpointType, marketDataAPIStreamSourceType, null, null, null, null); } @@ -59,16 +60,16 @@ public AlpacaAPI(String traderKeyID, String traderSecretKey, TraderAPIEndpointTy * Instantiates a new {@link AlpacaAPI}. Use this constructor if you are using the Trading or Market Data APIs for a * single Alpaca account and a custom {@link OkHttpClient} instance. * - * @param traderKeyID the Trader key ID - * @param traderSecretKey the Trader secret key - * @param traderAPIEndpointType the {@link TraderAPIEndpointType} - * @param marketDataAPISourceType the {@link MarketDataAPISourceType} - * @param okHttpClient an existing {@link OkHttpClient} or null to create a new default - * instance + * @param traderKeyID the Trader key ID + * @param traderSecretKey the Trader secret key + * @param traderAPIEndpointType the {@link TraderAPIEndpointType} + * @param marketDataAPIStreamSourceType the {@link MarketDataAPIStreamSourceType} + * @param okHttpClient an existing {@link OkHttpClient} or null to create a new + * default instance */ public AlpacaAPI(String traderKeyID, String traderSecretKey, TraderAPIEndpointType traderAPIEndpointType, - MarketDataAPISourceType marketDataAPISourceType, OkHttpClient okHttpClient) { - this(traderKeyID, traderSecretKey, null, traderAPIEndpointType, marketDataAPISourceType, null, null, null, + MarketDataAPIStreamSourceType marketDataAPIStreamSourceType, OkHttpClient okHttpClient) { + this(traderKeyID, traderSecretKey, null, traderAPIEndpointType, marketDataAPIStreamSourceType, null, null, null, okHttpClient); } @@ -125,27 +126,28 @@ public AlpacaAPI(String brokerAPIKey, String brokerAPISecret, BrokerAPIEndpointT /** * Instantiates a new {@link AlpacaAPI}. * - * @param traderKeyID the Trader key ID - * @param traderSecretKey the Trader secret key - * @param traderOAuthToken the Trader OAuth token - * @param traderAPIEndpointType the {@link TraderAPIEndpointType} - * @param marketDataAPISourceType the {@link MarketDataAPISourceType} - * @param brokerAPIKey the Broker API key - * @param brokerAPISecret the Broker API secret - * @param brokerAPIEndpointType the {@link BrokerAPIEndpointType} - * @param okHttpClient an existing {@link OkHttpClient} or null to create a new default - * instance + * @param traderKeyID the Trader key ID + * @param traderSecretKey the Trader secret key + * @param traderOAuthToken the Trader OAuth token + * @param traderAPIEndpointType the {@link TraderAPIEndpointType} + * @param marketDataAPIStreamSourceType the {@link MarketDataAPIStreamSourceType} + * @param brokerAPIKey the Broker API key + * @param brokerAPISecret the Broker API secret + * @param brokerAPIEndpointType the {@link BrokerAPIEndpointType} + * @param okHttpClient an existing {@link OkHttpClient} or null to create a new + * default instance */ public AlpacaAPI(String traderKeyID, String traderSecretKey, String traderOAuthToken, TraderAPIEndpointType traderAPIEndpointType, - MarketDataAPISourceType marketDataAPISourceType, + MarketDataAPIStreamSourceType marketDataAPIStreamSourceType, String brokerAPIKey, String brokerAPISecret, BrokerAPIEndpointType brokerAPIEndpointType, OkHttpClient okHttpClient) { this.traderKeyID = traderKeyID; this.traderSecretKey = traderSecretKey; this.traderOAuthToken = traderOAuthToken; this.traderAPIEndpointType = traderAPIEndpointType != null ? traderAPIEndpointType : PAPER; - this.marketDataAPISourceType = marketDataAPISourceType != null ? marketDataAPISourceType : IEX; + this.marketDataAPIStreamSourceType = marketDataAPIStreamSourceType != null ? + marketDataAPIStreamSourceType : IEX; this.brokerAPIKey = brokerAPIKey; this.brokerAPISecret = brokerAPISecret; this.brokerAPIEndpointType = brokerAPIEndpointType != null ? brokerAPIEndpointType : SANDBOX; @@ -154,13 +156,23 @@ public AlpacaAPI(String traderKeyID, String traderSecretKey, if (okHttpClient == null) { OkHttpClient.Builder clientBuilder = new OkHttpClient.Builder(); if (LOGGER.isDebugEnabled()) { - clientBuilder.addInterceptor(new HttpLoggingInterceptor(LOGGER::debug)); + final HttpLoggingInterceptor loggingInterceptor = new HttpLoggingInterceptor(LOGGER::debug); + loggingInterceptor.setLevel(BODY); + clientBuilder.addInterceptor(loggingInterceptor); } okHttpClient = clientBuilder.build(); } this.okHttpClient = okHttpClient; } + /** + * Closes the {@link OkHttpClient}. + */ + public void closeOkHttpClient() { + okHttpClient.dispatcher().executorService().shutdown(); + okHttpClient.connectionPool().evictAll(); + } + /** * Gets the {@link OkHttpClient}. * @@ -226,7 +238,7 @@ public static final class Builder { private String traderSecretKey; private String traderOAuthToken; private TraderAPIEndpointType traderAPIEndpointType; - private MarketDataAPISourceType marketDataAPISourceType; + private MarketDataAPIStreamSourceType marketDataAPISourceType; private String brokerAPIKey; private String brokerAPISecret; private BrokerAPIEndpointType brokerAPIEndpointType; @@ -254,7 +266,7 @@ public Builder withTraderAPIEndpointType(TraderAPIEndpointType traderAPIEndpoint return this; } - public Builder withMarketDataAPISourceType(MarketDataAPISourceType marketDataAPISourceType) { + public Builder withMarketDataAPIStreamSourceType(MarketDataAPIStreamSourceType marketDataAPISourceType) { this.marketDataAPISourceType = marketDataAPISourceType; return this; } diff --git a/src/main/java/net/jacobpeterson/alpaca/AlpacaBrokerAPI.java b/src/main/java/net/jacobpeterson/alpaca/AlpacaBrokerAPI.java index 7d9851b5..a4aa0c56 100644 --- a/src/main/java/net/jacobpeterson/alpaca/AlpacaBrokerAPI.java +++ b/src/main/java/net/jacobpeterson/alpaca/AlpacaBrokerAPI.java @@ -9,7 +9,6 @@ import net.jacobpeterson.alpaca.openapi.broker.api.CorporateActionsApi; import net.jacobpeterson.alpaca.openapi.broker.api.CountryInfoApi; import net.jacobpeterson.alpaca.openapi.broker.api.DocumentsApi; -import net.jacobpeterson.alpaca.openapi.broker.api.EventsApi; import net.jacobpeterson.alpaca.openapi.broker.api.FundingApi; import net.jacobpeterson.alpaca.openapi.broker.api.FundingWalletsApi; import net.jacobpeterson.alpaca.openapi.broker.api.JournalsApi; @@ -20,6 +19,7 @@ import net.jacobpeterson.alpaca.openapi.broker.api.ReportingApi; import net.jacobpeterson.alpaca.openapi.broker.api.TradingApi; import net.jacobpeterson.alpaca.openapi.broker.api.WatchlistApi; +import net.jacobpeterson.alpaca.rest.broker.EventsApiSSE; import okhttp3.OkHttpClient; import static com.google.common.base.Preconditions.checkNotNull; @@ -39,7 +39,7 @@ public class AlpacaBrokerAPI { private CorporateActionsApi corporateActions; private CountryInfoApi countryInfo; private DocumentsApi documents; - private EventsApi events; + private EventsApiSSE events; private FundingApi funding; private FundingWalletsApi fundingWallets; private JournalsApi journals; @@ -60,8 +60,8 @@ public class AlpacaBrokerAPI { * @param okHttpClient an existing {@link OkHttpClient} or null to create a new default * instance */ - @SuppressWarnings("UnnecessaryDefault") - AlpacaBrokerAPI(String brokerAPIKey, String brokerAPISecret, BrokerAPIEndpointType brokerAPIEndpointType, + @SuppressWarnings("UnnecessaryDefault") AlpacaBrokerAPI(String brokerAPIKey, String brokerAPISecret, + BrokerAPIEndpointType brokerAPIEndpointType, OkHttpClient okHttpClient) { checkNotNull(brokerAPIKey); checkNotNull(brokerAPISecret); @@ -74,7 +74,8 @@ public class AlpacaBrokerAPI { case PRODUCTION -> 1; default -> throw new UnsupportedOperationException(); }); - apiClient.addDefaultHeader("Authorization", createBrokerAPIAuthKey(brokerAPIKey, brokerAPISecret)); + apiClient.addDefaultHeader("Authorization", "Basic " + + createBrokerAPIAuthKey(brokerAPIKey, brokerAPISecret)); } /** @@ -171,13 +172,13 @@ public synchronized DocumentsApi documents() { } /** - * Gets the {@link EventsApi}. Lazily instantiated. + * Gets the {@link EventsApiSSE}. Lazily instantiated. * - * @return the {@link EventsApi} + * @return the {@link EventsApiSSE} */ - public synchronized EventsApi events() { + public synchronized EventsApiSSE events() { if (events == null) { - events = new EventsApi(apiClient); + events = new EventsApiSSE(apiClient); } return events; } diff --git a/src/main/java/net/jacobpeterson/alpaca/AlpacaMarketDataAPI.java b/src/main/java/net/jacobpeterson/alpaca/AlpacaMarketDataAPI.java index 1538581a..65682a4e 100644 --- a/src/main/java/net/jacobpeterson/alpaca/AlpacaMarketDataAPI.java +++ b/src/main/java/net/jacobpeterson/alpaca/AlpacaMarketDataAPI.java @@ -53,7 +53,8 @@ public class AlpacaMarketDataAPI { apiClient.addDefaultHeader("APCA-API-KEY-ID", traderKeyID); apiClient.addDefaultHeader("APCA-API-SECRET-KEY", traderSecretKey); } else { - apiClient.addDefaultHeader("Authorization", createBrokerAPIAuthKey(brokerAPIKey, brokerAPISecret)); + apiClient.addDefaultHeader("Authorization", "Basic " + + createBrokerAPIAuthKey(brokerAPIKey, brokerAPISecret)); } } diff --git a/src/main/java/net/jacobpeterson/alpaca/rest/broker/EventsApiSSE.java b/src/main/java/net/jacobpeterson/alpaca/rest/broker/EventsApiSSE.java new file mode 100644 index 00000000..da0ad98d --- /dev/null +++ b/src/main/java/net/jacobpeterson/alpaca/rest/broker/EventsApiSSE.java @@ -0,0 +1,180 @@ +package net.jacobpeterson.alpaca.rest.broker; + +import com.google.gson.reflect.TypeToken; +import net.jacobpeterson.alpaca.openapi.broker.ApiClient; +import net.jacobpeterson.alpaca.openapi.broker.ApiException; +import net.jacobpeterson.alpaca.openapi.broker.api.EventsApi; +import net.jacobpeterson.alpaca.openapi.broker.model.AccountStatusEvent; +import net.jacobpeterson.alpaca.openapi.broker.model.JournalStatusEvent; +import net.jacobpeterson.alpaca.openapi.broker.model.SubscribeToAdminActionSSE200ResponseInner; +import net.jacobpeterson.alpaca.openapi.broker.model.TradeUpdateEvent; +import net.jacobpeterson.alpaca.openapi.broker.model.TradeUpdateEventV2; +import net.jacobpeterson.alpaca.openapi.broker.model.TransferStatusEvent; +import net.jacobpeterson.alpaca.util.sse.SSEListener; +import net.jacobpeterson.alpaca.util.sse.SSERequest; +import okhttp3.Request; +import okhttp3.Response; +import okhttp3.sse.EventSource; +import okhttp3.sse.EventSourceListener; +import okhttp3.sse.EventSources; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.lang.reflect.Type; +import java.time.LocalDate; +import java.time.OffsetDateTime; +import java.util.List; + +import static java.util.concurrent.TimeUnit.SECONDS; +import static net.jacobpeterson.alpaca.openapi.broker.JSON.getGson; + +/** + * {@link EventsApiSSE} add SSE support to {@link EventsApi}. + */ +public class EventsApiSSE { + + private final EventsApi eventsAPI; + private final EventSource.Factory eventSourceFactory; + + /** + * Instantiates a new {@link EventsApiSSE}. + * + * @param apiClient the api client + */ + public EventsApiSSE(ApiClient apiClient) { + eventsAPI = new EventsApi(apiClient); + eventSourceFactory = EventSources.createFactory(apiClient.getHttpClient().newBuilder() // Shallow clone + .readTimeout(0, SECONDS) + .writeTimeout(0, SECONDS) + .build()); + } + + /** + * See {@link EventsApi#getV1EventsNta(String, String, String, Integer, Integer, String, String, Boolean)}. + * + * @return a {@link SSERequest} + */ + public SSERequest subscribeToV1EventsNta(String id, String since, String until, Integer sinceId, Integer untilId, + String sinceUlid, String untilUlid, Boolean includePreprocessing, + SSEListener> sseListener) throws ApiException { // TODO OpenAPI response type is broken + final Request request = eventsAPI.getV1EventsNtaCall(id, since, until, sinceId, untilId, sinceUlid, untilUlid, + includePreprocessing, null).request(); + return new SSERequest(eventSourceFactory.newEventSource(request, createEventSourceListener(sseListener, + new TypeToken>() {}.getType()))); // TODO OpenAPI response type is broken + } + + /** + * See {@link EventsApi#subscribeToAdminActionSSE(OffsetDateTime, OffsetDateTime, String, String)}. + * + * @return a {@link SSERequest} + */ + public SSERequest subscribeToAdminAction(OffsetDateTime since, OffsetDateTime until, String sinceId, + String untilId, SSEListener> sseListener) throws ApiException { + final Request request = eventsAPI.subscribeToAdminActionSSECall(since, until, sinceId, untilId, null).request(); + return new SSERequest(eventSourceFactory.newEventSource(request, createEventSourceListener(sseListener, + new TypeToken>() {}.getType()))); + } + + /** + * See + * {@link EventsApi#subscribeToJournalStatusSSE(OffsetDateTime, OffsetDateTime, Integer, Integer, String, String, + * String)}. + * + * @return a {@link SSERequest} + */ + public SSERequest subscribeToJournalStatus(OffsetDateTime since, OffsetDateTime until, Integer sinceId, + Integer untilId, String sinceUlid, String untilUlid, String id, + SSEListener> sseListener) throws ApiException { + final Request request = eventsAPI.subscribeToJournalStatusSSECall(since, until, sinceId, untilId, sinceUlid, + untilUlid, id, null).request(); + return new SSERequest(eventSourceFactory.newEventSource(request, createEventSourceListener(sseListener, + new TypeToken>() {}.getType()))); + } + + /** + * See {@link EventsApi#subscribeToTradeSSE(OffsetDateTime, OffsetDateTime, Integer, Integer, String, String)}. + * + * @return a {@link SSERequest} + */ + public SSERequest subscribeToTrade(OffsetDateTime since, OffsetDateTime until, Integer sinceId, + Integer untilId, String sinceUlid, String untilUlid, SSEListener> sseListener) + throws ApiException { + final Request request = eventsAPI.subscribeToTradeSSECall(since, until, sinceId, untilId, sinceUlid, untilUlid, + null).request(); + return new SSERequest(eventSourceFactory.newEventSource(request, createEventSourceListener(sseListener, + new TypeToken>() {}.getType()))); + } + + /** + * See {@link EventsApi#subscribeToTradeV2SSE(OffsetDateTime, OffsetDateTime, String, String)}. + * + * @return a {@link SSERequest} + */ + public SSERequest subscribeToTradeV2(OffsetDateTime since, OffsetDateTime until, String sinceId, + String untilId, SSEListener> sseListener) throws ApiException { + final Request request = eventsAPI.subscribeToTradeV2SSECall(since, until, sinceId, untilId, null).request(); + return new SSERequest(eventSourceFactory.newEventSource(request, createEventSourceListener(sseListener, + new TypeToken>() {}.getType()))); + } + + /** + * See + * {@link EventsApi#subscribeToTransferStatusSSE(OffsetDateTime, OffsetDateTime, Integer, Integer, String, + * String)}. + * + * @return a {@link SSERequest} + */ + public SSERequest subscribeToTransferStatus(OffsetDateTime since, OffsetDateTime until, Integer sinceId, + Integer untilId, String sinceUlid, String untilUlid, SSEListener> sseListener) + throws ApiException { + final Request request = eventsAPI.subscribeToTransferStatusSSECall(since, until, sinceId, untilId, sinceUlid, + untilUlid, null).request(); + return new SSERequest(eventSourceFactory.newEventSource(request, createEventSourceListener(sseListener, + new TypeToken>() {}.getType()))); + } + + /** + * See + * {@link EventsApi#suscribeToAccountStatusSSE(LocalDate, LocalDate, Integer, Integer, String, String, String)}. + * + * @return a {@link SSERequest} + */ + public SSERequest subscribeToAccountStatus(LocalDate since, LocalDate until, Integer sinceId, Integer untilId, + String sinceUlid, String untilUlid, String id, SSEListener> sseListener) + throws ApiException { + final Request request = eventsAPI.suscribeToAccountStatusSSECall(since, until, sinceId, untilId, sinceUlid, + untilUlid, id, null).request(); + return new SSERequest(eventSourceFactory.newEventSource(request, createEventSourceListener(sseListener, + new TypeToken>() {}.getType()))); + } + + private EventSourceListener createEventSourceListener(SSEListener sseListener, Type responseTypeToken) { + return new EventSourceListener() { + @Override + public void onClosed(@NotNull EventSource eventSource) { + sseListener.onClose(); + } + + @Override + public void onEvent(@NotNull EventSource eventSource, @Nullable String id, @Nullable String type, + @NotNull String data) { + sseListener.onEvent(getGson().fromJson(data, responseTypeToken)); + } + + @Override + public void onFailure(@NotNull EventSource eventSource, @Nullable Throwable throwable, + @Nullable Response response) { + if (throwable != null && throwable.getMessage().equals("canceled")) { + sseListener.onClose(); + return; + } + sseListener.onFailure(throwable, response); + } + + @Override + public void onOpen(@NotNull EventSource eventSource, @NotNull Response response) { + sseListener.onOpen(); + } + }; + } +} diff --git a/src/main/java/net/jacobpeterson/alpaca/util/apitype/market_data_api_source_type.json b/src/main/java/net/jacobpeterson/alpaca/util/apitype/market_data_api_stream_source_type.json similarity index 56% rename from src/main/java/net/jacobpeterson/alpaca/util/apitype/market_data_api_source_type.json rename to src/main/java/net/jacobpeterson/alpaca/util/apitype/market_data_api_stream_source_type.json index e0f74cdc..97b87531 100644 --- a/src/main/java/net/jacobpeterson/alpaca/util/apitype/market_data_api_source_type.json +++ b/src/main/java/net/jacobpeterson/alpaca/util/apitype/market_data_api_stream_source_type.json @@ -1,6 +1,6 @@ { "type": "string", - "javaName": "MarketDataAPISourceType", + "javaName": "MarketDataAPIStreamSourceType", "enum": [ "iex", "sip" diff --git a/src/main/java/net/jacobpeterson/alpaca/util/sse/SSEListener.java b/src/main/java/net/jacobpeterson/alpaca/util/sse/SSEListener.java new file mode 100644 index 00000000..9ca01253 --- /dev/null +++ b/src/main/java/net/jacobpeterson/alpaca/util/sse/SSEListener.java @@ -0,0 +1,38 @@ +package net.jacobpeterson.alpaca.util.sse; + +import okhttp3.Response; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +/** + * {@link SSEListener} is a listener interface for server-sent events (SSE). + * + * @param the data type + */ +public interface SSEListener { + + /** + * Called on SSE request open. + */ + void onOpen(); + + /** + * Called on SSE request close. + */ + void onClose(); + + /** + * Called on SSE request failure. + * + * @param throwable the {@link Throwable} + * @param response the {@link Response} + */ + void onFailure(@Nullable Throwable throwable, @Nullable Response response); + + /** + * Called on SSE request data receive event. + * + * @param data the {@link T} data + */ + void onEvent(@NotNull T data); +} diff --git a/src/main/java/net/jacobpeterson/alpaca/util/sse/SSEListenerAdapter.java b/src/main/java/net/jacobpeterson/alpaca/util/sse/SSEListenerAdapter.java new file mode 100644 index 00000000..492e9e0f --- /dev/null +++ b/src/main/java/net/jacobpeterson/alpaca/util/sse/SSEListenerAdapter.java @@ -0,0 +1,33 @@ +package net.jacobpeterson.alpaca.util.sse; + +import okhttp3.Response; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * {@link SSEListenerAdapter} and adapter for {@link SSEListener}. + * + * @param the data type + */ +public class SSEListenerAdapter implements SSEListener { + + private static final Logger LOGGER = LoggerFactory.getLogger(SSEListenerAdapter.class); + + public void onOpen() { + LOGGER.info("SSE connection opened."); + } + + public void onClose() { + LOGGER.info("SSE connection closed."); + } + + public void onFailure(@Nullable Throwable throwable, @Nullable Response response) { + LOGGER.error("SSE connection failed! {}", response, throwable); + } + + public void onEvent(@NotNull T data) { + LOGGER.info("SSE event received: {}", data); + } +} diff --git a/src/main/java/net/jacobpeterson/alpaca/util/sse/SSERequest.java b/src/main/java/net/jacobpeterson/alpaca/util/sse/SSERequest.java new file mode 100644 index 00000000..65e5b96e --- /dev/null +++ b/src/main/java/net/jacobpeterson/alpaca/util/sse/SSERequest.java @@ -0,0 +1,36 @@ +package net.jacobpeterson.alpaca.util.sse; + +import okhttp3.sse.EventSource; + +/** + * {@link SSERequest} is a {@link EventSource} wrapper class. + */ +public class SSERequest { + + private final EventSource eventSource; + + /** + * Instantiates a new {@link SSERequest}. + * + * @param eventSource the {@link EventSource} + */ + public SSERequest(EventSource eventSource) { + this.eventSource = eventSource; + } + + /** + * Closes the internal {@link EventSource}. + */ + public void close() { + eventSource.cancel(); + } + + /** + * Gets the internal {@link EventSource}. + * + * @return the {@link EventSource} + */ + public EventSource getInternalEventSource() { + return eventSource; + } +} From c37268aa6d86390335c54967d6425a475f0368ed Mon Sep 17 00:00:00 2001 From: Jacob Peterson <14127217+Petersoj@users.noreply.github.com> Date: Mon, 4 Mar 2024 10:13:20 -0800 Subject: [PATCH 208/271] WIP add updated trades streaming functionality --- README.md | 473 +----------------- build.gradle | 8 + .../net/jacobpeterson/alpaca/AlpacaAPI.java | 16 + .../jacobpeterson/alpaca/AlpacaBrokerAPI.java | 4 +- .../authorization/authorization_message.json | 13 - .../enums/streaming_message_type.json | 9 - .../streaming/listening/listening_data.json | 10 - .../listening/listening_message.json | 13 - .../endpoint/streaming/streaming_message.json | 10 - .../streaming/trade/trade_update_message.json | 13 - .../alpaca/rest/broker/EventsApiSSE.java | 37 +- .../alpaca/util/sse/SSEListener.java | 14 +- .../alpaca/util/sse/SSEListenerAdapter.java | 8 +- .../alpaca/websocket/AlpacaWebsocket.java | 287 +++++++++++ .../websocket/AlpacaWebsocketInterface.java | 82 +++ .../AlpacaWebsocketMessageListener.java | 20 + .../AlpacaWebsocketStateListener.java | 33 ++ .../websocket/trades/TradesListener.java | 11 + .../trades/TradesListenerAdapter.java | 19 + .../websocket/trades/TradesWebsocket.java | 219 ++++++++ .../trades/TradesWebsocketInterface.java | 26 + .../authorization/authorization_data.json | 1 - .../authorization/authorization_message.json | 12 + .../model/listening/listening_data.json | 9 + .../model/listening/listening_message.json | 12 + .../trades/model/trades_stream_message.json | 9 + .../model/trades_stream_message_type.json | 8 + .../model/tradeupdate}/trade_update.json | 22 +- .../tradeupdate}/trade_update_event.json | 7 +- .../tradeupdate/trade_update_message.json | 12 + 30 files changed, 845 insertions(+), 572 deletions(-) delete mode 100644 src/main/java/net/jacobpeterson/alpaca/endpoint/streaming/authorization/authorization_message.json delete mode 100644 src/main/java/net/jacobpeterson/alpaca/endpoint/streaming/enums/streaming_message_type.json delete mode 100644 src/main/java/net/jacobpeterson/alpaca/endpoint/streaming/listening/listening_data.json delete mode 100644 src/main/java/net/jacobpeterson/alpaca/endpoint/streaming/listening/listening_message.json delete mode 100644 src/main/java/net/jacobpeterson/alpaca/endpoint/streaming/streaming_message.json delete mode 100644 src/main/java/net/jacobpeterson/alpaca/endpoint/streaming/trade/trade_update_message.json create mode 100644 src/main/java/net/jacobpeterson/alpaca/websocket/AlpacaWebsocket.java create mode 100644 src/main/java/net/jacobpeterson/alpaca/websocket/AlpacaWebsocketInterface.java create mode 100644 src/main/java/net/jacobpeterson/alpaca/websocket/AlpacaWebsocketMessageListener.java create mode 100644 src/main/java/net/jacobpeterson/alpaca/websocket/AlpacaWebsocketStateListener.java create mode 100644 src/main/java/net/jacobpeterson/alpaca/websocket/trades/TradesListener.java create mode 100644 src/main/java/net/jacobpeterson/alpaca/websocket/trades/TradesListenerAdapter.java create mode 100644 src/main/java/net/jacobpeterson/alpaca/websocket/trades/TradesWebsocket.java create mode 100644 src/main/java/net/jacobpeterson/alpaca/websocket/trades/TradesWebsocketInterface.java rename src/main/java/net/jacobpeterson/alpaca/{endpoint/streaming => websocket/trades/model}/authorization/authorization_data.json (69%) create mode 100644 src/main/java/net/jacobpeterson/alpaca/websocket/trades/model/authorization/authorization_message.json create mode 100644 src/main/java/net/jacobpeterson/alpaca/websocket/trades/model/listening/listening_data.json create mode 100644 src/main/java/net/jacobpeterson/alpaca/websocket/trades/model/listening/listening_message.json create mode 100644 src/main/java/net/jacobpeterson/alpaca/websocket/trades/model/trades_stream_message.json create mode 100644 src/main/java/net/jacobpeterson/alpaca/websocket/trades/model/trades_stream_message_type.json rename src/main/java/net/jacobpeterson/alpaca/{endpoint/streaming/trade => websocket/trades/model/tradeupdate}/trade_update.json (52%) rename src/main/java/net/jacobpeterson/alpaca/{endpoint/streaming/trade/enums => websocket/trades/model/tradeupdate}/trade_update_event.json (83%) create mode 100644 src/main/java/net/jacobpeterson/alpaca/websocket/trades/model/tradeupdate/trade_update_message.json diff --git a/README.md b/README.md index c5ceae7f..afb356b9 100644 --- a/README.md +++ b/README.md @@ -1,13 +1,13 @@

Logo

GitHub Repository - Maven Central - Javadocs + Maven Central + Javadocs GitHub

# Overview -This library is a Java client implementation of the Alpaca API. Alpaca lets you trade with algorithms, connect with apps, and build services all with a commission-free trading API for stocks, crypto, and options. This library uses the [Alpaca OpenAPI Specifications](https://docs.alpaca.markets/v1.1/openapi) to generate clients for the REST API with the [OkHttp](https://square.github.io/okhttp/) library, but implements the websocket and SSE streaming interface using a custom implementation with [OkHttp](https://square.github.io/okhttp/) library. This library is community developed and if you have any questions, please ask them on [Github Discussions](https://github.com/Petersoj/alpaca-java/discussions), the [Alpaca Slack #dev-alpaca-java channel](https://alpaca.markets/slack), or on the [Alpaca Forums](https://forum.alpaca.markets/). +This library is a Java client implementation of the Alpaca API. Alpaca lets you trade with algorithms, connect with apps, and build services all with a commission-free trading API for stocks, crypto, and options. This library uses the [Alpaca OpenAPI Specifications](https://docs.alpaca.markets/v1.1/openapi) to generate clients for the REST API with the [OkHttp](https://square.github.io/okhttp/) library, but implements the websocket and SSE streaming interface using a custom implementation with the [OkHttp](https://square.github.io/okhttp/) library. This library is community developed and if you have any questions, please ask them on [Github Discussions](https://github.com/Petersoj/alpaca-java/discussions), the [Alpaca Slack #dev-alpaca-java channel](https://alpaca.markets/slack), or on the [Alpaca Forums](https://forum.alpaca.markets/). Give this repository a star ⭐ if it helped you build a trading algorithm in Java! @@ -34,480 +34,33 @@ Note that you don't have to use the Maven Central artifacts. Instead, you can cl For logging, this library uses [SLF4j](http://www.slf4j.org/) which serves as an interface for various logging frameworks. This enables you to use whatever logging framework you would like. However, if you do not add a logging framework as a dependency in your project, the console will output a message stating that SLF4j is defaulting to a no-operation (NOP) logger implementation. To enable logging, add a logging framework of your choice as a dependency to your project such as [Logback](http://logback.qos.ch/), [Log4j 2](http://logging.apache.org/log4j/2.x/index.html), [SLF4j-simple](http://www.slf4j.org/manual.html), or [Apache Commons Logging](https://commons.apache.org/proper/commons-logging/). # Examples -Note that the examples below are not exhaustive. Refer to the [Javadoc](https://javadoc.io/doc/net.jacobpeterson/alpaca-java) for all classes and method signatures. +Note that the examples below are not exhaustive. Refer to the [Javadoc](https://javadoc.io/doc/net.jacobpeterson.alpaca/alpaca-java) for all classes and method signatures. ## [`AlpacaAPI`](src/main/java/net/jacobpeterson/alpaca/AlpacaAPI.java) -The [`AlpacaAPI`](src/main/java/net/jacobpeterson/alpaca/AlpacaAPI.java) class contains several instances of various [`AlpacaEndpoints`](src/main/java/net/jacobpeterson/alpaca/rest/endpoint/AlpacaEndpoint.java) and [`AlpacaWebsockets`](src/main/java/net/jacobpeterson/alpaca/websocket/AlpacaWebsocket.java) to interface with Alpaca. You will generally only need one instance of this class in your application. Note that many methods inside the various [`AlpacaEndpoints`](src/main/java/net/jacobpeterson/alpaca/rest/endpoint/AlpacaEndpoint.java) allow `null` to be passed in as a parameter if it is optional. +[`AlpacaAPI`](src/main/java/net/jacobpeterson/alpaca/AlpacaAPI.java) is the main class used to interface with the various Alpaca API endpoints. If you are using the Trading or Market Data APIs for a single Alpaca account or if you are using the Broker API, you will generally only need one instance of this class. However, if you are using the Trading API with OAuth to act on behalf of an Alpaca account, this class is optimized so that it can be instantiated quickly, especially when an existing `OkHttpClient` is given in the constructor. Additionally, all API endpoint instances are instantiated lazily. This class is thread-safe. -The Alpaca API specification is located [here](https://docs.alpaca.markets/api-documentation/api-v2/) and the [`AlpacaAPI`](src/main/java/net/jacobpeterson/alpaca/AlpacaAPI.java) Javadoc is located [here](https://javadoc.io/doc/net.jacobpeterson/alpaca-java/latest/net/jacobpeterson/alpaca/AlpacaAPI.html). +The Alpaca API documentation is located [here](https://docs.alpaca.markets/) and the [`AlpacaAPI`](src/main/java/net/jacobpeterson/alpaca/AlpacaAPI.java) Javadoc is located [here](https://javadoc.io/doc/net.jacobpeterson.alpaca/alpaca-java/latest/net/jacobpeterson/alpaca/AlpacaAPI.html). Example usage: ```java -// This constructor uses the 'alpaca.properties' file on the classpath for configuration -AlpacaAPI alpacaAPI = new AlpacaAPI(); - -// This constructor passes in a 'keyID' and 'secretKey' and uses the endpoint API type and data API -// type defined in the 'alpaca.properties' file (which default to 'paper' and 'iex' respectively) -String keyID = ""; -String secretKey = ""; -AlpacaAPI alpacaAPI = new AlpacaAPI(keyID, secretKey); - -// This constructor passes in a 'keyID' and 'secretKey' and uses the passed in endpoint API type -// and data API type (which are 'LIVE' and 'SIP' respectively in this example) -AlpacaAPI alpacaAPI = new AlpacaAPI(keyID, secretKey, EndpointAPIType.LIVE, DataAPIType.SIP); - -// This constructor is for OAuth tokens -String oAuthToken = ""; -AlpacaAPI alpacaAPI = new AlpacaAPI(oAuthToken); - -// This approach uses a builder pattern. Attributes not provided explicitly are retrieved from 'alpaca.properties' file -AlpacaAPI alpacaAPI = AlpacaAPI.builder() - .withEndpointAPIType(EndpointAPIType.PAPER) - .build(); +// TODO ``` Note that this library uses [OkHttp](https://square.github.io/okhttp/) as its HTTP client library which creates background threads to service requests via a connection pool. These threads persist even if the main thread exits, so if you want to destroy these threads when you're done using [`AlpacaAPI`](src/main/java/net/jacobpeterson/alpaca/AlpacaAPI.java), call `alpacaAPI.closeOkHttpClient();`. -See the [OkHttpClient Documentation](https://square.github.io/okhttp/4.x/okhttp/okhttp3/-ok-http-client/#shutdown-isnt-necessary) for more information. - -## [`AlpacaClientException`](src/main/java/net/jacobpeterson/alpaca/rest/AlpacaClientException.java) -[`AlpacaClientException`](src/main/java/net/jacobpeterson/alpaca/rest/AlpacaClientException.java) is thrown anytime an exception occurs when using various [`AlpacaEndpoints`](src/main/java/net/jacobpeterson/alpaca/rest/endpoint/AlpacaEndpoint.java). It should be caught and handled within your trading algorithm application accordingly. - -## [`AccountEndpoint`](src/main/java/net/jacobpeterson/alpaca/rest/endpoint/account/AccountEndpoint.java) -The Account API serves important information related to an account, including account status, funds available for trade, funds available for withdrawal, and various flags relevant to an account's ability to trade. - -Example usage: +## Trading API ```java -try { - // Get 'Account' information and print it out - Account account = alpacaAPI.account().get(); - System.out.println(account); -} catch (AlpacaClientException exception) { - exception.printStackTrace(); -} -``` - -## [`CryptoMarketDataEndpoint`](src/main/java/net/jacobpeterson/alpaca/rest/endpoint/marketdata/crypto/CryptoMarketDataEndpoint.java) -Alpaca provides cryptocurrency data from multiple venues/exchanges, namely: Coinbase, ErisX, and FTX. - -Example usage: -```java -try { - // Get BTCUSD 50 one-hour bars starting on 12/18/2021 from Coinbase and print them out - CryptoBarsResponse btcBarsResponse = alpacaAPI.cryptoMarketData().getBars( - "BTCUSD", - Arrays.asList(Exchange.COINBASE), - ZonedDateTime.of(2021, 12, 18, 0, 0, 0, 0, ZoneId.of("America/New_York")), - 50, - null, - 1, - BarTimePeriod.HOUR); - btcBarsResponse.getBars().forEach(System.out::println); - - // Get the Best Bid and Offer across multiple exchanges (XBBO) and print it out - XbboResponse etcXBBO = alpacaAPI.cryptoMarketData().getXBBO( - "ETHUSD", - null); - System.out.println(etcXBBO); -} catch (AlpacaClientException exception) { - exception.printStackTrace(); -} +// TODO ``` -## [`StockMarketDataEndpoint`](src/main/java/net/jacobpeterson/alpaca/rest/endpoint/marketdata/stock/StockMarketDataEndpoint.java) -The Data API v2 provides market data through an easy-to-use API for historical stock market data. - -Example usage: +## Market Data API ```java -try { - // Get AAPL one hour, split-adjusted bars from 7/6/2021 market open - // to 7/8/2021 market close from the SIP feed and print them out - StockBarsResponse aaplBarsResponse = alpacaAPI.stockMarketData().getBars( - "AAPL", - ZonedDateTime.of(2021, 7, 6, 9, 30, 0, 0, ZoneId.of("America/New_York")), - ZonedDateTime.of(2021, 7, 8, 12 + 4, 0, 0, 0, ZoneId.of("America/New_York")), - null, - null, - 1, - BarTimePeriod.HOUR, - BarAdjustment.SPLIT, - BarFeed.SIP); - aaplBarsResponse.getBars().forEach(System.out::println); - - // Get AAPL first 10 trades on 7/8/2021 at market open and print them out - StockTradesResponse aaplTradesResponse = alpacaAPI.stockMarketData().getTrades( - "AAPL", - ZonedDateTime.of(2021, 7, 8, 9, 30, 0, 0, ZoneId.of("America/New_York")), - ZonedDateTime.of(2021, 7, 8, 9, 31, 0, 0, ZoneId.of("America/New_York")), - 10, - null); - aaplTradesResponse.getTrades().forEach(System.out::println); - - // Print out latest AAPL trade - Trade latestAAPLTrade = alpacaAPI.stockMarketData().getLatestTrade("AAPL").getTrade(); - System.out.printf("Latest AAPL Trade: %s\n", latestAAPLTrade); - - // Print out snapshot of AAPL, GME, and TSLA - Map snapshots = alpacaAPI.stockMarketData() - .getSnapshots(Arrays.asList("AAPL", "GME", "TSLA")); - snapshots.forEach((symbol, snapshot) -> - System.out.printf("Symbol: %s\nSnapshot: %s\n\n", symbol, snapshot)); -} catch (AlpacaClientException exception) { - exception.printStackTrace(); -} +// TODO ``` -## [`OrdersEndpoint`](src/main/java/net/jacobpeterson/alpaca/rest/endpoint/orders/OrdersEndpoint.java) -The Orders API allows a user to monitor, place, and cancel their orders with Alpaca. - -Example usage: +## Broker API ```java -try { - // Print all the 'Order's of AAPL and TSLA until 7/6/2021 in - // ascending (oldest to newest) order - List orders = alpacaAPI.orders().get( - CurrentOrderStatus.ALL, - null, - null, - ZonedDateTime.of(2021, 7, 6, 0, 0, 0, 0, ZoneId.of("America/New_York")), - SortDirection.ASCENDING, - true, - Arrays.asList("AAPL", "TSLA")); - orders.forEach(System.out::println); - - // Request a new limit order for TSLA for 100 shares at a limit price - // of $653.00 and TIF of DAY. - Order tslaLimitOrder = alpacaAPI.orders().requestLimitOrder( - "TSLA", - 100, - OrderSide.BUY, - OrderTimeInForce.DAY, - 653.00, - false); - System.out.printf("Requested %s %s order at %s\n", - tslaLimitOrder.getSymbol(), - tslaLimitOrder.getType(), - tslaLimitOrder.getSubmittedAt()); - - // Check if TSLA limit order has filled 5 seconds later and if it hasn't - // replace it with a higher limit order so it fills! - Thread.sleep(5000); - tslaLimitOrder = alpacaAPI.orders().get(tslaLimitOrder.getId(), false); - if (tslaLimitOrder.getFilledAt() == null && tslaLimitOrder.getStatus().equals(OrderStatus.NEW)) { - Order replacedOrder = alpacaAPI.orders().replace( - tslaLimitOrder.getId(), - 100, - OrderTimeInForce.DAY, - 655.00, - null, null, null); - System.out.printf("Replaced TSLA order: %s\n", replacedOrder); - } - - // Cancel all open orders after 2 seconds - Thread.sleep(2000); - List cancelledOrders = alpacaAPI.orders().cancelAll(); - cancelledOrders.forEach((cancelledOrder) -> System.out.printf("Cancelled: %s\n", cancelledOrder)); - - // Request a new fractional market order for 0.5 shares of GME - alpacaAPI.orders().requestFractionalMarketOrder("GME", 0.5, OrderSide.BUY); - // Request a new notional market order for $25 worth of GME shares - alpacaAPI.orders().requestNotionalMarketOrder("GME", 25d, OrderSide.BUY); -} catch (AlpacaClientException | InterruptedException exception) { - exception.printStackTrace(); -} -``` - -## [`PositionsEndpoint`](src/main/java/net/jacobpeterson/alpaca/rest/endpoint/positions/PositionsEndpoint.java) -The Positions API provides information about an account's current open positions. - -Example usage: -```java -try { - // Close 50% of all open TSLA and AAPL positions - List openPositions = alpacaAPI.positions().get(); - for (Position openPosition : openPositions) { - if (openPosition.getSymbol().equals("TSLA") || openPosition.getSymbol().equals("AAPL")) { - Order closePositionOrder = alpacaAPI.positions() - .close(openPosition.getSymbol(), null, 50d); - System.out.printf("Closing 50%% of %s position: %s\n", - openPosition.getSymbol(), closePositionOrder); - } - } -} catch (AlpacaClientException exception) { - exception.printStackTrace(); -} -``` - -## [`AssetsEndpoint`](src/main/java/net/jacobpeterson/alpaca/rest/endpoint/assets/AssetsEndpoint.java) -The Assets API serves as the master list of assets available for trade and data consumption from Alpaca. - -Example usage: -```java -try { - // Print out a CSV of all active US Equity 'Asset's - List activeUSEquities = alpacaAPI.assets().get(AssetStatus.ACTIVE, AssetClass.US_EQUITY); - System.out.println(activeUSEquities - .stream().map(Asset::getSymbol) - .collect(Collectors.joining(", "))); - - // Print out TSLA 'Asset' information - Asset tslaAsset = alpacaAPI.assets().getBySymbol("TSLA"); - System.out.println(tslaAsset); -} catch (AlpacaClientException exception) { - exception.printStackTrace(); -} -``` - -## [`WatchlistEndpoint`](src/main/java/net/jacobpeterson/alpaca/rest/endpoint/watchlist/WatchlistEndpoint.java) -The Watchlist API provides CRUD operation for the account's watchlist. - -Example usage: -```java -try { - // Print out CSV all 'Watchlist' names - List watchlists = alpacaAPI.watchlist().get(); - System.out.println(watchlists.stream() - .map(Watchlist::getName) - .collect(Collectors.joining(", "))); - - // Create a watchlist for day trading with TSLA and AAPL initially - Watchlist dayTradeWatchlist = alpacaAPI.watchlist().create("Day Trade", "TSLA", "AAPL"); - - // Remove TSLA and add MSFT to 'dayTradeWatchlist' - alpacaAPI.watchlist().removeSymbol(dayTradeWatchlist.getId(), "TSLA"); - alpacaAPI.watchlist().addAsset(dayTradeWatchlist.getId(), "MSFT"); - - // Set the updated watchlist variable - dayTradeWatchlist = alpacaAPI.watchlist().get(dayTradeWatchlist.getId()); - - // Print CSV of 'dayTradeWatchlist' 'Asset's - System.out.println(dayTradeWatchlist.getAssets() - .stream().map(Asset::getSymbol) - .collect(Collectors.joining(", "))); - - // Delete the 'dayTradeWatchlist' - alpacaAPI.watchlist().delete(dayTradeWatchlist.getId()); -} catch (AlpacaClientException exception) { - exception.printStackTrace(); -} -``` - -## [`CalendarEndpoint`](src/main/java/net/jacobpeterson/alpaca/rest/endpoint/calendar/CalendarEndpoint.java) -The calendar API serves the full list of market days from 1970 to 2029. - -Example usage: -```java -try { - // Get the 'Calendar's of the week of Christmas 2020 and print them out - List calendar = alpacaAPI.calendar().get( - LocalDate.of(2020, 12, 20), - LocalDate.of(2020, 12, 27)); - calendar.forEach(System.out::println); -} catch (AlpacaClientException exception) { - exception.printStackTrace(); -} -``` - -## [`ClockEndpoint`](src/main/java/net/jacobpeterson/alpaca/rest/endpoint/clock/ClockEndpoint.java) -The clock API serves the current market timestamp, whether the market is currently open, as well as the times of the next market open and close. - -Example usage: -```java -try { - // Get the market 'Clock' and print it out - Clock clock = alpacaAPI.clock().get(); - System.out.println(clock); -} catch (AlpacaClientException exception) { - exception.printStackTrace(); -} -``` - -## [`AccountConfigurationEndpoint`](src/main/java/net/jacobpeterson/alpaca/rest/endpoint/accountconfiguration/AccountConfigurationEndpoint.java) -The Account Configuration API provides custom configurations about your trading account settings. These configurations control various allow you to modify settings to suit your trading needs. - -Example usage: -```java -try { - // Update the 'AccountConfiguration' to block new orders - AccountConfiguration accountConfiguration = alpacaAPI.accountConfiguration().get(); - accountConfiguration.setSuspendTrade(true); - alpacaAPI.accountConfiguration().set(accountConfiguration); -} catch (AlpacaClientException exception) { - exception.printStackTrace(); -} -``` - -## [`AccountActivitiesEndpoint`](src/main/java/net/jacobpeterson/alpaca/rest/endpoint/accountactivities/AccountActivitiesEndpoint.java) -The Account Activities API provides access to a historical record of transaction activities that have impacted your account. - -Example usage: -```java -try { - // Print all order fill and cash deposit 'AccountActivity's on 7/8/2021 - List accountActivities = alpacaAPI.accountActivities().get( - ZonedDateTime.of(2021, 7, 8, 0, 0, 0, 0, ZoneId.of("America/New_York")), - null, - null, - SortDirection.ASCENDING, - null, - null, - ActivityType.FILL, ActivityType.CSD); - for (AccountActivity accountActivity : accountActivities) { - if (accountActivity instanceof TradeActivity) { - System.out.println("TradeActivity: " + (TradeActivity) accountActivity); - } else if (accountActivity instanceof NonTradeActivity) { - System.out.println("NonTradeActivity: " + (NonTradeActivity) accountActivity); - } - } -} catch (AlpacaClientException exception) { - exception.printStackTrace(); -} -``` - -## [`PortfolioHistoryEndpoint`](src/main/java/net/jacobpeterson/alpaca/rest/endpoint/portfoliohistory/PortfolioHistoryEndpoint.java) -The Portfolio History API returns the timeseries data for equity and profit loss information of the account. - -Example usage: -```java -try { - // Get 3 days of one-hour 'PortfolioHistory' on 7/8/2021 and print out its data points - PortfolioHistory portfolioHistory = alpacaAPI.portfolioHistory().get( - 3, - PortfolioPeriodUnit.DAY, - PortfolioTimeFrame.ONE_HOUR, - LocalDate.of(2021, 7, 8), - false); - System.out.printf("Timeframe: %s, Base value: %s \n", - portfolioHistory.getTimeframe(), - portfolioHistory.getBaseValue()); - portfolioHistory.getDataPoints().forEach(System.out::println); -} catch (AlpacaClientException exception) { - exception.printStackTrace(); -} -``` - -## [`StreamingWebsocket`](src/main/java/net/jacobpeterson/alpaca/websocket/streaming/StreamingWebsocket.java) -Alpaca offers websocket streaming of order updates. - -Example usage: -```java -// Add a 'StreamingListener' that simply prints streaming information -StreamingListener streamingListener = (messageType, message) -> - System.out.printf("%s: %s\n", messageType.name(), message); -alpacaAPI.streaming().setListener(streamingListener); - -// Listen 'AuthorizationMessage' and 'ListeningMessage' messages that contain -// information about the stream's current state. Note that these are subscribed -// to before the websocket is connected since these messages usually are sent -// upon websocket connection. -alpacaAPI.streaming().streams(StreamingMessageType.AUTHORIZATION, - StreamingMessageType.LISTENING); - -// Connect the websocket and confirm authentication -alpacaAPI.streaming().connect(); -alpacaAPI.streaming().waitForAuthorization(5, TimeUnit.SECONDS); -if (!alpacaAPI.streaming().isValid()) { - System.out.println("Websocket not valid!"); - return; -} - -// Listen to the 'trade update' streams. -alpacaAPI.streaming().streams(StreamingMessageType.TRADE_UPDATES); - -// Wait a few seconds -Thread.sleep(5000); - -// Manually disconnect the websocket -alpacaAPI.streaming().disconnect(); -``` - -## [`StockMarketDataWebsocket`](src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/stock/StockMarketDataWebsocket.java) -Alpaca's Data API v2 provides websocket streaming for trades, quotes, and minute bars. This helps receive the most up-to-date market information that could help your trading strategy to act upon certain market movement. - -Example usage: -```java -// Add a 'MarketDataListener' that simply prints market data information -MarketDataListener marketDataListener = (messageType, message) -> - System.out.printf("%s: %s\n", messageType.name(), message); -alpacaAPI.stockMarketDataStreaming().setListener(marketDataListener); - -// Listen to 'SubscriptionsMessage', 'SuccessMessage', and 'ErrorMessage' control messages -// that contain information about the stream's current state. Note that these are subscribed -// to before the websocket is connected since these messages usually are sent -// upon websocket connection. -alpacaAPI.stockMarketDataStreaming().subscribeToControl( - MarketDataMessageType.SUCCESS, - MarketDataMessageType.SUBSCRIPTION, - MarketDataMessageType.ERROR); - -// Connect the websocket and confirm authentication -alpacaAPI.stockMarketDataStreaming().connect(); -alpacaAPI.stockMarketDataStreaming().waitForAuthorization(5, TimeUnit.SECONDS); -if (!alpacaAPI.stockMarketDataStreaming().isValid()) { - System.out.println("Websocket not valid!"); - return; -} - -// Listen to AAPL and TSLA trades and all bars via the wildcard operator ('*'). -alpacaAPI.stockMarketDataStreaming().subscribe( - Arrays.asList("AAPL", "TSLA"), - null, - Arrays.asList("*")); - -// Wait a few seconds -Thread.sleep(5000); - -// Manually disconnect the websocket -alpacaAPI.stockMarketDataStreaming().disconnect(); -``` - -## [`CryptoMarketDataWebsocket`](src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/crypto/CryptoMarketDataWebsocket.java) -Alpaca also offers cryptocurrency websocket streaming for trades, quotes, and minute bars. - -The usage is identical to the [`StockMarketDataWebsocket`](https://github.com/Petersoj/alpaca-java/#stockmarketdatawebsocket) usage except that accessing the websocket instance via the `AlpacaAPI` instance is done using: `alpacaAPI.cryptoMarketDataStreaming()` instead of `alpacaAPI.stockMarketDataStreaming()`. - -Example usage: -```java -// The 'CryptoMarketDataWebsocket' setup is identical to the 'StockMarketDataWebsocket' setup - -// Listen to BTCUSD and ETHUSD trades and all bars via the wildcard operator ('*'). -alpacaAPI.cryptoMarketDataStreaming().subscribe( - Arrays.asList("BTCUSD", "ETHUSD"), - null, - Arrays.asList("*")); -``` - -## [`NewsMarketDataWebsocket`](src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/news/NewsMarketDataWebsocket.java) -Alpaca also offers news websocket streaming for symbols. - -The usage is identical to the [`StockMarketDataWebsocket`](https://github.com/Petersoj/alpaca-java/#stockmarketdatawebsocket) usage except that accessing the websocket instance via the `AlpacaAPI` instance is done using: `alpacaAPI.newsMarketDataStreaming()` instead of `alpacaAPI.stockMarketDataStreaming()`. - -Example usage: -```java -MarketDataListener marketDataListener = (messageType, message) -> - System.out.printf("%s: %s\n", messageType.name(), message); - -MarketDataWebsocketInterface newsWebsocket = alpacaAPI.newsMarketDataStreaming(); - -newsWebsocket.setListener(marketDataListener); -newsWebsocket.subscribeToControl( - MarketDataMessageType.SUCCESS, - MarketDataMessageType.SUBSCRIPTION, - MarketDataMessageType.ERROR); - -newsWebsocket.connect(); -newsWebsocket.waitForAuthorization(5, TimeUnit.SECONDS); -if (!newsWebsocket.isValid()) { - System.out.println("Websocket not valid!"); - return; -} -newsWebsocket.subscribe(null, null, null, Arrays.asList("*")); - -// Wait a few seconds -Thread.sleep(5000); - -// Manually disconnect the websocket -newsWebsocket.disconnect(); +// TODO ``` # Building diff --git a/build.gradle b/build.gradle index 1c8e27b1..a0729cd0 100644 --- a/build.gradle +++ b/build.gradle @@ -238,6 +238,14 @@ final def fixOpenAPIGeneratedClientIssuesTask = tasks.register("fixOpenAPIGenera "[\\s\\S]*?authentications\\);", "") .replaceAll("for \\(String authName : authNames\\)[\\s\\S]*?uri\\);\\n. {7}}", "") } + + // Make the 'JSON' classes of the generate clients have a static Gson initializer + specIDsOfFileNames.keySet().stream().map { it.replace(".json", "") }.forEach { + final def jsonClientFile = Paths.get(generatedClientLibrariesPath.getPath(), it, sourceFilesPath, + it.replace("-", ""), "JSON.java").toFile() + jsonClientFile.text = jsonClientFile.text + .replaceAll("\\{\\n {8}GsonBuilder", "static {\n GsonBuilder") + } } } generateOpenAPIClientsTask.configure { diff --git a/src/main/java/net/jacobpeterson/alpaca/AlpacaAPI.java b/src/main/java/net/jacobpeterson/alpaca/AlpacaAPI.java index 6e6f2da3..001aa617 100644 --- a/src/main/java/net/jacobpeterson/alpaca/AlpacaAPI.java +++ b/src/main/java/net/jacobpeterson/alpaca/AlpacaAPI.java @@ -3,6 +3,8 @@ import net.jacobpeterson.alpaca.model.util.apitype.BrokerAPIEndpointType; import net.jacobpeterson.alpaca.model.util.apitype.MarketDataAPIStreamSourceType; import net.jacobpeterson.alpaca.model.util.apitype.TraderAPIEndpointType; +import net.jacobpeterson.alpaca.websocket.trades.TradesWebsocket; +import net.jacobpeterson.alpaca.websocket.trades.TradesWebsocketInterface; import okhttp3.OkHttpClient; import okhttp3.logging.HttpLoggingInterceptor; import org.slf4j.Logger; @@ -40,6 +42,7 @@ public class AlpacaAPI { private AlpacaTraderAPI trader; private AlpacaMarketDataAPI marketData; private AlpacaBrokerAPI broker; + private TradesWebsocket tradesWebsocket; /** * Instantiates a new {@link AlpacaAPI}. Use this constructor if you are using the Trading or Market Data APIs for a @@ -220,6 +223,19 @@ public synchronized AlpacaBrokerAPI broker() { return broker; } + /** + * Gets the {@link TradesWebsocketInterface}. Lazily instantiated. + * + * @return the {@link TradesWebsocketInterface} + */ + public synchronized TradesWebsocketInterface tradesStream() { + if (tradesWebsocket == null) { + tradesWebsocket = new TradesWebsocket(okHttpClient, traderAPIEndpointType, + traderKeyID, traderSecretKey, traderOAuthToken); + } + return tradesWebsocket; + } + /** * Creates a {@link Builder} for {@link AlpacaAPI}. * diff --git a/src/main/java/net/jacobpeterson/alpaca/AlpacaBrokerAPI.java b/src/main/java/net/jacobpeterson/alpaca/AlpacaBrokerAPI.java index a4aa0c56..942d71b3 100644 --- a/src/main/java/net/jacobpeterson/alpaca/AlpacaBrokerAPI.java +++ b/src/main/java/net/jacobpeterson/alpaca/AlpacaBrokerAPI.java @@ -60,8 +60,8 @@ public class AlpacaBrokerAPI { * @param okHttpClient an existing {@link OkHttpClient} or null to create a new default * instance */ - @SuppressWarnings("UnnecessaryDefault") AlpacaBrokerAPI(String brokerAPIKey, String brokerAPISecret, - BrokerAPIEndpointType brokerAPIEndpointType, + @SuppressWarnings("UnnecessaryDefault") + AlpacaBrokerAPI(String brokerAPIKey, String brokerAPISecret, BrokerAPIEndpointType brokerAPIEndpointType, OkHttpClient okHttpClient) { checkNotNull(brokerAPIKey); checkNotNull(brokerAPISecret); diff --git a/src/main/java/net/jacobpeterson/alpaca/endpoint/streaming/authorization/authorization_message.json b/src/main/java/net/jacobpeterson/alpaca/endpoint/streaming/authorization/authorization_message.json deleted file mode 100644 index 5e266609..00000000 --- a/src/main/java/net/jacobpeterson/alpaca/endpoint/streaming/authorization/authorization_message.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "type": "object", - "title": "See Streaming.", - "extends": { - "existingJavaType": "net.jacobpeterson.alpaca.model.endpoint.streaming.StreamingMessage" - }, - "properties": { - "data": { - "existingJavaType": "net.jacobpeterson.alpaca.model.endpoint.streaming.authorization.AuthorizationData", - "title": "The {@link net.jacobpeterson.alpaca.model.endpoint.streaming.authorization.AuthorizationData}." - } - } -} diff --git a/src/main/java/net/jacobpeterson/alpaca/endpoint/streaming/enums/streaming_message_type.json b/src/main/java/net/jacobpeterson/alpaca/endpoint/streaming/enums/streaming_message_type.json deleted file mode 100644 index 454ee643..00000000 --- a/src/main/java/net/jacobpeterson/alpaca/endpoint/streaming/enums/streaming_message_type.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "type": "string", - "title": "See Streaming.", - "enum": [ - "listening", - "authorization", - "trade_updates" - ] -} diff --git a/src/main/java/net/jacobpeterson/alpaca/endpoint/streaming/listening/listening_data.json b/src/main/java/net/jacobpeterson/alpaca/endpoint/streaming/listening/listening_data.json deleted file mode 100644 index eb9d6eb9..00000000 --- a/src/main/java/net/jacobpeterson/alpaca/endpoint/streaming/listening/listening_data.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "type": "object", - "title": "See Streaming.", - "properties": { - "streams": { - "existingJavaType": "java.util.ArrayList", - "title": "The {@link java.util.ArrayList} of {@link net.jacobpeterson.alpaca.model.endpoint.streaming.enums.StreamingMessageType}s." - } - } -} diff --git a/src/main/java/net/jacobpeterson/alpaca/endpoint/streaming/listening/listening_message.json b/src/main/java/net/jacobpeterson/alpaca/endpoint/streaming/listening/listening_message.json deleted file mode 100644 index 24413cb8..00000000 --- a/src/main/java/net/jacobpeterson/alpaca/endpoint/streaming/listening/listening_message.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "type": "object", - "title": "See Streaming.", - "extends": { - "existingJavaType": "net.jacobpeterson.alpaca.model.endpoint.streaming.StreamingMessage" - }, - "properties": { - "data": { - "existingJavaType": "net.jacobpeterson.alpaca.model.endpoint.streaming.listening.ListeningData", - "title": "The {@link net.jacobpeterson.alpaca.model.endpoint.streaming.listening.ListeningData}." - } - } -} diff --git a/src/main/java/net/jacobpeterson/alpaca/endpoint/streaming/streaming_message.json b/src/main/java/net/jacobpeterson/alpaca/endpoint/streaming/streaming_message.json deleted file mode 100644 index c7799c71..00000000 --- a/src/main/java/net/jacobpeterson/alpaca/endpoint/streaming/streaming_message.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "type": "object", - "title": "See Streaming.", - "properties": { - "stream": { - "existingJavaType": "net.jacobpeterson.alpaca.model.endpoint.streaming.enums.StreamingMessageType", - "title": "The {@link net.jacobpeterson.alpaca.model.endpoint.streaming.enums.StreamingMessageType}." - } - } -} diff --git a/src/main/java/net/jacobpeterson/alpaca/endpoint/streaming/trade/trade_update_message.json b/src/main/java/net/jacobpeterson/alpaca/endpoint/streaming/trade/trade_update_message.json deleted file mode 100644 index 014d3d6f..00000000 --- a/src/main/java/net/jacobpeterson/alpaca/endpoint/streaming/trade/trade_update_message.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "type": "object", - "extends": { - "existingJavaType": "net.jacobpeterson.alpaca.model.endpoint.streaming.StreamingMessage" - }, - "title": "See Streaming.", - "properties": { - "data": { - "existingJavaType": "net.jacobpeterson.alpaca.model.endpoint.streaming.trade.TradeUpdate", - "title": "The {@link net.jacobpeterson.alpaca.model.endpoint.streaming.trade.TradeUpdate}." - } - } -} diff --git a/src/main/java/net/jacobpeterson/alpaca/rest/broker/EventsApiSSE.java b/src/main/java/net/jacobpeterson/alpaca/rest/broker/EventsApiSSE.java index da0ad98d..cff5a03c 100644 --- a/src/main/java/net/jacobpeterson/alpaca/rest/broker/EventsApiSSE.java +++ b/src/main/java/net/jacobpeterson/alpaca/rest/broker/EventsApiSSE.java @@ -23,7 +23,6 @@ import java.lang.reflect.Type; import java.time.LocalDate; import java.time.OffsetDateTime; -import java.util.List; import static java.util.concurrent.TimeUnit.SECONDS; import static net.jacobpeterson.alpaca.openapi.broker.JSON.getGson; @@ -54,13 +53,13 @@ public EventsApiSSE(ApiClient apiClient) { * * @return a {@link SSERequest} */ - public SSERequest subscribeToV1EventsNta(String id, String since, String until, Integer sinceId, Integer untilId, - String sinceUlid, String untilUlid, Boolean includePreprocessing, - SSEListener> sseListener) throws ApiException { // TODO OpenAPI response type is broken + public SSERequest subscribeToNonTradingActivitiesEvents(String id, String since, String until, Integer sinceId, + Integer untilId, String sinceUlid, String untilUlid, Boolean includePreprocessing, + SSEListener sseListener) throws ApiException { // TODO OpenAPI response type is broken final Request request = eventsAPI.getV1EventsNtaCall(id, since, until, sinceId, untilId, sinceUlid, untilUlid, includePreprocessing, null).request(); return new SSERequest(eventSourceFactory.newEventSource(request, createEventSourceListener(sseListener, - new TypeToken>() {}.getType()))); // TODO OpenAPI response type is broken + new TypeToken() {}.getType()))); // TODO OpenAPI response type is broken } /** @@ -69,10 +68,10 @@ public SSERequest subscribeToV1EventsNta(String id, String since, String until, * @return a {@link SSERequest} */ public SSERequest subscribeToAdminAction(OffsetDateTime since, OffsetDateTime until, String sinceId, - String untilId, SSEListener> sseListener) throws ApiException { + String untilId, SSEListener sseListener) throws ApiException { final Request request = eventsAPI.subscribeToAdminActionSSECall(since, until, sinceId, untilId, null).request(); return new SSERequest(eventSourceFactory.newEventSource(request, createEventSourceListener(sseListener, - new TypeToken>() {}.getType()))); + new TypeToken() {}.getType()))); } /** @@ -84,11 +83,11 @@ public SSERequest subscribeToAdminAction(OffsetDateTime since, OffsetDateTime un */ public SSERequest subscribeToJournalStatus(OffsetDateTime since, OffsetDateTime until, Integer sinceId, Integer untilId, String sinceUlid, String untilUlid, String id, - SSEListener> sseListener) throws ApiException { + SSEListener sseListener) throws ApiException { final Request request = eventsAPI.subscribeToJournalStatusSSECall(since, until, sinceId, untilId, sinceUlid, untilUlid, id, null).request(); return new SSERequest(eventSourceFactory.newEventSource(request, createEventSourceListener(sseListener, - new TypeToken>() {}.getType()))); + new TypeToken() {}.getType()))); } /** @@ -97,12 +96,12 @@ public SSERequest subscribeToJournalStatus(OffsetDateTime since, OffsetDateTime * @return a {@link SSERequest} */ public SSERequest subscribeToTrade(OffsetDateTime since, OffsetDateTime until, Integer sinceId, - Integer untilId, String sinceUlid, String untilUlid, SSEListener> sseListener) + Integer untilId, String sinceUlid, String untilUlid, SSEListener sseListener) throws ApiException { final Request request = eventsAPI.subscribeToTradeSSECall(since, until, sinceId, untilId, sinceUlid, untilUlid, null).request(); return new SSERequest(eventSourceFactory.newEventSource(request, createEventSourceListener(sseListener, - new TypeToken>() {}.getType()))); + new TypeToken() {}.getType()))); } /** @@ -111,10 +110,10 @@ public SSERequest subscribeToTrade(OffsetDateTime since, OffsetDateTime until, I * @return a {@link SSERequest} */ public SSERequest subscribeToTradeV2(OffsetDateTime since, OffsetDateTime until, String sinceId, - String untilId, SSEListener> sseListener) throws ApiException { + String untilId, SSEListener sseListener) throws ApiException { final Request request = eventsAPI.subscribeToTradeV2SSECall(since, until, sinceId, untilId, null).request(); return new SSERequest(eventSourceFactory.newEventSource(request, createEventSourceListener(sseListener, - new TypeToken>() {}.getType()))); + new TypeToken() {}.getType()))); } /** @@ -125,12 +124,12 @@ public SSERequest subscribeToTradeV2(OffsetDateTime since, OffsetDateTime until, * @return a {@link SSERequest} */ public SSERequest subscribeToTransferStatus(OffsetDateTime since, OffsetDateTime until, Integer sinceId, - Integer untilId, String sinceUlid, String untilUlid, SSEListener> sseListener) + Integer untilId, String sinceUlid, String untilUlid, SSEListener sseListener) throws ApiException { final Request request = eventsAPI.subscribeToTransferStatusSSECall(since, until, sinceId, untilId, sinceUlid, untilUlid, null).request(); return new SSERequest(eventSourceFactory.newEventSource(request, createEventSourceListener(sseListener, - new TypeToken>() {}.getType()))); + new TypeToken() {}.getType()))); } /** @@ -140,12 +139,12 @@ public SSERequest subscribeToTransferStatus(OffsetDateTime since, OffsetDateTime * @return a {@link SSERequest} */ public SSERequest subscribeToAccountStatus(LocalDate since, LocalDate until, Integer sinceId, Integer untilId, - String sinceUlid, String untilUlid, String id, SSEListener> sseListener) + String sinceUlid, String untilUlid, String id, SSEListener sseListener) throws ApiException { final Request request = eventsAPI.suscribeToAccountStatusSSECall(since, until, sinceId, untilId, sinceUlid, untilUlid, id, null).request(); return new SSERequest(eventSourceFactory.newEventSource(request, createEventSourceListener(sseListener, - new TypeToken>() {}.getType()))); + new TypeToken() {}.getType()))); } private EventSourceListener createEventSourceListener(SSEListener sseListener, Type responseTypeToken) { @@ -158,7 +157,7 @@ public void onClosed(@NotNull EventSource eventSource) { @Override public void onEvent(@NotNull EventSource eventSource, @Nullable String id, @Nullable String type, @NotNull String data) { - sseListener.onEvent(getGson().fromJson(data, responseTypeToken)); + sseListener.onMessage(getGson().fromJson(data, responseTypeToken)); } @Override @@ -168,7 +167,7 @@ public void onFailure(@NotNull EventSource eventSource, @Nullable Throwable thro sseListener.onClose(); return; } - sseListener.onFailure(throwable, response); + sseListener.onError(throwable, response); } @Override diff --git a/src/main/java/net/jacobpeterson/alpaca/util/sse/SSEListener.java b/src/main/java/net/jacobpeterson/alpaca/util/sse/SSEListener.java index 9ca01253..5b28942b 100644 --- a/src/main/java/net/jacobpeterson/alpaca/util/sse/SSEListener.java +++ b/src/main/java/net/jacobpeterson/alpaca/util/sse/SSEListener.java @@ -12,27 +12,27 @@ public interface SSEListener { /** - * Called on SSE request open. + * Called on SSE open. */ void onOpen(); /** - * Called on SSE request close. + * Called on SSE close. */ void onClose(); /** - * Called on SSE request failure. + * Called on SSE error. * * @param throwable the {@link Throwable} * @param response the {@link Response} */ - void onFailure(@Nullable Throwable throwable, @Nullable Response response); + void onError(@Nullable Throwable throwable, @Nullable Response response); /** - * Called on SSE request data receive event. + * Called on SSE message received. * - * @param data the {@link T} data + * @param message the {@link T} message */ - void onEvent(@NotNull T data); + void onMessage(@NotNull T message); } diff --git a/src/main/java/net/jacobpeterson/alpaca/util/sse/SSEListenerAdapter.java b/src/main/java/net/jacobpeterson/alpaca/util/sse/SSEListenerAdapter.java index 492e9e0f..57e33dfb 100644 --- a/src/main/java/net/jacobpeterson/alpaca/util/sse/SSEListenerAdapter.java +++ b/src/main/java/net/jacobpeterson/alpaca/util/sse/SSEListenerAdapter.java @@ -23,11 +23,11 @@ public void onClose() { LOGGER.info("SSE connection closed."); } - public void onFailure(@Nullable Throwable throwable, @Nullable Response response) { - LOGGER.error("SSE connection failed! {}", response, throwable); + public void onError(@Nullable Throwable throwable, @Nullable Response response) { + LOGGER.error("SSE connection error! {}", response, throwable); } - public void onEvent(@NotNull T data) { - LOGGER.info("SSE event received: {}", data); + public void onMessage(@NotNull T message) { + LOGGER.info("SSE message received: {}", message); } } diff --git a/src/main/java/net/jacobpeterson/alpaca/websocket/AlpacaWebsocket.java b/src/main/java/net/jacobpeterson/alpaca/websocket/AlpacaWebsocket.java new file mode 100644 index 00000000..d475cc8a --- /dev/null +++ b/src/main/java/net/jacobpeterson/alpaca/websocket/AlpacaWebsocket.java @@ -0,0 +1,287 @@ +package net.jacobpeterson.alpaca.websocket; + +import okhttp3.HttpUrl; +import okhttp3.OkHttpClient; +import okhttp3.Request; +import okhttp3.Response; +import okhttp3.WebSocket; +import okhttp3.WebSocketListener; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.time.Duration; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.Future; + +import static com.google.common.base.Preconditions.checkNotNull; +import static java.util.concurrent.ForkJoinPool.commonPool; + +/** + * {@link AlpacaWebsocket} represents an abstract websocket for Alpaca. + * + * @param the {@link AlpacaWebsocketMessageListener} type parameter + * @param the 'message type' type parameter + * @param the 'message' type parameter + */ +public abstract class AlpacaWebsocket> extends WebSocketListener + implements AlpacaWebsocketInterface { + + /** + * Defines a websocket normal closure code. + * + * @see WebSocket#close(int, String) + */ + public static final int WEBSOCKET_NORMAL_CLOSURE_CODE = 1000; + + /** + * Defines a websocket normal closure message. + * + * @see WebSocket#close(int, String) + */ + public static final String WEBSOCKET_NORMAL_CLOSURE_MESSAGE = "Normal closure"; + + /** + * Defines the maximum number of reconnection attempts to be made by an {@link AlpacaWebsocket}. + */ + public static int MAX_RECONNECT_ATTEMPTS = 10; + + /** + * Defines the sleep interval {@link Duration} between reconnection attempts made by an {@link AlpacaWebsocket}. + */ + public static Duration RECONNECT_SLEEP_INTERVAL = Duration.ofSeconds(1); + + private static final Logger LOGGER = LoggerFactory.getLogger(AlpacaWebsocket.class); + + protected final OkHttpClient okHttpClient; + protected final HttpUrl websocketURL; + protected final String websocketName; + protected final String keyID; + protected final String secretKey; + protected final String oAuthToken; + protected final boolean useOAuth; + protected L listener; + protected AlpacaWebsocketStateListener alpacaWebsocketStateListener; + protected WebSocket websocket; + protected boolean connected; + protected boolean authenticated; + protected CompletableFuture authenticationMessageFuture; + protected boolean intentionalClose; + protected int reconnectAttempts; + + protected boolean automaticallyReconnect; + + /** + * Instantiates a {@link AlpacaWebsocket}. + * + * @param okHttpClient the {@link OkHttpClient} + * @param websocketURL the websocket {@link HttpUrl} + * @param websocketName the websocket name + * @param keyID the key ID + * @param secretKey the secret key + * @param oAuthToken the OAuth token + */ + public AlpacaWebsocket(OkHttpClient okHttpClient, HttpUrl websocketURL, String websocketName, + String keyID, String secretKey, String oAuthToken) { + checkNotNull(okHttpClient); + checkNotNull(websocketURL); + checkNotNull(websocketName); + + this.okHttpClient = okHttpClient; + this.websocketURL = websocketURL; + this.websocketName = websocketName; + this.keyID = keyID; + this.secretKey = secretKey; + this.oAuthToken = oAuthToken; + useOAuth = oAuthToken != null; + + automaticallyReconnect = true; + } + + @Override + public void connect() { + if (!isConnected()) { + final Request websocketRequest = new Request.Builder() + .url(websocketURL) + .get() + .build(); + websocket = okHttpClient.newWebSocket(websocketRequest, this); + } + } + + @Override + public void disconnect() { + if (websocket != null && isConnected()) { + intentionalClose = true; + websocket.close(WEBSOCKET_NORMAL_CLOSURE_CODE, WEBSOCKET_NORMAL_CLOSURE_MESSAGE); + } else { + cleanupState(); + } + } + + @Override + public boolean isConnected() { + return connected; + } + + @Override + public boolean isAuthenticated() { + return authenticated; + } + + @Override + public void onOpen(@NotNull WebSocket webSocket, @NotNull Response response) { + connected = true; + + LOGGER.info("{} websocket opened.", websocketName); + LOGGER.debug("{} websocket response: {}", websocketName, response); + + // Call 'onConnection' or 'onReconnection' async to avoid any potential deadlocking since this is called + // in sync with 'onMessage' in OkHttp's 'WebSocketListener' + commonPool().execute(() -> { + if (reconnectAttempts > 0) { + onReconnection(); + } else { + onConnection(); + } + }); + + if (alpacaWebsocketStateListener != null) { + alpacaWebsocketStateListener.onOpen(response); + } + } + + @Override + public void onClosed(@NotNull WebSocket webSocket, int code, @NotNull String reason) { + connected = false; + + if (intentionalClose) { + LOGGER.info("{} websocket closed.", websocketName); + LOGGER.debug("Close code: {}, Reason: {}", code, reason); + cleanupState(); + } else { + LOGGER.error("{} websocket closed unintentionally! Code: {}, Reason: {}", websocketName, code, reason); + handleReconnectionAttempt(); + } + + if (alpacaWebsocketStateListener != null) { + alpacaWebsocketStateListener.onClosed(code, reason); + } + } + + @Override + public void onFailure(@NotNull WebSocket webSocket, @NotNull Throwable cause, @Nullable Response response) { + LOGGER.error("{} websocket failure!", websocketName, cause); + + // A websocket failure occurs when either there is a connection failure or when the client throws + // an exception when receiving a message. In either case, OkHttp will close the websocket connection, + // so try to reopen it. + connected = false; + handleReconnectionAttempt(); + + if (alpacaWebsocketStateListener != null) { + alpacaWebsocketStateListener.onFailure(cause); + } + } + + /** + * Attempts to reconnect the disconnected {@link #websocket} asynchronously. + */ + private void handleReconnectionAttempt() { + if (!automaticallyReconnect) { + return; + } + if (reconnectAttempts < MAX_RECONNECT_ATTEMPTS) { + LOGGER.info("Attempting to reconnect {} websocket in {} seconds... (attempt {} of {})", + websocketName, RECONNECT_SLEEP_INTERVAL.toSeconds(), reconnectAttempts + 1, MAX_RECONNECT_ATTEMPTS); + reconnectAttempts++; + commonPool().execute(() -> { + try { + Thread.sleep(RECONNECT_SLEEP_INTERVAL.toMillis()); + } catch (InterruptedException interruptedException) { + return; + } + connect(); + }); + } else { + LOGGER.error("Exhausted {} reconnection attempts. Not attempting to reconnect.", MAX_RECONNECT_ATTEMPTS); + cleanupState(); + } + } + + /** + * Cleans up this instance's state variables. + */ + protected void cleanupState() { + websocket = null; + connected = false; + authenticated = false; + if (authenticationMessageFuture != null && !authenticationMessageFuture.isDone()) { + authenticationMessageFuture.complete(false); + } + authenticationMessageFuture = null; + intentionalClose = false; + reconnectAttempts = 0; + } + + /** + * Called asynchronously when a websocket connection is made. + */ + protected abstract void onConnection(); + + /** + * Called asynchronously when a websocket reconnection is made after unintentional disconnection. + */ + protected abstract void onReconnection(); + + /** + * Sends an authentication message to authenticate this websocket stream. + */ + protected abstract void sendAuthenticationMessage(); + + @Override + public Future getAuthorizationFuture() { + if (authenticationMessageFuture == null) { + authenticationMessageFuture = new CompletableFuture<>(); + } + return authenticationMessageFuture; + } + + /** + * Calls the {@link AlpacaWebsocketMessageListener}. + * + * @param messageType the message type + * @param message the message + */ + protected void callListener(T messageType, M message) { + if (listener != null) { + try { + listener.onMessage(messageType, message); + } catch (Exception exception) { + LOGGER.error("{} listener threw exception!", websocketName, exception); + } + } + } + + @Override + public void setListener(L listener) { + this.listener = listener; + } + + public AlpacaWebsocketStateListener getWebsocketStateListener() { + return alpacaWebsocketStateListener; + } + + public void setAlpacaWebsocketStateListener(AlpacaWebsocketStateListener alpacaWebsocketStateListener) { + this.alpacaWebsocketStateListener = alpacaWebsocketStateListener; + } + + public boolean doesAutomaticallyReconnect() { + return automaticallyReconnect; + } + + public void setAutomaticallyReconnect(boolean automaticallyReconnect) { + this.automaticallyReconnect = automaticallyReconnect; + } +} diff --git a/src/main/java/net/jacobpeterson/alpaca/websocket/AlpacaWebsocketInterface.java b/src/main/java/net/jacobpeterson/alpaca/websocket/AlpacaWebsocketInterface.java new file mode 100644 index 00000000..033475a2 --- /dev/null +++ b/src/main/java/net/jacobpeterson/alpaca/websocket/AlpacaWebsocketInterface.java @@ -0,0 +1,82 @@ +package net.jacobpeterson.alpaca.websocket; + +import java.util.concurrent.ExecutionException; +import java.util.concurrent.Future; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; + +/** + * {@link AlpacaWebsocketInterface} defines an interface for Alpaca websockets. + * + * @param the {@link AlpacaWebsocketMessageListener} type parameter + */ +public interface AlpacaWebsocketInterface> { + + /** + * Connects this Websocket. + */ + void connect(); + + /** + * Disconnects this Websocket. + */ + void disconnect(); + + /** + * Returns true if this websocket is connected, false otherwise. + * + * @return a boolean + */ + boolean isConnected(); + + /** + * Returns true if this websocket is authenticated, false otherwise. + * + * @return a boolean + */ + boolean isAuthenticated(); + + /** + * Returns true if {@link #isConnected()} and {@link #isAuthenticated()}, false + * otherwise. + * + * @return a boolean + */ + default boolean isValid() { + return isConnected() && isAuthenticated(); + } + + /** + * Gets a {@link Boolean} {@link Future} that completes when an authentication message that is received after a new + * websocket connection indicates successful authentication. + *
+ * Note that if this {@link AlpacaWebsocketInterface} is already authorized, the returned {@link Future} will likely + * never complete. + * + * @return a {@link Boolean} {@link Future} + */ + Future getAuthorizationFuture(); + + /** + * Waits for {@link #getAuthorizationFuture()} to complete and returns its value, except when timeout + * time has elapsed, then this will return false. + * + * @param timeout the timeout time + * @param unit the timeout {@link TimeUnit} + * + * @return a boolean + */ + default boolean waitForAuthorization(long timeout, TimeUnit unit) { + try { + return getAuthorizationFuture().get(timeout, unit); + } catch (InterruptedException | ExecutionException | TimeoutException ignored) {} + return false; + } + + /** + * Sets the {@link AlpacaWebsocketMessageListener} for this {@link AlpacaWebsocketInterface}. + * + * @param listener the {@link AlpacaWebsocketMessageListener} + */ + void setListener(L listener); +} diff --git a/src/main/java/net/jacobpeterson/alpaca/websocket/AlpacaWebsocketMessageListener.java b/src/main/java/net/jacobpeterson/alpaca/websocket/AlpacaWebsocketMessageListener.java new file mode 100644 index 00000000..da55535c --- /dev/null +++ b/src/main/java/net/jacobpeterson/alpaca/websocket/AlpacaWebsocketMessageListener.java @@ -0,0 +1,20 @@ +package net.jacobpeterson.alpaca.websocket; + +/** + * {@link AlpacaWebsocketMessageListener} defines a listener interface for messages from an {@link AlpacaWebsocket} + * instances. + * + * @param the 'message type' type parameter + * @param the 'message' type parameter + */ +@FunctionalInterface +public interface AlpacaWebsocketMessageListener { + + /** + * Called when a message is received. + * + * @param messageType the message type + * @param message the message + */ + void onMessage(T messageType, M message); +} diff --git a/src/main/java/net/jacobpeterson/alpaca/websocket/AlpacaWebsocketStateListener.java b/src/main/java/net/jacobpeterson/alpaca/websocket/AlpacaWebsocketStateListener.java new file mode 100644 index 00000000..2667c2ca --- /dev/null +++ b/src/main/java/net/jacobpeterson/alpaca/websocket/AlpacaWebsocketStateListener.java @@ -0,0 +1,33 @@ +package net.jacobpeterson.alpaca.websocket; + +import okhttp3.Response; +import okhttp3.WebSocket; + +/** + * {@link AlpacaWebsocketStateListener} is an interface to listen for various state changes in an {@link WebSocket} + * instance. + */ +public interface AlpacaWebsocketStateListener { + + /** + * Called when the {@link WebSocket} is connected. + * + * @param response the HTTP websocket upgrade {@link Response} + */ + void onOpen(Response response); + + /** + * Called when the {@link WebSocket} is disconnected. + * + * @param code the code + * @param reason the reason + */ + void onClosed(int code, String reason); + + /** + * Called when a {@link WebSocket} error has occurred. + * + * @param cause the cause {@link Throwable} + */ + void onFailure(Throwable cause); +} diff --git a/src/main/java/net/jacobpeterson/alpaca/websocket/trades/TradesListener.java b/src/main/java/net/jacobpeterson/alpaca/websocket/trades/TradesListener.java new file mode 100644 index 00000000..c96baee5 --- /dev/null +++ b/src/main/java/net/jacobpeterson/alpaca/websocket/trades/TradesListener.java @@ -0,0 +1,11 @@ +package net.jacobpeterson.alpaca.websocket.trades; + +import net.jacobpeterson.alpaca.model.websocket.trades.model.TradesStreamMessage; +import net.jacobpeterson.alpaca.model.websocket.trades.model.TradesStreamMessageType; +import net.jacobpeterson.alpaca.websocket.AlpacaWebsocketMessageListener; + +/** + * {@link TradesListener} defines a listener interface for {@link TradesWebsocket} messages. + */ +public interface TradesListener extends AlpacaWebsocketMessageListener { +} diff --git a/src/main/java/net/jacobpeterson/alpaca/websocket/trades/TradesListenerAdapter.java b/src/main/java/net/jacobpeterson/alpaca/websocket/trades/TradesListenerAdapter.java new file mode 100644 index 00000000..a6dd93ec --- /dev/null +++ b/src/main/java/net/jacobpeterson/alpaca/websocket/trades/TradesListenerAdapter.java @@ -0,0 +1,19 @@ +package net.jacobpeterson.alpaca.websocket.trades; + +import net.jacobpeterson.alpaca.model.websocket.trades.model.TradesStreamMessage; +import net.jacobpeterson.alpaca.model.websocket.trades.model.TradesStreamMessageType; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * {@link TradesListenerAdapter} is an adapter class for {@link TradesListener}. + */ +public class TradesListenerAdapter implements TradesListener { + + private static final Logger LOGGER = LoggerFactory.getLogger(TradesListenerAdapter.class); + + @Override + public void onMessage(TradesStreamMessageType messageType, TradesStreamMessage message) { + LOGGER.info("{} message received: {}", messageType, message); + } +} diff --git a/src/main/java/net/jacobpeterson/alpaca/websocket/trades/TradesWebsocket.java b/src/main/java/net/jacobpeterson/alpaca/websocket/trades/TradesWebsocket.java new file mode 100644 index 00000000..a6fc8896 --- /dev/null +++ b/src/main/java/net/jacobpeterson/alpaca/websocket/trades/TradesWebsocket.java @@ -0,0 +1,219 @@ +package net.jacobpeterson.alpaca.websocket.trades; + +import com.google.gson.JsonArray; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import com.google.gson.JsonPrimitive; +import net.jacobpeterson.alpaca.model.util.apitype.TraderAPIEndpointType; +import net.jacobpeterson.alpaca.model.websocket.trades.model.TradesStreamMessage; +import net.jacobpeterson.alpaca.model.websocket.trades.model.TradesStreamMessageType; +import net.jacobpeterson.alpaca.model.websocket.trades.model.authorization.AuthorizationData; +import net.jacobpeterson.alpaca.model.websocket.trades.model.authorization.AuthorizationMessage; +import net.jacobpeterson.alpaca.model.websocket.trades.model.listening.ListeningMessage; +import net.jacobpeterson.alpaca.model.websocket.trades.model.tradeupdate.TradeUpdateMessage; +import net.jacobpeterson.alpaca.websocket.AlpacaWebsocket; +import okhttp3.HttpUrl; +import okhttp3.OkHttpClient; +import okhttp3.WebSocket; +import okio.ByteString; +import org.jetbrains.annotations.NotNull; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.Arrays; +import java.util.Collection; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +import static com.google.common.base.Preconditions.checkNotNull; +import static com.google.common.base.Preconditions.checkState; +import static com.google.common.base.Predicates.not; +import static com.google.common.collect.Iterables.toArray; +import static com.google.gson.JsonParser.parseString; +import static java.lang.String.format; +import static java.util.Collections.singletonList; +import static java.util.concurrent.TimeUnit.SECONDS; +import static net.jacobpeterson.alpaca.model.websocket.trades.model.TradesStreamMessageType.TRADE_UPDATES; +import static net.jacobpeterson.alpaca.openapi.trader.JSON.getGson; + +/** + * {@link TradesWebsocket} is an {@link AlpacaWebsocket} implementation and provides the + * {@link TradesWebsocketInterface} interface for + * Trades Streaming. + */ +public class TradesWebsocket extends AlpacaWebsocket + implements TradesWebsocketInterface { + + private static final Logger LOGGER = LoggerFactory.getLogger(TradesWebsocket.class); + private static final String STREAM_ELEMENT_KEY = "stream"; + private static final List SUBSCRIBABLE_STREAMING_MESSAGE_TYPES = + singletonList(TRADE_UPDATES); + + /** + * Creates a {@link HttpUrl} for {@link TradesWebsocket}. + * + * @param traderAPIEndpointType the {@link TraderAPIEndpointType} + * + * @return a {@link HttpUrl} + */ + @SuppressWarnings("UnnecessaryDefault") + private static HttpUrl createWebsocketURL(TraderAPIEndpointType traderAPIEndpointType) { + return new HttpUrl.Builder() + .scheme("https") + .host((switch (traderAPIEndpointType) { + case LIVE -> "api"; + case PAPER -> "paper-api"; + default -> throw new UnsupportedOperationException(); + }) + ".alpaca.markets") + .addPathSegment("stream") + .build(); + } + + private final Set listenedMessageTypes; + + /** + * Instantiates a new {@link TradesWebsocket}. + * + * @param okHttpClient the {@link OkHttpClient} + * @param traderAPIEndpointType the {@link TraderAPIEndpointType} + * @param keyID the key ID + * @param secretKey the secret key + * @param oAuthToken the OAuth token + */ + public TradesWebsocket(OkHttpClient okHttpClient, TraderAPIEndpointType traderAPIEndpointType, + String keyID, String secretKey, String oAuthToken) { + super(okHttpClient, createWebsocketURL(traderAPIEndpointType), "Trades Stream", keyID, secretKey, oAuthToken); + listenedMessageTypes = new HashSet<>(); + } + + @Override + protected void cleanupState() { + super.cleanupState(); + listenedMessageTypes.clear(); + } + + @Override + protected void onConnection() { + sendAuthenticationMessage(); + } + + @Override + protected void onReconnection() { + sendAuthenticationMessage(); + if (waitForAuthorization(5, SECONDS)) { + subscribe(toArray(listenedMessageTypes, TradesStreamMessageType.class)); + } + } + + @Override + protected void sendAuthenticationMessage() { + // Ensure that the authorization Future exists + getAuthorizationFuture(); + + final JsonObject authObject = new JsonObject(); + authObject.addProperty("action", "authenticate"); + final JsonObject authData = new JsonObject(); + if (useOAuth) { + authData.addProperty("oauth_token", oAuthToken); + } else { + authData.addProperty("key_id", keyID); + authData.addProperty("secret_key", secretKey); + } + authObject.add("data", authData); + + LOGGER.info("{} websocket sending authentication message...", websocketName); + websocket.send(authObject.toString()); + } + + @Override + public void onMessage(@NotNull WebSocket webSocket, @NotNull ByteString byteString) { + final String messageString = byteString.utf8(); + LOGGER.trace("Websocket message received: {}", messageString); + + // Parse JSON string and identify 'messageType' + final JsonElement messageElement = parseString(messageString); + checkState(messageElement instanceof JsonObject, "Message must be a JsonObject! Received: %s", messageElement); + final JsonObject messageObject = messageElement.getAsJsonObject(); + final JsonElement streamElement = messageObject.get(STREAM_ELEMENT_KEY); + checkState(streamElement instanceof JsonPrimitive, + "Message must contain %s element! Received: %s", STREAM_ELEMENT_KEY, messageElement); + final TradesStreamMessageType messageType = getGson().fromJson(streamElement, TradesStreamMessageType.class); + checkNotNull(messageType, "TradesStreamMessageType not found in message: %s", messageObject); + + // Deserialize message based on 'messageType' + TradesStreamMessage message; + switch (messageType) { + case AUTHORIZATION: + message = getGson().fromJson(messageObject, AuthorizationMessage.class); + final AuthorizationData authorizationData = ((AuthorizationMessage) message).getData(); + authenticated = authorizationData.getStatus().equalsIgnoreCase("authorized") && + authorizationData.getAction().equalsIgnoreCase("authenticate"); + if (!authenticated) { + throw new RuntimeException(format("%s websocket not authenticated! Received: %s.", + websocketName, message)); + } else { + LOGGER.info("{} websocket authenticated.", websocketName); + } + if (authenticationMessageFuture != null) { + authenticationMessageFuture.complete(authenticated); + } + break; + case LISTENING: + message = getGson().fromJson(messageObject, ListeningMessage.class); + // Remove all 'TradeStreamMessageType's that are no longer listened to and add new ones + final List currentTypes = ((ListeningMessage) message).getData().getStreams(); + currentTypes.stream() + .filter(not(listenedMessageTypes::contains)) + .forEach(listenedMessageTypes::remove); + listenedMessageTypes.addAll(currentTypes); + break; + case TRADE_UPDATES: + message = getGson().fromJson(messageObject, TradeUpdateMessage.class); + break; + default: + throw new UnsupportedOperationException(); + } + + // Call listener + if (listenedMessageTypes.contains(messageType)) { + callListener(messageType, message); + } + } + + @Override + public void subscribe(TradesStreamMessageType... messageTypes) { + if (messageTypes == null || messageTypes.length == 0) { + return; + } + + // Add all non-subscribable 'messageTypes' before connecting or sending websocket message + Arrays.stream(messageTypes) + .filter(not(SUBSCRIBABLE_STREAMING_MESSAGE_TYPES::contains)) + .forEach(listenedMessageTypes::add); + + // Create streams subscription JSON message + final JsonObject requestObject = new JsonObject(); + requestObject.addProperty("action", "listen"); + final JsonArray streamsArray = new JsonArray(); + Arrays.stream(messageTypes) + .filter(SUBSCRIBABLE_STREAMING_MESSAGE_TYPES::contains) + .forEach((type) -> streamsArray.add(type.toString())); + if (streamsArray.isEmpty()) { + return; + } else if (!isConnected()) { + throw new IllegalStateException("This websocket must be connected before subscribing to streams!"); + } + final JsonObject dataObject = new JsonObject(); + dataObject.add("streams", streamsArray); + requestObject.add("data", dataObject); + + websocket.send(requestObject.toString()); + LOGGER.info("Requested streams: {}.", streamsArray); + } + + @Override + public Collection subscriptions() { + return new HashSet<>(listenedMessageTypes); + } +} diff --git a/src/main/java/net/jacobpeterson/alpaca/websocket/trades/TradesWebsocketInterface.java b/src/main/java/net/jacobpeterson/alpaca/websocket/trades/TradesWebsocketInterface.java new file mode 100644 index 00000000..b866e37e --- /dev/null +++ b/src/main/java/net/jacobpeterson/alpaca/websocket/trades/TradesWebsocketInterface.java @@ -0,0 +1,26 @@ +package net.jacobpeterson.alpaca.websocket.trades; + +import net.jacobpeterson.alpaca.model.websocket.trades.model.TradesStreamMessageType; +import net.jacobpeterson.alpaca.websocket.AlpacaWebsocketInterface; + +import java.util.Collection; + +/** + * {@link TradesWebsocketInterface} is an {@link AlpacaWebsocketInterface} for a {@link TradesWebsocket}. + */ +public interface TradesWebsocketInterface extends AlpacaWebsocketInterface { + + /** + * Sets the {@link TradesStreamMessageType}s for this stream. + * + * @param messageTypes the {@link TradesStreamMessageType}s + */ + void subscribe(TradesStreamMessageType... messageTypes); + + /** + * Gets all the currently subscribed {@link TradesStreamMessageType}s. + * + * @return a {@link Collection} of {@link TradesStreamMessageType} + */ + Collection subscriptions(); +} diff --git a/src/main/java/net/jacobpeterson/alpaca/endpoint/streaming/authorization/authorization_data.json b/src/main/java/net/jacobpeterson/alpaca/websocket/trades/model/authorization/authorization_data.json similarity index 69% rename from src/main/java/net/jacobpeterson/alpaca/endpoint/streaming/authorization/authorization_data.json rename to src/main/java/net/jacobpeterson/alpaca/websocket/trades/model/authorization/authorization_data.json index e88df4d5..f05ae9bd 100644 --- a/src/main/java/net/jacobpeterson/alpaca/endpoint/streaming/authorization/authorization_data.json +++ b/src/main/java/net/jacobpeterson/alpaca/websocket/trades/model/authorization/authorization_data.json @@ -1,6 +1,5 @@ { "type": "object", - "title": "See Streaming.", "properties": { "status": { "existingJavaType": "java.lang.String", diff --git a/src/main/java/net/jacobpeterson/alpaca/websocket/trades/model/authorization/authorization_message.json b/src/main/java/net/jacobpeterson/alpaca/websocket/trades/model/authorization/authorization_message.json new file mode 100644 index 00000000..82edef4d --- /dev/null +++ b/src/main/java/net/jacobpeterson/alpaca/websocket/trades/model/authorization/authorization_message.json @@ -0,0 +1,12 @@ +{ + "type": "object", + "extends": { + "existingJavaType": "net.jacobpeterson.alpaca.model.websocket.trades.model.TradesStreamMessage" + }, + "properties": { + "data": { + "existingJavaType": "net.jacobpeterson.alpaca.model.websocket.trades.model.authorization.AuthorizationData", + "title": "The {@link net.jacobpeterson.alpaca.model.websocket.trades.model.authorization.AuthorizationData}." + } + } +} diff --git a/src/main/java/net/jacobpeterson/alpaca/websocket/trades/model/listening/listening_data.json b/src/main/java/net/jacobpeterson/alpaca/websocket/trades/model/listening/listening_data.json new file mode 100644 index 00000000..78a68ba5 --- /dev/null +++ b/src/main/java/net/jacobpeterson/alpaca/websocket/trades/model/listening/listening_data.json @@ -0,0 +1,9 @@ +{ + "type": "object", + "properties": { + "streams": { + "existingJavaType": "java.util.ArrayList", + "title": "The {@link java.util.ArrayList} of {@link net.jacobpeterson.alpaca.model.websocket.trades.model.TradesStreamMessageType}s." + } + } +} diff --git a/src/main/java/net/jacobpeterson/alpaca/websocket/trades/model/listening/listening_message.json b/src/main/java/net/jacobpeterson/alpaca/websocket/trades/model/listening/listening_message.json new file mode 100644 index 00000000..58b7b54f --- /dev/null +++ b/src/main/java/net/jacobpeterson/alpaca/websocket/trades/model/listening/listening_message.json @@ -0,0 +1,12 @@ +{ + "type": "object", + "extends": { + "existingJavaType": "net.jacobpeterson.alpaca.model.websocket.trades.model.TradesStreamMessage" + }, + "properties": { + "data": { + "existingJavaType": "net.jacobpeterson.alpaca.model.websocket.trades.model.listening.ListeningData", + "title": "The {@link net.jacobpeterson.alpaca.model.websocket.trades.model.listening.ListeningData}." + } + } +} diff --git a/src/main/java/net/jacobpeterson/alpaca/websocket/trades/model/trades_stream_message.json b/src/main/java/net/jacobpeterson/alpaca/websocket/trades/model/trades_stream_message.json new file mode 100644 index 00000000..fa337195 --- /dev/null +++ b/src/main/java/net/jacobpeterson/alpaca/websocket/trades/model/trades_stream_message.json @@ -0,0 +1,9 @@ +{ + "type": "object", + "properties": { + "stream": { + "existingJavaType": "net.jacobpeterson.alpaca.model.websocket.trades.model.TradesStreamMessageType", + "title": "The {@link net.jacobpeterson.alpaca.model.websocket.trades.model.TradesStreamMessageType}." + } + } +} diff --git a/src/main/java/net/jacobpeterson/alpaca/websocket/trades/model/trades_stream_message_type.json b/src/main/java/net/jacobpeterson/alpaca/websocket/trades/model/trades_stream_message_type.json new file mode 100644 index 00000000..98ab6ea6 --- /dev/null +++ b/src/main/java/net/jacobpeterson/alpaca/websocket/trades/model/trades_stream_message_type.json @@ -0,0 +1,8 @@ +{ + "type": "string", + "enum": [ + "listening", + "authorization", + "trade_updates" + ] +} diff --git a/src/main/java/net/jacobpeterson/alpaca/endpoint/streaming/trade/trade_update.json b/src/main/java/net/jacobpeterson/alpaca/websocket/trades/model/tradeupdate/trade_update.json similarity index 52% rename from src/main/java/net/jacobpeterson/alpaca/endpoint/streaming/trade/trade_update.json rename to src/main/java/net/jacobpeterson/alpaca/websocket/trades/model/tradeupdate/trade_update.json index c0072275..ab5b6875 100644 --- a/src/main/java/net/jacobpeterson/alpaca/endpoint/streaming/trade/trade_update.json +++ b/src/main/java/net/jacobpeterson/alpaca/websocket/trades/model/tradeupdate/trade_update.json @@ -1,24 +1,32 @@ { "type": "object", - "title": "See Streaming.", "properties": { "event": { - "existingJavaType": "net.jacobpeterson.alpaca.model.endpoint.streaming.trade.enums.TradeUpdateEvent", - "title": "The {@link net.jacobpeterson.alpaca.model.endpoint.streaming.trade.enums.TradeUpdateEvent}." + "existingJavaType": "net.jacobpeterson.alpaca.model.websocket.trades.model.tradeupdate.TradeUpdateEvent", + "title": "The {@link net.jacobpeterson.alpaca.model.websocket.trades.model.tradeupdate.TradeUpdateEvent}." + }, + "execution_id": { + "existingJavaType": "java.lang.String", + "title": "The execution ID." }, "price": { "existingJavaType": "java.lang.String", "title": "The price." }, - "timestamp": { - "existingJavaType": "java.time.ZonedDateTime", - "title": "The timestamp." - }, "position_qty": { "existingJavaType": "java.lang.String", "javaName": "positionQuantity", "title": "The position quantity." }, + "qty": { + "existingJavaType": "java.lang.String", + "javaName": "quantity", + "title": "The quantity." + }, + "timestamp": { + "existingJavaType": "java.time.OffsetDateTime", + "title": "The timestamp." + }, "order": { "existingJavaType": "net.jacobpeterson.alpaca.openapi.trader.model.Order", "title": "The {@link net.jacobpeterson.alpaca.openapi.trader.model.Order}." diff --git a/src/main/java/net/jacobpeterson/alpaca/endpoint/streaming/trade/enums/trade_update_event.json b/src/main/java/net/jacobpeterson/alpaca/websocket/trades/model/tradeupdate/trade_update_event.json similarity index 83% rename from src/main/java/net/jacobpeterson/alpaca/endpoint/streaming/trade/enums/trade_update_event.json rename to src/main/java/net/jacobpeterson/alpaca/websocket/trades/model/tradeupdate/trade_update_event.json index e422fe52..1277728e 100644 --- a/src/main/java/net/jacobpeterson/alpaca/endpoint/streaming/trade/enums/trade_update_event.json +++ b/src/main/java/net/jacobpeterson/alpaca/websocket/trades/model/tradeupdate/trade_update_event.json @@ -1,6 +1,5 @@ { "type": "string", - "title": "See Orders.", "enum": [ "new", "fill", @@ -24,13 +23,13 @@ "description": "Sent when an order has been routed to exchanges for execution." }, { - "description": "Sent when your order has been completely filled." + "description": "Sent when an order has been completely filled." }, { "description": "Sent when a number of shares less than the total remaining quantity on your order has been filled." }, { - "description": "Sent when your requested cancelation of an order is processed." + "description": "Sent when your requested cancellation of an order is processed." }, { "description": "Sent when an order has reached the end of its lifespan, as determined by the order’s time in force value." @@ -51,7 +50,7 @@ "description": "Sent when your order has been stopped, and a trade is guaranteed for the order, usually at a stated price or better, but has not yet occurred." }, { - "description": "Sent when the order is awaiting cancelation. Most cancelations will occur without the order entering this state." + "description": "Sent when the order is awaiting cancellation. Most cancellations will occur without the order entering this state." }, { "description": "Sent when the order is awaiting replacement." diff --git a/src/main/java/net/jacobpeterson/alpaca/websocket/trades/model/tradeupdate/trade_update_message.json b/src/main/java/net/jacobpeterson/alpaca/websocket/trades/model/tradeupdate/trade_update_message.json new file mode 100644 index 00000000..6876c589 --- /dev/null +++ b/src/main/java/net/jacobpeterson/alpaca/websocket/trades/model/tradeupdate/trade_update_message.json @@ -0,0 +1,12 @@ +{ + "type": "object", + "extends": { + "existingJavaType": "net.jacobpeterson.alpaca.model.websocket.trades.model.TradesStreamMessage" + }, + "properties": { + "data": { + "existingJavaType": "net.jacobpeterson.alpaca.model.websocket.trades.model.tradeupdate.TradeUpdate", + "title": "The {@link net.jacobpeterson.alpaca.model.websocket.trades.model.tradeupdate.TradeUpdate}." + } + } +} From 27e951b2aa2f3c5934c8b2809afb1a07fbfe0b35 Mon Sep 17 00:00:00 2001 From: Jacob Peterson <14127217+Petersoj@users.noreply.github.com> Date: Mon, 4 Mar 2024 21:54:44 -0800 Subject: [PATCH 209/271] WIP update market data websocket POJO JSON schemas --- .../common/historical/bar/bar.json | 41 ----------- .../historical/bar/enums/bar_time_period.json | 28 -------- .../common/historical/quote/quote.json | 21 ------ .../common/historical/trade/trade.json | 21 ------ .../control/subscriptions_message.json | 25 ------- .../realtime/control/success_message.json | 14 ---- .../common/realtime/market_data_message.json | 11 --- .../common/realtime/quote/quote_message.json | 24 ------- .../common/realtime/symbol_message.json | 14 ---- .../common/realtime/trade/trade_message.json | 24 ------- .../crypto/historical/bar/crypto_bar.json | 14 ---- .../historical/bar/crypto_bars_response.json | 14 ---- .../bar/latest_crypto_bars_response.json | 10 --- .../orderbook/crypto_orderbook.json | 21 ------ .../latest_crypto_orderbooks_response.json | 10 --- .../crypto/historical/quote/crypto_quote.json | 19 ------ .../quote/latest_crypto_quotes_response.json | 10 --- .../historical/snapshot/crypto_snapshot.json | 31 --------- .../snapshot/crypto_snapshots_response.json | 10 --- .../crypto/historical/trade/crypto_trade.json | 19 ------ .../trade/crypto_trades_response.json | 14 ---- .../trade/latest_crypto_trades_response.json | 10 --- .../realtime/bar/crypto_bar_message.json | 14 ---- .../realtime/quote/crypto_quote_message.json | 19 ------ .../realtime/trade/crypto_trade_message.json | 19 ------ .../market_data/news/common/image.json | 13 ---- .../market_data/news/common/news_article.json | 39 ----------- .../market_data/news/news_response.json | 12 ---- .../news/realtime/news_message.json | 40 ----------- .../historical/bar/enums/bar_adjustment.json | 24 ------- .../stock/historical/bar/enums/bar_feed.json | 8 --- .../bar/multi_stock_bars_response.json | 14 ---- .../stock/historical/bar/stock_bar.json | 14 ---- .../historical/bar/stock_bars_response.json | 18 ----- .../quote/latest_stock_quote_response.json | 14 ---- .../stock/historical/quote/stock_quote.json | 34 ---------- .../quote/stock_quotes_response.json | 18 ----- .../stock/historical/snapshot/snapshot.json | 26 ------- .../trade/latest_stock_trade_response.json | 14 ---- .../stock/historical/trade/stock_trade.json | 29 -------- .../trade/stock_trades_response.json | 18 ----- .../stock/realtime/bar/stock_bar_message.json | 14 ---- .../realtime/quote/stock_quote_message.json | 39 ----------- .../realtime/trade/stock_trade_message.json | 29 -------- .../model}/control/error_message.json | 3 +- .../model/control/success_message.json | 13 ++++ .../model/control/success_message_type.json | 7 ++ .../marketdata/model/market_data_message.json | 4 ++ .../crypto/CryptoMarketDataWebsocket.java | 27 ++++++++ .../crypto/model/bar/crypto_bar_message.json | 53 +++++++++++++++ .../model/crypto_market_data_message.json | 13 ++++ .../crypto_market_data_message_type.json} | 19 ++++-- .../orderbook/crypto_orderbook_entry.json | 5 +- .../orderbook/crypto_orderbook_message.json | 28 ++++++++ .../model/quote/crypto_quote_message.json | 38 +++++++++++ .../crypto_subscriptions_message.json | 33 +++++++++ .../model/trade/crypto_trade_message.json | 38 +++++++++++ .../model/trade/crypto_trade_taker_side.json} | 1 - .../streams/news/NewsMarketDataWebsocket.java | 15 ++++ .../streams/news/model/news/news_message.json | 38 +++++++++++ .../news/model/news_market_data_message.json | 13 ++++ .../model/news_market_data_message_type.json | 23 +++++++ .../stock/StockMarketDataWebsocket.java | 30 ++++++++ .../stock/model/bar/stock_bar_message.json} | 27 +++++--- ...tock_limit_up_limit_down_band_message.json | 38 +++++++++++ .../model/quote/stock_quote_message.json | 58 ++++++++++++++++ .../model/stock_market_data_message.json | 13 ++++ .../model/stock_market_data_message_type.json | 55 +++++++++++++++ .../stock_subscriptions_message.json | 49 +++++++++++++ .../model/trade/stock_trade_message.json | 48 +++++++++++++ .../stock_trade_cancel_error_action.json | 15 ++++ .../stock_trade_cancel_error_message.json | 48 +++++++++++++ .../stock_trade_correction_message.json | 68 +++++++++++++++++++ .../stock_trading_status_message.json | 43 ++++++++++++ .../websocket/trades/TradesWebsocket.java | 10 ++- 75 files changed, 846 insertions(+), 901 deletions(-) delete mode 100644 src/main/java/net/jacobpeterson/alpaca/endpoint/market_data/common/historical/bar/bar.json delete mode 100644 src/main/java/net/jacobpeterson/alpaca/endpoint/market_data/common/historical/bar/enums/bar_time_period.json delete mode 100644 src/main/java/net/jacobpeterson/alpaca/endpoint/market_data/common/historical/quote/quote.json delete mode 100644 src/main/java/net/jacobpeterson/alpaca/endpoint/market_data/common/historical/trade/trade.json delete mode 100644 src/main/java/net/jacobpeterson/alpaca/endpoint/market_data/common/realtime/control/subscriptions_message.json delete mode 100644 src/main/java/net/jacobpeterson/alpaca/endpoint/market_data/common/realtime/control/success_message.json delete mode 100644 src/main/java/net/jacobpeterson/alpaca/endpoint/market_data/common/realtime/market_data_message.json delete mode 100644 src/main/java/net/jacobpeterson/alpaca/endpoint/market_data/common/realtime/quote/quote_message.json delete mode 100644 src/main/java/net/jacobpeterson/alpaca/endpoint/market_data/common/realtime/symbol_message.json delete mode 100644 src/main/java/net/jacobpeterson/alpaca/endpoint/market_data/common/realtime/trade/trade_message.json delete mode 100644 src/main/java/net/jacobpeterson/alpaca/endpoint/market_data/crypto/historical/bar/crypto_bar.json delete mode 100644 src/main/java/net/jacobpeterson/alpaca/endpoint/market_data/crypto/historical/bar/crypto_bars_response.json delete mode 100644 src/main/java/net/jacobpeterson/alpaca/endpoint/market_data/crypto/historical/bar/latest_crypto_bars_response.json delete mode 100644 src/main/java/net/jacobpeterson/alpaca/endpoint/market_data/crypto/historical/orderbook/crypto_orderbook.json delete mode 100644 src/main/java/net/jacobpeterson/alpaca/endpoint/market_data/crypto/historical/orderbook/latest_crypto_orderbooks_response.json delete mode 100644 src/main/java/net/jacobpeterson/alpaca/endpoint/market_data/crypto/historical/quote/crypto_quote.json delete mode 100644 src/main/java/net/jacobpeterson/alpaca/endpoint/market_data/crypto/historical/quote/latest_crypto_quotes_response.json delete mode 100644 src/main/java/net/jacobpeterson/alpaca/endpoint/market_data/crypto/historical/snapshot/crypto_snapshot.json delete mode 100644 src/main/java/net/jacobpeterson/alpaca/endpoint/market_data/crypto/historical/snapshot/crypto_snapshots_response.json delete mode 100644 src/main/java/net/jacobpeterson/alpaca/endpoint/market_data/crypto/historical/trade/crypto_trade.json delete mode 100644 src/main/java/net/jacobpeterson/alpaca/endpoint/market_data/crypto/historical/trade/crypto_trades_response.json delete mode 100644 src/main/java/net/jacobpeterson/alpaca/endpoint/market_data/crypto/historical/trade/latest_crypto_trades_response.json delete mode 100644 src/main/java/net/jacobpeterson/alpaca/endpoint/market_data/crypto/realtime/bar/crypto_bar_message.json delete mode 100644 src/main/java/net/jacobpeterson/alpaca/endpoint/market_data/crypto/realtime/quote/crypto_quote_message.json delete mode 100644 src/main/java/net/jacobpeterson/alpaca/endpoint/market_data/crypto/realtime/trade/crypto_trade_message.json delete mode 100644 src/main/java/net/jacobpeterson/alpaca/endpoint/market_data/news/common/image.json delete mode 100644 src/main/java/net/jacobpeterson/alpaca/endpoint/market_data/news/common/news_article.json delete mode 100644 src/main/java/net/jacobpeterson/alpaca/endpoint/market_data/news/news_response.json delete mode 100644 src/main/java/net/jacobpeterson/alpaca/endpoint/market_data/news/realtime/news_message.json delete mode 100644 src/main/java/net/jacobpeterson/alpaca/endpoint/market_data/stock/historical/bar/enums/bar_adjustment.json delete mode 100644 src/main/java/net/jacobpeterson/alpaca/endpoint/market_data/stock/historical/bar/enums/bar_feed.json delete mode 100644 src/main/java/net/jacobpeterson/alpaca/endpoint/market_data/stock/historical/bar/multi_stock_bars_response.json delete mode 100644 src/main/java/net/jacobpeterson/alpaca/endpoint/market_data/stock/historical/bar/stock_bar.json delete mode 100644 src/main/java/net/jacobpeterson/alpaca/endpoint/market_data/stock/historical/bar/stock_bars_response.json delete mode 100644 src/main/java/net/jacobpeterson/alpaca/endpoint/market_data/stock/historical/quote/latest_stock_quote_response.json delete mode 100644 src/main/java/net/jacobpeterson/alpaca/endpoint/market_data/stock/historical/quote/stock_quote.json delete mode 100644 src/main/java/net/jacobpeterson/alpaca/endpoint/market_data/stock/historical/quote/stock_quotes_response.json delete mode 100644 src/main/java/net/jacobpeterson/alpaca/endpoint/market_data/stock/historical/snapshot/snapshot.json delete mode 100644 src/main/java/net/jacobpeterson/alpaca/endpoint/market_data/stock/historical/trade/latest_stock_trade_response.json delete mode 100644 src/main/java/net/jacobpeterson/alpaca/endpoint/market_data/stock/historical/trade/stock_trade.json delete mode 100644 src/main/java/net/jacobpeterson/alpaca/endpoint/market_data/stock/historical/trade/stock_trades_response.json delete mode 100644 src/main/java/net/jacobpeterson/alpaca/endpoint/market_data/stock/realtime/bar/stock_bar_message.json delete mode 100644 src/main/java/net/jacobpeterson/alpaca/endpoint/market_data/stock/realtime/quote/stock_quote_message.json delete mode 100644 src/main/java/net/jacobpeterson/alpaca/endpoint/market_data/stock/realtime/trade/stock_trade_message.json rename src/main/java/net/jacobpeterson/alpaca/{endpoint/market_data/common/realtime => websocket/marketdata/model}/control/error_message.json (57%) create mode 100644 src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/model/control/success_message.json create mode 100644 src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/model/control/success_message_type.json create mode 100644 src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/model/market_data_message.json create mode 100644 src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/streams/crypto/CryptoMarketDataWebsocket.java create mode 100644 src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/streams/crypto/model/bar/crypto_bar_message.json create mode 100644 src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/streams/crypto/model/crypto_market_data_message.json rename src/main/java/net/jacobpeterson/alpaca/{endpoint/market_data/common/realtime/enums/market_data_message_type.json => websocket/marketdata/streams/crypto/model/crypto_market_data_message_type.json} (58%) rename src/main/java/net/jacobpeterson/alpaca/{endpoint/market_data/crypto/historical => websocket/marketdata/streams/crypto/model}/orderbook/crypto_orderbook_entry.json (57%) create mode 100644 src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/streams/crypto/model/orderbook/crypto_orderbook_message.json create mode 100644 src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/streams/crypto/model/quote/crypto_quote_message.json create mode 100644 src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/streams/crypto/model/subscriptions/crypto_subscriptions_message.json create mode 100644 src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/streams/crypto/model/trade/crypto_trade_message.json rename src/main/java/net/jacobpeterson/alpaca/{endpoint/market_data/crypto/common/enums/taker_side.json => websocket/marketdata/streams/crypto/model/trade/crypto_trade_taker_side.json} (52%) create mode 100644 src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/streams/news/NewsMarketDataWebsocket.java create mode 100644 src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/streams/news/model/news/news_message.json create mode 100644 src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/streams/news/model/news_market_data_message.json create mode 100644 src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/streams/news/model/news_market_data_message_type.json create mode 100644 src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/streams/stock/StockMarketDataWebsocket.java rename src/main/java/net/jacobpeterson/alpaca/{endpoint/market_data/common/realtime/bar/bar_message.json => websocket/marketdata/streams/stock/model/bar/stock_bar_message.json} (51%) create mode 100644 src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/streams/stock/model/limituplimitdownband/stock_limit_up_limit_down_band_message.json create mode 100644 src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/streams/stock/model/quote/stock_quote_message.json create mode 100644 src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/streams/stock/model/stock_market_data_message.json create mode 100644 src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/streams/stock/model/stock_market_data_message_type.json create mode 100644 src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/streams/stock/model/subscriptions/stock_subscriptions_message.json create mode 100644 src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/streams/stock/model/trade/stock_trade_message.json create mode 100644 src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/streams/stock/model/tradecancelerror/stock_trade_cancel_error_action.json create mode 100644 src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/streams/stock/model/tradecancelerror/stock_trade_cancel_error_message.json create mode 100644 src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/streams/stock/model/tradecorrection/stock_trade_correction_message.json create mode 100644 src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/streams/stock/model/tradingstatus/stock_trading_status_message.json diff --git a/src/main/java/net/jacobpeterson/alpaca/endpoint/market_data/common/historical/bar/bar.json b/src/main/java/net/jacobpeterson/alpaca/endpoint/market_data/common/historical/bar/bar.json deleted file mode 100644 index b2f3b42f..00000000 --- a/src/main/java/net/jacobpeterson/alpaca/endpoint/market_data/common/historical/bar/bar.json +++ /dev/null @@ -1,41 +0,0 @@ -{ - "type": "object", - "title": "See Market Data.", - "properties": { - "t": { - "existingJavaType": "java.time.ZonedDateTime", - "javaName": "timestamp", - "title": "Timestamp with nanosecond precision." - }, - "o": { - "existingJavaType": "java.lang.Double", - "javaName": "open", - "title": "Open price." - }, - "h": { - "existingJavaType": "java.lang.Double", - "javaName": "high", - "title": "High price." - }, - "l": { - "existingJavaType": "java.lang.Double", - "javaName": "low", - "title": "Low price." - }, - "c": { - "existingJavaType": "java.lang.Double", - "javaName": "close", - "title": "Close price." - }, - "n": { - "existingJavaType": "java.lang.Long", - "javaName": "tradeCount", - "title": "Trade count." - }, - "vw": { - "existingJavaType": "java.lang.Double", - "javaName": "vwap", - "title": "VWAP (Volume Weighted Average Price)." - } - } -} diff --git a/src/main/java/net/jacobpeterson/alpaca/endpoint/market_data/common/historical/bar/enums/bar_time_period.json b/src/main/java/net/jacobpeterson/alpaca/endpoint/market_data/common/historical/bar/enums/bar_time_period.json deleted file mode 100644 index 0305b24e..00000000 --- a/src/main/java/net/jacobpeterson/alpaca/endpoint/market_data/common/historical/bar/enums/bar_time_period.json +++ /dev/null @@ -1,28 +0,0 @@ -{ - "type": "string", - "title": "See Market Data.", - "enum": [ - "Min", - "Hour", - "Day", - "Week", - "Month" - ], - "javaEnums": [ - { - "name": "MINUTE" - }, - { - "name": "HOUR" - }, - { - "name": "DAY" - }, - { - "name": "WEEK" - }, - { - "name": "MONTH" - } - ] -} diff --git a/src/main/java/net/jacobpeterson/alpaca/endpoint/market_data/common/historical/quote/quote.json b/src/main/java/net/jacobpeterson/alpaca/endpoint/market_data/common/historical/quote/quote.json deleted file mode 100644 index ed75e88f..00000000 --- a/src/main/java/net/jacobpeterson/alpaca/endpoint/market_data/common/historical/quote/quote.json +++ /dev/null @@ -1,21 +0,0 @@ -{ - "type": "object", - "title": "See Market Data.", - "properties": { - "t": { - "existingJavaType": "java.time.ZonedDateTime", - "javaName": "timestamp", - "title": "Timestamp with nanosecond precision." - }, - "ap": { - "existingJavaType": "java.lang.Double", - "javaName": "askPrice", - "title": "Ask price." - }, - "bp": { - "existingJavaType": "java.lang.Double", - "javaName": "bidPrice", - "title": "Bid price." - } - } -} diff --git a/src/main/java/net/jacobpeterson/alpaca/endpoint/market_data/common/historical/trade/trade.json b/src/main/java/net/jacobpeterson/alpaca/endpoint/market_data/common/historical/trade/trade.json deleted file mode 100644 index 1cacafa5..00000000 --- a/src/main/java/net/jacobpeterson/alpaca/endpoint/market_data/common/historical/trade/trade.json +++ /dev/null @@ -1,21 +0,0 @@ -{ - "type": "object", - "title": "See Market Data.", - "properties": { - "t": { - "existingJavaType": "java.time.ZonedDateTime", - "javaName": "timestamp", - "title": "Timestamp with nanosecond precision." - }, - "p": { - "existingJavaType": "java.lang.Double", - "javaName": "price", - "title": "Trade price." - }, - "i": { - "existingJavaType": "java.lang.Long", - "javaName": "tradeID", - "title": "Trade ID." - } - } -} diff --git a/src/main/java/net/jacobpeterson/alpaca/endpoint/market_data/common/realtime/control/subscriptions_message.json b/src/main/java/net/jacobpeterson/alpaca/endpoint/market_data/common/realtime/control/subscriptions_message.json deleted file mode 100644 index b44cbb85..00000000 --- a/src/main/java/net/jacobpeterson/alpaca/endpoint/market_data/common/realtime/control/subscriptions_message.json +++ /dev/null @@ -1,25 +0,0 @@ -{ - "type": "object", - "extends": { - "existingJavaType": "net.jacobpeterson.alpaca.model.endpoint.marketdata.common.realtime.MarketDataMessage" - }, - "title": "See Market Data.", - "properties": { - "trades": { - "existingJavaType": "java.util.ArrayList", - "title": "The {@link java.util.ArrayList} of subscribed trades." - }, - "quotes": { - "existingJavaType": "java.util.ArrayList", - "title": "The {@link java.util.ArrayList} of subscribed quotes." - }, - "bars": { - "existingJavaType": "java.util.ArrayList", - "title": "The {@link java.util.ArrayList} of subscribed bars." - }, - "news": { - "existingJavaType": "java.util.ArrayList", - "title": "The {@link java.util.ArrayList} of subscribed news." - } - } -} diff --git a/src/main/java/net/jacobpeterson/alpaca/endpoint/market_data/common/realtime/control/success_message.json b/src/main/java/net/jacobpeterson/alpaca/endpoint/market_data/common/realtime/control/success_message.json deleted file mode 100644 index 2edd335b..00000000 --- a/src/main/java/net/jacobpeterson/alpaca/endpoint/market_data/common/realtime/control/success_message.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "type": "object", - "extends": { - "existingJavaType": "net.jacobpeterson.alpaca.model.endpoint.marketdata.common.realtime.MarketDataMessage" - }, - "title": "See Market Data.", - "properties": { - "msg": { - "existingJavaType": "java.lang.String", - "javaName": "message", - "title": "The success message." - } - } -} diff --git a/src/main/java/net/jacobpeterson/alpaca/endpoint/market_data/common/realtime/market_data_message.json b/src/main/java/net/jacobpeterson/alpaca/endpoint/market_data/common/realtime/market_data_message.json deleted file mode 100644 index d41cd481..00000000 --- a/src/main/java/net/jacobpeterson/alpaca/endpoint/market_data/common/realtime/market_data_message.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "type": "object", - "title": "See Market Data.", - "properties": { - "T": { - "existingJavaType": "net.jacobpeterson.alpaca.model.endpoint.marketdata.common.realtime.enums.MarketDataMessageType", - "javaName": "messageType", - "title": "The {@link net.jacobpeterson.alpaca.model.endpoint.marketdata.common.realtime.enums.MarketDataMessageType}." - } - } -} diff --git a/src/main/java/net/jacobpeterson/alpaca/endpoint/market_data/common/realtime/quote/quote_message.json b/src/main/java/net/jacobpeterson/alpaca/endpoint/market_data/common/realtime/quote/quote_message.json deleted file mode 100644 index d6bdaabc..00000000 --- a/src/main/java/net/jacobpeterson/alpaca/endpoint/market_data/common/realtime/quote/quote_message.json +++ /dev/null @@ -1,24 +0,0 @@ -{ - "type": "object", - "extends": { - "existingJavaType": "net.jacobpeterson.alpaca.model.endpoint.marketdata.common.realtime.SymbolMessage" - }, - "title": "See Market Data.", - "properties": { - "ap": { - "existingJavaType": "java.lang.Double", - "javaName": "askPrice", - "title": "Ask price." - }, - "bp": { - "existingJavaType": "java.lang.Double", - "javaName": "bidPrice", - "title": "Bid price." - }, - "t": { - "existingJavaType": "java.time.ZonedDateTime", - "javaName": "timestamp", - "title": "Timestamp with nanosecond precision." - } - } -} diff --git a/src/main/java/net/jacobpeterson/alpaca/endpoint/market_data/common/realtime/symbol_message.json b/src/main/java/net/jacobpeterson/alpaca/endpoint/market_data/common/realtime/symbol_message.json deleted file mode 100644 index a6f40f69..00000000 --- a/src/main/java/net/jacobpeterson/alpaca/endpoint/market_data/common/realtime/symbol_message.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "type": "object", - "extends": { - "existingJavaType": "net.jacobpeterson.alpaca.model.endpoint.marketdata.common.realtime.MarketDataMessage" - }, - "title": "See Market Data.", - "properties": { - "S": { - "existingJavaType": "java.lang.String", - "javaName": "symbol", - "title": "The symbol." - } - } -} diff --git a/src/main/java/net/jacobpeterson/alpaca/endpoint/market_data/common/realtime/trade/trade_message.json b/src/main/java/net/jacobpeterson/alpaca/endpoint/market_data/common/realtime/trade/trade_message.json deleted file mode 100644 index ca01450f..00000000 --- a/src/main/java/net/jacobpeterson/alpaca/endpoint/market_data/common/realtime/trade/trade_message.json +++ /dev/null @@ -1,24 +0,0 @@ -{ - "type": "object", - "extends": { - "existingJavaType": "net.jacobpeterson.alpaca.model.endpoint.marketdata.common.realtime.SymbolMessage" - }, - "title": "See Market Data.", - "properties": { - "i": { - "existingJavaType": "java.lang.Integer", - "javaName": "tradeID", - "title": "Trade ID." - }, - "p": { - "existingJavaType": "java.lang.Double", - "javaName": "price", - "title": "Trade price." - }, - "t": { - "existingJavaType": "java.time.ZonedDateTime", - "javaName": "timestamp", - "title": "Timestamp with nanosecond precision." - } - } -} diff --git a/src/main/java/net/jacobpeterson/alpaca/endpoint/market_data/crypto/historical/bar/crypto_bar.json b/src/main/java/net/jacobpeterson/alpaca/endpoint/market_data/crypto/historical/bar/crypto_bar.json deleted file mode 100644 index 1fbf8b5d..00000000 --- a/src/main/java/net/jacobpeterson/alpaca/endpoint/market_data/crypto/historical/bar/crypto_bar.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "type": "object", - "extends": { - "existingJavaType": "net.jacobpeterson.alpaca.model.endpoint.marketdata.common.historical.bar.Bar" - }, - "title": "See Historical Crypto Data.", - "properties": { - "v": { - "existingJavaType": "java.lang.Double", - "javaName": "volume", - "title": "Volume." - } - } -} diff --git a/src/main/java/net/jacobpeterson/alpaca/endpoint/market_data/crypto/historical/bar/crypto_bars_response.json b/src/main/java/net/jacobpeterson/alpaca/endpoint/market_data/crypto/historical/bar/crypto_bars_response.json deleted file mode 100644 index 8461d5cc..00000000 --- a/src/main/java/net/jacobpeterson/alpaca/endpoint/market_data/crypto/historical/bar/crypto_bars_response.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "type": "object", - "title": "See Historical Crypto Data.", - "properties": { - "bars": { - "existingJavaType": "java.util.Map>", - "title": "The {@link java.util.ArrayList} of {@link net.jacobpeterson.alpaca.model.endpoint.marketdata.crypto.historical.bar.CryptoBar}s." - }, - "next_page_token": { - "existingJavaType": "java.lang.String", - "title": "Token that can be used to query the next page." - } - } -} diff --git a/src/main/java/net/jacobpeterson/alpaca/endpoint/market_data/crypto/historical/bar/latest_crypto_bars_response.json b/src/main/java/net/jacobpeterson/alpaca/endpoint/market_data/crypto/historical/bar/latest_crypto_bars_response.json deleted file mode 100644 index b502ddc3..00000000 --- a/src/main/java/net/jacobpeterson/alpaca/endpoint/market_data/crypto/historical/bar/latest_crypto_bars_response.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "type": "object", - "title": "See Historical Crypto Data.", - "properties": { - "bars": { - "existingJavaType": "java.util.Map", - "title": "The {@link java.util.Map} of {@link net.jacobpeterson.alpaca.model.endpoint.marketdata.crypto.historical.bar.CryptoBar}s." - } - } -} diff --git a/src/main/java/net/jacobpeterson/alpaca/endpoint/market_data/crypto/historical/orderbook/crypto_orderbook.json b/src/main/java/net/jacobpeterson/alpaca/endpoint/market_data/crypto/historical/orderbook/crypto_orderbook.json deleted file mode 100644 index 04409010..00000000 --- a/src/main/java/net/jacobpeterson/alpaca/endpoint/market_data/crypto/historical/orderbook/crypto_orderbook.json +++ /dev/null @@ -1,21 +0,0 @@ -{ - "type": "object", - "title": "See Latest Orderbook.", - "properties": { - "t": { - "existingJavaType": "java.time.ZonedDateTime", - "javaName": "timestamp", - "title": "Timestamp in RFC-3339 format with nanosecond precision." - }, - "b": { - "existingJavaType": "java.util.ArrayList", - "javaName": "bidSide", - "title": "An {@link java.util.ArrayList} of {@link net.jacobpeterson.alpaca.model.endpoint.marketdata.crypto.historical.orderbook.CryptoOrderbookEntry}s for the buy/bid side." - }, - "a": { - "existingJavaType": "java.util.ArrayList", - "javaName": "askSide", - "title": "An {@link java.util.ArrayList} of {@link net.jacobpeterson.alpaca.model.endpoint.marketdata.crypto.historical.orderbook.CryptoOrderbookEntry}s for the ask/sell side." - } - } -} diff --git a/src/main/java/net/jacobpeterson/alpaca/endpoint/market_data/crypto/historical/orderbook/latest_crypto_orderbooks_response.json b/src/main/java/net/jacobpeterson/alpaca/endpoint/market_data/crypto/historical/orderbook/latest_crypto_orderbooks_response.json deleted file mode 100644 index 31c605d3..00000000 --- a/src/main/java/net/jacobpeterson/alpaca/endpoint/market_data/crypto/historical/orderbook/latest_crypto_orderbooks_response.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "type": "object", - "title": "See Historical Crypto Data.", - "properties": { - "orderbooks": { - "existingJavaType": "java.util.Map", - "title": "The {@link java.util.Map} of {@link net.jacobpeterson.alpaca.model.endpoint.marketdata.crypto.historical.orderbook.CryptoOrderbook}s." - } - } -} diff --git a/src/main/java/net/jacobpeterson/alpaca/endpoint/market_data/crypto/historical/quote/crypto_quote.json b/src/main/java/net/jacobpeterson/alpaca/endpoint/market_data/crypto/historical/quote/crypto_quote.json deleted file mode 100644 index 97e50e0b..00000000 --- a/src/main/java/net/jacobpeterson/alpaca/endpoint/market_data/crypto/historical/quote/crypto_quote.json +++ /dev/null @@ -1,19 +0,0 @@ -{ - "type": "object", - "extends": { - "existingJavaType": "net.jacobpeterson.alpaca.model.endpoint.marketdata.common.historical.quote.Quote" - }, - "title": "See Historical Crypto Data.", - "properties": { - "as": { - "existingJavaType": "java.lang.Double", - "javaName": "askSize", - "title": "Ask size." - }, - "bs": { - "existingJavaType": "java.lang.Double", - "javaName": "bidSize", - "title": "Bid size." - } - } -} diff --git a/src/main/java/net/jacobpeterson/alpaca/endpoint/market_data/crypto/historical/quote/latest_crypto_quotes_response.json b/src/main/java/net/jacobpeterson/alpaca/endpoint/market_data/crypto/historical/quote/latest_crypto_quotes_response.json deleted file mode 100644 index 995ccd02..00000000 --- a/src/main/java/net/jacobpeterson/alpaca/endpoint/market_data/crypto/historical/quote/latest_crypto_quotes_response.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "type": "object", - "title": "See Historical Crypto Data.", - "properties": { - "quotes": { - "existingJavaType": "java.util.Map", - "title": "The {@link java.util.Map} of {@link net.jacobpeterson.alpaca.model.endpoint.marketdata.crypto.historical.quote.CryptoQuote}s." - } - } -} diff --git a/src/main/java/net/jacobpeterson/alpaca/endpoint/market_data/crypto/historical/snapshot/crypto_snapshot.json b/src/main/java/net/jacobpeterson/alpaca/endpoint/market_data/crypto/historical/snapshot/crypto_snapshot.json deleted file mode 100644 index 9ff6ab0d..00000000 --- a/src/main/java/net/jacobpeterson/alpaca/endpoint/market_data/crypto/historical/snapshot/crypto_snapshot.json +++ /dev/null @@ -1,31 +0,0 @@ -{ - "type": "object", - "title": "See Snapshots.", - "properties": { - "dailyBar": { - "existingJavaType": "net.jacobpeterson.alpaca.model.endpoint.marketdata.crypto.historical.bar.CryptoBar", - "javaName": "dailyBar", - "title": "OHLC aggregate of all the trades in a given interval." - }, - "latestQuote": { - "existingJavaType": "net.jacobpeterson.alpaca.model.endpoint.marketdata.crypto.historical.quote.CryptoQuote", - "javaName": "latestQuote", - "title": "The best bid and ask information for a given security." - }, - "latestTrade": { - "existingJavaType": "net.jacobpeterson.alpaca.model.endpoint.marketdata.crypto.historical.trade.CryptoTrade", - "javaName": "latestTrade", - "title": "A crypto trade" - }, - "minuteBar": { - "existingJavaType": "net.jacobpeterson.alpaca.model.endpoint.marketdata.crypto.historical.bar.CryptoBar", - "javaName": "minuteBar", - "title": "OHLC aggregate of all the trades in a given interval." - }, - "prevDailyBar": { - "existingJavaType": "net.jacobpeterson.alpaca.model.endpoint.marketdata.crypto.historical.bar.CryptoBar", - "javaName": "prevDailyBar", - "title": "OHLC aggregate of all the trades in a given interval." - } - } -} diff --git a/src/main/java/net/jacobpeterson/alpaca/endpoint/market_data/crypto/historical/snapshot/crypto_snapshots_response.json b/src/main/java/net/jacobpeterson/alpaca/endpoint/market_data/crypto/historical/snapshot/crypto_snapshots_response.json deleted file mode 100644 index 3ec3f3b8..00000000 --- a/src/main/java/net/jacobpeterson/alpaca/endpoint/market_data/crypto/historical/snapshot/crypto_snapshots_response.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "type": "object", - "title": "See Historical Crypto Data.", - "properties": { - "snapshots": { - "existingJavaType": "java.util.Map", - "title": "The {@link java.util.Map} of {@link net.jacobpeterson.alpaca.model.endpoint.marketdata.crypto.historical.snapshot.CryptoSnapshot}s." - } - } -} diff --git a/src/main/java/net/jacobpeterson/alpaca/endpoint/market_data/crypto/historical/trade/crypto_trade.json b/src/main/java/net/jacobpeterson/alpaca/endpoint/market_data/crypto/historical/trade/crypto_trade.json deleted file mode 100644 index 8e5bf0da..00000000 --- a/src/main/java/net/jacobpeterson/alpaca/endpoint/market_data/crypto/historical/trade/crypto_trade.json +++ /dev/null @@ -1,19 +0,0 @@ -{ - "type": "object", - "extends": { - "existingJavaType": "net.jacobpeterson.alpaca.model.endpoint.marketdata.common.historical.trade.Trade" - }, - "title": "See Historical Crypto Data.", - "properties": { - "s": { - "existingJavaType": "java.lang.Double", - "javaName": "size", - "title": "Trade size." - }, - "tks": { - "existingJavaType": "net.jacobpeterson.alpaca.model.endpoint.marketdata.crypto.common.enums.TakerSide", - "javaName": "takerSide", - "title": "The {@link net.jacobpeterson.alpaca.model.endpoint.marketdata.crypto.common.enums.TakerSide} of the trade." - } - } -} diff --git a/src/main/java/net/jacobpeterson/alpaca/endpoint/market_data/crypto/historical/trade/crypto_trades_response.json b/src/main/java/net/jacobpeterson/alpaca/endpoint/market_data/crypto/historical/trade/crypto_trades_response.json deleted file mode 100644 index 0a007487..00000000 --- a/src/main/java/net/jacobpeterson/alpaca/endpoint/market_data/crypto/historical/trade/crypto_trades_response.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "type": "object", - "title": "See Historical Crypto Data.", - "properties": { - "trades": { - "existingJavaType": "java.util.Map>", - "title": "The {@link java.util.ArrayList} of {@link net.jacobpeterson.alpaca.model.endpoint.marketdata.crypto.historical.trade.CryptoTrade}s." - }, - "next_page_token": { - "existingJavaType": "java.lang.String", - "title": "Token that can be used to query the next page." - } - } -} diff --git a/src/main/java/net/jacobpeterson/alpaca/endpoint/market_data/crypto/historical/trade/latest_crypto_trades_response.json b/src/main/java/net/jacobpeterson/alpaca/endpoint/market_data/crypto/historical/trade/latest_crypto_trades_response.json deleted file mode 100644 index 4e6749c2..00000000 --- a/src/main/java/net/jacobpeterson/alpaca/endpoint/market_data/crypto/historical/trade/latest_crypto_trades_response.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "type": "object", - "title": "See Historical Crypto Data.", - "properties": { - "trades": { - "existingJavaType": "java.util.Map", - "title": "The {@link java.util.Map} of {@link net.jacobpeterson.alpaca.model.endpoint.marketdata.crypto.historical.trade.CryptoTrade}s." - } - } -} diff --git a/src/main/java/net/jacobpeterson/alpaca/endpoint/market_data/crypto/realtime/bar/crypto_bar_message.json b/src/main/java/net/jacobpeterson/alpaca/endpoint/market_data/crypto/realtime/bar/crypto_bar_message.json deleted file mode 100644 index 82cfe47f..00000000 --- a/src/main/java/net/jacobpeterson/alpaca/endpoint/market_data/crypto/realtime/bar/crypto_bar_message.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "type": "object", - "extends": { - "existingJavaType": "net.jacobpeterson.alpaca.model.endpoint.marketdata.common.realtime.bar.BarMessage" - }, - "title": "See Realtime Crypto Market Data.", - "properties": { - "v": { - "existingJavaType": "java.lang.Double", - "javaName": "volume", - "title": "Volume." - } - } -} diff --git a/src/main/java/net/jacobpeterson/alpaca/endpoint/market_data/crypto/realtime/quote/crypto_quote_message.json b/src/main/java/net/jacobpeterson/alpaca/endpoint/market_data/crypto/realtime/quote/crypto_quote_message.json deleted file mode 100644 index 1eac99c5..00000000 --- a/src/main/java/net/jacobpeterson/alpaca/endpoint/market_data/crypto/realtime/quote/crypto_quote_message.json +++ /dev/null @@ -1,19 +0,0 @@ -{ - "type": "object", - "extends": { - "existingJavaType": "net.jacobpeterson.alpaca.model.endpoint.marketdata.common.realtime.quote.QuoteMessage" - }, - "title": "See Realtime Crypto Market Data.", - "properties": { - "as": { - "existingJavaType": "java.lang.Double", - "javaName": "askSize", - "title": "Ask size." - }, - "bs": { - "existingJavaType": "java.lang.Double", - "javaName": "bidSize", - "title": "Bid size." - } - } -} diff --git a/src/main/java/net/jacobpeterson/alpaca/endpoint/market_data/crypto/realtime/trade/crypto_trade_message.json b/src/main/java/net/jacobpeterson/alpaca/endpoint/market_data/crypto/realtime/trade/crypto_trade_message.json deleted file mode 100644 index 09ee5a87..00000000 --- a/src/main/java/net/jacobpeterson/alpaca/endpoint/market_data/crypto/realtime/trade/crypto_trade_message.json +++ /dev/null @@ -1,19 +0,0 @@ -{ - "type": "object", - "extends": { - "existingJavaType": "net.jacobpeterson.alpaca.model.endpoint.marketdata.common.realtime.trade.TradeMessage" - }, - "title": "See Realtime Crypto Market Data.", - "properties": { - "s": { - "existingJavaType": "java.lang.Double", - "javaName": "size", - "title": "Trade size." - }, - "tks": { - "existingJavaType": "net.jacobpeterson.alpaca.model.endpoint.marketdata.crypto.common.enums.TakerSide", - "javaName": "takerSide", - "title": "The {@link net.jacobpeterson.alpaca.model.endpoint.marketdata.crypto.common.enums.TakerSide} of the trade." - } - } -} diff --git a/src/main/java/net/jacobpeterson/alpaca/endpoint/market_data/news/common/image.json b/src/main/java/net/jacobpeterson/alpaca/endpoint/market_data/news/common/image.json deleted file mode 100644 index 2f3bf607..00000000 --- a/src/main/java/net/jacobpeterson/alpaca/endpoint/market_data/news/common/image.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "type": "object", - "title": "See News.", - "properties": { - "size": { - "existingJavaType": "java.lang.String" - }, - "url": { - "existingJavaType": "java.lang.String" - } - } - } - \ No newline at end of file diff --git a/src/main/java/net/jacobpeterson/alpaca/endpoint/market_data/news/common/news_article.json b/src/main/java/net/jacobpeterson/alpaca/endpoint/market_data/news/common/news_article.json deleted file mode 100644 index ee80a6d7..00000000 --- a/src/main/java/net/jacobpeterson/alpaca/endpoint/market_data/news/common/news_article.json +++ /dev/null @@ -1,39 +0,0 @@ -{ - "type": "object", - "title": "See News.", - "properties": { - "author": { - "existingJavaType": "java.lang.String" - }, - "content": { - "existingJavaType": "java.lang.String" - }, - "created_at": { - "existingJavaType": "java.time.ZonedDateTime" - }, - "headline": { - "existingJavaType": "java.lang.String" - }, - "id": { - "existingJavaType": "java.lang.Long" - }, - "images": { - "existingJavaType": "java.util.ArrayList" - }, - "source": { - "existingJavaType": "java.lang.String" - }, - "summary": { - "existingJavaType": "java.lang.String" - }, - "symbols": { - "existingJavaType": "java.util.ArrayList" - }, - "updated_at": { - "existingJavaType": "java.time.ZonedDateTime" - }, - "url": { - "existingJavaType": "java.lang.String" - } - } - } diff --git a/src/main/java/net/jacobpeterson/alpaca/endpoint/market_data/news/news_response.json b/src/main/java/net/jacobpeterson/alpaca/endpoint/market_data/news/news_response.json deleted file mode 100644 index b25ed7f9..00000000 --- a/src/main/java/net/jacobpeterson/alpaca/endpoint/market_data/news/news_response.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "type": "object", - "title": "See News.", - "properties": { - "news": { - "existingJavaType": "java.util.ArrayList" - }, - "next_page_token": { - "existingJavaType": "java.lang.String" - } - } -} diff --git a/src/main/java/net/jacobpeterson/alpaca/endpoint/market_data/news/realtime/news_message.json b/src/main/java/net/jacobpeterson/alpaca/endpoint/market_data/news/realtime/news_message.json deleted file mode 100644 index bee011fe..00000000 --- a/src/main/java/net/jacobpeterson/alpaca/endpoint/market_data/news/realtime/news_message.json +++ /dev/null @@ -1,40 +0,0 @@ -{ - "type": "object", - "title": "See News.", - "extends": { - "existingJavaType": "net.jacobpeterson.alpaca.model.endpoint.marketdata.common.realtime.MarketDataMessage" - }, - "properties": { - "headline": { - "existingJavaType": "java.lang.String" - }, - "created_at": { - "existingJavaType": "java.time.ZonedDateTime" - }, - "updated_at": { - "existingJavaType": "java.time.ZonedDateTime" - }, - "author": { - "existingJavaType": "java.lang.String" - }, - "summary": { - "existingJavaType": "java.lang.String" - }, - "content": { - "existingJavaType": "java.lang.String" - }, - - "url": { - "existingJavaType": "java.lang.String" - }, - "symbols": { - "existingJavaType": "java.util.ArrayList" - }, - "source": { - "existingJavaType": "java.lang.String" - }, - "id": { - "existingJavaType": "java.lang.Long" - } - } - } diff --git a/src/main/java/net/jacobpeterson/alpaca/endpoint/market_data/stock/historical/bar/enums/bar_adjustment.json b/src/main/java/net/jacobpeterson/alpaca/endpoint/market_data/stock/historical/bar/enums/bar_adjustment.json deleted file mode 100644 index d42b31b2..00000000 --- a/src/main/java/net/jacobpeterson/alpaca/endpoint/market_data/stock/historical/bar/enums/bar_adjustment.json +++ /dev/null @@ -1,24 +0,0 @@ -{ - "type": "string", - "title": "See Historical Stock Data.", - "enum": [ - "raw", - "split", - "dividend", - "all" - ], - "javaEnums": [ - { - "name": "RAW" - }, - { - "name": "SPLIT" - }, - { - "name": "DIVIDEND" - }, - { - "name": "ALL" - } - ] -} diff --git a/src/main/java/net/jacobpeterson/alpaca/endpoint/market_data/stock/historical/bar/enums/bar_feed.json b/src/main/java/net/jacobpeterson/alpaca/endpoint/market_data/stock/historical/bar/enums/bar_feed.json deleted file mode 100644 index 87d8a1a4..00000000 --- a/src/main/java/net/jacobpeterson/alpaca/endpoint/market_data/stock/historical/bar/enums/bar_feed.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "type": "string", - "title": "See Historical Stock Data.", - "enum": [ - "IEX", - "SIP" - ] -} diff --git a/src/main/java/net/jacobpeterson/alpaca/endpoint/market_data/stock/historical/bar/multi_stock_bars_response.json b/src/main/java/net/jacobpeterson/alpaca/endpoint/market_data/stock/historical/bar/multi_stock_bars_response.json deleted file mode 100644 index 65028159..00000000 --- a/src/main/java/net/jacobpeterson/alpaca/endpoint/market_data/stock/historical/bar/multi_stock_bars_response.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "type": "object", - "title": "See Historical Stock Data.", - "properties": { - "bars": { - "existingJavaType": "java.util.HashMap>", - "title": "The {@link net.jacobpeterson.alpaca.model.endpoint.marketdata.stock.historical.bar.StockBar}s of {@link java.lang.String} symbols {@link java.util.HashMap}." - }, - "next_page_token": { - "existingJavaType": "java.lang.String", - "title": "Token that can be used to query the next page." - } - } -} diff --git a/src/main/java/net/jacobpeterson/alpaca/endpoint/market_data/stock/historical/bar/stock_bar.json b/src/main/java/net/jacobpeterson/alpaca/endpoint/market_data/stock/historical/bar/stock_bar.json deleted file mode 100644 index ae851453..00000000 --- a/src/main/java/net/jacobpeterson/alpaca/endpoint/market_data/stock/historical/bar/stock_bar.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "type": "object", - "extends": { - "existingJavaType": "net.jacobpeterson.alpaca.model.endpoint.marketdata.common.historical.bar.Bar" - }, - "title": "See Historical Stock Data.", - "properties": { - "v": { - "existingJavaType": "java.lang.Long", - "javaName": "volume", - "title": "Volume." - } - } -} diff --git a/src/main/java/net/jacobpeterson/alpaca/endpoint/market_data/stock/historical/bar/stock_bars_response.json b/src/main/java/net/jacobpeterson/alpaca/endpoint/market_data/stock/historical/bar/stock_bars_response.json deleted file mode 100644 index 5add43d2..00000000 --- a/src/main/java/net/jacobpeterson/alpaca/endpoint/market_data/stock/historical/bar/stock_bars_response.json +++ /dev/null @@ -1,18 +0,0 @@ -{ - "type": "object", - "title": "See Historical Stock Data.", - "properties": { - "bars": { - "existingJavaType": "java.util.ArrayList", - "title": "The {@link java.util.ArrayList} of {@link net.jacobpeterson.alpaca.model.endpoint.marketdata.stock.historical.bar.StockBar}s. This may be null if no bars exist." - }, - "symbol": { - "existingJavaType": "java.lang.String", - "title": "Symbol that was queried." - }, - "next_page_token": { - "existingJavaType": "java.lang.String", - "title": "Token that can be used to query the next page." - } - } -} diff --git a/src/main/java/net/jacobpeterson/alpaca/endpoint/market_data/stock/historical/quote/latest_stock_quote_response.json b/src/main/java/net/jacobpeterson/alpaca/endpoint/market_data/stock/historical/quote/latest_stock_quote_response.json deleted file mode 100644 index 4a9a3992..00000000 --- a/src/main/java/net/jacobpeterson/alpaca/endpoint/market_data/stock/historical/quote/latest_stock_quote_response.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "type": "object", - "title": "See Historical Stock Data.", - "properties": { - "symbol": { - "existingJavaType": "java.lang.String", - "title": "Symbol that was queried." - }, - "quote": { - "existingJavaType": "net.jacobpeterson.alpaca.model.endpoint.marketdata.stock.historical.quote.StockQuote", - "title": "The latest {@link net.jacobpeterson.alpaca.model.endpoint.marketdata.stock.historical.quote.StockQuote}." - } - } -} diff --git a/src/main/java/net/jacobpeterson/alpaca/endpoint/market_data/stock/historical/quote/stock_quote.json b/src/main/java/net/jacobpeterson/alpaca/endpoint/market_data/stock/historical/quote/stock_quote.json deleted file mode 100644 index 164f093f..00000000 --- a/src/main/java/net/jacobpeterson/alpaca/endpoint/market_data/stock/historical/quote/stock_quote.json +++ /dev/null @@ -1,34 +0,0 @@ -{ - "type": "object", - "extends": { - "existingJavaType": "net.jacobpeterson.alpaca.model.endpoint.marketdata.common.historical.quote.Quote" - }, - "title": "See Historical Stock Data.", - "properties": { - "ax": { - "existingJavaType": "java.lang.String", - "javaName": "askExchange", - "title": "Ask exchange." - }, - "as": { - "existingJavaType": "java.lang.Integer", - "javaName": "askSize", - "title": "Ask size." - }, - "bx": { - "existingJavaType": "java.lang.String", - "javaName": "bidExchange", - "title": "Bid exchange." - }, - "bs": { - "existingJavaType": "java.lang.Integer", - "javaName": "bidSize", - "title": "Bid size." - }, - "c": { - "existingJavaType": "java.util.ArrayList", - "javaName": "conditions", - "title": "The {@link java.util.ArrayList} of quote conditions." - } - } -} diff --git a/src/main/java/net/jacobpeterson/alpaca/endpoint/market_data/stock/historical/quote/stock_quotes_response.json b/src/main/java/net/jacobpeterson/alpaca/endpoint/market_data/stock/historical/quote/stock_quotes_response.json deleted file mode 100644 index 418dcf6b..00000000 --- a/src/main/java/net/jacobpeterson/alpaca/endpoint/market_data/stock/historical/quote/stock_quotes_response.json +++ /dev/null @@ -1,18 +0,0 @@ -{ - "type": "object", - "title": "See Historical Stock Data.", - "properties": { - "quotes": { - "existingJavaType": "java.util.ArrayList", - "title": "The {@link java.util.ArrayList} of {@link net.jacobpeterson.alpaca.model.endpoint.marketdata.stock.historical.quote.StockQuote}s." - }, - "symbol": { - "existingJavaType": "java.lang.String", - "title": "Symbol that was queried." - }, - "next_page_token": { - "existingJavaType": "java.lang.String", - "title": "Token that can be used to query the next page." - } - } -} diff --git a/src/main/java/net/jacobpeterson/alpaca/endpoint/market_data/stock/historical/snapshot/snapshot.json b/src/main/java/net/jacobpeterson/alpaca/endpoint/market_data/stock/historical/snapshot/snapshot.json deleted file mode 100644 index 9617b3de..00000000 --- a/src/main/java/net/jacobpeterson/alpaca/endpoint/market_data/stock/historical/snapshot/snapshot.json +++ /dev/null @@ -1,26 +0,0 @@ -{ - "type": "object", - "title": "See Historical Stock Data.", - "properties": { - "latestTrade": { - "existingJavaType": "net.jacobpeterson.alpaca.model.endpoint.marketdata.stock.historical.trade.StockTrade", - "title": "The latest {@link net.jacobpeterson.alpaca.model.endpoint.marketdata.stock.historical.trade.StockTrade}." - }, - "latestQuote": { - "existingJavaType": "net.jacobpeterson.alpaca.model.endpoint.marketdata.stock.historical.quote.StockQuote", - "title": "The latest {@link net.jacobpeterson.alpaca.model.endpoint.marketdata.stock.historical.quote.StockQuote}." - }, - "minuteBar": { - "existingJavaType": "net.jacobpeterson.alpaca.model.endpoint.marketdata.stock.historical.bar.StockBar", - "title": "The minute {@link net.jacobpeterson.alpaca.model.endpoint.marketdata.stock.historical.bar.StockBar}." - }, - "dailyBar": { - "existingJavaType": "net.jacobpeterson.alpaca.model.endpoint.marketdata.stock.historical.bar.StockBar", - "title": "The daily {@link net.jacobpeterson.alpaca.model.endpoint.marketdata.stock.historical.bar.StockBar}." - }, - "prevDailyBar": { - "existingJavaType": "net.jacobpeterson.alpaca.model.endpoint.marketdata.stock.historical.bar.StockBar", - "title": "The previous daily {@link net.jacobpeterson.alpaca.model.endpoint.marketdata.stock.historical.bar.StockBar}." - } - } -} diff --git a/src/main/java/net/jacobpeterson/alpaca/endpoint/market_data/stock/historical/trade/latest_stock_trade_response.json b/src/main/java/net/jacobpeterson/alpaca/endpoint/market_data/stock/historical/trade/latest_stock_trade_response.json deleted file mode 100644 index b4765b53..00000000 --- a/src/main/java/net/jacobpeterson/alpaca/endpoint/market_data/stock/historical/trade/latest_stock_trade_response.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "type": "object", - "title": "See Historical Stock Data.", - "properties": { - "symbol": { - "existingJavaType": "java.lang.String", - "title": "Symbol that was queried." - }, - "trade": { - "existingJavaType": "net.jacobpeterson.alpaca.model.endpoint.marketdata.stock.historical.trade.StockTrade", - "title": "The latest {@link net.jacobpeterson.alpaca.model.endpoint.marketdata.stock.historical.trade.StockTrade}." - } - } -} diff --git a/src/main/java/net/jacobpeterson/alpaca/endpoint/market_data/stock/historical/trade/stock_trade.json b/src/main/java/net/jacobpeterson/alpaca/endpoint/market_data/stock/historical/trade/stock_trade.json deleted file mode 100644 index ea3e96cf..00000000 --- a/src/main/java/net/jacobpeterson/alpaca/endpoint/market_data/stock/historical/trade/stock_trade.json +++ /dev/null @@ -1,29 +0,0 @@ -{ - "type": "object", - "extends": { - "existingJavaType": "net.jacobpeterson.alpaca.model.endpoint.marketdata.common.historical.trade.Trade" - }, - "title": "See Historical Stock Data.", - "properties": { - "x": { - "existingJavaType": "java.lang.String", - "javaName": "exchange", - "title": "Exchange where the trade happened." - }, - "s": { - "existingJavaType": "java.lang.Integer", - "javaName": "size", - "title": "Trade size." - }, - "c": { - "existingJavaType": "java.util.ArrayList", - "javaName": "conditions", - "title": "The {@link java.util.ArrayList} of trade conditions." - }, - "z": { - "existingJavaType": "java.lang.String", - "javaName": "tape", - "title": "Tape." - } - } -} diff --git a/src/main/java/net/jacobpeterson/alpaca/endpoint/market_data/stock/historical/trade/stock_trades_response.json b/src/main/java/net/jacobpeterson/alpaca/endpoint/market_data/stock/historical/trade/stock_trades_response.json deleted file mode 100644 index 9b08b1da..00000000 --- a/src/main/java/net/jacobpeterson/alpaca/endpoint/market_data/stock/historical/trade/stock_trades_response.json +++ /dev/null @@ -1,18 +0,0 @@ -{ - "type": "object", - "title": "See Historical Stock Data.", - "properties": { - "trades": { - "existingJavaType": "java.util.ArrayList", - "title": "The {@link java.util.ArrayList} of {@link net.jacobpeterson.alpaca.model.endpoint.marketdata.stock.historical.trade.StockTrade}s." - }, - "symbol": { - "existingJavaType": "java.lang.String", - "title": "Symbol that was queried." - }, - "next_page_token": { - "existingJavaType": "java.lang.String", - "title": "Token that can be used to query the next page." - } - } -} diff --git a/src/main/java/net/jacobpeterson/alpaca/endpoint/market_data/stock/realtime/bar/stock_bar_message.json b/src/main/java/net/jacobpeterson/alpaca/endpoint/market_data/stock/realtime/bar/stock_bar_message.json deleted file mode 100644 index 6bfbf62a..00000000 --- a/src/main/java/net/jacobpeterson/alpaca/endpoint/market_data/stock/realtime/bar/stock_bar_message.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "type": "object", - "extends": { - "existingJavaType": "net.jacobpeterson.alpaca.model.endpoint.marketdata.common.realtime.bar.BarMessage" - }, - "title": "See Realtime Stock Market Data.", - "properties": { - "v": { - "existingJavaType": "java.lang.Long", - "javaName": "volume", - "title": "Volume." - } - } -} diff --git a/src/main/java/net/jacobpeterson/alpaca/endpoint/market_data/stock/realtime/quote/stock_quote_message.json b/src/main/java/net/jacobpeterson/alpaca/endpoint/market_data/stock/realtime/quote/stock_quote_message.json deleted file mode 100644 index 2633ccde..00000000 --- a/src/main/java/net/jacobpeterson/alpaca/endpoint/market_data/stock/realtime/quote/stock_quote_message.json +++ /dev/null @@ -1,39 +0,0 @@ -{ - "type": "object", - "extends": { - "existingJavaType": "net.jacobpeterson.alpaca.model.endpoint.marketdata.common.realtime.quote.QuoteMessage" - }, - "title": "See Realtime Stock Market Data.", - "properties": { - "ax": { - "existingJavaType": "java.lang.String", - "javaName": "askExchange", - "title": "Ask exchange code." - }, - "as": { - "existingJavaType": "java.lang.Integer", - "javaName": "askSize", - "title": "Ask size." - }, - "bx": { - "existingJavaType": "java.lang.String", - "javaName": "bidExchange", - "title": "Bid exchange code." - }, - "bs": { - "existingJavaType": "java.lang.Integer", - "javaName": "bidSize", - "title": "Bid size." - }, - "c": { - "existingJavaType": "java.util.ArrayList", - "javaName": "conditions", - "title": "The {@link java.util.ArrayList} of quote conditions." - }, - "z": { - "existingJavaType": "java.lang.String", - "javaName": "tape", - "title": "Tape." - } - } -} diff --git a/src/main/java/net/jacobpeterson/alpaca/endpoint/market_data/stock/realtime/trade/stock_trade_message.json b/src/main/java/net/jacobpeterson/alpaca/endpoint/market_data/stock/realtime/trade/stock_trade_message.json deleted file mode 100644 index c3b0bc51..00000000 --- a/src/main/java/net/jacobpeterson/alpaca/endpoint/market_data/stock/realtime/trade/stock_trade_message.json +++ /dev/null @@ -1,29 +0,0 @@ -{ - "type": "object", - "extends": { - "existingJavaType": "net.jacobpeterson.alpaca.model.endpoint.marketdata.common.realtime.trade.TradeMessage" - }, - "title": "See Realtime Stock Market Data.", - "properties": { - "x": { - "existingJavaType": "java.lang.String", - "javaName": "exchange", - "title": "Exchange code where the trade occurred." - }, - "s": { - "existingJavaType": "java.lang.Integer", - "javaName": "size", - "title": "Trade size." - }, - "c": { - "existingJavaType": "java.util.ArrayList", - "javaName": "conditions", - "title": "The {@link java.util.ArrayList} of trade conditions." - }, - "z": { - "existingJavaType": "java.lang.String", - "javaName": "tape", - "title": "Tape." - } - } -} diff --git a/src/main/java/net/jacobpeterson/alpaca/endpoint/market_data/common/realtime/control/error_message.json b/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/model/control/error_message.json similarity index 57% rename from src/main/java/net/jacobpeterson/alpaca/endpoint/market_data/common/realtime/control/error_message.json rename to src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/model/control/error_message.json index 39b9e7f2..655d6a03 100644 --- a/src/main/java/net/jacobpeterson/alpaca/endpoint/market_data/common/realtime/control/error_message.json +++ b/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/model/control/error_message.json @@ -1,9 +1,8 @@ { "type": "object", "extends": { - "existingJavaType": "net.jacobpeterson.alpaca.model.endpoint.marketdata.common.realtime.MarketDataMessage" + "existingJavaType": "net.jacobpeterson.alpaca.model.websocket.marketdata.model.MarketDataMessage" }, - "title": "See Market Data.", "properties": { "code": { "existingJavaType": "java.lang.Integer", diff --git a/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/model/control/success_message.json b/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/model/control/success_message.json new file mode 100644 index 00000000..e471b400 --- /dev/null +++ b/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/model/control/success_message.json @@ -0,0 +1,13 @@ +{ + "type": "object", + "extends": { + "existingJavaType": "net.jacobpeterson.alpaca.model.websocket.marketdata.model.MarketDataMessage" + }, + "properties": { + "msg": { + "existingJavaType": "net.jacobpeterson.alpaca.model.websocket.marketdata.model.control.SuccessMessageType", + "javaName": "messageType", + "title": "The success message type." + } + } +} diff --git a/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/model/control/success_message_type.json b/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/model/control/success_message_type.json new file mode 100644 index 00000000..61c6c983 --- /dev/null +++ b/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/model/control/success_message_type.json @@ -0,0 +1,7 @@ +{ + "type": "string", + "enum": [ + "success", + "authenticated" + ] +} diff --git a/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/model/market_data_message.json b/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/model/market_data_message.json new file mode 100644 index 00000000..e1a8346a --- /dev/null +++ b/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/model/market_data_message.json @@ -0,0 +1,4 @@ +{ + "type": "object", + "properties": {} +} diff --git a/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/streams/crypto/CryptoMarketDataWebsocket.java b/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/streams/crypto/CryptoMarketDataWebsocket.java new file mode 100644 index 00000000..8eed2ce2 --- /dev/null +++ b/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/streams/crypto/CryptoMarketDataWebsocket.java @@ -0,0 +1,27 @@ +package net.jacobpeterson.alpaca.websocket.marketdata.streams.crypto; + +import net.jacobpeterson.alpaca.model.endpoint.marketdata.crypto.realtime.bar.CryptoBarMessage; +import net.jacobpeterson.alpaca.model.endpoint.marketdata.crypto.realtime.quote.CryptoQuoteMessage; +import net.jacobpeterson.alpaca.model.endpoint.marketdata.crypto.realtime.trade.CryptoTradeMessage; +import net.jacobpeterson.alpaca.websocket.marketdata.MarketDataWebsocket; +import okhttp3.OkHttpClient; + +/** + * {@link CryptoMarketDataWebsocket} is a {@link MarketDataWebsocket} for + * Realtime + * Crypto Market Data + */ +public class CryptoMarketDataWebsocket extends MarketDataWebsocket { + + /** + * Instantiates a new {@link CryptoMarketDataWebsocket}. + * + * @param okHttpClient the {@link OkHttpClient} + * @param keyID the key ID + * @param secretKey the secret key + */ + public CryptoMarketDataWebsocket(OkHttpClient okHttpClient, String keyID, String secretKey) { + super(okHttpClient, "v1beta3/crypto/us", "Crypto", keyID, secretKey, CryptoTradeMessage.class, + CryptoQuoteMessage.class, CryptoBarMessage.class); + } +} diff --git a/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/streams/crypto/model/bar/crypto_bar_message.json b/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/streams/crypto/model/bar/crypto_bar_message.json new file mode 100644 index 00000000..f24e3e99 --- /dev/null +++ b/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/streams/crypto/model/bar/crypto_bar_message.json @@ -0,0 +1,53 @@ +{ + "type": "object", + "extends": { + "existingJavaType": "net.jacobpeterson.alpaca.model.websocket.marketdata.streams.crypto.model.CryptoMarketDataMessage" + }, + "properties": { + "S": { + "existingJavaType": "java.lang.String", + "javaName": "symbol", + "title": "The symbol." + }, + "o": { + "existingJavaType": "java.lang.Double", + "javaName": "open", + "title": "The open price." + }, + "h": { + "existingJavaType": "java.lang.Double", + "javaName": "high", + "title": "The high price." + }, + "l": { + "existingJavaType": "java.lang.Double", + "javaName": "low", + "title": "The low price." + }, + "c": { + "existingJavaType": "java.lang.Double", + "javaName": "close", + "title": "The close price." + }, + "t": { + "existingJavaType": "java.time.OffsetDateTime", + "javaName": "timestamp", + "title": "The timestamp." + }, + "v": { + "existingJavaType": "java.lang.Long", + "javaName": "volume", + "title": "The volume." + }, + "n": { + "existingJavaType": "java.lang.Long", + "javaName": "tradeCount", + "title": "The trade count." + }, + "vw": { + "existingJavaType": "java.lang.Double", + "javaName": "vwap", + "title": "The VWAP (Volume Weighted Average Price)." + } + } +} diff --git a/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/streams/crypto/model/crypto_market_data_message.json b/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/streams/crypto/model/crypto_market_data_message.json new file mode 100644 index 00000000..0ced4e8f --- /dev/null +++ b/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/streams/crypto/model/crypto_market_data_message.json @@ -0,0 +1,13 @@ +{ + "type": "object", + "extends": { + "existingJavaType": "net.jacobpeterson.alpaca.model.websocket.marketdata.model.MarketDataMessage" + }, + "properties": { + "T": { + "existingJavaType": "net.jacobpeterson.alpaca.model.websocket.marketdata.streams.crypto.model.CryptoMarketDataMessageType", + "javaName": "messageType", + "title": "The {@link net.jacobpeterson.alpaca.model.websocket.marketdata.streams.crypto.model.CryptoMarketDataMessageType}." + } + } +} diff --git a/src/main/java/net/jacobpeterson/alpaca/endpoint/market_data/common/realtime/enums/market_data_message_type.json b/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/streams/crypto/model/crypto_market_data_message_type.json similarity index 58% rename from src/main/java/net/jacobpeterson/alpaca/endpoint/market_data/common/realtime/enums/market_data_message_type.json rename to src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/streams/crypto/model/crypto_market_data_message_type.json index 6999050d..a84d1728 100644 --- a/src/main/java/net/jacobpeterson/alpaca/endpoint/market_data/common/realtime/enums/market_data_message_type.json +++ b/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/streams/crypto/model/crypto_market_data_message_type.json @@ -1,6 +1,5 @@ { "type": "string", - "title": "See Market Data.", "enum": [ "success", "error", @@ -8,7 +7,9 @@ "t", "q", "b", - "n" + "d", + "u", + "o" ], "javaEnums": [ { @@ -21,16 +22,22 @@ "name": "SUBSCRIPTION" }, { - "name": "TRADE" + "name": "TRADES" }, { - "name": "QUOTE" + "name": "QUOTES" }, { - "name": "BAR" + "name": "MINUTE_BARS" }, { - "name": "NEWS" + "name": "DAILY_BARS" + }, + { + "name": "UPDATED_BARS" + }, + { + "name": "ORDER_BOOKS" } ] } diff --git a/src/main/java/net/jacobpeterson/alpaca/endpoint/market_data/crypto/historical/orderbook/crypto_orderbook_entry.json b/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/streams/crypto/model/orderbook/crypto_orderbook_entry.json similarity index 57% rename from src/main/java/net/jacobpeterson/alpaca/endpoint/market_data/crypto/historical/orderbook/crypto_orderbook_entry.json rename to src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/streams/crypto/model/orderbook/crypto_orderbook_entry.json index 5387ecc7..51ce64b5 100644 --- a/src/main/java/net/jacobpeterson/alpaca/endpoint/market_data/crypto/historical/orderbook/crypto_orderbook_entry.json +++ b/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/streams/crypto/model/orderbook/crypto_orderbook_entry.json @@ -1,16 +1,15 @@ { "type": "object", - "title": "See Latest Orderbook.", "properties": { "p": { "existingJavaType": "java.lang.Double", "javaName": "price", - "title": "Price." + "title": "The price." }, "s": { "existingJavaType": "java.lang.Double", "javaName": "size", - "title": "Size." + "title": "The size." } } } diff --git a/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/streams/crypto/model/orderbook/crypto_orderbook_message.json b/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/streams/crypto/model/orderbook/crypto_orderbook_message.json new file mode 100644 index 00000000..80aa0c54 --- /dev/null +++ b/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/streams/crypto/model/orderbook/crypto_orderbook_message.json @@ -0,0 +1,28 @@ +{ + "type": "object", + "extends": { + "existingJavaType": "net.jacobpeterson.alpaca.model.websocket.marketdata.streams.crypto.model.CryptoMarketDataMessage" + }, + "properties": { + "S": { + "existingJavaType": "java.lang.String", + "javaName": "symbol", + "title": "The symbol." + }, + "a": { + "existingJavaType": "java.util.ArrayList", + "javaName": "asks", + "title": "The {@link java.util.ArrayList} of {@link net.jacobpeterson.alpaca.model.websocket.marketdata.streams.crypto.model.orderbook.CryptoOrderBookEntry} asks." + }, + "b": { + "existingJavaType": "java.util.ArrayList", + "javaName": "bids", + "title": "The {@link java.util.ArrayList} of {@link net.jacobpeterson.alpaca.model.websocket.marketdata.streams.crypto.model.orderbook.CryptoOrderBookEntry} bids." + }, + "t": { + "existingJavaType": "java.time.OffsetDateTime", + "javaName": "timestamp", + "title": "The timestamp with nanosecond precision." + } + } +} diff --git a/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/streams/crypto/model/quote/crypto_quote_message.json b/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/streams/crypto/model/quote/crypto_quote_message.json new file mode 100644 index 00000000..d314de7a --- /dev/null +++ b/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/streams/crypto/model/quote/crypto_quote_message.json @@ -0,0 +1,38 @@ +{ + "type": "object", + "extends": { + "existingJavaType": "net.jacobpeterson.alpaca.model.websocket.marketdata.streams.crypto.model.CryptoMarketDataMessage" + }, + "properties": { + "S": { + "existingJavaType": "java.lang.String", + "javaName": "symbol", + "title": "The symbol." + }, + "ap": { + "existingJavaType": "java.lang.Double", + "javaName": "askPrice", + "title": "The ask price." + }, + "as": { + "existingJavaType": "java.lang.Double", + "javaName": "askSize", + "title": "The ask size." + }, + "bp": { + "existingJavaType": "java.lang.Double", + "javaName": "bidPrice", + "title": "The bid price." + }, + "bs": { + "existingJavaType": "java.lang.Double", + "javaName": "bidSize", + "title": "The bid size." + }, + "t": { + "existingJavaType": "java.time.OffsetDateTime", + "javaName": "timestamp", + "title": "The timestamp with nanosecond precision." + } + } +} diff --git a/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/streams/crypto/model/subscriptions/crypto_subscriptions_message.json b/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/streams/crypto/model/subscriptions/crypto_subscriptions_message.json new file mode 100644 index 00000000..19b1f191 --- /dev/null +++ b/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/streams/crypto/model/subscriptions/crypto_subscriptions_message.json @@ -0,0 +1,33 @@ +{ + "type": "object", + "extends": { + "existingJavaType": "net.jacobpeterson.alpaca.model.websocket.marketdata.streams.crypto.model.CryptoMarketDataMessage" + }, + "properties": { + "trades": { + "existingJavaType": "java.util.ArrayList", + "title": "The {@link java.util.ArrayList} of symbols subscribed to trades." + }, + "quotes": { + "existingJavaType": "java.util.ArrayList", + "title": "The {@link java.util.ArrayList} of symbols subscribed to quotes." + }, + "bars": { + "existingJavaType": "java.util.ArrayList", + "javaName": "minuteBars", + "title": "The {@link java.util.ArrayList} of symbols subscribed to minute bars." + }, + "dailyBars": { + "existingJavaType": "java.util.ArrayList", + "title": "The {@link java.util.ArrayList} of symbols subscribed to daily bars." + }, + "updatedBars": { + "existingJavaType": "java.util.ArrayList", + "title": "The {@link java.util.ArrayList} of symbols subscribed to updated bars." + }, + "orderbooks": { + "existingJavaType": "java.util.ArrayList", + "title": "The {@link java.util.ArrayList} of symbols subscribed to orderbooks." + } + } +} diff --git a/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/streams/crypto/model/trade/crypto_trade_message.json b/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/streams/crypto/model/trade/crypto_trade_message.json new file mode 100644 index 00000000..13118dd7 --- /dev/null +++ b/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/streams/crypto/model/trade/crypto_trade_message.json @@ -0,0 +1,38 @@ +{ + "type": "object", + "extends": { + "existingJavaType": "net.jacobpeterson.alpaca.model.websocket.marketdata.streams.crypto.model.CryptoMarketDataMessage" + }, + "properties": { + "S": { + "existingJavaType": "java.lang.String", + "javaName": "symbol", + "title": "The symbol." + }, + "i": { + "existingJavaType": "java.lang.Long", + "javaName": "tradeID", + "title": "The trade ID." + }, + "p": { + "existingJavaType": "java.lang.Double", + "javaName": "price", + "title": "The trade price." + }, + "s": { + "existingJavaType": "java.lang.Double", + "javaName": "size", + "title": "The trade size." + }, + "t": { + "existingJavaType": "java.time.OffsetDateTime", + "javaName": "timestamp", + "title": "The timestamp with nanosecond precision." + }, + "tks": { + "existingJavaType": "net.jacobpeterson.alpaca.model.websocket.marketdata.streams.crypto.model.trade.CryptoTradeTakerSide", + "javaName": "takerSide", + "title": "The taker side." + } + } +} diff --git a/src/main/java/net/jacobpeterson/alpaca/endpoint/market_data/crypto/common/enums/taker_side.json b/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/streams/crypto/model/trade/crypto_trade_taker_side.json similarity index 52% rename from src/main/java/net/jacobpeterson/alpaca/endpoint/market_data/crypto/common/enums/taker_side.json rename to src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/streams/crypto/model/trade/crypto_trade_taker_side.json index 8c8e4adf..d793fbfb 100644 --- a/src/main/java/net/jacobpeterson/alpaca/endpoint/market_data/crypto/common/enums/taker_side.json +++ b/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/streams/crypto/model/trade/crypto_trade_taker_side.json @@ -1,6 +1,5 @@ { "type": "string", - "title": "See Crypto Data.", "enum": [ "B", "S" diff --git a/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/streams/news/NewsMarketDataWebsocket.java b/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/streams/news/NewsMarketDataWebsocket.java new file mode 100644 index 00000000..8e92fa69 --- /dev/null +++ b/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/streams/news/NewsMarketDataWebsocket.java @@ -0,0 +1,15 @@ +package net.jacobpeterson.alpaca.websocket.marketdata.streams.news; + +import net.jacobpeterson.alpaca.model.endpoint.marketdata.common.realtime.bar.BarMessage; +import net.jacobpeterson.alpaca.model.endpoint.marketdata.common.realtime.quote.QuoteMessage; +import net.jacobpeterson.alpaca.model.endpoint.marketdata.common.realtime.trade.TradeMessage; +import net.jacobpeterson.alpaca.websocket.marketdata.MarketDataWebsocket; +import okhttp3.OkHttpClient; + +public class NewsMarketDataWebsocket extends MarketDataWebsocket { + public NewsMarketDataWebsocket(OkHttpClient okHttpClient, + String keyID, String secretKey) { + super(okHttpClient, "v1beta1/news", "News", keyID, secretKey, TradeMessage.class, + QuoteMessage.class, BarMessage.class); + } +} diff --git a/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/streams/news/model/news/news_message.json b/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/streams/news/model/news/news_message.json new file mode 100644 index 00000000..047c0790 --- /dev/null +++ b/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/streams/news/model/news/news_message.json @@ -0,0 +1,38 @@ +{ + "type": "object", + "extends": { + "existingJavaType": "net.jacobpeterson.alpaca.model.websocket.marketdata.streams.news.model.NewsMarketDataMessage" + }, + "properties": { + "id": { + "existingJavaType": "java.lang.Long" + }, + "source": { + "existingJavaType": "java.lang.String" + }, + "headline": { + "existingJavaType": "java.lang.String" + }, + "author": { + "existingJavaType": "java.lang.String" + }, + "summary": { + "existingJavaType": "java.lang.String" + }, + "content": { + "existingJavaType": "java.lang.String" + }, + "url": { + "existingJavaType": "java.lang.String" + }, + "symbols": { + "existingJavaType": "java.util.ArrayList" + }, + "created_at": { + "existingJavaType": "java.time.OffsetDateTime" + }, + "updated_at": { + "existingJavaType": "java.time.OffsetDateTime" + } + } +} diff --git a/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/streams/news/model/news_market_data_message.json b/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/streams/news/model/news_market_data_message.json new file mode 100644 index 00000000..1f967c5b --- /dev/null +++ b/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/streams/news/model/news_market_data_message.json @@ -0,0 +1,13 @@ +{ + "type": "object", + "extends": { + "existingJavaType": "net.jacobpeterson.alpaca.model.websocket.marketdata.model.MarketDataMessage" + }, + "properties": { + "T": { + "existingJavaType": "net.jacobpeterson.alpaca.model.websocket.marketdata.streams.news.model.NewsMarketDataMessageType", + "javaName": "messageType", + "title": "The {@link net.jacobpeterson.alpaca.model.websocket.marketdata.streams.news.model.NewsMarketDataMessageType}." + } + } +} diff --git a/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/streams/news/model/news_market_data_message_type.json b/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/streams/news/model/news_market_data_message_type.json new file mode 100644 index 00000000..4ddd6017 --- /dev/null +++ b/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/streams/news/model/news_market_data_message_type.json @@ -0,0 +1,23 @@ +{ + "type": "string", + "enum": [ + "success", + "error", + "subscription", + "n" + ], + "javaEnums": [ + { + "name": "SUCCESS" + }, + { + "name": "ERROR" + }, + { + "name": "SUBSCRIPTION" + }, + { + "name": "NEWS" + } + ] +} diff --git a/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/streams/stock/StockMarketDataWebsocket.java b/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/streams/stock/StockMarketDataWebsocket.java new file mode 100644 index 00000000..87bfe6c9 --- /dev/null +++ b/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/streams/stock/StockMarketDataWebsocket.java @@ -0,0 +1,30 @@ +package net.jacobpeterson.alpaca.websocket.marketdata.streams.stock; + +import net.jacobpeterson.alpaca.model.endpoint.marketdata.stock.realtime.bar.StockBarMessage; +import net.jacobpeterson.alpaca.model.endpoint.marketdata.stock.realtime.quote.StockQuoteMessage; +import net.jacobpeterson.alpaca.model.endpoint.marketdata.stock.realtime.trade.StockTradeMessage; +import net.jacobpeterson.alpaca.model.properties.DataAPIType; +import net.jacobpeterson.alpaca.websocket.marketdata.MarketDataWebsocket; +import okhttp3.OkHttpClient; + +/** + * {@link StockMarketDataWebsocket} is a {@link MarketDataWebsocket} for + * Realtime + * Stock Market Data + */ +public class StockMarketDataWebsocket extends MarketDataWebsocket { + + /** + * Instantiates a new {@link StockMarketDataWebsocket}. + * + * @param okHttpClient the {@link OkHttpClient} + * @param dataAPIType the {@link DataAPIType} + * @param keyID the key ID + * @param secretKey the secret key + */ + public StockMarketDataWebsocket(OkHttpClient okHttpClient, DataAPIType dataAPIType, + String keyID, String secretKey) { + super(okHttpClient, "v2/" + dataAPIType.toString(), "Stock", keyID, secretKey, StockTradeMessage.class, + StockQuoteMessage.class, StockBarMessage.class); + } +} diff --git a/src/main/java/net/jacobpeterson/alpaca/endpoint/market_data/common/realtime/bar/bar_message.json b/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/streams/stock/model/bar/stock_bar_message.json similarity index 51% rename from src/main/java/net/jacobpeterson/alpaca/endpoint/market_data/common/realtime/bar/bar_message.json rename to src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/streams/stock/model/bar/stock_bar_message.json index 9df2a564..95c6c0d3 100644 --- a/src/main/java/net/jacobpeterson/alpaca/endpoint/market_data/common/realtime/bar/bar_message.json +++ b/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/streams/stock/model/bar/stock_bar_message.json @@ -1,44 +1,53 @@ { "type": "object", "extends": { - "existingJavaType": "net.jacobpeterson.alpaca.model.endpoint.marketdata.common.realtime.SymbolMessage" + "existingJavaType": "net.jacobpeterson.alpaca.model.websocket.marketdata.streams.stock.model.StockMarketDataMessage" }, - "title": "See Market Data.", "properties": { + "S": { + "existingJavaType": "java.lang.String", + "javaName": "symbol", + "title": "The symbol." + }, "o": { "existingJavaType": "java.lang.Double", "javaName": "open", - "title": "Open price." + "title": "The open price." }, "h": { "existingJavaType": "java.lang.Double", "javaName": "high", - "title": "High price." + "title": "The high price." }, "l": { "existingJavaType": "java.lang.Double", "javaName": "low", - "title": "Low price." + "title": "The low price." }, "c": { "existingJavaType": "java.lang.Double", "javaName": "close", - "title": "Close price." + "title": "The close price." }, "t": { - "existingJavaType": "java.time.ZonedDateTime", + "existingJavaType": "java.time.OffsetDateTime", "javaName": "timestamp", "title": "The timestamp." }, + "v": { + "existingJavaType": "java.lang.Long", + "javaName": "volume", + "title": "The volume." + }, "n": { "existingJavaType": "java.lang.Long", "javaName": "tradeCount", - "title": "Trade count." + "title": "The trade count." }, "vw": { "existingJavaType": "java.lang.Double", "javaName": "vwap", - "title": "VWAP (Volume Weighted Average Price)." + "title": "The VWAP (Volume Weighted Average Price)." } } } diff --git a/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/streams/stock/model/limituplimitdownband/stock_limit_up_limit_down_band_message.json b/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/streams/stock/model/limituplimitdownband/stock_limit_up_limit_down_band_message.json new file mode 100644 index 00000000..db9238cf --- /dev/null +++ b/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/streams/stock/model/limituplimitdownband/stock_limit_up_limit_down_band_message.json @@ -0,0 +1,38 @@ +{ + "type": "object", + "extends": { + "existingJavaType": "net.jacobpeterson.alpaca.model.websocket.marketdata.streams.stock.model.StockMarketDataMessage" + }, + "properties": { + "S": { + "existingJavaType": "java.lang.String", + "javaName": "symbol", + "title": "The symbol." + }, + "u": { + "existingJavaType": "java.lang.Double", + "javaName": "limitUpPrice", + "title": "The upper limit price band of a security." + }, + "d": { + "existingJavaType": "java.lang.Double", + "javaName": "limitDownPrice", + "title": "The lower limit price band of a security." + }, + "i": { + "existingJavaType": "java.lang.String", + "javaName": "indicator", + "title": "The indicator." + }, + "t": { + "existingJavaType": "java.time.OffsetDateTime", + "javaName": "timestamp", + "title": "The timestamp with nanosecond precision." + }, + "z": { + "existingJavaType": "java.lang.String", + "javaName": "tape", + "title": "The tape." + } + } +} diff --git a/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/streams/stock/model/quote/stock_quote_message.json b/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/streams/stock/model/quote/stock_quote_message.json new file mode 100644 index 00000000..27ff6daa --- /dev/null +++ b/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/streams/stock/model/quote/stock_quote_message.json @@ -0,0 +1,58 @@ +{ + "type": "object", + "extends": { + "existingJavaType": "net.jacobpeterson.alpaca.model.websocket.marketdata.streams.stock.model.StockMarketDataMessage" + }, + "properties": { + "S": { + "existingJavaType": "java.lang.String", + "javaName": "symbol", + "title": "The symbol." + }, + "ax": { + "existingJavaType": "java.lang.String", + "javaName": "askExchange", + "title": "The ask exchange code." + }, + "ap": { + "existingJavaType": "java.lang.Double", + "javaName": "askPrice", + "title": "The ask price." + }, + "as": { + "existingJavaType": "java.lang.Integer", + "javaName": "askSize", + "title": "The ask size." + }, + "bx": { + "existingJavaType": "java.lang.String", + "javaName": "bidExchange", + "title": "The bid exchange code." + }, + "bp": { + "existingJavaType": "java.lang.Double", + "javaName": "bidPrice", + "title": "The bid price." + }, + "bs": { + "existingJavaType": "java.lang.Integer", + "javaName": "bidSize", + "title": "The bid size." + }, + "c": { + "existingJavaType": "java.util.ArrayList", + "javaName": "conditions", + "title": "The {@link java.util.ArrayList} of quote conditions." + }, + "t": { + "existingJavaType": "java.time.OffsetDateTime", + "javaName": "timestamp", + "title": "The timestamp with nanosecond precision." + }, + "z": { + "existingJavaType": "java.lang.String", + "javaName": "tape", + "title": "The tape." + } + } +} diff --git a/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/streams/stock/model/stock_market_data_message.json b/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/streams/stock/model/stock_market_data_message.json new file mode 100644 index 00000000..8fbfaf6b --- /dev/null +++ b/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/streams/stock/model/stock_market_data_message.json @@ -0,0 +1,13 @@ +{ + "type": "object", + "extends": { + "existingJavaType": "net.jacobpeterson.alpaca.model.websocket.marketdata.model.MarketDataMessage" + }, + "properties": { + "T": { + "existingJavaType": "net.jacobpeterson.alpaca.model.websocket.marketdata.streams.stock.model.StockMarketDataMessageType", + "javaName": "messageType", + "title": "The {@link net.jacobpeterson.alpaca.model.websocket.marketdata.streams.stock.model.StockMarketDataMessageType}." + } + } +} diff --git a/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/streams/stock/model/stock_market_data_message_type.json b/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/streams/stock/model/stock_market_data_message_type.json new file mode 100644 index 00000000..17495738 --- /dev/null +++ b/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/streams/stock/model/stock_market_data_message_type.json @@ -0,0 +1,55 @@ +{ + "type": "string", + "enum": [ + "success", + "error", + "subscription", + "t", + "q", + "b", + "d", + "u", + "c", + "x", + "l", + "s" + ], + "javaEnums": [ + { + "name": "SUCCESS" + }, + { + "name": "ERROR" + }, + { + "name": "SUBSCRIPTION" + }, + { + "name": "TRADES" + }, + { + "name": "QUOTES" + }, + { + "name": "MINUTE_BARS" + }, + { + "name": "DAILY_BARS" + }, + { + "name": "UPDATED_BARS" + }, + { + "name": "TRADE_CORRECTIONS" + }, + { + "name": "TRADE_CANCEL_ERRORS" + }, + { + "name": "LIMIT_UP_LIMIT_DOWN_BANDS" + }, + { + "name": "TRADING_STATUSES" + } + ] +} diff --git a/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/streams/stock/model/subscriptions/stock_subscriptions_message.json b/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/streams/stock/model/subscriptions/stock_subscriptions_message.json new file mode 100644 index 00000000..986947f6 --- /dev/null +++ b/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/streams/stock/model/subscriptions/stock_subscriptions_message.json @@ -0,0 +1,49 @@ +{ + "type": "object", + "extends": { + "existingJavaType": "net.jacobpeterson.alpaca.model.websocket.marketdata.streams.stock.model.StockMarketDataMessage" + }, + "properties": { + "trades": { + "existingJavaType": "java.util.ArrayList", + "title": "The {@link java.util.ArrayList} of symbols subscribed to trades." + }, + "quotes": { + "existingJavaType": "java.util.ArrayList", + "title": "The {@link java.util.ArrayList} of symbols subscribed to quotes." + }, + "bars": { + "existingJavaType": "java.util.ArrayList", + "javaName": "minuteBars", + "title": "The {@link java.util.ArrayList} of symbols subscribed to minute bars." + }, + "dailyBars": { + "existingJavaType": "java.util.ArrayList", + "title": "The {@link java.util.ArrayList} of symbols subscribed to daily bars." + }, + "updatedBars": { + "existingJavaType": "java.util.ArrayList", + "title": "The {@link java.util.ArrayList} of symbols subscribed to updated bars." + }, + "corrections": { + "existingJavaType": "java.util.ArrayList", + "javaName": "tradeCorrections", + "title": "The {@link java.util.ArrayList} of symbols subscribed to trade corrections." + }, + "cancelErrors": { + "existingJavaType": "java.util.ArrayList", + "javaName": "tradeCancelErrors", + "title": "The {@link java.util.ArrayList} of symbols subscribed to trade cancels/errors." + }, + "lulds": { + "existingJavaType": "java.util.ArrayList", + "javaName": "limitUpLimitDownBands", + "title": "The {@link java.util.ArrayList} of symbols subscribed to limit up - limit down bands." + }, + "statuses": { + "existingJavaType": "java.util.ArrayList", + "javaName": "tradingStatuses", + "title": "The {@link java.util.ArrayList} of symbols subscribed to trading statuses." + } + } +} diff --git a/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/streams/stock/model/trade/stock_trade_message.json b/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/streams/stock/model/trade/stock_trade_message.json new file mode 100644 index 00000000..4294856a --- /dev/null +++ b/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/streams/stock/model/trade/stock_trade_message.json @@ -0,0 +1,48 @@ +{ + "type": "object", + "extends": { + "existingJavaType": "net.jacobpeterson.alpaca.model.websocket.marketdata.streams.stock.model.StockMarketDataMessage" + }, + "properties": { + "S": { + "existingJavaType": "java.lang.String", + "javaName": "symbol", + "title": "The symbol." + }, + "i": { + "existingJavaType": "java.lang.Long", + "javaName": "tradeID", + "title": "The trade ID." + }, + "x": { + "existingJavaType": "java.lang.String", + "javaName": "exchange", + "title": "The exchange code where the trade occurred." + }, + "p": { + "existingJavaType": "java.lang.Double", + "javaName": "price", + "title": "The trade price." + }, + "s": { + "existingJavaType": "java.lang.Integer", + "javaName": "size", + "title": "The trade size." + }, + "c": { + "existingJavaType": "java.util.ArrayList", + "javaName": "conditions", + "title": "The {@link java.util.ArrayList} of trade conditions." + }, + "t": { + "existingJavaType": "java.time.OffsetDateTime", + "javaName": "timestamp", + "title": "The timestamp with nanosecond precision." + }, + "z": { + "existingJavaType": "java.lang.String", + "javaName": "tape", + "title": "The tape." + } + } +} diff --git a/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/streams/stock/model/tradecancelerror/stock_trade_cancel_error_action.json b/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/streams/stock/model/tradecancelerror/stock_trade_cancel_error_action.json new file mode 100644 index 00000000..c13371a9 --- /dev/null +++ b/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/streams/stock/model/tradecancelerror/stock_trade_cancel_error_action.json @@ -0,0 +1,15 @@ +{ + "type": "string", + "enum": [ + "C", + "E" + ], + "javaEnums": [ + { + "name": "CANCEL" + }, + { + "name": "ERROR" + } + ] +} diff --git a/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/streams/stock/model/tradecancelerror/stock_trade_cancel_error_message.json b/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/streams/stock/model/tradecancelerror/stock_trade_cancel_error_message.json new file mode 100644 index 00000000..577586c8 --- /dev/null +++ b/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/streams/stock/model/tradecancelerror/stock_trade_cancel_error_message.json @@ -0,0 +1,48 @@ +{ + "type": "object", + "extends": { + "existingJavaType": "net.jacobpeterson.alpaca.model.websocket.marketdata.streams.stock.model.StockMarketDataMessage" + }, + "properties": { + "S": { + "existingJavaType": "java.lang.String", + "javaName": "symbol", + "title": "The symbol." + }, + "i": { + "existingJavaType": "java.lang.Long", + "javaName": "tradeID", + "title": "The trade ID." + }, + "x": { + "existingJavaType": "java.lang.String", + "javaName": "exchange", + "title": "The exchange code where the trade cancel/error occurred." + }, + "p": { + "existingJavaType": "java.lang.Double", + "javaName": "price", + "title": "The trade price." + }, + "s": { + "existingJavaType": "java.lang.Integer", + "javaName": "size", + "title": "The trade size." + }, + "a": { + "existingJavaType": "net.jacobpeterson.alpaca.model.websocket.marketdata.streams.stock.model.tradecancelerror.StockTradeCancelErrorAction", + "javaName": "action", + "title": "The trade cancel/error action." + }, + "t": { + "existingJavaType": "java.time.OffsetDateTime", + "javaName": "timestamp", + "title": "The timestamp with nanosecond precision." + }, + "z": { + "existingJavaType": "java.lang.String", + "javaName": "tape", + "title": "The tape." + } + } +} diff --git a/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/streams/stock/model/tradecorrection/stock_trade_correction_message.json b/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/streams/stock/model/tradecorrection/stock_trade_correction_message.json new file mode 100644 index 00000000..0b6daaca --- /dev/null +++ b/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/streams/stock/model/tradecorrection/stock_trade_correction_message.json @@ -0,0 +1,68 @@ +{ + "type": "object", + "extends": { + "existingJavaType": "net.jacobpeterson.alpaca.model.websocket.marketdata.streams.stock.model.StockMarketDataMessage" + }, + "properties": { + "S": { + "existingJavaType": "java.lang.String", + "javaName": "symbol", + "title": "The symbol." + }, + "x": { + "existingJavaType": "java.lang.String", + "javaName": "exchange", + "title": "The exchange code where the trade correction occurred." + }, + "oi": { + "existingJavaType": "java.lang.Integer", + "javaName": "originalTradeID", + "title": "The original trade ID." + }, + "op": { + "existingJavaType": "java.lang.Double", + "javaName": "originalPrice", + "title": "The original trade price." + }, + "os": { + "existingJavaType": "java.lang.Integer", + "javaName": "originalSize", + "title": "The original trade size." + }, + "oc": { + "existingJavaType": "java.util.ArrayList", + "javaName": "originalConditions", + "title": "The {@link java.util.ArrayList} of original trade conditions." + }, + "ci": { + "existingJavaType": "java.lang.Integer", + "javaName": "correctedTradeID", + "title": "The corrected trade ID." + }, + "cp": { + "existingJavaType": "java.lang.Double", + "javaName": "correctedPrice", + "title": "The corrected trade price." + }, + "cs": { + "existingJavaType": "java.lang.Integer", + "javaName": "correctedSize", + "title": "The corrected trade size." + }, + "cc": { + "existingJavaType": "java.util.ArrayList", + "javaName": "correctedConditions", + "title": "The {@link java.util.ArrayList} of original trade conditions." + }, + "t": { + "existingJavaType": "java.time.OffsetDateTime", + "javaName": "timestamp", + "title": "The timestamp with nanosecond precision." + }, + "z": { + "existingJavaType": "java.lang.String", + "javaName": "tape", + "title": "The tape." + } + } +} diff --git a/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/streams/stock/model/tradingstatus/stock_trading_status_message.json b/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/streams/stock/model/tradingstatus/stock_trading_status_message.json new file mode 100644 index 00000000..d1946769 --- /dev/null +++ b/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/streams/stock/model/tradingstatus/stock_trading_status_message.json @@ -0,0 +1,43 @@ +{ + "type": "object", + "extends": { + "existingJavaType": "net.jacobpeterson.alpaca.model.websocket.marketdata.streams.stock.model.StockMarketDataMessage" + }, + "properties": { + "S": { + "existingJavaType": "java.lang.String", + "javaName": "symbol", + "title": "The symbol." + }, + "sc": { + "existingJavaType": "java.lang.String", + "javaName": "statusCode", + "title": "The status code." + }, + "sm": { + "existingJavaType": "java.lang.String", + "javaName": "statusMessage", + "title": "The status message." + }, + "rc": { + "existingJavaType": "java.lang.String", + "javaName": "reasonCode", + "title": "The reason code." + }, + "rm": { + "existingJavaType": "java.lang.String", + "javaName": "reasonMessage", + "title": "The reason message." + }, + "t": { + "existingJavaType": "java.time.OffsetDateTime", + "javaName": "timestamp", + "title": "The timestamp with nanosecond precision." + }, + "z": { + "existingJavaType": "java.lang.String", + "javaName": "tape", + "title": "The tape." + } + } +} diff --git a/src/main/java/net/jacobpeterson/alpaca/websocket/trades/TradesWebsocket.java b/src/main/java/net/jacobpeterson/alpaca/websocket/trades/TradesWebsocket.java index a6fc8896..5759d355 100644 --- a/src/main/java/net/jacobpeterson/alpaca/websocket/trades/TradesWebsocket.java +++ b/src/main/java/net/jacobpeterson/alpaca/websocket/trades/TradesWebsocket.java @@ -31,7 +31,6 @@ import static com.google.common.base.Predicates.not; import static com.google.common.collect.Iterables.toArray; import static com.google.gson.JsonParser.parseString; -import static java.lang.String.format; import static java.util.Collections.singletonList; import static java.util.concurrent.TimeUnit.SECONDS; import static net.jacobpeterson.alpaca.model.websocket.trades.model.TradesStreamMessageType.TRADE_UPDATES; @@ -127,7 +126,7 @@ protected void sendAuthenticationMessage() { } @Override - public void onMessage(@NotNull WebSocket webSocket, @NotNull ByteString byteString) { + public void onMessage(@NotNull WebSocket webSocket, @NotNull ByteString byteString) { // Binary framing final String messageString = byteString.utf8(); LOGGER.trace("Websocket message received: {}", messageString); @@ -147,11 +146,10 @@ public void onMessage(@NotNull WebSocket webSocket, @NotNull ByteString byteStri case AUTHORIZATION: message = getGson().fromJson(messageObject, AuthorizationMessage.class); final AuthorizationData authorizationData = ((AuthorizationMessage) message).getData(); - authenticated = authorizationData.getStatus().equalsIgnoreCase("authorized") && - authorizationData.getAction().equalsIgnoreCase("authenticate"); + authenticated = authorizationData.getAction().equalsIgnoreCase("authenticate") && + authorizationData.getStatus().equalsIgnoreCase("authorized"); if (!authenticated) { - throw new RuntimeException(format("%s websocket not authenticated! Received: %s.", - websocketName, message)); + throw new RuntimeException(websocketName + " websocket authentication failed!"); } else { LOGGER.info("{} websocket authenticated.", websocketName); } From 1eed19449629f8424dc581d8623e3182458ff02b Mon Sep 17 00:00:00 2001 From: Jacob Peterson <14127217+Petersoj@users.noreply.github.com> Date: Tue, 5 Mar 2024 09:25:23 -0800 Subject: [PATCH 210/271] WIP fix IllegalArgumentExceptions with JSON empty string values --- build.gradle | 9 ++++++--- .../jacobpeterson/alpaca/rest/broker/EventsApiSSE.java | 9 +++++++++ 2 files changed, 15 insertions(+), 3 deletions(-) diff --git a/build.gradle b/build.gradle index a0729cd0..b747b084 100644 --- a/build.gradle +++ b/build.gradle @@ -215,7 +215,8 @@ final def fixOpenAPIGeneratedClientIssuesTask = tasks.register("fixOpenAPIGenera .replace("this.type = this.getClass().getSimpleName();", "") // Remove the JSON validation that the generated client models always run (there isn't an option to - // disable it for some reason) + // disable it for some reason). Also, remove throwing an IllegalArgumentException for a null/empty string + // given to enum 'fromValue' methods. specIDsOfFileNames.keySet().stream().map { it.replace(".json", "") }.forEach { for (File modelFile : Paths.get(generatedClientLibrariesPath.getPath(), it, sourceFilesPath, it.replace("-", ""), "model").toFile().listFiles()) { @@ -223,9 +224,11 @@ final def fixOpenAPIGeneratedClientIssuesTask = tasks.register("fixOpenAPIGenera .replaceAll("/\\*\\*\\n {2}\\* Validates the JSON Element and throws an exception if issues " + "found[\\s\\S]*?\\*/", "") .replace("public static void validateJsonElement(JsonElement jsonElement) throws IOException {", - "public static boolean validate = false;\n public static void " + + "public static boolean validate = false;\n public static void " + "validateJsonElement(JsonElement jsonElement) throws IOException {\n" + - " if (!validate) return;") + " if (!validate) return;") + .replace("throw new IllegalArgumentException(\"Unexpected value '\" + value + \"'\");", + "return null;") } } diff --git a/src/main/java/net/jacobpeterson/alpaca/rest/broker/EventsApiSSE.java b/src/main/java/net/jacobpeterson/alpaca/rest/broker/EventsApiSSE.java index cff5a03c..5df2e615 100644 --- a/src/main/java/net/jacobpeterson/alpaca/rest/broker/EventsApiSSE.java +++ b/src/main/java/net/jacobpeterson/alpaca/rest/broker/EventsApiSSE.java @@ -19,6 +19,8 @@ import okhttp3.sse.EventSources; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import java.lang.reflect.Type; import java.time.LocalDate; @@ -32,6 +34,8 @@ */ public class EventsApiSSE { + private static final Logger LOGGER = LoggerFactory.getLogger(EventsApiSSE.class); + private final EventsApi eventsAPI; private final EventSource.Factory eventSourceFactory; @@ -151,18 +155,22 @@ private EventSourceListener createEventSourceListener(SSEListener sseList return new EventSourceListener() { @Override public void onClosed(@NotNull EventSource eventSource) { + LOGGER.info("Event source closed: {}", eventSource); sseListener.onClose(); } @Override public void onEvent(@NotNull EventSource eventSource, @Nullable String id, @Nullable String type, @NotNull String data) { + LOGGER.trace("Event source event: eventSource={} id={}, type={}, data={}", eventSource, id, type, data); sseListener.onMessage(getGson().fromJson(data, responseTypeToken)); } @Override public void onFailure(@NotNull EventSource eventSource, @Nullable Throwable throwable, @Nullable Response response) { + LOGGER.error("Event source failure: eventSource={} throwable={}, response={}", + eventSource, throwable, response); if (throwable != null && throwable.getMessage().equals("canceled")) { sseListener.onClose(); return; @@ -172,6 +180,7 @@ public void onFailure(@NotNull EventSource eventSource, @Nullable Throwable thro @Override public void onOpen(@NotNull EventSource eventSource, @NotNull Response response) { + LOGGER.info("Event source opened: {}", eventSource); sseListener.onOpen(); } }; From df6de0ff3ab841ba13c3d3d0fe8c30d25c987504 Mon Sep 17 00:00:00 2001 From: Jacob Peterson <14127217+Petersoj@users.noreply.github.com> Date: Thu, 7 Mar 2024 11:50:33 -0800 Subject: [PATCH 211/271] WIP fix OpenAPI 'generate client' tasks build logic flow --- README.md | 1 + build.gradle | 35 +++++++++++++++-------------------- 2 files changed, 16 insertions(+), 20 deletions(-) diff --git a/README.md b/README.md index afb356b9..3aa3cfcc 100644 --- a/README.md +++ b/README.md @@ -75,6 +75,7 @@ To install the built artifacts to your local Maven repository on your machine (t ``` # TODO +- Implement better reconnect logic for Websockets and SSE streaming - Implement Unit Testing for REST API and Websocket streaming (both live and mocked) # Contributing diff --git a/build.gradle b/build.gradle index b747b084..c6c53daa 100644 --- a/build.gradle +++ b/build.gradle @@ -125,17 +125,9 @@ def downloadOpenAPISpecFilesTask = tasks.register("downloadOpenAPISpecFiles") { .replace(",\"\":{\"type\":\"string\",\"x-stoplight\":{\"id\":\"1zg5jnb6rzdoo\"}}", "") } } +compileJava.dependsOn downloadOpenAPISpecFilesTask -final def generateOpenAPIClientsTask = tasks.register("generateOpenAPIClients") { - it.onlyIf { - !downloadOpenAPISpecFilesTask.get().state.upToDate && - downloadOpenAPISpecFilesTask.get().state.failure == null && - !downloadOpenAPISpecFilesTask.get().state.skipped && - downloadOpenAPISpecFilesTask.get().state.executed - } -} -compileJava.dependsOn generateOpenAPIClientsTask - +final def generateTasks = [] for (def specFileName : specIDsOfFileNames.keySet()) { final def specName = specFileName.replace(".json", "") final def inputSpecFile = new File(specDownloadPath, specFileName) @@ -185,17 +177,22 @@ for (def specFileName : specIDsOfFileNames.keySet()) { generateApiTests.set(false) generateApiDocumentation.set(false) } - generateOpenAPIClientsTask.configure { - dependsOn generateTask - } + compileJava.dependsOn generateTask + generateTasks.add(generateTask) } final def fixOpenAPIGeneratedClientIssuesTask = tasks.register("fixOpenAPIGeneratedClientIssues") { + configure { + mustRunAfter(generateTasks) + } + it.onlyIf { - !generateOpenAPIClientsTask.get().state.upToDate && - generateOpenAPIClientsTask.get().state.failure == null && - !generateOpenAPIClientsTask.get().state.skipped && - generateOpenAPIClientsTask.get().state.executed + generateTasks.stream().allMatch { generateTask -> + !generateTask.get().state.upToDate && + generateTask.get().state.failure == null && + !generateTask.get().state.skipped && + generateTask.get().state.executed + } } doLast { @@ -251,9 +248,7 @@ final def fixOpenAPIGeneratedClientIssuesTask = tasks.register("fixOpenAPIGenera } } } -generateOpenAPIClientsTask.configure { - finalizedBy fixOpenAPIGeneratedClientIssuesTask -} +compileJava.dependsOn fixOpenAPIGeneratedClientIssuesTask // // END Alpaca OpenAPI Specification (OAS) client generation From ec35299fe7287c7bd5a3797e6e92f871b5bd3827 Mon Sep 17 00:00:00 2001 From: Jacob Peterson <14127217+Petersoj@users.noreply.github.com> Date: Thu, 7 Mar 2024 18:06:34 -0800 Subject: [PATCH 212/271] Finish updating websocket interface and functionality --- .../net/jacobpeterson/alpaca/AlpacaAPI.java | 106 ++++++--- .../alpaca/AlpacaMarketDataAPI.java | 1 - .../alpaca/rest/broker/EventsApiSSE.java | 2 +- ...=> market_data_websocket_source_type.json} | 1 - .../alpaca/util/sse/SSEListenerAdapter.java | 4 +- .../alpaca/websocket/AlpacaWebsocket.java | 89 +++---- .../websocket/AlpacaWebsocketInterface.java | 24 +- .../AlpacaWebsocketMessageListener.java | 20 -- .../marketdata/MarketDataWebsocket.java | 198 ++++++++++++++++ .../MarketDataWebsocketInterface.java | 8 + .../crypto/CryptoMarketDataListener.java | 55 +++++ .../CryptoMarketDataListenerAdapter.java | 30 +++ .../crypto/CryptoMarketDataWebsocket.java | 174 ++++++++++++-- .../CryptoMarketDataWebsocketInterface.java | 116 +++++++++ .../control/crypto_subscriptions_message.json | 34 +++ ...ntry.json => crypto_order_book_entry.json} | 0 .../orderbook/crypto_order_book_message.json | 28 +++ .../orderbook/crypto_orderbook_message.json | 28 --- .../crypto_subscriptions_message.json | 33 --- .../streams/news/NewsMarketDataListener.java | 17 ++ .../news/NewsMarketDataListenerAdapter.java | 12 + .../streams/news/NewsMarketDataWebsocket.java | 86 ++++++- .../NewsMarketDataWebsocketInterface.java | 36 +++ .../control/news_subscriptions_message.json | 12 + .../streams/news/model/news/news_message.json | 2 +- .../stock/StockMarketDataListener.java | 78 +++++++ .../stock/StockMarketDataListenerAdapter.java | 42 ++++ .../stock/StockMarketDataWebsocket.java | 220 ++++++++++++++++-- .../StockMarketDataWebsocketInterface.java | 136 +++++++++++ .../control/stock_subscriptions_message.json | 39 ++++ .../model/quote/stock_quote_message.json | 4 +- .../stock_subscriptions_message.json | 49 ---- .../model/trade/stock_trade_message.json | 4 +- .../stock_trade_correction_message.json | 8 +- .../websocket/trades/TradesListener.java | 11 - .../trades/TradesListenerAdapter.java | 19 -- .../websocket/trades/TradesWebsocket.java | 217 ----------------- .../trades/TradesWebsocketInterface.java | 26 --- .../model/listening/listening_data.json | 9 - .../websocket/updates/UpdatesListener.java | 17 ++ .../websocket/updates/UpdatesWebsocket.java | 172 ++++++++++++++ .../updates/UpdatesWebsocketInterface.java | 24 ++ .../authorization/authorization_data.json | 0 .../authorization/authorization_message.json | 6 +- .../model/listening/listening_data.json | 9 + .../model/listening/listening_message.json | 6 +- .../model/tradeupdate/trade_update.json | 4 +- .../model/tradeupdate/trade_update_event.json | 0 .../tradeupdate/trade_update_message.json | 6 +- .../model/updates_message.json} | 4 +- .../model/updates_message_type.json} | 0 51 files changed, 1658 insertions(+), 568 deletions(-) rename src/main/java/net/jacobpeterson/alpaca/util/apitype/{market_data_api_stream_source_type.json => market_data_websocket_source_type.json} (56%) delete mode 100644 src/main/java/net/jacobpeterson/alpaca/websocket/AlpacaWebsocketMessageListener.java create mode 100644 src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/MarketDataWebsocket.java create mode 100644 src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/MarketDataWebsocketInterface.java create mode 100644 src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/streams/crypto/CryptoMarketDataListener.java create mode 100644 src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/streams/crypto/CryptoMarketDataListenerAdapter.java create mode 100644 src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/streams/crypto/CryptoMarketDataWebsocketInterface.java create mode 100644 src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/streams/crypto/model/control/crypto_subscriptions_message.json rename src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/streams/crypto/model/orderbook/{crypto_orderbook_entry.json => crypto_order_book_entry.json} (100%) create mode 100644 src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/streams/crypto/model/orderbook/crypto_order_book_message.json delete mode 100644 src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/streams/crypto/model/orderbook/crypto_orderbook_message.json delete mode 100644 src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/streams/crypto/model/subscriptions/crypto_subscriptions_message.json create mode 100644 src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/streams/news/NewsMarketDataListener.java create mode 100644 src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/streams/news/NewsMarketDataListenerAdapter.java create mode 100644 src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/streams/news/NewsMarketDataWebsocketInterface.java create mode 100644 src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/streams/news/model/control/news_subscriptions_message.json create mode 100644 src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/streams/stock/StockMarketDataListener.java create mode 100644 src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/streams/stock/StockMarketDataListenerAdapter.java create mode 100644 src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/streams/stock/StockMarketDataWebsocketInterface.java create mode 100644 src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/streams/stock/model/control/stock_subscriptions_message.json delete mode 100644 src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/streams/stock/model/subscriptions/stock_subscriptions_message.json delete mode 100644 src/main/java/net/jacobpeterson/alpaca/websocket/trades/TradesListener.java delete mode 100644 src/main/java/net/jacobpeterson/alpaca/websocket/trades/TradesListenerAdapter.java delete mode 100644 src/main/java/net/jacobpeterson/alpaca/websocket/trades/TradesWebsocket.java delete mode 100644 src/main/java/net/jacobpeterson/alpaca/websocket/trades/TradesWebsocketInterface.java delete mode 100644 src/main/java/net/jacobpeterson/alpaca/websocket/trades/model/listening/listening_data.json create mode 100644 src/main/java/net/jacobpeterson/alpaca/websocket/updates/UpdatesListener.java create mode 100644 src/main/java/net/jacobpeterson/alpaca/websocket/updates/UpdatesWebsocket.java create mode 100644 src/main/java/net/jacobpeterson/alpaca/websocket/updates/UpdatesWebsocketInterface.java rename src/main/java/net/jacobpeterson/alpaca/websocket/{trades => updates}/model/authorization/authorization_data.json (100%) rename src/main/java/net/jacobpeterson/alpaca/websocket/{trades => updates}/model/authorization/authorization_message.json (66%) create mode 100644 src/main/java/net/jacobpeterson/alpaca/websocket/updates/model/listening/listening_data.json rename src/main/java/net/jacobpeterson/alpaca/websocket/{trades => updates}/model/listening/listening_message.json (69%) rename src/main/java/net/jacobpeterson/alpaca/websocket/{trades => updates}/model/tradeupdate/trade_update.json (90%) rename src/main/java/net/jacobpeterson/alpaca/websocket/{trades => updates}/model/tradeupdate/trade_update_event.json (100%) rename src/main/java/net/jacobpeterson/alpaca/websocket/{trades => updates}/model/tradeupdate/trade_update_message.json (69%) rename src/main/java/net/jacobpeterson/alpaca/websocket/{trades/model/trades_stream_message.json => updates/model/updates_message.json} (69%) rename src/main/java/net/jacobpeterson/alpaca/websocket/{trades/model/trades_stream_message_type.json => updates/model/updates_message_type.json} (100%) diff --git a/src/main/java/net/jacobpeterson/alpaca/AlpacaAPI.java b/src/main/java/net/jacobpeterson/alpaca/AlpacaAPI.java index 001aa617..2ae2e22e 100644 --- a/src/main/java/net/jacobpeterson/alpaca/AlpacaAPI.java +++ b/src/main/java/net/jacobpeterson/alpaca/AlpacaAPI.java @@ -1,17 +1,23 @@ package net.jacobpeterson.alpaca; import net.jacobpeterson.alpaca.model.util.apitype.BrokerAPIEndpointType; -import net.jacobpeterson.alpaca.model.util.apitype.MarketDataAPIStreamSourceType; +import net.jacobpeterson.alpaca.model.util.apitype.MarketDataWebsocketSourceType; import net.jacobpeterson.alpaca.model.util.apitype.TraderAPIEndpointType; -import net.jacobpeterson.alpaca.websocket.trades.TradesWebsocket; -import net.jacobpeterson.alpaca.websocket.trades.TradesWebsocketInterface; +import net.jacobpeterson.alpaca.websocket.marketdata.streams.crypto.CryptoMarketDataWebsocket; +import net.jacobpeterson.alpaca.websocket.marketdata.streams.crypto.CryptoMarketDataWebsocketInterface; +import net.jacobpeterson.alpaca.websocket.marketdata.streams.news.NewsMarketDataWebsocket; +import net.jacobpeterson.alpaca.websocket.marketdata.streams.news.NewsMarketDataWebsocketInterface; +import net.jacobpeterson.alpaca.websocket.marketdata.streams.stock.StockMarketDataWebsocket; +import net.jacobpeterson.alpaca.websocket.marketdata.streams.stock.StockMarketDataWebsocketInterface; +import net.jacobpeterson.alpaca.websocket.updates.UpdatesWebsocket; +import net.jacobpeterson.alpaca.websocket.updates.UpdatesWebsocketInterface; import okhttp3.OkHttpClient; import okhttp3.logging.HttpLoggingInterceptor; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import static net.jacobpeterson.alpaca.model.util.apitype.BrokerAPIEndpointType.SANDBOX; -import static net.jacobpeterson.alpaca.model.util.apitype.MarketDataAPIStreamSourceType.IEX; +import static net.jacobpeterson.alpaca.model.util.apitype.MarketDataWebsocketSourceType.IEX; import static net.jacobpeterson.alpaca.model.util.apitype.TraderAPIEndpointType.PAPER; import static okhttp3.logging.HttpLoggingInterceptor.Level.BODY; @@ -33,7 +39,7 @@ public class AlpacaAPI { private final String traderSecretKey; private final String traderOAuthToken; private final TraderAPIEndpointType traderAPIEndpointType; - private final MarketDataAPIStreamSourceType marketDataAPIStreamSourceType; + private final MarketDataWebsocketSourceType marketDataWebsocketSourceType; private final String brokerAPIKey; private final String brokerAPISecret; private final BrokerAPIEndpointType brokerAPIEndpointType; @@ -42,7 +48,10 @@ public class AlpacaAPI { private AlpacaTraderAPI trader; private AlpacaMarketDataAPI marketData; private AlpacaBrokerAPI broker; - private TradesWebsocket tradesWebsocket; + private UpdatesWebsocket updatesWebsocket; + private StockMarketDataWebsocket stockMarketDataWebsocket; + private CryptoMarketDataWebsocket cryptoMarketDataWebsocket; + private NewsMarketDataWebsocket newsMarketDataWebsocket; /** * Instantiates a new {@link AlpacaAPI}. Use this constructor if you are using the Trading or Market Data APIs for a @@ -51,11 +60,11 @@ public class AlpacaAPI { * @param traderKeyID the Trader key ID * @param traderSecretKey the Trader secret key * @param traderAPIEndpointType the {@link TraderAPIEndpointType} - * @param marketDataAPIStreamSourceType the {@link MarketDataAPIStreamSourceType} + * @param marketDataWebsocketSourceType the {@link MarketDataWebsocketSourceType} */ public AlpacaAPI(String traderKeyID, String traderSecretKey, TraderAPIEndpointType traderAPIEndpointType, - MarketDataAPIStreamSourceType marketDataAPIStreamSourceType) { - this(traderKeyID, traderSecretKey, null, traderAPIEndpointType, marketDataAPIStreamSourceType, null, null, null, + MarketDataWebsocketSourceType marketDataWebsocketSourceType) { + this(traderKeyID, traderSecretKey, null, traderAPIEndpointType, marketDataWebsocketSourceType, null, null, null, null); } @@ -66,13 +75,13 @@ public AlpacaAPI(String traderKeyID, String traderSecretKey, TraderAPIEndpointTy * @param traderKeyID the Trader key ID * @param traderSecretKey the Trader secret key * @param traderAPIEndpointType the {@link TraderAPIEndpointType} - * @param marketDataAPIStreamSourceType the {@link MarketDataAPIStreamSourceType} + * @param marketDataWebsocketSourceType the {@link MarketDataWebsocketSourceType} * @param okHttpClient an existing {@link OkHttpClient} or null to create a new * default instance */ public AlpacaAPI(String traderKeyID, String traderSecretKey, TraderAPIEndpointType traderAPIEndpointType, - MarketDataAPIStreamSourceType marketDataAPIStreamSourceType, OkHttpClient okHttpClient) { - this(traderKeyID, traderSecretKey, null, traderAPIEndpointType, marketDataAPIStreamSourceType, null, null, null, + MarketDataWebsocketSourceType marketDataWebsocketSourceType, OkHttpClient okHttpClient) { + this(traderKeyID, traderSecretKey, null, traderAPIEndpointType, marketDataWebsocketSourceType, null, null, null, okHttpClient); } @@ -133,7 +142,7 @@ public AlpacaAPI(String brokerAPIKey, String brokerAPISecret, BrokerAPIEndpointT * @param traderSecretKey the Trader secret key * @param traderOAuthToken the Trader OAuth token * @param traderAPIEndpointType the {@link TraderAPIEndpointType} - * @param marketDataAPIStreamSourceType the {@link MarketDataAPIStreamSourceType} + * @param marketDataWebsocketSourceType the {@link MarketDataWebsocketSourceType} * @param brokerAPIKey the Broker API key * @param brokerAPISecret the Broker API secret * @param brokerAPIEndpointType the {@link BrokerAPIEndpointType} @@ -142,18 +151,20 @@ public AlpacaAPI(String brokerAPIKey, String brokerAPISecret, BrokerAPIEndpointT */ public AlpacaAPI(String traderKeyID, String traderSecretKey, String traderOAuthToken, TraderAPIEndpointType traderAPIEndpointType, - MarketDataAPIStreamSourceType marketDataAPIStreamSourceType, + MarketDataWebsocketSourceType marketDataWebsocketSourceType, String brokerAPIKey, String brokerAPISecret, BrokerAPIEndpointType brokerAPIEndpointType, OkHttpClient okHttpClient) { this.traderKeyID = traderKeyID; this.traderSecretKey = traderSecretKey; this.traderOAuthToken = traderOAuthToken; - this.traderAPIEndpointType = traderAPIEndpointType != null ? traderAPIEndpointType : PAPER; - this.marketDataAPIStreamSourceType = marketDataAPIStreamSourceType != null ? - marketDataAPIStreamSourceType : IEX; + this.traderAPIEndpointType = traderAPIEndpointType != null ? + traderAPIEndpointType : PAPER; + this.marketDataWebsocketSourceType = marketDataWebsocketSourceType != null ? + marketDataWebsocketSourceType : IEX; this.brokerAPIKey = brokerAPIKey; this.brokerAPISecret = brokerAPISecret; - this.brokerAPIEndpointType = brokerAPIEndpointType != null ? brokerAPIEndpointType : SANDBOX; + this.brokerAPIEndpointType = brokerAPIEndpointType != null ? + brokerAPIEndpointType : SANDBOX; // Create default OkHttpClient instance if (okHttpClient == null) { @@ -224,16 +235,55 @@ public synchronized AlpacaBrokerAPI broker() { } /** - * Gets the {@link TradesWebsocketInterface}. Lazily instantiated. + * Gets the {@link UpdatesWebsocketInterface}. Lazily instantiated. * - * @return the {@link TradesWebsocketInterface} + * @return the {@link UpdatesWebsocketInterface} */ - public synchronized TradesWebsocketInterface tradesStream() { - if (tradesWebsocket == null) { - tradesWebsocket = new TradesWebsocket(okHttpClient, traderAPIEndpointType, + public synchronized UpdatesWebsocketInterface updatesStream() { + if (updatesWebsocket == null) { + updatesWebsocket = new UpdatesWebsocket(okHttpClient, traderAPIEndpointType, traderKeyID, traderSecretKey, traderOAuthToken); } - return tradesWebsocket; + return updatesWebsocket; + } + + /** + * Gets the {@link StockMarketDataWebsocketInterface}. Lazily instantiated. + * + * @return the {@link StockMarketDataWebsocketInterface} + */ + public synchronized StockMarketDataWebsocketInterface stockMarketDataStream() { + if (stockMarketDataWebsocket == null) { + stockMarketDataWebsocket = new StockMarketDataWebsocket(okHttpClient, + traderKeyID, traderSecretKey, brokerAPIKey, brokerAPISecret, marketDataWebsocketSourceType); + } + return stockMarketDataWebsocket; + } + + /** + * Gets the {@link CryptoMarketDataWebsocketInterface}. Lazily instantiated. + * + * @return the {@link CryptoMarketDataWebsocketInterface} + */ + public synchronized CryptoMarketDataWebsocketInterface cryptoMarketDataStream() { + if (cryptoMarketDataWebsocket == null) { + cryptoMarketDataWebsocket = new CryptoMarketDataWebsocket(okHttpClient, + traderKeyID, traderSecretKey, brokerAPIKey, brokerAPISecret); + } + return cryptoMarketDataWebsocket; + } + + /** + * Gets the {@link NewsMarketDataWebsocketInterface}. Lazily instantiated. + * + * @return the {@link NewsMarketDataWebsocketInterface} + */ + public synchronized NewsMarketDataWebsocketInterface newsMarketDataStream() { + if (newsMarketDataWebsocket == null) { + newsMarketDataWebsocket = new NewsMarketDataWebsocket(okHttpClient, + traderKeyID, traderSecretKey, brokerAPIKey, brokerAPISecret); + } + return newsMarketDataWebsocket; } /** @@ -254,7 +304,7 @@ public static final class Builder { private String traderSecretKey; private String traderOAuthToken; private TraderAPIEndpointType traderAPIEndpointType; - private MarketDataAPIStreamSourceType marketDataAPISourceType; + private MarketDataWebsocketSourceType marketDataWebsocketSourceType; private String brokerAPIKey; private String brokerAPISecret; private BrokerAPIEndpointType brokerAPIEndpointType; @@ -282,8 +332,8 @@ public Builder withTraderAPIEndpointType(TraderAPIEndpointType traderAPIEndpoint return this; } - public Builder withMarketDataAPIStreamSourceType(MarketDataAPIStreamSourceType marketDataAPISourceType) { - this.marketDataAPISourceType = marketDataAPISourceType; + public Builder withMarketDataWebsocketSourceType(MarketDataWebsocketSourceType marketDataWebsocketSourceType) { + this.marketDataWebsocketSourceType = marketDataWebsocketSourceType; return this; } @@ -309,7 +359,7 @@ public Builder withOkHttpClient(OkHttpClient okHttpClient) { public AlpacaAPI build() { return new AlpacaAPI(traderKeyID, traderSecretKey, traderOAuthToken, traderAPIEndpointType, - marketDataAPISourceType, brokerAPIKey, brokerAPISecret, brokerAPIEndpointType, okHttpClient); + marketDataWebsocketSourceType, brokerAPIKey, brokerAPISecret, brokerAPIEndpointType, okHttpClient); } } } diff --git a/src/main/java/net/jacobpeterson/alpaca/AlpacaMarketDataAPI.java b/src/main/java/net/jacobpeterson/alpaca/AlpacaMarketDataAPI.java index 65682a4e..1728c09f 100644 --- a/src/main/java/net/jacobpeterson/alpaca/AlpacaMarketDataAPI.java +++ b/src/main/java/net/jacobpeterson/alpaca/AlpacaMarketDataAPI.java @@ -46,7 +46,6 @@ public class AlpacaMarketDataAPI { checkNotNull(okHttpClient); final boolean traderKeysGiven = traderKeyID != null && traderSecretKey != null; - apiClient = new ApiClient(okHttpClient); apiClient.setServerIndex(traderKeysGiven ? 0 : 1); if (traderKeysGiven) { diff --git a/src/main/java/net/jacobpeterson/alpaca/rest/broker/EventsApiSSE.java b/src/main/java/net/jacobpeterson/alpaca/rest/broker/EventsApiSSE.java index 5df2e615..ea2341e5 100644 --- a/src/main/java/net/jacobpeterson/alpaca/rest/broker/EventsApiSSE.java +++ b/src/main/java/net/jacobpeterson/alpaca/rest/broker/EventsApiSSE.java @@ -155,7 +155,7 @@ private EventSourceListener createEventSourceListener(SSEListener sseList return new EventSourceListener() { @Override public void onClosed(@NotNull EventSource eventSource) { - LOGGER.info("Event source closed: {}", eventSource); + LOGGER.info("Event source closed: eventSource={}", eventSource); sseListener.onClose(); } diff --git a/src/main/java/net/jacobpeterson/alpaca/util/apitype/market_data_api_stream_source_type.json b/src/main/java/net/jacobpeterson/alpaca/util/apitype/market_data_websocket_source_type.json similarity index 56% rename from src/main/java/net/jacobpeterson/alpaca/util/apitype/market_data_api_stream_source_type.json rename to src/main/java/net/jacobpeterson/alpaca/util/apitype/market_data_websocket_source_type.json index 97b87531..5a27c8fb 100644 --- a/src/main/java/net/jacobpeterson/alpaca/util/apitype/market_data_api_stream_source_type.json +++ b/src/main/java/net/jacobpeterson/alpaca/util/apitype/market_data_websocket_source_type.json @@ -1,6 +1,5 @@ { "type": "string", - "javaName": "MarketDataAPIStreamSourceType", "enum": [ "iex", "sip" diff --git a/src/main/java/net/jacobpeterson/alpaca/util/sse/SSEListenerAdapter.java b/src/main/java/net/jacobpeterson/alpaca/util/sse/SSEListenerAdapter.java index 57e33dfb..0bd194d1 100644 --- a/src/main/java/net/jacobpeterson/alpaca/util/sse/SSEListenerAdapter.java +++ b/src/main/java/net/jacobpeterson/alpaca/util/sse/SSEListenerAdapter.java @@ -24,10 +24,10 @@ public void onClose() { } public void onError(@Nullable Throwable throwable, @Nullable Response response) { - LOGGER.error("SSE connection error! {}", response, throwable); + LOGGER.error("SSE connection error! response={}", response, throwable); } public void onMessage(@NotNull T message) { - LOGGER.info("SSE message received: {}", message); + LOGGER.info("SSE message received: message={}", message); } } diff --git a/src/main/java/net/jacobpeterson/alpaca/websocket/AlpacaWebsocket.java b/src/main/java/net/jacobpeterson/alpaca/websocket/AlpacaWebsocket.java index d475cc8a..518435c2 100644 --- a/src/main/java/net/jacobpeterson/alpaca/websocket/AlpacaWebsocket.java +++ b/src/main/java/net/jacobpeterson/alpaca/websocket/AlpacaWebsocket.java @@ -20,13 +20,8 @@ /** * {@link AlpacaWebsocket} represents an abstract websocket for Alpaca. - * - * @param the {@link AlpacaWebsocketMessageListener} type parameter - * @param the 'message type' type parameter - * @param the 'message' type parameter */ -public abstract class AlpacaWebsocket> extends WebSocketListener - implements AlpacaWebsocketInterface { +public abstract class AlpacaWebsocket extends WebSocketListener implements AlpacaWebsocketInterface { /** * Defines a websocket normal closure code. @@ -45,7 +40,7 @@ public abstract class AlpacaWebsocket authenticationMessageFuture; @@ -78,12 +68,8 @@ public abstract class AlpacaWebsocket { @@ -146,7 +125,6 @@ public void onOpen(@NotNull WebSocket webSocket, @NotNull Response response) { onConnection(); } }); - if (alpacaWebsocketStateListener != null) { alpacaWebsocketStateListener.onOpen(response); } @@ -155,16 +133,13 @@ public void onOpen(@NotNull WebSocket webSocket, @NotNull Response response) { @Override public void onClosed(@NotNull WebSocket webSocket, int code, @NotNull String reason) { connected = false; - if (intentionalClose) { - LOGGER.info("{} websocket closed.", websocketName); - LOGGER.debug("Close code: {}, Reason: {}", code, reason); + LOGGER.info("{} websocket closed. code={}, reason={}", websocketName, code, reason); cleanupState(); } else { - LOGGER.error("{} websocket closed unintentionally! Code: {}, Reason: {}", websocketName, code, reason); + LOGGER.error("{} websocket closed unintentionally! code={}, reason={}", websocketName, code, reason); handleReconnectionAttempt(); } - if (alpacaWebsocketStateListener != null) { alpacaWebsocketStateListener.onClosed(code, reason); } @@ -173,13 +148,11 @@ public void onClosed(@NotNull WebSocket webSocket, int code, @NotNull String rea @Override public void onFailure(@NotNull WebSocket webSocket, @NotNull Throwable cause, @Nullable Response response) { LOGGER.error("{} websocket failure!", websocketName, cause); - // A websocket failure occurs when either there is a connection failure or when the client throws // an exception when receiving a message. In either case, OkHttp will close the websocket connection, // so try to reopen it. connected = false; handleReconnectionAttempt(); - if (alpacaWebsocketStateListener != null) { alpacaWebsocketStateListener.onFailure(cause); } @@ -225,6 +198,16 @@ protected void cleanupState() { reconnectAttempts = 0; } + /** + * Sends a message to the underlying {@link #websocket}. + * + * @param message the message + */ + protected void sendWebsocketMessage(String message) { + LOGGER.trace("Websocket message sent: {}", message); + websocket.send(message); + } + /** * Called asynchronously when a websocket connection is made. */ @@ -248,40 +231,30 @@ public Future getAuthorizationFuture() { return authenticationMessageFuture; } - /** - * Calls the {@link AlpacaWebsocketMessageListener}. - * - * @param messageType the message type - * @param message the message - */ - protected void callListener(T messageType, M message) { - if (listener != null) { - try { - listener.onMessage(messageType, message); - } catch (Exception exception) { - LOGGER.error("{} listener threw exception!", websocketName, exception); - } - } - } - @Override - public void setListener(L listener) { - this.listener = listener; - } - - public AlpacaWebsocketStateListener getWebsocketStateListener() { - return alpacaWebsocketStateListener; - } - public void setAlpacaWebsocketStateListener(AlpacaWebsocketStateListener alpacaWebsocketStateListener) { this.alpacaWebsocketStateListener = alpacaWebsocketStateListener; } + @Override public boolean doesAutomaticallyReconnect() { return automaticallyReconnect; } + @Override public void setAutomaticallyReconnect(boolean automaticallyReconnect) { this.automaticallyReconnect = automaticallyReconnect; } + + public OkHttpClient getOkHttpClient() { + return okHttpClient; + } + + public HttpUrl getWebsocketURL() { + return websocketURL; + } + + public WebSocket getWebsocket() { + return websocket; + } } diff --git a/src/main/java/net/jacobpeterson/alpaca/websocket/AlpacaWebsocketInterface.java b/src/main/java/net/jacobpeterson/alpaca/websocket/AlpacaWebsocketInterface.java index 033475a2..48adfb4d 100644 --- a/src/main/java/net/jacobpeterson/alpaca/websocket/AlpacaWebsocketInterface.java +++ b/src/main/java/net/jacobpeterson/alpaca/websocket/AlpacaWebsocketInterface.java @@ -7,10 +7,8 @@ /** * {@link AlpacaWebsocketInterface} defines an interface for Alpaca websockets. - * - * @param the {@link AlpacaWebsocketMessageListener} type parameter */ -public interface AlpacaWebsocketInterface> { +public interface AlpacaWebsocketInterface { /** * Connects this Websocket. @@ -74,9 +72,23 @@ default boolean waitForAuthorization(long timeout, TimeUnit unit) { } /** - * Sets the {@link AlpacaWebsocketMessageListener} for this {@link AlpacaWebsocketInterface}. + * Sets the {@link AlpacaWebsocketStateListener}. * - * @param listener the {@link AlpacaWebsocketMessageListener} + * @param alpacaWebsocketStateListener the {@link AlpacaWebsocketStateListener} */ - void setListener(L listener); + void setAlpacaWebsocketStateListener(AlpacaWebsocketStateListener alpacaWebsocketStateListener); + + /** + * Returns true if this websocket automatically reconnects, false otherwise. + * + * @return a boolean + */ + boolean doesAutomaticallyReconnect(); + + /** + * Sets whether to automatically reconnect and reauthenticate on a websocket failure. true by default. + * + * @param automaticallyReconnect true to automatically reconnect, false otherwise + */ + void setAutomaticallyReconnect(boolean automaticallyReconnect); } diff --git a/src/main/java/net/jacobpeterson/alpaca/websocket/AlpacaWebsocketMessageListener.java b/src/main/java/net/jacobpeterson/alpaca/websocket/AlpacaWebsocketMessageListener.java deleted file mode 100644 index da55535c..00000000 --- a/src/main/java/net/jacobpeterson/alpaca/websocket/AlpacaWebsocketMessageListener.java +++ /dev/null @@ -1,20 +0,0 @@ -package net.jacobpeterson.alpaca.websocket; - -/** - * {@link AlpacaWebsocketMessageListener} defines a listener interface for messages from an {@link AlpacaWebsocket} - * instances. - * - * @param the 'message type' type parameter - * @param the 'message' type parameter - */ -@FunctionalInterface -public interface AlpacaWebsocketMessageListener { - - /** - * Called when a message is received. - * - * @param messageType the message type - * @param message the message - */ - void onMessage(T messageType, M message); -} diff --git a/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/MarketDataWebsocket.java b/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/MarketDataWebsocket.java new file mode 100644 index 00000000..fbb7dca9 --- /dev/null +++ b/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/MarketDataWebsocket.java @@ -0,0 +1,198 @@ +package net.jacobpeterson.alpaca.websocket.marketdata; + +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import net.jacobpeterson.alpaca.model.websocket.marketdata.model.MarketDataMessage; +import net.jacobpeterson.alpaca.model.websocket.marketdata.model.control.ErrorMessage; +import net.jacobpeterson.alpaca.model.websocket.marketdata.model.control.SuccessMessage; +import net.jacobpeterson.alpaca.websocket.AlpacaWebsocket; +import okhttp3.HttpUrl; +import okhttp3.OkHttpClient; +import okhttp3.WebSocket; +import org.jetbrains.annotations.NotNull; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.Set; +import java.util.function.Function; + +import static com.google.common.collect.Sets.difference; +import static com.google.gson.JsonParser.parseString; +import static java.util.concurrent.TimeUnit.SECONDS; +import static net.jacobpeterson.alpaca.model.websocket.marketdata.model.control.SuccessMessageType.AUTHENTICATED; +import static net.jacobpeterson.alpaca.openapi.marketdata.JSON.getGson; + +/** + * {@link MarketDataWebsocket} is an abstract {@link AlpacaWebsocket} implementation for + * Market Data Streaming. + * + * @param the 'message type' type + * @param the 'subscription message' type + * @param the 'listener' type + */ +public abstract class MarketDataWebsocket extends AlpacaWebsocket + implements MarketDataWebsocketInterface { + + private static final Logger LOGGER = LoggerFactory.getLogger(MarketDataWebsocket.class); + private static final Set AUTH_FAILURE_MESSAGES = Set.of("auth failed", "auth timeout", "not authenticated"); + + protected final String authKey; + protected final String authSecret; + protected final Class messageTypeClass; + protected final Class subscriptionsMessageClass; + protected S subscriptionsMessage; + protected L listener; + + /** + * Instantiates a new {@link MarketDataWebsocket}. + * + * @param okHttpClient the {@link OkHttpClient} + * @param websocketURL the websocket {@link HttpUrl} + * @param websocketMarketDataTypeName the websocket market data type name {@link String} + * @param traderKeyID the trader key ID + * @param traderSecretKey the trader secret key + * @param brokerAPIKey the broker API key + * @param brokerAPISecret the broker API secret + * @param messageTypeClass the {@link T} message type {@link Class} + * @param subscriptionsMessageClass the {@link S} subscription message {@link Class} + */ + protected MarketDataWebsocket(OkHttpClient okHttpClient, HttpUrl websocketURL, String websocketMarketDataTypeName, + String traderKeyID, String traderSecretKey, String brokerAPIKey, String brokerAPISecret, + Class messageTypeClass, Class subscriptionsMessageClass) { + super(okHttpClient, websocketURL, websocketMarketDataTypeName + " Market Data"); + final boolean traderKeysGiven = traderKeyID != null && traderSecretKey != null; + this.authKey = traderKeysGiven ? traderKeyID : brokerAPIKey; + this.authSecret = traderKeysGiven ? traderSecretKey : brokerAPISecret; + this.messageTypeClass = messageTypeClass; + this.subscriptionsMessageClass = subscriptionsMessageClass; + } + + @Override + protected void cleanupState() { + super.cleanupState(); + subscriptionsMessage = null; + } + + @Override + protected void onConnection() { + sendAuthenticationMessage(); + } + + @Override + protected void onReconnection() { + sendAuthenticationMessage(); + if (waitForAuthorization(5, SECONDS) && subscriptionsMessage != null) { + sendSubscriptionMessage(subscriptionsMessage, true); + } + } + + @Override + protected void sendAuthenticationMessage() { + // Ensure that the authorization Future exists + getAuthorizationFuture(); + + final JsonObject authObject = new JsonObject(); + authObject.addProperty("action", "auth"); + authObject.addProperty("key", authKey); + authObject.addProperty("secret", authSecret); + + LOGGER.info("{} websocket sending authentication message...", websocketName); + sendWebsocketMessage(authObject.toString()); + } + + @Override + public void onMessage(@NotNull WebSocket webSocket, @NotNull String message) { // Text framing + LOGGER.trace("Websocket message received: {}", message); + + // Loop through message array and handle each message according to its type + for (JsonElement arrayElement : parseString(message).getAsJsonArray()) { + final JsonObject messageObject = arrayElement.getAsJsonObject(); + final T messageType = getGson().fromJson(messageObject.get("T"), messageTypeClass); + if (isSuccessMessageType(messageType)) { + final SuccessMessage successMessage = getGson().fromJson(messageObject, SuccessMessage.class); + if (successMessage.getMessageType() == AUTHENTICATED) { + LOGGER.info("{} websocket authenticated.", websocketName); + authenticated = true; + if (authenticationMessageFuture != null) { + authenticationMessageFuture.complete(true); + } + } + } else if (isErrorMessageType(messageType)) { + final ErrorMessage errorMessage = getGson().fromJson(messageObject, ErrorMessage.class); + if (AUTH_FAILURE_MESSAGES.contains(errorMessage.getMessage()) && authenticationMessageFuture != null) { + authenticated = false; + authenticationMessageFuture.complete(false); + throw new RuntimeException(websocketName + " websocket authentication failed!"); + } else { + throw new RuntimeException(websocketName + " websocket error! Message: " + errorMessage); + } + } else if (isSubscriptionMessageType(messageType)) { + subscriptionsMessage = getGson().fromJson(messageObject, subscriptionsMessageClass); + } else if (listener != null) { + callListenerWithMessage(messageType, messageObject); + } + } + } + + /** + * Sets the websocket stream's subscriptions for a specific message type. + * + * @param previousSubscriptions the previous subscriptions symbol {@link Set} + * @param newSubscriptions the new subscriptions symbol {@link Set} + * @param subscriptionUpdateObjectCreator the subscription update object creator {@link Function} + */ + protected void setSubscriptions(@NotNull Set previousSubscriptions, + @NotNull Set newSubscriptions, @NotNull Function, S> subscriptionUpdateObjectCreator) { + // Unsubscribe from previous subscriptions + final Set unsubscribeSet = difference(previousSubscriptions, newSubscriptions); + if (!unsubscribeSet.isEmpty()) { + sendSubscriptionMessage(subscriptionUpdateObjectCreator.apply(unsubscribeSet), false); + } + // Subscribe to new subscriptions + final Set subscribeSet = difference(newSubscriptions, previousSubscriptions); + if (!subscribeSet.isEmpty()) { + sendSubscriptionMessage(subscriptionUpdateObjectCreator.apply(subscribeSet), true); + } + } + + private void sendSubscriptionMessage(S subscriptionsMessage, boolean subscribe) { + final JsonObject subscribeObject = getGson().toJsonTree(subscriptionsMessage).getAsJsonObject(); + subscribeObject.addProperty("action", subscribe ? "subscribe" : "unsubscribe"); + sendWebsocketMessage(getGson().toJson(subscribeObject)); + } + + /** + * Whether the given messageType is "success". + * + * @param messageType the message type + * + * @return a boolean + */ + protected abstract boolean isSuccessMessageType(T messageType); + + /** + * Whether the given messageType is "error". + * + * @param messageType the message type + * + * @return a boolean + */ + protected abstract boolean isErrorMessageType(T messageType); + + /** + * Whether the given messageType is "subscription". + * + * @param messageType the message type + * + * @return a boolean + */ + protected abstract boolean isSubscriptionMessageType(T messageType); + + /** + * Calls the {@link #listener} with a {@link MarketDataMessage} from a {@link JsonObject}. + * + * @param messageType the message type + * @param messageObject the message {@link JsonObject} + */ + protected abstract void callListenerWithMessage(T messageType, JsonObject messageObject); +} diff --git a/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/MarketDataWebsocketInterface.java b/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/MarketDataWebsocketInterface.java new file mode 100644 index 00000000..989389eb --- /dev/null +++ b/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/MarketDataWebsocketInterface.java @@ -0,0 +1,8 @@ +package net.jacobpeterson.alpaca.websocket.marketdata; + +import net.jacobpeterson.alpaca.websocket.AlpacaWebsocketInterface; + +/** + * {@link MarketDataWebsocketInterface} is an {@link AlpacaWebsocketInterface} for {@link MarketDataWebsocket}. + */ +public interface MarketDataWebsocketInterface extends AlpacaWebsocketInterface {} diff --git a/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/streams/crypto/CryptoMarketDataListener.java b/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/streams/crypto/CryptoMarketDataListener.java new file mode 100644 index 00000000..ae8efe52 --- /dev/null +++ b/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/streams/crypto/CryptoMarketDataListener.java @@ -0,0 +1,55 @@ +package net.jacobpeterson.alpaca.websocket.marketdata.streams.crypto; + +import net.jacobpeterson.alpaca.model.websocket.marketdata.streams.crypto.model.bar.CryptoBarMessage; +import net.jacobpeterson.alpaca.model.websocket.marketdata.streams.crypto.model.orderbook.CryptoOrderBookMessage; +import net.jacobpeterson.alpaca.model.websocket.marketdata.streams.crypto.model.quote.CryptoQuoteMessage; +import net.jacobpeterson.alpaca.model.websocket.marketdata.streams.crypto.model.trade.CryptoTradeMessage; + +/** + * {@link CryptoMarketDataListener} defines a listener interface for {@link CryptoMarketDataWebsocketInterface} + * messages. + */ +public interface CryptoMarketDataListener { + + /** + * Called when a {@link CryptoTradeMessage} is received. + * + * @param trade the {@link CryptoTradeMessage} + */ + void onTrade(CryptoTradeMessage trade); + + /** + * Called when a {@link CryptoQuoteMessage} is received. + * + * @param quote the {@link CryptoQuoteMessage} + */ + void onQuote(CryptoQuoteMessage quote); + + /** + * Called when a {@link CryptoBarMessage} is received. + * + * @param bar the {@link CryptoBarMessage} + */ + void onMinuteBar(CryptoBarMessage bar); + + /** + * Called when a {@link CryptoBarMessage} is received. + * + * @param bar the {@link CryptoBarMessage} + */ + void onDailyBar(CryptoBarMessage bar); + + /** + * Called when a {@link CryptoBarMessage} is received. + * + * @param bar the {@link CryptoBarMessage} + */ + void onUpdatedBar(CryptoBarMessage bar); + + /** + * Called when a {@link CryptoOrderBookMessage} is received. + * + * @param orderBook the {@link CryptoOrderBookMessage} + */ + void onOrderBook(CryptoOrderBookMessage orderBook); +} diff --git a/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/streams/crypto/CryptoMarketDataListenerAdapter.java b/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/streams/crypto/CryptoMarketDataListenerAdapter.java new file mode 100644 index 00000000..cf1cc348 --- /dev/null +++ b/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/streams/crypto/CryptoMarketDataListenerAdapter.java @@ -0,0 +1,30 @@ +package net.jacobpeterson.alpaca.websocket.marketdata.streams.crypto; + +import net.jacobpeterson.alpaca.model.websocket.marketdata.streams.crypto.model.bar.CryptoBarMessage; +import net.jacobpeterson.alpaca.model.websocket.marketdata.streams.crypto.model.orderbook.CryptoOrderBookMessage; +import net.jacobpeterson.alpaca.model.websocket.marketdata.streams.crypto.model.quote.CryptoQuoteMessage; +import net.jacobpeterson.alpaca.model.websocket.marketdata.streams.crypto.model.trade.CryptoTradeMessage; + +/** + * {@link CryptoMarketDataListenerAdapter} is an adapter for {@link CryptoMarketDataListener}. + */ +public class CryptoMarketDataListenerAdapter implements CryptoMarketDataListener { + + @Override + public void onTrade(CryptoTradeMessage trade) {} + + @Override + public void onQuote(CryptoQuoteMessage quote) {} + + @Override + public void onMinuteBar(CryptoBarMessage bar) {} + + @Override + public void onDailyBar(CryptoBarMessage bar) {} + + @Override + public void onUpdatedBar(CryptoBarMessage bar) {} + + @Override + public void onOrderBook(CryptoOrderBookMessage orderBook) {} +} diff --git a/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/streams/crypto/CryptoMarketDataWebsocket.java b/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/streams/crypto/CryptoMarketDataWebsocket.java index 8eed2ce2..ed3a88ad 100644 --- a/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/streams/crypto/CryptoMarketDataWebsocket.java +++ b/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/streams/crypto/CryptoMarketDataWebsocket.java @@ -1,27 +1,175 @@ package net.jacobpeterson.alpaca.websocket.marketdata.streams.crypto; -import net.jacobpeterson.alpaca.model.endpoint.marketdata.crypto.realtime.bar.CryptoBarMessage; -import net.jacobpeterson.alpaca.model.endpoint.marketdata.crypto.realtime.quote.CryptoQuoteMessage; -import net.jacobpeterson.alpaca.model.endpoint.marketdata.crypto.realtime.trade.CryptoTradeMessage; +import com.google.gson.JsonObject; +import net.jacobpeterson.alpaca.model.websocket.marketdata.streams.crypto.model.CryptoMarketDataMessageType; +import net.jacobpeterson.alpaca.model.websocket.marketdata.streams.crypto.model.bar.CryptoBarMessage; +import net.jacobpeterson.alpaca.model.websocket.marketdata.streams.crypto.model.control.CryptoSubscriptionsMessage; +import net.jacobpeterson.alpaca.model.websocket.marketdata.streams.crypto.model.orderbook.CryptoOrderBookMessage; +import net.jacobpeterson.alpaca.model.websocket.marketdata.streams.crypto.model.quote.CryptoQuoteMessage; +import net.jacobpeterson.alpaca.model.websocket.marketdata.streams.crypto.model.trade.CryptoTradeMessage; import net.jacobpeterson.alpaca.websocket.marketdata.MarketDataWebsocket; +import okhttp3.HttpUrl; import okhttp3.OkHttpClient; +import java.util.Set; + +import static net.jacobpeterson.alpaca.model.websocket.marketdata.streams.crypto.model.CryptoMarketDataMessageType.ERROR; +import static net.jacobpeterson.alpaca.model.websocket.marketdata.streams.crypto.model.CryptoMarketDataMessageType.SUBSCRIPTION; +import static net.jacobpeterson.alpaca.model.websocket.marketdata.streams.crypto.model.CryptoMarketDataMessageType.SUCCESS; +import static net.jacobpeterson.alpaca.openapi.marketdata.JSON.getGson; + /** - * {@link CryptoMarketDataWebsocket} is a {@link MarketDataWebsocket} for - * Realtime - * Crypto Market Data + * {@link CryptoMarketDataWebsocket} is an implementation for {@link CryptoMarketDataWebsocketInterface}. */ -public class CryptoMarketDataWebsocket extends MarketDataWebsocket { +public class CryptoMarketDataWebsocket + extends MarketDataWebsocket + implements CryptoMarketDataWebsocketInterface { /** * Instantiates a new {@link CryptoMarketDataWebsocket}. * - * @param okHttpClient the {@link OkHttpClient} - * @param keyID the key ID - * @param secretKey the secret key + * @param okHttpClient the {@link OkHttpClient} + * @param traderKeyID the trader key ID + * @param traderSecretKey the trader secret key + * @param brokerAPIKey the broker API key + * @param brokerAPISecret the broker API secret */ - public CryptoMarketDataWebsocket(OkHttpClient okHttpClient, String keyID, String secretKey) { - super(okHttpClient, "v1beta3/crypto/us", "Crypto", keyID, secretKey, CryptoTradeMessage.class, - CryptoQuoteMessage.class, CryptoBarMessage.class); + public CryptoMarketDataWebsocket(OkHttpClient okHttpClient, String traderKeyID, String traderSecretKey, + String brokerAPIKey, String brokerAPISecret) { + super(okHttpClient, new HttpUrl.Builder() + .scheme("https") + .host("stream.data.alpaca.markets") + .addPathSegments("v1beta3/crypto/us") + .build(), + "Crypto", traderKeyID, traderSecretKey, brokerAPIKey, brokerAPISecret, + CryptoMarketDataMessageType.class, CryptoSubscriptionsMessage.class); + } + + @Override + protected boolean isSuccessMessageType(CryptoMarketDataMessageType messageType) { + return messageType == SUCCESS; + } + + @Override + protected boolean isErrorMessageType(CryptoMarketDataMessageType messageType) { + return messageType == ERROR; + } + + @Override + protected boolean isSubscriptionMessageType(CryptoMarketDataMessageType messageType) { + return messageType == SUBSCRIPTION; + } + + @Override + protected void callListenerWithMessage(CryptoMarketDataMessageType messageType, JsonObject messageObject) { + switch (messageType) { + case TRADES: + listener.onTrade(getGson().fromJson(messageObject, CryptoTradeMessage.class)); + break; + case QUOTES: + listener.onQuote(getGson().fromJson(messageObject, CryptoQuoteMessage.class)); + break; + case MINUTE_BARS: + listener.onMinuteBar(getGson().fromJson(messageObject, CryptoBarMessage.class)); + break; + case DAILY_BARS: + listener.onDailyBar(getGson().fromJson(messageObject, CryptoBarMessage.class)); + break; + case UPDATED_BARS: + listener.onUpdatedBar(getGson().fromJson(messageObject, CryptoBarMessage.class)); + break; + case ORDER_BOOKS: + listener.onOrderBook(getGson().fromJson(messageObject, CryptoOrderBookMessage.class)); + break; + default: + throw new UnsupportedOperationException(); + } + } + + @Override + public void setListener(CryptoMarketDataListener listener) { + this.listener = listener; + } + + @Override + public void setTradeSubscriptions(Set symbols) { + symbols = symbols == null ? Set.of() : symbols; + setSubscriptions(getTradeSubscriptions(), symbols, + set -> newEmptyCryptoSubscriptionsMessage().withTrades(set)); + } + + @Override + public Set getTradeSubscriptions() { + return subscriptionsMessage == null ? Set.of() : subscriptionsMessage.getTrades(); + } + + @Override + public void setQuoteSubscriptions(Set symbols) { + symbols = symbols == null ? Set.of() : symbols; + setSubscriptions(getQuoteSubscriptions(), symbols, + set -> newEmptyCryptoSubscriptionsMessage().withQuotes(set)); + } + + @Override + public Set getQuoteSubscriptions() { + return subscriptionsMessage == null ? Set.of() : subscriptionsMessage.getQuotes(); + } + + @Override + public void setMinuteBarSubscriptions(Set symbols) { + symbols = symbols == null ? Set.of() : symbols; + setSubscriptions(getMinuteBarSubscriptions(), symbols, + set -> newEmptyCryptoSubscriptionsMessage().withMinuteBars(set)); + } + + @Override + public Set getMinuteBarSubscriptions() { + return subscriptionsMessage == null ? Set.of() : subscriptionsMessage.getMinuteBars(); + } + + @Override + public void setDailyBarSubscriptions(Set symbols) { + symbols = symbols == null ? Set.of() : symbols; + setSubscriptions(getDailyBarSubscriptions(), symbols, + set -> newEmptyCryptoSubscriptionsMessage().withDailyBars(set)); + } + + @Override + public Set getDailyBarSubscriptions() { + return subscriptionsMessage == null ? Set.of() : subscriptionsMessage.getDailyBars(); + } + + @Override + public void setUpdatedBarSubscriptions(Set symbols) { + symbols = symbols == null ? Set.of() : symbols; + setSubscriptions(getUpdatedBarSubscriptions(), symbols, + set -> newEmptyCryptoSubscriptionsMessage().withUpdatedBars(set)); + } + + @Override + public Set getUpdatedBarSubscriptions() { + return subscriptionsMessage == null ? Set.of() : subscriptionsMessage.getUpdatedBars(); + } + + @Override + public void setOrderBookSubscriptions(Set symbols) { + symbols = symbols == null ? Set.of() : symbols; + setSubscriptions(getOrderBookSubscriptions(), symbols, + set -> newEmptyCryptoSubscriptionsMessage().withOrderBooks(set)); + } + + @Override + public Set getOrderBookSubscriptions() { + return subscriptionsMessage == null ? Set.of() : subscriptionsMessage.getOrderBooks(); + } + + private CryptoSubscriptionsMessage newEmptyCryptoSubscriptionsMessage() { + return new CryptoSubscriptionsMessage() + .withTrades(null) + .withQuotes(null) + .withMinuteBars(null) + .withDailyBars(null) + .withUpdatedBars(null) + .withOrderBooks(null); } } diff --git a/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/streams/crypto/CryptoMarketDataWebsocketInterface.java b/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/streams/crypto/CryptoMarketDataWebsocketInterface.java new file mode 100644 index 00000000..a24e250f --- /dev/null +++ b/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/streams/crypto/CryptoMarketDataWebsocketInterface.java @@ -0,0 +1,116 @@ +package net.jacobpeterson.alpaca.websocket.marketdata.streams.crypto; + +import net.jacobpeterson.alpaca.model.websocket.marketdata.streams.crypto.model.CryptoMarketDataMessageType; +import net.jacobpeterson.alpaca.websocket.marketdata.MarketDataWebsocketInterface; + +import java.util.Set; + +/** + * {@link CryptoMarketDataWebsocketInterface} is a {@link MarketDataWebsocketInterface} for + * {@link CryptoMarketDataWebsocket}. + */ +public interface CryptoMarketDataWebsocketInterface extends MarketDataWebsocketInterface { + + /** + * Sets the {@link CryptoMarketDataListener}. + * + * @param listener the {@link CryptoMarketDataListener} + */ + void setListener(CryptoMarketDataListener listener); + + /** + * Subscribes the given symbols to {@link CryptoMarketDataMessageType#TRADES}. This will remove all + * previous {@link CryptoMarketDataMessageType#QUOTES} subscriptions. + * + * @param symbols the {@link Set} of symbols (use an asterisk ("*") as a wildcard to subscribe to all available + * symbols) or null to unsubscribe from all symbols + */ + void setTradeSubscriptions(Set symbols); + + /** + * Gets the current symbols subscribed to {@link CryptoMarketDataMessageType#TRADES}. + * + * @return a {@link Set} of {@link String} symbols + */ + Set getTradeSubscriptions(); + + /** + * Subscribes the given symbols to {@link CryptoMarketDataMessageType#QUOTES}. This will remove all + * previous {@link CryptoMarketDataMessageType#QUOTES} subscriptions. + * + * @param symbols the {@link Set} of symbols (use an asterisk ("*") as a wildcard to subscribe to all available + * symbols) or null to unsubscribe from all symbols + */ + void setQuoteSubscriptions(Set symbols); + + /** + * Gets the current symbols subscribed to {@link CryptoMarketDataMessageType#QUOTES}. + * + * @return a {@link Set} of {@link String} symbols + */ + Set getQuoteSubscriptions(); + + /** + * Subscribes the given symbols to {@link CryptoMarketDataMessageType#MINUTE_BARS}. This will remove + * all previous {@link CryptoMarketDataMessageType#QUOTES} subscriptions. + * + * @param symbols the {@link Set} of symbols (use an asterisk ("*") as a wildcard to subscribe to all available + * symbols) or null to unsubscribe from all symbols + */ + void setMinuteBarSubscriptions(Set symbols); + + /** + * Gets the current symbols subscribed to {@link CryptoMarketDataMessageType#MINUTE_BARS}. + * + * @return a {@link Set} of {@link String} symbols + */ + Set getMinuteBarSubscriptions(); + + /** + * Subscribes the given symbols to {@link CryptoMarketDataMessageType#DAILY_BARS}. This will remove all + * previous {@link CryptoMarketDataMessageType#QUOTES} subscriptions. + * + * @param symbols the {@link Set} of symbols (use an asterisk ("*") as a wildcard to subscribe to all available + * symbols) or null to unsubscribe from all symbols + */ + void setDailyBarSubscriptions(Set symbols); + + /** + * Gets the current symbols subscribed to {@link CryptoMarketDataMessageType#DAILY_BARS}. + * + * @return a {@link Set} of {@link String} symbols + */ + Set getDailyBarSubscriptions(); + + /** + * Subscribes the given symbols to {@link CryptoMarketDataMessageType#UPDATED_BARS}. This will remove + * all previous {@link CryptoMarketDataMessageType#QUOTES} subscriptions. + * + * @param symbols the {@link Set} of symbols (use an asterisk ("*") as a wildcard to subscribe to all available + * symbols) or null to unsubscribe from all symbols + */ + void setUpdatedBarSubscriptions(Set symbols); + + /** + * Gets the current symbols subscribed to {@link CryptoMarketDataMessageType#UPDATED_BARS}. + * + * @return a {@link Set} of {@link String} symbols + */ + Set getUpdatedBarSubscriptions(); + + /** + * Subscribes the given symbols to {@link CryptoMarketDataMessageType#ORDER_BOOKS}. This will remove + * all previous {@link CryptoMarketDataMessageType#QUOTES} subscriptions. + * + * @param symbols the {@link Set} of symbols (use an asterisk ("*") as a wildcard to subscribe to all available + * symbols) or null to unsubscribe from all symbols + */ + void setOrderBookSubscriptions(Set symbols); + + /** + * Gets the current symbols subscribed to {@link CryptoMarketDataMessageType#ORDER_BOOKS}. + * + * @return a {@link Set} of {@link String} symbols + */ + Set getOrderBookSubscriptions(); +} diff --git a/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/streams/crypto/model/control/crypto_subscriptions_message.json b/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/streams/crypto/model/control/crypto_subscriptions_message.json new file mode 100644 index 00000000..6d8438a8 --- /dev/null +++ b/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/streams/crypto/model/control/crypto_subscriptions_message.json @@ -0,0 +1,34 @@ +{ + "type": "object", + "extends": { + "existingJavaType": "net.jacobpeterson.alpaca.model.websocket.marketdata.streams.crypto.model.CryptoMarketDataMessage" + }, + "properties": { + "trades": { + "existingJavaType": "java.util.Set", + "title": "The {@link java.util.Set} of symbols subscribed to trades." + }, + "quotes": { + "existingJavaType": "java.util.Set", + "title": "The {@link java.util.Set} of symbols subscribed to quotes." + }, + "bars": { + "existingJavaType": "java.util.Set", + "javaName": "minuteBars", + "title": "The {@link java.util.Set} of symbols subscribed to minute bars." + }, + "dailyBars": { + "existingJavaType": "java.util.Set", + "title": "The {@link java.util.Set} of symbols subscribed to daily bars." + }, + "updatedBars": { + "existingJavaType": "java.util.Set", + "title": "The {@link java.util.Set} of symbols subscribed to updated bars." + }, + "orderbooks": { + "existingJavaType": "java.util.Set", + "javaName": "orderBooks", + "title": "The {@link java.util.Set} of symbols subscribed to order books." + } + } +} diff --git a/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/streams/crypto/model/orderbook/crypto_orderbook_entry.json b/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/streams/crypto/model/orderbook/crypto_order_book_entry.json similarity index 100% rename from src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/streams/crypto/model/orderbook/crypto_orderbook_entry.json rename to src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/streams/crypto/model/orderbook/crypto_order_book_entry.json diff --git a/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/streams/crypto/model/orderbook/crypto_order_book_message.json b/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/streams/crypto/model/orderbook/crypto_order_book_message.json new file mode 100644 index 00000000..76c632fa --- /dev/null +++ b/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/streams/crypto/model/orderbook/crypto_order_book_message.json @@ -0,0 +1,28 @@ +{ + "type": "object", + "extends": { + "existingJavaType": "net.jacobpeterson.alpaca.model.websocket.marketdata.streams.crypto.model.CryptoMarketDataMessage" + }, + "properties": { + "S": { + "existingJavaType": "java.lang.String", + "javaName": "symbol", + "title": "The symbol." + }, + "a": { + "existingJavaType": "java.util.Set", + "javaName": "asks", + "title": "The {@link java.util.Set} of {@link net.jacobpeterson.alpaca.model.websocket.marketdata.streams.crypto.model.orderbook.CryptoOrderBookEntry} asks." + }, + "b": { + "existingJavaType": "java.util.Set", + "javaName": "bids", + "title": "The {@link java.util.Set} of {@link net.jacobpeterson.alpaca.model.websocket.marketdata.streams.crypto.model.orderbook.CryptoOrderBookEntry} bids." + }, + "t": { + "existingJavaType": "java.time.OffsetDateTime", + "javaName": "timestamp", + "title": "The timestamp with nanosecond precision." + } + } +} diff --git a/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/streams/crypto/model/orderbook/crypto_orderbook_message.json b/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/streams/crypto/model/orderbook/crypto_orderbook_message.json deleted file mode 100644 index 80aa0c54..00000000 --- a/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/streams/crypto/model/orderbook/crypto_orderbook_message.json +++ /dev/null @@ -1,28 +0,0 @@ -{ - "type": "object", - "extends": { - "existingJavaType": "net.jacobpeterson.alpaca.model.websocket.marketdata.streams.crypto.model.CryptoMarketDataMessage" - }, - "properties": { - "S": { - "existingJavaType": "java.lang.String", - "javaName": "symbol", - "title": "The symbol." - }, - "a": { - "existingJavaType": "java.util.ArrayList", - "javaName": "asks", - "title": "The {@link java.util.ArrayList} of {@link net.jacobpeterson.alpaca.model.websocket.marketdata.streams.crypto.model.orderbook.CryptoOrderBookEntry} asks." - }, - "b": { - "existingJavaType": "java.util.ArrayList", - "javaName": "bids", - "title": "The {@link java.util.ArrayList} of {@link net.jacobpeterson.alpaca.model.websocket.marketdata.streams.crypto.model.orderbook.CryptoOrderBookEntry} bids." - }, - "t": { - "existingJavaType": "java.time.OffsetDateTime", - "javaName": "timestamp", - "title": "The timestamp with nanosecond precision." - } - } -} diff --git a/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/streams/crypto/model/subscriptions/crypto_subscriptions_message.json b/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/streams/crypto/model/subscriptions/crypto_subscriptions_message.json deleted file mode 100644 index 19b1f191..00000000 --- a/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/streams/crypto/model/subscriptions/crypto_subscriptions_message.json +++ /dev/null @@ -1,33 +0,0 @@ -{ - "type": "object", - "extends": { - "existingJavaType": "net.jacobpeterson.alpaca.model.websocket.marketdata.streams.crypto.model.CryptoMarketDataMessage" - }, - "properties": { - "trades": { - "existingJavaType": "java.util.ArrayList", - "title": "The {@link java.util.ArrayList} of symbols subscribed to trades." - }, - "quotes": { - "existingJavaType": "java.util.ArrayList", - "title": "The {@link java.util.ArrayList} of symbols subscribed to quotes." - }, - "bars": { - "existingJavaType": "java.util.ArrayList", - "javaName": "minuteBars", - "title": "The {@link java.util.ArrayList} of symbols subscribed to minute bars." - }, - "dailyBars": { - "existingJavaType": "java.util.ArrayList", - "title": "The {@link java.util.ArrayList} of symbols subscribed to daily bars." - }, - "updatedBars": { - "existingJavaType": "java.util.ArrayList", - "title": "The {@link java.util.ArrayList} of symbols subscribed to updated bars." - }, - "orderbooks": { - "existingJavaType": "java.util.ArrayList", - "title": "The {@link java.util.ArrayList} of symbols subscribed to orderbooks." - } - } -} diff --git a/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/streams/news/NewsMarketDataListener.java b/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/streams/news/NewsMarketDataListener.java new file mode 100644 index 00000000..c47cb6dc --- /dev/null +++ b/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/streams/news/NewsMarketDataListener.java @@ -0,0 +1,17 @@ +package net.jacobpeterson.alpaca.websocket.marketdata.streams.news; + +import net.jacobpeterson.alpaca.model.websocket.marketdata.streams.news.model.news.NewsMessage; + +/** + * {@link NewsMarketDataListener} defines a listener interface for {@link NewsMarketDataWebsocketInterface} messages. + */ +@FunctionalInterface +public interface NewsMarketDataListener { + + /** + * Called when a {@link NewsMessage} is received. + * + * @param news the {@link NewsMessage} + */ + void onNews(NewsMessage news); +} diff --git a/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/streams/news/NewsMarketDataListenerAdapter.java b/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/streams/news/NewsMarketDataListenerAdapter.java new file mode 100644 index 00000000..2278df82 --- /dev/null +++ b/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/streams/news/NewsMarketDataListenerAdapter.java @@ -0,0 +1,12 @@ +package net.jacobpeterson.alpaca.websocket.marketdata.streams.news; + +import net.jacobpeterson.alpaca.model.websocket.marketdata.streams.news.model.news.NewsMessage; + +/** + * {@link NewsMarketDataListenerAdapter} is an adapter for {@link NewsMarketDataListener}. + */ +public class NewsMarketDataListenerAdapter implements NewsMarketDataListener { + + @Override + public void onNews(NewsMessage news) {} +} diff --git a/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/streams/news/NewsMarketDataWebsocket.java b/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/streams/news/NewsMarketDataWebsocket.java index 8e92fa69..495f7932 100644 --- a/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/streams/news/NewsMarketDataWebsocket.java +++ b/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/streams/news/NewsMarketDataWebsocket.java @@ -1,15 +1,85 @@ package net.jacobpeterson.alpaca.websocket.marketdata.streams.news; -import net.jacobpeterson.alpaca.model.endpoint.marketdata.common.realtime.bar.BarMessage; -import net.jacobpeterson.alpaca.model.endpoint.marketdata.common.realtime.quote.QuoteMessage; -import net.jacobpeterson.alpaca.model.endpoint.marketdata.common.realtime.trade.TradeMessage; +import com.google.gson.JsonObject; +import net.jacobpeterson.alpaca.model.websocket.marketdata.streams.news.model.NewsMarketDataMessageType; +import net.jacobpeterson.alpaca.model.websocket.marketdata.streams.news.model.control.NewsSubscriptionsMessage; +import net.jacobpeterson.alpaca.model.websocket.marketdata.streams.news.model.news.NewsMessage; import net.jacobpeterson.alpaca.websocket.marketdata.MarketDataWebsocket; +import okhttp3.HttpUrl; import okhttp3.OkHttpClient; -public class NewsMarketDataWebsocket extends MarketDataWebsocket { - public NewsMarketDataWebsocket(OkHttpClient okHttpClient, - String keyID, String secretKey) { - super(okHttpClient, "v1beta1/news", "News", keyID, secretKey, TradeMessage.class, - QuoteMessage.class, BarMessage.class); +import java.util.Set; + +import static net.jacobpeterson.alpaca.model.websocket.marketdata.streams.news.model.NewsMarketDataMessageType.ERROR; +import static net.jacobpeterson.alpaca.model.websocket.marketdata.streams.news.model.NewsMarketDataMessageType.SUBSCRIPTION; +import static net.jacobpeterson.alpaca.model.websocket.marketdata.streams.news.model.NewsMarketDataMessageType.SUCCESS; +import static net.jacobpeterson.alpaca.openapi.marketdata.JSON.getGson; + +/** + * {@link NewsMarketDataWebsocket} is an implementation for {@link NewsMarketDataWebsocketInterface}. + */ +public class NewsMarketDataWebsocket + extends MarketDataWebsocket + implements NewsMarketDataWebsocketInterface { + + /** + * Instantiates a new {@link NewsMarketDataWebsocket}. + * + * @param okHttpClient the {@link OkHttpClient} + * @param traderKeyID the trader key ID + * @param traderSecretKey the trader secret key + * @param brokerAPIKey the broker API key + * @param brokerAPISecret the broker API secret + */ + public NewsMarketDataWebsocket(OkHttpClient okHttpClient, String traderKeyID, String traderSecretKey, + String brokerAPIKey, String brokerAPISecret) { + super(okHttpClient, new HttpUrl.Builder() + .scheme("https") + .host("stream.data.alpaca.markets") + .addPathSegments("v1beta1/news") + .build(), + "News", traderKeyID, traderSecretKey, brokerAPIKey, brokerAPISecret, + NewsMarketDataMessageType.class, NewsSubscriptionsMessage.class); + } + + @Override + protected boolean isSuccessMessageType(NewsMarketDataMessageType messageType) { + return messageType == SUCCESS; + } + + @Override + protected boolean isErrorMessageType(NewsMarketDataMessageType messageType) { + return messageType == ERROR; + } + + @Override + protected boolean isSubscriptionMessageType(NewsMarketDataMessageType messageType) { + return messageType == SUBSCRIPTION; + } + + @Override + protected void callListenerWithMessage(NewsMarketDataMessageType messageType, JsonObject messageObject) { + if (messageType == NewsMarketDataMessageType.NEWS) { + listener.onNews(getGson().fromJson(messageObject, NewsMessage.class)); + } else { + throw new UnsupportedOperationException(); + } + } + + @Override + public void setListener(NewsMarketDataListener listener) { + this.listener = listener; + } + + @Override + public void setNewsSubscriptions(Set symbols) { + symbols = symbols == null ? Set.of() : symbols; + setSubscriptions(getNewsSubscriptions(), symbols, + set -> new NewsSubscriptionsMessage().withNews(set)); + } + + @Override + public Set getNewsSubscriptions() { + return subscriptionsMessage == null ? Set.of() : subscriptionsMessage.getNews(); } } diff --git a/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/streams/news/NewsMarketDataWebsocketInterface.java b/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/streams/news/NewsMarketDataWebsocketInterface.java new file mode 100644 index 00000000..8a89857d --- /dev/null +++ b/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/streams/news/NewsMarketDataWebsocketInterface.java @@ -0,0 +1,36 @@ +package net.jacobpeterson.alpaca.websocket.marketdata.streams.news; + +import net.jacobpeterson.alpaca.model.websocket.marketdata.streams.news.model.NewsMarketDataMessageType; +import net.jacobpeterson.alpaca.websocket.marketdata.MarketDataWebsocketInterface; + +import java.util.Set; + +/** + * {@link NewsMarketDataWebsocketInterface} is a {@link MarketDataWebsocketInterface} for + * {@link NewsMarketDataWebsocket}. + */ +public interface NewsMarketDataWebsocketInterface extends MarketDataWebsocketInterface { + + /** + * Sets the {@link NewsMarketDataListener}. + * + * @param listener the {@link NewsMarketDataListener} + */ + void setListener(NewsMarketDataListener listener); + + /** + * Subscribes the given symbols to {@link NewsMarketDataMessageType#NEWS}. This will remove all + * previous {@link NewsMarketDataMessageType#NEWS} subscriptions. + * + * @param symbols the {@link Set} of symbols (use an asterisk ("*") as a wildcard to subscribe to all available + * symbols) or null to unsubscribe from all symbols + */ + void setNewsSubscriptions(Set symbols); + + /** + * Gets the current symbols subscribed to {@link NewsMarketDataMessageType#NEWS}. + * + * @return a {@link Set} of {@link String} symbols + */ + Set getNewsSubscriptions(); +} diff --git a/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/streams/news/model/control/news_subscriptions_message.json b/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/streams/news/model/control/news_subscriptions_message.json new file mode 100644 index 00000000..dbd84da7 --- /dev/null +++ b/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/streams/news/model/control/news_subscriptions_message.json @@ -0,0 +1,12 @@ +{ + "type": "object", + "extends": { + "existingJavaType": "net.jacobpeterson.alpaca.model.websocket.marketdata.streams.news.model.NewsMarketDataMessage" + }, + "properties": { + "news": { + "existingJavaType": "java.util.Set", + "title": "The {@link java.util.Set} of symbols subscribed to news." + } + } +} diff --git a/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/streams/news/model/news/news_message.json b/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/streams/news/model/news/news_message.json index 047c0790..638a3a67 100644 --- a/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/streams/news/model/news/news_message.json +++ b/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/streams/news/model/news/news_message.json @@ -26,7 +26,7 @@ "existingJavaType": "java.lang.String" }, "symbols": { - "existingJavaType": "java.util.ArrayList" + "existingJavaType": "java.util.Set" }, "created_at": { "existingJavaType": "java.time.OffsetDateTime" diff --git a/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/streams/stock/StockMarketDataListener.java b/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/streams/stock/StockMarketDataListener.java new file mode 100644 index 00000000..396e6af2 --- /dev/null +++ b/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/streams/stock/StockMarketDataListener.java @@ -0,0 +1,78 @@ +package net.jacobpeterson.alpaca.websocket.marketdata.streams.stock; + +import net.jacobpeterson.alpaca.model.websocket.marketdata.streams.stock.model.bar.StockBarMessage; +import net.jacobpeterson.alpaca.model.websocket.marketdata.streams.stock.model.limituplimitdownband.StockLimitUpLimitDownBandMessage; +import net.jacobpeterson.alpaca.model.websocket.marketdata.streams.stock.model.quote.StockQuoteMessage; +import net.jacobpeterson.alpaca.model.websocket.marketdata.streams.stock.model.trade.StockTradeMessage; +import net.jacobpeterson.alpaca.model.websocket.marketdata.streams.stock.model.tradecancelerror.StockTradeCancelErrorMessage; +import net.jacobpeterson.alpaca.model.websocket.marketdata.streams.stock.model.tradecorrection.StockTradeCorrectionMessage; +import net.jacobpeterson.alpaca.model.websocket.marketdata.streams.stock.model.tradingstatus.StockTradingStatusMessage; + +/** + * {@link StockMarketDataListener} defines a listener interface for {@link StockMarketDataWebsocketInterface} messages. + */ +public interface StockMarketDataListener { + + /** + * Called when a {@link StockTradeMessage} is received. + * + * @param trade the {@link StockTradeMessage} + */ + void onTrade(StockTradeMessage trade); + + /** + * Called when a {@link StockQuoteMessage} is received. + * + * @param quote the {@link StockQuoteMessage} + */ + void onQuote(StockQuoteMessage quote); + + /** + * Called when a {@link StockBarMessage} is received. + * + * @param bar the {@link StockBarMessage} + */ + void onMinuteBar(StockBarMessage bar); + + /** + * Called when a {@link StockBarMessage} is received. + * + * @param bar the {@link StockBarMessage} + */ + void onDailyBar(StockBarMessage bar); + + /** + * Called when a {@link StockBarMessage} is received. + * + * @param bar the {@link StockBarMessage} + */ + void onUpdatedBar(StockBarMessage bar); + + /** + * Called when a {@link StockTradeCorrectionMessage} is received. + * + * @param tradeCorrection the {@link StockTradeCorrectionMessage} + */ + void onTradeCorrection(StockTradeCorrectionMessage tradeCorrection); + + /** + * Called when a {@link StockTradeCancelErrorMessage} is received. + * + * @param tradeCancelError the {@link StockTradeCancelErrorMessage} + */ + void onTradeCancelError(StockTradeCancelErrorMessage tradeCancelError); + + /** + * Called when a {@link StockLimitUpLimitDownBandMessage} is received. + * + * @param limitUpLimitDownBand the {@link StockLimitUpLimitDownBandMessage} + */ + void onLimitUpLimitDownBand(StockLimitUpLimitDownBandMessage limitUpLimitDownBand); + + /** + * Called when a {link StockTradingStatusMessage} is received. + * + * @param tradingStatus the {@link StockTradingStatusMessage} + */ + void onTradingStatus(StockTradingStatusMessage tradingStatus); +} diff --git a/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/streams/stock/StockMarketDataListenerAdapter.java b/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/streams/stock/StockMarketDataListenerAdapter.java new file mode 100644 index 00000000..df99badf --- /dev/null +++ b/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/streams/stock/StockMarketDataListenerAdapter.java @@ -0,0 +1,42 @@ +package net.jacobpeterson.alpaca.websocket.marketdata.streams.stock; + +import net.jacobpeterson.alpaca.model.websocket.marketdata.streams.stock.model.bar.StockBarMessage; +import net.jacobpeterson.alpaca.model.websocket.marketdata.streams.stock.model.limituplimitdownband.StockLimitUpLimitDownBandMessage; +import net.jacobpeterson.alpaca.model.websocket.marketdata.streams.stock.model.quote.StockQuoteMessage; +import net.jacobpeterson.alpaca.model.websocket.marketdata.streams.stock.model.trade.StockTradeMessage; +import net.jacobpeterson.alpaca.model.websocket.marketdata.streams.stock.model.tradecancelerror.StockTradeCancelErrorMessage; +import net.jacobpeterson.alpaca.model.websocket.marketdata.streams.stock.model.tradecorrection.StockTradeCorrectionMessage; +import net.jacobpeterson.alpaca.model.websocket.marketdata.streams.stock.model.tradingstatus.StockTradingStatusMessage; + +/** + * {@link StockMarketDataListenerAdapter} is an adapter for {@link StockMarketDataListener}. + */ +public class StockMarketDataListenerAdapter implements StockMarketDataListener { + + @Override + public void onTrade(StockTradeMessage trade) {} + + @Override + public void onQuote(StockQuoteMessage quote) {} + + @Override + public void onMinuteBar(StockBarMessage bar) {} + + @Override + public void onDailyBar(StockBarMessage bar) {} + + @Override + public void onUpdatedBar(StockBarMessage bar) {} + + @Override + public void onTradeCorrection(StockTradeCorrectionMessage tradeCorrection) {} + + @Override + public void onTradeCancelError(StockTradeCancelErrorMessage tradeCancelError) {} + + @Override + public void onLimitUpLimitDownBand(StockLimitUpLimitDownBandMessage limitUpLimitDownBand) {} + + @Override + public void onTradingStatus(StockTradingStatusMessage tradingStatus) {} +} diff --git a/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/streams/stock/StockMarketDataWebsocket.java b/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/streams/stock/StockMarketDataWebsocket.java index 87bfe6c9..753d0246 100644 --- a/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/streams/stock/StockMarketDataWebsocket.java +++ b/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/streams/stock/StockMarketDataWebsocket.java @@ -1,30 +1,218 @@ package net.jacobpeterson.alpaca.websocket.marketdata.streams.stock; -import net.jacobpeterson.alpaca.model.endpoint.marketdata.stock.realtime.bar.StockBarMessage; -import net.jacobpeterson.alpaca.model.endpoint.marketdata.stock.realtime.quote.StockQuoteMessage; -import net.jacobpeterson.alpaca.model.endpoint.marketdata.stock.realtime.trade.StockTradeMessage; -import net.jacobpeterson.alpaca.model.properties.DataAPIType; +import com.google.gson.JsonObject; +import net.jacobpeterson.alpaca.model.util.apitype.MarketDataWebsocketSourceType; +import net.jacobpeterson.alpaca.model.websocket.marketdata.streams.stock.model.StockMarketDataMessageType; +import net.jacobpeterson.alpaca.model.websocket.marketdata.streams.stock.model.bar.StockBarMessage; +import net.jacobpeterson.alpaca.model.websocket.marketdata.streams.stock.model.control.StockSubscriptionsMessage; +import net.jacobpeterson.alpaca.model.websocket.marketdata.streams.stock.model.limituplimitdownband.StockLimitUpLimitDownBandMessage; +import net.jacobpeterson.alpaca.model.websocket.marketdata.streams.stock.model.quote.StockQuoteMessage; +import net.jacobpeterson.alpaca.model.websocket.marketdata.streams.stock.model.trade.StockTradeMessage; +import net.jacobpeterson.alpaca.model.websocket.marketdata.streams.stock.model.tradecancelerror.StockTradeCancelErrorMessage; +import net.jacobpeterson.alpaca.model.websocket.marketdata.streams.stock.model.tradecorrection.StockTradeCorrectionMessage; +import net.jacobpeterson.alpaca.model.websocket.marketdata.streams.stock.model.tradingstatus.StockTradingStatusMessage; import net.jacobpeterson.alpaca.websocket.marketdata.MarketDataWebsocket; +import okhttp3.HttpUrl; import okhttp3.OkHttpClient; +import java.util.Set; + +import static net.jacobpeterson.alpaca.model.websocket.marketdata.streams.stock.model.StockMarketDataMessageType.ERROR; +import static net.jacobpeterson.alpaca.model.websocket.marketdata.streams.stock.model.StockMarketDataMessageType.SUBSCRIPTION; +import static net.jacobpeterson.alpaca.model.websocket.marketdata.streams.stock.model.StockMarketDataMessageType.SUCCESS; +import static net.jacobpeterson.alpaca.openapi.marketdata.JSON.getGson; + /** - * {@link StockMarketDataWebsocket} is a {@link MarketDataWebsocket} for - * Realtime - * Stock Market Data + * {@link StockMarketDataWebsocket} is an implementation for {@link StockMarketDataWebsocketInterface}. */ -public class StockMarketDataWebsocket extends MarketDataWebsocket { +public class StockMarketDataWebsocket + extends MarketDataWebsocket + implements StockMarketDataWebsocketInterface { + + private static HttpUrl createWebsocketURL(boolean isSandbox, + MarketDataWebsocketSourceType marketDataWebsocketSourceType) { + return new HttpUrl.Builder() + .scheme("https") + .host(isSandbox ? "stream.data.sandbox.alpaca.markets" : "stream.data.alpaca.markets") + .addPathSegment("v2") + .addPathSegment(marketDataWebsocketSourceType.toString()) + .build(); + } /** * Instantiates a new {@link StockMarketDataWebsocket}. * - * @param okHttpClient the {@link OkHttpClient} - * @param dataAPIType the {@link DataAPIType} - * @param keyID the key ID - * @param secretKey the secret key + * @param okHttpClient the {@link OkHttpClient} + * @param traderKeyID the trader key ID + * @param traderSecretKey the trader secret key + * @param brokerAPIKey the broker API key + * @param brokerAPISecret the broker API secret + * @param marketDataWebsocketSourceType the {@link MarketDataWebsocketSourceType} */ - public StockMarketDataWebsocket(OkHttpClient okHttpClient, DataAPIType dataAPIType, - String keyID, String secretKey) { - super(okHttpClient, "v2/" + dataAPIType.toString(), "Stock", keyID, secretKey, StockTradeMessage.class, - StockQuoteMessage.class, StockBarMessage.class); + public StockMarketDataWebsocket(OkHttpClient okHttpClient, String traderKeyID, String traderSecretKey, + String brokerAPIKey, String brokerAPISecret, MarketDataWebsocketSourceType marketDataWebsocketSourceType) { + super(okHttpClient, + createWebsocketURL(brokerAPIKey != null && brokerAPISecret != null, marketDataWebsocketSourceType), + "Stock", traderKeyID, traderSecretKey, brokerAPIKey, brokerAPISecret, + StockMarketDataMessageType.class, StockSubscriptionsMessage.class); + } + + @Override + protected boolean isSuccessMessageType(StockMarketDataMessageType messageType) { + return messageType == SUCCESS; + } + + @Override + protected boolean isErrorMessageType(StockMarketDataMessageType messageType) { + return messageType == ERROR; + } + + @Override + protected boolean isSubscriptionMessageType(StockMarketDataMessageType messageType) { + return messageType == SUBSCRIPTION; + } + + @Override + protected void callListenerWithMessage(StockMarketDataMessageType messageType, JsonObject messageObject) { + switch (messageType) { + case TRADES: + listener.onTrade(getGson().fromJson(messageObject, + StockTradeMessage.class)); + break; + case QUOTES: + listener.onQuote(getGson().fromJson(messageObject, + StockQuoteMessage.class)); + break; + case MINUTE_BARS: + listener.onMinuteBar(getGson().fromJson(messageObject, + StockBarMessage.class)); + break; + case DAILY_BARS: + listener.onDailyBar(getGson().fromJson(messageObject, + StockBarMessage.class)); + break; + case UPDATED_BARS: + listener.onUpdatedBar(getGson().fromJson(messageObject, + StockBarMessage.class)); + break; + case TRADE_CORRECTIONS: + listener.onTradeCorrection(getGson().fromJson(messageObject, + StockTradeCorrectionMessage.class)); + break; + case TRADE_CANCEL_ERRORS: + listener.onTradeCancelError(getGson().fromJson(messageObject, + StockTradeCancelErrorMessage.class)); + break; + case LIMIT_UP_LIMIT_DOWN_BANDS: + listener.onLimitUpLimitDownBand(getGson().fromJson(messageObject, + StockLimitUpLimitDownBandMessage.class)); + break; + case TRADING_STATUSES: + listener.onTradingStatus(getGson().fromJson(messageObject, + StockTradingStatusMessage.class)); + break; + default: + throw new UnsupportedOperationException(); + } + } + + @Override + public void setListener(StockMarketDataListener listener) { + this.listener = listener; + } + + @Override + public void setTradeSubscriptions(Set symbols) { + symbols = symbols == null ? Set.of() : symbols; + setSubscriptions(getTradeSubscriptions(), symbols, + set -> newEmptyStockSubscriptionsMessage().withTrades(set)); + } + + @Override + public Set getTradeSubscriptions() { + return subscriptionsMessage == null ? Set.of() : subscriptionsMessage.getTrades(); + } + + @Override + public void setQuoteSubscriptions(Set symbols) { + symbols = symbols == null ? Set.of() : symbols; + setSubscriptions(getQuoteSubscriptions(), symbols, + set -> newEmptyStockSubscriptionsMessage().withQuotes(set)); + } + + @Override + public Set getQuoteSubscriptions() { + return subscriptionsMessage == null ? Set.of() : subscriptionsMessage.getQuotes(); + } + + @Override + public void setMinuteBarSubscriptions(Set symbols) { + symbols = symbols == null ? Set.of() : symbols; + setSubscriptions(getMinuteBarSubscriptions(), symbols, + set -> newEmptyStockSubscriptionsMessage().withMinuteBars(set)); + } + + @Override + public Set getMinuteBarSubscriptions() { + return subscriptionsMessage == null ? Set.of() : subscriptionsMessage.getMinuteBars(); + } + + @Override + public void setDailyBarSubscriptions(Set symbols) { + symbols = symbols == null ? Set.of() : symbols; + setSubscriptions(getDailyBarSubscriptions(), symbols, + set -> newEmptyStockSubscriptionsMessage().withDailyBars(set)); + } + + @Override + public Set getDailyBarSubscriptions() { + return subscriptionsMessage == null ? Set.of() : subscriptionsMessage.getDailyBars(); + } + + @Override + public void setUpdatedBarSubscriptions(Set symbols) { + symbols = symbols == null ? Set.of() : symbols; + setSubscriptions(getUpdatedBarSubscriptions(), symbols, + set -> newEmptyStockSubscriptionsMessage().withUpdatedBars(set)); + } + + @Override + public Set getUpdatedBarSubscriptions() { + return subscriptionsMessage == null ? Set.of() : subscriptionsMessage.getUpdatedBars(); + } + + @Override + public void setLimitUpLimitDownBandSubscriptions(Set symbols) { + symbols = symbols == null ? Set.of() : symbols; + setSubscriptions(getLimitUpLimitDownBandSubscriptions(), symbols, + set -> newEmptyStockSubscriptionsMessage().withLimitUpLimitDownBands(set)); + } + + @Override + public Set getLimitUpLimitDownBandSubscriptions() { + return subscriptionsMessage == null ? Set.of() : subscriptionsMessage.getLimitUpLimitDownBands(); + } + + @Override + public void setTradingStatuseSubscriptions(Set symbols) { + symbols = symbols == null ? Set.of() : symbols; + setSubscriptions(getTradingStatuseSubscriptions(), symbols, + set -> newEmptyStockSubscriptionsMessage().withTradingStatuses(set)); + } + + @Override + public Set getTradingStatuseSubscriptions() { + return subscriptionsMessage == null ? Set.of() : subscriptionsMessage.getTradingStatuses(); + } + + private StockSubscriptionsMessage newEmptyStockSubscriptionsMessage() { + return new StockSubscriptionsMessage() + .withTrades(null) + .withQuotes(null) + .withMinuteBars(null) + .withDailyBars(null) + .withUpdatedBars(null) + .withLimitUpLimitDownBands(null) + .withTradingStatuses(null); } } diff --git a/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/streams/stock/StockMarketDataWebsocketInterface.java b/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/streams/stock/StockMarketDataWebsocketInterface.java new file mode 100644 index 00000000..32fc3b8d --- /dev/null +++ b/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/streams/stock/StockMarketDataWebsocketInterface.java @@ -0,0 +1,136 @@ +package net.jacobpeterson.alpaca.websocket.marketdata.streams.stock; + +import net.jacobpeterson.alpaca.model.websocket.marketdata.streams.stock.model.StockMarketDataMessageType; +import net.jacobpeterson.alpaca.websocket.marketdata.MarketDataWebsocketInterface; + +import java.util.Set; + +/** + * {@link StockMarketDataWebsocketInterface} is a {@link MarketDataWebsocketInterface} for + * {@link StockMarketDataWebsocket}. + */ +public interface StockMarketDataWebsocketInterface extends MarketDataWebsocketInterface { + + /** + * Sets the {@link StockMarketDataListener}. + * + * @param listener the {@link StockMarketDataListener} + */ + void setListener(StockMarketDataListener listener); + + /** + * Subscribes the given symbols to {@link StockMarketDataMessageType#TRADES}. This will remove all + * previous {@link StockMarketDataMessageType#TRADES} subscriptions. + *
+ * Note that this will automatically apply the same symbols subscription list to the + * {@link StockMarketDataMessageType#TRADE_CORRECTIONS} and {@link StockMarketDataMessageType#TRADE_CANCEL_ERRORS} + * types. + * + * @param symbols the {@link Set} of symbols (use an asterisk ("*") as a wildcard to subscribe to all available + * symbols) or null to unsubscribe from all symbols + */ + void setTradeSubscriptions(Set symbols); + + /** + * Gets the current symbols subscribed to {@link StockMarketDataMessageType#TRADES}. + * + * @return a {@link Set} of {@link String} symbols + */ + Set getTradeSubscriptions(); + + /** + * Subscribes the given symbols to {@link StockMarketDataMessageType#QUOTES}. This will remove all + * previous {@link StockMarketDataMessageType#QUOTES} subscriptions. + * + * @param symbols the {@link Set} of symbols (use an asterisk ("*") as a wildcard to subscribe to all available + * symbols) or null to unsubscribe from all symbols + */ + void setQuoteSubscriptions(Set symbols); + + /** + * Gets the current symbols subscribed to {@link StockMarketDataMessageType#QUOTES}. + * + * @return a {@link Set} of {@link String} symbols + */ + Set getQuoteSubscriptions(); + + /** + * Subscribes the given symbols to {@link StockMarketDataMessageType#MINUTE_BARS}. This will remove all + * previous {@link StockMarketDataMessageType#MINUTE_BARS} subscriptions. + * + * @param symbols the {@link Set} of symbols (use an asterisk ("*") as a wildcard to subscribe to all available + * symbols) or null to unsubscribe from all symbols + */ + void setMinuteBarSubscriptions(Set symbols); + + /** + * Gets the current symbols subscribed to {@link StockMarketDataMessageType#MINUTE_BARS}. + * + * @return a {@link Set} of {@link String} symbols + */ + Set getMinuteBarSubscriptions(); + + /** + * Subscribes the given symbols to {@link StockMarketDataMessageType#DAILY_BARS}. This will remove all + * previous {@link StockMarketDataMessageType#DAILY_BARS} subscriptions. + * + * @param symbols the {@link Set} of symbols (use an asterisk ("*") as a wildcard to subscribe to all available + * symbols) or null to unsubscribe from all symbols + */ + void setDailyBarSubscriptions(Set symbols); + + /** + * Gets the current symbols subscribed to {@link StockMarketDataMessageType#DAILY_BARS}. + * + * @return a {@link Set} of {@link String} symbols + */ + Set getDailyBarSubscriptions(); + + /** + * Subscribes the given symbols to {@link StockMarketDataMessageType#UPDATED_BARS}. This will remove + * all previous {@link StockMarketDataMessageType#UPDATED_BARS} subscriptions. + * + * @param symbols the {@link Set} of symbols (use an asterisk ("*") as a wildcard to subscribe to all available + * symbols) or null to unsubscribe from all symbols + */ + void setUpdatedBarSubscriptions(Set symbols); + + /** + * Gets the current symbols subscribed to {@link StockMarketDataMessageType#UPDATED_BARS}. + * + * @return a {@link Set} of {@link String} symbols + */ + Set getUpdatedBarSubscriptions(); + + /** + * Subscribes the given symbols to {@link StockMarketDataMessageType#LIMIT_UP_LIMIT_DOWN_BANDS}. This + * will remove all previous {@link StockMarketDataMessageType#LIMIT_UP_LIMIT_DOWN_BANDS} subscriptions. + * + * @param symbols the {@link Set} of symbols (use an asterisk ("*") as a wildcard to subscribe to all available + * symbols) or null to unsubscribe from all symbols + */ + void setLimitUpLimitDownBandSubscriptions(Set symbols); + + /** + * Gets the current symbols subscribed to {@link StockMarketDataMessageType#LIMIT_UP_LIMIT_DOWN_BANDS}. + * + * @return a {@link Set} of {@link String} symbols + */ + Set getLimitUpLimitDownBandSubscriptions(); + + /** + * Subscribes the given symbols to {@link StockMarketDataMessageType#TRADING_STATUSES}. This will + * remove all previous {@link StockMarketDataMessageType#TRADING_STATUSES} subscriptions. + * + * @param symbols the {@link Set} of symbols (use an asterisk ("*") as a wildcard to subscribe to all available + * symbols) or null to unsubscribe from all symbols + */ + void setTradingStatuseSubscriptions(Set symbols); + + /** + * Gets the current symbols subscribed to {@link StockMarketDataMessageType#TRADING_STATUSES}. + * + * @return a {@link Set} of {@link String} symbols + */ + Set getTradingStatuseSubscriptions(); +} diff --git a/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/streams/stock/model/control/stock_subscriptions_message.json b/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/streams/stock/model/control/stock_subscriptions_message.json new file mode 100644 index 00000000..05a1d449 --- /dev/null +++ b/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/streams/stock/model/control/stock_subscriptions_message.json @@ -0,0 +1,39 @@ +{ + "type": "object", + "extends": { + "existingJavaType": "net.jacobpeterson.alpaca.model.websocket.marketdata.streams.stock.model.StockMarketDataMessage" + }, + "properties": { + "trades": { + "existingJavaType": "java.util.Set", + "title": "The {@link java.util.Set} of symbols subscribed to trades." + }, + "quotes": { + "existingJavaType": "java.util.Set", + "title": "The {@link java.util.Set} of symbols subscribed to quotes." + }, + "bars": { + "existingJavaType": "java.util.Set", + "javaName": "minuteBars", + "title": "The {@link java.util.Set} of symbols subscribed to minute bars." + }, + "dailyBars": { + "existingJavaType": "java.util.Set", + "title": "The {@link java.util.Set} of symbols subscribed to daily bars." + }, + "updatedBars": { + "existingJavaType": "java.util.Set", + "title": "The {@link java.util.Set} of symbols subscribed to updated bars." + }, + "lulds": { + "existingJavaType": "java.util.Set", + "javaName": "limitUpLimitDownBands", + "title": "The {@link java.util.Set} of symbols subscribed to limit up - limit down bands." + }, + "statuses": { + "existingJavaType": "java.util.Set", + "javaName": "tradingStatuses", + "title": "The {@link java.util.Set} of symbols subscribed to trading statuses." + } + } +} diff --git a/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/streams/stock/model/quote/stock_quote_message.json b/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/streams/stock/model/quote/stock_quote_message.json index 27ff6daa..b0e2d72f 100644 --- a/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/streams/stock/model/quote/stock_quote_message.json +++ b/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/streams/stock/model/quote/stock_quote_message.json @@ -40,9 +40,9 @@ "title": "The bid size." }, "c": { - "existingJavaType": "java.util.ArrayList", + "existingJavaType": "java.util.Set", "javaName": "conditions", - "title": "The {@link java.util.ArrayList} of quote conditions." + "title": "The {@link java.util.Set} of quote conditions." }, "t": { "existingJavaType": "java.time.OffsetDateTime", diff --git a/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/streams/stock/model/subscriptions/stock_subscriptions_message.json b/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/streams/stock/model/subscriptions/stock_subscriptions_message.json deleted file mode 100644 index 986947f6..00000000 --- a/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/streams/stock/model/subscriptions/stock_subscriptions_message.json +++ /dev/null @@ -1,49 +0,0 @@ -{ - "type": "object", - "extends": { - "existingJavaType": "net.jacobpeterson.alpaca.model.websocket.marketdata.streams.stock.model.StockMarketDataMessage" - }, - "properties": { - "trades": { - "existingJavaType": "java.util.ArrayList", - "title": "The {@link java.util.ArrayList} of symbols subscribed to trades." - }, - "quotes": { - "existingJavaType": "java.util.ArrayList", - "title": "The {@link java.util.ArrayList} of symbols subscribed to quotes." - }, - "bars": { - "existingJavaType": "java.util.ArrayList", - "javaName": "minuteBars", - "title": "The {@link java.util.ArrayList} of symbols subscribed to minute bars." - }, - "dailyBars": { - "existingJavaType": "java.util.ArrayList", - "title": "The {@link java.util.ArrayList} of symbols subscribed to daily bars." - }, - "updatedBars": { - "existingJavaType": "java.util.ArrayList", - "title": "The {@link java.util.ArrayList} of symbols subscribed to updated bars." - }, - "corrections": { - "existingJavaType": "java.util.ArrayList", - "javaName": "tradeCorrections", - "title": "The {@link java.util.ArrayList} of symbols subscribed to trade corrections." - }, - "cancelErrors": { - "existingJavaType": "java.util.ArrayList", - "javaName": "tradeCancelErrors", - "title": "The {@link java.util.ArrayList} of symbols subscribed to trade cancels/errors." - }, - "lulds": { - "existingJavaType": "java.util.ArrayList", - "javaName": "limitUpLimitDownBands", - "title": "The {@link java.util.ArrayList} of symbols subscribed to limit up - limit down bands." - }, - "statuses": { - "existingJavaType": "java.util.ArrayList", - "javaName": "tradingStatuses", - "title": "The {@link java.util.ArrayList} of symbols subscribed to trading statuses." - } - } -} diff --git a/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/streams/stock/model/trade/stock_trade_message.json b/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/streams/stock/model/trade/stock_trade_message.json index 4294856a..239a8164 100644 --- a/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/streams/stock/model/trade/stock_trade_message.json +++ b/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/streams/stock/model/trade/stock_trade_message.json @@ -30,9 +30,9 @@ "title": "The trade size." }, "c": { - "existingJavaType": "java.util.ArrayList", + "existingJavaType": "java.util.Set", "javaName": "conditions", - "title": "The {@link java.util.ArrayList} of trade conditions." + "title": "The {@link java.util.Set} of trade conditions." }, "t": { "existingJavaType": "java.time.OffsetDateTime", diff --git a/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/streams/stock/model/tradecorrection/stock_trade_correction_message.json b/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/streams/stock/model/tradecorrection/stock_trade_correction_message.json index 0b6daaca..acb6a6ab 100644 --- a/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/streams/stock/model/tradecorrection/stock_trade_correction_message.json +++ b/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/streams/stock/model/tradecorrection/stock_trade_correction_message.json @@ -30,9 +30,9 @@ "title": "The original trade size." }, "oc": { - "existingJavaType": "java.util.ArrayList", + "existingJavaType": "java.util.Set", "javaName": "originalConditions", - "title": "The {@link java.util.ArrayList} of original trade conditions." + "title": "The {@link java.util.Set} of original trade conditions." }, "ci": { "existingJavaType": "java.lang.Integer", @@ -50,9 +50,9 @@ "title": "The corrected trade size." }, "cc": { - "existingJavaType": "java.util.ArrayList", + "existingJavaType": "java.util.Set", "javaName": "correctedConditions", - "title": "The {@link java.util.ArrayList} of original trade conditions." + "title": "The {@link java.util.Set} of original trade conditions." }, "t": { "existingJavaType": "java.time.OffsetDateTime", diff --git a/src/main/java/net/jacobpeterson/alpaca/websocket/trades/TradesListener.java b/src/main/java/net/jacobpeterson/alpaca/websocket/trades/TradesListener.java deleted file mode 100644 index c96baee5..00000000 --- a/src/main/java/net/jacobpeterson/alpaca/websocket/trades/TradesListener.java +++ /dev/null @@ -1,11 +0,0 @@ -package net.jacobpeterson.alpaca.websocket.trades; - -import net.jacobpeterson.alpaca.model.websocket.trades.model.TradesStreamMessage; -import net.jacobpeterson.alpaca.model.websocket.trades.model.TradesStreamMessageType; -import net.jacobpeterson.alpaca.websocket.AlpacaWebsocketMessageListener; - -/** - * {@link TradesListener} defines a listener interface for {@link TradesWebsocket} messages. - */ -public interface TradesListener extends AlpacaWebsocketMessageListener { -} diff --git a/src/main/java/net/jacobpeterson/alpaca/websocket/trades/TradesListenerAdapter.java b/src/main/java/net/jacobpeterson/alpaca/websocket/trades/TradesListenerAdapter.java deleted file mode 100644 index a6dd93ec..00000000 --- a/src/main/java/net/jacobpeterson/alpaca/websocket/trades/TradesListenerAdapter.java +++ /dev/null @@ -1,19 +0,0 @@ -package net.jacobpeterson.alpaca.websocket.trades; - -import net.jacobpeterson.alpaca.model.websocket.trades.model.TradesStreamMessage; -import net.jacobpeterson.alpaca.model.websocket.trades.model.TradesStreamMessageType; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -/** - * {@link TradesListenerAdapter} is an adapter class for {@link TradesListener}. - */ -public class TradesListenerAdapter implements TradesListener { - - private static final Logger LOGGER = LoggerFactory.getLogger(TradesListenerAdapter.class); - - @Override - public void onMessage(TradesStreamMessageType messageType, TradesStreamMessage message) { - LOGGER.info("{} message received: {}", messageType, message); - } -} diff --git a/src/main/java/net/jacobpeterson/alpaca/websocket/trades/TradesWebsocket.java b/src/main/java/net/jacobpeterson/alpaca/websocket/trades/TradesWebsocket.java deleted file mode 100644 index 5759d355..00000000 --- a/src/main/java/net/jacobpeterson/alpaca/websocket/trades/TradesWebsocket.java +++ /dev/null @@ -1,217 +0,0 @@ -package net.jacobpeterson.alpaca.websocket.trades; - -import com.google.gson.JsonArray; -import com.google.gson.JsonElement; -import com.google.gson.JsonObject; -import com.google.gson.JsonPrimitive; -import net.jacobpeterson.alpaca.model.util.apitype.TraderAPIEndpointType; -import net.jacobpeterson.alpaca.model.websocket.trades.model.TradesStreamMessage; -import net.jacobpeterson.alpaca.model.websocket.trades.model.TradesStreamMessageType; -import net.jacobpeterson.alpaca.model.websocket.trades.model.authorization.AuthorizationData; -import net.jacobpeterson.alpaca.model.websocket.trades.model.authorization.AuthorizationMessage; -import net.jacobpeterson.alpaca.model.websocket.trades.model.listening.ListeningMessage; -import net.jacobpeterson.alpaca.model.websocket.trades.model.tradeupdate.TradeUpdateMessage; -import net.jacobpeterson.alpaca.websocket.AlpacaWebsocket; -import okhttp3.HttpUrl; -import okhttp3.OkHttpClient; -import okhttp3.WebSocket; -import okio.ByteString; -import org.jetbrains.annotations.NotNull; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.util.Arrays; -import java.util.Collection; -import java.util.HashSet; -import java.util.List; -import java.util.Set; - -import static com.google.common.base.Preconditions.checkNotNull; -import static com.google.common.base.Preconditions.checkState; -import static com.google.common.base.Predicates.not; -import static com.google.common.collect.Iterables.toArray; -import static com.google.gson.JsonParser.parseString; -import static java.util.Collections.singletonList; -import static java.util.concurrent.TimeUnit.SECONDS; -import static net.jacobpeterson.alpaca.model.websocket.trades.model.TradesStreamMessageType.TRADE_UPDATES; -import static net.jacobpeterson.alpaca.openapi.trader.JSON.getGson; - -/** - * {@link TradesWebsocket} is an {@link AlpacaWebsocket} implementation and provides the - * {@link TradesWebsocketInterface} interface for - * Trades Streaming. - */ -public class TradesWebsocket extends AlpacaWebsocket - implements TradesWebsocketInterface { - - private static final Logger LOGGER = LoggerFactory.getLogger(TradesWebsocket.class); - private static final String STREAM_ELEMENT_KEY = "stream"; - private static final List SUBSCRIBABLE_STREAMING_MESSAGE_TYPES = - singletonList(TRADE_UPDATES); - - /** - * Creates a {@link HttpUrl} for {@link TradesWebsocket}. - * - * @param traderAPIEndpointType the {@link TraderAPIEndpointType} - * - * @return a {@link HttpUrl} - */ - @SuppressWarnings("UnnecessaryDefault") - private static HttpUrl createWebsocketURL(TraderAPIEndpointType traderAPIEndpointType) { - return new HttpUrl.Builder() - .scheme("https") - .host((switch (traderAPIEndpointType) { - case LIVE -> "api"; - case PAPER -> "paper-api"; - default -> throw new UnsupportedOperationException(); - }) + ".alpaca.markets") - .addPathSegment("stream") - .build(); - } - - private final Set listenedMessageTypes; - - /** - * Instantiates a new {@link TradesWebsocket}. - * - * @param okHttpClient the {@link OkHttpClient} - * @param traderAPIEndpointType the {@link TraderAPIEndpointType} - * @param keyID the key ID - * @param secretKey the secret key - * @param oAuthToken the OAuth token - */ - public TradesWebsocket(OkHttpClient okHttpClient, TraderAPIEndpointType traderAPIEndpointType, - String keyID, String secretKey, String oAuthToken) { - super(okHttpClient, createWebsocketURL(traderAPIEndpointType), "Trades Stream", keyID, secretKey, oAuthToken); - listenedMessageTypes = new HashSet<>(); - } - - @Override - protected void cleanupState() { - super.cleanupState(); - listenedMessageTypes.clear(); - } - - @Override - protected void onConnection() { - sendAuthenticationMessage(); - } - - @Override - protected void onReconnection() { - sendAuthenticationMessage(); - if (waitForAuthorization(5, SECONDS)) { - subscribe(toArray(listenedMessageTypes, TradesStreamMessageType.class)); - } - } - - @Override - protected void sendAuthenticationMessage() { - // Ensure that the authorization Future exists - getAuthorizationFuture(); - - final JsonObject authObject = new JsonObject(); - authObject.addProperty("action", "authenticate"); - final JsonObject authData = new JsonObject(); - if (useOAuth) { - authData.addProperty("oauth_token", oAuthToken); - } else { - authData.addProperty("key_id", keyID); - authData.addProperty("secret_key", secretKey); - } - authObject.add("data", authData); - - LOGGER.info("{} websocket sending authentication message...", websocketName); - websocket.send(authObject.toString()); - } - - @Override - public void onMessage(@NotNull WebSocket webSocket, @NotNull ByteString byteString) { // Binary framing - final String messageString = byteString.utf8(); - LOGGER.trace("Websocket message received: {}", messageString); - - // Parse JSON string and identify 'messageType' - final JsonElement messageElement = parseString(messageString); - checkState(messageElement instanceof JsonObject, "Message must be a JsonObject! Received: %s", messageElement); - final JsonObject messageObject = messageElement.getAsJsonObject(); - final JsonElement streamElement = messageObject.get(STREAM_ELEMENT_KEY); - checkState(streamElement instanceof JsonPrimitive, - "Message must contain %s element! Received: %s", STREAM_ELEMENT_KEY, messageElement); - final TradesStreamMessageType messageType = getGson().fromJson(streamElement, TradesStreamMessageType.class); - checkNotNull(messageType, "TradesStreamMessageType not found in message: %s", messageObject); - - // Deserialize message based on 'messageType' - TradesStreamMessage message; - switch (messageType) { - case AUTHORIZATION: - message = getGson().fromJson(messageObject, AuthorizationMessage.class); - final AuthorizationData authorizationData = ((AuthorizationMessage) message).getData(); - authenticated = authorizationData.getAction().equalsIgnoreCase("authenticate") && - authorizationData.getStatus().equalsIgnoreCase("authorized"); - if (!authenticated) { - throw new RuntimeException(websocketName + " websocket authentication failed!"); - } else { - LOGGER.info("{} websocket authenticated.", websocketName); - } - if (authenticationMessageFuture != null) { - authenticationMessageFuture.complete(authenticated); - } - break; - case LISTENING: - message = getGson().fromJson(messageObject, ListeningMessage.class); - // Remove all 'TradeStreamMessageType's that are no longer listened to and add new ones - final List currentTypes = ((ListeningMessage) message).getData().getStreams(); - currentTypes.stream() - .filter(not(listenedMessageTypes::contains)) - .forEach(listenedMessageTypes::remove); - listenedMessageTypes.addAll(currentTypes); - break; - case TRADE_UPDATES: - message = getGson().fromJson(messageObject, TradeUpdateMessage.class); - break; - default: - throw new UnsupportedOperationException(); - } - - // Call listener - if (listenedMessageTypes.contains(messageType)) { - callListener(messageType, message); - } - } - - @Override - public void subscribe(TradesStreamMessageType... messageTypes) { - if (messageTypes == null || messageTypes.length == 0) { - return; - } - - // Add all non-subscribable 'messageTypes' before connecting or sending websocket message - Arrays.stream(messageTypes) - .filter(not(SUBSCRIBABLE_STREAMING_MESSAGE_TYPES::contains)) - .forEach(listenedMessageTypes::add); - - // Create streams subscription JSON message - final JsonObject requestObject = new JsonObject(); - requestObject.addProperty("action", "listen"); - final JsonArray streamsArray = new JsonArray(); - Arrays.stream(messageTypes) - .filter(SUBSCRIBABLE_STREAMING_MESSAGE_TYPES::contains) - .forEach((type) -> streamsArray.add(type.toString())); - if (streamsArray.isEmpty()) { - return; - } else if (!isConnected()) { - throw new IllegalStateException("This websocket must be connected before subscribing to streams!"); - } - final JsonObject dataObject = new JsonObject(); - dataObject.add("streams", streamsArray); - requestObject.add("data", dataObject); - - websocket.send(requestObject.toString()); - LOGGER.info("Requested streams: {}.", streamsArray); - } - - @Override - public Collection subscriptions() { - return new HashSet<>(listenedMessageTypes); - } -} diff --git a/src/main/java/net/jacobpeterson/alpaca/websocket/trades/TradesWebsocketInterface.java b/src/main/java/net/jacobpeterson/alpaca/websocket/trades/TradesWebsocketInterface.java deleted file mode 100644 index b866e37e..00000000 --- a/src/main/java/net/jacobpeterson/alpaca/websocket/trades/TradesWebsocketInterface.java +++ /dev/null @@ -1,26 +0,0 @@ -package net.jacobpeterson.alpaca.websocket.trades; - -import net.jacobpeterson.alpaca.model.websocket.trades.model.TradesStreamMessageType; -import net.jacobpeterson.alpaca.websocket.AlpacaWebsocketInterface; - -import java.util.Collection; - -/** - * {@link TradesWebsocketInterface} is an {@link AlpacaWebsocketInterface} for a {@link TradesWebsocket}. - */ -public interface TradesWebsocketInterface extends AlpacaWebsocketInterface { - - /** - * Sets the {@link TradesStreamMessageType}s for this stream. - * - * @param messageTypes the {@link TradesStreamMessageType}s - */ - void subscribe(TradesStreamMessageType... messageTypes); - - /** - * Gets all the currently subscribed {@link TradesStreamMessageType}s. - * - * @return a {@link Collection} of {@link TradesStreamMessageType} - */ - Collection subscriptions(); -} diff --git a/src/main/java/net/jacobpeterson/alpaca/websocket/trades/model/listening/listening_data.json b/src/main/java/net/jacobpeterson/alpaca/websocket/trades/model/listening/listening_data.json deleted file mode 100644 index 78a68ba5..00000000 --- a/src/main/java/net/jacobpeterson/alpaca/websocket/trades/model/listening/listening_data.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "type": "object", - "properties": { - "streams": { - "existingJavaType": "java.util.ArrayList", - "title": "The {@link java.util.ArrayList} of {@link net.jacobpeterson.alpaca.model.websocket.trades.model.TradesStreamMessageType}s." - } - } -} diff --git a/src/main/java/net/jacobpeterson/alpaca/websocket/updates/UpdatesListener.java b/src/main/java/net/jacobpeterson/alpaca/websocket/updates/UpdatesListener.java new file mode 100644 index 00000000..3a752308 --- /dev/null +++ b/src/main/java/net/jacobpeterson/alpaca/websocket/updates/UpdatesListener.java @@ -0,0 +1,17 @@ +package net.jacobpeterson.alpaca.websocket.updates; + +import net.jacobpeterson.alpaca.model.websocket.updates.model.tradeupdate.TradeUpdateMessage; + +/** + * {@link UpdatesListener} defines a listener interface for {@link UpdatesWebsocketInterface} messages. + */ +@FunctionalInterface +public interface UpdatesListener { + + /** + * Called when a {@link TradeUpdateMessage} is received. + * + * @param tradeUpdate the {@link TradeUpdateMessage} + */ + void onTradeUpdate(TradeUpdateMessage tradeUpdate); +} diff --git a/src/main/java/net/jacobpeterson/alpaca/websocket/updates/UpdatesWebsocket.java b/src/main/java/net/jacobpeterson/alpaca/websocket/updates/UpdatesWebsocket.java new file mode 100644 index 00000000..99e1db42 --- /dev/null +++ b/src/main/java/net/jacobpeterson/alpaca/websocket/updates/UpdatesWebsocket.java @@ -0,0 +1,172 @@ +package net.jacobpeterson.alpaca.websocket.updates; + +import com.google.gson.JsonArray; +import com.google.gson.JsonObject; +import net.jacobpeterson.alpaca.model.util.apitype.TraderAPIEndpointType; +import net.jacobpeterson.alpaca.model.websocket.updates.model.UpdatesMessageType; +import net.jacobpeterson.alpaca.model.websocket.updates.model.authorization.AuthorizationMessage; +import net.jacobpeterson.alpaca.model.websocket.updates.model.tradeupdate.TradeUpdateMessage; +import net.jacobpeterson.alpaca.websocket.AlpacaWebsocket; +import okhttp3.HttpUrl; +import okhttp3.OkHttpClient; +import okhttp3.WebSocket; +import okio.ByteString; +import org.jetbrains.annotations.NotNull; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import static com.google.gson.JsonParser.parseString; +import static java.util.concurrent.TimeUnit.SECONDS; +import static net.jacobpeterson.alpaca.model.websocket.updates.model.UpdatesMessageType.TRADE_UPDATES; +import static net.jacobpeterson.alpaca.openapi.trader.JSON.getGson; + +/** + * {@link UpdatesWebsocket} is an {@link AlpacaWebsocket} implementation and provides the + * {@link UpdatesWebsocketInterface} interface for + * Updates Streaming. + */ +public class UpdatesWebsocket extends AlpacaWebsocket implements UpdatesWebsocketInterface { + + private static final Logger LOGGER = LoggerFactory.getLogger(UpdatesWebsocket.class); + + @SuppressWarnings("UnnecessaryDefault") + private static HttpUrl createWebsocketURL(TraderAPIEndpointType traderAPIEndpointType) { + return new HttpUrl.Builder() + .scheme("https") + .host((switch (traderAPIEndpointType) { + case LIVE -> "api"; + case PAPER -> "paper-api"; + default -> throw new UnsupportedOperationException(); + }) + ".alpaca.markets") + .addPathSegment("stream") + .build(); + } + + protected final String keyID; + protected final String secretKey; + protected final String oAuthToken; + protected UpdatesListener listener; + protected boolean listenToTradeUpdates; + + /** + * Instantiates a new {@link UpdatesWebsocket}. + * + * @param okHttpClient the {@link OkHttpClient} + * @param traderAPIEndpointType the {@link TraderAPIEndpointType} + * @param keyID the key ID + * @param secretKey the secret key + * @param oAuthToken the OAuth token + */ + public UpdatesWebsocket(OkHttpClient okHttpClient, TraderAPIEndpointType traderAPIEndpointType, + String keyID, String secretKey, String oAuthToken) { + super(okHttpClient, createWebsocketURL(traderAPIEndpointType), "Trades Stream"); + this.keyID = keyID; + this.secretKey = secretKey; + this.oAuthToken = oAuthToken; + } + + @Override + protected void cleanupState() { + super.cleanupState(); + } + + @Override + protected void onConnection() { + sendAuthenticationMessage(); + } + + @Override + protected void onReconnection() { + sendAuthenticationMessage(); + if (waitForAuthorization(5, SECONDS) && listenToTradeUpdates) { + sendTradeUpdatesListenMessage(); + } + } + + @Override + protected void sendAuthenticationMessage() { + // Ensure that the authorization Future exists + getAuthorizationFuture(); + + final JsonObject authObject = new JsonObject(); + authObject.addProperty("action", "authenticate"); + final JsonObject authData = new JsonObject(); + if (oAuthToken != null) { + authData.addProperty("oauth_token", oAuthToken); + } else { + authData.addProperty("key_id", keyID); + authData.addProperty("secret_key", secretKey); + } + authObject.add("data", authData); + + LOGGER.info("{} websocket sending authentication message...", websocketName); + sendWebsocketMessage(authObject.toString()); + } + + @Override + public void onMessage(@NotNull WebSocket webSocket, @NotNull ByteString byteString) { // Binary framing + final String messageString = byteString.utf8(); + LOGGER.trace("Websocket message received: message={}", messageString); + + // Parse JSON string and identify 'messageType' + final JsonObject messageObject = parseString(messageString).getAsJsonObject(); + final UpdatesMessageType messageType = + getGson().fromJson(messageObject.get("stream"), UpdatesMessageType.class); + + // Deserialize message based on 'messageType' and call listener + switch (messageType) { + case AUTHORIZATION: + final AuthorizationMessage authorizationMessage = + getGson().fromJson(messageObject, AuthorizationMessage.class); + authenticated = authorizationMessage.getData().getAction().equalsIgnoreCase("authenticate") && + authorizationMessage.getData().getStatus().equalsIgnoreCase("authorized"); + if (authenticationMessageFuture != null) { + authenticationMessageFuture.complete(authenticated); + } + if (!authenticated) { + throw new RuntimeException(websocketName + " websocket authentication failed!"); + } else { + LOGGER.info("{} websocket authenticated.", websocketName); + } + break; + case LISTENING: + break; + case TRADE_UPDATES: + if (listener != null) { + listener.onTradeUpdate(getGson().fromJson(messageObject, TradeUpdateMessage.class)); + } + break; + default: + throw new UnsupportedOperationException(); + } + } + + @Override + public void setListener(UpdatesListener listener) { + this.listener = listener; + } + + @Override + public void subscribeToTradeUpdates(boolean subscribe) { + listenToTradeUpdates = subscribe; + sendTradeUpdatesListenMessage(); + } + + private void sendTradeUpdatesListenMessage() { + if (!isConnected()) { + throw new IllegalStateException("This websocket must be connected before subscribing to streams!"); + } + final JsonObject requestObject = new JsonObject(); + requestObject.addProperty("action", "listen"); + final JsonArray streamsArray = new JsonArray(); + if (listenToTradeUpdates) { + streamsArray.add(TRADE_UPDATES.toString()); + } + final JsonObject dataObject = new JsonObject(); + dataObject.add("streams", streamsArray); + requestObject.add("data", dataObject); + + sendWebsocketMessage(requestObject.toString()); + LOGGER.info("Requested streams: streams={}.", streamsArray); + } +} diff --git a/src/main/java/net/jacobpeterson/alpaca/websocket/updates/UpdatesWebsocketInterface.java b/src/main/java/net/jacobpeterson/alpaca/websocket/updates/UpdatesWebsocketInterface.java new file mode 100644 index 00000000..0c86554c --- /dev/null +++ b/src/main/java/net/jacobpeterson/alpaca/websocket/updates/UpdatesWebsocketInterface.java @@ -0,0 +1,24 @@ +package net.jacobpeterson.alpaca.websocket.updates; + +import net.jacobpeterson.alpaca.model.websocket.updates.model.UpdatesMessageType; +import net.jacobpeterson.alpaca.websocket.AlpacaWebsocketInterface; + +/** + * {@link UpdatesWebsocketInterface} is an {@link AlpacaWebsocketInterface} for {@link UpdatesWebsocket}. + */ +public interface UpdatesWebsocketInterface extends AlpacaWebsocketInterface { + + /** + * Sets the {@link UpdatesListener}. + * + * @param listener the {@link UpdatesListener} + */ + void setListener(UpdatesListener listener); + + /** + * Subscribes to {@link UpdatesMessageType#TRADE_UPDATES}. + * + * @param subscribe true to subscribe, false otherwise + */ + void subscribeToTradeUpdates(boolean subscribe); +} diff --git a/src/main/java/net/jacobpeterson/alpaca/websocket/trades/model/authorization/authorization_data.json b/src/main/java/net/jacobpeterson/alpaca/websocket/updates/model/authorization/authorization_data.json similarity index 100% rename from src/main/java/net/jacobpeterson/alpaca/websocket/trades/model/authorization/authorization_data.json rename to src/main/java/net/jacobpeterson/alpaca/websocket/updates/model/authorization/authorization_data.json diff --git a/src/main/java/net/jacobpeterson/alpaca/websocket/trades/model/authorization/authorization_message.json b/src/main/java/net/jacobpeterson/alpaca/websocket/updates/model/authorization/authorization_message.json similarity index 66% rename from src/main/java/net/jacobpeterson/alpaca/websocket/trades/model/authorization/authorization_message.json rename to src/main/java/net/jacobpeterson/alpaca/websocket/updates/model/authorization/authorization_message.json index 82edef4d..4c991d10 100644 --- a/src/main/java/net/jacobpeterson/alpaca/websocket/trades/model/authorization/authorization_message.json +++ b/src/main/java/net/jacobpeterson/alpaca/websocket/updates/model/authorization/authorization_message.json @@ -1,12 +1,12 @@ { "type": "object", "extends": { - "existingJavaType": "net.jacobpeterson.alpaca.model.websocket.trades.model.TradesStreamMessage" + "existingJavaType": "net.jacobpeterson.alpaca.model.websocket.updates.model.UpdatesMessage" }, "properties": { "data": { - "existingJavaType": "net.jacobpeterson.alpaca.model.websocket.trades.model.authorization.AuthorizationData", - "title": "The {@link net.jacobpeterson.alpaca.model.websocket.trades.model.authorization.AuthorizationData}." + "existingJavaType": "net.jacobpeterson.alpaca.model.websocket.updates.model.authorization.AuthorizationData", + "title": "The {@link net.jacobpeterson.alpaca.model.websocket.updates.model.authorization.AuthorizationData}." } } } diff --git a/src/main/java/net/jacobpeterson/alpaca/websocket/updates/model/listening/listening_data.json b/src/main/java/net/jacobpeterson/alpaca/websocket/updates/model/listening/listening_data.json new file mode 100644 index 00000000..b2798264 --- /dev/null +++ b/src/main/java/net/jacobpeterson/alpaca/websocket/updates/model/listening/listening_data.json @@ -0,0 +1,9 @@ +{ + "type": "object", + "properties": { + "streams": { + "existingJavaType": "java.util.Set", + "title": "The {@link java.util.Set} of {@link net.jacobpeterson.alpaca.model.websocket.updates.model.UpdatesMessageType}s." + } + } +} diff --git a/src/main/java/net/jacobpeterson/alpaca/websocket/trades/model/listening/listening_message.json b/src/main/java/net/jacobpeterson/alpaca/websocket/updates/model/listening/listening_message.json similarity index 69% rename from src/main/java/net/jacobpeterson/alpaca/websocket/trades/model/listening/listening_message.json rename to src/main/java/net/jacobpeterson/alpaca/websocket/updates/model/listening/listening_message.json index 58b7b54f..0590794f 100644 --- a/src/main/java/net/jacobpeterson/alpaca/websocket/trades/model/listening/listening_message.json +++ b/src/main/java/net/jacobpeterson/alpaca/websocket/updates/model/listening/listening_message.json @@ -1,12 +1,12 @@ { "type": "object", "extends": { - "existingJavaType": "net.jacobpeterson.alpaca.model.websocket.trades.model.TradesStreamMessage" + "existingJavaType": "net.jacobpeterson.alpaca.model.websocket.updates.model.UpdatesMessage" }, "properties": { "data": { - "existingJavaType": "net.jacobpeterson.alpaca.model.websocket.trades.model.listening.ListeningData", - "title": "The {@link net.jacobpeterson.alpaca.model.websocket.trades.model.listening.ListeningData}." + "existingJavaType": "net.jacobpeterson.alpaca.model.websocket.updates.model.listening.ListeningData", + "title": "The {@link net.jacobpeterson.alpaca.model.websocket.updates.model.listening.ListeningData}." } } } diff --git a/src/main/java/net/jacobpeterson/alpaca/websocket/trades/model/tradeupdate/trade_update.json b/src/main/java/net/jacobpeterson/alpaca/websocket/updates/model/tradeupdate/trade_update.json similarity index 90% rename from src/main/java/net/jacobpeterson/alpaca/websocket/trades/model/tradeupdate/trade_update.json rename to src/main/java/net/jacobpeterson/alpaca/websocket/updates/model/tradeupdate/trade_update.json index ab5b6875..27f86dc2 100644 --- a/src/main/java/net/jacobpeterson/alpaca/websocket/trades/model/tradeupdate/trade_update.json +++ b/src/main/java/net/jacobpeterson/alpaca/websocket/updates/model/tradeupdate/trade_update.json @@ -2,8 +2,8 @@ "type": "object", "properties": { "event": { - "existingJavaType": "net.jacobpeterson.alpaca.model.websocket.trades.model.tradeupdate.TradeUpdateEvent", - "title": "The {@link net.jacobpeterson.alpaca.model.websocket.trades.model.tradeupdate.TradeUpdateEvent}." + "existingJavaType": "net.jacobpeterson.alpaca.model.websocket.updates.model.tradeupdate.TradeUpdateEvent", + "title": "The {@link net.jacobpeterson.alpaca.model.websocket.updates.model.tradeupdate.TradeUpdateEvent}." }, "execution_id": { "existingJavaType": "java.lang.String", diff --git a/src/main/java/net/jacobpeterson/alpaca/websocket/trades/model/tradeupdate/trade_update_event.json b/src/main/java/net/jacobpeterson/alpaca/websocket/updates/model/tradeupdate/trade_update_event.json similarity index 100% rename from src/main/java/net/jacobpeterson/alpaca/websocket/trades/model/tradeupdate/trade_update_event.json rename to src/main/java/net/jacobpeterson/alpaca/websocket/updates/model/tradeupdate/trade_update_event.json diff --git a/src/main/java/net/jacobpeterson/alpaca/websocket/trades/model/tradeupdate/trade_update_message.json b/src/main/java/net/jacobpeterson/alpaca/websocket/updates/model/tradeupdate/trade_update_message.json similarity index 69% rename from src/main/java/net/jacobpeterson/alpaca/websocket/trades/model/tradeupdate/trade_update_message.json rename to src/main/java/net/jacobpeterson/alpaca/websocket/updates/model/tradeupdate/trade_update_message.json index 6876c589..af22fa58 100644 --- a/src/main/java/net/jacobpeterson/alpaca/websocket/trades/model/tradeupdate/trade_update_message.json +++ b/src/main/java/net/jacobpeterson/alpaca/websocket/updates/model/tradeupdate/trade_update_message.json @@ -1,12 +1,12 @@ { "type": "object", "extends": { - "existingJavaType": "net.jacobpeterson.alpaca.model.websocket.trades.model.TradesStreamMessage" + "existingJavaType": "net.jacobpeterson.alpaca.model.websocket.updates.model.UpdatesMessage" }, "properties": { "data": { - "existingJavaType": "net.jacobpeterson.alpaca.model.websocket.trades.model.tradeupdate.TradeUpdate", - "title": "The {@link net.jacobpeterson.alpaca.model.websocket.trades.model.tradeupdate.TradeUpdate}." + "existingJavaType": "net.jacobpeterson.alpaca.model.websocket.updates.model.tradeupdate.TradeUpdate", + "title": "The {@link net.jacobpeterson.alpaca.model.websocket.updates.model.tradeupdate.TradeUpdate}." } } } diff --git a/src/main/java/net/jacobpeterson/alpaca/websocket/trades/model/trades_stream_message.json b/src/main/java/net/jacobpeterson/alpaca/websocket/updates/model/updates_message.json similarity index 69% rename from src/main/java/net/jacobpeterson/alpaca/websocket/trades/model/trades_stream_message.json rename to src/main/java/net/jacobpeterson/alpaca/websocket/updates/model/updates_message.json index fa337195..ad625676 100644 --- a/src/main/java/net/jacobpeterson/alpaca/websocket/trades/model/trades_stream_message.json +++ b/src/main/java/net/jacobpeterson/alpaca/websocket/updates/model/updates_message.json @@ -2,8 +2,8 @@ "type": "object", "properties": { "stream": { - "existingJavaType": "net.jacobpeterson.alpaca.model.websocket.trades.model.TradesStreamMessageType", - "title": "The {@link net.jacobpeterson.alpaca.model.websocket.trades.model.TradesStreamMessageType}." + "existingJavaType": "net.jacobpeterson.alpaca.model.websocket.updates.model.UpdatesMessageType", + "title": "The {@link net.jacobpeterson.alpaca.model.websocket.updates.model.UpdatesMessageType}." } } } diff --git a/src/main/java/net/jacobpeterson/alpaca/websocket/trades/model/trades_stream_message_type.json b/src/main/java/net/jacobpeterson/alpaca/websocket/updates/model/updates_message_type.json similarity index 100% rename from src/main/java/net/jacobpeterson/alpaca/websocket/trades/model/trades_stream_message_type.json rename to src/main/java/net/jacobpeterson/alpaca/websocket/updates/model/updates_message_type.json From ee5f1ae3fd8d1e95a8271f1d8a349b2b35ae712d Mon Sep 17 00:00:00 2001 From: Jacob Peterson <14127217+Petersoj@users.noreply.github.com> Date: Thu, 7 Mar 2024 22:06:54 -0800 Subject: [PATCH 213/271] Refactor REST API wrapper classes packages --- src/main/java/net/jacobpeterson/alpaca/AlpacaAPI.java | 3 +++ .../alpaca/{ => rest/broker}/AlpacaBrokerAPI.java | 6 +++--- .../alpaca/rest/broker/{ => events}/EventsApiSSE.java | 2 +- .../alpaca/{ => rest/marketdata}/AlpacaMarketDataAPI.java | 4 ++-- .../alpaca/{ => rest/trader}/AlpacaTraderAPI.java | 4 ++-- 5 files changed, 11 insertions(+), 8 deletions(-) rename src/main/java/net/jacobpeterson/alpaca/{ => rest/broker}/AlpacaBrokerAPI.java (97%) rename src/main/java/net/jacobpeterson/alpaca/rest/broker/{ => events}/EventsApiSSE.java (99%) rename src/main/java/net/jacobpeterson/alpaca/{ => rest/marketdata}/AlpacaMarketDataAPI.java (97%) rename src/main/java/net/jacobpeterson/alpaca/{ => rest/trader}/AlpacaTraderAPI.java (97%) diff --git a/src/main/java/net/jacobpeterson/alpaca/AlpacaAPI.java b/src/main/java/net/jacobpeterson/alpaca/AlpacaAPI.java index 2ae2e22e..c1c73096 100644 --- a/src/main/java/net/jacobpeterson/alpaca/AlpacaAPI.java +++ b/src/main/java/net/jacobpeterson/alpaca/AlpacaAPI.java @@ -3,6 +3,9 @@ import net.jacobpeterson.alpaca.model.util.apitype.BrokerAPIEndpointType; import net.jacobpeterson.alpaca.model.util.apitype.MarketDataWebsocketSourceType; import net.jacobpeterson.alpaca.model.util.apitype.TraderAPIEndpointType; +import net.jacobpeterson.alpaca.rest.broker.AlpacaBrokerAPI; +import net.jacobpeterson.alpaca.rest.marketdata.AlpacaMarketDataAPI; +import net.jacobpeterson.alpaca.rest.trader.AlpacaTraderAPI; import net.jacobpeterson.alpaca.websocket.marketdata.streams.crypto.CryptoMarketDataWebsocket; import net.jacobpeterson.alpaca.websocket.marketdata.streams.crypto.CryptoMarketDataWebsocketInterface; import net.jacobpeterson.alpaca.websocket.marketdata.streams.news.NewsMarketDataWebsocket; diff --git a/src/main/java/net/jacobpeterson/alpaca/AlpacaBrokerAPI.java b/src/main/java/net/jacobpeterson/alpaca/rest/broker/AlpacaBrokerAPI.java similarity index 97% rename from src/main/java/net/jacobpeterson/alpaca/AlpacaBrokerAPI.java rename to src/main/java/net/jacobpeterson/alpaca/rest/broker/AlpacaBrokerAPI.java index 942d71b3..c2d6aa2e 100644 --- a/src/main/java/net/jacobpeterson/alpaca/AlpacaBrokerAPI.java +++ b/src/main/java/net/jacobpeterson/alpaca/rest/broker/AlpacaBrokerAPI.java @@ -1,4 +1,4 @@ -package net.jacobpeterson.alpaca; +package net.jacobpeterson.alpaca.rest.broker; import net.jacobpeterson.alpaca.model.util.apitype.BrokerAPIEndpointType; import net.jacobpeterson.alpaca.openapi.broker.ApiClient; @@ -19,7 +19,7 @@ import net.jacobpeterson.alpaca.openapi.broker.api.ReportingApi; import net.jacobpeterson.alpaca.openapi.broker.api.TradingApi; import net.jacobpeterson.alpaca.openapi.broker.api.WatchlistApi; -import net.jacobpeterson.alpaca.rest.broker.EventsApiSSE; +import net.jacobpeterson.alpaca.rest.broker.events.EventsApiSSE; import okhttp3.OkHttpClient; import static com.google.common.base.Preconditions.checkNotNull; @@ -61,7 +61,7 @@ public class AlpacaBrokerAPI { * instance */ @SuppressWarnings("UnnecessaryDefault") - AlpacaBrokerAPI(String brokerAPIKey, String brokerAPISecret, BrokerAPIEndpointType brokerAPIEndpointType, + public AlpacaBrokerAPI(String brokerAPIKey, String brokerAPISecret, BrokerAPIEndpointType brokerAPIEndpointType, OkHttpClient okHttpClient) { checkNotNull(brokerAPIKey); checkNotNull(brokerAPISecret); diff --git a/src/main/java/net/jacobpeterson/alpaca/rest/broker/EventsApiSSE.java b/src/main/java/net/jacobpeterson/alpaca/rest/broker/events/EventsApiSSE.java similarity index 99% rename from src/main/java/net/jacobpeterson/alpaca/rest/broker/EventsApiSSE.java rename to src/main/java/net/jacobpeterson/alpaca/rest/broker/events/EventsApiSSE.java index ea2341e5..5bf918de 100644 --- a/src/main/java/net/jacobpeterson/alpaca/rest/broker/EventsApiSSE.java +++ b/src/main/java/net/jacobpeterson/alpaca/rest/broker/events/EventsApiSSE.java @@ -1,4 +1,4 @@ -package net.jacobpeterson.alpaca.rest.broker; +package net.jacobpeterson.alpaca.rest.broker.events; import com.google.gson.reflect.TypeToken; import net.jacobpeterson.alpaca.openapi.broker.ApiClient; diff --git a/src/main/java/net/jacobpeterson/alpaca/AlpacaMarketDataAPI.java b/src/main/java/net/jacobpeterson/alpaca/rest/marketdata/AlpacaMarketDataAPI.java similarity index 97% rename from src/main/java/net/jacobpeterson/alpaca/AlpacaMarketDataAPI.java rename to src/main/java/net/jacobpeterson/alpaca/rest/marketdata/AlpacaMarketDataAPI.java index 1728c09f..4206b485 100644 --- a/src/main/java/net/jacobpeterson/alpaca/AlpacaMarketDataAPI.java +++ b/src/main/java/net/jacobpeterson/alpaca/rest/marketdata/AlpacaMarketDataAPI.java @@ -1,4 +1,4 @@ -package net.jacobpeterson.alpaca; +package net.jacobpeterson.alpaca.rest.marketdata; import net.jacobpeterson.alpaca.openapi.marketdata.ApiClient; import net.jacobpeterson.alpaca.openapi.marketdata.api.CorporateActionsApi; @@ -38,7 +38,7 @@ public class AlpacaMarketDataAPI { * @param brokerAPISecret the Broker API secret * @param okHttpClient an existing {@link OkHttpClient} or null to create a new default instance */ - AlpacaMarketDataAPI(String traderKeyID, String traderSecretKey, + public AlpacaMarketDataAPI(String traderKeyID, String traderSecretKey, String brokerAPIKey, String brokerAPISecret, OkHttpClient okHttpClient) { checkArgument((traderKeyID != null && traderSecretKey != null) ^ (brokerAPIKey != null && brokerAPISecret != null), diff --git a/src/main/java/net/jacobpeterson/alpaca/AlpacaTraderAPI.java b/src/main/java/net/jacobpeterson/alpaca/rest/trader/AlpacaTraderAPI.java similarity index 97% rename from src/main/java/net/jacobpeterson/alpaca/AlpacaTraderAPI.java rename to src/main/java/net/jacobpeterson/alpaca/rest/trader/AlpacaTraderAPI.java index 07485e9c..515ba377 100644 --- a/src/main/java/net/jacobpeterson/alpaca/AlpacaTraderAPI.java +++ b/src/main/java/net/jacobpeterson/alpaca/rest/trader/AlpacaTraderAPI.java @@ -1,4 +1,4 @@ -package net.jacobpeterson.alpaca; +package net.jacobpeterson.alpaca.rest.trader; import net.jacobpeterson.alpaca.model.util.apitype.TraderAPIEndpointType; import net.jacobpeterson.alpaca.openapi.trader.ApiClient; @@ -48,7 +48,7 @@ public class AlpacaTraderAPI { * instance */ @SuppressWarnings("UnnecessaryDefault") - AlpacaTraderAPI(String traderKeyID, String traderSecretKey, String traderOAuthToken, + public AlpacaTraderAPI(String traderKeyID, String traderSecretKey, String traderOAuthToken, TraderAPIEndpointType traderAPIEndpointType, OkHttpClient okHttpClient) { checkArgument((traderKeyID != null && traderSecretKey != null) ^ (traderOAuthToken != null), "You must specify a (trader key ID and secret key) or an (OAuth token)!"); From 09d7a15c640c10c2139211c1b6bc3220ebfc7660 Mon Sep 17 00:00:00 2001 From: Jacob Peterson <14127217+Petersoj@users.noreply.github.com> Date: Fri, 8 Mar 2024 10:53:25 -0800 Subject: [PATCH 214/271] AlpacaWebsocket bug fix, change OpenAPI client methods visibility --- build.gradle | 16 ++++++++++++++++ .../alpaca/rest/broker/events/EventsApiSSE.java | 4 ++-- .../alpaca/websocket/AlpacaWebsocket.java | 5 +++++ 3 files changed, 23 insertions(+), 2 deletions(-) diff --git a/build.gradle b/build.gradle index c6c53daa..2048c1aa 100644 --- a/build.gradle +++ b/build.gradle @@ -229,6 +229,22 @@ final def fixOpenAPIGeneratedClientIssuesTask = tasks.register("fixOpenAPIGenera } } + // Make certain method signatures in the generated client API classes 'protected' instead of 'public' + specIDsOfFileNames.keySet().stream().map { it.replace(".json", "") }.forEach { + for (File apiFile : Paths.get(generatedClientLibrariesPath.getPath(), + it, sourceFilesPath, it.replace("-", ""), "api").toFile().listFiles()) { + // Don't make replacements in the broker 'events' API as that class is wrapped + if (apiFile.name == "EventsApi.java") { + continue + } + // Make replacements + apiFile.text = apiFile.text + .replace("public okhttp3.Call", "protected okhttp3.Call") + .replace("public ApiResponse", "protected ApiResponse") + .replaceAll("\\n.*public ApiClient[\\s\\S]*?customBaseUrl;\\n {4}}", "") + } + } + // Remove the necessary authentication header logic from the generate client's ApiClient specIDsOfFileNames.keySet().stream().map { it.replace(".json", "") }.forEach { final def apiClientFile = Paths.get(generatedClientLibrariesPath.getPath(), it, sourceFilesPath, diff --git a/src/main/java/net/jacobpeterson/alpaca/rest/broker/events/EventsApiSSE.java b/src/main/java/net/jacobpeterson/alpaca/rest/broker/events/EventsApiSSE.java index 5bf918de..65efee8f 100644 --- a/src/main/java/net/jacobpeterson/alpaca/rest/broker/events/EventsApiSSE.java +++ b/src/main/java/net/jacobpeterson/alpaca/rest/broker/events/EventsApiSSE.java @@ -169,12 +169,12 @@ public void onEvent(@NotNull EventSource eventSource, @Nullable String id, @Null @Override public void onFailure(@NotNull EventSource eventSource, @Nullable Throwable throwable, @Nullable Response response) { - LOGGER.error("Event source failure: eventSource={} throwable={}, response={}", - eventSource, throwable, response); if (throwable != null && throwable.getMessage().equals("canceled")) { sseListener.onClose(); return; } + LOGGER.error("Event source failure: eventSource={} throwable={}, response={}", + eventSource, throwable, response); sseListener.onError(throwable, response); } diff --git a/src/main/java/net/jacobpeterson/alpaca/websocket/AlpacaWebsocket.java b/src/main/java/net/jacobpeterson/alpaca/websocket/AlpacaWebsocket.java index 518435c2..e4440fbb 100644 --- a/src/main/java/net/jacobpeterson/alpaca/websocket/AlpacaWebsocket.java +++ b/src/main/java/net/jacobpeterson/alpaca/websocket/AlpacaWebsocket.java @@ -147,6 +147,11 @@ public void onClosed(@NotNull WebSocket webSocket, int code, @NotNull String rea @Override public void onFailure(@NotNull WebSocket webSocket, @NotNull Throwable cause, @Nullable Response response) { + if (intentionalClose) { + onClosed(webSocket, WEBSOCKET_NORMAL_CLOSURE_CODE, WEBSOCKET_NORMAL_CLOSURE_MESSAGE); + return; + } + LOGGER.error("{} websocket failure!", websocketName, cause); // A websocket failure occurs when either there is a connection failure or when the client throws // an exception when receiving a message. In either case, OkHttp will close the websocket connection, From f80a4292f78bb006fc106d0c3c40cc3b6e722013 Mon Sep 17 00:00:00 2001 From: Jacob Peterson <14127217+Petersoj@users.noreply.github.com> Date: Fri, 8 Mar 2024 10:56:21 -0800 Subject: [PATCH 215/271] Update README --- README.md | 209 ++++++++++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 196 insertions(+), 13 deletions(-) diff --git a/README.md b/README.md index 3aa3cfcc..25c7d6ff 100644 --- a/README.md +++ b/README.md @@ -7,7 +7,7 @@

# Overview -This library is a Java client implementation of the Alpaca API. Alpaca lets you trade with algorithms, connect with apps, and build services all with a commission-free trading API for stocks, crypto, and options. This library uses the [Alpaca OpenAPI Specifications](https://docs.alpaca.markets/v1.1/openapi) to generate clients for the REST API with the [OkHttp](https://square.github.io/okhttp/) library, but implements the websocket and SSE streaming interface using a custom implementation with the [OkHttp](https://square.github.io/okhttp/) library. This library is community developed and if you have any questions, please ask them on [Github Discussions](https://github.com/Petersoj/alpaca-java/discussions), the [Alpaca Slack #dev-alpaca-java channel](https://alpaca.markets/slack), or on the [Alpaca Forums](https://forum.alpaca.markets/). +This library is a Java client implementation of the Alpaca API. Alpaca lets you trade with algorithms, connect with apps, and build services all with a commission-free trading API for stocks, crypto, and options. This library uses the [Alpaca OpenAPI Specifications](https://docs.alpaca.markets/v1.1/openapi) to generate clients for the REST API with the [OkHttp](https://square.github.io/okhttp/) library, but implements the websocket and SSE streaming interface using a custom implementation with the [OkHttp](https://square.github.io/okhttp/) library. This library is community developed and if you have any questions, please ask them on [Github Discussions](https://github.com/Petersoj/alpaca-java/discussions), the [Alpaca Slack #dev-alpaca-java channel](https://alpaca.markets/slack), or on the [Alpaca Forums](https://forum.alpaca.markets/). This library strives to provide the complete Alpaca API as a Java client implementation, so open a [new issue](https://github.com/Petersoj/alpaca-java/issues) or [new pull request](https://github.com/Petersoj/alpaca-java/pulls) if you find something missing. Give this repository a star ⭐ if it helped you build a trading algorithm in Java! @@ -33,6 +33,9 @@ Note that you don't have to use the Maven Central artifacts. Instead, you can cl # Logger For logging, this library uses [SLF4j](http://www.slf4j.org/) which serves as an interface for various logging frameworks. This enables you to use whatever logging framework you would like. However, if you do not add a logging framework as a dependency in your project, the console will output a message stating that SLF4j is defaulting to a no-operation (NOP) logger implementation. To enable logging, add a logging framework of your choice as a dependency to your project such as [Logback](http://logback.qos.ch/), [Log4j 2](http://logging.apache.org/log4j/2.x/index.html), [SLF4j-simple](http://www.slf4j.org/manual.html), or [Apache Commons Logging](https://commons.apache.org/proper/commons-logging/). +# `BigDecimal` vs. `Double` +It is generally considered bad practice to represent currency values in floating-point data types such as `float` or `double` because it can lead to [rounding errors](https://ta4j.github.io/ta4j-wiki/Num.html) and [precision loss](https://stackoverflow.com/a/3730040/4352701) in calculations. However, using floating-point data types can have significant performance benefits compared to using arbitrary-precision number data types, especially in a quantitative finance and algorithmic trading environment. Because of this, `alpaca-java` uses the `Double` data type (the `double` boxed type) when using the Market Data APIs and the `BigDecimal` type when using the Trading or Broker APIs. The thinking behind this is that exact decimal quantities are important when placing real trades with real money, but less important when performing calculations of financial indicators. The best solution would be to use the [TA4j](https://github.com/ta4j/ta4j) `Num` interface so that you can decide what data type to use based on your use case, but that is on the [TODO list](#todo) for now. + # Examples Note that the examples below are not exhaustive. Refer to the [Javadoc](https://javadoc.io/doc/net.jacobpeterson.alpaca/alpaca-java) for all classes and method signatures. @@ -42,25 +45,204 @@ Note that the examples below are not exhaustive. Refer to the [Javadoc](https:// The Alpaca API documentation is located [here](https://docs.alpaca.markets/) and the [`AlpacaAPI`](src/main/java/net/jacobpeterson/alpaca/AlpacaAPI.java) Javadoc is located [here](https://javadoc.io/doc/net.jacobpeterson.alpaca/alpaca-java/latest/net/jacobpeterson/alpaca/AlpacaAPI.html). Example usage: + +Use this code if you are using the Trading or Market Data APIs for a single Alpaca account: ```java -// TODO +final String keyID = ""; +final String secretKey = ""; +final TraderAPIEndpointType endpointType = TraderAPIEndpointType.PAPER; // or 'LIVE' +final MarketDataWebsocketSourceType sourceType = MarketDataWebsocketSourceType.IEX; // or 'SIP' +final AlpacaAPI alpacaAPI = new AlpacaAPI(keyID, secretKey, endpointType, sourceType); ``` -Note that this library uses [OkHttp](https://square.github.io/okhttp/) as its HTTP client library which creates background threads to service requests via a connection pool. These threads persist even if the main thread exits, so if you want to destroy these threads when you're done using [`AlpacaAPI`](src/main/java/net/jacobpeterson/alpaca/AlpacaAPI.java), call `alpacaAPI.closeOkHttpClient();`. +Use this code if you are using the Trading API with OAuth to act on behalf of an Alpaca account: +```java +final String oAuthToken = ""; +final TraderAPIEndpointType endpointType = TraderAPIEndpointType.PAPER; // or 'LIVE' +final AlpacaAPI alpacaAPI = new AlpacaAPI(oAuthToken, endpointType); +``` -## Trading API +Use this code if you are using the Broker API: ```java -// TODO +final String brokerAPIKey = ""; +final String brokerAPISecret = ""; +final BrokerAPIEndpointType endpointType = BrokerAPIEndpointType.SANDBOX; // or 'PRODUCTION' +final AlpacaAPI alpacaAPI = new AlpacaAPI(brokerAPIKey, brokerAPISecret, endpointType); ``` -## Market Data API +Note that this library uses [OkHttp](https://square.github.io/okhttp/) as its HTTP client library which creates background threads to service requests via a connection pool. These threads persist even if the main thread exits, so if you want to destroy these threads when you're done using [`AlpacaAPI`](src/main/java/net/jacobpeterson/alpaca/AlpacaAPI.java), use `alpacaAPI.closeOkHttpClient();`. + +## [`Trader API`](src/main/java/net/jacobpeterson/alpaca/rest/trader/AlpacaTraderAPI.java) +The [`Trader API`](src/main/java/net/jacobpeterson/alpaca/rest/trader/AlpacaTraderAPI.java) is used for placing trades, updating account details, getting open positions, and more. Refer to the [Javadoc](https://javadoc.io/doc/net.jacobpeterson.alpaca/alpaca-java/latest/net/jacobpeterson/alpaca/rest/trader/AlpacaTraderAPI.html) for a list of all available method signatures. + +Example usage: ```java -// TODO +// Place a market order to buy one share of Apple +final Order openingOrder = alpacaAPI.trader().orders() + .postOrder(new PostOrderRequest() + .symbol("AAPL") + .qty("1") + .side(OrderSide.BUY) + .type(OrderType.MARKET) + .timeInForce(TimeInForce.GTC)); +System.out.println("Opening Apple order: " + openingOrder); + +// Wait for massive gains +Thread.sleep(10_000); + +// Close the Apple position +Order closingOrder = alpacaAPI.trader().positions() + .deleteOpenPosition("AAPL", null, new BigDecimal("100")); +System.out.println("Closing Apple order: " + openingOrder); + +// Wait for closing trade to fill +Thread.sleep(10_000); + +// Print out PnL +final String openFillPrice = alpacaAPI.trader().orders() + .getOrderByOrderID(UUID.fromString(openingOrder.getId()), false) + .getFilledAvgPrice(); +final String closeFillPrice = alpacaAPI.trader().orders() + .getOrderByOrderID(UUID.fromString(closingOrder.getId()), false) + .getFilledAvgPrice(); +System.out.println("PnL from Apple trade: " + + new BigDecimal(closeFillPrice).subtract(new BigDecimal(openFillPrice))); ``` -## Broker API +## [`Market Data API`](src/main/java/net/jacobpeterson/alpaca/rest/marketdata/AlpacaMarketDataAPI.java) +The [`Market Data API`](src/main/java/net/jacobpeterson/alpaca/rest/marketdata/AlpacaMarketDataAPI.java) is used getting market data for stocks, cryptocurrencies, options, and more. Refer to the [Javadoc](https://javadoc.io/doc/net.jacobpeterson.alpaca/alpaca-java/latest/net/jacobpeterson/alpaca/rest/marketdata/AlpacaMarketDataAPI.html) for a list of all available method signatures. + +Example usage: ```java -// TODO +// Print out the latest Tesla trade +final StockTrade latestTSLATrade = alpacaAPI.marketData().stock() + .stockLatestTradeSingle("TSLA", StockFeed.IEX, null).getTrade(); +System.out.printf("Latest TSLA trade: price=%s, size=%s\n", + latestTSLATrade.getP(), latestTSLATrade.getS()); + +// Print out the highest Bitcoin ask price on the order book +final CryptoOrderbook latestBitcoinOrderBooks = alpacaAPI.marketData().crypto() + .cryptoLatestOrderbooks(CryptoLoc.US, "BTC/USD").getOrderbooks().get("BTC/USD"); +final Double highestBitcoinAskPrice = latestBitcoinOrderBooks.getA().stream() + .map(CryptoOrderbookEntry::getP) + .max(Double::compare) + .orElse(null); +System.out.println("Bitcoin highest ask price: " + highestBitcoinAskPrice); + +// Print out the latest Microsoft option trade +final String latestMSFTOptionTrade = alpacaAPI.marketData().option() + .optionChain("MSFT").getSnapshots().entrySet().stream() + .filter(entry -> entry.getValue().getLatestTrade() != null) + .map(entry -> Map.entry(entry.getKey(), entry.getValue().getLatestTrade())) + .max(Comparator.comparing(entry -> entry.getValue().getT())) + .map(entry -> String.format("ticker=%s, time=%s, price=%s", + entry.getKey(), entry.getValue().getT(), entry.getValue().getP())) + .orElse(null); +System.out.println("Latest Microsoft option trade: " + latestMSFTOptionTrade); +``` + +## [`Broker API`](src/main/java/net/jacobpeterson/alpaca/rest/broker/AlpacaBrokerAPI.java) +The [`Broker API`](src/main/java/net/jacobpeterson/alpaca/rest/broker/AlpacaBrokerAPI.java) is used for creating new Alpaca accounts for your end users, funding their accounts, placing trades, and more. Refer to the [Javadoc](https://javadoc.io/doc/net.jacobpeterson.alpaca/alpaca-java/latest/net/jacobpeterson/alpaca/rest/broker/AlpacaBrokerAPI.html) for a list of all available method signatures. + +Example usage: +```java +// Listen to the trade events (SSE) and print them out +final SSERequest sseRequest = alpacaAPI.broker().events() + .subscribeToTradeV2(null, null, null, null, new SSEListenerAdapter<>()); + +// Wait for SSE channel to be ready +Thread.sleep(2000); + +// Buy one share of GME for an account +alpacaAPI.broker().trading() + .createOrderForAccount(UUID.fromString(""), + new CreateOrderRequest() + .symbol("GME") + .qty(BigDecimal.ONE) + .side(OrderSide.SELL) + .timeInForce(TimeInForce.GTC) + .type(OrderType.MARKET)); +// Wait to be filled +Thread.sleep(2000); + +// Close the SSE stream and the OkHttpClient to exit cleanly +sseRequest.close(); +alpacaAPI.closeOkHttpClient(); +``` + +## [`Updates Stream`](src/main/java/net/jacobpeterson/alpaca/websocket/updates/UpdatesWebsocket.java) +The [`Updates Stream`](src/main/java/net/jacobpeterson/alpaca/websocket/updates/UpdatesWebsocket.java) is used for listening to trade updates in realtime. Refer to the [Javadoc](https://javadoc.io/doc/net.jacobpeterson.alpaca/alpaca-java/latest/net/jacobpeterson/alpaca/websocket/updates/UpdatesWebsocketInterface.html) for a list of all available method signatures. + +Example usage: +```java +// Connect to the 'updates' stream and wait until it's authorized +alpacaAPI.updatesStream().connect(); +if (!alpacaAPI.updatesStream().waitForAuthorization(5, TimeUnit.SECONDS)) { + throw new RuntimeException(); +} + +// Print out trade updates +alpacaAPI.updatesStream().setListener(System.out::println); +alpacaAPI.updatesStream().subscribeToTradeUpdates(true); + +// Place a trade +alpacaAPI.trader().orders().postOrder(new PostOrderRequest() + .symbol("AAPL") + .qty("1") + .side(OrderSide.BUY) + .type(OrderType.MARKET) + .timeInForce(TimeInForce.GTC)); + +// Wait a few seconds +Thread.sleep(5000); + +// Close the trade +alpacaAPI.trader().positions().deleteAllOpenPositions(true); + +// Wait a few seconds +Thread.sleep(5000); + +// Disconnect the 'updates' stream and exit cleanly +alpacaAPI.updatesStream().disconnect(); +alpacaAPI.closeOkHttpClient(); +``` + +## [`Market Data Stream`](src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/MarketDataWebsocket.java) +The [`Market Data Stream`](src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/MarketDataWebsocket.java) is used for listening to stock market data, crypto market data, and news data in realtime. Refer to the Javadocs ([stock](https://javadoc.io/doc/net.jacobpeterson.alpaca/alpaca-java/latest/net/jacobpeterson/alpaca/websocket/marketdata/streams/stock/StockMarketDataWebsocketInterface.html), [crypto](https://javadoc.io/doc/net.jacobpeterson.alpaca/alpaca-java/latest/net/jacobpeterson/alpaca/websocket/marketdata/streams/crypto/CryptoMarketDataWebsocketInterface.html), [news](https://javadoc.io/doc/net.jacobpeterson.alpaca/alpaca-java/latest/net/jacobpeterson/alpaca/websocket/marketdata/streams/news/NewsMarketDataWebsocketInterface.html)) for a list of all available method signatures. + +Example usage: +```java +// Connect to the 'stock market data' stream and wait until it's authorized +alpacaAPI.stockMarketDataStream().connect(); +if (!alpacaAPI.stockMarketDataStream().waitForAuthorization(5, TimeUnit.SECONDS)) { + throw new RuntimeException(); +} + +// Print out trade messages +alpacaAPI.stockMarketDataStream().setListener(new StockMarketDataListenerAdapter() { + @Override + public void onTrade(StockTradeMessage trade) { + System.out.println("Received trade: " + trade); + } +}); + +// Subscribe to AAPL trades +System.out.println("Subscribed to Apple trades."); +alpacaAPI.stockMarketDataStream().setTradeSubscriptions(Set.of("AAPL")); + +// Wait a few seconds +Thread.sleep(5000); + +// Unsubscribe from AAPL and subscribe to TSLA and MSFT +System.out.println("Subscribed to Tesla and Microsoft trades."); +alpacaAPI.stockMarketDataStream().setTradeSubscriptions(Set.of("TSLA", "MSFT")); + +// Wait a few seconds +Thread.sleep(5000); + +// Disconnect the 'stock market data' stream and exit cleanly +alpacaAPI.stockMarketDataStream().disconnect(); +alpacaAPI.closeOkHttpClient(); ``` # Building @@ -75,12 +257,13 @@ To install the built artifacts to your local Maven repository on your machine (t ``` # TODO -- Implement better reconnect logic for Websockets and SSE streaming -- Implement Unit Testing for REST API and Websocket streaming (both live and mocked) +- Implement better reconnect logic for Websockets and SSE streaming. +- Implement Unit Testing for REST API and Websocket streaming (both live and mocked). +- Use [TA4j](https://github.com/ta4j/ta4j) `Num` interface instead of `Double` or `BigDecimal` for number data types so that users can use either `Double` or `BigDecimal` for performance or precision in price data. # Contributing Contributions are welcome! -Do the following before starting your Pull Request: +Do the following before starting your pull request: 1. Create a new branch in your forked repository for your feature or bug fix instead of committing directly to the `master` branch in your fork. -2. Use the `dev` branch as the base branch in your Pull Request. +2. Use the `dev` branch as the base branch in your pull request. From a20ba8f8f526e5de954416054907bfa1802f817f Mon Sep 17 00:00:00 2001 From: Jacob Peterson <14127217+Petersoj@users.noreply.github.com> Date: Fri, 8 Mar 2024 11:05:03 -0800 Subject: [PATCH 216/271] Consolidate throwing exception for disconnected websockets --- README.md | 1 + .../net/jacobpeterson/alpaca/websocket/AlpacaWebsocket.java | 3 +++ .../alpaca/websocket/updates/UpdatesWebsocket.java | 3 --- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 25c7d6ff..d3b6dace 100644 --- a/README.md +++ b/README.md @@ -162,6 +162,7 @@ alpacaAPI.broker().trading() .side(OrderSide.SELL) .timeInForce(TimeInForce.GTC) .type(OrderType.MARKET)); + // Wait to be filled Thread.sleep(2000); diff --git a/src/main/java/net/jacobpeterson/alpaca/websocket/AlpacaWebsocket.java b/src/main/java/net/jacobpeterson/alpaca/websocket/AlpacaWebsocket.java index e4440fbb..d6f920c2 100644 --- a/src/main/java/net/jacobpeterson/alpaca/websocket/AlpacaWebsocket.java +++ b/src/main/java/net/jacobpeterson/alpaca/websocket/AlpacaWebsocket.java @@ -209,6 +209,9 @@ protected void cleanupState() { * @param message the message */ protected void sendWebsocketMessage(String message) { + if (!isConnected()) { + throw new IllegalStateException("This websocket must be connected before send a message!"); + } LOGGER.trace("Websocket message sent: {}", message); websocket.send(message); } diff --git a/src/main/java/net/jacobpeterson/alpaca/websocket/updates/UpdatesWebsocket.java b/src/main/java/net/jacobpeterson/alpaca/websocket/updates/UpdatesWebsocket.java index 99e1db42..ae31a623 100644 --- a/src/main/java/net/jacobpeterson/alpaca/websocket/updates/UpdatesWebsocket.java +++ b/src/main/java/net/jacobpeterson/alpaca/websocket/updates/UpdatesWebsocket.java @@ -153,9 +153,6 @@ public void subscribeToTradeUpdates(boolean subscribe) { } private void sendTradeUpdatesListenMessage() { - if (!isConnected()) { - throw new IllegalStateException("This websocket must be connected before subscribing to streams!"); - } final JsonObject requestObject = new JsonObject(); requestObject.addProperty("action", "listen"); final JsonArray streamsArray = new JsonArray(); From b34f8dd0e2449eec65401792fb988d79a4ab42b3 Mon Sep 17 00:00:00 2001 From: Jacob Peterson <14127217+Petersoj@users.noreply.github.com> Date: Fri, 8 Mar 2024 11:07:52 -0800 Subject: [PATCH 217/271] Undo premature version number change --- README.md | 4 ++-- build.gradle | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index d3b6dace..dade8336 100644 --- a/README.md +++ b/README.md @@ -15,7 +15,7 @@ Give this repository a star ⭐ if it helped you build a trading algorithm in Ja If you are using Gradle as your build tool, add the following dependency to your `build.gradle` file: ``` -implementation group: "net.jacobpeterson.alpaca", name: "alpaca-java", version: "10.0.0" +implementation group: "net.jacobpeterson.alpaca", name: "alpaca-java", version: "9.2.0" ``` If you are using Maven as your build tool, add the following dependency to your `pom.xml` file: @@ -24,7 +24,7 @@ If you are using Maven as your build tool, add the following dependency to your net.jacobpeterson.alpaca alpaca-java - 10.0.0 + 9.2.0 ``` diff --git a/build.gradle b/build.gradle index 2048c1aa..08d8e81d 100644 --- a/build.gradle +++ b/build.gradle @@ -22,7 +22,7 @@ plugins { final def projectGroup = "net.jacobpeterson.alpaca" final def projectArtifactID = "alpaca-java" -final def projectVersion = "10.0.0-SNAPSHOT" +final def projectVersion = "9.2.0-SNAPSHOT" group = projectGroup version = projectVersion From 32495432b5ee3e49c48fdf61ee2c7a1ae59ee9a3 Mon Sep 17 00:00:00 2001 From: Jacob Peterson <14127217+Petersoj@users.noreply.github.com> Date: Fri, 8 Mar 2024 11:08:33 -0800 Subject: [PATCH 218/271] Update to 10.0.0 --- README.md | 4 ++-- build.gradle | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index dade8336..d3b6dace 100644 --- a/README.md +++ b/README.md @@ -15,7 +15,7 @@ Give this repository a star ⭐ if it helped you build a trading algorithm in Ja If you are using Gradle as your build tool, add the following dependency to your `build.gradle` file: ``` -implementation group: "net.jacobpeterson.alpaca", name: "alpaca-java", version: "9.2.0" +implementation group: "net.jacobpeterson.alpaca", name: "alpaca-java", version: "10.0.0" ``` If you are using Maven as your build tool, add the following dependency to your `pom.xml` file: @@ -24,7 +24,7 @@ If you are using Maven as your build tool, add the following dependency to your net.jacobpeterson.alpaca alpaca-java - 9.2.0 + 10.0.0 ``` diff --git a/build.gradle b/build.gradle index 08d8e81d..2048c1aa 100644 --- a/build.gradle +++ b/build.gradle @@ -22,7 +22,7 @@ plugins { final def projectGroup = "net.jacobpeterson.alpaca" final def projectArtifactID = "alpaca-java" -final def projectVersion = "9.2.0-SNAPSHOT" +final def projectVersion = "10.0.0-SNAPSHOT" group = projectGroup version = projectVersion From 5b9f9e2b20a931d9c108a68d0d999abe9b95c2fb Mon Sep 17 00:00:00 2001 From: Jacob Peterson <14127217+Petersoj@users.noreply.github.com> Date: Fri, 8 Mar 2024 12:27:15 -0800 Subject: [PATCH 219/271] Minor update to README --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index d3b6dace..0ad65e93 100644 --- a/README.md +++ b/README.md @@ -34,7 +34,7 @@ Note that you don't have to use the Maven Central artifacts. Instead, you can cl For logging, this library uses [SLF4j](http://www.slf4j.org/) which serves as an interface for various logging frameworks. This enables you to use whatever logging framework you would like. However, if you do not add a logging framework as a dependency in your project, the console will output a message stating that SLF4j is defaulting to a no-operation (NOP) logger implementation. To enable logging, add a logging framework of your choice as a dependency to your project such as [Logback](http://logback.qos.ch/), [Log4j 2](http://logging.apache.org/log4j/2.x/index.html), [SLF4j-simple](http://www.slf4j.org/manual.html), or [Apache Commons Logging](https://commons.apache.org/proper/commons-logging/). # `BigDecimal` vs. `Double` -It is generally considered bad practice to represent currency values in floating-point data types such as `float` or `double` because it can lead to [rounding errors](https://ta4j.github.io/ta4j-wiki/Num.html) and [precision loss](https://stackoverflow.com/a/3730040/4352701) in calculations. However, using floating-point data types can have significant performance benefits compared to using arbitrary-precision number data types, especially in a quantitative finance and algorithmic trading environment. Because of this, `alpaca-java` uses the `Double` data type (the `double` boxed type) when using the Market Data APIs and the `BigDecimal` type when using the Trading or Broker APIs. The thinking behind this is that exact decimal quantities are important when placing real trades with real money, but less important when performing calculations of financial indicators. The best solution would be to use the [TA4j](https://github.com/ta4j/ta4j) `Num` interface so that you can decide what data type to use based on your use case, but that is on the [TODO list](#todo) for now. +It is generally considered bad practice to represent currency values in floating-point data types such as `float` or `double` because it can lead to [rounding errors](https://ta4j.github.io/ta4j-wiki/Num.html) and [precision loss](https://stackoverflow.com/a/3730040/4352701) in calculations. However, using floating-point data types can have significant performance benefits compared to using arbitrary-precision number data types, especially in a quantitative finance and algorithmic trading environment. Because of this, `alpaca-java` uses the `Double` data type (the `double` boxed type) when using the Market Data APIs and the `BigDecimal` data type when using the Trading or Broker APIs. The thinking behind this is that exact decimal quantities are important when placing real trades with real money, but less important when performing calculations of financial indicators. The best solution would be to use the [TA4j](https://github.com/ta4j/ta4j) `Num` interface so that you can decide what data type to use based on your use case, but that is on the [TODO list](#todo) for now. By the way, using `BigDecimal` wouldn't matter if Alpaca used floating-point data types in their internal systems or in their REST APIs, but the fact that they use `String` data types in some of the REST API JSON responses and their Trading and Broker OpenAPI specifications don't use the `double` data type, this leads me to believe that using `BigDecimal` does actually matter. # Examples Note that the examples below are not exhaustive. Refer to the [Javadoc](https://javadoc.io/doc/net.jacobpeterson.alpaca/alpaca-java) for all classes and method signatures. @@ -228,15 +228,15 @@ alpacaAPI.stockMarketDataStream().setListener(new StockMarketDataListenerAdapter }); // Subscribe to AAPL trades -System.out.println("Subscribed to Apple trades."); alpacaAPI.stockMarketDataStream().setTradeSubscriptions(Set.of("AAPL")); +System.out.println("Subscribed to Apple trades."); // Wait a few seconds Thread.sleep(5000); // Unsubscribe from AAPL and subscribe to TSLA and MSFT -System.out.println("Subscribed to Tesla and Microsoft trades."); alpacaAPI.stockMarketDataStream().setTradeSubscriptions(Set.of("TSLA", "MSFT")); +System.out.println("Subscribed to Tesla and Microsoft trades."); // Wait a few seconds Thread.sleep(5000); From 933c28f521d33c3d1681d738840693501a6bac44 Mon Sep 17 00:00:00 2001 From: Jacob Peterson <14127217+Petersoj@users.noreply.github.com> Date: Sat, 9 Mar 2024 08:38:58 -0800 Subject: [PATCH 220/271] Fix OpenAPI generated client 'anyOf' and 'oneOf' issues --- build.gradle | 20 ++++++++--- .../alpaca/rest/broker/AlpacaBrokerAPI.java | 33 +++++++++++++++++++ .../alpaca/rest/trader/AlpacaTraderAPI.java | 9 +++++ 3 files changed, 58 insertions(+), 4 deletions(-) diff --git a/build.gradle b/build.gradle index 2048c1aa..7eadeb3c 100644 --- a/build.gradle +++ b/build.gradle @@ -220,10 +220,15 @@ final def fixOpenAPIGeneratedClientIssuesTask = tasks.register("fixOpenAPIGenera modelFile.text = modelFile.text .replaceAll("/\\*\\*\\n {2}\\* Validates the JSON Element and throws an exception if issues " + "found[\\s\\S]*?\\*/", "") + .replace("import java.io.IOException;", + "import java.io.IOException;\nimport java.util.function.Predicate;") .replace("public static void validateJsonElement(JsonElement jsonElement) throws IOException {", - "public static boolean validate = false;\n public static void " + - "validateJsonElement(JsonElement jsonElement) throws IOException {\n" + - " if (!validate) return;") + "public static boolean validate = false;\n" + + " public static Predicate isValid = jsonElement -> true;\n" + + " public static void validateJsonElement(JsonElement jsonElement) " + + "throws IOException {\n" + + " if (!isValid.test(jsonElement)) throw new RuntimeException();\n" + + " if (!validate) return;\n") .replace("throw new IllegalArgumentException(\"Unexpected value '\" + value + \"'\");", "return null;") } @@ -255,12 +260,19 @@ final def fixOpenAPIGeneratedClientIssuesTask = tasks.register("fixOpenAPIGenera .replaceAll("for \\(String authName : authNames\\)[\\s\\S]*?uri\\);\\n. {7}}", "") } - // Make the 'JSON' classes of the generate clients have a static Gson initializer + // Make the 'JSON' classes of the generate clients have a static Gson initializer. Also fix the OffsetDateTime + // Gson type adapter to accommodate ISO local dates. specIDsOfFileNames.keySet().stream().map { it.replace(".json", "") }.forEach { final def jsonClientFile = Paths.get(generatedClientLibrariesPath.getPath(), it, sourceFilesPath, it.replace("-", ""), "JSON.java").toFile() jsonClientFile.text = jsonClientFile.text .replaceAll("\\{\\n {8}GsonBuilder", "static {\n GsonBuilder") + .replaceAll("}[\\s\\S].*return OffsetDateTime\\.parse\\(date, formatter\\);", + "} else if (!date.contains(\"T\")) {\n" + + "return LocalDate.parse(date, DateTimeFormatter.ISO_LOCAL_DATE)" + + ".atTime(java.time.LocalTime.MIN.atOffset(java.time.ZoneOffset.UTC));\n" + + "}\n" + + "return OffsetDateTime.parse(date, formatter);") } } } diff --git a/src/main/java/net/jacobpeterson/alpaca/rest/broker/AlpacaBrokerAPI.java b/src/main/java/net/jacobpeterson/alpaca/rest/broker/AlpacaBrokerAPI.java index c2d6aa2e..cfbd3e75 100644 --- a/src/main/java/net/jacobpeterson/alpaca/rest/broker/AlpacaBrokerAPI.java +++ b/src/main/java/net/jacobpeterson/alpaca/rest/broker/AlpacaBrokerAPI.java @@ -1,5 +1,6 @@ package net.jacobpeterson.alpaca.rest.broker; +import com.google.gson.JsonElement; import net.jacobpeterson.alpaca.model.util.apitype.BrokerAPIEndpointType; import net.jacobpeterson.alpaca.openapi.broker.ApiClient; import net.jacobpeterson.alpaca.openapi.broker.api.AccountsApi; @@ -19,9 +20,18 @@ import net.jacobpeterson.alpaca.openapi.broker.api.ReportingApi; import net.jacobpeterson.alpaca.openapi.broker.api.TradingApi; import net.jacobpeterson.alpaca.openapi.broker.api.WatchlistApi; +import net.jacobpeterson.alpaca.openapi.broker.model.AdminActionLegacyNote; +import net.jacobpeterson.alpaca.openapi.broker.model.AdminActionLiquidation; +import net.jacobpeterson.alpaca.openapi.broker.model.AdminActionTransactionCancel; +import net.jacobpeterson.alpaca.openapi.broker.model.JNLC; +import net.jacobpeterson.alpaca.openapi.broker.model.JNLS; +import net.jacobpeterson.alpaca.openapi.broker.model.NonTradeActivity; +import net.jacobpeterson.alpaca.openapi.broker.model.TradeActivity; import net.jacobpeterson.alpaca.rest.broker.events.EventsApiSSE; import okhttp3.OkHttpClient; +import java.util.function.BiFunction; + import static com.google.common.base.Preconditions.checkNotNull; import static net.jacobpeterson.alpaca.util.apikey.APIKeyUtil.createBrokerAPIAuthKey; @@ -31,6 +41,29 @@ */ public class AlpacaBrokerAPI { + // Set validation predicates for 'anyOf' and 'oneOf' models + static { + // 'AccountsApi' models + TradeActivity.isValid = jsonElement -> jsonElement.getAsJsonObject().has("type"); + NonTradeActivity.isValid = jsonElement -> !jsonElement.getAsJsonObject().has("type"); + + // 'JournalsApi' models + final BiFunction transferPredicate = (jnlc, jsonElement) -> { + final String entryType = jsonElement.getAsJsonObject().get("entry_type").getAsString(); + return jnlc ? entryType.equals("JNLC") : entryType.equals("JNLS"); + }; + JNLS.isValid = jsonElement -> transferPredicate.apply(true, jsonElement); + JNLC.isValid = jsonElement -> transferPredicate.apply(false, jsonElement); + + // 'EventsApi' models + AdminActionLegacyNote.isValid = jsonElement -> jsonElement.getAsJsonObject() + .get("type").getAsString().contains("note_admin_event"); + AdminActionLiquidation.isValid = jsonElement -> jsonElement.getAsJsonObject() + .get("type").getAsString().equals("liquidation_admin_event"); + AdminActionTransactionCancel.isValid = jsonElement -> jsonElement.getAsJsonObject() + .get("type").getAsString().equals("transaction_cancel_admin_event"); + } + private final ApiClient apiClient; private AccountsApi accounts; private AssetsApi assets; diff --git a/src/main/java/net/jacobpeterson/alpaca/rest/trader/AlpacaTraderAPI.java b/src/main/java/net/jacobpeterson/alpaca/rest/trader/AlpacaTraderAPI.java index 515ba377..7b560a26 100644 --- a/src/main/java/net/jacobpeterson/alpaca/rest/trader/AlpacaTraderAPI.java +++ b/src/main/java/net/jacobpeterson/alpaca/rest/trader/AlpacaTraderAPI.java @@ -13,6 +13,8 @@ import net.jacobpeterson.alpaca.openapi.trader.api.PortfolioHistoryApi; import net.jacobpeterson.alpaca.openapi.trader.api.PositionsApi; import net.jacobpeterson.alpaca.openapi.trader.api.WatchlistsApi; +import net.jacobpeterson.alpaca.openapi.trader.model.NonTradeActivities; +import net.jacobpeterson.alpaca.openapi.trader.model.TradingActivities; import okhttp3.OkHttpClient; import static com.google.common.base.Preconditions.checkArgument; @@ -24,6 +26,13 @@ */ public class AlpacaTraderAPI { + // Set validation predicates for 'anyOf' and 'oneOf' models + static { + // 'AccountConfigurationsApi' models + TradingActivities.isValid = jsonElement -> jsonElement.getAsJsonObject().has("type"); + NonTradeActivities.isValid = jsonElement -> !jsonElement.getAsJsonObject().has("type"); + } + private final ApiClient apiClient; private AccountActivitiesApi accountActivities; private AccountConfigurationsApi accountConfigurations; From 5d205a7cfad21a11d7b14e28b47b4b70fd5a3364 Mon Sep 17 00:00:00 2001 From: Jacob Peterson <14127217+Petersoj@users.noreply.github.com> Date: Sun, 10 Mar 2024 20:10:44 -0700 Subject: [PATCH 221/271] Minor update to README --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 0ad65e93..c7f1cef2 100644 --- a/README.md +++ b/README.md @@ -91,7 +91,7 @@ System.out.println("Opening Apple order: " + openingOrder); Thread.sleep(10_000); // Close the Apple position -Order closingOrder = alpacaAPI.trader().positions() +final Order closingOrder = alpacaAPI.trader().positions() .deleteOpenPosition("AAPL", null, new BigDecimal("100")); System.out.println("Closing Apple order: " + openingOrder); From ab2225f88f224cad8a95e39326b97e7f246bddd5 Mon Sep 17 00:00:00 2001 From: Jacob Peterson <14127217+Petersoj@users.noreply.github.com> Date: Tue, 12 Mar 2024 12:10:38 -0700 Subject: [PATCH 222/271] Update websocket `CryptoOrderBookMessage` to add `r` boolean --- README.md | 2 +- .../crypto/model/orderbook/crypto_order_book_message.json | 5 +++++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index c7f1cef2..6f855358 100644 --- a/README.md +++ b/README.md @@ -110,7 +110,7 @@ System.out.println("PnL from Apple trade: " + ``` ## [`Market Data API`](src/main/java/net/jacobpeterson/alpaca/rest/marketdata/AlpacaMarketDataAPI.java) -The [`Market Data API`](src/main/java/net/jacobpeterson/alpaca/rest/marketdata/AlpacaMarketDataAPI.java) is used getting market data for stocks, cryptocurrencies, options, and more. Refer to the [Javadoc](https://javadoc.io/doc/net.jacobpeterson.alpaca/alpaca-java/latest/net/jacobpeterson/alpaca/rest/marketdata/AlpacaMarketDataAPI.html) for a list of all available method signatures. +The [`Market Data API`](src/main/java/net/jacobpeterson/alpaca/rest/marketdata/AlpacaMarketDataAPI.java) is used for getting market data for stocks, cryptocurrencies, options, and more. Refer to the [Javadoc](https://javadoc.io/doc/net.jacobpeterson.alpaca/alpaca-java/latest/net/jacobpeterson/alpaca/rest/marketdata/AlpacaMarketDataAPI.html) for a list of all available method signatures. Example usage: ```java diff --git a/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/streams/crypto/model/orderbook/crypto_order_book_message.json b/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/streams/crypto/model/orderbook/crypto_order_book_message.json index 76c632fa..79814a8a 100644 --- a/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/streams/crypto/model/orderbook/crypto_order_book_message.json +++ b/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/streams/crypto/model/orderbook/crypto_order_book_message.json @@ -23,6 +23,11 @@ "existingJavaType": "java.time.OffsetDateTime", "javaName": "timestamp", "title": "The timestamp with nanosecond precision." + }, + "r": { + "existingJavaType": "java.lang.Boolean", + "javaName": "reset", + "title": "Whether this particular {@link net.jacobpeterson.alpaca.model.websocket.marketdata.streams.crypto.model.orderbook.CryptoOrderBookMessage} contains the whole order book and not just an update. Typically true for the first message and false for subsequent messages." } } } From c8d41b72161c8827dc331fe246c8d6de198b7b3c Mon Sep 17 00:00:00 2001 From: Jacob Peterson <14127217+Petersoj@users.noreply.github.com> Date: Sat, 23 Mar 2024 18:57:10 -0700 Subject: [PATCH 223/271] Minor fixes/changes --- README.md | 2 +- build.gradle | 4 ++-- .../java/net/jacobpeterson/alpaca/util/apikey/APIKeyUtil.java | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 6f855358..e91ae098 100644 --- a/README.md +++ b/README.md @@ -7,7 +7,7 @@

# Overview -This library is a Java client implementation of the Alpaca API. Alpaca lets you trade with algorithms, connect with apps, and build services all with a commission-free trading API for stocks, crypto, and options. This library uses the [Alpaca OpenAPI Specifications](https://docs.alpaca.markets/v1.1/openapi) to generate clients for the REST API with the [OkHttp](https://square.github.io/okhttp/) library, but implements the websocket and SSE streaming interface using a custom implementation with the [OkHttp](https://square.github.io/okhttp/) library. This library is community developed and if you have any questions, please ask them on [Github Discussions](https://github.com/Petersoj/alpaca-java/discussions), the [Alpaca Slack #dev-alpaca-java channel](https://alpaca.markets/slack), or on the [Alpaca Forums](https://forum.alpaca.markets/). This library strives to provide the complete Alpaca API as a Java client implementation, so open a [new issue](https://github.com/Petersoj/alpaca-java/issues) or [new pull request](https://github.com/Petersoj/alpaca-java/pulls) if you find something missing. +This library is a Java client implementation of the [Alpaca](https://alpaca.markets) API. Alpaca lets you trade with algorithms, connect with apps, and build services all with a commission-free trading API for stocks, crypto, and options. This library uses the [Alpaca OpenAPI Specifications](https://docs.alpaca.markets/openapi) to generate clients for the REST API with the [OkHttp](https://square.github.io/okhttp/) library, but implements the websocket and SSE streaming interface using a custom implementation with the [OkHttp](https://square.github.io/okhttp/) library. This library is community developed and if you have any questions, please ask them on [Github Discussions](https://github.com/Petersoj/alpaca-java/discussions), the [Alpaca Slack #dev-alpaca-java channel](https://alpaca.markets/slack), or on the [Alpaca Forums](https://forum.alpaca.markets/). This library strives to provide the complete Alpaca API as a Java client implementation, so open a [new issue](https://github.com/Petersoj/alpaca-java/issues) or [new pull request](https://github.com/Petersoj/alpaca-java/pulls) if you find something missing. Give this repository a star ⭐ if it helped you build a trading algorithm in Java! diff --git a/build.gradle b/build.gradle index 7eadeb3c..6009b4a1 100644 --- a/build.gradle +++ b/build.gradle @@ -87,7 +87,7 @@ javadoc { // BEGIN Alpaca OpenAPI Specification (OAS) client generation // -// These IDs come from the URLs on this page: https://docs.alpaca.markets/v1.1/openapi +// These IDs come from the URLs on this page: https://docs.alpaca.markets/openapi final def specIDsOfFileNames = Map.of( "trader.json", "657760422129f9005cf4bd58", "broker.json", "657771fc2471d20070183049", @@ -107,7 +107,7 @@ def downloadOpenAPISpecFilesTask = tasks.register("downloadOpenAPISpecFiles") { // Download spec files specIDsOfFileNames.forEach { fileName, alpacaSpecID -> final def outputFile = new File(specDownloadPath, fileName) - final def specURL = "https://docs.alpaca.markets/v1.1/openapi/${alpacaSpecID}" + final def specURL = "https://docs.alpaca.markets/openapi/${alpacaSpecID}" outputFile.getParentFile().mkdirs() try { logger.info("Downloading OpenAPI spec file from: {}", specURL) diff --git a/src/main/java/net/jacobpeterson/alpaca/util/apikey/APIKeyUtil.java b/src/main/java/net/jacobpeterson/alpaca/util/apikey/APIKeyUtil.java index 29de58ec..c9d0ce07 100644 --- a/src/main/java/net/jacobpeterson/alpaca/util/apikey/APIKeyUtil.java +++ b/src/main/java/net/jacobpeterson/alpaca/util/apikey/APIKeyUtil.java @@ -15,7 +15,7 @@ public class APIKeyUtil { * * @return the key {@link String} * - * @see Alpaca Docs + * @see Alpaca Docs */ public static String createBrokerAPIAuthKey(String brokerAPIKey, String brokerAPISecret) { return Base64.getEncoder().encodeToString((brokerAPIKey + ":" + brokerAPISecret).getBytes()); From ea620b890c7083da0db4c5a40b3abf1ed1ba294e Mon Sep 17 00:00:00 2001 From: Jacob Peterson <14127217+Petersoj@users.noreply.github.com> Date: Sat, 23 Mar 2024 18:57:55 -0700 Subject: [PATCH 224/271] Update to 10.0.1 --- README.md | 4 ++-- build.gradle | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index e91ae098..fdb5fc2d 100644 --- a/README.md +++ b/README.md @@ -15,7 +15,7 @@ Give this repository a star ⭐ if it helped you build a trading algorithm in Ja If you are using Gradle as your build tool, add the following dependency to your `build.gradle` file: ``` -implementation group: "net.jacobpeterson.alpaca", name: "alpaca-java", version: "10.0.0" +implementation group: "net.jacobpeterson.alpaca", name: "alpaca-java", version: "10.0.1" ``` If you are using Maven as your build tool, add the following dependency to your `pom.xml` file: @@ -24,7 +24,7 @@ If you are using Maven as your build tool, add the following dependency to your net.jacobpeterson.alpaca alpaca-java - 10.0.0 + 10.0.1 ``` diff --git a/build.gradle b/build.gradle index 6009b4a1..5dc0a489 100644 --- a/build.gradle +++ b/build.gradle @@ -22,7 +22,7 @@ plugins { final def projectGroup = "net.jacobpeterson.alpaca" final def projectArtifactID = "alpaca-java" -final def projectVersion = "10.0.0-SNAPSHOT" +final def projectVersion = "10.0.1-SNAPSHOT" group = projectGroup version = projectVersion From a937af46cf1396a217501f597659be7cc11648ed Mon Sep 17 00:00:00 2001 From: Jacob Peterson <14127217+Petersoj@users.noreply.github.com> Date: Sun, 14 Apr 2024 10:19:52 -0700 Subject: [PATCH 225/271] Fix README logo URL, remove unused variable --- README.md | 2 +- .../plugin/jsonschema2pojoadapted/JSONSchemaFileConfig.groovy | 2 -- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/README.md b/README.md index fdb5fc2d..449b8c23 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -

Logo

+

Logo

GitHub Repository Maven Central diff --git a/buildSrc/src/main/groovy/net/jacobpeterson/alpaca/buildsrc/plugin/jsonschema2pojoadapted/JSONSchemaFileConfig.groovy b/buildSrc/src/main/groovy/net/jacobpeterson/alpaca/buildsrc/plugin/jsonschema2pojoadapted/JSONSchemaFileConfig.groovy index 698a30b9..d4dfd8fd 100644 --- a/buildSrc/src/main/groovy/net/jacobpeterson/alpaca/buildsrc/plugin/jsonschema2pojoadapted/JSONSchemaFileConfig.groovy +++ b/buildSrc/src/main/groovy/net/jacobpeterson/alpaca/buildsrc/plugin/jsonschema2pojoadapted/JSONSchemaFileConfig.groovy @@ -7,7 +7,6 @@ class JSONSchemaFileConfig { private File sourceFile private String targetPackage - private String outputFilePath /** * Instantiates a new {@link JSONSchemaFileConfig}. @@ -18,7 +17,6 @@ class JSONSchemaFileConfig { JSONSchemaFileConfig(File sourceFile, String targetPackage) { this.sourceFile = sourceFile this.targetPackage = targetPackage - this.outputFilePath = outputFilePath } File getSourceFile() { From 125c78003d17c3a434085838cc4a06985b3fc354 Mon Sep 17 00:00:00 2001 From: Jacob Peterson <14127217+Petersoj@users.noreply.github.com> Date: Tue, 2 Jul 2024 19:44:37 -0700 Subject: [PATCH 226/271] Minor updates to README --- README.md | 10 +++------- src/main/java/net/jacobpeterson/alpaca/AlpacaAPI.java | 10 +--------- 2 files changed, 4 insertions(+), 16 deletions(-) diff --git a/README.md b/README.md index 449b8c23..a7a4efb9 100644 --- a/README.md +++ b/README.md @@ -7,7 +7,7 @@

# Overview -This library is a Java client implementation of the [Alpaca](https://alpaca.markets) API. Alpaca lets you trade with algorithms, connect with apps, and build services all with a commission-free trading API for stocks, crypto, and options. This library uses the [Alpaca OpenAPI Specifications](https://docs.alpaca.markets/openapi) to generate clients for the REST API with the [OkHttp](https://square.github.io/okhttp/) library, but implements the websocket and SSE streaming interface using a custom implementation with the [OkHttp](https://square.github.io/okhttp/) library. This library is community developed and if you have any questions, please ask them on [Github Discussions](https://github.com/Petersoj/alpaca-java/discussions), the [Alpaca Slack #dev-alpaca-java channel](https://alpaca.markets/slack), or on the [Alpaca Forums](https://forum.alpaca.markets/). This library strives to provide the complete Alpaca API as a Java client implementation, so open a [new issue](https://github.com/Petersoj/alpaca-java/issues) or [new pull request](https://github.com/Petersoj/alpaca-java/pulls) if you find something missing. +This library is a Java client implementation of the [Alpaca](https://alpaca.markets) API. Alpaca lets you trade with algorithms, connect with apps, and build services all with a commission-free trading API for stocks, crypto, and options. This library uses the [Alpaca OpenAPI Specification](https://docs.alpaca.markets/openapi) to generate clients for the REST API with the [OkHttp](https://square.github.io/okhttp/) library, but implements the websocket and SSE streaming interface using a custom implementation with the [OkHttp](https://square.github.io/okhttp/) library. This library is community developed and if you have any questions, please ask them on [Github Discussions](https://github.com/Petersoj/alpaca-java/discussions), the [Alpaca Slack #dev-alpaca-java channel](https://alpaca.markets/slack), or on the [Alpaca Forums](https://forum.alpaca.markets/). This library strives to provide the complete Alpaca API as a Java client implementation, so open a [new issue](https://github.com/Petersoj/alpaca-java/issues) or [new pull request](https://github.com/Petersoj/alpaca-java/pulls) if you find something missing. Give this repository a star ⭐ if it helped you build a trading algorithm in Java! @@ -34,7 +34,7 @@ Note that you don't have to use the Maven Central artifacts. Instead, you can cl For logging, this library uses [SLF4j](http://www.slf4j.org/) which serves as an interface for various logging frameworks. This enables you to use whatever logging framework you would like. However, if you do not add a logging framework as a dependency in your project, the console will output a message stating that SLF4j is defaulting to a no-operation (NOP) logger implementation. To enable logging, add a logging framework of your choice as a dependency to your project such as [Logback](http://logback.qos.ch/), [Log4j 2](http://logging.apache.org/log4j/2.x/index.html), [SLF4j-simple](http://www.slf4j.org/manual.html), or [Apache Commons Logging](https://commons.apache.org/proper/commons-logging/). # `BigDecimal` vs. `Double` -It is generally considered bad practice to represent currency values in floating-point data types such as `float` or `double` because it can lead to [rounding errors](https://ta4j.github.io/ta4j-wiki/Num.html) and [precision loss](https://stackoverflow.com/a/3730040/4352701) in calculations. However, using floating-point data types can have significant performance benefits compared to using arbitrary-precision number data types, especially in a quantitative finance and algorithmic trading environment. Because of this, `alpaca-java` uses the `Double` data type (the `double` boxed type) when using the Market Data APIs and the `BigDecimal` data type when using the Trading or Broker APIs. The thinking behind this is that exact decimal quantities are important when placing real trades with real money, but less important when performing calculations of financial indicators. The best solution would be to use the [TA4j](https://github.com/ta4j/ta4j) `Num` interface so that you can decide what data type to use based on your use case, but that is on the [TODO list](#todo) for now. By the way, using `BigDecimal` wouldn't matter if Alpaca used floating-point data types in their internal systems or in their REST APIs, but the fact that they use `String` data types in some of the REST API JSON responses and their Trading and Broker OpenAPI specifications don't use the `double` data type, this leads me to believe that using `BigDecimal` does actually matter. +It is generally considered bad practice to represent monetary values using floating-point number data types such as `float` or `double` because it can lead to [rounding errors](https://ta4j.github.io/ta4j-wiki/Num.html) and [precision loss](https://stackoverflow.com/a/3730040/4352701). However, using floating-point number data types can have significant performance benefits compared to using fixed-point number data types (via `BigDecimal`), especially in a quantitative finance and algorithmic trading environment. Because of this, `alpaca-java` uses the `Double` data type (the `double` boxed type) when using the Market Data APIs and the `BigDecimal` data type when using the Trading or Broker APIs. The rationale behind this is that exact decimal quantities are important when placing real trades with real money, but less important when performing calculations through technical indicators. The best solution would be to use the [TA4j](https://github.com/ta4j/ta4j) `Num` interface so that you can decide what data type to use based on your use case, but that is on the [TODO list](#todo) for now. By the way, using `BigDecimal` wouldn't matter if Alpaca used floating-point data types in their internal systems, but the fact that they use `String` data types in some of the REST API JSON responses and their Trading and Broker OpenAPI specifications don't use the `double` data type, this leads me to believe that using `BigDecimal` does actually matter. # Examples Note that the examples below are not exhaustive. Refer to the [Javadoc](https://javadoc.io/doc/net.jacobpeterson.alpaca/alpaca-java) for all classes and method signatures. @@ -70,8 +70,6 @@ final BrokerAPIEndpointType endpointType = BrokerAPIEndpointType.SANDBOX; // or final AlpacaAPI alpacaAPI = new AlpacaAPI(brokerAPIKey, brokerAPISecret, endpointType); ``` -Note that this library uses [OkHttp](https://square.github.io/okhttp/) as its HTTP client library which creates background threads to service requests via a connection pool. These threads persist even if the main thread exits, so if you want to destroy these threads when you're done using [`AlpacaAPI`](src/main/java/net/jacobpeterson/alpaca/AlpacaAPI.java), use `alpacaAPI.closeOkHttpClient();`. - ## [`Trader API`](src/main/java/net/jacobpeterson/alpaca/rest/trader/AlpacaTraderAPI.java) The [`Trader API`](src/main/java/net/jacobpeterson/alpaca/rest/trader/AlpacaTraderAPI.java) is used for placing trades, updating account details, getting open positions, and more. Refer to the [Javadoc](https://javadoc.io/doc/net.jacobpeterson.alpaca/alpaca-java/latest/net/jacobpeterson/alpaca/rest/trader/AlpacaTraderAPI.html) for a list of all available method signatures. @@ -168,7 +166,6 @@ Thread.sleep(2000); // Close the SSE stream and the OkHttpClient to exit cleanly sseRequest.close(); -alpacaAPI.closeOkHttpClient(); ``` ## [`Updates Stream`](src/main/java/net/jacobpeterson/alpaca/websocket/updates/UpdatesWebsocket.java) @@ -205,7 +202,6 @@ Thread.sleep(5000); // Disconnect the 'updates' stream and exit cleanly alpacaAPI.updatesStream().disconnect(); -alpacaAPI.closeOkHttpClient(); ``` ## [`Market Data Stream`](src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/MarketDataWebsocket.java) @@ -243,7 +239,6 @@ Thread.sleep(5000); // Disconnect the 'stock market data' stream and exit cleanly alpacaAPI.stockMarketDataStream().disconnect(); -alpacaAPI.closeOkHttpClient(); ``` # Building @@ -258,6 +253,7 @@ To install the built artifacts to your local Maven repository on your machine (t ``` # TODO +- Implement Options market data. - Implement better reconnect logic for Websockets and SSE streaming. - Implement Unit Testing for REST API and Websocket streaming (both live and mocked). - Use [TA4j](https://github.com/ta4j/ta4j) `Num` interface instead of `Double` or `BigDecimal` for number data types so that users can use either `Double` or `BigDecimal` for performance or precision in price data. diff --git a/src/main/java/net/jacobpeterson/alpaca/AlpacaAPI.java b/src/main/java/net/jacobpeterson/alpaca/AlpacaAPI.java index c1c73096..a4da83cf 100644 --- a/src/main/java/net/jacobpeterson/alpaca/AlpacaAPI.java +++ b/src/main/java/net/jacobpeterson/alpaca/AlpacaAPI.java @@ -171,7 +171,7 @@ public AlpacaAPI(String traderKeyID, String traderSecretKey, // Create default OkHttpClient instance if (okHttpClient == null) { - OkHttpClient.Builder clientBuilder = new OkHttpClient.Builder(); + final OkHttpClient.Builder clientBuilder = new OkHttpClient.Builder(); if (LOGGER.isDebugEnabled()) { final HttpLoggingInterceptor loggingInterceptor = new HttpLoggingInterceptor(LOGGER::debug); loggingInterceptor.setLevel(BODY); @@ -182,14 +182,6 @@ public AlpacaAPI(String traderKeyID, String traderSecretKey, this.okHttpClient = okHttpClient; } - /** - * Closes the {@link OkHttpClient}. - */ - public void closeOkHttpClient() { - okHttpClient.dispatcher().executorService().shutdown(); - okHttpClient.connectionPool().evictAll(); - } - /** * Gets the {@link OkHttpClient}. * From d985c839aaa6a645a67858deb6ad5be8d2435a7e Mon Sep 17 00:00:00 2001 From: Jacob Peterson <14127217+Petersoj@users.noreply.github.com> Date: Sun, 26 Jan 2025 18:09:53 -0800 Subject: [PATCH 227/271] Update OpenAPI spec IDs to v1.2, fix `EventsApiSSE` NTA events call --- build.gradle | 16 +++++----------- .../alpaca/rest/broker/events/EventsApiSSE.java | 12 +++++++----- 2 files changed, 12 insertions(+), 16 deletions(-) diff --git a/build.gradle b/build.gradle index 5dc0a489..c3948da4 100644 --- a/build.gradle +++ b/build.gradle @@ -89,9 +89,9 @@ javadoc { // These IDs come from the URLs on this page: https://docs.alpaca.markets/openapi final def specIDsOfFileNames = Map.of( - "trader.json", "657760422129f9005cf4bd58", - "broker.json", "657771fc2471d20070183049", - "market-data.json", "6577704d1fb6b9007032bf39") + "trader.json", "673e19fc6dc876001ff495b2", + "broker.json", "673e1a03a0b2160060120aa0", + "market-data.json", "673e19f7fd59830026ac651b") final File specDownloadPath = new File(project.layout.buildDirectory.get().getAsFile(), "/download/openapi/") final File generatedClientLibrariesPath = new File(project.layout.buildDirectory.get().getAsFile(), @@ -196,7 +196,7 @@ final def fixOpenAPIGeneratedClientIssuesTask = tasks.register("fixOpenAPIGenera } doLast { - // TODO remove these manual fixes once OpenAPI fixes these issues + // TODO remove these manual and hacky fixes once OpenAPI fixes these issues final def sourceFilesPath = "src/main/java/net/jacobpeterson/alpaca/openapi" @@ -250,7 +250,7 @@ final def fixOpenAPIGeneratedClientIssuesTask = tasks.register("fixOpenAPIGenera } } - // Remove the necessary authentication header logic from the generate client's ApiClient + // Remove the necessary authentication header logic from the generate client's 'ApiClient' specIDsOfFileNames.keySet().stream().map { it.replace(".json", "") }.forEach { final def apiClientFile = Paths.get(generatedClientLibrariesPath.getPath(), it, sourceFilesPath, it.replace("-", ""), "ApiClient.java").toFile() @@ -293,20 +293,17 @@ publishing { artifactId = projectArtifactID version = projectVersion from(components["java"]) - pom { name = projectArtifactID description = "A Java API for Alpaca, the commission free, algo friendly, stock trading broker." url = "https://github.com/Petersoj/alpaca-java" inceptionYear = "2018" - licenses { license { name = "MIT License" url = "https://opensource.org/licenses/MIT" } } - developers { developer { id = "Petersoj" @@ -317,7 +314,6 @@ publishing { name = "main(String[] args)" } } - scm { url = "https://github.com/Petersoj/alpaca-java.git" connection = "scm:git:https://github.com/Petersoj/alpaca-java.git" @@ -326,7 +322,6 @@ publishing { } } } - repositories { maven { name = "OSSRH" @@ -339,7 +334,6 @@ publishing { } } } - signing { sign publishing.publications.mavenJava } diff --git a/src/main/java/net/jacobpeterson/alpaca/rest/broker/events/EventsApiSSE.java b/src/main/java/net/jacobpeterson/alpaca/rest/broker/events/EventsApiSSE.java index 65efee8f..2c18b5ff 100644 --- a/src/main/java/net/jacobpeterson/alpaca/rest/broker/events/EventsApiSSE.java +++ b/src/main/java/net/jacobpeterson/alpaca/rest/broker/events/EventsApiSSE.java @@ -6,6 +6,7 @@ import net.jacobpeterson.alpaca.openapi.broker.api.EventsApi; import net.jacobpeterson.alpaca.openapi.broker.model.AccountStatusEvent; import net.jacobpeterson.alpaca.openapi.broker.model.JournalStatusEvent; +import net.jacobpeterson.alpaca.openapi.broker.model.NonTradeActivityEvent; import net.jacobpeterson.alpaca.openapi.broker.model.SubscribeToAdminActionSSE200ResponseInner; import net.jacobpeterson.alpaca.openapi.broker.model.TradeUpdateEvent; import net.jacobpeterson.alpaca.openapi.broker.model.TradeUpdateEventV2; @@ -25,6 +26,7 @@ import java.lang.reflect.Type; import java.time.LocalDate; import java.time.OffsetDateTime; +import java.util.UUID; import static java.util.concurrent.TimeUnit.SECONDS; import static net.jacobpeterson.alpaca.openapi.broker.JSON.getGson; @@ -53,17 +55,17 @@ public EventsApiSSE(ApiClient apiClient) { } /** - * See {@link EventsApi#getV1EventsNta(String, String, String, Integer, Integer, String, String, Boolean)}. + * See {@link EventsApi#getV1EventsNta(String, String, String, Integer, Integer, String, String, Boolean, UUID)}. * * @return a {@link SSERequest} */ public SSERequest subscribeToNonTradingActivitiesEvents(String id, String since, String until, Integer sinceId, - Integer untilId, String sinceUlid, String untilUlid, Boolean includePreprocessing, - SSEListener sseListener) throws ApiException { // TODO OpenAPI response type is broken + Integer untilId, String sinceUlid, String untilUlid, Boolean includePreprocessing, UUID groupId, + SSEListener sseListener) throws ApiException { final Request request = eventsAPI.getV1EventsNtaCall(id, since, until, sinceId, untilId, sinceUlid, untilUlid, - includePreprocessing, null).request(); + includePreprocessing, groupId, null).request(); return new SSERequest(eventSourceFactory.newEventSource(request, createEventSourceListener(sseListener, - new TypeToken() {}.getType()))); // TODO OpenAPI response type is broken + new TypeToken() {}.getType()))); } /** From 1230b41d7b45924a10ffce12c3b9629d88415fc1 Mon Sep 17 00:00:00 2001 From: Jacob Peterson <14127217+Petersoj@users.noreply.github.com> Date: Mon, 1 Sep 2025 12:37:15 -0600 Subject: [PATCH 228/271] Remove `.editorconfig` --- .editorconfig | 1206 ------------------------------------------------- 1 file changed, 1206 deletions(-) delete mode 100644 .editorconfig diff --git a/.editorconfig b/.editorconfig deleted file mode 100644 index e8ed66b1..00000000 --- a/.editorconfig +++ /dev/null @@ -1,1206 +0,0 @@ -[*] -charset = utf-8 -end_of_line = lf -indent_size = 4 -indent_style = space -insert_final_newline = true -max_line_length = 120 -tab_width = 4 -ij_continuation_indent_size = 8 -ij_formatter_off_tag = @formatter:off -ij_formatter_on_tag = @formatter:on -ij_formatter_tags_enabled = true -ij_smart_tabs = false -ij_visual_guides = -ij_wrap_on_typing = false - -[*.css] -ij_css_align_closing_brace_with_properties = false -ij_css_blank_lines_around_nested_selector = 1 -ij_css_blank_lines_between_blocks = 1 -ij_css_block_comment_add_space = false -ij_css_brace_placement = end_of_line -ij_css_enforce_quotes_on_format = false -ij_css_hex_color_long_format = false -ij_css_hex_color_lower_case = false -ij_css_hex_color_short_format = false -ij_css_hex_color_upper_case = false -ij_css_keep_blank_lines_in_code = 2 -ij_css_keep_indents_on_empty_lines = false -ij_css_keep_single_line_blocks = false -ij_css_properties_order = font,font-family,font-size,font-weight,font-style,font-variant,font-size-adjust,font-stretch,line-height,position,z-index,top,right,bottom,left,display,visibility,float,clear,overflow,overflow-x,overflow-y,clip,zoom,align-content,align-items,align-self,flex,flex-flow,flex-basis,flex-direction,flex-grow,flex-shrink,flex-wrap,justify-content,order,box-sizing,width,min-width,max-width,height,min-height,max-height,margin,margin-top,margin-right,margin-bottom,margin-left,padding,padding-top,padding-right,padding-bottom,padding-left,table-layout,empty-cells,caption-side,border-spacing,border-collapse,list-style,list-style-position,list-style-type,list-style-image,content,quotes,counter-reset,counter-increment,resize,cursor,user-select,nav-index,nav-up,nav-right,nav-down,nav-left,transition,transition-delay,transition-timing-function,transition-duration,transition-property,transform,transform-origin,animation,animation-name,animation-duration,animation-play-state,animation-timing-function,animation-delay,animation-iteration-count,animation-direction,text-align,text-align-last,vertical-align,white-space,text-decoration,text-emphasis,text-emphasis-color,text-emphasis-style,text-emphasis-position,text-indent,text-justify,letter-spacing,word-spacing,text-outline,text-transform,text-wrap,text-overflow,text-overflow-ellipsis,text-overflow-mode,word-wrap,word-break,tab-size,hyphens,pointer-events,opacity,color,border,border-width,border-style,border-color,border-top,border-top-width,border-top-style,border-top-color,border-right,border-right-width,border-right-style,border-right-color,border-bottom,border-bottom-width,border-bottom-style,border-bottom-color,border-left,border-left-width,border-left-style,border-left-color,border-radius,border-top-left-radius,border-top-right-radius,border-bottom-right-radius,border-bottom-left-radius,border-image,border-image-source,border-image-slice,border-image-width,border-image-outset,border-image-repeat,outline,outline-width,outline-style,outline-color,outline-offset,background,background-color,background-image,background-repeat,background-attachment,background-position,background-position-x,background-position-y,background-clip,background-origin,background-size,box-decoration-break,box-shadow,text-shadow -ij_css_space_after_colon = true -ij_css_space_before_opening_brace = true -ij_css_use_double_quotes = true -ij_css_value_alignment = do_not_align - -[*.feature] -indent_size = 2 -ij_gherkin_keep_indents_on_empty_lines = false - -[*.java] -ij_java_align_consecutive_assignments = false -ij_java_align_consecutive_variable_declarations = false -ij_java_align_group_field_declarations = false -ij_java_align_multiline_annotation_parameters = false -ij_java_align_multiline_array_initializer_expression = false -ij_java_align_multiline_assignment = false -ij_java_align_multiline_binary_operation = false -ij_java_align_multiline_chained_methods = false -ij_java_align_multiline_deconstruction_list_components = false -ij_java_align_multiline_extends_list = false -ij_java_align_multiline_for = false -ij_java_align_multiline_method_parentheses = false -ij_java_align_multiline_parameters = false -ij_java_align_multiline_parameters_in_calls = false -ij_java_align_multiline_parenthesized_expression = false -ij_java_align_multiline_records = false -ij_java_align_multiline_resources = false -ij_java_align_multiline_ternary_operation = false -ij_java_align_multiline_text_blocks = false -ij_java_align_multiline_throws_list = false -ij_java_align_subsequent_simple_methods = false -ij_java_align_throws_keyword = false -ij_java_align_types_in_multi_catch = false -ij_java_annotation_parameter_wrap = normal -ij_java_array_initializer_new_line_after_left_brace = false -ij_java_array_initializer_right_brace_on_new_line = false -ij_java_array_initializer_wrap = normal -ij_java_assert_statement_colon_on_next_line = false -ij_java_assert_statement_wrap = normal -ij_java_assignment_wrap = normal -ij_java_binary_operation_sign_on_next_line = false -ij_java_binary_operation_wrap = normal -ij_java_blank_lines_after_anonymous_class_header = 0 -ij_java_blank_lines_after_class_header = 1 -ij_java_blank_lines_after_imports = 1 -ij_java_blank_lines_after_package = 1 -ij_java_blank_lines_around_class = 1 -ij_java_blank_lines_around_field = 0 -ij_java_blank_lines_around_field_in_interface = 0 -ij_java_blank_lines_around_initializer = 1 -ij_java_blank_lines_around_method = 1 -ij_java_blank_lines_around_method_in_interface = 1 -ij_java_blank_lines_before_class_end = 0 -ij_java_blank_lines_before_imports = 1 -ij_java_blank_lines_before_method_body = 0 -ij_java_blank_lines_before_package = 0 -ij_java_block_brace_style = end_of_line -ij_java_block_comment_add_space = false -ij_java_block_comment_at_first_column = true -ij_java_builder_methods = -ij_java_call_parameters_new_line_after_left_paren = false -ij_java_call_parameters_right_paren_on_new_line = false -ij_java_call_parameters_wrap = normal -ij_java_case_statement_on_separate_line = true -ij_java_catch_on_new_line = false -ij_java_class_annotation_wrap = normal -ij_java_class_brace_style = end_of_line -ij_java_class_count_to_use_import_on_demand = 999 -ij_java_class_names_in_javadoc = 3 -ij_java_deconstruction_list_wrap = normal -ij_java_do_not_indent_top_level_class_members = false -ij_java_do_not_wrap_after_single_annotation = false -ij_java_do_not_wrap_after_single_annotation_in_parameter = false -ij_java_do_while_brace_force = always -ij_java_doc_add_blank_line_after_description = true -ij_java_doc_add_blank_line_after_param_comments = true -ij_java_doc_add_blank_line_after_return = true -ij_java_doc_add_p_tag_on_empty_lines = true -ij_java_doc_align_exception_comments = true -ij_java_doc_align_param_comments = true -ij_java_doc_do_not_wrap_if_one_line = false -ij_java_doc_enable_formatting = true -ij_java_doc_enable_leading_asterisks = true -ij_java_doc_indent_on_continuation = false -ij_java_doc_keep_empty_lines = true -ij_java_doc_keep_empty_parameter_tag = true -ij_java_doc_keep_empty_return_tag = true -ij_java_doc_keep_empty_throws_tag = true -ij_java_doc_keep_invalid_tags = true -ij_java_doc_param_description_on_new_line = false -ij_java_doc_preserve_line_breaks = false -ij_java_doc_use_throws_not_exception_tag = true -ij_java_else_on_new_line = false -ij_java_entity_dd_prefix = -ij_java_entity_dd_suffix = EJB -ij_java_entity_eb_prefix = -ij_java_entity_eb_suffix = Bean -ij_java_entity_hi_prefix = -ij_java_entity_hi_suffix = Home -ij_java_entity_lhi_prefix = Local -ij_java_entity_lhi_suffix = Home -ij_java_entity_li_prefix = Local -ij_java_entity_li_suffix = -ij_java_entity_pk_class = java.lang.String -ij_java_entity_ri_prefix = -ij_java_entity_ri_suffix = -ij_java_entity_vo_prefix = -ij_java_entity_vo_suffix = VO -ij_java_enum_constants_wrap = split_into_lines -ij_java_extends_keyword_wrap = normal -ij_java_extends_list_wrap = normal -ij_java_field_annotation_wrap = normal -ij_java_field_name_prefix = -ij_java_field_name_suffix = -ij_java_filter_class_prefix = -ij_java_filter_class_suffix = -ij_java_filter_dd_prefix = -ij_java_filter_dd_suffix = -ij_java_finally_on_new_line = false -ij_java_for_brace_force = always -ij_java_for_statement_new_line_after_left_paren = false -ij_java_for_statement_right_paren_on_new_line = false -ij_java_for_statement_wrap = normal -ij_java_generate_final_locals = false -ij_java_generate_final_parameters = false -ij_java_if_brace_force = always -ij_java_imports_layout = *,|,javax.**,java.**,|,$* -ij_java_indent_case_from_switch = true -ij_java_insert_inner_class_imports = false -ij_java_insert_override_annotation = true -ij_java_keep_blank_lines_before_right_brace = 1 -ij_java_keep_blank_lines_between_package_declaration_and_header = 1 -ij_java_keep_blank_lines_in_code = 1 -ij_java_keep_blank_lines_in_declarations = 1 -ij_java_keep_builder_methods_indents = false -ij_java_keep_control_statement_in_one_line = true -ij_java_keep_first_column_comment = true -ij_java_keep_indents_on_empty_lines = false -ij_java_keep_line_breaks = true -ij_java_keep_multiple_expressions_in_one_line = true -ij_java_keep_simple_blocks_in_one_line = true -ij_java_keep_simple_classes_in_one_line = true -ij_java_keep_simple_lambdas_in_one_line = true -ij_java_keep_simple_methods_in_one_line = true -ij_java_label_indent_absolute = false -ij_java_label_indent_size = 0 -ij_java_lambda_brace_style = end_of_line -ij_java_layout_static_imports_separately = true -ij_java_line_comment_add_space = false -ij_java_line_comment_add_space_on_reformat = false -ij_java_line_comment_at_first_column = true -ij_java_listener_class_prefix = -ij_java_listener_class_suffix = -ij_java_local_variable_name_prefix = -ij_java_local_variable_name_suffix = -ij_java_message_dd_prefix = -ij_java_message_dd_suffix = EJB -ij_java_message_eb_prefix = -ij_java_message_eb_suffix = Bean -ij_java_method_annotation_wrap = normal -ij_java_method_brace_style = end_of_line -ij_java_method_call_chain_wrap = normal -ij_java_method_parameters_new_line_after_left_paren = false -ij_java_method_parameters_right_paren_on_new_line = false -ij_java_method_parameters_wrap = normal -ij_java_modifier_list_wrap = false -ij_java_multi_catch_types_wrap = normal -ij_java_names_count_to_use_import_on_demand = 999 -ij_java_new_line_after_lparen_in_annotation = false -ij_java_new_line_after_lparen_in_deconstruction_pattern = false -ij_java_new_line_after_lparen_in_record_header = false -ij_java_packages_to_use_import_on_demand = -ij_java_parameter_annotation_wrap = normal -ij_java_parameter_name_prefix = -ij_java_parameter_name_suffix = -ij_java_parentheses_expression_new_line_after_left_paren = false -ij_java_parentheses_expression_right_paren_on_new_line = false -ij_java_place_assignment_sign_on_next_line = false -ij_java_prefer_longer_names = true -ij_java_prefer_parameters_wrap = false -ij_java_record_components_wrap = normal -ij_java_repeat_annotations = -ij_java_repeat_synchronized = true -ij_java_replace_instanceof_and_cast = false -ij_java_replace_null_check = true -ij_java_replace_sum_lambda_with_method_ref = true -ij_java_resource_list_new_line_after_left_paren = false -ij_java_resource_list_right_paren_on_new_line = false -ij_java_resource_list_wrap = normal -ij_java_rparen_on_new_line_in_annotation = false -ij_java_rparen_on_new_line_in_deconstruction_pattern = false -ij_java_rparen_on_new_line_in_record_header = false -ij_java_servlet_class_prefix = -ij_java_servlet_class_suffix = -ij_java_servlet_dd_prefix = -ij_java_servlet_dd_suffix = -ij_java_session_dd_prefix = -ij_java_session_dd_suffix = EJB -ij_java_session_eb_prefix = -ij_java_session_eb_suffix = Bean -ij_java_session_hi_prefix = -ij_java_session_hi_suffix = Home -ij_java_session_lhi_prefix = Local -ij_java_session_lhi_suffix = Home -ij_java_session_li_prefix = Local -ij_java_session_li_suffix = -ij_java_session_ri_prefix = -ij_java_session_ri_suffix = -ij_java_session_si_prefix = -ij_java_session_si_suffix = Service -ij_java_space_after_closing_angle_bracket_in_type_argument = false -ij_java_space_after_colon = true -ij_java_space_after_comma = true -ij_java_space_after_comma_in_type_arguments = true -ij_java_space_after_for_semicolon = true -ij_java_space_after_quest = true -ij_java_space_after_type_cast = true -ij_java_space_before_annotation_array_initializer_left_brace = false -ij_java_space_before_annotation_parameter_list = false -ij_java_space_before_array_initializer_left_brace = false -ij_java_space_before_catch_keyword = true -ij_java_space_before_catch_left_brace = true -ij_java_space_before_catch_parentheses = true -ij_java_space_before_class_left_brace = true -ij_java_space_before_colon = true -ij_java_space_before_colon_in_foreach = true -ij_java_space_before_comma = false -ij_java_space_before_deconstruction_list = false -ij_java_space_before_do_left_brace = true -ij_java_space_before_else_keyword = true -ij_java_space_before_else_left_brace = true -ij_java_space_before_finally_keyword = true -ij_java_space_before_finally_left_brace = true -ij_java_space_before_for_left_brace = true -ij_java_space_before_for_parentheses = true -ij_java_space_before_for_semicolon = false -ij_java_space_before_if_left_brace = true -ij_java_space_before_if_parentheses = true -ij_java_space_before_method_call_parentheses = false -ij_java_space_before_method_left_brace = true -ij_java_space_before_method_parentheses = false -ij_java_space_before_opening_angle_bracket_in_type_parameter = false -ij_java_space_before_quest = true -ij_java_space_before_switch_left_brace = true -ij_java_space_before_switch_parentheses = true -ij_java_space_before_synchronized_left_brace = true -ij_java_space_before_synchronized_parentheses = true -ij_java_space_before_try_left_brace = true -ij_java_space_before_try_parentheses = true -ij_java_space_before_type_parameter_list = false -ij_java_space_before_while_keyword = true -ij_java_space_before_while_left_brace = true -ij_java_space_before_while_parentheses = true -ij_java_space_inside_one_line_enum_braces = false -ij_java_space_within_empty_array_initializer_braces = false -ij_java_space_within_empty_method_call_parentheses = false -ij_java_space_within_empty_method_parentheses = false -ij_java_spaces_around_additive_operators = true -ij_java_spaces_around_annotation_eq = true -ij_java_spaces_around_assignment_operators = true -ij_java_spaces_around_bitwise_operators = true -ij_java_spaces_around_equality_operators = true -ij_java_spaces_around_lambda_arrow = true -ij_java_spaces_around_logical_operators = true -ij_java_spaces_around_method_ref_dbl_colon = false -ij_java_spaces_around_multiplicative_operators = true -ij_java_spaces_around_relational_operators = true -ij_java_spaces_around_shift_operators = true -ij_java_spaces_around_type_bounds_in_type_parameters = true -ij_java_spaces_around_unary_operator = false -ij_java_spaces_within_angle_brackets = false -ij_java_spaces_within_annotation_parentheses = false -ij_java_spaces_within_array_initializer_braces = false -ij_java_spaces_within_braces = false -ij_java_spaces_within_brackets = false -ij_java_spaces_within_cast_parentheses = false -ij_java_spaces_within_catch_parentheses = false -ij_java_spaces_within_deconstruction_list = false -ij_java_spaces_within_for_parentheses = false -ij_java_spaces_within_if_parentheses = false -ij_java_spaces_within_method_call_parentheses = false -ij_java_spaces_within_method_parentheses = false -ij_java_spaces_within_parentheses = false -ij_java_spaces_within_record_header = false -ij_java_spaces_within_switch_parentheses = false -ij_java_spaces_within_synchronized_parentheses = false -ij_java_spaces_within_try_parentheses = false -ij_java_spaces_within_while_parentheses = false -ij_java_special_else_if_treatment = true -ij_java_static_field_name_prefix = -ij_java_static_field_name_suffix = -ij_java_subclass_name_prefix = -ij_java_subclass_name_suffix = Impl -ij_java_ternary_operation_signs_on_next_line = false -ij_java_ternary_operation_wrap = normal -ij_java_test_name_prefix = -ij_java_test_name_suffix = Test -ij_java_throws_keyword_wrap = normal -ij_java_throws_list_wrap = normal -ij_java_use_external_annotations = false -ij_java_use_fq_class_names = false -ij_java_use_relative_indents = false -ij_java_use_single_class_imports = true -ij_java_variable_annotation_wrap = normal -ij_java_visibility = public -ij_java_while_brace_force = always -ij_java_while_on_new_line = false -ij_java_wrap_comments = true -ij_java_wrap_first_method_in_call_chain = false -ij_java_wrap_long_lines = false - -[*.less] -indent_size = 2 -ij_less_align_closing_brace_with_properties = false -ij_less_blank_lines_around_nested_selector = 1 -ij_less_blank_lines_between_blocks = 1 -ij_less_block_comment_add_space = false -ij_less_brace_placement = 0 -ij_less_enforce_quotes_on_format = false -ij_less_hex_color_long_format = false -ij_less_hex_color_lower_case = false -ij_less_hex_color_short_format = false -ij_less_hex_color_upper_case = false -ij_less_keep_blank_lines_in_code = 2 -ij_less_keep_indents_on_empty_lines = false -ij_less_keep_single_line_blocks = false -ij_less_line_comment_add_space = false -ij_less_line_comment_at_first_column = false -ij_less_properties_order = font,font-family,font-size,font-weight,font-style,font-variant,font-size-adjust,font-stretch,line-height,position,z-index,top,right,bottom,left,display,visibility,float,clear,overflow,overflow-x,overflow-y,clip,zoom,align-content,align-items,align-self,flex,flex-flow,flex-basis,flex-direction,flex-grow,flex-shrink,flex-wrap,justify-content,order,box-sizing,width,min-width,max-width,height,min-height,max-height,margin,margin-top,margin-right,margin-bottom,margin-left,padding,padding-top,padding-right,padding-bottom,padding-left,table-layout,empty-cells,caption-side,border-spacing,border-collapse,list-style,list-style-position,list-style-type,list-style-image,content,quotes,counter-reset,counter-increment,resize,cursor,user-select,nav-index,nav-up,nav-right,nav-down,nav-left,transition,transition-delay,transition-timing-function,transition-duration,transition-property,transform,transform-origin,animation,animation-name,animation-duration,animation-play-state,animation-timing-function,animation-delay,animation-iteration-count,animation-direction,text-align,text-align-last,vertical-align,white-space,text-decoration,text-emphasis,text-emphasis-color,text-emphasis-style,text-emphasis-position,text-indent,text-justify,letter-spacing,word-spacing,text-outline,text-transform,text-wrap,text-overflow,text-overflow-ellipsis,text-overflow-mode,word-wrap,word-break,tab-size,hyphens,pointer-events,opacity,color,border,border-width,border-style,border-color,border-top,border-top-width,border-top-style,border-top-color,border-right,border-right-width,border-right-style,border-right-color,border-bottom,border-bottom-width,border-bottom-style,border-bottom-color,border-left,border-left-width,border-left-style,border-left-color,border-radius,border-top-left-radius,border-top-right-radius,border-bottom-right-radius,border-bottom-left-radius,border-image,border-image-source,border-image-slice,border-image-width,border-image-outset,border-image-repeat,outline,outline-width,outline-style,outline-color,outline-offset,background,background-color,background-image,background-repeat,background-attachment,background-position,background-position-x,background-position-y,background-clip,background-origin,background-size,box-decoration-break,box-shadow,text-shadow -ij_less_space_after_colon = true -ij_less_space_before_opening_brace = true -ij_less_use_double_quotes = true -ij_less_value_alignment = 0 - -[*.proto] -indent_size = 2 -tab_width = 2 -ij_continuation_indent_size = 4 -ij_protobuf_keep_blank_lines_in_code = 2 -ij_protobuf_keep_indents_on_empty_lines = false -ij_protobuf_keep_line_breaks = true -ij_protobuf_space_after_comma = true -ij_protobuf_space_before_comma = false -ij_protobuf_spaces_around_assignment_operators = true -ij_protobuf_spaces_within_braces = false -ij_protobuf_spaces_within_brackets = false - -[*.sass] -indent_size = 2 -ij_sass_align_closing_brace_with_properties = false -ij_sass_blank_lines_around_nested_selector = 1 -ij_sass_blank_lines_between_blocks = 1 -ij_sass_brace_placement = 0 -ij_sass_enforce_quotes_on_format = false -ij_sass_hex_color_long_format = false -ij_sass_hex_color_lower_case = false -ij_sass_hex_color_short_format = false -ij_sass_hex_color_upper_case = false -ij_sass_keep_blank_lines_in_code = 2 -ij_sass_keep_indents_on_empty_lines = false -ij_sass_keep_single_line_blocks = false -ij_sass_line_comment_add_space = false -ij_sass_line_comment_at_first_column = false -ij_sass_properties_order = font,font-family,font-size,font-weight,font-style,font-variant,font-size-adjust,font-stretch,line-height,position,z-index,top,right,bottom,left,display,visibility,float,clear,overflow,overflow-x,overflow-y,clip,zoom,align-content,align-items,align-self,flex,flex-flow,flex-basis,flex-direction,flex-grow,flex-shrink,flex-wrap,justify-content,order,box-sizing,width,min-width,max-width,height,min-height,max-height,margin,margin-top,margin-right,margin-bottom,margin-left,padding,padding-top,padding-right,padding-bottom,padding-left,table-layout,empty-cells,caption-side,border-spacing,border-collapse,list-style,list-style-position,list-style-type,list-style-image,content,quotes,counter-reset,counter-increment,resize,cursor,user-select,nav-index,nav-up,nav-right,nav-down,nav-left,transition,transition-delay,transition-timing-function,transition-duration,transition-property,transform,transform-origin,animation,animation-name,animation-duration,animation-play-state,animation-timing-function,animation-delay,animation-iteration-count,animation-direction,text-align,text-align-last,vertical-align,white-space,text-decoration,text-emphasis,text-emphasis-color,text-emphasis-style,text-emphasis-position,text-indent,text-justify,letter-spacing,word-spacing,text-outline,text-transform,text-wrap,text-overflow,text-overflow-ellipsis,text-overflow-mode,word-wrap,word-break,tab-size,hyphens,pointer-events,opacity,color,border,border-width,border-style,border-color,border-top,border-top-width,border-top-style,border-top-color,border-right,border-right-width,border-right-style,border-right-color,border-bottom,border-bottom-width,border-bottom-style,border-bottom-color,border-left,border-left-width,border-left-style,border-left-color,border-radius,border-top-left-radius,border-top-right-radius,border-bottom-right-radius,border-bottom-left-radius,border-image,border-image-source,border-image-slice,border-image-width,border-image-outset,border-image-repeat,outline,outline-width,outline-style,outline-color,outline-offset,background,background-color,background-image,background-repeat,background-attachment,background-position,background-position-x,background-position-y,background-clip,background-origin,background-size,box-decoration-break,box-shadow,text-shadow -ij_sass_space_after_colon = true -ij_sass_space_before_opening_brace = true -ij_sass_use_double_quotes = true -ij_sass_value_alignment = 0 - -[*.scss] -indent_size = 2 -ij_scss_align_closing_brace_with_properties = false -ij_scss_blank_lines_around_nested_selector = 1 -ij_scss_blank_lines_between_blocks = 1 -ij_scss_block_comment_add_space = false -ij_scss_brace_placement = 0 -ij_scss_enforce_quotes_on_format = false -ij_scss_hex_color_long_format = false -ij_scss_hex_color_lower_case = false -ij_scss_hex_color_short_format = false -ij_scss_hex_color_upper_case = false -ij_scss_keep_blank_lines_in_code = 2 -ij_scss_keep_indents_on_empty_lines = false -ij_scss_keep_single_line_blocks = false -ij_scss_line_comment_add_space = false -ij_scss_line_comment_at_first_column = false -ij_scss_properties_order = font,font-family,font-size,font-weight,font-style,font-variant,font-size-adjust,font-stretch,line-height,position,z-index,top,right,bottom,left,display,visibility,float,clear,overflow,overflow-x,overflow-y,clip,zoom,align-content,align-items,align-self,flex,flex-flow,flex-basis,flex-direction,flex-grow,flex-shrink,flex-wrap,justify-content,order,box-sizing,width,min-width,max-width,height,min-height,max-height,margin,margin-top,margin-right,margin-bottom,margin-left,padding,padding-top,padding-right,padding-bottom,padding-left,table-layout,empty-cells,caption-side,border-spacing,border-collapse,list-style,list-style-position,list-style-type,list-style-image,content,quotes,counter-reset,counter-increment,resize,cursor,user-select,nav-index,nav-up,nav-right,nav-down,nav-left,transition,transition-delay,transition-timing-function,transition-duration,transition-property,transform,transform-origin,animation,animation-name,animation-duration,animation-play-state,animation-timing-function,animation-delay,animation-iteration-count,animation-direction,text-align,text-align-last,vertical-align,white-space,text-decoration,text-emphasis,text-emphasis-color,text-emphasis-style,text-emphasis-position,text-indent,text-justify,letter-spacing,word-spacing,text-outline,text-transform,text-wrap,text-overflow,text-overflow-ellipsis,text-overflow-mode,word-wrap,word-break,tab-size,hyphens,pointer-events,opacity,color,border,border-width,border-style,border-color,border-top,border-top-width,border-top-style,border-top-color,border-right,border-right-width,border-right-style,border-right-color,border-bottom,border-bottom-width,border-bottom-style,border-bottom-color,border-left,border-left-width,border-left-style,border-left-color,border-radius,border-top-left-radius,border-top-right-radius,border-bottom-right-radius,border-bottom-left-radius,border-image,border-image-source,border-image-slice,border-image-width,border-image-outset,border-image-repeat,outline,outline-width,outline-style,outline-color,outline-offset,background,background-color,background-image,background-repeat,background-attachment,background-position,background-position-x,background-position-y,background-clip,background-origin,background-size,box-decoration-break,box-shadow,text-shadow -ij_scss_space_after_colon = true -ij_scss_space_before_opening_brace = true -ij_scss_use_double_quotes = true -ij_scss_value_alignment = 0 - -[*.vue] -indent_size = 2 -tab_width = 2 -ij_continuation_indent_size = 4 -ij_vue_indent_children_of_top_level = template -ij_vue_interpolation_new_line_after_start_delimiter = true -ij_vue_interpolation_new_line_before_end_delimiter = true -ij_vue_interpolation_wrap = off -ij_vue_keep_indents_on_empty_lines = false -ij_vue_spaces_within_interpolation_expressions = true - -[.editorconfig] -ij_editorconfig_align_group_field_declarations = false -ij_editorconfig_space_after_colon = false -ij_editorconfig_space_after_comma = true -ij_editorconfig_space_before_colon = false -ij_editorconfig_space_before_comma = false -ij_editorconfig_spaces_around_assignment_operators = true - -[{*.ant,*.fxml,*.jhm,*.jnlp,*.jrxml,*.pom,*.rng,*.tld,*.wadl,*.wsdl,*.xml,*.xsd,*.xsl,*.xslt,*.xul}] -ij_xml_align_attributes = true -ij_xml_align_text = false -ij_xml_attribute_wrap = normal -ij_xml_block_comment_add_space = false -ij_xml_block_comment_at_first_column = true -ij_xml_keep_blank_lines = 2 -ij_xml_keep_indents_on_empty_lines = false -ij_xml_keep_line_breaks = true -ij_xml_keep_line_breaks_in_text = true -ij_xml_keep_whitespaces = false -ij_xml_keep_whitespaces_around_cdata = preserve -ij_xml_keep_whitespaces_inside_cdata = false -ij_xml_line_comment_at_first_column = true -ij_xml_space_after_tag_name = false -ij_xml_space_around_equals_in_attribute = false -ij_xml_space_inside_empty_tag = false -ij_xml_text_wrap = normal -ij_xml_use_custom_settings = false - -[{*.ats,*.cts,*.mts,*.ts}] -ij_typescript_align_imports = false -ij_typescript_align_multiline_array_initializer_expression = false -ij_typescript_align_multiline_binary_operation = false -ij_typescript_align_multiline_chained_methods = false -ij_typescript_align_multiline_extends_list = false -ij_typescript_align_multiline_for = false -ij_typescript_align_multiline_parameters = false -ij_typescript_align_multiline_parameters_in_calls = false -ij_typescript_align_multiline_ternary_operation = false -ij_typescript_align_object_properties = 0 -ij_typescript_align_union_types = false -ij_typescript_align_var_statements = 0 -ij_typescript_array_initializer_new_line_after_left_brace = false -ij_typescript_array_initializer_right_brace_on_new_line = false -ij_typescript_array_initializer_wrap = normal -ij_typescript_assignment_wrap = normal -ij_typescript_binary_operation_sign_on_next_line = false -ij_typescript_binary_operation_wrap = normal -ij_typescript_blacklist_imports = rxjs/Rx,node_modules/**,**/node_modules/**,@angular/material,@angular/material/typings/** -ij_typescript_blank_lines_after_imports = 1 -ij_typescript_blank_lines_around_class = 1 -ij_typescript_blank_lines_around_field = 0 -ij_typescript_blank_lines_around_field_in_interface = 0 -ij_typescript_blank_lines_around_function = 1 -ij_typescript_blank_lines_around_method = 1 -ij_typescript_blank_lines_around_method_in_interface = 1 -ij_typescript_block_brace_style = end_of_line -ij_typescript_block_comment_add_space = false -ij_typescript_block_comment_at_first_column = true -ij_typescript_call_parameters_new_line_after_left_paren = false -ij_typescript_call_parameters_right_paren_on_new_line = false -ij_typescript_call_parameters_wrap = normal -ij_typescript_catch_on_new_line = false -ij_typescript_chained_call_dot_on_new_line = true -ij_typescript_class_brace_style = end_of_line -ij_typescript_comma_on_new_line = false -ij_typescript_do_while_brace_force = always -ij_typescript_else_on_new_line = false -ij_typescript_enforce_trailing_comma = remove -ij_typescript_enum_constants_wrap = split_into_lines -ij_typescript_extends_keyword_wrap = normal -ij_typescript_extends_list_wrap = normal -ij_typescript_field_prefix = _ -ij_typescript_file_name_style = relaxed -ij_typescript_finally_on_new_line = false -ij_typescript_for_brace_force = always -ij_typescript_for_statement_new_line_after_left_paren = false -ij_typescript_for_statement_right_paren_on_new_line = false -ij_typescript_for_statement_wrap = normal -ij_typescript_force_quote_style = true -ij_typescript_force_semicolon_style = true -ij_typescript_function_expression_brace_style = end_of_line -ij_typescript_if_brace_force = always -ij_typescript_import_merge_members = global -ij_typescript_import_prefer_absolute_path = global -ij_typescript_import_sort_members = true -ij_typescript_import_sort_module_name = false -ij_typescript_import_use_node_resolution = true -ij_typescript_imports_wrap = on_every_item -ij_typescript_indent_case_from_switch = true -ij_typescript_indent_chained_calls = true -ij_typescript_indent_package_children = 0 -ij_typescript_jsdoc_include_types = false -ij_typescript_jsx_attribute_value = braces -ij_typescript_keep_blank_lines_in_code = 1 -ij_typescript_keep_first_column_comment = true -ij_typescript_keep_indents_on_empty_lines = false -ij_typescript_keep_line_breaks = true -ij_typescript_keep_simple_blocks_in_one_line = true -ij_typescript_keep_simple_methods_in_one_line = true -ij_typescript_line_comment_add_space = true -ij_typescript_line_comment_at_first_column = false -ij_typescript_method_brace_style = end_of_line -ij_typescript_method_call_chain_wrap = normal -ij_typescript_method_parameters_new_line_after_left_paren = false -ij_typescript_method_parameters_right_paren_on_new_line = false -ij_typescript_method_parameters_wrap = normal -ij_typescript_object_literal_wrap = on_every_item -ij_typescript_object_types_wrap = on_every_item -ij_typescript_parentheses_expression_new_line_after_left_paren = false -ij_typescript_parentheses_expression_right_paren_on_new_line = false -ij_typescript_place_assignment_sign_on_next_line = false -ij_typescript_prefer_as_type_cast = false -ij_typescript_prefer_explicit_types_function_expression_returns = false -ij_typescript_prefer_explicit_types_function_returns = false -ij_typescript_prefer_explicit_types_vars_fields = false -ij_typescript_prefer_parameters_wrap = false -ij_typescript_property_prefix = -ij_typescript_reformat_c_style_comments = false -ij_typescript_space_after_colon = true -ij_typescript_space_after_comma = true -ij_typescript_space_after_dots_in_rest_parameter = false -ij_typescript_space_after_generator_mult = true -ij_typescript_space_after_property_colon = true -ij_typescript_space_after_quest = true -ij_typescript_space_after_type_colon = true -ij_typescript_space_after_unary_not = false -ij_typescript_space_before_async_arrow_lparen = true -ij_typescript_space_before_catch_keyword = true -ij_typescript_space_before_catch_left_brace = true -ij_typescript_space_before_catch_parentheses = true -ij_typescript_space_before_class_lbrace = true -ij_typescript_space_before_class_left_brace = true -ij_typescript_space_before_colon = true -ij_typescript_space_before_comma = false -ij_typescript_space_before_do_left_brace = true -ij_typescript_space_before_else_keyword = true -ij_typescript_space_before_else_left_brace = true -ij_typescript_space_before_finally_keyword = true -ij_typescript_space_before_finally_left_brace = true -ij_typescript_space_before_for_left_brace = true -ij_typescript_space_before_for_parentheses = true -ij_typescript_space_before_for_semicolon = false -ij_typescript_space_before_function_left_parenth = true -ij_typescript_space_before_generator_mult = false -ij_typescript_space_before_if_left_brace = true -ij_typescript_space_before_if_parentheses = true -ij_typescript_space_before_method_call_parentheses = false -ij_typescript_space_before_method_left_brace = true -ij_typescript_space_before_method_parentheses = false -ij_typescript_space_before_property_colon = false -ij_typescript_space_before_quest = true -ij_typescript_space_before_switch_left_brace = true -ij_typescript_space_before_switch_parentheses = true -ij_typescript_space_before_try_left_brace = true -ij_typescript_space_before_type_colon = false -ij_typescript_space_before_unary_not = false -ij_typescript_space_before_while_keyword = true -ij_typescript_space_before_while_left_brace = true -ij_typescript_space_before_while_parentheses = true -ij_typescript_spaces_around_additive_operators = true -ij_typescript_spaces_around_arrow_function_operator = true -ij_typescript_spaces_around_assignment_operators = true -ij_typescript_spaces_around_bitwise_operators = true -ij_typescript_spaces_around_equality_operators = true -ij_typescript_spaces_around_logical_operators = true -ij_typescript_spaces_around_multiplicative_operators = true -ij_typescript_spaces_around_relational_operators = true -ij_typescript_spaces_around_shift_operators = true -ij_typescript_spaces_around_unary_operator = false -ij_typescript_spaces_within_array_initializer_brackets = false -ij_typescript_spaces_within_brackets = false -ij_typescript_spaces_within_catch_parentheses = false -ij_typescript_spaces_within_for_parentheses = false -ij_typescript_spaces_within_if_parentheses = false -ij_typescript_spaces_within_imports = false -ij_typescript_spaces_within_interpolation_expressions = false -ij_typescript_spaces_within_method_call_parentheses = false -ij_typescript_spaces_within_method_parentheses = false -ij_typescript_spaces_within_object_literal_braces = false -ij_typescript_spaces_within_object_type_braces = true -ij_typescript_spaces_within_parentheses = false -ij_typescript_spaces_within_switch_parentheses = false -ij_typescript_spaces_within_type_assertion = false -ij_typescript_spaces_within_union_types = true -ij_typescript_spaces_within_while_parentheses = false -ij_typescript_special_else_if_treatment = true -ij_typescript_ternary_operation_signs_on_next_line = false -ij_typescript_ternary_operation_wrap = normal -ij_typescript_union_types_wrap = on_every_item -ij_typescript_use_chained_calls_group_indents = false -ij_typescript_use_double_quotes = true -ij_typescript_use_explicit_js_extension = auto -ij_typescript_use_path_mapping = always -ij_typescript_use_public_modifier = false -ij_typescript_use_semicolon_after_statement = true -ij_typescript_var_declaration_wrap = normal -ij_typescript_while_brace_force = always -ij_typescript_while_on_new_line = false -ij_typescript_wrap_comments = true - -[{*.bash,*.sh,*.zsh}] -indent_size = 2 -tab_width = 2 -ij_shell_binary_ops_start_line = false -ij_shell_keep_column_alignment_padding = false -ij_shell_minify_program = false -ij_shell_redirect_followed_by_space = false -ij_shell_switch_cases_indented = false -ij_shell_use_unix_line_separator = true - -[{*.cjs,*.js}] -ij_javascript_align_imports = false -ij_javascript_align_multiline_array_initializer_expression = false -ij_javascript_align_multiline_binary_operation = false -ij_javascript_align_multiline_chained_methods = false -ij_javascript_align_multiline_extends_list = false -ij_javascript_align_multiline_for = false -ij_javascript_align_multiline_parameters = false -ij_javascript_align_multiline_parameters_in_calls = false -ij_javascript_align_multiline_ternary_operation = false -ij_javascript_align_object_properties = 0 -ij_javascript_align_union_types = false -ij_javascript_align_var_statements = 0 -ij_javascript_array_initializer_new_line_after_left_brace = false -ij_javascript_array_initializer_right_brace_on_new_line = false -ij_javascript_array_initializer_wrap = normal -ij_javascript_assignment_wrap = normal -ij_javascript_binary_operation_sign_on_next_line = false -ij_javascript_binary_operation_wrap = normal -ij_javascript_blacklist_imports = rxjs/Rx,node_modules/**,**/node_modules/**,@angular/material,@angular/material/typings/** -ij_javascript_blank_lines_after_imports = 1 -ij_javascript_blank_lines_around_class = 1 -ij_javascript_blank_lines_around_field = 0 -ij_javascript_blank_lines_around_function = 1 -ij_javascript_blank_lines_around_method = 1 -ij_javascript_block_brace_style = end_of_line -ij_javascript_block_comment_add_space = false -ij_javascript_block_comment_at_first_column = true -ij_javascript_call_parameters_new_line_after_left_paren = false -ij_javascript_call_parameters_right_paren_on_new_line = false -ij_javascript_call_parameters_wrap = normal -ij_javascript_catch_on_new_line = false -ij_javascript_chained_call_dot_on_new_line = true -ij_javascript_class_brace_style = end_of_line -ij_javascript_comma_on_new_line = false -ij_javascript_do_while_brace_force = always -ij_javascript_else_on_new_line = false -ij_javascript_enforce_trailing_comma = remove -ij_javascript_extends_keyword_wrap = normal -ij_javascript_extends_list_wrap = normal -ij_javascript_field_prefix = _ -ij_javascript_file_name_style = relaxed -ij_javascript_finally_on_new_line = false -ij_javascript_for_brace_force = always -ij_javascript_for_statement_new_line_after_left_paren = false -ij_javascript_for_statement_right_paren_on_new_line = false -ij_javascript_for_statement_wrap = normal -ij_javascript_force_quote_style = true -ij_javascript_force_semicolon_style = true -ij_javascript_function_expression_brace_style = end_of_line -ij_javascript_if_brace_force = always -ij_javascript_import_merge_members = global -ij_javascript_import_prefer_absolute_path = global -ij_javascript_import_sort_members = true -ij_javascript_import_sort_module_name = false -ij_javascript_import_use_node_resolution = true -ij_javascript_imports_wrap = normal -ij_javascript_indent_case_from_switch = true -ij_javascript_indent_chained_calls = true -ij_javascript_indent_package_children = 0 -ij_javascript_jsx_attribute_value = braces -ij_javascript_keep_blank_lines_in_code = 1 -ij_javascript_keep_first_column_comment = true -ij_javascript_keep_indents_on_empty_lines = false -ij_javascript_keep_line_breaks = true -ij_javascript_keep_simple_blocks_in_one_line = true -ij_javascript_keep_simple_methods_in_one_line = true -ij_javascript_line_comment_add_space = true -ij_javascript_line_comment_at_first_column = false -ij_javascript_method_brace_style = end_of_line -ij_javascript_method_call_chain_wrap = normal -ij_javascript_method_parameters_new_line_after_left_paren = false -ij_javascript_method_parameters_right_paren_on_new_line = false -ij_javascript_method_parameters_wrap = normal -ij_javascript_object_literal_wrap = on_every_item -ij_javascript_object_types_wrap = on_every_item -ij_javascript_parentheses_expression_new_line_after_left_paren = false -ij_javascript_parentheses_expression_right_paren_on_new_line = false -ij_javascript_place_assignment_sign_on_next_line = false -ij_javascript_prefer_as_type_cast = false -ij_javascript_prefer_explicit_types_function_expression_returns = false -ij_javascript_prefer_explicit_types_function_returns = false -ij_javascript_prefer_explicit_types_vars_fields = false -ij_javascript_prefer_parameters_wrap = false -ij_javascript_property_prefix = -ij_javascript_reformat_c_style_comments = false -ij_javascript_space_after_colon = true -ij_javascript_space_after_comma = true -ij_javascript_space_after_dots_in_rest_parameter = false -ij_javascript_space_after_generator_mult = true -ij_javascript_space_after_property_colon = true -ij_javascript_space_after_quest = true -ij_javascript_space_after_type_colon = true -ij_javascript_space_after_unary_not = false -ij_javascript_space_before_async_arrow_lparen = true -ij_javascript_space_before_catch_keyword = true -ij_javascript_space_before_catch_left_brace = true -ij_javascript_space_before_catch_parentheses = true -ij_javascript_space_before_class_lbrace = true -ij_javascript_space_before_class_left_brace = true -ij_javascript_space_before_colon = true -ij_javascript_space_before_comma = false -ij_javascript_space_before_do_left_brace = true -ij_javascript_space_before_else_keyword = true -ij_javascript_space_before_else_left_brace = true -ij_javascript_space_before_finally_keyword = true -ij_javascript_space_before_finally_left_brace = true -ij_javascript_space_before_for_left_brace = true -ij_javascript_space_before_for_parentheses = true -ij_javascript_space_before_for_semicolon = false -ij_javascript_space_before_function_left_parenth = true -ij_javascript_space_before_generator_mult = false -ij_javascript_space_before_if_left_brace = true -ij_javascript_space_before_if_parentheses = true -ij_javascript_space_before_method_call_parentheses = false -ij_javascript_space_before_method_left_brace = true -ij_javascript_space_before_method_parentheses = false -ij_javascript_space_before_property_colon = false -ij_javascript_space_before_quest = true -ij_javascript_space_before_switch_left_brace = true -ij_javascript_space_before_switch_parentheses = true -ij_javascript_space_before_try_left_brace = true -ij_javascript_space_before_type_colon = false -ij_javascript_space_before_unary_not = false -ij_javascript_space_before_while_keyword = true -ij_javascript_space_before_while_left_brace = true -ij_javascript_space_before_while_parentheses = true -ij_javascript_spaces_around_additive_operators = true -ij_javascript_spaces_around_arrow_function_operator = true -ij_javascript_spaces_around_assignment_operators = true -ij_javascript_spaces_around_bitwise_operators = true -ij_javascript_spaces_around_equality_operators = true -ij_javascript_spaces_around_logical_operators = true -ij_javascript_spaces_around_multiplicative_operators = true -ij_javascript_spaces_around_relational_operators = true -ij_javascript_spaces_around_shift_operators = true -ij_javascript_spaces_around_unary_operator = false -ij_javascript_spaces_within_array_initializer_brackets = false -ij_javascript_spaces_within_brackets = false -ij_javascript_spaces_within_catch_parentheses = false -ij_javascript_spaces_within_for_parentheses = false -ij_javascript_spaces_within_if_parentheses = false -ij_javascript_spaces_within_imports = false -ij_javascript_spaces_within_interpolation_expressions = false -ij_javascript_spaces_within_method_call_parentheses = false -ij_javascript_spaces_within_method_parentheses = false -ij_javascript_spaces_within_object_literal_braces = false -ij_javascript_spaces_within_object_type_braces = true -ij_javascript_spaces_within_parentheses = false -ij_javascript_spaces_within_switch_parentheses = false -ij_javascript_spaces_within_type_assertion = false -ij_javascript_spaces_within_union_types = true -ij_javascript_spaces_within_while_parentheses = false -ij_javascript_special_else_if_treatment = true -ij_javascript_ternary_operation_signs_on_next_line = false -ij_javascript_ternary_operation_wrap = normal -ij_javascript_union_types_wrap = on_every_item -ij_javascript_use_chained_calls_group_indents = false -ij_javascript_use_double_quotes = true -ij_javascript_use_explicit_js_extension = auto -ij_javascript_use_path_mapping = always -ij_javascript_use_public_modifier = false -ij_javascript_use_semicolon_after_statement = true -ij_javascript_var_declaration_wrap = normal -ij_javascript_while_brace_force = always -ij_javascript_while_on_new_line = false -ij_javascript_wrap_comments = true - -[{*.ft,*.vm,*.vsl}] -ij_vtl_keep_indents_on_empty_lines = false - -[{*.gant,*.groovy,*.gy}] -ij_groovy_align_group_field_declarations = false -ij_groovy_align_multiline_array_initializer_expression = false -ij_groovy_align_multiline_assignment = false -ij_groovy_align_multiline_binary_operation = false -ij_groovy_align_multiline_chained_methods = false -ij_groovy_align_multiline_extends_list = false -ij_groovy_align_multiline_for = false -ij_groovy_align_multiline_list_or_map = false -ij_groovy_align_multiline_method_parentheses = false -ij_groovy_align_multiline_parameters = false -ij_groovy_align_multiline_parameters_in_calls = false -ij_groovy_align_multiline_resources = false -ij_groovy_align_multiline_ternary_operation = false -ij_groovy_align_multiline_throws_list = false -ij_groovy_align_named_args_in_map = false -ij_groovy_align_throws_keyword = false -ij_groovy_array_initializer_new_line_after_left_brace = false -ij_groovy_array_initializer_right_brace_on_new_line = false -ij_groovy_array_initializer_wrap = normal -ij_groovy_assert_statement_wrap = normal -ij_groovy_assignment_wrap = normal -ij_groovy_binary_operation_wrap = normal -ij_groovy_blank_lines_after_class_header = 0 -ij_groovy_blank_lines_after_imports = 1 -ij_groovy_blank_lines_after_package = 1 -ij_groovy_blank_lines_around_class = 1 -ij_groovy_blank_lines_around_field = 0 -ij_groovy_blank_lines_around_field_in_interface = 0 -ij_groovy_blank_lines_around_method = 1 -ij_groovy_blank_lines_around_method_in_interface = 1 -ij_groovy_blank_lines_before_imports = 1 -ij_groovy_blank_lines_before_method_body = 0 -ij_groovy_blank_lines_before_package = 0 -ij_groovy_block_brace_style = end_of_line -ij_groovy_block_comment_add_space = false -ij_groovy_block_comment_at_first_column = true -ij_groovy_call_parameters_new_line_after_left_paren = false -ij_groovy_call_parameters_right_paren_on_new_line = false -ij_groovy_call_parameters_wrap = normal -ij_groovy_catch_on_new_line = false -ij_groovy_class_annotation_wrap = split_into_lines -ij_groovy_class_brace_style = end_of_line -ij_groovy_class_count_to_use_import_on_demand = 999 -ij_groovy_do_while_brace_force = always -ij_groovy_else_on_new_line = false -ij_groovy_enable_groovydoc_formatting = true -ij_groovy_enum_constants_wrap = split_into_lines -ij_groovy_extends_keyword_wrap = normal -ij_groovy_extends_list_wrap = normal -ij_groovy_field_annotation_wrap = split_into_lines -ij_groovy_finally_on_new_line = false -ij_groovy_for_brace_force = always -ij_groovy_for_statement_new_line_after_left_paren = false -ij_groovy_for_statement_right_paren_on_new_line = false -ij_groovy_for_statement_wrap = normal -ij_groovy_ginq_general_clause_wrap_policy = 2 -ij_groovy_ginq_having_wrap_policy = 1 -ij_groovy_ginq_indent_having_clause = true -ij_groovy_ginq_indent_on_clause = true -ij_groovy_ginq_on_wrap_policy = 1 -ij_groovy_ginq_space_after_keyword = true -ij_groovy_if_brace_force = always -ij_groovy_import_annotation_wrap = 2 -ij_groovy_imports_layout = *,|,javax.**,java.**,|,$* -ij_groovy_indent_case_from_switch = true -ij_groovy_indent_label_blocks = true -ij_groovy_insert_inner_class_imports = false -ij_groovy_keep_blank_lines_before_right_brace = 1 -ij_groovy_keep_blank_lines_in_code = 1 -ij_groovy_keep_blank_lines_in_declarations = 1 -ij_groovy_keep_control_statement_in_one_line = true -ij_groovy_keep_first_column_comment = true -ij_groovy_keep_indents_on_empty_lines = false -ij_groovy_keep_line_breaks = true -ij_groovy_keep_multiple_expressions_in_one_line = true -ij_groovy_keep_simple_blocks_in_one_line = true -ij_groovy_keep_simple_classes_in_one_line = true -ij_groovy_keep_simple_lambdas_in_one_line = true -ij_groovy_keep_simple_methods_in_one_line = true -ij_groovy_label_indent_absolute = false -ij_groovy_label_indent_size = 0 -ij_groovy_lambda_brace_style = end_of_line -ij_groovy_layout_static_imports_separately = true -ij_groovy_line_comment_add_space = false -ij_groovy_line_comment_add_space_on_reformat = false -ij_groovy_line_comment_at_first_column = true -ij_groovy_method_annotation_wrap = split_into_lines -ij_groovy_method_brace_style = end_of_line -ij_groovy_method_call_chain_wrap = normal -ij_groovy_method_parameters_new_line_after_left_paren = false -ij_groovy_method_parameters_right_paren_on_new_line = false -ij_groovy_method_parameters_wrap = normal -ij_groovy_modifier_list_wrap = false -ij_groovy_names_count_to_use_import_on_demand = 999 -ij_groovy_packages_to_use_import_on_demand = * -ij_groovy_parameter_annotation_wrap = normal -ij_groovy_parentheses_expression_new_line_after_left_paren = false -ij_groovy_parentheses_expression_right_paren_on_new_line = false -ij_groovy_prefer_parameters_wrap = false -ij_groovy_resource_list_new_line_after_left_paren = false -ij_groovy_resource_list_right_paren_on_new_line = false -ij_groovy_resource_list_wrap = normal -ij_groovy_space_after_assert_separator = true -ij_groovy_space_after_colon = true -ij_groovy_space_after_comma = true -ij_groovy_space_after_comma_in_type_arguments = true -ij_groovy_space_after_for_semicolon = true -ij_groovy_space_after_quest = true -ij_groovy_space_after_type_cast = true -ij_groovy_space_before_annotation_parameter_list = false -ij_groovy_space_before_array_initializer_left_brace = false -ij_groovy_space_before_assert_separator = false -ij_groovy_space_before_catch_keyword = true -ij_groovy_space_before_catch_left_brace = true -ij_groovy_space_before_catch_parentheses = true -ij_groovy_space_before_class_left_brace = true -ij_groovy_space_before_closure_left_brace = true -ij_groovy_space_before_colon = true -ij_groovy_space_before_comma = false -ij_groovy_space_before_do_left_brace = true -ij_groovy_space_before_else_keyword = true -ij_groovy_space_before_else_left_brace = true -ij_groovy_space_before_finally_keyword = true -ij_groovy_space_before_finally_left_brace = true -ij_groovy_space_before_for_left_brace = true -ij_groovy_space_before_for_parentheses = true -ij_groovy_space_before_for_semicolon = false -ij_groovy_space_before_if_left_brace = true -ij_groovy_space_before_if_parentheses = true -ij_groovy_space_before_method_call_parentheses = false -ij_groovy_space_before_method_left_brace = true -ij_groovy_space_before_method_parentheses = false -ij_groovy_space_before_quest = true -ij_groovy_space_before_record_parentheses = false -ij_groovy_space_before_switch_left_brace = true -ij_groovy_space_before_switch_parentheses = true -ij_groovy_space_before_synchronized_left_brace = true -ij_groovy_space_before_synchronized_parentheses = true -ij_groovy_space_before_try_left_brace = true -ij_groovy_space_before_try_parentheses = true -ij_groovy_space_before_while_keyword = true -ij_groovy_space_before_while_left_brace = true -ij_groovy_space_before_while_parentheses = true -ij_groovy_space_in_named_argument = true -ij_groovy_space_in_named_argument_before_colon = false -ij_groovy_space_within_empty_array_initializer_braces = false -ij_groovy_space_within_empty_method_call_parentheses = false -ij_groovy_spaces_around_additive_operators = true -ij_groovy_spaces_around_assignment_operators = true -ij_groovy_spaces_around_bitwise_operators = true -ij_groovy_spaces_around_equality_operators = true -ij_groovy_spaces_around_lambda_arrow = true -ij_groovy_spaces_around_logical_operators = true -ij_groovy_spaces_around_multiplicative_operators = true -ij_groovy_spaces_around_regex_operators = true -ij_groovy_spaces_around_relational_operators = true -ij_groovy_spaces_around_shift_operators = true -ij_groovy_spaces_within_annotation_parentheses = false -ij_groovy_spaces_within_array_initializer_braces = false -ij_groovy_spaces_within_braces = true -ij_groovy_spaces_within_brackets = false -ij_groovy_spaces_within_cast_parentheses = false -ij_groovy_spaces_within_catch_parentheses = false -ij_groovy_spaces_within_for_parentheses = false -ij_groovy_spaces_within_gstring_injection_braces = false -ij_groovy_spaces_within_if_parentheses = false -ij_groovy_spaces_within_list_or_map = false -ij_groovy_spaces_within_method_call_parentheses = false -ij_groovy_spaces_within_method_parentheses = false -ij_groovy_spaces_within_parentheses = false -ij_groovy_spaces_within_switch_parentheses = false -ij_groovy_spaces_within_synchronized_parentheses = false -ij_groovy_spaces_within_try_parentheses = false -ij_groovy_spaces_within_tuple_expression = false -ij_groovy_spaces_within_while_parentheses = false -ij_groovy_special_else_if_treatment = true -ij_groovy_ternary_operation_wrap = normal -ij_groovy_throws_keyword_wrap = normal -ij_groovy_throws_list_wrap = normal -ij_groovy_use_flying_geese_braces = false -ij_groovy_use_fq_class_names = false -ij_groovy_use_fq_class_names_in_javadoc = false -ij_groovy_use_relative_indents = false -ij_groovy_use_single_class_imports = true -ij_groovy_variable_annotation_wrap = normal -ij_groovy_while_brace_force = always -ij_groovy_while_on_new_line = false -ij_groovy_wrap_chain_calls_after_dot = false -ij_groovy_wrap_long_lines = false - -[{*.har,*.jsb2,*.jsb3,*.json,.babelrc,.eslintrc,.prettierrc,.stylelintrc,bowerrc,jest.config}] -indent_size = 2 -ij_json_array_wrapping = split_into_lines -ij_json_keep_blank_lines_in_code = 0 -ij_json_keep_indents_on_empty_lines = false -ij_json_keep_line_breaks = false -ij_json_keep_trailing_comma = false -ij_json_object_wrapping = split_into_lines -ij_json_property_alignment = do_not_align -ij_json_space_after_colon = true -ij_json_space_after_comma = true -ij_json_space_before_colon = false -ij_json_space_before_comma = false -ij_json_spaces_within_braces = false -ij_json_spaces_within_brackets = false -ij_json_wrap_long_lines = false - -[{*.htm,*.html,*.ng,*.peb,*.sht,*.shtm,*.shtml}] -ij_html_add_new_line_before_tags = body,div,p,form,h1,h2,h3 -ij_html_align_attributes = false -ij_html_align_text = false -ij_html_attribute_wrap = normal -ij_html_block_comment_add_space = false -ij_html_block_comment_at_first_column = true -ij_html_do_not_align_children_of_min_lines = 0 -ij_html_do_not_break_if_inline_tags = title,h1,h2,h3,h4,h5,h6,p -ij_html_do_not_indent_children_of_tags = html,body,thead,tbody,tfoot -ij_html_enforce_quotes = true -ij_html_inline_tags = a,abbr,acronym,b,basefont,bdo,big,br,cite,cite,code,dfn,em,font,i,img,input,kbd,label,q,s,samp,select,small,span,strike,strong,sub,sup,textarea,tt,u,var -ij_html_keep_blank_lines = 1 -ij_html_keep_indents_on_empty_lines = false -ij_html_keep_line_breaks = true -ij_html_keep_line_breaks_in_text = true -ij_html_keep_whitespaces = false -ij_html_keep_whitespaces_inside = span,pre,textarea -ij_html_line_comment_at_first_column = true -ij_html_new_line_after_last_attribute = never -ij_html_new_line_before_first_attribute = never -ij_html_quote_style = double -ij_html_remove_new_line_before_tags = br -ij_html_space_after_tag_name = false -ij_html_space_around_equality_in_attribute = false -ij_html_space_inside_empty_tag = false -ij_html_text_wrap = normal - -[{*.http,*.rest}] -indent_size = 0 -ij_continuation_indent_size = 4 -ij_http-request_call_parameters_wrap = normal -ij_http-request_method_parameters_wrap = split_into_lines -ij_http-request_space_before_comma = true -ij_http-request_spaces_around_assignment_operators = true - -[{*.jsf,*.jsp,*.jspf,*.tag,*.tagf,*.xjsp}] -ij_jsp_jsp_prefer_comma_separated_import_list = false -ij_jsp_keep_indents_on_empty_lines = false - -[{*.jspx,*.tagx}] -ij_jspx_keep_indents_on_empty_lines = false - -[{*.kt,*.kts}] -ij_kotlin_align_in_columns_case_branch = false -ij_kotlin_align_multiline_binary_operation = false -ij_kotlin_align_multiline_extends_list = false -ij_kotlin_align_multiline_method_parentheses = false -ij_kotlin_align_multiline_parameters = false -ij_kotlin_align_multiline_parameters_in_calls = false -ij_kotlin_allow_trailing_comma = false -ij_kotlin_allow_trailing_comma_on_call_site = false -ij_kotlin_assignment_wrap = normal -ij_kotlin_blank_lines_after_class_header = 0 -ij_kotlin_blank_lines_around_block_when_branches = 0 -ij_kotlin_blank_lines_before_declaration_with_comment_or_annotation_on_separate_line = 1 -ij_kotlin_block_comment_add_space = false -ij_kotlin_block_comment_at_first_column = true -ij_kotlin_call_parameters_new_line_after_left_paren = false -ij_kotlin_call_parameters_right_paren_on_new_line = false -ij_kotlin_call_parameters_wrap = normal -ij_kotlin_catch_on_new_line = false -ij_kotlin_class_annotation_wrap = split_into_lines -ij_kotlin_continuation_indent_for_chained_calls = true -ij_kotlin_continuation_indent_for_expression_bodies = true -ij_kotlin_continuation_indent_in_argument_lists = true -ij_kotlin_continuation_indent_in_elvis = true -ij_kotlin_continuation_indent_in_if_conditions = true -ij_kotlin_continuation_indent_in_parameter_lists = true -ij_kotlin_continuation_indent_in_supertype_lists = true -ij_kotlin_else_on_new_line = false -ij_kotlin_enum_constants_wrap = split_into_lines -ij_kotlin_extends_list_wrap = normal -ij_kotlin_field_annotation_wrap = split_into_lines -ij_kotlin_finally_on_new_line = false -ij_kotlin_if_rparen_on_new_line = false -ij_kotlin_import_nested_classes = false -ij_kotlin_imports_layout = *,java.**,javax.**,kotlin.**,^ -ij_kotlin_insert_whitespaces_in_simple_one_line_method = true -ij_kotlin_keep_blank_lines_before_right_brace = 1 -ij_kotlin_keep_blank_lines_in_code = 1 -ij_kotlin_keep_blank_lines_in_declarations = 1 -ij_kotlin_keep_first_column_comment = true -ij_kotlin_keep_indents_on_empty_lines = false -ij_kotlin_keep_line_breaks = true -ij_kotlin_lbrace_on_next_line = false -ij_kotlin_line_break_after_multiline_when_entry = true -ij_kotlin_line_comment_add_space = false -ij_kotlin_line_comment_add_space_on_reformat = false -ij_kotlin_line_comment_at_first_column = true -ij_kotlin_method_annotation_wrap = split_into_lines -ij_kotlin_method_call_chain_wrap = normal -ij_kotlin_method_parameters_new_line_after_left_paren = false -ij_kotlin_method_parameters_right_paren_on_new_line = false -ij_kotlin_method_parameters_wrap = normal -ij_kotlin_name_count_to_use_star_import = 2147483647 -ij_kotlin_name_count_to_use_star_import_for_members = 2147483647 -ij_kotlin_packages_to_use_import_on_demand = -ij_kotlin_parameter_annotation_wrap = normal -ij_kotlin_space_after_comma = true -ij_kotlin_space_after_extend_colon = true -ij_kotlin_space_after_type_colon = true -ij_kotlin_space_before_catch_parentheses = true -ij_kotlin_space_before_comma = false -ij_kotlin_space_before_extend_colon = true -ij_kotlin_space_before_for_parentheses = true -ij_kotlin_space_before_if_parentheses = true -ij_kotlin_space_before_lambda_arrow = true -ij_kotlin_space_before_type_colon = false -ij_kotlin_space_before_when_parentheses = true -ij_kotlin_space_before_while_parentheses = true -ij_kotlin_spaces_around_additive_operators = true -ij_kotlin_spaces_around_assignment_operators = true -ij_kotlin_spaces_around_equality_operators = true -ij_kotlin_spaces_around_function_type_arrow = true -ij_kotlin_spaces_around_logical_operators = true -ij_kotlin_spaces_around_multiplicative_operators = true -ij_kotlin_spaces_around_range = false -ij_kotlin_spaces_around_relational_operators = true -ij_kotlin_spaces_around_unary_operator = false -ij_kotlin_spaces_around_when_arrow = true -ij_kotlin_variable_annotation_wrap = normal -ij_kotlin_while_on_new_line = false -ij_kotlin_wrap_elvis_expressions = 1 -ij_kotlin_wrap_expression_body_functions = 1 -ij_kotlin_wrap_first_method_in_call_chain = false - -[{*.markdown,*.md}] -ij_markdown_force_one_space_after_blockquote_symbol = true -ij_markdown_force_one_space_after_header_symbol = true -ij_markdown_force_one_space_after_list_bullet = true -ij_markdown_force_one_space_between_words = true -ij_markdown_format_tables = true -ij_markdown_insert_quote_arrows_on_wrap = true -ij_markdown_keep_indents_on_empty_lines = false -ij_markdown_keep_line_breaks_inside_text_blocks = true -ij_markdown_max_lines_around_block_elements = 1 -ij_markdown_max_lines_around_header = 1 -ij_markdown_max_lines_between_paragraphs = 1 -ij_markdown_min_lines_around_block_elements = 1 -ij_markdown_min_lines_around_header = 1 -ij_markdown_min_lines_between_paragraphs = 1 -ij_markdown_wrap_text_if_long = true -ij_markdown_wrap_text_inside_blockquotes = true - -[{*.pb,*.textproto}] -indent_size = 2 -tab_width = 2 -ij_continuation_indent_size = 4 -ij_prototext_keep_blank_lines_in_code = 2 -ij_prototext_keep_indents_on_empty_lines = false -ij_prototext_keep_line_breaks = true -ij_prototext_space_after_colon = true -ij_prototext_space_after_comma = true -ij_prototext_space_before_colon = false -ij_prototext_space_before_comma = false -ij_prototext_spaces_within_braces = true -ij_prototext_spaces_within_brackets = false - -[{*.properties,spring.handlers,spring.schemas}] -ij_properties_align_group_field_declarations = false -ij_properties_keep_blank_lines = true -ij_properties_key_value_delimiter = colon -ij_properties_spaces_around_key_value_delimiter = false - -[{*.qute.htm,*.qute.html,*.qute.json,*.qute.txt,*.qute.yaml,*.qute.yml}] -ij_qute_keep_indents_on_empty_lines = false - -[{*.toml,Cargo.lock,Cargo.toml.orig,Gopkg.lock,Pipfile,poetry.lock}] -ij_toml_keep_indents_on_empty_lines = false - -[{*.yaml,*.yml}] -indent_size = 2 -ij_yaml_align_values_properties = do_not_align -ij_yaml_autoinsert_sequence_marker = true -ij_yaml_block_mapping_on_new_line = false -ij_yaml_indent_sequence_value = true -ij_yaml_keep_indents_on_empty_lines = false -ij_yaml_keep_line_breaks = true -ij_yaml_sequence_on_new_line = false -ij_yaml_space_before_colon = false -ij_yaml_spaces_within_braces = true -ij_yaml_spaces_within_brackets = true From 3190abb18dd088409236cdc9b0574a4d409d2799 Mon Sep 17 00:00:00 2001 From: Jacob Peterson <14127217+Petersoj@users.noreply.github.com> Date: Mon, 1 Sep 2025 12:37:50 -0600 Subject: [PATCH 229/271] Update Gradle wrapper --- gradle.properties | 3 + gradle/wrapper/gradle-wrapper.jar | Bin 59203 -> 43764 bytes gradle/wrapper/gradle-wrapper.properties | 2 +- gradlew | 286 ++++++++++++++--------- gradlew.bat | 41 ++-- 5 files changed, 203 insertions(+), 129 deletions(-) create mode 100644 gradle.properties diff --git a/gradle.properties b/gradle.properties new file mode 100644 index 00000000..31634b4e --- /dev/null +++ b/gradle.properties @@ -0,0 +1,3 @@ +org.gradle.logging.level=info +org.gradle.logging.stacktrace=all +org.gradle.jvmargs=-XX:MaxRAMPercentage=100.0 -XX:MinHeapFreeRatio=5 -XX:MaxHeapFreeRatio=15 diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index e708b1c023ec8b20f512888fe07c5bd3ff77bb8f..1b33c55baabb587c669f562ae36f953de2481846 100644 GIT binary patch literal 43764 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

K7UpUK$>l@ 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 E2O(~oYXATM diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 2ea3535d..ff23a68d 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.6-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.14.2-bin.zip networkTimeout=10000 validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME diff --git a/gradlew b/gradlew index 4f906e0c..23d15a93 100755 --- a/gradlew +++ b/gradlew @@ -1,7 +1,7 @@ -#!/usr/bin/env sh +#!/bin/sh # -# Copyright 2015 the original author or authors. +# Copyright © 2015-2021 the original authors. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -15,81 +15,115 @@ # See the License for the specific language governing permissions and # limitations under the License. # +# SPDX-License-Identifier: Apache-2.0 +# ############################################################################## -## -## Gradle start up script for UN*X -## +# +# Gradle start up script for POSIX generated by Gradle. +# +# Important for running: +# +# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is +# noncompliant, but you have some other compliant shell such as ksh or +# bash, then to run this script, type that shell name before the whole +# command line, like: +# +# ksh Gradle +# +# Busybox and similar reduced shells will NOT work, because this script +# requires all of these POSIX shell features: +# * functions; +# * expansions «$var», «${var}», «${var:-default}», «${var+SET}», +# «${var#prefix}», «${var%suffix}», and «$( cmd )»; +# * compound commands having a testable exit status, especially «case»; +# * various built-in commands including «command», «set», and «ulimit». +# +# Important for patching: +# +# (2) This script targets any POSIX shell, so it avoids extensions provided +# by Bash, Ksh, etc; in particular arrays are avoided. +# +# The "traditional" practice of packing multiple parameters into a +# space-separated string is a well documented source of bugs and security +# problems, so this is (mostly) avoided, by progressively accumulating +# options in "$@", and eventually passing that to Java. +# +# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, +# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; +# see the in-line comments for details. +# +# There are tweaks for specific operating systems such as AIX, CygWin, +# Darwin, MinGW, and NonStop. +# +# (3) This script is generated from the Groovy template +# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# within the Gradle project. +# +# You can find Gradle at https://github.com/gradle/gradle/. +# ############################################################################## # Attempt to set APP_HOME + # Resolve links: $0 may be a link -PRG="$0" -# Need this for relative symlinks. -while [ -h "$PRG" ] ; do - ls=`ls -ld "$PRG"` - link=`expr "$ls" : '.*-> \(.*\)$'` - if expr "$link" : '/.*' > /dev/null; then - PRG="$link" - else - PRG=`dirname "$PRG"`"/$link" - fi +app_path=$0 + +# Need this for daisy-chained symlinks. +while + APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path + [ -h "$app_path" ] +do + ls=$( ls -ld "$app_path" ) + link=${ls#*' -> '} + case $link in #( + /*) app_path=$link ;; #( + *) app_path=$APP_HOME$link ;; + esac done -SAVED="`pwd`" -cd "`dirname \"$PRG\"`/" >/dev/null -APP_HOME="`pwd -P`" -cd "$SAVED" >/dev/null -APP_NAME="Gradle" -APP_BASE_NAME=`basename "$0"` - -# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. -DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' +# This is normally unused +# shellcheck disable=SC2034 +APP_BASE_NAME=${0##*/} +# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) +APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit # Use the maximum available, or set MAX_FD != -1 to use that value. -MAX_FD="maximum" +MAX_FD=maximum warn () { echo "$*" -} +} >&2 die () { echo echo "$*" echo exit 1 -} +} >&2 # OS specific support (must be 'true' or 'false'). cygwin=false msys=false darwin=false nonstop=false -case "`uname`" in - CYGWIN* ) - cygwin=true - ;; - Darwin* ) - darwin=true - ;; - MINGW* ) - msys=true - ;; - NONSTOP* ) - nonstop=true - ;; +case "$( uname )" in #( + CYGWIN* ) cygwin=true ;; #( + Darwin* ) darwin=true ;; #( + MSYS* | MINGW* ) msys=true ;; #( + NONSTOP* ) nonstop=true ;; esac -CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar +CLASSPATH="\\\"\\\"" # Determine the Java command to use to start the JVM. if [ -n "$JAVA_HOME" ] ; then if [ -x "$JAVA_HOME/jre/sh/java" ] ; then # IBM's JDK on AIX uses strange locations for the executables - JAVACMD="$JAVA_HOME/jre/sh/java" + JAVACMD=$JAVA_HOME/jre/sh/java else - JAVACMD="$JAVA_HOME/bin/java" + JAVACMD=$JAVA_HOME/bin/java fi if [ ! -x "$JAVACMD" ] ; then die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME @@ -98,88 +132,120 @@ Please set the JAVA_HOME variable in your environment to match the location of your Java installation." fi else - JAVACMD="java" - which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + JAVACMD=java + if ! command -v java >/dev/null 2>&1 + then + die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. Please set the JAVA_HOME variable in your environment to match the location of your Java installation." + fi fi # Increase the maximum file descriptors if we can. -if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then - MAX_FD_LIMIT=`ulimit -H -n` - if [ $? -eq 0 ] ; then - if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then - MAX_FD="$MAX_FD_LIMIT" - fi - ulimit -n $MAX_FD - if [ $? -ne 0 ] ; then - warn "Could not set maximum file descriptor limit: $MAX_FD" - fi - else - warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" - fi +if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then + case $MAX_FD in #( + max*) + # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC2039,SC3045 + MAX_FD=$( ulimit -H -n ) || + warn "Could not query maximum file descriptor limit" + esac + case $MAX_FD in #( + '' | soft) :;; #( + *) + # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC2039,SC3045 + ulimit -n "$MAX_FD" || + warn "Could not set maximum file descriptor limit to $MAX_FD" + esac fi -# For Darwin, add options to specify how the application appears in the dock -if $darwin; then - GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" -fi +# Collect all arguments for the java command, stacking in reverse order: +# * args from the command line +# * the main class name +# * -classpath +# * -D...appname settings +# * --module-path (only if needed) +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. # For Cygwin or MSYS, switch paths to Windows format before running java -if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then - APP_HOME=`cygpath --path --mixed "$APP_HOME"` - CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` - - JAVACMD=`cygpath --unix "$JAVACMD"` - - # We build the pattern for arguments to be converted via cygpath - ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` - SEP="" - for dir in $ROOTDIRSRAW ; do - ROOTDIRS="$ROOTDIRS$SEP$dir" - SEP="|" - done - OURCYGPATTERN="(^($ROOTDIRS))" - # Add a user-defined pattern to the cygpath arguments - if [ "$GRADLE_CYGPATTERN" != "" ] ; then - OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" - fi +if "$cygwin" || "$msys" ; then + APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) + CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) + + JAVACMD=$( cygpath --unix "$JAVACMD" ) + # Now convert the arguments - kludge to limit ourselves to /bin/sh - i=0 - for arg in "$@" ; do - CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` - CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option - - if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition - eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` - else - eval `echo args$i`="\"$arg\"" + for arg do + if + case $arg in #( + -*) false ;; # don't mess with options #( + /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath + [ -e "$t" ] ;; #( + *) false ;; + esac + then + arg=$( cygpath --path --ignore --mixed "$arg" ) fi - i=`expr $i + 1` + # Roll the args list around exactly as many times as the number of + # args, so each arg winds up back in the position where it started, but + # possibly modified. + # + # NB: a `for` loop captures its iteration list before it begins, so + # changing the positional parameters here affects neither the number of + # iterations, nor the values presented in `arg`. + shift # remove old arg + set -- "$@" "$arg" # push replacement arg done - case $i in - 0) set -- ;; - 1) set -- "$args0" ;; - 2) set -- "$args0" "$args1" ;; - 3) set -- "$args0" "$args1" "$args2" ;; - 4) set -- "$args0" "$args1" "$args2" "$args3" ;; - 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; - 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; - 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; - 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; - 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; - esac fi -# Escape application args -save () { - for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done - echo " " -} -APP_ARGS=`save "$@"` -# Collect all arguments for the java command, following the shell quoting and substitution rules -eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' + +# Collect all arguments for the java command: +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, +# and any embedded shellness will be escaped. +# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be +# treated as '${Hostname}' itself on the command line. + +set -- \ + "-Dorg.gradle.appname=$APP_BASE_NAME" \ + -classpath "$CLASSPATH" \ + -jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \ + "$@" + +# Stop when "xargs" is not available. +if ! command -v xargs >/dev/null 2>&1 +then + die "xargs is not available" +fi + +# Use "xargs" to parse quoted args. +# +# With -n1 it outputs one arg per line, with the quotes and backslashes removed. +# +# In Bash we could simply go: +# +# readarray ARGS < <( xargs -n1 <<<"$var" ) && +# set -- "${ARGS[@]}" "$@" +# +# but POSIX shell has neither arrays nor command substitution, so instead we +# post-process each arg (as a line of input to sed) to backslash-escape any +# character that might be a shell metacharacter, then use eval to reverse +# that process (while maintaining the separation between arguments), and wrap +# the whole thing up as a single "set" statement. +# +# This will of course break if any of these variables contains a newline or +# an unmatched quote. +# + +eval "set -- $( + printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | + xargs -n1 | + sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | + tr '\n' ' ' + )" '"$@"' exec "$JAVACMD" "$@" diff --git a/gradlew.bat b/gradlew.bat index 107acd32..db3a6ac2 100644 --- a/gradlew.bat +++ b/gradlew.bat @@ -13,8 +13,10 @@ @rem See the License for the specific language governing permissions and @rem limitations under the License. @rem +@rem SPDX-License-Identifier: Apache-2.0 +@rem -@if "%DEBUG%" == "" @echo off +@if "%DEBUG%"=="" @echo off @rem ########################################################################## @rem @rem Gradle startup script for Windows @@ -25,7 +27,8 @@ if "%OS%"=="Windows_NT" setlocal set DIRNAME=%~dp0 -if "%DIRNAME%" == "" set DIRNAME=. +if "%DIRNAME%"=="" set DIRNAME=. +@rem This is normally unused set APP_BASE_NAME=%~n0 set APP_HOME=%DIRNAME% @@ -40,13 +43,13 @@ if defined JAVA_HOME goto findJavaFromJavaHome set JAVA_EXE=java.exe %JAVA_EXE% -version >NUL 2>&1 -if "%ERRORLEVEL%" == "0" goto execute +if %ERRORLEVEL% equ 0 goto execute -echo. -echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. +echo. 1>&2 +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 goto fail @@ -56,32 +59,34 @@ set JAVA_EXE=%JAVA_HOME%/bin/java.exe if exist "%JAVA_EXE%" goto execute -echo. -echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. +echo. 1>&2 +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 goto fail :execute @rem Setup the command line -set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar +set CLASSPATH= @rem Execute Gradle -"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %* :end @rem End local scope for the variables with windows NT shell -if "%ERRORLEVEL%"=="0" goto mainEnd +if %ERRORLEVEL% equ 0 goto mainEnd :fail rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of rem the _cmd.exe /c_ return code! -if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 -exit /b 1 +set EXIT_CODE=%ERRORLEVEL% +if %EXIT_CODE% equ 0 set EXIT_CODE=1 +if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% +exit /b %EXIT_CODE% :mainEnd if "%OS%"=="Windows_NT" endlocal From 1d77898ee793a39e7cdd3a8acbd9a7cb12467292 Mon Sep 17 00:00:00 2001 From: Jacob Peterson <14127217+Petersoj@users.noreply.github.com> Date: Mon, 1 Sep 2025 12:40:06 -0600 Subject: [PATCH 230/271] Remove `.gitattributes` --- .gitattributes | 7 ------- 1 file changed, 7 deletions(-) delete mode 100644 .gitattributes diff --git a/.gitattributes b/.gitattributes deleted file mode 100644 index 710d818f..00000000 --- a/.gitattributes +++ /dev/null @@ -1,7 +0,0 @@ -# Handle line endings automatically for files detected as text and leave all files detected as binary untouched. -* text=auto - -# Declare files that should always have CRLF line endings at checkout. -*.sln text eol=crlf -*.bat text eol=crlf -*.cmd text eol=crlf From 0b8f28aba489db35dca3a68b8a44bfddabf79704 Mon Sep 17 00:00:00 2001 From: Jacob Peterson <14127217+Petersoj@users.noreply.github.com> Date: Tue, 23 Sep 2025 11:13:36 -0600 Subject: [PATCH 231/271] Update Gradle wrapper --- gradle/wrapper/gradle-wrapper.jar | Bin 43764 -> 45457 bytes gradle/wrapper/gradle-wrapper.properties | 2 +- gradlew | 5 +- gradlew.bat | 187 +++++++++++------------ 4 files changed, 95 insertions(+), 99 deletions(-) diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index 1b33c55baabb587c669f562ae36f953de2481846..8bdaf60c75ab801e22807dde59e12a8735a34077 100644 GIT binary patch delta 37256 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 delta 35551 zcmYJZV|bna)5V*{Y~1X)L1WvtZQHhXxMQoaZ98df+je97^#6O#xz79h)jhM;%=fb< zejogP5xmysJ1}Y-zK;P#^eNya^!*RyrWsaa*o?`cG4E0x(uI5*J=Ql{I8pVHbrf*&ViJbv&0$Zx^9HzKJYQ+2@eUCip7Q~vv%wZxh=X(hybkQ-d%4h08A3r-BgR1yDQOhGU!yc)KY_R) z<~z-KN~9P>0@{5up2;>ZO7$o~VmdL?8yt&VFrbN!Ax~@SD^gB(*;lok#cYX1yF0ri zTfoNS4~q_qcA&~muAcevb&3QXO?~0wIJt9T@@k%iwWyg|@`P{EtB0FDW2TTpJ449e zuN$b!Af;6128-YK{g=RgMOrWWfwmiBb%I9~ClxAv$Tv$EFuBIYWT39uPZWMY_)u>-6QS>Dpp%(#NEFIeU zjJN#v$j{|sq!va#kM7Uh3#%b(XnIqbX?K%PlWA%C!0rz)hR9!_CvWd*YWqemcDG<_ ztH|`aB23nP=k&Rwy!(xW{j|Wn?pi2hNM1G%1t1en-wK?TTrRDhBR7g@m1Q#C7R_i_ zL3gbJo7pkkx%%3RHtl+`z|2k&Q(IqCA$2glZe)H(AF@Q`UUFJnn$##p$J+Wg29V06 z^$W;@!nT*;@Fm6WWuq~~ZbeD|5ihjEEcv%uhGHE&8e;#tPwF|FJFRb1H*J)HAb-%_ zATZ3|un`ABE3ffkn8#v4L?T+D&Ath57i3+NL7H6VrjcSx00}9XLCoNTea8^xLS$ul zj~YlyyKT+NZn9!<(nGF`y+z)ulWL?2y{qJxmB*f{ug(}O0}n4IaigLNKcqBbBr*t= zAbGz_({CW|vYA*MC0CMUm#7EfqwiX&)Q#eM9U657>_Z_=xQ_KLM zO%6h`rx~)x-7(vp@br}&k(TFMBXDg~(68W~7Id{DO7>I%!1Is@@Z$NA0*S#kM~}+M zO;#+U>;QsYyR6@9itLyZXt?aMAe&1UyFw@2JH?lLl_gE+<6YSM)@Ls;5 zX&SY^f>-?i>qi@tYFRsQFtCPi5dY~o7hMQ=A%`xA!7Ch4v_2OI`%GK?^Fs@VApw2} zQc^|&han&EY+T$iZ))h?oVJ-iFcS2P_&EdlYjyzUIxot79StR&<&wfumAu}Bs9%YpbNZ+1Q6_U5E>>Jo(Gcc?vo73mT|MU zjZUVk4qN7C;+OIaIiiV369ED#h6Bf;tb$G|3w$vB9@Xu`$R4ZvbCmXCj*}^O+=%@F z?=UU%P|G2nihG9%jS$(?h*>v|@=Mlj^g-^oXqx>TK_|sk=2c$Oy!7?DbCN)O^j5Ja zz{rC@_R^7N3(lv$2dGRhkafdoB)-0To|uCK*;$MQWvw&`~J&*b;AnbCAg8}xm^Q^Ypo+fh_OqPzc* zWPK%OH*$E-|C-La5++UiU(+>1{?~KIM86Uve~<&^=M6CY^aS9WD6nq)uraZ1sL^LQ zf3yG5CeC$~Vv=FGYEP}28=rH_Wqf6pxo_YXK*uDxxt$y!H09AXhZG#cTCTkC-a5{_ z%N+N9-9Ij&2NQD)+FiUmcCVLTBwkJp)>R@`@l}*9Yd2O!N_+zuTc;?ak-CRawvt;k z^zi~^YhZmxD>SpY>PBSc3m2?38$48*!Epy=%tQ!zr8U^!w1IVI>7>_GI=Fd7wc{Y# zVCxmr1UiIe5`EI?@3BbcO$i!mIZXkKBc3HkXM5>}@Sv#ulzG$CRGIiCSrXn0jUO%2 z%qFL7?!3E?^5LSxzZ%b9UbO1!=<`B$bqax(RaPih2k`E=37ylvM0v@1i!}hfFH2}w zvN4&MnPa5&YkDRf!YI&JbZMmYxkFo?CzP#){V*K`yvg4bB12^1P-ArAWn@og8pJ7{ zy>T8}r;g02H$f}sj9NjTvesSpv8>v?J?qC)J#KIT40LBAhIPXy_OX~v?1ArOJy zS?%=pXOb4ddE_iQcSy{>LEg!ldXtnK!TlE;VI+vU8O^`&j4kL8atsZ4XSD~#g`Oy7 zGeqF!ev<8TyfzmZbk;|X0~V2gb_O) z_@8OloSoSzC5RX0@CzBks;Dq5iQ0hyOD%F5+l^6>C-0{ET4N;K8!XeeGZ%@J-Dk7enSJ zxiQ``wpU9n8nmzC5P}3s(FoeBXGkf+k{S-V&gy@9;e{_NBv0L=|T!{Qb zcmbg?KO`F&&H99L0;=@mYUbvJw@i%PP!!X7-kRqpAVkrW}Z(P}X7Kut#HlOn0( z9;4KaiG_OrL*-N#+++{f|Fi@p@qK^}0t`$y5e3H*cP^%2H{CvQuOlDf63e=PD_TZ*Er2A}3kqg z;SOi^KKTtFvm~xW?E-yT+S`VA&i2P9?e^Ep;W8N8{ud%WA#Z!l#p6tFI^TdS?E--m zatLuAurYb^6m)i$f<38)L*6!tRLzz7JyexEo#5zHSdQ;Jcr8?=e>Yx%4t=t`t(49O z(Qdt&vg?Iuu4z5uQP{KpX8?1h82cjLX5+DUWdfiQhQMoZTU_7Ogs() z$Y5@4-O?}G&H*$|%Z)z1Qf_vwu{LA8sm4|TOxMcfxlpwYT~GbXSf$v&PVWDfP*~Bf zBjj&*S2=|F_lS8UgH~Ar&gHZS$3gla3sqMKU1XLSYuBq zC|pj}*|05*nI|HNO3`8=>8mw3s@OgK3kzgS-~- zA4}J0_nB-EjHu~K>{aJWO{7RJ@p(q(?Zof=u+?*Q71nl9MNkhA>8$SNiaF>*kfe9-5ZZw9$5s?X_wRv+66j-AiQFTAX9C6boKn)z=SGf_R zs~dTH*P?QqE2LOcv3qjg9_gq)g*=!pQR~e%#vNv(;L4<1^$%3%xsZbL>dFQTTTB7L zYJX{FIgt1AxOn_SE#tU=ueLfv1x8GC!^TY4aWf6AO2AdhCKRXWJ54saLUsu}9e?UIF{9wu)__c$BjVfHHJV;A zhYVV#cIZ5%7iJAy*D|&hb93@El0wF)$Nce4RlU%4s}FbBKDa0lNj0b?i9*!eliscz zodbJd(Id6B#d8UVh-(`Q;ednhCz)^jlD5p2xStUJkK;xI@Xh<>1S@qFad|%OkqbW8 znVl68ZQ*?W*2Pk+^~|laLAs~x#?dbF3&$%-@9lZgq1rG%{)bP1H0d|CU}c!^Dzb*B zmNfDgX?o{Rf5?QfzwnSI21 zkYHzU9R=B?O7mO6gH7q(FltF9hECeLF~*f%HF(3jjpO8j1^k%VLT4%(f70AKl7vuV zemQmc>s02~G!f*z)z$29iJA93EdehD1_jCx^f<^ub{-T7yt-^~5_>@qTbGwMJx7lP6}LNr(_prpAFt zWd~4xIkP1FMzdYf%d;^c2==XPj+g~5Pf#g-& zLgR>80`CNs$QgV}R+hyjnn!Tn^!A|Gzkt^;Sk(-{c6Ie$(>6cGjhBwRj57B;6MV6U zyBD+W@8+8^8|o~h6Ky`hPWl!mg*{7|`$dUGT&_U?A+-lycI%k=(ck3<-YA_u(K+?` z6GhRf$0LMU#JLrFB1u0M2>KU(LKmH?S;g@*4R76n57qV%1 zSR+cm4zfql_dUk+8De}Do~3@VQP8`qqx@vav-B0=e}nJJ|1xs}8VtkQ-oc40NO4+*oMypQV@`FbPBrinn*))GcdlkzS`|6!Qz~ z=|xUIk$K-iz81%pmo}fF5wuA3zU1}IKF-W`zMR(I27;CL8a&tbeC6NBSvxw*k2E)z zr{Px>re&`;;S;Q7v*^^&j$9##Ukl6(>kT!v`N_ zo;v(qg(sg1qnFN$u!z%@WY=leHXC-yQ_d%dU3&h8Ab(Q!4#hKMUu)`vJOzd+1+D~d z1GFL1{z4#D1;d6N!6+}RhlFAD^OKEb=o9wk89C~RJ#*B#{M|a$oWi^ULxBqZwPtYvb9qofWYm z-n-zqIruA~1uuY#RX?v|oB?YR{DRCPM+~$?ob@BF53nk;>w1POhuK5?hCRzHe&qwM zMXV+PsT6T%4z2MHI8V07A{{rfr4j?zBOSz8P3yxlfoavEL2|fI&TorKhD?!WDIw8t z1oMR*Ex3k3vm{4R@^X#CjyxQWdqw(RqYe1?a?AdEt)%|%wIY}}PD%z;v6i1#0Qh~! zO^SBJX8)#`7iec=sslMBIznn8;Xorm`W%w!8meT$?X*TTFoJx;{w#=;DuNF5=O24^ zgE&m7l$G<&e)7zDa@u-)$|39li!uz@y&E0XdM!vle(iREKZ`2ADwR~FUxO(gy zaI5`|_# z0pHNAj-FHF0G+}T$qxU#SCB|GLd_;1Ae6I)axC>LhcSk&!ID55;6I*#p`(v?jrA51j3d%qd;tN)@r8pvbNX_tH_#~N z5tdENu+KVm=kWn;p}ypq)7i}U^BLwI=oNA`1bm-#febi8rK0G<49$NbP#c5ue&Pu7 z3U!x7=M5eWdkTg~)yy$~Vphfo_zx%}xy7tD@1{-JKC=bGXHb2BK| zo-7D9UqX>ZaO6L)B%_lnHJ?-+HR)fpaLFtR?Ren&uh_ZVli996H3AA|AMSWCx z(%F_pOiH)=nDY;2Bnmey!G4Ggjhn&>*HJ`&5JI%GG$*g%HVdXiP=tA+jsfi%t65SQ zq?8j@cE+Bp9a)o|x@%LWY-}k@^@y9xbBTQ@;wq`faHl|ph<=HXT*CvgeQIn9fN?2% zaEpawYPn71V2!CJwB!yHSs!4SG)S#!H4Q&Pi<3cJFx~KaN@k1S5p^P%5s52rhuHTF zak86IyZ%nd?z;0=;0KE<{D*@T%0noMMfj_;lmuARJFca#WQQIk9MRp(lG+~PWB@`V z+4RgO(x)k=C=3^Un!H2>C|fGO=^QV%dxpB7r^@yI{)&PCy-a8-zEqw7u*N0&MhT66 zEMb$K|H3WCKF!$lf`A7eMEnftQ zO|p_WO>P0~mBVF3!B32v0Sid^A&1v~MkGk1t%ND6K=chQUkS3bjKks1iySv-xud>I z@s|o;A+Q&&EYuH-Fa!|#(@Xey=h)N!$kXid^6L}A|9d6Fv$O9KHF|-vj)W!UleoL%#wE7t;Gp<9x6 zlP(A-RpHA9!+c%*&DDaTw7I)w8i(Oxdr~Jc)^YfG{30!>_gJmt$q4t0wN{w4p`(IB zE9;H8xVP*6{uue&OfU8s`uRl2_Ln zkaBW*#cY7M3ei&`b2Ann*n6F<+kn|pSeiChX8Tq>&TAc-^w3$NL zVYFD*2}8aZH2~m2)l9-}UWDObZ~L+RygAsbUt1|x4!X#at|TrttAK*=jZFZsSUB4) zRU%4i@vTj&!83g04C;0fVZ!elG=`UbQfnxws6c^Jj8ERma2K-1GpNYyuvMWm*e_<4 zFZ*8cHFyuU`W+4*NJb}|{D|QjO3g??e)Hd^q|@S#`u*Pk6aGKM8%ZMoRQx|(lM_ip zP*Os9o#jz~mrOQ=!lVEn_$E>$h59q_|I>9$XNCl9GV(4x2hqbHnEL{%AtHr1;=zOu zv!m$k6=vYqhbN>z(sSR=<>O%O>-PF~E1t-i}gF}=)MYQ*u}$xl{BrHy={Y@&GH zY^eOuJu2KnU|P@SAyt3zwtQgH6T~S?epQugU7ciG^Mg|lw?YKCW-QG4LB3p}Sfdg- z27dlz>5oBeYyKrI!6@OcCmIIm#qu2StheP>>R4nu?I zJX#965ONPvine}|{x#GkJ(VXCU&jpZc#1RD;cL%H2Oy@ntD)gkdXIEdy-(nFwKoA& zKEB<=tRiF#E-caJpS+XqIMj!Hk2aSQ6*il?8sOPCYI4A3=o};dsIC0( zl;d>jysNuE)hP4MbRhdd+hu^uS@@}u%YeU6Dti4f~w4u_y-OdV|-qWIxu4wxJi&zm+Z`*e%3g|;(`+{7XM!8 zI>6wx(N55j-A424OTn?gL$aU6?r{&=juA0SF-}bGgQQs&@?vkfyrVB7^;R1P{`ct5 zSYq8F_%0IAw_iq0m+B!tqZQeI@T!PqYd8Zc+YxT-&$81~?80r}3jq-Kw6m5GQFz^8bHe!Tw8p6A5v?|G&v4YC<_OFj`et8(kd3Zy1t&pix4_hUScI5e=LO z3Ip}sB1(fY?x&!wh;-;Ck><+Zp-m*ID!u3X_UZj1y~m;TX06SdGR*2ICyy+)El$_nQ&f5ED0iBF!_aW8}C03bB zAa-+d`AYlG4icGOUBO7x%i_lRnWIgu!D!?Or+Lh*8!JlH-Nhs#---JNS8Lu9xbyp( zi=3)7GVBc|dDnRrjbHs}eT1<4s=@^xP0O3eFoqkj=Gur3C;jZ*^LU-!G zr&*jKRJ`b)QNDABj-aK1i%9+LYQB-*YE`!mR=!E;-HA5HyAYuMj+w$8Vd$bQI+a`% zBNviFF7}{{4kf%^Ngs?MxJFSRickS!an?y$;TN1* znzYVm@a+xh<%(Q71yt=WF6&CM1l2?@r}UrI}22@E%dS9)9y=L2PL;JFofWk(y`JSpqLDX z8`jpc2kNx@96s@MrU8K6%hFvm5_0s8<170FhOtjByI{uf3{v9os)~n=NJAO_0g1Zh zVABd%%;0+$Tz4F}mq9k)JX0wBgj|4%_~q(CJ#F}89%9Yf=qMtvk%2?vD}Q|%b3zGl zuRRj}rUz--cqt4AEj&XE(cdfb_LxcXJCxE9Q>oZ0+TeqGW4`5SteqNH)ie2OE?)C> zGmdGj{J<(1dsjwkSByP8Qi#9nr;(Di{|6(bzlmkanv_1s{ln8=tZ?++&C+cm2V&O5 z5qnmhLjzB9DDMC$&+!g%fZpeQzOuivZ;UL0o8mz8{0y~V;R6+pC9%{iKNB#edaaM4 z0O6a;t(SwW!?E^?-!0{acYzJtJ+Q0c07uB*-=x8?))4$@F7Xvs$dausbVP~M16O-& z|LGHA!}v^{v?uZN2aQN*0yRKy=)_+8Z=3GlecZ=zBgaY!W2hW@i#*L zG3Vt0S*qV2a*$1-J?jyVvkLZtBa%WSA@W;JSQ831TF zHx5%;G(+9{m^RQELa{DUM!OL-xQAyL#DXlSTQTaf>*qxgf3xC_th+-(&IDA-Fu7b#_o*gJKFMg|~NnuNAh zv~7Qb&ksZTx6lS{m$%8YIk%vQr=fd@?-X;5+UIr21qNe-#=m~Wlewu4Wv=M7{m}Lfct-P!JypG))+PpVMO!;aoe!Ey2G4tIji181H9N%Z5*!>P0%&9)kd z^Hs!}Q*DKeliE$PiF>8T%{C7p38Rv)Q*BDz;;HcPC)3LCvY;AN)^sPbtSn?`2W5v9 zbOb1ejHL1uDHlqHfnn|nmmhW*d6qyWiAXM7L>n4^?n0tzyX65Bw9YCtV$MG$u5fnSPCIzPKdidn!{cKt=OInFY<O_65e(4m6jj>(r+GP9S`_g_21ajkkIIA~ZBwyHSPy2z}M zn-v^#)4X19DfwQOA7nVAW-Zhlih~Yps=Z|=$bhoF%G&98-|oR~g+Won(9v#}up5t z5i8fYQVE~dd_2`s{W<2wHGTIVT98YnqTQKJWg6`Rq!VeYU)UsVI>~b$L;jv3yKkg? ztY0kN-oAMgldw=*G!p_#cg_;zApXv~vrQG@4jOG4gih|S%_sE2zmM`D`h**C=B_#! z23%l_d`385|8cZPLsDtzQaCJP~T z9PjnVf7sCGNU)XXpRw%z3uf^XYq`0BlT!TxD4$E^Wlf)rXN$t$^NkQylaxeJdLu(3 z0(Trc(u%FwC0AwPi5~@h5Ri!}p27H%IA}fYm?oYYwkQ5RO%G%FLsTMkMh&x1lJ`(A z`p=Enzmy+ey--Pm)<$&9E#pj38SO{oTn3Ev+XWsZk#yoYdKMFhX0!RDf<(RpA$Uhm z2ng91dQrV?@2-4n7(j5#se(a7MRjuFm2$>r;wJdhM%`_|)@?*$oR?`+*nlxxH4V|! zwYWcOX8R1yOiUP51^w2R_@Y>v2_r04&U)q?nydYlf6jvNMrTG?zH@KFD7A%p2E4?x zKyd~{KdR6>+4ebG9~x_Syayv0lyEJ+r2S+3$JG(=Kd7%2Fg4zWuMFD)F;yxkj19jz zm%>fxU3Xb9TtCM`S)tpmg-hZrvx;RQkRR4oCsUN2y|7}cAgi*_+(>?H<~EQFT}Eo(2^iFDwC9AkZet# z5#q&Qmt?l+QFxYOt6#!xe7#%SG`XV;8*A;Vz`aJ#Yl%X9^HsR^sZ4YeN&bkonEJ*P6MVr|jJh2uo4C4RRoavA zop>D5G0n?cjd0Eq!X>n=8c|MhZ%a!)4Gz)n`cJxU?l5C;mDuGYOX@iWsgO8D9JF@2 z!hD_J@aFY8h}+A;)lYm9L+n$qEIoTc?1;DNB(a z8>2L)>6rAXg-qsq?TKuWs8Q}vEjPw1XyR4qY?8`HMrCKW!+i?^f6$K^!Gi{oMuFB{ z3sLRPcwGu}dw&7)N1aF%m$ezL5SztBv-fTH(|6vo{1|3W-SI*%5-ILg5L4aQ4$!7U zFWMOO_BkIBCS2lSZC~L2ZkEj76ma41B_qwF?sjU z|04y*)sb?(||E&lT#$>pD6CWnNH!Fw((H;ycad1NT?yqe5d^?Y^y0yDtE z1@Eb@=|QUL6Dg-$Rcs|JcWlKk=gF`nLC9LC7#AOCB@v!OPeeZ@VI^XHFg@!30M@Z& zH}`Aem^%G99V1y?$1UANu5|4Oe(cWypx;HrAm~Pm*U&g^mBo$^c&3efTJQYK0nru& zpE`jk7Qkugl9NO>Qir$>7P%}u?1(1X5lzcIM&-KE#iXjeSgf%mz3Fq1anZ<|vZbjM zoq({xgU*zx4JmaG>2YBMSR{BPFm&x~Pr|^^`MfgdSK}J&%#Rb(Tc$kpMDJHEE2@d2 zKSM{yYa+*vvLgdCy-V1U`hULZA+V^by46N3F{#agLYz4` zUG#=hr0u_hMPfT8T*J+se_{RTmzSh|(WqxzM; zSfBs7)+8`1DDJe-GCROPxx#p;_w=>Pl|mSC{~L-(!^0-=PBN&37@ZApI0@R-6gw)KsEY5($Mcyky-?|xirLHS zW9XR{=TXubo?YMKgF6Qrf($ifB(Mq*<UH0{XTb81#ye;beWBetn$eD6e+qycgClN!mf#Dg z%>N&YA5v93>ibvOg8wQjE-D6O9g4$}+-Y~HC8<&WPF#;R@QqaN-*M2Me{19L#REq} zLq%F0=g(Ur9|$bEpN=~a&lDo--@c)xTDrQbx=v0!5$gAR;~3HnK~7Djhq;eeFHOJ56K3EIa+d&YO$3sACzE^b)+nbAM_Ua^30JqT$TiegvS$OGq^n2tqs%Ie17$;kFs;gc zPESj9ydud2g$?iG9m)8BY8uw=dQCF}(PU_iCIVW{_?VYX(_c$DSzoJ+QRC~Gu6opX zdLa`ulUY2;(_Z5CUd*>hHecxHQV9m?M3j{9tQ3D+zRcJ9Z2z*?g+hcpl-w4d7z_7N z>ZJB`lBv#(d5X8=mr0!s&0=l5LssT$ue`Eup}(dt6n1pnVTTf8s6#ddnp~s*&l}HL z@A+c>6^G!z;_!+q02S@$)i6FU=N76QrKNBwRN@v3Xy9ap5rQiNkkmj)XiH^+qVZ&P zxNk#_=PSEwa`7mg*F*i;9)`&4``PhJO15)D=!wl=EEhTu1sPzIDL(%s*m2B#?9&Z= zf4HjwOS$IkcSk0uRKH5IwX=oWW=oZ=FrLa#n>p_wh~4-Dq<;X{R?vZ$zgCzrOAY;1 zL0wtJa2ays6zZM#oBd6$Z20Y$`k{q7Rpio~XW!V_`CZn^9R-S;r)7LfpSzAe?CI-w zQ5Yf6fauLx-)e}}=nsgyPgp?E7NU`5xb;8aY8Buz7IV-{KDM6l^d^*21HImjY{k3`_gibq~f&{L87;FV|hGZfi1^G{_&M|VK1UbXzE^}wXWXvHo@5ZjI(%@UW2 zNVlHFJC-tYoVeidFa;ByulY32ktG+^p7N^s?c1#ab3NtdKwpc9Eq`w^ z*CYoZNaB|IN|2UvK@((bk8)l|*v5M^s4IQH*fryjZRiDrWA9*EkyGl#I1G$|FDE_i zgH1ug8)VFKX&qrm%XAEK^0n3Hn)9{@xrFcUh1QLx-`CR~$)F+V?N@gzv zmuVq-oA4n}1`4|GlBvK0QGm<*(AMYg&zlEw|2E?0$Xx5apBLGKQ=O!~&H)r-dHlxp zedq0_{0#2zDM+4We*9aoQD6Yiti4@qch$SmuOs$k=dPW6kFEm8o+bO`@5Gov2BgZ^ z>Oa+`F*~9#?BN%$e~0<^ZvGs))DbAz;;?e(~n8zm1*Xb`ObOfp6K&Rm}pt}`QLsK%fjbE z^>4p8_`mb*Z_>iRb)|U)4Bb#|X;^jC0bCq~c_Hm@y-uhB#CrY#-wgj=@8Hb|<4PoY zB?Ly15bnV|N5!Nln&IWR48=Na?Cv!VVvh#jwpXnt{oo|kIrlK~R<7_ya zfT<$dX82?Phi!HT$DCLZWiPAG!)a8N$fq&rg!ea4`L5E`Y_gBVu&st<*6)X~weIV6 zERyq-kgLiSa;ac*^+Zvcno7k;gvGTyA~#&!@zSXBi*1=)PV?G&+CPzqkI2qyN%amx zqyuxVjx4~v91TZ7?b2}tRCKwE%P#SGZ#^pY@i%X?_mNnu6I zx|-<)3UwM0D4#ghZ~0u<3wttP?AT}T0g}Vch{Hw}ytK`&SuwQU-O8ncSnZe=t%Eaq z*;!*5YEmY3vVOd6DC+6B&7k*0eq=xs;v|girvzhi4nCc@x^AQE7IiV|B zmDv%?DdMv-99BR?9kaEuwR`d*6}I?=Wg<01qR7k3FR=O@Ngp%^A+9BB3zC$%+k3!s|8zvD=&uc?5seXWIj_r8qqOLD|z5uV7zRkK9=Xj|w4D zUSkg5YzZA7c-i_!!R;_cfH^ZRu)M2xw_thT#I%gB5mp#H<$I;NSw z@(Ybo(*#Duk{I({!QP#Oe1GOYNNE3tb%7`UUoi59dwP8IFBn0E`u~EFL~I<4L}xjA zpgNono+|cNj|n^XrXA60b3jpJ3{hU2+x$99fKZ|y5e!jAAsy|~=;gRs`evG`85>Np z*H1nF2yt3f#ZIb-HP}rSkz6ZFOk|N85z)anK82fnKYKIwO;YQ>@^|C*Julr)-TS`F zZ(GLG{Lc*jt{meI2RpslLlBq{QZB!(fprnZ5hn(szM?Af#S6hkW$iy?&KTufg2-Eq zoV4(iCJbD{#6u@t<|-|4RM5z3Y9t1OB!6M5ghU0%W-N&<+ZJ|-8OHz_vLsM?@st9s z;SRNQ7CG2eXyq1A?S2)8Gv%g-bp7&oexR-7k70QXNp_Ww>B{9jT6Nsq?=|I_^peapI zNvyZH2QoT6n7h^NwAJK-i@WI?^!P>vc)wfbEj77TIC8yV9B+R0BBUDzo(+}?u?9&u zjE+0i-!b`t2txd6MzOVgt>s+l9D&@3n z9E3$+Q`j}IRYN+r5sJkLjx#!v1Z!se;FEZy48OJ+Y=)Xl4Omj8k86Y4+ftjSr=fll z?8_H**ta6|(ID>D0;GQdV+$V*aQn+cCLC`qL$TKD=3(f6AXM4%>G&fIs&n@jC9MZp z@z^>f@UeBX+9E01l__>?KhIDm%tq6}x0WH^@(DMwu9XxjS)QC*j=xZcGCkiqB6|UT zD9ZFLlq6sz>7kY}yh@NNx}O#w_S=O%8ig)Z;mYa77cCpdYOH1ebrma#2=(^ReQ1&JHOs)BKK?l8&dw+`8|qy)nPosH{NTwW{{1YGuFiRZsibY+9*Xv)wRQ&)qmrJhxUU{rctQ`QrP*?8oHl>91P-P(P7?}mpv3Su``@mVTy^(5Zc3cq z?kz^?E^vdSo$+)zZFsbntf=UNUuN`|7|SBz26IM;z2Id`J(^}Olp6Mf>%n0y%2=g# zx*q%714I3L<^{?Idm^@LxtIOiS>WDSLF?b!f;&dZ{EXAhP(g zcAH&IB^6cHz>*E~1SL;(d;1ofH~nmUFwGKf4K)_cMHzx3&@XXwAG$HJlu44b-v?RE z!iNA?DPeqxNM540_3U)WjIz1jgZrpH2Z=ry0Qgs3qSrN1IaIptQ6@#r5`UC;7e_>_ z0ybQ~t8mw7vv!~F0rIg38Xuk0liu!#u?opCWD^+$@Pxo80Y0(Q+8Eyj!1xSlw&~$1 zjgbc9uo3wdKWe5Xfgu^@awCgNn)%ZhfywLo=Yz>EO~#1AgFe&nme?6zNNDHpp?(!D zlS4OJsXNkNkCG+*?oM26hr5eVg%@e$wEEq>Fz6Vg(Bj~fuZVoqQ?3!adu_+%nTp=& znS-{4Kz42diDx|F+3X+41mjLW60Ul&D2dD2@{#A8YTE=rmz>jXPo_MVgQ?e;V;|jH z_`PCq`mS_EDUQ+;p@$*w?InYuqFz8Y?Y!n>!NMy&0A zWPsg>tA!#h6#RISxT>{9K%c6t<~;4HOo@_9!~8GtMn^BHk>z`LrQHt-c7!#ugH0v= zVquYF5f<4RLOPtOB@W4=PvepS*ax1h&bx-ce^AHxbV%QcwKenN4>boXm!JpCb>v#r3gw^ZjH(-u!CnsbT?%7 zg~XQ2Cqg^T?BfCM>p4Gt&K1F}Xt zh)9g&_GHa&Nti>k+l=lM$yOug%U&WvXGmF{pQ%IZd~?q=K|8B^v_uqtA6=6yB&Z9a zDQ*c6B%o}_BOJHYkh>!Jrf!goWU6D_s%t;}c}?BOjY4yBEhK^@=+A;Q>rr(E!5bV2U!P}6@{1@%8Z zpZ<>Te2DLmXlj2DPV5wX#x@~*e*YpTW85X5mK7tGrTbEWj(z6WeMh;R2JXy~wR}bW z;lCp0QTqEO^gHYudx5Duv^>fpI@}L?r?;MzUiQ?Er`cO{6QVNx9`2o6p!PLi^7ME; zjkZlpGAF3OoUo>*3W00L{JI~G++vzTP&*jnpg{Q<&aR&bmtbg9E1#kum6Xqa|*7kYom2Kwr$%sJGPS@cWkqh z?AW$#+qP|WY<29M{=akT+^ktOYt5Tg>tfb;$9M*JV23Ql9vo_KYkASyx6Rtox9l1L zd@8uEkzyY~iq&8-h3lS*qR-m5Zr&mIS9)c|uQvwKzrFv-E_=lXB9LYcVEJomFcPv%WsO|wTLrX#D#BWQ@(!Pl0 z(OC99`(1v*g7REkKN1HziV&8B$32B8J**q~3V2j*Hd|v~`eTI*8my5<8|kJO3!Wl& zlopfFB6)00Q5crg&J}W%w&Z)NN(K*QnIxuR_@;$ed^X<4g48i;Lct>kJ9V|>-ntn* zI0Mvo{#~kk)1>ogX8ye^u9vs=1uBSBY95Df~Hqz8pjD&ak=m$4H>HI4#_CtJ!h!rpbp6mC@l;-t_vUqeyHI=>R_R7d)J}0!> z|J#s$@|M?s3h94hPPNio(t2V)004yZ#y4#iGJj%eOuVAYOkylHmDcIBY=B{iYtd23 z(A;dwY+^?+eb19~qZ(h>&aUIzW(n<&LeKg6b>S_5)oHks-*7e z)*oJd42G4t`OaLIZx}CG`g2u#b?NDaeg%1BAUI=|4 z*-Hp<&2RHtYhMT6lmjx^ z@w2<0!ln%K8+IEkQAVq3wlsOvVoYQX#VZ}OxlKqtE>jb6PEW}p&;XXa$~ikI;U$^M zPPz0)kx{yfbR~GxGUU;gh&PIiH^r5Mnvh9Mu~MR|l4q<;kL>87AOn8-CeIY!r+2Bk zn{@b%o8oqN@|x$lg4)vPl`WvcCKb3&s0|+WrwiQ1qYstQ7AP#Yq^2ywCa26_7$*B- zYvvnmaZRF1cKEn3L)1fj>(PKVKbunIGm9sy3)pf zgzO6StB^#n$_GPPTc4sPYb+MaC9^%7T7k-z82vsB(gz{c@av9Q(VPRoVm+#?#h*D* zYQLa{c~}-Qd|~9ddXi={b19(N572cliB{8csAg8LWCJ7=GlBZ&$lw{4jq*)8vS<1m zR<-^5*PjThmgz^ZwxM9`@TTzKq3Lstu&(~KQG!WJKb1@y<|aB=Pg3@ZvQXUT6!Kr` z(lv7MP-L?R`w#6l_iP=50=ir#OB9Ktm&QiFj=EG}jUH4JL2Dh3DTWAIL~uL4OE+0e#Eq(~z#-O)uKPtE!u z;nDejaT`8BO^FE9T~*WwE7@aPKnHE84*qK8;qcayJ$~4L47TfoaTLItB!_(~r$2$W z&*Op>w5K1bclDB`EJPrK{D#(DeNsHt3Hjra}({;;pkN3_H2ic~7A%JSZ`pYuF zDjc;;OHp2#AdWbZIoDVsp9Lc~3nxzKf|mY+2T7-MG` z^sZ4^qEaaEEvmG0166~k!qFu;hcDs}j$(x8GmqIcK3GD1PMpAO#rZ*6fuFf%38Eyy z3P9Fi{rk2QUudl{N!I8H5N^$Ep@Ic$0odvw(f1llL8a0;^V@_4IrP=4R6?w+rFoj9 z5Stn%9fzB9L-Tc;Pi-$1VIX4qs#K~}=QF-+pLK*4T2_Gp{yPLOgW41NVg``VpoEDu z6Jrg-cRs;C2n%Y~KUIaXM{c(4f#MCe3wu1SvzEvlaZ=S#KledOwdmf1?@Q%0p z!PQIQ^c-&>mCs!Dq!oM&m@mz-z!1znvjmuN{?fMV6`O^#>x~38a->UZ_VD?!Zq0KZ zKz-s+`t(y{$Y4uWs7`hZDZT;@J0A>mZ*=%;ZojlRY(0KF%`v> ze)U$D>dS~*!FLKwo5^I9v1W{qihO&QMJEF9t5x$-ZlbiC2bL;}iJ1=P2E&toGJGn; zy%-!KE!J^$KS0fobx8q(>gULa88DYGiiH*>gUs|Bnh-eS#;6@ zHNN~v4Dx&7=sv+%anI}u=de7^fKhX|V#oo*}Yv zlo=Ig5JpbsfvKh%YHp2^)aVgCAG%$}5}au^Oly%9ea>n6?snX)vtpuQa&%+Cpuee@ zZg0J7=s9PKL0C1*bs3yExahoh=y{ZfV2%CCjNy@sm_r~(mF&E9w51jsfhnH}x-+sk zg~J3<^92=I8m1#*dm|(aju%-clHL090^u3= z+U8>Y#qJ7$9)Z4{i1lb@n`?oi9dfjD;4-&!r+_i$B^&%IebvNl!3nh9mGI1CQMmNuwpfl88ttWh0JF5r68@ z>H}dY`Ms3a>#&jDy!bIUsri>M`S+_8d!Xq|BsLh>zF&92>1FflX6>DzAhFp_VVH2+ zu1NfK22P@^JPv9w&^k7zFzr(uY}n`4E8a{aWqI`B(j>RM65m)&kPE+8$p0LW5L-g9 zY}S9snvosn5r;;YXPls|3t3JOsI@S+&q_7PXUtQ|Xe+gSyNJ_3DoYSk;Z_uL02d(+?X zV55OIw}}SUL2WjA#cqm2!En8*F`H8|u?Qk`bMRZOCzA!D-OJq`v07CNUXXZ`*9P`R zM=R#IM}r9%cY`4#%;I_yvOo5khrG2)Yqk9OVI<-VEYiA~+eYGSp@igJEU}}2o)Wxn z8}=VV$83+i2Lpv#jNx0ejQ8&*RC_i4h&#>6LGLBRWI%W7|0qAUUT!GUrV|U+XS!_*a zaOH|~G#JTYmnN>0r$bsWddlt=KPWcos_5{SViV$<9cl+>Z#C5tUMrcc#8};=_GnLBtooYi|QZ_gkW!1xjoi?a3y~aFr`l6 zbwU|&Ce8GcshcEr2$B~7GeLmKvt=JZB$&oXHb|sL8B`Jieg>WhePs&)&xv+^Qi$%C^~M^G8Lu5L$uX?{{hXgFiik;j~YENafq6g zAu9sgmwZ0l%yuHCEhZBs@CnmHn_e$Z=0sMuYsu)lLuss`_Cai%eobRe7OPw(IjGzO z@jL{Yb<=H;sq#`CzfBiF0w4Cbh?h?At*<{OgW@uWDC?7-hI$#+1)fgUs6IqgHfzc0 zY>jxssdEtPNu}r?;lL1+bv^>PYB3GhE^QTu8%)T2^fIv(G`WBaQJC{6P$0_%g&@^Y z4u9msMy)77SNI&sH!qP1ir6h@rBW^m&~Y+WhNY0bh$lxo8yq1a&wDhLm|Cw*kqu$B z40LIy4W@vXu1O0MuXPEA4x_b1Qyn!qmy2LB?{Jm0tK?8pb2ikOtPuv1>gnbHc){p2 zO*A>FQI9FOoakZS*!3q*OW|vWd8DmUdFS}0GL_+BKkM3BHH)hE$&At`%V}Ea7C2pg zEVz}7fOsQ$kAg`y1;G&0y(=!A`6`B`cW6T_dUwQLpaM*hLBrv(kSAvOoG%uqG3WuIBy|iIT!O1oJ)03*MIhZGB1s3Fr zbadADOCGwu`F2r^zk@iL#U;v|X1O^eJJ0W$ER!}a$SThxZgg(#bxeyI_!K)O%DEIZ zH-TgaOOWmHV`V)cBTbCz9fh{D|F{lkoMhjmg+?BaWYk>=P9e(|%A=rc?3w(m39 z153$)_r?usuh94dxK!v7e>V5b^ZU_67jhzI)FQS6#5wR~EZw~BODiXbTfsMPTxsUy z^RAy?AiK0SM32mzuJzeFsFz3aj}5BdGRS8O0^rI?-}>{-JEw;#E(YZ69aBY^ zn1@Q_v*9CFW zVh|ffv3|fiEhVmZy@Q8eOE)}PuNTU1@;Sb_r9$D|r6evnUrt%x;v%-3`kw_vOiZDA zHI&7GzhZi|JMZVxy_En*eLC`L4SMCl2yqP>5^J`5Cv0M03V2X5bA^5d08JxPr0TE6 zJ9Q8X3~W!czn$YZ;HsDS#?8O8u0c);b(Pa6@3(+xmy`Dc($=cx;nhA})U%O=@)H70 z!gKe36Zj39%nzrWePz*mFUvH7*c9&&mhfv4qV+HkKF^91Iutoe6m(0eY%X2n1oEfx2Syu zr)+`0y|-9KvbitV)g$Kuq!@Q!w&QX|1$P8Twi_>J8Z~tDNJZJuF=|}}cX%cQjPZlv zfA!zcYVY~X+l^^?3KW!66Zo=6-EnxX#PH?do@lWHgk~lS3h{}K{L#G2tg}=>kd||I z>FHTUBoSlo5Dq>|vTE z!a0fUkIj;o$q~}7_A6DKHpn?q)VZcOcm&Uq%~I$Uvgp*-!hBLyxTS^`Y1SZA`m6!g znSK%FUt1lZ1(s24tLo=SGAqlXArV!9Y=|5dTGY z@tM;>6O=!xIx#7HqCaJ02L2^IU~q!1L?`jr>kOC=f$R2q8Uqq#n29=I%3|7c8#1^UYA zTl^7Mhhs$z5Wox};Hltx!_dL9_6E%v0R3 zEEUgfvPN|S?PG)MbNjKE=vIrH{FIe3;3&WygUORaIo`A15ez?Nt)Ps-8`2)3*^z>| z=maa{GXs@Pb!1-L<~-%O;U#$RQRC53xfQuB8NOAyRat!ka9{JXbFl}upmnW5Ks)*Vvm|Rkw5j^@z+1mSAjW75|q*R@;jajWKYd0_I$vf zHc!TMpiq~|CC+`IR+k2rmI1sHFnLqvJYzr@oT`X>3sYv?+2?;r;_2LRH`c18fUt;?rN)Vs#o3wXCbq-q>HD0ZkXnKV= z4~0ZDvDfpN!tuYM{wJ-Ds)LA8V1R&3(EKN+4?3~{5xjNOF~0v4P5<`sdAI0vlYL%x z#dEP;vkNQgj z780N;EaC!$GQ54N#JHH_TF{&GuQdq`(t+y1T!)jbd#~u<}pFG zqBD9ID8YtV@uUg$yW*lU(5-1U0z1ZZ)LWU)WWi%ADotXbXk4Fc5AG?WKRVomUHR&U zg%qZ-r-SJ-64ysC($s~EiwTy|uAuoZ#rmhfxKt1%YIle|O1&Aq&9EGs-S7Z=$9NQ# z6jn5oC3lTcIFpH8MUPrA@*MA_3BN^66KP2w5T1|F4t_LRX~^a>7SG4WtgD_Q#UV<{ zWQP<20yL2eJ2Pq|3Eu|+Hy#hbi^bnUXUiUGuGFyv zs=_dlRSRfv4U2-NCW4bz*a3wN1SZNIiv zc}k*sE^#t)Yf8e%L@I?j5#UC=T2~+nd>$>c{6KrP?ue02n=)X7*y8A_g>U4bE<>fx zn^XNLS)#YV1BM)C=UfB@c!Hu0lr&BNcLU{eR}L>ns!Dld`s;Cz3ndKC%f=8xov)jU zFksRhA)0Z|wYo+3H=@gUb^;!pP>;pH;H-~-Y8&|@q5cqzkusWkzuo=CB?(hPz`cOPUU@{ z45M()PR?OM;zsDv36}4{XVExZD%+_zU}|UTdxQ`agJey^tjDMu8x|PL4zLu$YN#Gg zac^JT1)9~8(h)Q)vlp23<5n>MMWJSj`F4!8;!U>rBliu1XiR19DW*K3>ssz%XzrlZ z>T(ilVxdTbppRZv!VzCpPZu11FculZqk!-oio3sI2PW~mL@}U{#S>!~Cukrhz)*U< zxCP%sG5j&rFpOtuFI$Ed@FG%oFk7y$u$qAmQi%D5op{MqZbv(24&Lx!*2v}}34c;b-T$3oHSoDKtKWgWd49pek zLt5`4Qs$&G#?tYz)%`$9orWSPjDFtp-FZ21nU^{^iD}BF!L^ne!z=uimewXs-5E|? z@OIlw`dih7KMW-Wc!%tnx$FgKC>@Q;%wH}cxmX@_QCM$Z(K28Kqgp?cY-naQc9=nh zh&|$=)|T=u*mLA3QEGFWmidEUg@_(j=Y!nrpQdoI8&} zLX*#V{^7zuO0pT8o48>(q%b$e)P}PbY>*Ji;Kqtt5wWfSR7VPw!`Kerp#>$FSjVD1 zyEn1oWI_Lk*w111nre0&Xwc?3*tPJUG8mY|^^N`$MR&3;3mkI#(&^#pMMFlQ)u%Wa zI|?GWPmHfMb(FZ)UBqjBU#vbRYNJe7C~-OU2rR540+MH5{S=GhMaBRYB+R5^w2rfc z_FbhFTCtA-i&}46Bsk8qZGvSF(5N{7VKe-!ZAbg9lG!Br{tW+#yyfcRYT=Y=hy9X< zq(6p_U(K ztjidkM$kB>?`bO@Z}U57#IO6Bxt+m99z6_(Jkcw%ZE%=mbvf!T(S=1??l_skWfC!6 z<0npNUtLzRE@7FZ^|E+-+1wC1OL7HFdW!S(De8$!WBaormcH_MW=SlK2|2qJHzJ>q zDq5onP)IK=bZ^YF^t~eAnY5$w`{N=FpK4^T$%kvgIr}1H9wbR zZmn7R{e)BH=}nr+*H|{Eeb+A{h8wz(m#j2nfK~?CQ9K$;{65Zemx)n)zz2|bpvTXvK-q%!c}2fB;1?K4va&bR+O*|=0usSt&VXNHWTOV*m^?9ezvJe$rFiV1}DnC2tXn) z1KE;xekCl(%Bgs@|8SUpW0lLtdWPM%vg{2#t=i~&d)x^iC@b6aw|wMNI@|Qe*%=^6 z;|St;_Wzbqif%vi3Eq^Zl6E)H+9z$EWWKo(lD`fh_p$;9TFS&9pihdDCZ83#eg2e4&ym1V(me zr1td8c?L5=B6giGe^hAtfEZv(0d<+`Fh>8bu7VTh$GvbgeBxhGqz3ruTFnDGZ?4bby{>^hk5gC?Yc3$5#XC@0}(3o=(- zyUzILDQMeTTxKDsEcr=eDla3q z838_;pIx}C*~QLY_)yLWyUwN`yw6O^-5D}u6LG8$sKevXS4>Yk(1ddng?WkG(k~7y z&`UzSKchFWBsJ)3yg2HDl#~2mdYSmZahducZ$*^mE7hDzy{sj_0HfBE2Goe)NzjNyqY%)p zN@1sc8>-w#cZ_e7S*RRtPS9s+k@afCPI(}y*Iek{_pB#EW{OB9?=|QeUUH4Tkaz~K z*Igi;-`}|IP`{H)@11rnJxpg6+Qm)cS3M5ZMUu&(x#!c1mHM~Dw&%qC+st+9CiN_t zx^eC%`M305c>y*59R$uk`u{ulo!_Z+Cl~IX+D4a_n&bgGwFtw{m6zbBxhn^{tI$@D z2=Q>pRODU)rHKmt2L!_%rOX#xo?ep0zlw1njkqA~6c8d^!;yB`0YXtjETdtLYZj7@#K9xF=i2+v$$dNTYGsQ!T&38wBw;Nw0khstDzRxOlfbe&PprTCN@8W( zR@S!sxFjEId`Y!k(%BqXN@!!pW{oR!e^s+WzZUawzNLa+kv3MwZPF|`a;IIz#o5A% zs~_q04~8L{=bi2%FDxmO*yr?1REWKyc)XX5Ret=1s(!j?MfT4tbFUW4AgC%=1CEncd;5chU88@|&4Ln&HFSRj$tr>U-(rdEPNy(THTacB4qxv+? zOu%42c&+mmLtftxwUwG$1Lo$hsIv_=vs}L)0BkLE!T-Me&m2Bb>%?e3B_NCk-l(gu z7zlV<0AfOc$!Xncl7&CF6afm2SPMR3gFH$Bx{9RXcuHztfG*6MsT)>;#j4E4m}N|h zC2DDS(umXcii-|aGytZk@aH*3r|V*o3~_sUlBs*J8$)6^~?WvqIGH{l?F&T>**Cj+Wxqo1m)h$_7E5 zu_NZ)DC@trr{~9MM&}*2X~x(B)tiVj11~i(1O%P?IG-*TXg^Q`l7J|chNX}1(OHZZ z*`~3sG3x-zQumzt=5UzpYkXz`&B>#WLyV^LA~(Rrl;yG3iT`|}*T$o2civkT2WQD< zzzUUhmEy$sb^s{OMO1oYQ&e7bGx+=DBC=j-uKWpXj3eNDIZ@#vrqO_n!*im0ITB%U z*;aMZ)r@2X$`0k}8QEz3B1{P>JrvUiR0;P8U^wxco#NQB~W?;3S{_^?2n+>C|3 z3)+kYw}hxx8B>f7a03!~y_aj}FE3#i5i{5m6IH{g_~E`>v=GxYMfI-qXJ_a(dtR(m z2aH(h*ImwSOP|RNo*xcQ2%K%8q$)Rdequ&)rEUs_(7e0J0o~u7G7g}v5L-2`D4^V- z&fGcztMg!CHHa=sHMoBYS##HrAv`I?ajIsDW}Y&NFsL-`;nGX zB^B8avzBcu-c0p$D5a`2)8FSdR zY0*mkKJyKJJNqG`(<2G~YAHNda*Ic*60(>l`c6$Vc7YvxhRO~mf?EJ)(-RnWPBE?7 zk^y$0W%c!K-D!jm)6_T$wSlEWE){ypTsZ(9$0h;xpfLjTU|VYxr9bJEU&2{W6cOE) zfuOP01)NqKMdzJKv(B|gQ=MevXp>{+aQJ}EbrGHG;gUcms$KV9)}}A#(AewA$m5VA zl5lGf1^OIqkz1G}Bz4uJ{dkXu`n|vD?gjyksLLddFQ8Y4;NIXYbP5->Y9DomPi_p& zpQckVEGOoz6U{d1Th?nGgg}zRt-kQ;vEc^^6 zVCJ&NK~2CiFa$Ap(P9#tFAfkz%$8uspk&Q}%l=Hm#ooP|Ss=H*!ya1XnVb)N0Lvo6 z_X6F=DQDsYmwkjhyLv!O`RtEaQRlj5z;1^(4|b<@$?;#{reg71B4r!tG~`|NQWDYu z02`s}8-KjpdButf$=w{O#dP!&AT7ks{fOBk8b%fy9{S`AddI9~qzjPWQ52f#@D^6` zwnSp6zZ2`aqbWjJtvK!A)m2^2&5NzOl;pAQs`i_pmcmLmdOtI^5nfVaw0ZlB$|J;J zK~cBJcCOVPQ0W|kxWLvmNcl#itO*P<0@@at;*o2y z%1LplUjKo=h9*tsm2;r9%XK-*LIQW2)6?UiS-XBN+mvY_s$$C#YU4l02@vd|Pb4}A<}n(yG-)6}xaE>UQ`6mh{ebJYoH7`hFHRr*e9cq$ z7n3EA$5+*|9}cU37+5A#fx@8}R1cU9+A+^y5UsRKA3b@S72E8u-4da@V}vFMJ2Sz(bh8Z;F$$ z-n`oTS+p+LcIkK}6Us4&v((d6oP1z3ZNn@r@o8H@9H^DwSIR36@bB)C7UJ9=I8^9* z;E-Obx6SLBjxN2nvB(?e=%UbKFEJK;AYPga=!1RoA)Swl#a7FVMIrpnx8JWid7f>k zvtDf4Z|QHn>?$NRh`Vo5LJY>7&W=n%1KK*d?JItMequ0do)#f!4UX*vI8XI9ACc|g zcNk&OB^E{y6@yW5;6$6>zuvS@bv1ls-zDBw5A`>3FvD370UNvkJ0zw#GhZ(1l<+)K z^m=cR0lfy+TA8+A6j|gN>V(Ee0-psi=bbBidnU``vWe38ZGa}~0`02wUivev)*l5@ z@>yq73uFjE9fqG<_-+8I6*^LKPCw9FkMm`GvTaq6y+99HV7Xb%UG71c;k}A>s}3pD0Es!IpL3IFo{|(9*-Septi8N<-q3U@qrBYx;PO3e73Hj2JP8 zIqS2Z*Zc*FfUJNLdK7d%S=GFf<~<5y{mWnJoqJO(o*|LHsbnE?)}ld?5}&7j!;m() zK<*QQ5EZiz_OLg_P01GC9%hQil3t^AYZ-FudTzKGfi8A+ZZ)7j;G%HoKYuf)1AY{fKg2R8|= z4to{$D&xO7DK?22Brl-gHRfa-j-?-3gm)s{e8^qBGcs!C&zE-Dn}60UY@DjY4%aNa zO`-}SH2HI;V1`506%k%FSQJUQ6EZBML>5gc0lgg}t|Kumb*yepD{?zttH(Gt;$;*T zGiz@Cx_Ihz;pG-b$79|+sSRirUBeaq6nk0odFaxV+xF(*#rBNfp+5yJ--30H7#X9*$cN&u@Sw^Zk6e0- z=ihx{bP%W(T3Q&YFsOACnw&dwieB|i`*CNRc29YTOD&(?pnSnHoAWMuX?mw`H!-7R zcZ!={9>m2fZ*Q$Do(uCY7tf?~DOXYX1+=t^2=&fMc_S4Ngs@%=1)N_n*01+sB6&u- z)JO>hJ)YG2X5>7$yaK%cUd*aUb`7@{#@pp&=06vsYJC{D-896xFRzgL+)}rU&V|P2 zJol3rMEn)RQV|n>8;4V($)H`J;C^2(%8gFo&AIg=CEGa-W8zdHBC>o-k83r_2cD?Z z&CYJe0k-@g02TySL(`nZ0?wN;f3h2&06$=eE+2oaU0`@~IlSsgm@}F2TXd2x7&x-` zj@fNow!4d=x32f)ME~Tn2{kr9y%WFl)aN#U+BOJ0EXJDX6R%fman$7D&FPlVR4xBh zYSb!HWV^OwzMeTaScM?IZ(l;b0m3hiMm}V+JwU)@G3nslX#ZWURORZ$QB2N$!2MF(_8v6^r|Nbi(jIJ0lYx9OiI4u z)^1>!dpDWvrGFNAE3=XHRo+E1L~C^2jj>m=31jIsi3*%wga4d9T2dl+4Hk`RIt?$e zS6KY>gQQPsQD~P+GO#a!$PV+dxVos4k$`~+oo}8Vl-p9GiaKH>0`VerZOf2x z&&WL@NR!-K#e^XspgZHXQRhcoZG+^ngaqGy#CIt-<50GEeY^ISYXS8y&7qY7kHn8F z#)zK-tJop;&sf9VdOIQ4!eXtccf;hc0bxq+5)T-|pIB$}91|JBvcTK%gY6&Hc)7TO z8j(KVdKX0{y8oX+fO{`Mhv0yPe}w>$eS8 z&Hgge!-^tDPw#^Z9sutm3a3d`8(d5PQQKuZuN1J%TeHDk9}u-&nC&7YxP^(o)UX?T zzv4SSxbnW;ycC|=kG}37VE(tCTQu1)%ka$O)&B2kP%t|w*t+%2 z>m&BRS1zbQ{_VaEkm0s7>0FQgY`t`z{A}`&IoFPeB%{pxX6QR7Q=>{aM6rAbHYw-5 z^Zu`ml!Y`v_Vr&6hzI_E+Jr?s2e7_RlqN+*xGt~Fw>j99L1ID4_?Ohb{z8rw!^1x= zztw4i1huiO!>tkr_ zr0r#_b3amg@^w1jBJ3daM;%Qs!F%=~81_A+7{|jr8W_k1trDAwDD;c$FM%>#1sL7N zcsZBYF%$E;2DMt&iduLYvoG62t~|)i#majmuPp~?!7=vE4{-xw-Q4VY)(q{?X-3TE%R#`451jj5O$j7WB3@xozn}|((q0-a=%-J|?xJ$Sv zR#;3#_@d13!n`i*j2+VGjmF)I(AHccEYBMJy+9Teq(*5Vy8VGu~Xr<|8-|v~nx<7K>hG?US%2io{O1CsLl;#^^8j@TB26 zIz7S@U6$by>qx4f@=@m7f3xpPm=6g4fBAmG|I4?S<3vil@r6!gPND$He-8n~bA{Jc z>Ey-eQk4F&`x5i0A9~j15^cFM>oQjY*P#9~@WT*#gAmDNg%M^2zrOgsPt(7@K7RcG zF+3+(+M=%eNjp+X|0H}Q=+YOklf6t&?uLpL5z+f&nB-0wMCE00h` zCjVb!3J|S`-kHfXDY*Vvolf7TYm7mW+}Q3P654J;4g0me9>w?pc70;12Uu^VO@2GU z&mk&llq#nKZMi{_Py=_SOrKyL!h~e50#Q%+&I3M@$Hc2{8KzT0fxRC?Uo4w|MIXNt zx8)iv_a`2)+gsIR!YpI6C;4lR$%^_@rdgZl6Q7hvW!X8g(U)h#XG<~Jhy$D?Lr?(s%o1P zf*2B4*7ik7!kQJ{3K^b)pOW<-FdZtiQ5{Z%df!&Zs;fl)mxM)d5RyBIVQNT?(2#4NL_kU*= zUW?W(ZPzSOVIOjZuP6$z{^hLvQhk&VHbEe&;$MQjfmF_3RIXmaME*=L?rNz=c!h^2OB71la2QL2`%{ZHxS!+OsSa@rfm4VOdg$N%2AHGvogv5MhPk` zzq+MUrJ*|}*45%Ah~$#M!HPQwFLbTdx@M1Ze*M1vq1$wk2~BZdk_98tZjX&XHOuudfQb#TY!Rkk9O+&)~NYe*^h>!0;i&i}ZZkoDph|&B)$|RncOvF|_0( z)@Ief?%k^RRWh?xmZ2eH8*qd3R$Am@;!;R|S@w&!yzshTO+1nvc~x}mdop^7syHt& z&`hALB}Tq6;VssVa3Vm4CclbU4)`ePEsc*>F5RG(G81yXr0*d+3QOD6jd<+bQ|=qe zEg)^3(vekM&8t~`7_6&u?JvtM4X!Tq3r+Na`9rvL6*>X(g+Y1njA|~Y@O_=r%c=bm zb7xD!z|M_2UDk#KFv!Qz)f(Nub;S_(_ZH5(k2%xZKNg$NI7_gGQMgwEar<7ypmoq@Xyp^l5ENeZnT>EQJPd zGy}S|R<)6>1>6&zOhaVb3!3f&DF7%r9~+wFB?NhX68cj7Wfn&+5X`wTFyxliNA^aE zn)m>|@%5i>tw;H0{{;4rfcgaa{{y*t^-u}*_=(mTSU{aT4dEoJWbomp0ROl++s!?j7<0K zNWbD!X3_wdslzJbS!l9=YDT)HBn}Sk#R>Qm*AiwcW_XSAczSj1vnh)uc*k~8jKJw| zR~qfYM_|#EGkW8?3r%AXK;YyyIiz4WNV#~N9WkADoYuIbN{0LQj0@Q6!0Xn>fH$MI z*~z{n5i;mkz{;HLWqTDfsIq*jN`k^9tgPN?lfJpvdA2DRM>DA`LU*${lLs`o;u()T zjastG?_pI9*6uk)Vd}|{^2uSyRTSvU7ByNnRp9$;Hb&9L0iK5;=-xIk9hUNsW9c;l zM+9|jZq=Vi67F<_8f*bO==TUDG1y8hvDO?xe4gsyTBk&`HUJ;!bn&f&Lix_@z>$kAsnBnnC@W{OA4LQa}zN`~Z8PGRtJX7&;-g92K*81-14G zw?}^c6?#H)6e5ZLkxwUhwrlC`z0l8A^HLDV)P4|&nBzKJivJPMCwR2Wqv^fTPt0Id*@-!WtqVF=%Ao*Ju~%rebC9~ew+)m|AH_Cvt!HR z^K9sS^e~i)h;`sVv49&&^j9LTDQ0URO>Za(Sp)(C7Q1FJ7;&;NLn+AciH`rGkY#d$ z+Dc2acu>bl2QR8n(!=42F)&;l;Bm&+>|~5mHAaY{jntv*D~i>Wm?S&vX{fUEO}GYn z&wE?nj~uT!1jIrrwDn{2D>GD%zA|d>!T*p~6j$j;Qt~j7OJ&8Wk$mEFI^m8rmzQ_X zPXHRtqgbj%P$y(WJRlP6IW7iUu_n)REU=r}G1H$lxHgnj{d_AqZe^yYw%}2~;?8Km zL@{0{i?Oy+QD9+rnKd(1=R(Dz^gGFH?L!Eqf&)SBvhFas66s|{~4NB0J3VH08}LoC;7pt{?To`2Wj z`tA$Q7yTsRX9CqaC80xNomy>AS`%T`+pMI6cSVTSgLo?}Df>TNoq1Ff*B-}XOj#5H z7KjB#mas1ZPY`5_2LiGNN}E7{00o4SO3+{{V1UT>s9_TZ;)W;+h><0c3If6dMB)Mn z0?I>u8huqGgrz7_+&URO!6E0&ADR2f?|1K=$;{k)?tH)VIO}^qHKNAV^sWyPd|vRx z^PQ$DH*BAJ8f5n|)rfn7hV8vB{gNC}QJ((1_2)EGi*HRnd0-?)KQQ(EJ&T>MvFW}_ z)31p-$TQ z?1>6awB;{splC~gq5Mv}yp%dMY?UvWIOX~f7<*m1&T;5+16_AC!1{;paBQb-#5m&l zW0RasrJ9ljtyp7k(;zw}0bLPIb>qJE;Zz>+CrHXus|yyR1{;F!j@aPJ zbEL=tCb_4i^guP{L+C_J!hvF8+5kQHj%}{f9}Q*m7f*;c7Y&@APWtF>u>`$sFKLd7 z9e3ztUaGm~?D?C>^Hr1&i5=({|92Pj%$}9T?>}C>S{UMzs@S{@^NF3WtTa7!%+5n{ zO+41j+K1jdGGJY=UYm9zn$ElhzvB~z5w+L}5?!EJ%dahDUj4(FtI{RiitxOpbiFQgP& zc=l+yxHpdVlEjI>7ixc|;EEwAqcD&3A$|UHwi`8LpV>9iBRzO^+Vz zTkxY!WNb8vsb~{%-jMA)Gput>7QzzH=Vxi>#?cAFxT}Y;uct1l$TQLu3|h(i2Dw7! zE$(@7l(#A+i|t~ju*pcn@aUtypT&QLTe>5(XV4*|I&x{8xQ+C7|9!gNO#SgBi1`g;_u?vqs!SA8IR|x`u}_qz3xPR zbBM3YP)l3xGqZ3xRuTXH;^fIO0VTJwRlrJ~?6PaZx0CoI9)|r>=5uEcru{iF5<$*u zY9i#D+n*{*;?L%O)ay!8ak_PAb(GW?RqETL zj{;dWUW!~gc7_FgEeCJcxC7`u%ws$>UfTz4|3X3PDYDNJ7A&m=KyMX2@JzF+cH-_P zQWA7GYk`CxjS=7>@JOvYu%|)(csNwv3O(@IBFg>L;6UAKcxfO&W>_wdLb)J7RooX) z9%R+o0bd)ux*|YGT2>j1i)@xP@fJ%skR|1&$W=%iEpVTjf#;v zErH)(z@Zzq%E}5ZH~_2OBy0PeYx4z^E92<`GOGcoOOeN>W;^K2bNdFC$Op4{8faH1 zXa^qb;28m{GU036vgi!H;{^aRiE5|~ZiqHS?t}nsNLAbokf|L*5CH*2xPgx@h5|Ch zT?nv70Odq*Q?mvb>1ibG1?^Q?(Y5J*2ZI`LAiq%oq=IPXtq9057=}8j25{=tHzOdaAq04U3WJGF zHb8)Eu@nl0M?mix5VQrHXwn1Vg*{Np7tn@G>2wf+yn)qeO%zHG5k)Z_0swIEkP2L< z)fp=kN*4i!7Ql64mukSEYkgE#5e4TZ8oL`*D!!E(Nx_UaSv j+6D+geLfC^M|+mQ*Ow$yL@ceNaI6S{mE76Panj42;u diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index ff23a68d..2e111328 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.14.2-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-9.1.0-bin.zip networkTimeout=10000 validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME diff --git a/gradlew b/gradlew index 23d15a93..adff685a 100755 --- a/gradlew +++ b/gradlew @@ -1,7 +1,7 @@ #!/bin/sh # -# Copyright © 2015-2021 the original authors. +# Copyright © 2015 the original authors. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -114,7 +114,6 @@ case "$( uname )" in #( NONSTOP* ) nonstop=true ;; esac -CLASSPATH="\\\"\\\"" # Determine the Java command to use to start the JVM. @@ -172,7 +171,6 @@ fi # For Cygwin or MSYS, switch paths to Windows format before running java if "$cygwin" || "$msys" ; then APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) - CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) JAVACMD=$( cygpath --unix "$JAVACMD" ) @@ -212,7 +210,6 @@ DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' set -- \ "-Dorg.gradle.appname=$APP_BASE_NAME" \ - -classpath "$CLASSPATH" \ -jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \ "$@" diff --git a/gradlew.bat b/gradlew.bat index db3a6ac2..e509b2dd 100644 --- a/gradlew.bat +++ b/gradlew.bat @@ -1,94 +1,93 @@ -@rem -@rem Copyright 2015 the original author or authors. -@rem -@rem Licensed under the Apache License, Version 2.0 (the "License"); -@rem you may not use this file except in compliance with the License. -@rem You may obtain a copy of the License at -@rem -@rem https://www.apache.org/licenses/LICENSE-2.0 -@rem -@rem Unless required by applicable law or agreed to in writing, software -@rem distributed under the License is distributed on an "AS IS" BASIS, -@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -@rem See the License for the specific language governing permissions and -@rem limitations under the License. -@rem -@rem SPDX-License-Identifier: Apache-2.0 -@rem - -@if "%DEBUG%"=="" @echo off -@rem ########################################################################## -@rem -@rem Gradle startup script for Windows -@rem -@rem ########################################################################## - -@rem Set local scope for the variables with windows NT shell -if "%OS%"=="Windows_NT" setlocal - -set DIRNAME=%~dp0 -if "%DIRNAME%"=="" set DIRNAME=. -@rem This is normally unused -set APP_BASE_NAME=%~n0 -set APP_HOME=%DIRNAME% - -@rem Resolve any "." and ".." in APP_HOME to make it shorter. -for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi - -@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. -set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" - -@rem Find java.exe -if defined JAVA_HOME goto findJavaFromJavaHome - -set JAVA_EXE=java.exe -%JAVA_EXE% -version >NUL 2>&1 -if %ERRORLEVEL% equ 0 goto execute - -echo. 1>&2 -echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 -echo. 1>&2 -echo Please set the JAVA_HOME variable in your environment to match the 1>&2 -echo location of your Java installation. 1>&2 - -goto fail - -:findJavaFromJavaHome -set JAVA_HOME=%JAVA_HOME:"=% -set JAVA_EXE=%JAVA_HOME%/bin/java.exe - -if exist "%JAVA_EXE%" goto execute - -echo. 1>&2 -echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 -echo. 1>&2 -echo Please set the JAVA_HOME variable in your environment to match the 1>&2 -echo location of your Java installation. 1>&2 - -goto fail - -:execute -@rem Setup the command line - -set CLASSPATH= - - -@rem Execute Gradle -"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %* - -:end -@rem End local scope for the variables with windows NT shell -if %ERRORLEVEL% equ 0 goto mainEnd - -:fail -rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of -rem the _cmd.exe /c_ return code! -set EXIT_CODE=%ERRORLEVEL% -if %EXIT_CODE% equ 0 set EXIT_CODE=1 -if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% -exit /b %EXIT_CODE% - -:mainEnd -if "%OS%"=="Windows_NT" endlocal - -:omega +@rem +@rem Copyright 2015 the original author or authors. +@rem +@rem Licensed under the Apache License, Version 2.0 (the "License"); +@rem you may not use this file except in compliance with the License. +@rem You may obtain a copy of the License at +@rem +@rem https://www.apache.org/licenses/LICENSE-2.0 +@rem +@rem Unless required by applicable law or agreed to in writing, software +@rem distributed under the License is distributed on an "AS IS" BASIS, +@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@rem See the License for the specific language governing permissions and +@rem limitations under the License. +@rem +@rem SPDX-License-Identifier: Apache-2.0 +@rem + +@if "%DEBUG%"=="" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +set DIRNAME=%~dp0 +if "%DIRNAME%"=="" set DIRNAME=. +@rem This is normally unused +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Resolve any "." and ".." in APP_HOME to make it shorter. +for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if %ERRORLEVEL% equ 0 goto execute + +echo. 1>&2 +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto execute + +echo. 1>&2 +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 + +goto fail + +:execute +@rem Setup the command line + + + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %* + +:end +@rem End local scope for the variables with windows NT shell +if %ERRORLEVEL% equ 0 goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +set EXIT_CODE=%ERRORLEVEL% +if %EXIT_CODE% equ 0 set EXIT_CODE=1 +if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% +exit /b %EXIT_CODE% + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega From b692f8ecc8eb0793327cee710063f4c892ec826e Mon Sep 17 00:00:00 2001 From: Jacob Peterson <14127217+Petersoj@users.noreply.github.com> Date: Thu, 9 Oct 2025 13:43:04 -0600 Subject: [PATCH 232/271] Add `.github/FUNDING.yml` --- .github/FUNDING.yml | 1 + 1 file changed, 1 insertion(+) create mode 100644 .github/FUNDING.yml diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml new file mode 100644 index 00000000..55ede737 --- /dev/null +++ b/.github/FUNDING.yml @@ -0,0 +1 @@ +github: Petersoj \ No newline at end of file From 1888a791aedda3fb1deb60aed4ab38c3a28e3e1a Mon Sep 17 00:00:00 2001 From: Jacob Peterson <14127217+Petersoj@users.noreply.github.com> Date: Tue, 9 Jun 2026 13:27:13 -0600 Subject: [PATCH 233/271] Update Gradle wrapper --- gradle/wrapper/gradle-wrapper.jar | Bin 45457 -> 48966 bytes gradle/wrapper/gradle-wrapper.properties | 2 +- gradlew | 2 +- 3 files changed, 2 insertions(+), 2 deletions(-) diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index 8bdaf60c75ab801e22807dde59e12a8735a34077..d997cfc60f4cff0e7451d19d49a82fa986695d07 100644 GIT binary patch delta 39724 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ⅆA{k6{Sa_dPB>TF4Im=mf;z@rf2j7Tr#P>Bp9qL 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 delta 36423 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 diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 2e111328..1a704683 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-9.1.0-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-9.5.0-bin.zip networkTimeout=10000 validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME diff --git a/gradlew b/gradlew index adff685a..739907df 100755 --- a/gradlew +++ b/gradlew @@ -57,7 +57,7 @@ # Darwin, MinGW, and NonStop. # # (3) This script is generated from the Groovy template -# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# https://github.com/gradle/gradle/blob/2d6327017519d23b96af35865dc997fcb544fb40/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt # within the Gradle project. # # You can find Gradle at https://github.com/gradle/gradle/. From e3824a00c66649cef657d158517016f015b5200f Mon Sep 17 00:00:00 2001 From: Jacob Peterson <14127217+Petersoj@users.noreply.github.com> Date: Tue, 9 Jun 2026 17:04:07 -0600 Subject: [PATCH 234/271] Remove `JSONSchema2POJOAdaptedPlugin` in `buildSrc` --- buildSrc/build.gradle | 29 ----- .../GeneratePOJOsTask.groovy | 121 ------------------ .../JSONSchema2POJOAdaptedPlugin.groovy | 26 ---- .../JSONSchemaFileConfig.groovy | 29 ----- 4 files changed, 205 deletions(-) delete mode 100644 buildSrc/build.gradle delete mode 100644 buildSrc/src/main/groovy/net/jacobpeterson/alpaca/buildsrc/plugin/jsonschema2pojoadapted/GeneratePOJOsTask.groovy delete mode 100644 buildSrc/src/main/groovy/net/jacobpeterson/alpaca/buildsrc/plugin/jsonschema2pojoadapted/JSONSchema2POJOAdaptedPlugin.groovy delete mode 100644 buildSrc/src/main/groovy/net/jacobpeterson/alpaca/buildsrc/plugin/jsonschema2pojoadapted/JSONSchemaFileConfig.groovy diff --git a/buildSrc/build.gradle b/buildSrc/build.gradle deleted file mode 100644 index 1ca86d32..00000000 --- a/buildSrc/build.gradle +++ /dev/null @@ -1,29 +0,0 @@ -plugins { - id "groovy-gradle-plugin" -} - -gradlePlugin { - plugins { - jsonschema2pojoAdapted { - id = "jsonschema2pojo-adapted" - implementationClass = - "net.jacobpeterson.alpaca.buildsrc.plugin.jsonschema2pojoadapted.JSONSchema2POJOAdaptedPlugin" - } - } -} - -repositories { - mavenCentral() - gradlePluginPortal() -} - -dependencies { - gradleApi() - - // Google Guava - implementation group: "com.google.guava", name: "guava", version: "33.0.0-jre" - - // JsonSchema2Pojo - implementation group: "org.jsonschema2pojo", name: "jsonschema2pojo-core", version: "1.2.1" - implementation group: "org.jsonschema2pojo", name: "jsonschema2pojo-gradle-plugin", version: "1.2.1" -} diff --git a/buildSrc/src/main/groovy/net/jacobpeterson/alpaca/buildsrc/plugin/jsonschema2pojoadapted/GeneratePOJOsTask.groovy b/buildSrc/src/main/groovy/net/jacobpeterson/alpaca/buildsrc/plugin/jsonschema2pojoadapted/GeneratePOJOsTask.groovy deleted file mode 100644 index 650a7c7b..00000000 --- a/buildSrc/src/main/groovy/net/jacobpeterson/alpaca/buildsrc/plugin/jsonschema2pojoadapted/GeneratePOJOsTask.groovy +++ /dev/null @@ -1,121 +0,0 @@ -package net.jacobpeterson.alpaca.buildsrc.plugin.jsonschema2pojoadapted - -import org.gradle.api.DefaultTask -import org.gradle.api.model.ReplacedBy -import org.gradle.api.tasks.Input -import org.gradle.api.tasks.Internal -import org.gradle.api.tasks.SourceSetContainer -import org.gradle.api.tasks.TaskAction -import org.jsonschema2pojo.GenerationConfig -import org.jsonschema2pojo.Jsonschema2Pojo -import org.jsonschema2pojo.gradle.GradleRuleLogger - -import java.nio.file.Files -import java.nio.file.Path - -import static JSONSchema2POJOAdaptedPlugin.EXTENSION_KEY -import static com.google.common.base.Preconditions.checkArgument - -/** - * {@link GeneratePOJOsTask} is a Gradle task that invokes jsonschema2pojo with special configurations - * and generation logic. - * @see GenerateJsonSchemaJavaTask - */ -class GeneratePOJOsTask extends DefaultTask { - - @ReplacedBy("configurationString") - GenerationConfig configuration - - @Input - String getConfigurationString() { - configuration.toString() - } - - @Internal - List jsonSchemaFileConfigs - - GeneratePOJOsTask() { - group = "Build" - description = "Generates Java POJOs from JSON schemas." - - // Validate plugin configuration - configuration = project.extensions.getByName(EXTENSION_KEY) as GenerationConfig - checkArgument(!configuration.source.hasNext(), "'source' should not be set for '${name}'.") - checkArgument(configuration.targetDirectory == null, "'targetDirectory' should not be set for '${name}'.") - - // Set custom defaults on plugin configuration - configuration.targetDirectory = project.file( - "${project.layout.buildDirectory.getAsFile().get().getPath()}/generated-sources/jsonschema2pojo") - configuration.targetPackage = configuration.targetPackage == "" ? - "${project.group}.model" : configuration.targetPackage - configuration.propertyWordDelimiters = ["-", "_"] as char[] - configuration.annotationStyle = "gson" - configuration.sourceType = "jsonschema" - configuration.customDateTimePattern = "yyyy-MM-ddTHH:mm:ssZ" - configuration.includeConstructors = true - configuration.serializable = true - configuration.includeGetters = true - configuration.includeSetters = true - configuration.includeCopyConstructor = true - configuration.generateBuilders = true - - // Get project main java source set - final def projectMainJavaSourceSet = - project.extensions.getByType(SourceSetContainer).named("main").get().java - - // Walk through source files to create a list of JSON schema files to process - jsonSchemaFileConfigs = new ArrayList<>() - projectMainJavaSourceSet.srcDirs.each { srcDir -> - if (!srcDir.exists()) { - return - } - final def sourceDirPath = srcDir.getAbsolutePath() - srcDir.eachFileRecurse { sourceFile -> - final def sourceFilePath = sourceFile.getAbsolutePath() - if (sourceFilePath.endsWith(".json")) { - def targetPackage = sourceFile.getParentFile().getAbsolutePath() - .substring(sourceDirPath.length()) - .replace(File.separator, ".").replace("-", "").replace("_", "") - .toLowerCase() - targetPackage = targetPackage.replace("${project.group}", "") - targetPackage = targetPackage.startsWith(".") ? targetPackage.substring(1) : targetPackage - targetPackage = configuration.targetPackage + - (targetPackage.isEmpty() ? "" : targetPackage) - jsonSchemaFileConfigs.add(new JSONSchemaFileConfig(sourceFile, targetPackage)) - - inputs.file(sourceFile) - outputs.file(project.file(configuration.targetDirectory.getAbsolutePath() + File.separator + - targetPackage.replace(".", File.separator) + File.separator + - sourceFile.getName().replace(".json", ".java"))) - } - } - } - - outputs.dir(configuration.targetDirectory) - outputs.cacheIf { true } - - projectMainJavaSourceSet.srcDirs(configuration.targetDirectory) - } - - @TaskAction - def execute() { - logger.info("Deleting existing generated POJOs...") - Files.walk(configuration.targetDirectory.toPath()) - .sorted(Comparator.reverseOrder()) - .map(Path::toFile) - .peek(file -> logger.info("Deleting file: {}", file.getPath())) - .forEach(File::delete) - logger.info("Deleted existing generated POJOs.") - - logger.info("Generating POJOs from JSON schemas...") - final def gradleRuleLogger = new GradleRuleLogger(logger) - jsonSchemaFileConfigs.forEach { jsonSchemaFileConfig -> - configuration.source = List.of(jsonSchemaFileConfig.sourceFile.toURI().toURL()) - configuration.targetPackage = jsonSchemaFileConfig.targetPackage - Jsonschema2Pojo.generate(configuration, gradleRuleLogger) - logger.info("Generated POJO for: {}", jsonSchemaFileConfig.targetPackage + "." + - jsonSchemaFileConfig.sourceFile.getName()) - } - logger.info("Generated POJOs from JSON schemas.") - } -} diff --git a/buildSrc/src/main/groovy/net/jacobpeterson/alpaca/buildsrc/plugin/jsonschema2pojoadapted/JSONSchema2POJOAdaptedPlugin.groovy b/buildSrc/src/main/groovy/net/jacobpeterson/alpaca/buildsrc/plugin/jsonschema2pojoadapted/JSONSchema2POJOAdaptedPlugin.groovy deleted file mode 100644 index 963e8276..00000000 --- a/buildSrc/src/main/groovy/net/jacobpeterson/alpaca/buildsrc/plugin/jsonschema2pojoadapted/JSONSchema2POJOAdaptedPlugin.groovy +++ /dev/null @@ -1,26 +0,0 @@ -package net.jacobpeterson.alpaca.buildsrc.plugin.jsonschema2pojoadapted - -import org.gradle.api.GradleException -import org.gradle.api.Plugin -import org.gradle.api.Project -import org.jsonschema2pojo.gradle.JsonSchemaExtension - -/** - * {@link JSONSchema2POJOAdaptedPlugin} is a Gradle plugin that adapts the jsonschema2pojo Gradle plugin - * to add/change some features. - */ -class JSONSchema2POJOAdaptedPlugin implements Plugin { - - public static final EXTENSION_KEY = "jsonSchema2POJOAdaptedConfig" - - @Override - void apply(Project project) { - project.extensions.create(EXTENSION_KEY, JsonSchemaExtension) - if (project.plugins.hasPlugin("java")) { - project.tasks.register("generatePOJOs", GeneratePOJOsTask) - project.tasks.compileJava.dependsOn "generatePOJOs" - } else { - throw new GradleException("'java' Gradle plugin required.") - } - } -} diff --git a/buildSrc/src/main/groovy/net/jacobpeterson/alpaca/buildsrc/plugin/jsonschema2pojoadapted/JSONSchemaFileConfig.groovy b/buildSrc/src/main/groovy/net/jacobpeterson/alpaca/buildsrc/plugin/jsonschema2pojoadapted/JSONSchemaFileConfig.groovy deleted file mode 100644 index d4dfd8fd..00000000 --- a/buildSrc/src/main/groovy/net/jacobpeterson/alpaca/buildsrc/plugin/jsonschema2pojoadapted/JSONSchemaFileConfig.groovy +++ /dev/null @@ -1,29 +0,0 @@ -package net.jacobpeterson.alpaca.buildsrc.plugin.jsonschema2pojoadapted - -/** - * {@link JSONSchemaFileConfig} provides the configuration fields for a single JSON schema file. - */ -class JSONSchemaFileConfig { - - private File sourceFile - private String targetPackage - - /** - * Instantiates a new {@link JSONSchemaFileConfig}. - * - * @param sourceFile the source file - * @param targetPackage the target package - */ - JSONSchemaFileConfig(File sourceFile, String targetPackage) { - this.sourceFile = sourceFile - this.targetPackage = targetPackage - } - - File getSourceFile() { - return sourceFile - } - - String getTargetPackage() { - return targetPackage - } -} From 8cdb622812f6b0dedb19fa48092f8e85a45ac696 Mon Sep 17 00:00:00 2001 From: Jacob Peterson <14127217+Petersoj@users.noreply.github.com> Date: Wed, 10 Jun 2026 09:45:47 -0600 Subject: [PATCH 235/271] Update Gradle properties to use configuration cache and GC interval --- gradle.properties | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/gradle.properties b/gradle.properties index 31634b4e..64701b8a 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,3 +1,4 @@ +org.gradle.jvmargs=-XX:MaxRAMPercentage=100 -XX:MinHeapFreeRatio=5 -XX:MaxHeapFreeRatio=15 -XX:+UseG1GC -XX:G1PeriodicGCInterval=60000 org.gradle.logging.level=info org.gradle.logging.stacktrace=all -org.gradle.jvmargs=-XX:MaxRAMPercentage=100.0 -XX:MinHeapFreeRatio=5 -XX:MaxHeapFreeRatio=15 +org.gradle.configuration-cache=true From 83f560c2993dacb724a032ee9b0f5353b4d5f906 Mon Sep 17 00:00:00 2001 From: Jacob Peterson <14127217+Petersoj@users.noreply.github.com> Date: Sat, 20 Jun 2026 12:52:58 -0600 Subject: [PATCH 236/271] Add `module-common` Gradle convention plugin in `buildSrc` for modular design change --- buildSrc/build.gradle.kts | 20 +++ buildSrc/src/main/kotlin/ModuleCommon.kt | 3 + .../src/main/kotlin/module-common.gradle.kts | 122 ++++++++++++++++++ 3 files changed, 145 insertions(+) create mode 100644 buildSrc/build.gradle.kts create mode 100644 buildSrc/src/main/kotlin/ModuleCommon.kt create mode 100644 buildSrc/src/main/kotlin/module-common.gradle.kts diff --git a/buildSrc/build.gradle.kts b/buildSrc/build.gradle.kts new file mode 100644 index 00000000..70810aad --- /dev/null +++ b/buildSrc/build.gradle.kts @@ -0,0 +1,20 @@ +plugins { + `kotlin-dsl` +} + +repositories { + mavenCentral() + gradlePluginPortal() +} + +dependencies { + implementation("com.google.guava:guava:33.6.0-jre") + + // Define versions for `module-common.gradle.kts` plugins: + implementation("io.freefair.lombok:io.freefair.lombok.gradle.plugin:9.5.0") + implementation("net.ltgt.errorprone:net.ltgt.errorprone.gradle.plugin:5.1.0") + implementation("com.github.ben-manes:gradle-versions-plugin:0.54.0") + + // Define versions for `openapi-generator-adapted.gradle.kts` plugins: + implementation("org.openapi.generator:org.openapi.generator.gradle.plugin:7.23.0") +} diff --git a/buildSrc/src/main/kotlin/ModuleCommon.kt b/buildSrc/src/main/kotlin/ModuleCommon.kt new file mode 100644 index 00000000..477c7ecc --- /dev/null +++ b/buildSrc/src/main/kotlin/ModuleCommon.kt @@ -0,0 +1,3 @@ +const val PROJECT_GROUP = "net.jacobpeterson.alpacajava" +const val PROJECT_VERSION = "11.0.0" +const val JRELEASER_MAVEN_REPOSITORY_DIRECTORY = "jreleaser-staging" diff --git a/buildSrc/src/main/kotlin/module-common.gradle.kts b/buildSrc/src/main/kotlin/module-common.gradle.kts new file mode 100644 index 00000000..4c8a5502 --- /dev/null +++ b/buildSrc/src/main/kotlin/module-common.gradle.kts @@ -0,0 +1,122 @@ +import net.ltgt.gradle.errorprone.CheckSeverity.WARN +import net.ltgt.gradle.errorprone.errorprone +import org.gradle.api.JavaVersion.VERSION_25 + +plugins { + `java-library` + id("io.freefair.lombok") + id("net.ltgt.errorprone") + id("com.github.ben-manes.versions") + `maven-publish` +} + +group = PROJECT_GROUP +version = PROJECT_VERSION + +java { + sourceCompatibility = VERSION_25 + targetCompatibility = VERSION_25 + withJavadocJar() + withSourcesJar() +} + +repositories { + mavenCentral() +} + +val guavaVersion = "33.6.0-jre" + +dependencies { + api("org.jspecify:jspecify:1.0.0") + api("com.google.guava:guava:$guavaVersion") + + implementation("org.slf4j:slf4j-api:2.0.17") + + errorprone("com.google.errorprone:error_prone_core:2.49.0") + errorprone("com.uber.nullaway:nullaway:0.13.4") + errorprone("net.jacobpeterson:final-coat:1.2.3") +} + +tasks.withType(JavaCompile::class).configureEach { + options.errorprone { + allErrorsAsWarnings = true + allSuggestionsAsWarnings = true + disableWarningsInGeneratedCode = true + excludedPaths = ".*/build/.*" + + disable("MissingSummary") + disable("NullableOptional") + check("Varifier", WARN) + check("IdentifierName", WARN) + check("MissingBraces", WARN) + check("FieldCanBeFinal", WARN) + check("MissingDefault", WARN) + check("SwitchDefault", WARN) + check("RedundantNullCheck", WARN) + check("FieldMissingNullable", WARN) + check("ParameterMissingNullable", WARN) + check("ReturnMissingNullable", WARN) + + check("NullAway", WARN) + option("NullAway:OnlyNullMarked", true) + option("NullAway:JSpecifyMode", true) + check("RequireExplicitNullMarking", WARN) + + check("FinalCoat", WARN) + } +} + +tasks.withType(Javadoc::class).configureEach { + options { + (this as StandardJavadocDocletOptions).addBooleanOption("Xdoclint:none", true) + links("https://docs.oracle.com/en/java/javase/${java.targetCompatibility.majorVersion}/docs/api/", + "https://jspecify.dev/docs/api/", + "https://guava.dev/releases/$guavaVersion/api/docs/", + "https://errorprone.info/api/latest/") + } +} +tasks.withType(Javadoc::class).configureEach { + project.configurations.flatMap { it.dependencies.withType(ProjectDependency::class) }.forEach { + project(it.path).tasks.withType(Javadoc::class).forEach { dependencyJavadocTask -> + this@configureEach.dependsOn(dependencyJavadocTask) + this@configureEach.options { + (this as StandardJavadocDocletOptions).linksOffline( + "https://javadoc.io/doc/${it.group}/${it.name}/${it.version}/", + dependencyJavadocTask.destinationDir!!.path) + } + } + } +} + +publishing { + publications.create("jreleaser", MavenPublication::class) { + from(components["java"]) + pom { + name = provider { artifactId } + description = provider { project.description } + url = "https://github.com/Petersoj/alpaca-java" + inceptionYear = "2025" + licenses { + license { + name = "MIT License" + url = "https://opensource.org/licenses/MIT" + } + } + developers { + developer { + id = "Petersoj" + name = "Jacob Peterson" + } + } + scm { + connection = pom.url.map { "scm:git:$it.git" } + developerConnection = connection + url = pom.url + } + } + } + repositories.maven { + name = "jreleaser" + url = uri(rootProject.layout.buildDirectory.dir(JRELEASER_MAVEN_REPOSITORY_DIRECTORY)) + } +} From f7ffc910085370fdc4f09d41e69c0927c272ee61 Mon Sep 17 00:00:00 2001 From: Jacob Peterson <14127217+Petersoj@users.noreply.github.com> Date: Sat, 20 Jun 2026 12:55:54 -0600 Subject: [PATCH 237/271] Add `openapi-generator-adapted` Gradle convention plugin --- .../OpenApiGeneratorAdaptedExtension.kt | 7 + .../openapi-generator-adapted.gradle.kts | 216 ++++++++++++++++++ 2 files changed, 223 insertions(+) create mode 100644 buildSrc/src/main/kotlin/OpenApiGeneratorAdaptedExtension.kt create mode 100644 buildSrc/src/main/kotlin/openapi-generator-adapted.gradle.kts diff --git a/buildSrc/src/main/kotlin/OpenApiGeneratorAdaptedExtension.kt b/buildSrc/src/main/kotlin/OpenApiGeneratorAdaptedExtension.kt new file mode 100644 index 00000000..9efc9920 --- /dev/null +++ b/buildSrc/src/main/kotlin/OpenApiGeneratorAdaptedExtension.kt @@ -0,0 +1,7 @@ +import org.gradle.api.provider.Property + +interface OpenApiGeneratorAdaptedExtension { + + val environmentUrlProduction: Property + val environmentUrlSandbox: Property +} diff --git a/buildSrc/src/main/kotlin/openapi-generator-adapted.gradle.kts b/buildSrc/src/main/kotlin/openapi-generator-adapted.gradle.kts new file mode 100644 index 00000000..2177a025 --- /dev/null +++ b/buildSrc/src/main/kotlin/openapi-generator-adapted.gradle.kts @@ -0,0 +1,216 @@ +import com.google.common.base.CaseFormat.LOWER_CAMEL +import com.google.common.base.CaseFormat.LOWER_HYPHEN +import com.google.common.base.CaseFormat.UPPER_CAMEL + +plugins { + java + id("org.openapi.generator") +} + +val jacksonVersion = "3.2.0" +val jacksonAnnotationsVersion = "2.22" + +dependencies { + implementation("tools.jackson.core:jackson-core:$jacksonVersion") + implementation("tools.jackson.core:jackson-databind:$jacksonVersion") + implementation("com.fasterxml.jackson.core:jackson-annotations:$jacksonAnnotationsVersion") +} + +val extension = extensions.create("openApiGeneratorAdapted", OpenApiGeneratorAdaptedExtension::class) +tasks.openApiGenerate.configure { + inputSpec = file("openapi.json") + generatorName = "java" + val outputPackageName = "${project.group}.${project.name.replace("-", "")}" + configOptions.put("invokerPackage", outputPackageName) + val apiPackageName = "api" + configOptions.put("apiPackage", "$outputPackageName.$apiPackageName") + configOptions.put("modelPackage", "$outputPackageName.model") + configOptions.put("library", "native") + configOptions.put("documentationProvider", "none") + configOptions.put("useJackson3", "true") + configOptions.put("openApiNullable", "false") + cleanupOutput = true + skipValidateSpec = true + generateModelTests = false + generateModelDocumentation = false + generateApiTests = false + generateApiDocumentation = false + val projectName = project.name + val projectDescription = project.description + val environmentUrlProduction = extension.environmentUrlProduction.get() + val environmentUrlSandbox = extension.environmentUrlSandbox.get() + doLast { + val srcMainJava = outputDir.get().asFile.resolve("src/main/java/") + srcMainJava.walkTopDown().filter { it.isFile }.forEach { + it.writeText(it.readText() + .replace("@javax.annotation.Nullable", "@org.jspecify.annotations.Nullable") + .replace("@javax.annotation.Nonnull", "@org.jspecify.annotations.NonNull") + .replace("@javax.annotation.Generated", "@javax.annotation.processing.Generated")) + } + val outputPackageDirectory = srcMainJava.resolve(outputPackageName.replace('.', '/')) + val apiClassNamesOfMethodNames = outputPackageDirectory.resolve(apiPackageName).list() + .map { it.replace(".java", "") } + .associateBy { UPPER_CAMEL.to(LOWER_CAMEL, it.substring(0, it.length - 3)) } + val className = "${LOWER_HYPHEN.to(UPPER_CAMEL, projectName)}Api" + outputPackageDirectory.resolve("${className}.java").writeText(""" + package ${outputPackageName}; + + import com.google.errorprone.annotations.concurrent.LazyInit; + import net.jacobpeterson.alpacajava.common.AlpacaHeader; + import net.jacobpeterson.alpacajava.common.ApiEnvironment; + import ${outputPackageName}.${apiPackageName}.*; + import org.jspecify.annotations.NullMarked; + import org.jspecify.annotations.Nullable; + import tools.jackson.databind.ObjectMapper; + + import java.io.InputStream; + import java.net.http.HttpClient; + import java.net.http.HttpRequest; + import java.net.http.HttpResponse; + import java.util.function.Consumer; + + import static com.google.common.net.HttpHeaders.AUTHORIZATION; + import static java.time.Duration.ofSeconds; + import static net.jacobpeterson.alpacajava.common.AlpacaHeader.API_KEY_ID; + import static net.jacobpeterson.alpacajava.common.AlpacaHeader.API_SECRET_KEY; + import static net.jacobpeterson.alpacajava.common.AlpacaHeader.AUTHORIZATION_TOKEN_BEARER_PREFIX; + import static net.jacobpeterson.alpacajava.common.ApiEnvironment.PRODUCTION; + + /** + * {@link ${className}}: $projectDescription + */ + @NullMarked + public class $className implements AutoCloseable { + + /** + * The URL for {@link ApiEnvironment#PRODUCTION}: "$environmentUrlProduction" + */ + public static final String ENVIRONMENT_URL_PRODUCTION ="$environmentUrlProduction"; + + /** + * The URL for {@link ApiEnvironment#SANDBOX}: "$environmentUrlSandbox" + */ + public static final String ENVIRONMENT_URL_SANDBOX = "$environmentUrlSandbox"; + + private final HttpClient httpClient; + private final ApiClient apiClient; + ${apiClassNamesOfMethodNames.entries.joinToString(separator = "\n ") { (methodName, apiClassName) -> + "private @Nullable @LazyInit $apiClassName $methodName;" }} + + /** + * Calls {@link #${className}(HttpClient, ObjectMapper, String, String, String, ApiEnvironment, Consumer, Consumer)} + * with everything set to null except authenticationKeyID, + * authenticationSecretKey, and apiEnvironment. + */ + public ${className}(final @Nullable String authenticationKeyID, final @Nullable String authenticationSecretKey, + final @Nullable ApiEnvironment apiEnvironment) { + this(null, null, authenticationKeyID, authenticationSecretKey, null, apiEnvironment, null, null); + } + + /** + * Calls {@link #${className}(HttpClient, ObjectMapper, String, String, String, ApiEnvironment, Consumer, Consumer)} + * with everything set to null except authorizationBearerToken and + * apiEnvironment. + */ + public ${className}(final @Nullable String authorizationBearerToken, final @Nullable ApiEnvironment apiEnvironment) { + this(null, null, null, null, authorizationBearerToken, apiEnvironment, null, null); + } + + /** + * Instantiates a new {@link ${className}}. + * + * @param httpClient the {@link HttpClient}, or null to use a new default instance + * @param objectMapper the {@link ObjectMapper}, or null to use a new default instance + * @param authenticationKeyID the {@link AlpacaHeader#API_KEY_ID} value + * @param authenticationSecretKey the {@link AlpacaHeader#API_SECRET_KEY} value + * @param authorizationBearerToken the {@link AlpacaHeader#AUTHORIZATION_TOKEN_BEARER_PREFIX} suffix value + * @param apiEnvironment the {@link ApiEnvironment}. If {@link ApiEnvironment#PRODUCTION}, then + * {@link #ENVIRONMENT_URL_PRODUCTION} is used. If {@link ApiEnvironment#SANDBOX}, + * then {@link #ENVIRONMENT_URL_SANDBOX} is used. + * @param requestInterceptor the {@link HttpRequest.Builder} {@link Consumer} to invoke before sending a + * request + * @param responseInterceptor the {@link HttpResponse} {@link Consumer} to invoke before processing a response + */ + public ${className}(final @Nullable HttpClient httpClient, final @Nullable ObjectMapper objectMapper, + final @Nullable String authenticationKeyID, final @Nullable String authenticationSecretKey, + final @Nullable String authorizationBearerToken, final @Nullable ApiEnvironment apiEnvironment, + final @Nullable Consumer requestInterceptor, + final @Nullable Consumer> responseInterceptor) { + this.httpClient = httpClient != null ? httpClient : + HttpClient.newBuilder().connectTimeout(ofSeconds(10)).build(); + final var baseUri = apiEnvironment == PRODUCTION ? ENVIRONMENT_URL_PRODUCTION : ENVIRONMENT_URL_SANDBOX; + apiClient = new ApiClient(null, objectMapper != null ? objectMapper : + ApiClient.createDefaultObjectMapper(), null) { + + @Override + public HttpClient getHttpClient() { + // Re-use `HttpClient` instead of building a new one for each tag + return ${className}.this.httpClient; + } + + @Override + public String getBaseUri() { + return baseUri; + } + }; + apiClient.setReadTimeout(ofSeconds(10)); + apiClient.setRequestInterceptor(builder -> { + if (authenticationKeyID != null) { + builder.header(API_KEY_ID, authenticationKeyID); + } + if (authenticationSecretKey != null) { + builder.header(API_SECRET_KEY, authenticationSecretKey); + } + if (authorizationBearerToken != null) { + builder.header(AUTHORIZATION, AUTHORIZATION_TOKEN_BEARER_PREFIX + authorizationBearerToken); + } + if (requestInterceptor != null) { + requestInterceptor.accept(builder); + } + }); + apiClient.setResponseInterceptor(responseInterceptor); + } + + @Override + public void close() { + httpClient.close(); + } + + ${apiClassNamesOfMethodNames.entries.joinToString(separator = "\n") { (methodName, apiClassName) -> """ + /** + * @return {@link ${apiClassName}} internally-cached instance + */ + public $apiClassName ${methodName}() { + if (${methodName} == null) { + $methodName = new ${apiClassName}(apiClient); + } + return ${methodName}; + } + """.trimIndent() }} + } + """.trimIndent()) + } +} +tasks.withType(JavaCompile::class).configureEach { + dependsOn(tasks.openApiGenerate) +} +tasks.withType(Jar::class).configureEach { + dependsOn(tasks.openApiGenerate) +} +sourceSets { + main { + java { + srcDir(openApiGenerate.outputDir.file("src/main/java/")) + } + } +} + +tasks.withType(Javadoc::class).configureEach { + options { + (this as StandardJavadocDocletOptions).links( + // TODO uncomment once Jackson Core published Javadoc artifact is fixed + // "https://javadoc.io/doc/tools.jackson.core/jackson-core/$jacksonVersion", + "https://javadoc.io/doc/tools.jackson.core/jackson-databind/$jacksonVersion", + "https://javadoc.io/doc/com.fasterxml.jackson.core/jackson-annotations/$jacksonAnnotationsVersion") + } +} From c18eed8be879223f8b0757d1a77c62449d9611cf Mon Sep 17 00:00:00 2001 From: Jacob Peterson <14127217+Petersoj@users.noreply.github.com> Date: Sat, 20 Jun 2026 12:57:41 -0600 Subject: [PATCH 238/271] Add root Gradle build script with JReleaser plugin --- build.gradle.kts | 49 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 49 insertions(+) create mode 100644 build.gradle.kts diff --git a/build.gradle.kts b/build.gradle.kts new file mode 100644 index 00000000..86685aa5 --- /dev/null +++ b/build.gradle.kts @@ -0,0 +1,49 @@ +import org.jreleaser.model.Active.ALWAYS +import org.jreleaser.model.Active.NEVER + +plugins { + java + id("org.jreleaser") version "1.24.0" +} + +group = PROJECT_GROUP +version = PROJECT_VERSION + +repositories { + mavenCentral() +} + +jreleaser { + signing { + pgp { + active = ALWAYS + armored = true + } + } + deploy { + maven { + mavenCentral { + create("sonatype") { + active = ALWAYS + url = "https://central.sonatype.com/api/v1/publisher" + stagingRepositories.add(layout.buildDirectory.dir(JRELEASER_MAVEN_REPOSITORY_DIRECTORY) + .map { it.asFile.path }) + skipPublicationCheck = true + } + } + } + } + release { + github { + uploadAssets = NEVER + } + } +} + +subprojects { + tasks.configureEach { + rootProject.tasks.jreleaserFullRelease.configure { + mustRunAfter(this@configureEach) + } + } +} From 1d04d8d1056c398e5a014c0a79525d3bf51bb88f Mon Sep 17 00:00:00 2001 From: Jacob Peterson <14127217+Petersoj@users.noreply.github.com> Date: Sat, 20 Jun 2026 12:58:10 -0600 Subject: [PATCH 239/271] Add Lombok config --- lombok.config | 8 ++++++++ 1 file changed, 8 insertions(+) create mode 100644 lombok.config diff --git a/lombok.config b/lombok.config new file mode 100644 index 00000000..d266d7ff --- /dev/null +++ b/lombok.config @@ -0,0 +1,8 @@ +config.stopBubbling = true +lombok.accessors.chain = true +lombok.addLombokGeneratedAnnotation = true +lombok.addNullAnnotations = jspecify +lombok.toString.callSuper = call +lombok.equalsAndHashCode.callSuper = call +lombok.builder.className = Builder +lombok.log.fieldName = LOGGER From 017886b78e614752dd04d0728bc880ecd78c6561 Mon Sep 17 00:00:00 2001 From: Jacob Peterson <14127217+Petersoj@users.noreply.github.com> Date: Sat, 20 Jun 2026 12:59:23 -0600 Subject: [PATCH 240/271] Add initial `common` module with `AlpacaHeader` and `ApiEnvironment` --- module/common/build.gradle.kts | 5 ++++ .../alpacajava/common/AlpacaHeader.java | 28 +++++++++++++++++++ .../alpacajava/common/ApiEnvironment.java | 20 +++++++++++++ 3 files changed, 53 insertions(+) create mode 100644 module/common/build.gradle.kts create mode 100644 module/common/src/main/java/net/jacobpeterson/alpacajava/common/AlpacaHeader.java create mode 100644 module/common/src/main/java/net/jacobpeterson/alpacajava/common/ApiEnvironment.java diff --git a/module/common/build.gradle.kts b/module/common/build.gradle.kts new file mode 100644 index 00000000..a12c9978 --- /dev/null +++ b/module/common/build.gradle.kts @@ -0,0 +1,5 @@ +plugins { + id("module-common") +} + +description = "The common module for various Alpaca Java modules." diff --git a/module/common/src/main/java/net/jacobpeterson/alpacajava/common/AlpacaHeader.java b/module/common/src/main/java/net/jacobpeterson/alpacajava/common/AlpacaHeader.java new file mode 100644 index 00000000..7663fd18 --- /dev/null +++ b/module/common/src/main/java/net/jacobpeterson/alpacajava/common/AlpacaHeader.java @@ -0,0 +1,28 @@ +package net.jacobpeterson.alpacajava.common; + +import com.google.common.net.HttpHeaders; +import org.jspecify.annotations.NullMarked; + +/** + * {@link AlpacaHeader} contains Alpaca header {@link String} constants. + */ +@NullMarked +public final class AlpacaHeader { + + /** + * The API Key ID header {@link String}: "APCA-API-KEY-ID" + */ + public static final String API_KEY_ID = "APCA-API-KEY-ID"; + + /** + * The API Secret Key header {@link String}: "APCA-API-SECRET-KEY" + */ + public static final String API_SECRET_KEY = "APCA-API-SECRET-KEY"; + + /** + * The {@link HttpHeaders#AUTHORIZATION} token bearer prefix: "Bearer " + */ + public static final String AUTHORIZATION_TOKEN_BEARER_PREFIX = "Bearer "; + + private AlpacaHeader() {} +} diff --git a/module/common/src/main/java/net/jacobpeterson/alpacajava/common/ApiEnvironment.java b/module/common/src/main/java/net/jacobpeterson/alpacajava/common/ApiEnvironment.java new file mode 100644 index 00000000..047dde96 --- /dev/null +++ b/module/common/src/main/java/net/jacobpeterson/alpacajava/common/ApiEnvironment.java @@ -0,0 +1,20 @@ +package net.jacobpeterson.alpacajava.common; + +import org.jspecify.annotations.NullMarked; + +/** + * {@link ApiEnvironment} is an enum that represents the API environment to use. + */ +@NullMarked +public enum ApiEnvironment { + + /** + * Production/live environment. + */ + PRODUCTION, + + /** + * Sandbox/testing/paper environment. + */ + SANDBOX +} From db6634cc25abd7c12a9038865d6699c82c415329 Mon Sep 17 00:00:00 2001 From: Jacob Peterson <14127217+Petersoj@users.noreply.github.com> Date: Mon, 22 Jun 2026 19:56:52 -0700 Subject: [PATCH 241/271] Add Gradle build scripts for `broker`, `market-data`, and `trading` modules --- module/broker/build.gradle.kts | 15 +++++++++++++++ module/market-data/build.gradle.kts | 15 +++++++++++++++ module/trading/build.gradle.kts | 15 +++++++++++++++ settings.gradle.kts | 5 +++++ 4 files changed, 50 insertions(+) create mode 100644 module/broker/build.gradle.kts create mode 100644 module/market-data/build.gradle.kts create mode 100644 module/trading/build.gradle.kts create mode 100644 settings.gradle.kts diff --git a/module/broker/build.gradle.kts b/module/broker/build.gradle.kts new file mode 100644 index 00000000..bb5e64af --- /dev/null +++ b/module/broker/build.gradle.kts @@ -0,0 +1,15 @@ +plugins { + id("module-common") + id("openapi-generator-adapted") +} + +description = "A Java library for the Alpaca brokerage platform Broker API." + +dependencies { + api(project(":module:common")) +} + +openApiGeneratorAdapted { + environmentUrlProduction = "https://broker-api.alpaca.markets" + environmentUrlSandbox = "https://broker-api.sandbox.alpaca.markets" +} diff --git a/module/market-data/build.gradle.kts b/module/market-data/build.gradle.kts new file mode 100644 index 00000000..848ba876 --- /dev/null +++ b/module/market-data/build.gradle.kts @@ -0,0 +1,15 @@ +plugins { + id("module-common") + id("openapi-generator-adapted") +} + +description = "A Java library for the Alpaca brokerage platform Market Data API." + +dependencies { + api(project(":module:common")) +} + +openApiGeneratorAdapted { + environmentUrlProduction = "https://data.alpaca.markets" + environmentUrlSandbox = "https://data.sandbox.alpaca.markets" +} diff --git a/module/trading/build.gradle.kts b/module/trading/build.gradle.kts new file mode 100644 index 00000000..3a73dfc0 --- /dev/null +++ b/module/trading/build.gradle.kts @@ -0,0 +1,15 @@ +plugins { + id("module-common") + id("openapi-generator-adapted") +} + +description = "A Java library for the Alpaca brokerage platform Trading API." + +dependencies { + api(project(":module:common")) +} + +openApiGeneratorAdapted { + environmentUrlProduction = "https://api.alpaca.markets" + environmentUrlSandbox = "https://paper-api.alpaca.markets" +} diff --git a/settings.gradle.kts b/settings.gradle.kts new file mode 100644 index 00000000..3685ed2b --- /dev/null +++ b/settings.gradle.kts @@ -0,0 +1,5 @@ +rootProject.name = "alpaca-java" +include("module:common") +include("module:market-data") +include("module:trading") +include("module:broker") From e6418c7ab9616b3c73bcb6c79c6c1d31e24fb3ec Mon Sep 17 00:00:00 2001 From: Jacob Peterson <14127217+Petersoj@users.noreply.github.com> Date: Mon, 22 Jun 2026 19:57:24 -0700 Subject: [PATCH 242/271] Add OpenAPI spec JSON files downloaded from Alpaca Docs for `broker`, `market-data`, and `trading` modules --- module/broker/openapi.json | 23074 ++++++++++++++++++++++++++++++ module/market-data/openapi.json | 8634 +++++++++++ module/trading/openapi.json | 9291 ++++++++++++ 3 files changed, 40999 insertions(+) create mode 100644 module/broker/openapi.json create mode 100644 module/market-data/openapi.json create mode 100644 module/trading/openapi.json diff --git a/module/broker/openapi.json b/module/broker/openapi.json new file mode 100644 index 00000000..0a0b338b --- /dev/null +++ b/module/broker/openapi.json @@ -0,0 +1,23074 @@ +{ + "components": { + "examples": { + "CryptoOrderResponse": { + "value": { + "asset_class": "crypto", + "asset_id": "a1733398-6acc-4e92-af24-0d0667f78713", + "canceled_at": null, + "client_order_id": "5b5d3d67-06ad-4ffa-af65-a117d0fc5a59", + "created_at": "2023-12-12T22:36:51.337711497Z", + "expired_at": null, + "extended_hours": false, + "failed_at": null, + "filled_at": null, + "filled_avg_price": null, + "filled_qty": "0", + "hwm": null, + "id": "38e482f3-79a8-4f75-a057-f07a1ec6a397", + "legs": null, + "limit_price": "2100", + "notional": null, + "order_class": "", + "order_type": "limit", + "qty": "0.02", + "replaced_at": null, + "replaced_by": null, + "replaces": null, + "side": "buy", + "source": null, + "status": "pending_new", + "stop_price": null, + "submitted_at": "2023-12-12T22:36:51.313261061Z", + "subtag": null, + "symbol": "ETH/USD", + "time_in_force": "gtc", + "trail_percent": null, + "trail_price": null, + "type": "limit", + "updated_at": "2023-12-12T22:36:51.337754768Z" + } + }, + "EquityOrderResponse": { + "value": { + "asset_class": "us_equity", + "asset_id": "b0b6dd9d-8b9b-48a9-ba46-b9d54906e415", + "canceled_at": null, + "client_order_id": "5680c4bc-9ac1-4a12-a44c-df427ba53032", + "created_at": "2023-12-12T22:31:24.668464435Z", + "expired_at": null, + "extended_hours": false, + "failed_at": null, + "filled_at": null, + "filled_avg_price": null, + "filled_qty": "0", + "hwm": null, + "id": "7b08df51-c1ac-453c-99f9-323a5f075f0d", + "legs": null, + "limit_price": "150", + "notional": null, + "order_class": "", + "order_type": "limit", + "qty": "2", + "replaced_at": null, + "replaced_by": null, + "replaces": null, + "side": "buy", + "source": null, + "status": "accepted", + "stop_price": null, + "submitted_at": "2023-12-12T22:31:24.577215743Z", + "subtag": null, + "symbol": "AAPL", + "time_in_force": "gtc", + "trail_percent": null, + "trail_price": null, + "type": "limit", + "updated_at": "2023-12-12T22:31:24.668464435Z" + } + }, + "IPOEventAllocation": { + "summary": "Final allocation result for an account (account-scoped event)", + "value": { + "account_id": "6221407b-32b2-3e6b-a7fe-cd79e1b68ba3", + "at": "2026-03-20T00:15:42.318901Z", + "correspondent": "COR1", + "event_id": "01J9RTPZGHKVB8M3X4F5J7LCQ9", + "offering_reference": "FI111225", + "payload": { + "allocated_amount": "1764", + "allocated_shares": "42", + "cusip_id": "123456789", + "final_price": "42", + "subject": "IPO Allocation: Example Corp" + }, + "received_at": "2026-03-20T00:15:42.103572Z", + "verb": "Allocation" + } + }, + "IPOEventOffering": { + "summary": "New IPO offering published", + "value": { + "at": "2026-03-20T12:24:58.807230Z", + "event_id": "01J9RPMV5TKB8WX3M4F1KZ7QH2", + "offering_reference": "FI111225", + "payload": { + "available_to_order": "Available", + "max_price": "42", + "min_price": "42", + "name": "Example Corp", + "source": "example source", + "ticker_symbol": "EXMP" + }, + "received_at": "2026-03-20T12:24:58.500000Z", + "verb": "Offering" + } + }, + "IPOEventOfferingCancellation": { + "summary": "Offering was cancelled (no payload field)", + "value": { + "at": "2026-03-20T12:24:52.325158Z", + "event_id": "01J9RVB6Y4ZK8M3N7QD2WX1RFP", + "offering_reference": "FI111225", + "received_at": "2026-03-20T12:24:52.110430Z", + "verb": "OfferingCancellation" + } + }, + "IPOEventOfferingUpdate": { + "summary": "Existing offering updated (e.g. price range narrowed)", + "value": { + "at": "2026-03-20T15:42:11.118274Z", + "event_id": "01J9RQ5HNZQK7M3RDVJ8XBPCT1", + "offering_reference": "FI111225", + "payload": { + "available_to_order": "Available", + "max_price": "44", + "min_price": "40", + "name": "Example Corp", + "source": "example source", + "ticker_symbol": "EXMP" + }, + "received_at": "2026-03-20T15:42:10.892140Z", + "verb": "OfferingUpdate" + } + }, + "IPOEventProspectus": { + "summary": "Prospectus document is now available", + "value": { + "at": "2026-03-19T23:26:46.853262Z", + "event_id": "01J9RSGD8JR2A4K7MZNTPVQ5XW", + "offering_reference": "FI111225", + "payload": { + "prospectus_url": "https://example.com/prospectus/example-corp.pdf", + "subject": "IPO Prospectus: Example Corp" + }, + "received_at": "2026-03-19T23:26:46.612880Z", + "verb": "Prospectus" + } + }, + "IPOEventSixtyMinMail": { + "summary": "60-minute pricing window opened (no new orders accepted)", + "value": { + "at": "2026-03-19T22:50:11.729329Z", + "event_id": "01J9RT9P3WYH8KCJM2LFQX5ZRD", + "offering_reference": "FI111225", + "payload": { + "sixty_minute_expiration_time": "2026-03-19T23:50:11.729329Z", + "subject": "IPO 60 Minute Mail: Example Corp" + }, + "received_at": "2026-03-19T22:50:11.512640Z", + "verb": "SixtyMinMail" + } + }, + "IPOOfferingGetExample": { + "summary": "Single IPO offering", + "value": { + "data": { + "anticipated_shares": 5000000, + "availability": "available", + "cusip_id": "123456789", + "description": "Example Corp is a leading provider of example services.", + "ipo_reference": "FI111225", + "logo_small": "https://example.com/logos/exmp-small.png", + "max_price": "44", + "max_ticket_size": "100000", + "min_price": "40", + "min_ticket_size": "100", + "name": "Example Corp", + "no_new_orders": false, + "offering_type": "IPO", + "prospectus_url": "https://example.com/prospectus/example-corp.pdf", + "settlement_date": "2026-04-04", + "ticker_symbol": "EXMP", + "trade_date": "2026-04-02", + "underwriters": [ + "Goldman Sachs", + "Morgan Stanley" + ], + "unit_step_size": "1" + } + } + }, + "IPOOfferingListExample": { + "summary": "Page of available IPO offerings", + "value": { + "data": [ + { + "anticipated_shares": 5000000, + "availability": "available", + "cusip_id": "123456789", + "description": "Example Corp is a leading provider of example services.", + "ipo_reference": "FI111225", + "logo_small": "https://example.com/logos/exmp-small.png", + "max_price": "44", + "max_ticket_size": "100000", + "min_price": "40", + "min_ticket_size": "100", + "name": "Example Corp", + "no_new_orders": false, + "offering_type": "IPO", + "prospectus_url": "https://example.com/prospectus/example-corp.pdf", + "settlement_date": "2026-04-04", + "ticker_symbol": "EXMP", + "trade_date": "2026-04-02", + "underwriters": [ + "Goldman Sachs", + "Morgan Stanley" + ], + "unit_step_size": "1" + } + ], + "next_page_token": null + } + }, + "IPOOrderResponse": { + "value": { + "asset_class": "ipo", + "asset_id": "9b6c7c1a-9eb2-4d4a-8a3a-1bf4c1d5cbaa", + "canceled_at": null, + "client_order_id": "0b3a90b6-6b5c-4d83-8c0e-9b3eaf4a8d11", + "created_at": "2025-09-15T13:30:42.117344821Z", + "expired_at": null, + "extended_hours": false, + "failed_at": null, + "filled_at": null, + "filled_avg_price": null, + "filled_qty": "0", + "hwm": null, + "id": "5e2a8f1a-9c10-4d3a-8c64-7f59c1d18c11", + "legs": null, + "limit_price": null, + "notional": "500", + "order_class": "", + "order_type": "market", + "qty": null, + "replaced_at": null, + "replaced_by": null, + "replaces": null, + "side": "buy", + "source": null, + "status": "accepted", + "stop_price": null, + "submitted_at": "2025-09-15T13:30:42.094518219Z", + "subtag": null, + "symbol": "FI111225", + "time_in_force": "gtc", + "trail_percent": null, + "trail_price": null, + "type": "market", + "updated_at": "2025-09-15T13:30:42.117344821Z" + } + }, + "MultilegOptionsOrderResponse": { + "value": { + "asset_class": "", + "asset_id": "", + "canceled_at": null, + "client_order_id": "646b1fe6-b212-4f54-94c6-429e7bcdee04", + "created_at": "2024-12-10T16:15:53.677230742Z", + "expired_at": null, + "extended_hours": false, + "failed_at": null, + "filled_at": "2024-12-10T16:15:53.694Z", + "filled_avg_price": "1.28", + "filled_qty": "1", + "hwm": null, + "id": "83f37e9f-6b1f-49ed-8fc6-3e6af716323f", + "legs": [ + { + "asset_class": "us_option", + "asset_id": "f0ea14b2-8a49-4e9b-89d1-894c6e518a76", + "canceled_at": null, + "client_order_id": "cc8cc104-fe43-476c-b25c-f62650fb73f9", + "created_at": "2024-12-10T16:15:53.677230742Z", + "expired_at": null, + "expires_at": "2024-12-10T21:00:00Z", + "extended_hours": false, + "failed_at": null, + "filled_at": "2024-12-10T16:15:53.694Z", + "filled_avg_price": "0.43", + "filled_qty": "3", + "hwm": null, + "id": "df4ff24a-c58a-4e37-8b9f-ef32b83a11f2", + "legs": null, + "limit_price": null, + "notional": null, + "order_class": "mleg", + "order_type": "", + "position_intent": "buy_to_open", + "qty": "3", + "ratio_qty": "3", + "replaced_at": null, + "replaced_by": null, + "replaces": null, + "side": "buy", + "source": null, + "status": "filled", + "stop_price": null, + "submitted_at": "2024-12-10T16:15:53.684952901Z", + "subtag": null, + "symbol": "AAPL241213C00250000", + "time_in_force": "day", + "trail_percent": null, + "trail_price": null, + "type": "", + "updated_at": "2024-12-10T16:15:53.725091158Z" + }, + { + "asset_class": "us_option", + "asset_id": "f89940db-eeb1-46e6-8f9b-bb1f27a0b395", + "canceled_at": null, + "client_order_id": "0bd2d36d-4af2-4dfb-8418-333a5d5026fa", + "created_at": "2024-12-10T16:15:53.677230742Z", + "expired_at": null, + "expires_at": "2024-12-10T21:00:00Z", + "extended_hours": false, + "failed_at": null, + "filled_at": "2024-12-10T16:15:53.694Z", + "filled_avg_price": "0.01", + "filled_qty": "1", + "hwm": null, + "id": "ecd91110-c34d-4e9d-a7bf-a9c27c40f8b5", + "legs": null, + "limit_price": null, + "notional": null, + "order_class": "mleg", + "order_type": "", + "position_intent": "sell_to_open", + "qty": "1", + "ratio_qty": "1", + "replaced_at": null, + "replaced_by": null, + "replaces": null, + "side": "sell", + "source": null, + "status": "filled", + "stop_price": null, + "submitted_at": "2024-12-10T16:15:53.684952901Z", + "subtag": null, + "symbol": "AAPL241213C00260000", + "time_in_force": "day", + "trail_percent": null, + "trail_price": null, + "type": "", + "updated_at": "2024-12-10T16:15:53.708983759Z" + } + ], + "limit_price": "10", + "notional": null, + "order_class": "mleg", + "order_type": "limit", + "qty": "1", + "replaced_at": null, + "replaced_by": null, + "replaces": null, + "side": "", + "source": null, + "status": "filled", + "stop_price": null, + "submitted_at": "2024-12-10T16:15:53.684952901Z", + "subtag": null, + "symbol": "", + "time_in_force": "day", + "trail_percent": null, + "trail_price": null, + "type": "limit", + "updated_at": "2024-12-10T16:15:53.725139688Z" + } + }, + "OptionOrderResponse": { + "value": { + "asset_class": "us_option", + "asset_id": "98359ef7-5124-49f3-85ea-5cf02df6defa", + "canceled_at": null, + "client_order_id": "58cd43a7-029e-457e-b77f-cd4f61f00f2a", + "created_at": "2023-12-12T21:35:49.102449524Z", + "expired_at": null, + "extended_hours": false, + "failed_at": null, + "filled_at": null, + "filled_avg_price": null, + "filled_qty": "0", + "hwm": null, + "id": "30a077fa-96f6-4f20-a052-4b921ee2f243", + "legs": null, + "limit_price": "10", + "notional": null, + "order_class": "simple", + "order_type": "limit", + "qty": "2", + "replaced_at": null, + "replaced_by": null, + "replaces": null, + "side": "buy", + "source": null, + "status": "pending_new", + "stop_price": null, + "submitted_at": "2023-12-12T21:35:49.056332248Z", + "subtag": null, + "symbol": "AAPL250620C00100000", + "time_in_force": "day", + "trail_percent": null, + "trail_price": null, + "type": "limit", + "updated_at": "2023-12-12T21:35:49.102504673Z" + } + }, + "SystemEventV2": { + "value": { + "at": "2026-01-05T10:28:23.163857Z", + "description": "End-of-day balances are now available.", + "event_id": "01F535Y1FVY8WZHE763HCNS8SZ", + "system_date": "2026-01-05", + "type": "eod_balances_ready" + } + }, + "TradeUpdateEventV2MultilegOptionsFill": { + "value": { + "account_id": "529248ad-c4cc-4a50-bea4-6bfd2953f83a", + "at": "2025-01-14T16:05:51.872012Z", + "event": "fill", + "event_id": "01G112NTT0XAXKDZK3AABK68TH", + "legs": [ + { + "execution_id": "ccf7d1dc-78e1-4eb5-92c6-5c86b2bcca8f", + "order_id": "8d58279f-7dc8-415f-8495-32f394935509", + "price": "0.04", + "qty": "1", + "symbol": "AAPL250117P00200000", + "timestamp": "2025-01-14T16:05:51.867802561Z" + }, + { + "execution_id": "06f94555-6db7-4059-a4c4-0b993084ccd0", + "order_id": "8b0e2cff-eace-4e6a-8810-cad42c63df59", + "price": "0.03", + "qty": "1", + "symbol": "AAPL250117C00250000", + "timestamp": "2025-01-14T16:05:51.867813051Z" + } + ], + "order": { + "asset_class": "", + "asset_id": "00000000-0000-0000-0000-000000000000", + "cancel_requested_at": null, + "canceled_at": null, + "client_order_id": "9d2f39de-adae-4dae-a67e-838bf21fb5ae", + "created_at": "2025-01-14T16:05:51.79424769Z", + "expired_at": null, + "extended_hours": false, + "failed_at": null, + "filled_at": "2025-01-14T16:05:51.867813051Z", + "filled_avg_price": "0.07", + "filled_qty": "1", + "hwm": null, + "id": "8af45a94-0b35-4053-ad4e-716c6783fcc9", + "legs": [ + { + "asset_class": "us_option", + "asset_id": "8d1ba989-d98b-4551-889f-4647c2e86e20", + "cancel_requested_at": null, + "canceled_at": null, + "client_order_id": "9c5ce763-bd6f-41eb-b1b1-ed2c0b99d268", + "created_at": "2025-01-14T16:05:51.79424769Z", + "expired_at": null, + "expires_at": "2025-01-14T21:00:00Z", + "extended_hours": false, + "failed_at": null, + "filled_at": "2025-01-14T16:05:51.867802561Z", + "filled_avg_price": "0.04", + "filled_qty": "1", + "hwm": null, + "id": "8d58279f-7dc8-415f-8495-32f394935509", + "legs": null, + "limit_price": null, + "notional": null, + "order_class": "mleg", + "order_type": "", + "position_intent": "buy_to_open", + "qty": "1", + "ratio_qty": "1", + "replaced_at": null, + "replaced_by": null, + "replaces": null, + "side": "buy", + "status": "filled", + "stop_price": null, + "submitted_at": "2025-01-14T16:05:51.800245966Z", + "symbol": "AAPL250117P00200000", + "time_in_force": "day", + "trail_percent": null, + "trail_price": null, + "type": "", + "updated_at": "2025-01-14T16:05:51.869810922Z" + }, + { + "asset_class": "us_option", + "asset_id": "e9f8c9ba-de7c-4e51-9eef-629f4cb79049", + "cancel_requested_at": null, + "canceled_at": null, + "client_order_id": "9cda9826-8fd5-4c01-8ee1-30c5286e2387", + "created_at": "2025-01-14T16:05:51.79424769Z", + "expired_at": null, + "expires_at": "2025-01-14T21:00:00Z", + "extended_hours": false, + "failed_at": null, + "filled_at": "2025-01-14T16:05:51.867813051Z", + "filled_avg_price": "0.03", + "filled_qty": "1", + "hwm": null, + "id": "8b0e2cff-eace-4e6a-8810-cad42c63df59", + "legs": null, + "limit_price": null, + "notional": null, + "order_class": "mleg", + "order_type": "", + "position_intent": "buy_to_open", + "qty": "1", + "ratio_qty": "1", + "replaced_at": null, + "replaced_by": null, + "replaces": null, + "side": "buy", + "status": "filled", + "stop_price": null, + "submitted_at": "2025-01-14T16:05:51.802310606Z", + "symbol": "AAPL250117C00250000", + "time_in_force": "day", + "trail_percent": null, + "trail_price": null, + "type": "", + "updated_at": "2025-01-14T16:05:51.870879612Z" + } + ], + "limit_price": "0.6", + "notional": null, + "order_class": "mleg", + "order_type": "limit", + "position_intent": "", + "qty": "1", + "replaced_at": null, + "replaced_by": null, + "replaces": null, + "side": "buy", + "status": "filled", + "stop_price": null, + "submitted_at": "2025-01-14T16:05:51.802310606Z", + "symbol": "", + "time_in_force": "day", + "trail_percent": null, + "trail_price": null, + "type": "limit", + "updated_at": "2025-01-14T16:05:51.870937762Z" + }, + "position_qtys": { + "AAPL250117C00250000": "1", + "AAPL250117P00200000": "1" + }, + "price": "0.07", + "qty": "1", + "timestamp": "2025-01-14T16:05:51.867813051Z" + } + }, + "TradeUpdateEventV2New": { + "value": { + "account_id": "529248ad-c4cc-4a50-bea4-6bfd2953f83a", + "at": "2022-04-19T14:12:30.656741Z", + "event": "new", + "event_id": "01G112NTT0XAXKDZK3AABK68TH", + "execution_id": "7e544af3-3104-4e1a-8cbc-dab2624949ff", + "order": { + "asset_class": "us_equity", + "asset_id": "a4778bc8-fad1-47b7-87fe-d5cde10d43f4", + "cancel_requested_at": null, + "canceled_at": null, + "client_order_id": "6d873193-dac6-4f72-8e13-c57853a9339d", + "commission": "1", + "created_at": "2022-04-19T10:12:30.57117938-04:00", + "expired_at": null, + "expires_at": "2022-04-19T21:00:00Z", + "extended_hours": false, + "failed_at": null, + "filled_at": null, + "filled_avg_price": null, + "filled_qty": "0", + "hwm": null, + "id": "edada91a-8b55-4916-a153-8c7a9817e708", + "legs": null, + "limit_price": "700", + "notional": null, + "order_class": "", + "order_type": "limit", + "position_intent": "buy_to_open", + "qty": "4", + "replaced_at": null, + "replaced_by": null, + "replaces": null, + "side": "buy", + "status": "new", + "stop_price": null, + "submitted_at": "2022-04-19T10:12:30.403135025-04:00", + "symbol": "TSLA", + "time_in_force": "day", + "trail_percent": null, + "trail_price": null, + "type": "limit", + "updated_at": "2022-04-19T10:12:30.609783218-04:00" + }, + "previous_execution_id": "aeb60660-412f-4537-8d1f-1101b3fc8f64", + "timestamp": "2022-04-19T14:12:30.602193534Z" + } + } + }, + "headers": { + "ratelimit_limit": { + "description": "Request limit per minute.", + "example": 100, + "schema": { + "type": "integer" + } + }, + "ratelimit_remaining": { + "description": "Request limit per minute remaining.", + "example": 90, + "schema": { + "type": "integer" + } + }, + "ratelimit_reset": { + "description": "The UNIX epoch when the remaining quota changes.", + "example": 1674044551, + "schema": { + "type": "integer" + } + } + }, + "parameters": { + "AccountDocumentType": { + "description": "The type of the account document", + "in": "query", + "name": "type", + "required": false, + "schema": { + "enum": [ + "account_application", + "account_statement", + "crypto_account_statement", + "options_application", + "tax_statement", + "trade_confirmation_json", + "trade_confirmation" + ], + "example": "account_statement", + "type": "string" + } + }, + "AccountID": { + "description": "Account identifier.", + "in": "path", + "name": "account_id", + "required": true, + "schema": { + "format": "uuid", + "type": "string" + } + }, + "ActivityType": { + "description": "see ActivityType model for details about what the different types mean", + "in": "path", + "name": "activity_type", + "required": true, + "schema": { + "enum": [ + "FILL", + "ACATC", + "ACATS", + "CGD", + "CIL", + "CSD", + "CSW", + "DIV", + "DIVCGL", + "DIVCGS", + "DIVNRA", + "DIVROC", + "DIVTXEX", + "FEE", + "INT", + "MEM", + "JNLC", + "JNLS", + "MA", + "OPASN", + "OPCA", + "OPCSH", + "OPEXC", + "OPEXP", + "OPTRD", + "PTC", + "REORG", + "SPIN", + "SPLIT", + "FOPT" + ], + "type": "string" + } + }, + "After": { + "description": "The response will include only ones submitted after this timestamp (exclusive)", + "in": "query", + "name": "after", + "schema": { + "example": "2021-03-16T18:38:01.942282Z", + "format": "date-time", + "type": "string" + } + }, + "AssetClass": { + "description": "A comma-separated list of asset classes, the response will include only orders in the specified asset classes. By specifying `us_option` as the class, you can query option orders by underlying symbol using the symbols parameter.", + "in": "query", + "name": "asset_class", + "schema": { + "items": { + "enum": [ + "us_equity", + "us_option", + "crypto", + "all" + ], + "type": "string" + }, + "type": "array" + } + }, + "Direction": { + "description": "The chronological order of response based on the submission time. asc or desc. Defaults to desc.", + "in": "query", + "name": "direction", + "schema": { + "enum": [ + "asc", + "desc" + ], + "example": "desc", + "type": "string" + } + }, + "DocumentID": { + "description": "Document identifier.", + "in": "path", + "name": "document_id", + "required": true, + "schema": { + "format": "uuid", + "type": "string" + } + }, + "OrderID": { + "description": "Order identifier.", + "in": "path", + "name": "order_id", + "required": true, + "schema": { + "type": "string" + } + }, + "PageToken": { + "description": "Used for pagination, this token retrieves the next page of results. It is obtained from the response of the preceding page when additional pages are available.", + "in": "query", + "name": "page_token", + "required": false, + "schema": { + "example": "MA==", + "type": "string" + } + }, + "Sort": { + "description": "The chronological order of response based on the submission time. asc or desc. Defaults to desc.", + "in": "query", + "name": "sort", + "schema": { + "enum": [ + "asc", + "desc" + ], + "example": "desc", + "type": "string" + } + }, + "Symbols": { + "description": "A comma-separated list of symbols to filter by.", + "in": "query", + "name": "symbols", + "schema": { + "example": "AAPL,TSLA,MSFT", + "type": "string" + } + }, + "Until": { + "description": "The response will include only ones submitted until this timestamp (exclusive)", + "in": "query", + "name": "until", + "schema": { + "example": "2021-03-16T18:38:01.942282Z", + "format": "date-time", + "type": "string" + } + }, + "end": { + "description": "The last date to retrieve data for (inclusive). Default: one week from the start date.\n", + "example": "2030-01-01", + "in": "query", + "name": "end", + "required": false, + "schema": { + "format": "date", + "type": "string" + } + }, + "legacy_date_type": { + "description": "Indicates what start and end mean. Default: TRADING. If TRADING is specified, returns a calendar whose trading date matches start, end. If SETTLEMENT is specified, returns the calendar whose settlement date matches start and end.\n", + "in": "query", + "name": "date_type", + "schema": { + "enum": [ + "TRADING", + "SETTLEMENT" + ], + "type": "string" + } + }, + "legacy_end": { + "description": "The last date to retrieve data for (inclusive).", + "in": "query", + "name": "end", + "schema": { + "format": "date-time", + "type": "string", + "x-go-type": "legacyTime" + } + }, + "legacy_start": { + "description": "The first date to retrieve data for (inclusive).", + "in": "query", + "name": "start", + "schema": { + "format": "date-time", + "type": "string", + "x-go-type": "legacyTime" + } + }, + "market": { + "description": "The market identifier. MIC, BIC, or acronym.\n\nAvailable market codes by region:\n\n**United States**\n- `BMO`: Bank of Montreal. US banking calendar (US operations).\n- `BNYM`: The Bank of New York Mellon. US banking calendar.\n- `BOATS`: Blue Ocean Alternative Trading System. US overnight trading.\n- `IEX`: Investors Exchange. US equities.\n- `IEXG`: Investors Exchange. US equities.\n- `NASDAQ`: National Association of Securities Dealers Automated Quotations. US equities.\n- `NYSE`: New York Stock Exchange. US equities.\n- `OCEA`: BlueOcean ATS. US overnight trading.\n- `OPRA`: Options Price Reporting Authority. US options.\n- `OTC`: Over-The-Counter. US OTC equities.\n- `OTCM`: OTC Markets. US OTC equities.\n- `SIFMA`: Securities Industry and Financial Markets Association. US bonds.\n- `XNAS`: NASDAQ. US equities.\n- `XNYS`: New York Stock Exchange. US equities.\n\n**Europe**\n- `CEUX`: Cboe CEUX Europe. European equities (Cboe Netherlands).\n- `CHIX`: Cboe CHIX Europe. European equities (Cboe UK).\n- `ISE`: Euronext Dublin. Irish equities.\n- `LSE`: London Stock Exchange. UK equities.\n- `MTA`: Euronext Milan. Italian equities.\n- `MTAA`: Euronext Milan. Italian equities.\n- `XAMS`: Euronext Amsterdam. Dutch equities.\n- `XBRU`: Euronext Brussels. Belgian equities.\n- `XDUB`: Euronext Dublin. Irish equities.\n- `XETR`: Frankfurt Stock Exchange. German equities.\n- `XETRA`: Frankfurt Stock Exchange. German equities.\n- `XLIS`: Euronext Lisbon. Portuguese equities.\n- `XLON`: London Stock Exchange. UK equities.\n- `XPAR`: Euronext Paris. French equities.\n\n**Asia & Middle East**\n- `HKEX`: Hong Kong Stock Exchange. Hong Kong equities.\n- `JPX`: Japan Exchange Group. Japanese equities.\n- `TADAWUL`: Saudi Stock Exchange. Saudi equities.\n- `XHKG`: Hong Kong Stock Exchange. Hong Kong equities.\n- `XSAU`: Saudi Stock Exchange. Saudi equities.\n- `XTKS`: Japan Exchange Group. Japanese equities.\n", + "in": "path", + "name": "market", + "required": true, + "schema": { + "$ref": "#/components/schemas/market" + } + }, + "markets": { + "description": "Comma-separated list of markets. Available market codes by region:\n\n**United States**\n- `BMO`: Bank of Montreal. US banking calendar (US operations).\n- `BNYM`: The Bank of New York Mellon. US banking calendar.\n- `BOATS`: Blue Ocean Alternative Trading System. US overnight trading.\n- `IEX`: Investors Exchange. US equities.\n- `IEXG`: Investors Exchange. US equities.\n- `NASDAQ`: National Association of Securities Dealers Automated Quotations. US equities.\n- `NYSE`: New York Stock Exchange. US equities.\n- `OCEA`: BlueOcean ATS. US overnight trading.\n- `OPRA`: Options Price Reporting Authority. US options.\n- `OTC`: Over-The-Counter. US OTC equities.\n- `OTCM`: OTC Markets. US OTC equities.\n- `SIFMA`: Securities Industry and Financial Markets Association. US bonds.\n- `XNAS`: NASDAQ. US equities.\n- `XNYS`: New York Stock Exchange. US equities.\n\n**Europe**\n- `CEUX`: Cboe CEUX Europe. European equities (Cboe Netherlands).\n- `CHIX`: Cboe CHIX Europe. European equities (Cboe UK).\n- `ISE`: Euronext Dublin. Irish equities.\n- `LSE`: London Stock Exchange. UK equities.\n- `MTA`: Euronext Milan. Italian equities.\n- `MTAA`: Euronext Milan. Italian equities.\n- `XAMS`: Euronext Amsterdam. Dutch equities.\n- `XBRU`: Euronext Brussels. Belgian equities.\n- `XDUB`: Euronext Dublin. Irish equities.\n- `XETR`: Frankfurt Stock Exchange. German equities.\n- `XETRA`: Frankfurt Stock Exchange. German equities.\n- `XLIS`: Euronext Lisbon. Portuguese equities.\n- `XLON`: London Stock Exchange. UK equities.\n- `XPAR`: Euronext Paris. French equities.\n\n**Asia & Middle East**\n- `HKEX`: Hong Kong Stock Exchange. Hong Kong equities.\n- `JPX`: Japan Exchange Group. Japanese equities.\n- `TADAWUL`: Saudi Stock Exchange. Saudi equities.\n- `XHKG`: Hong Kong Stock Exchange. Hong Kong equities.\n- `XSAU`: Saudi Stock Exchange. Saudi equities.\n- `XTKS`: Japan Exchange Group. Japanese equities.\n", + "example": "NYSE,LSE", + "in": "query", + "name": "markets", + "schema": { + "type": "string" + } + }, + "start": { + "description": "The first date to retrieve data for (inclusive). Default: today.\n", + "example": "2025-01-01", + "in": "query", + "name": "start", + "required": false, + "schema": { + "format": "date", + "type": "string" + } + }, + "time": { + "description": "Instead of the current time, use this time for the clock.", + "in": "query", + "name": "time", + "required": false, + "schema": { + "format": "date-time", + "type": "string" + } + }, + "timezone": { + "description": "Timezone of the times. Default: the timezone of the market.\n", + "in": "query", + "name": "timezone", + "required": false, + "schema": { + "enum": [ + "UTC" + ], + "type": "string" + } + } + }, + "responses": { + "400": { + "description": "One of the request parameters is invalid. See the returned message for details.\n", + "headers": { + "X-RateLimit-Limit": { + "$ref": "#/components/headers/ratelimit_limit" + }, + "X-RateLimit-Remaining": { + "$ref": "#/components/headers/ratelimit_remaining" + }, + "X-RateLimit-Reset": { + "$ref": "#/components/headers/ratelimit_reset" + } + } + }, + "401": { + "description": "Authentication headers are missing or invalid. Make sure you authenticate your request with a valid API key.\n" + }, + "403": { + "description": "The requested resource is forbidden.\n" + }, + "429": { + "description": "Too many requests. You hit the rate limit. Use the X-RateLimit-... response headers to make sure you're under the rate limit.\n", + "headers": { + "X-RateLimit-Limit": { + "$ref": "#/components/headers/ratelimit_limit" + }, + "X-RateLimit-Remaining": { + "$ref": "#/components/headers/ratelimit_remaining" + }, + "X-RateLimit-Reset": { + "$ref": "#/components/headers/ratelimit_reset" + } + } + }, + "500": { + "description": "Internal server error. We recommend retrying these later. If the issue persists, please contact us on Slack or on the Community Forum.\n" + }, + "BadRequest": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + }, + "description": "Malformed input." + }, + "Forbidden": { + "content": { + "application/json": { + "schema": { + "type": "string" + } + } + }, + "description": "Request is forbidden" + }, + "NotAuthorized": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + }, + "description": "Client is not authorized for this operation." + }, + "NotFound": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + }, + "description": "Resource does not exist." + } + }, + "schemas": { + "ACHRelationship": { + "properties": { + "account_id": { + "format": "uuid", + "type": "string" + }, + "account_owner_name": { + "description": "Name of the account owner", + "minLength": 1, + "type": "string" + }, + "bank_account_number": { + "minLength": 1, + "type": "string" + }, + "bank_account_type": { + "description": "Must be CHECKING or SAVINGS", + "enum": [ + "CHECKING", + "SAVINGS" + ], + "minLength": 1, + "type": "string" + }, + "bank_routing_number": { + "minLength": 1, + "type": "string" + }, + "created_at": { + "description": "Format: 2020-01-01T01:01:01Z", + "example": "2021-03-16T18:38:01.942282Z", + "format": "date-time", + "type": "string" + }, + "id": { + "example": "61e69015-8549-4bfd-b9c3-01e75843f47d", + "format": "uuid", + "type": "string" + }, + "nickname": { + "minLength": 1, + "type": "string" + }, + "status": { + "enum": [ + "QUEUED", + "APPROVED", + "REJECTED", + "PENDING", + "CANCEL_REQUESTED" + ], + "type": "string" + }, + "updated_at": { + "description": "Format: 2020-01-01T01:01:01Z", + "example": "2021-03-16T18:38:01.942282Z", + "format": "date-time", + "type": "string" + } + }, + "required": [ + "id", + "created_at", + "updated_at", + "account_id", + "status", + "account_owner_name" + ], + "title": "ACHRelationship", + "type": "object" + }, + "APRTier": { + "properties": { + "account_rate_bps": { + "description": "The annualized account interest rate, in basis points.", + "example": 450, + "type": "integer" + }, + "correspondent_rate_bps": { + "description": "The annualized correspondent fee rate, in basis points.", + "example": 25, + "type": "integer" + }, + "created_at": { + "description": "The timestamp the APR tier was created, in RFC 3339 format.", + "example": "2024-09-16T20:06:32.207729Z", + "format": "date-time", + "type": "string" + }, + "currency": { + "description": "The currency of the APR tier", + "example": "USD", + "type": "string" + }, + "details": { + "$ref": "#/components/schemas/APRTierDetails" + }, + "id": { + "description": "The unique identifier of the APR tier", + "example": "150df7eb-31e7-46b3-8848-a54ca21ff3bc", + "format": "uuid", + "type": "string" + }, + "is_default": { + "description": "True if this is the default APR tier. Only one APR tier can be default per currency.\n", + "example": true, + "type": "boolean" + }, + "name": { + "description": "The unique name of the APR tier", + "example": "gold", + "type": "string" + }, + "updated_at": { + "description": "The timestamp of the last update to the APR tier, in RFC 3339 format.", + "example": "2024-09-16T20:06:32.207729Z", + "format": "date-time", + "type": "string" + } + }, + "type": "object" + }, + "APRTierDetails": { + "description": "Additional details of the APR tier\n", + "properties": { + "as_of": { + "description": "The date on which the total balance was recorded, at the end of the day. YYYY-MM-DD format.\n", + "example": "2024-09-27", + "format": "date", + "type": "string" + }, + "total_accounts": { + "description": "The total number of accounts assigned this APR tier.", + "example": 42, + "type": "integer" + }, + "total_balance": { + "description": "The total balance of funds in this APR tier.", + "example": "10485.76", + "format": "decimal", + "type": "string" + } + }, + "type": "object" + }, + "AcatcActivityV2": { + "allOf": [ + { + "$ref": "#/components/schemas/CommonNTAActivityV2" + }, + { + "$ref": "#/components/schemas/CommonAcatActivityV2" + } + ], + "description": "Automated customer account transfer service (cash)", + "type": "object" + }, + "AcatsActivityV2": { + "allOf": [ + { + "$ref": "#/components/schemas/CommonNTAActivityV2" + }, + { + "$ref": "#/components/schemas/CommonAcatActivityV2" + }, + { + "properties": { + "symbol": { + "description": "The symbol of the security involved with the activity", + "type": "string" + } + }, + "required": [ + "symbol" + ], + "type": "object" + } + ], + "description": "Automated customer account transfer service (stock)", + "type": "object" + }, + "Account": { + "description": "Represents high level account info. Used when returning entire account information would not be useful like the getAllAccounts operation", + "properties": { + "account_number": { + "description": "A human-readable account number that can be shown to the end user", + "nullable": true, + "type": "string" + }, + "account_type": { + "$ref": "#/components/schemas/AccountType" + }, + "agreements": { + "items": { + "$ref": "#/components/schemas/Agreement" + }, + "type": "array" + }, + "cash_interest": { + "$ref": "#/components/schemas/AccountCashInterestResponse" + }, + "contact": { + "$ref": "#/components/schemas/Contact" + }, + "created_at": { + "description": "Timestamp (RFC3339) of account creation.", + "format": "date-time", + "type": "string" + }, + "crypto_status": { + "$ref": "#/components/schemas/AccountStatus" + }, + "currency": { + "$ref": "#/components/schemas/Currency" + }, + "disclosures": { + "$ref": "#/components/schemas/Disclosures" + }, + "documents": { + "description": "The documents associated with the primary owner of the account", + "items": { + "$ref": "#/components/schemas/OwnerDocument" + }, + "type": "array" + }, + "enabled_assets": { + "description": "Assets the user has enabled and is able to trade once status and/or crypto_status are ACTIVE", + "items": { + "$ref": "#/components/schemas/AssetClass" + }, + "type": "array" + }, + "fpsl": { + "$ref": "#/components/schemas/AccountFPSLResponse" + }, + "id": { + "description": "UUID that identifies the account for later reference", + "format": "uuid", + "type": "string" + }, + "identity": { + "$ref": "#/components/schemas/Identity" + }, + "last_equity": { + "description": "EOD equity calculation (cash + long market value + short market value)", + "format": "decimal", + "type": "string" + }, + "primary_account_holder_id": { + "description": "UUID that identifies the primary account holder (party) for accounts opened under the Multi-Live Accounts (MLA) flow.\nOnly populated when the account was created by referencing an existing account holder via `primary_account_holder_id`\non `POST /v1/accounts`. Otherwise `null`. Currently only supported for `account_type` of `trading` and `ira`.\n", + "format": "uuid", + "nullable": true, + "type": "string" + }, + "status": { + "$ref": "#/components/schemas/AccountStatus" + }, + "trusted_contact": { + "$ref": "#/components/schemas/TrustedContact" + } + }, + "required": [ + "id", + "account_number", + "status", + "currency", + "created_at", + "last_equity" + ], + "type": "object", + "x-examples": { + "example-1": { + "account_number": "9034005019", + "created_at": "2019-09-30T23:55:31.185998Z", + "currency": "USD", + "id": "0d18ae51-3c94-4511-b209-101e1666416b", + "last_equity": "1500.65", + "primary_account_holder_id": null, + "status": "APPROVED" + } + } + }, + "AccountCashInterestEvent": { + "description": "This property is included when the account's cash interest program had changed due to an enrollment, APR tier change, or unenrollment.\nThe from and to status or APR tier name will be included in the event, depending on the change.\n", + "properties": { + "apr_tier_name_from": { + "description": "The APR tier name before the change", + "type": "string" + }, + "apr_tier_name_to": { + "description": "The APR tier name after the change", + "type": "string" + }, + "currency": { + "description": "The currency of the cash interest program that changed.", + "example": "USD", + "type": "string" + }, + "status_from": { + "description": "The cash_interest program status of the account before the change", + "type": "string" + }, + "status_to": { + "description": "The cash_interest program status of the account after the change", + "type": "string" + } + }, + "type": "object" + }, + "AccountCashInterestPatch": { + "description": "Use this property to change the account's configuration for the USD cash interest program.\nTo enroll the account, specify the apr_tier_name. The status should not be specified on enrollment.\nTo change the APR tier, specify the new apr_tier_name. The status should not be specified on tier changes.\nThe unenroll, set the status to INACTIVE.\nAfter any change, the response will contain a status of PENDING_CHANGE.\nAn event showing the status change to ACTIVE (for enrollment or tier changes) or INACTIVE (for unenrollment) will be generated when the change is complete.\n", + "properties": { + "USD": { + "$ref": "#/components/schemas/AccountCashInterestProgram" + } + }, + "type": "object" + }, + "AccountCashInterestPost": { + "description": "The configuration of the account's USD cash interest program when creating an account.\nIf cash_interest is not provided and there is a default APR tier defined, that tier will be used.\nTo enroll the account in a non-default APR tier, provide the cash_interest object with the desired apr_tier_name. The status should not be specified on enrollment.\nThe response will contain a status of PENDING_CHANGE. An event showing the status change to ACTIVE will be generated when the enrollment is complete.\n", + "properties": { + "USD": { + "$ref": "#/components/schemas/AccountCashInterestProgram" + } + }, + "type": "object" + }, + "AccountCashInterestProgram": { + "properties": { + "apr_tier_name": { + "description": "The unique name of the APR tier for a specific program", + "example": "gold", + "type": "string" + }, + "status": { + "description": "The status of the account within a cash interest program. One of:\n- **ACTIVE**\nThe account is enrolled and eligible for idle cash to be swept at the end of day (EOD).\n- **INACTIVE**\nThe account is not enrolled due to it either not being eligible (e.g. the updated Alpaca Customer Agreement has not been signed), an APR tier needs to be assigned, or they have been unenrolled.\n- **PENDING_CHANGE**\nAn enrollment, APR Tier change, or unenrollment is in progress\n", + "example": "ACTIVE", + "type": "string" + } + }, + "type": "object" + }, + "AccountCashInterestResponse": { + "description": "The configuration and status of the account's USD cash interest program\n", + "properties": { + "USD": { + "$ref": "#/components/schemas/AccountCashInterestProgram" + } + }, + "type": "object" + }, + "AccountConfigurations": { + "description": "Represents additional configuration settings for an account", + "properties": { + "disable_overnight_trading": { + "description": "If true, overnight trading is disabled.", + "type": "boolean" + }, + "dtbp_check": { + "deprecated": true, + "description": "both, entry, or exit. Controls Day Trading Margin Call (DTMC) checks.", + "enum": [ + "both", + "entry", + "exit" + ], + "example": "both", + "type": "string", + "x-deprecation": { + "reason": "See https://docs.alpaca.markets/us/docs/understanding-finras-new-intraday-margin-rule-and-the-end-of-pdt", + "since": "2026-04-27T00:00:00Z", + "sunset": "2026-07-06T00:00:00Z" + } + }, + "fractional_trading": { + "description": "If true, account is able to participate in fractional trading", + "type": "boolean" + }, + "max_margin_multiplier": { + "description": "Can be \"1\" or \"2\"", + "type": "string" + }, + "max_options_trading_level": { + "description": "The desired maximum options trading level. 0=disabled, 1=Covered Call/Cash-Secured Put, 2=Long Call/Put, 3=Spreads/Straddles.", + "enum": [ + 0, + 1, + 2, + 3 + ], + "type": "integer" + }, + "no_shorting": { + "description": "If true, account becomes long-only mode.", + "type": "boolean" + }, + "pdt_check": { + "deprecated": true, + "example": "entry", + "type": "string", + "x-deprecation": { + "reason": "See https://docs.alpaca.markets/us/docs/understanding-finras-new-intraday-margin-rule-and-the-end-of-pdt", + "since": "2026-04-27T00:00:00Z", + "sunset": "2026-07-06T00:00:00Z" + } + }, + "ptp_no_exception_entry": { + "description": "If set to true then Alpaca will accept orders for PTP symbols with no exception. Default is false.", + "type": "string" + }, + "suspend_trade": { + "description": "If true, new orders are blocked.", + "type": "boolean" + }, + "trade_confirm_email": { + "description": "all or none. If none, emails for order fills are not sent.", + "enum": [ + "all", + "none" + ], + "type": "string" + } + }, + "title": "AccountConfigurations", + "type": "object" + }, + "AccountCreationRequest": { + "description": "Represents the fields required to create a new account", + "properties": { + "account_sub_type": { + "$ref": "#/components/schemas/AccountSubType" + }, + "account_type": { + "$ref": "#/components/schemas/AccountType" + }, + "agreements": { + "description": "The client must present the Alpaca Account and Margin Agreements to the end user, and confirm they have read and agreed to the agreement.", + "items": { + "$ref": "#/components/schemas/Agreement" + }, + "type": "array" + }, + "beneficiaries": { + "description": "IRA Account only. A user can submit max 6 beneficiaries.", + "items": { + "$ref": "#/components/schemas/Beneficiary" + }, + "type": "array" + }, + "cash_interest": { + "$ref": "#/components/schemas/AccountCashInterestPost" + }, + "contact": { + "$ref": "#/components/schemas/Contact" + }, + "disclosures": { + "$ref": "#/components/schemas/Disclosures" + }, + "documents": { + "items": { + "$ref": "#/components/schemas/OwnerDocumentUploadRequest" + }, + "type": "array" + }, + "enabled_assets": { + "description": "Will default to `us_equity`. Alpaca has the ability to update the default value upon request.", + "items": { + "$ref": "#/components/schemas/AssetClass" + }, + "type": "array" + }, + "fpsl": { + "$ref": "#/components/schemas/AccountFPSLPost" + }, + "identity": { + "$ref": "#/components/schemas/Identity" + }, + "investment_objective": { + "description": "The user's investment objective. This field should be used instead of the deprecated `investment_objective` under identity.\n", + "enum": [ + "generate_income", + "preserve_wealth", + "market_speculation", + "growth", + "balance_preserve_wealth_with_growth" + ], + "type": "string" + }, + "investment_time_horizon": { + "description": "The expected period of time the user plan to invest to achieve his/her financial goal(s). This field should be used instead of the deprecated `investment_time_horizon` under identity.\n", + "enum": [ + "less_than_1_year", + "1_to_2_years", + "3_to_5_years", + "6_to_10_years", + "more_than_10_years" + ], + "type": "string" + }, + "liquidity_needs": { + "description": "The user's ability to quickly and easily convert to cash all or a portion of the investments in this account without experiencing significant loss in value. This field should be used instead of the deprecated `liquidity_needs` under identity.\n", + "enum": [ + "very_important", + "important", + "somewhat_important", + "does_not_matter" + ], + "type": "string" + }, + "primary_account_holder_id": { + "description": "UUID of an existing account holder (party) to use as the primary account holder for the new account.\nUsed to open additional accounts under the Multi-Live Accounts (MLA) flow.\n\nWhen present:\n- `contact` and `identity` must be omitted (returns HTTP 400 otherwise).\n- `agreements` must still be supplied (e.g. `customer_agreement` for trading; `customer_agreement` and `etc_agreement` for IRA).\n- Only `account_type` values of `trading` and `ira` are supported.\n", + "format": "uuid", + "type": "string" + }, + "risk_tolerance": { + "description": "The user's investment risk tolerance. This field should be used instead of the deprecated `risk_tolerance` under identity.\n", + "enum": [ + "conservative", + "moderate", + "significant_risk" + ], + "type": "string" + }, + "trading_configurations": { + "$ref": "#/components/schemas/AccountConfigurations" + }, + "trusted_contact": { + "$ref": "#/components/schemas/TrustedContact" + } + }, + "required": [ + "contact", + "identity", + "disclosures", + "agreements" + ], + "title": "AccountCreationRequest", + "type": "object", + "x-examples": { + "multi-live-accounts": { + "summary": "Opening an additional account for an existing account holder (MLA)", + "value": { + "account_sub_type": "traditional", + "account_type": "ira", + "agreements": [ + { + "agreement": "customer_agreement", + "ip_address": "185.13.21.99", + "signed_at": "2020-09-11T18:13:44Z" + }, + { + "agreement": "etc_agreement", + "ip_address": "185.13.21.99", + "signed_at": "2020-09-11T18:13:44Z" + } + ], + "enabled_assets": [ + "us_equity" + ], + "primary_account_holder_id": "8b5b7e4c-9f2d-4f4a-9a7b-1b2c3d4e5f60", + "trusted_contact": { + "email_address": "jane.doe@example.com", + "family_name": "Doe", + "given_name": "Jane" + } + } + } + } + }, + "AccountDocument": { + "description": "A document associated with an account", + "example": { + "date": "2019-09-30", + "id": "0d18ae51-3c94-4511-b209-101e1666416b", + "sub_type": "1099-Comp", + "type": "account_statement" + }, + "properties": { + "date": { + "format": "date", + "type": "string" + }, + "id": { + "format": "uuid", + "type": "string" + }, + "name": { + "type": "string" + }, + "sub_type": { + "description": "The sub-type of the document. This is a free-form property.", + "type": "string" + }, + "type": { + "$ref": "#/components/schemas/AccountDocumentType" + } + }, + "required": [ + "id", + "type", + "date" + ], + "type": "object" + }, + "AccountDocumentType": { + "description": "The type of the account document\n", + "enum": [ + "account_application", + "account_statement", + "crypto_account_statement", + "options_application", + "tax_statement", + "trade_confirmation_json", + "trade_confirmation" + ], + "example": "account_application", + "type": "string" + }, + "AccountExtended": { + "description": "Represents an account with all data available. If your api response is missing some of these fields, there is a good chance you are using a route that returns `Account` instances instead of these.", + "properties": { + "account_name": { + "type": "string" + }, + "account_number": { + "nullable": true, + "type": "string" + }, + "account_sub_type": { + "$ref": "#/components/schemas/AccountSubType" + }, + "account_type": { + "$ref": "#/components/schemas/AccountType" + }, + "agreements": { + "items": { + "$ref": "#/components/schemas/Agreement" + }, + "type": "array" + }, + "beneficiaries": { + "items": { + "$ref": "#/components/schemas/Beneficiary" + }, + "type": "array" + }, + "cash_interest": { + "$ref": "#/components/schemas/AccountCashInterestResponse" + }, + "contact": { + "$ref": "#/components/schemas/Contact" + }, + "created_at": { + "format": "date-time", + "type": "string" + }, + "crypto_status": { + "$ref": "#/components/schemas/AccountStatus" + }, + "currency": { + "$ref": "#/components/schemas/Currency" + }, + "custodial_account_type": { + "$ref": "#/components/schemas/CustodialAccountType" + }, + "disclosures": { + "$ref": "#/components/schemas/Disclosures" + }, + "documents": { + "description": "The documents associated with the primary owner of the account", + "items": { + "$ref": "#/components/schemas/OwnerDocument" + }, + "type": "array" + }, + "enabled_assets": { + "items": { + "$ref": "#/components/schemas/AssetClass" + }, + "type": "array" + }, + "fpsl": { + "$ref": "#/components/schemas/AccountFPSLResponse" + }, + "id": { + "format": "uuid", + "type": "string" + }, + "identity": { + "$ref": "#/components/schemas/Identity" + }, + "investment_objective": { + "description": "The user's investment objective. This field should be used instead of the deprecated `investment_objective` under identity.\n", + "enum": [ + "generate_income", + "preserve_wealth", + "market_speculation", + "growth", + "balance_preserve_wealth_with_growth" + ], + "type": "string" + }, + "investment_time_horizon": { + "description": "The expected period of time the user plan to invest to achieve his/her financial goal(s). This field should be used instead of the deprecated `investment_time_horizon` under identity.\n", + "enum": [ + "less_than_1_year", + "1_to_2_years", + "3_to_5_years", + "6_to_10_years", + "more_than_10_years" + ], + "type": "string" + }, + "kyc_results": { + "$ref": "#/components/schemas/KYCResults" + }, + "last_equity": { + "format": "decimal", + "type": "string" + }, + "liquidity_needs": { + "description": "The user's ability to quickly and easily convert to cash all or a portion of the investments in this account without experiencing significant loss in value. This field should be used instead of the deprecated `liquidity_needs` under identity.\n", + "enum": [ + "very_important", + "important", + "somewhat_important", + "does_not_matter" + ], + "type": "string" + }, + "minor_identity": { + "$ref": "#/components/schemas/CustodialAccountMinorIdentity" + }, + "primary_account_holder_id": { + "description": "UUID that identifies the primary account holder (party) for accounts opened under the Multi-Live Accounts (MLA) flow.\nOnly populated when the account was created by referencing an existing account holder via `primary_account_holder_id`\non `POST /v1/accounts`. Otherwise `null`. Currently only supported for `account_type` of `trading` and `ira`.\n", + "format": "uuid", + "nullable": true, + "type": "string" + }, + "risk_tolerance": { + "description": "The user's investment risk tolerance. This field should be used instead of the deprecated `risk_tolerance` under identity.\n", + "enum": [ + "conservative", + "moderate", + "significant_risk" + ], + "type": "string" + }, + "status": { + "$ref": "#/components/schemas/AccountStatus" + }, + "trading_configurations": { + "$ref": "#/components/schemas/AccountConfigurations" + }, + "trusted_contact": { + "$ref": "#/components/schemas/TrustedContact" + }, + "usd": { + "description": "values in USD. This is returned for LCT (non-USD) accounts only.", + "properties": { + "last_equity": { + "example": "123.45", + "format": "decimal", + "type": "string" + } + }, + "type": "object" + } + }, + "required": [ + "id", + "account_number", + "status", + "currency", + "last_equity", + "created_at", + "account_type" + ], + "type": "object", + "x-examples": { + "example-1": { + "account_number": "601842165", + "account_sub_type": null, + "account_type": "trading", + "agreements": [ + { + "agreement": "margin_agreement", + "ip_address": "127.0.0.1", + "revision": "16.2021.05", + "signed_at": "2022-01-21T21:25:26.579487214Z" + }, + { + "agreement": "customer_agreement", + "ip_address": "127.0.0.1", + "revision": "22.2024.08", + "signed_at": "2022-01-21T21:25:26.579487214Z" + } + ], + "beneficiaries": [ + { + "date_of_birth": "1970-01-01", + "family_name": "Smith", + "given_name": "John", + "middle_name": "P", + "relationship": "spouse", + "share_pct": "100", + "tax_id": "xxx-xx-xxxx", + "tax_id_type": "USA_SSN", + "type": "primary" + } + ], + "contact": { + "city": "San Mateo", + "country": "USA", + "email_address": "strange_elbakyan_97324509@example.com", + "phone_number": "614-555-0697", + "postal_code": "94401", + "state": "CA", + "street_address": [ + "20 N San Mateo Dr" + ] + }, + "created_at": "2022-01-21T21:25:26.583576Z", + "crypto_status": "INACTIVE", + "currency": "USD", + "disclosures": { + "immediate_family_exposed": false, + "is_affiliated_exchange_or_finra": false, + "is_control_person": false, + "is_discretionary": false, + "is_politically_exposed": false + }, + "documents": [ + { + "content": "https://s3.amazonaws.com/stg.alpaca.markets/documents/accounts/3dcb795c-3ccc-402a-abb9-07e26a1b1326/2490f5d9-8988-4405-a3e0-d76e65de13c2.jpg?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=AKIAJLDF4SCWSL6HUQKA%2F20220210%2Fus-east-1%2Fs3%2Faws4_request&X-Amz-Date=20220210T162205Z&X-Amz-Expires=900&X-Amz-SignedHeaders=host&X-Amz-Signature=bc6cc0f541d569623f3973fb83734d7960a69886e8b175a9d472f666b87419f1", + "created_at": "2022-01-21T21:25:28.184231Z", + "document_sub_type": "passport", + "document_type": "identity_verification", + "id": "d5af1585-6c60-494d-9ea5-c5df62704229" + } + ], + "id": "3dcb795c-3ccc-402a-abb9-07e26a1b1326", + "identity": { + "country_of_birth": "USA", + "country_of_citizenship": "USA", + "country_of_tax_residence": "USA", + "date_of_birth": "1970-01-01", + "date_of_departure_from_usa": null, + "family_name": "Elbakyan", + "funding_source": [ + "employment_income" + ], + "given_name": "Strange", + "permanent_resident": null, + "tax_id_type": "USA_SNN", + "visa_expiration_date": null, + "visa_type": null + }, + "last_equity": "40645.13", + "primary_account_holder_id": null, + "status": "ACTIVE", + "trading_configurations": null, + "trusted_contact": { + "email_address": "strange_elbakyan_97324509@example.com", + "family_name": "Doe", + "given_name": "Jane" + } + } + } + }, + "AccountFPSLEvent": { + "description": "This property is included when the account's FPSL information had changed due to an enrollment, tier change, or unenrollment.\nThe from and to status or tier id will be included in the event, depending on the change.\n", + "properties": { + "US": { + "properties": { + "status_from": { + "description": "The FPSL program status of the account before the change", + "type": "string" + }, + "status_to": { + "description": "The FPSL program status of the account after the change", + "type": "string" + }, + "tier_from": { + "description": "The tier id before the change", + "type": "string" + }, + "tier_to": { + "description": "The tier id after the change", + "type": "string" + } + }, + "type": "object" + } + }, + "type": "object" + }, + "AccountFPSLItem": { + "properties": { + "status": { + "description": "The status of the account for this FPSL market. One of:\n- **ACTIVE**\nThe account is successfully enrolled for FPSL for this market.\n- **INACTIVE**\nThe account is not enrolled for FPSL for this market due to it either not being eligible, an FPSL tier has not been assigned, or it has been unenrolled.\n", + "example": "ACTIVE", + "type": "string" + }, + "tier_id": { + "description": "The id of the FPSL tier for this market", + "example": "61e69015-8549-4bfd-b9c3-01e75843f47d", + "format": "uuid", + "type": "string" + } + }, + "type": "object" + }, + "AccountFPSLItemPost": { + "properties": { + "tier_id": { + "description": "The id of the FPSL tier for this market", + "example": "61e69015-8549-4bfd-b9c3-01e75843f47d", + "format": "uuid", + "type": "string" + } + }, + "type": "object" + }, + "AccountFPSLPatch": { + "description": "The account's Fully Paid Securities Lending (FPSL) configuration.\nUse this property to change the account's configuration for the FPSL program.\nTo enroll the account for a market, specify the tier_id. The status should not be specified on enrollment.\nTo change the tier, specify the new tier_id. The status should not be specified on tier changes.\nTo unenroll, set the status to INACTIVE.\nTo re-enroll the account, set the status to ACTIVE. You can also specify the tier_id in case you want to change it.\nCurrently only US market is supported.\n", + "properties": { + "US": { + "$ref": "#/components/schemas/AccountFPSLItem" + } + }, + "type": "object" + }, + "AccountFPSLPost": { + "description": "The account's Fully Paid Securities Lending (FPSL) configuration.\nTo enroll the account for a market, specify the tier_id. The status should not be specified on enrollment.\nCurrently only the US market is supported.\n", + "properties": { + "US": { + "$ref": "#/components/schemas/AccountFPSLItemPost" + } + }, + "type": "object" + }, + "AccountFPSLResponse": { + "description": "The account's Fully Paid Securities Lending (FPSL) configuration.\nThis is only returned for accounts that have FPSL enabled.\n", + "properties": { + "US": { + "$ref": "#/components/schemas/AccountFPSLItem" + } + }, + "type": "object" + }, + "AccountLimit": { + "properties": { + "account_no": { + "type": "string" + }, + "amount_available": { + "format": "decimal", + "type": "string" + }, + "amount_in_use": { + "format": "decimal", + "type": "string" + }, + "amount_limit": { + "format": "decimal", + "type": "string" + } + }, + "required": [ + "account_no", + "amount_in_use", + "amount_available", + "amount_limit" + ], + "type": "object" + }, + "AccountStatus": { + "description": "Designates the current status of this account\n\nPossible Values:\n- **INACTIVE**\nAccount not set to trade given asset.\n- **ONBOARDING**\nAn application is expected for this user, but has not been submitted yet.\n- **SUBMITTED**\nThe application has been submitted and is being processed.\n- **SUBMISSION_FAILED**\nUsed to display if failure on submission\n- **ACTION_REQUIRED**\nThe application requires manual action.\n- **ACCOUNT_UPDATED**\nUsed to display when Account has been modified by user\n- **APPROVAL_PENDING**\nInitial value. The application approval process is in progress.\n- **APPROVED**\nThe account application has been approved, and waiting to be ACTIVE\n- **REJECTED**\nThe account application is rejected for some reason\n- **ACTIVE**\nThe account is fully active. Trading and funding are processed under this status.\n- **ACCOUNT_CLOSED**\nThe account is closed.\n", + "enum": [ + "INACTIVE", + "ONBOARDING", + "SUBMITTED", + "SUBMISSION_FAILED", + "ACTION_REQUIRED", + "ACCOUNT_UPDATED", + "APPROVAL_PENDING", + "APPROVED", + "REJECTED", + "ACTIVE", + "ACCOUNT_CLOSED" + ], + "example": "ACTIVE", + "type": "string" + }, + "AccountStatusEvent": { + "description": "Represents a change to certain account properties, sent over the events streaming API.\n", + "properties": { + "account_blocked": { + "description": "If true the account was blocked, if false, the account got unblocked", + "type": "boolean" + }, + "account_id": { + "description": "The unique identifier of the account that was changed", + "minLength": 1, + "type": "string" + }, + "account_number": { + "description": "The account number of the account that was changed", + "minLength": 1, + "type": "string" + }, + "admin_configurations": { + "$ref": "#/components/schemas/AdminConfigurations" + }, + "at": { + "description": "timestamp of event", + "minLength": 1, + "type": "string" + }, + "cash_interest": { + "$ref": "#/components/schemas/AccountCashInterestEvent" + }, + "crypto_status_from": { + "description": "account crypto_status changed from", + "type": "string" + }, + "crypto_status_to": { + "description": "account crypto_status changed to", + "type": "string" + }, + "event_id": { + "description": "monotonically increasing 64bit integer", + "type": "integer" + }, + "event_ulid": { + "description": "lexically sortable, monotonically increasing character array", + "format": "ulid", + "type": "string" + }, + "fpsl": { + "$ref": "#/components/schemas/AccountFPSLEvent" + }, + "kyc_results": { + "$ref": "#/components/schemas/KYCResults" + }, + "options": { + "$ref": "#/components/schemas/OptionsApprovalEvent" + }, + "pattern_day_trader": { + "deprecated": true, + "description": "If true the pattern_day_trader flag was set for the account, if false, the flag was reset.", + "type": "boolean", + "x-deprecation": { + "reason": "See https://docs.alpaca.markets/us/docs/understanding-finras-new-intraday-margin-rule-and-the-end-of-pdt", + "since": "2026-04-27T00:00:00Z", + "sunset": "2026-07-06T00:00:00Z" + } + }, + "reason": { + "deprecated": true, + "minLength": 1, + "type": "string" + }, + "status_from": { + "description": "The account status before the change", + "example": "APPROVED", + "type": "string" + }, + "status_to": { + "description": "The account status after the change", + "example": "ACTIVE", + "type": "string" + }, + "trading_blocked": { + "description": "If true the account cannot trade going forward, if false, the ban has been lifed", + "type": "boolean" + } + }, + "required": [ + "account_id", + "at", + "event_id", + "event_ulid" + ], + "title": "AccountStatusEvent", + "type": "object", + "x-examples": { + "example-1": { + "account_id": "4db36989-6565-4011-9126-39fe6b3d9bf6", + "account_number": "", + "at": "2021-06-14T09:59:15.232782Z", + "event_id": 122039, + "event_ulid": "01F84ZC0H0Q1QN7XPNWX44HF5J", + "kyc_results": null, + "status_from": "", + "status_to": "APPROVED" + } + } + }, + "AccountSubType": { + "description": "IRA Account only\n\nPossible values are:\n\n- traditional\n- roth", + "enum": [ + "traditional", + "roth" + ], + "example": "traditional", + "title": "AccountSubType", + "type": "string" + }, + "AccountTradingLimit": { + "properties": { + "available": { + "description": "The remaining net buying limit that can be used for trading.", + "format": "decimal", + "type": "string" + }, + "daily_net_limit": { + "description": "The net buying limit that can be reached before further trading activity is restricted. Please reach out to learn more about how this limit is determined.", + "format": "decimal", + "type": "string" + }, + "held": { + "description": "The limit that is currently being held for open orders", + "format": "decimal", + "type": "string" + }, + "swap_rate": { + "description": "The swap rate is applicable for Local Currency Trading (LCT) accounts", + "format": "decimal", + "type": "string" + }, + "usd": { + "$ref": "#/components/schemas/USDAccountTradingLimit" + }, + "used": { + "description": "The real time net value of cash inflows (buy trades, etc.) with cash outflows (sell trades, etc). This will be dynamic throughout the day based on user activity, with executed orders being reset at the start of the next trading day.", + "format": "decimal", + "type": "string" + } + }, + "title": "AccountTradingLimit", + "type": "object" + }, + "AccountType": { + "description": "Possible values are:\n\n- trading\n- custodial\n- donor_advised\n- ira", + "enum": [ + "trading", + "custodial", + "donor_advised", + "ira" + ], + "example": "trading", + "title": "AccountType", + "type": "string" + }, + "AccountUpdateRequest": { + "properties": { + "agreements": { + "description": "Additional agreements, or new revisions of existing agreements, read and signed by the account holder.", + "items": { + "$ref": "#/components/schemas/Agreement" + }, + "type": "array" + }, + "beneficiaries": { + "items": { + "$ref": "#/components/schemas/Beneficiary" + }, + "type": "array" + }, + "cash_interest": { + "$ref": "#/components/schemas/AccountCashInterestPatch" + }, + "contact": { + "$ref": "#/components/schemas/Contact" + }, + "disclosures": { + "$ref": "#/components/schemas/Disclosures" + }, + "fpsl": { + "$ref": "#/components/schemas/AccountFPSLPatch" + }, + "identity": { + "$ref": "#/components/schemas/Identity" + }, + "primary_account_holder_id": { + "description": "The UUID of the primary account holder. This field is immutable after the account is created.\n\n- Omitting the field, or supplying the value already associated with the account, is a no-op.\n- Supplying a value different from the current primary account holder returns HTTP 400.\n", + "format": "uuid", + "type": "string" + }, + "trusted_contact": { + "$ref": "#/components/schemas/TrustedContact" + } + }, + "type": "object", + "x-examples": { + "example-1": { + "beneficiaries": [ + { + "date_of_birth": "1970-01-01", + "family_name": "Smith", + "given_name": "John", + "middle_name": "P", + "relationship": "spouse", + "share_pct": "100", + "tax_id": "xxx-xx-xxxx", + "tax_id_type": "USA_SSN", + "type": "primary" + } + ], + "contact": { + "city": "San Mateo", + "country": "USA", + "email_address": "john.doe@example.com", + "phone_number": "+15556667788", + "postal_code": "94401", + "state": "CA", + "street_address": [ + "20 N San Mateo Dr" + ] + }, + "disclosures": { + "immediate_family_exposed": false, + "is_affiliated_exchange_or_finra": false, + "is_control_person": false, + "is_politically_exposed": false + }, + "identity": { + "country_of_birth": "AUS", + "country_of_citizenship": "AUS", + "country_of_tax_residence": "USA", + "date_of_birth": "1990-01-01", + "family_name": "Doe", + "funding_source": [ + "employment_income" + ], + "given_name": "John", + "tax_id": "666-55-4321", + "tax_id_type": "USA_SSN" + }, + "trusted_contact": { + "email_address": "jane.doe@example.com", + "family_name": "Doe", + "given_name": "Jane" + } + } + } + }, + "Activity": { + "allOf": [ + { + "properties": { + "account_id": { + "example": "c8f1ef5d-edc0-4f23-9ee4-378f19cb92a4", + "format": "uuid", + "type": "string" + }, + "activity_type": { + "$ref": "#/components/schemas/ActivityType" + }, + "id": { + "example": "20220208125959696::88b5f678-fef5-447b-af15-f21e367e6d8c", + "type": "string" + } + }, + "type": "object" + }, + { + "oneOf": [ + { + "$ref": "#/components/schemas/TradeActivity" + }, + { + "$ref": "#/components/schemas/NonTradeActivity" + } + ] + } + ], + "description": "Base for activity types", + "required": [ + "id", + "activity_type" + ], + "title": "Activity" + }, + "ActivityEventV2": { + "allOf": [ + { + "$ref": "#/components/schemas/ActivityEventV2CommonFields" + }, + { + "properties": { + "details": { + "oneOf": [ + { + "$ref": "#/components/schemas/ActivityV2DetailTRD" + }, + { + "$ref": "#/components/schemas/ActivityV2DetailNTA" + } + ] + } + }, + "required": [ + "details" + ], + "type": "object" + } + ], + "description": "Represents an account activity, sent over the Event Streaming API.", + "title": "ActivityEventV2", + "type": "object" + }, + "ActivityEventV2CommonFields": { + "description": "Represents the common fields for all Activity V2 Events", + "properties": { + "account_id": { + "description": "Account UUID", + "format": "uuid", + "type": "string" + }, + "activity_subtype": { + "description": "Sub category for activity type, if any", + "type": "string" + }, + "activity_type": { + "description": "The type of the activity, which can be trade or any of the non trade activities", + "type": "string" + }, + "at": { + "description": "Timestamp of event", + "format": "date-time", + "minLength": 1, + "type": "string" + }, + "currency": { + "description": "Currency code in ISO format", + "type": "string" + }, + "event_id": { + "description": "Lexically sortable, monotonically increasing character string", + "format": "ulid", + "type": "string" + }, + "executed_at": { + "description": "Execution time for the activity event", + "format": "date-time", + "minLength": 1, + "type": "string" + }, + "net_amount": { + "description": "The net amount of money (positive or negative) associated with the activity", + "format": "decimal", + "type": "string" + }, + "previous_id": { + "description": "Previous ID is presented if this activity corrects or cancels a previous trade or non trade activity. It contains execution_id or trns_id respectively", + "format": "uuid", + "type": "string" + }, + "price": { + "description": "The price of the security involved with the activity", + "format": "decimal", + "type": "string" + }, + "qty": { + "description": "The quantity of the security involved with the activity", + "format": "decimal", + "type": "string" + }, + "ref_id": { + "description": "The unique identifier for the activity. For trades, the execution_id is used, for other activities the trns_id is used.", + "format": "uuid", + "type": "string" + }, + "settle_date": { + "description": "Date when the activity settled", + "format": "date", + "minLength": 1, + "type": "string" + }, + "status": { + "description": "Status of the activity", + "type": "string" + }, + "swap_fee_bps": { + "description": "Currency conversion fee rate base-point in case of local currency activity", + "format": "decimal", + "type": "string" + }, + "swap_rate": { + "description": "Conversion rate for local currency activities", + "format": "decimal", + "type": "string" + } + }, + "required": [ + "account_id", + "at", + "event_id", + "activity_type", + "executed_at", + "status", + "settle_date", + "currency", + "ref_id" + ], + "type": "object" + }, + "ActivitySubType": { + "description": "Represents a more specific classification to the `activity_type`.\nThis field is optional and may not always be populated, depending on the activity type and the available data.\nEach `activity_type` has a set of valid `activity_sub_type` values.\n\nFull mapping of `activity_type` to `activity_sub_type`:\n\n- **DIV**: Dividend activity sub-types:\n - **CDIV**: Cash Dividend\n - **SDIV**: Stock Dividend\n - **SPD**: Substitute Payment In Lieu Of Dividend\n\n- **FEE**: Fee-related activity sub-types:\n - **REG**: Regulatory Fee\n - **TAF**: Trading Activity Fee\n - **LCT**: Local Currency Trading Fee\n - **ORF**: Options Regulatory Fee\n - **OCC**: Options Clearing Corporation Fee\n - **NRC**: Non-Retail Commission Fee\n - **NRV**: Non-Retail Venue Fee\n - **COM**: Commission\n - **CAT**: Consolidated Audit Trail Fee\n\n- **INT**: Interest-related activity sub-types:\n - **MGN**: Margin Interest\n - **CDT**: Credit Interest\n - **SWP**: Sweep Interest\n - **QII**: Qualified Interest\n\n- **MA**: Merger and Acquisition activity sub-types:\n - **CMA**: Cash Merger\n - **SMA**: Stock Merger\n - **SCMA**: Stock & Cash Merger\n\n- **NC**: Name Change activity sub types\n - **SNC**: Symbol Name Change\n - **CNC**: CUSIP Name Change\n - **SCNC**: Symbol & CUSIP Name Change\n\n- **OPCA**: Option Corporate Action activity sub-types:\n - **DIV.CDIV**: Cash Dividend\n - **DIV.SDIV**: Stock Dividend\n - **MA.CMA**: Cash Merger\n - **MA.SMA**: Stock Merger\n - **MA.SCMA**: Stock & Cash Merger\n - **NC.CNC**: CUSIP Name Change\n - **NC.SNC**: Symbol Name Change\n - **NC.SCNC**: Symbol & CUSIP Name Change\n - **SPIN**: Spin-off\n - **SPLIT.FSPLIT**: Forward Stock Split\n - **SPLIT.RSPLIT**: Reverse Stock Split\n - **SPLIT.USPLIT**: Unit Split\n\n- **REORG**: Reorganization activity sub-types:\n - **WRM**: Worthless Removal\n\n- **SPLIT**: Stock Split activity sub-types:\n - **FSPLIT**: Forward Stock Split\n - **RSPLIT**: Reverse Stock Split\n - **USPLIT**: Unit Split\n\n- **VOF**: Voluntary Offering activity sub-types:\n - **VTND**: Tender Offer\n - **VWRT**: Warrant Exercise\n - **VRGT**: Rights Offer\n - **VEXH**: Exchange Offer\n\n- **WH**: Withholding activity sub-types:\n - **SWH**: State Withholding\n - **FWH**: Federal Withholding\n - **SLWH**: Sales Withholding", + "title": "ActivitySubType", + "type": "string" + }, + "ActivityType": { + "description": "Represents the various kinds of activity.\n\nTradeActivity's will always have the type `FILL`\n\n- **FILL**\n Order Fills (Partial/Full)\n- **ACATC**\n ACATS IN/OUT (Cash)\n- **ACATS**\n ACATS IN/OUT (Securities)\n- **CGD**\n Capital Gain Distribution\n- **CIL**\n Cash in Lieu of Stock\n- **CSD**\n Cash Disbursement (+)\n- **CSW**\n Cash Withdrawable\n- **DIV**\n Dividend\n- **DIVCGL**\n Dividend (Capital Gain Long Term)\n- **DIVCGS**\n Dividend (Capital Gain Short Term)\n- **DIVNRA**\n Dividend Adjusted (NRA Withheld)\n- **DIVROC**\n Dividend Return of Capital\n- **DIVTXEX**\n Dividend (Tax Exempt)\n- **FEE**\n REG and TAF Fees\n- **INT**\n Interest (Credit/Margin)\n- **JNLC**\n Journal Entry (Cash)\n- **JNLS**\n Journal Entry (Stock)\n- **OPASN**\n Option Assignment\n- **OPCA**\n Option Corporate Action\n- **OPCSH**\n Option cash deliverable for non-standard contracts\n- **OPEXC**\n Option Exercise\n- **OPEXP**\n Option Expiration\n- **OPTRD**\n Option Trade\n- **MA**\n Merger/Acquisition\n- **PTC**\n Pass Thru Change\n- **REORG**\n Reorg CA\n- **SPIN**\n Stock Spinoff\n- **SPLIT**\n Stock Split\n- **FOPT**\n Free of Payment Transfers", + "enum": [ + "FILL", + "ACATC", + "ACATS", + "CGD", + "CIL", + "CSD", + "CSW", + "DIV", + "DIVCGL", + "DIVCGS", + "DIVNRA", + "DIVROC", + "DIVTXEX", + "FEE", + "INT", + "JNLC", + "JNLS", + "MA", + "OPASN", + "OPCA", + "OPCSH", + "OPEXC", + "OPEXP", + "OPTRD", + "PTC", + "REORG", + "SPIN", + "SPLIT", + "FOPT" + ], + "title": "ActivityType", + "type": "string" + }, + "ActivityV2DetailNTA": { + "allOf": [ + { + "oneOf": [ + { + "$ref": "#/components/schemas/DIVSPDActivityV2" + }, + { + "$ref": "#/components/schemas/CDIVActivityV2" + }, + { + "$ref": "#/components/schemas/SDIVActivityV2" + }, + { + "$ref": "#/components/schemas/ForwardSplitActivityV2" + }, + { + "$ref": "#/components/schemas/ReverseSplitActivityV2" + }, + { + "$ref": "#/components/schemas/UnitSplitActivityV2" + }, + { + "$ref": "#/components/schemas/SpinoffActivityV2" + }, + { + "$ref": "#/components/schemas/MAActivityV2" + }, + { + "$ref": "#/components/schemas/NCActivityV2" + }, + { + "$ref": "#/components/schemas/FixedIncomeRedemptionActivityV2" + }, + { + "$ref": "#/components/schemas/RightsDistributionActivityV2" + }, + { + "$ref": "#/components/schemas/RightsSubscriptionElectionActivityV2" + }, + { + "$ref": "#/components/schemas/TenderOfferActivityV2" + }, + { + "$ref": "#/components/schemas/ExchangeOfferActivityV2" + }, + { + "$ref": "#/components/schemas/WarrantExerciseElectionActivityV2" + }, + { + "$ref": "#/components/schemas/WRMActivityV2" + }, + { + "$ref": "#/components/schemas/OpcaCDIVActivityV2" + }, + { + "$ref": "#/components/schemas/OpcaSDIVActivityV2" + }, + { + "$ref": "#/components/schemas/OpcaMAActivityV2" + }, + { + "$ref": "#/components/schemas/OpcaNCActivityV2" + }, + { + "$ref": "#/components/schemas/OpcaSPINActivityV2" + }, + { + "$ref": "#/components/schemas/OpcaFSPLITActivityV2" + }, + { + "$ref": "#/components/schemas/OpcaRSPLITActivityV2" + }, + { + "$ref": "#/components/schemas/OpcaUSPLITActivityV2" + }, + { + "$ref": "#/components/schemas/OPASNActivityV2" + }, + { + "$ref": "#/components/schemas/OPEXCActivityV2" + }, + { + "$ref": "#/components/schemas/OPEXPActivityV2" + }, + { + "$ref": "#/components/schemas/OPTRDActivityV2" + }, + { + "$ref": "#/components/schemas/AcatsActivityV2" + }, + { + "$ref": "#/components/schemas/AcatcActivityV2" + }, + { + "$ref": "#/components/schemas/FOPTActivityV2" + }, + { + "$ref": "#/components/schemas/DIVNRAActivityV2" + }, + { + "$ref": "#/components/schemas/JNLSActivityV2" + }, + { + "$ref": "#/components/schemas/JNLCActivityV2" + }, + { + "$ref": "#/components/schemas/CSWActivityV2" + }, + { + "$ref": "#/components/schemas/FEEActivityV2" + } + ] + } + ], + "type": "object" + }, + "ActivityV2DetailTRD": { + "description": "Activity details for a fill or partial_fill event", + "properties": { + "asset_id": { + "description": "Asset ID (For options this represents the option contract ID)", + "format": "uuid", + "type": "string" + }, + "client_order_id": { + "description": "Order ID provided by the customer", + "type": "string" + }, + "commission": { + "description": "Commission to collect from the account holder", + "format": "decimal", + "type": "string" + }, + "cum_qty": { + "description": "Total filled quantity on the order", + "format": "decimal", + "type": "string" + }, + "cusip": { + "description": "CUSIP of the security involved with the activity", + "type": "string" + }, + "execution_type": { + "description": "The execution type", + "enum": [ + "fill", + "trade_correct", + "trade_bust" + ], + "type": "string" + }, + "leaves_qty": { + "description": "Unfilled quantity on the order, when order is filled value could be 0", + "format": "decimal", + "type": "string" + }, + "order_id": { + "description": "Order ID generated by Alpaca", + "format": "uuid", + "type": "string" + }, + "order_status": { + "description": "Identifies the current status of the order", + "type": "string" + }, + "side": { + "description": "Represents what side of the transaction an order was on", + "enum": [ + "buy", + "sell" + ], + "type": "string" + }, + "symbol": { + "description": "The symbol of the security involved with the activity", + "type": "string" + } + }, + "required": [ + "order_id", + "side", + "symbol", + "asset_id", + "leaves_qty", + "cum_qty", + "order_status", + "execution_type" + ], + "type": "object" + }, + "AdminActionBelongsTo": { + "description": "Represents structure of an Identifier for all AdminAction type", + "properties": { + "id_reference": { + "type": "string" + }, + "kind": { + "$ref": "#/components/schemas/AdminActionBelongsToKind" + } + }, + "title": "AdminActionBelongsTo", + "type": "object" + }, + "AdminActionBelongsToKind": { + "enum": [ + "account", + "owner", + "correspondent" + ] + }, + "AdminActionCategory": { + "description": "Category of the Admin Action", + "enum": [ + "order", + "other" + ] + }, + "AdminActionCreatedBy": { + "description": "Represents structure of an Creator's Identifier for all AdminAction type\n", + "properties": { + "id_reference": { + "type": "string" + }, + "kind": { + "$ref": "#/components/schemas/AdminActionCreatedByKind" + } + }, + "title": "AdminActionCreatedBy", + "type": "object" + }, + "AdminActionCreatedByKind": { + "enum": [ + "admin" + ] + }, + "AdminActionEventGeneral": { + "description": "Represents general fields for all AdminAction type", + "properties": { + "at": { + "description": "Timestamp of event", + "format": "date-time", + "type": "string" + }, + "belongs_to": { + "$ref": "#/components/schemas/AdminActionBelongsTo" + }, + "category": { + "$ref": "#/components/schemas/AdminActionCategory" + }, + "correspondent": { + "description": "Related correspondent", + "type": "string" + }, + "created_by": { + "$ref": "#/components/schemas/AdminActionCreatedBy" + }, + "event_id": { + "description": "Lexically sortable, monotonically increasing character array", + "format": "ulid", + "type": "string" + }, + "note": { + "description": "Free text form description of the admin action", + "type": "string" + }, + "replaces_event_id": { + "description": "Id of the replaced event (optional)", + "format": "ulid", + "type": "string" + }, + "type": { + "$ref": "#/components/schemas/AdminActionType" + }, + "visibility": { + "$ref": "#/components/schemas/AdminActionVisibility" + } + }, + "required": [ + "event_id", + "at", + "belongs_to", + "created_by", + "type", + "category", + "visibility", + "note", + "correspondent" + ], + "title": "AdminActionEvent", + "type": "object" + }, + "AdminActionLegacyNote": { + "allOf": [ + { + "$ref": "#/components/schemas/AdminActionEventGeneral" + }, + { + "properties": { + "context": { + "description": "Variable schema type which depends on the type", + "type": "object" + } + }, + "required": [ + "context" + ], + "title": "AdminActionContextLegacyNote", + "type": "object" + } + ], + "description": "Represents structure of a LegacyNote type AdminAction", + "x-examples": { + "example-1": { + "at": "2023-03-06T16:38:01Z", + "belongs_to": { + "kind": "account", + "value": "0bbf1dd7-4235-4eca-8b1a-0db63572c735" + }, + "category": "other", + "context": null, + "correspondent": "LPCA", + "created_by": { + "kind": "admin", + "value": "19455a3c-595f-457f-97b3-64a2b5aeae96" + }, + "event_id": "01GTVS4FVS2KJDTPYH2WM6NAXF", + "note": "Performed action: Positions split AMZN (long): 1.011266402\n", + "type": "legacy_note_admin_event", + "visibility": "internal" + } + } + }, + "AdminActionLiquidation": { + "allOf": [ + { + "$ref": "#/components/schemas/AdminActionEventGeneral" + }, + { + "properties": { + "context": { + "description": "Variable schema type which depends on the type", + "properties": { + "available_qty": { + "format": "decimal", + "type": "string" + }, + "error": { + "type": "string" + }, + "reason": { + "type": "string" + }, + "requested_qty": { + "format": "decimal", + "type": "string" + }, + "symbol": { + "type": "string" + } + }, + "type": "object" + } + }, + "required": [ + "context" + ], + "title": "AdminActionContextLiquidation", + "type": "object" + } + ], + "description": "Represents structure of a Liquidation type AdminAction", + "x-examples": { + "example-1": { + "at": "2023-03-06T16:38:01Z", + "belongs_to": { + "kind": "account", + "value": "0bbf1dd7-4235-4eca-8b1a-0db63572c735" + }, + "category": "accounts", + "context": { + "available_qty": "0.0001", + "error": "", + "reason": "Liquidation due to real time risk", + "requested_qty": "0.0001", + "symbol": "TSLA" + }, + "correspondent": "LPCA", + "created_by": { + "kind": "admin", + "value": "19455a3c-595f-457f-97b3-64a2b5aeae96" + }, + "event_id": "01GTVS4FVS2KJDTPYH2WM6NAXF", + "note": "Performed action: Position liquidated: Asset: TSLA, Quantity: 0.0001, Reason: Liquidation due to real time risk\n", + "type": "liquidation_admin_event", + "visibility": "external" + } + } + }, + "AdminActionTransactionCancel": { + "allOf": [ + { + "$ref": "#/components/schemas/AdminActionEventGeneral" + }, + { + "properties": { + "context": { + "description": "Variable schema type which depends on the type", + "properties": { + "entry_type": { + "type": "string" + }, + "external_id": { + "type": "string" + }, + "transaction_id": { + "format": "uuid", + "type": "string" + } + }, + "type": "object" + } + }, + "required": [ + "context" + ], + "title": "AdminActionContextTransactionCancel", + "type": "object" + } + ], + "description": "Represents structure of a TransactionCancel type AdminAction", + "x-examples": { + "example-1": { + "at": "2023-03-06T16:38:01Z", + "belongs_to": { + "kind": "account", + "value": "0bbf1dd7-4235-4eca-8b1a-0db63572c735" + }, + "category": "accounts", + "context": { + "entry_type": "JNLC", + "external_id": "d5eede2c-1c08-45df-9800-87ad5eefc11f", + "transaction_id": "fdf18af2-142b-409e-8aad-d1731e276af0" + }, + "correspondent": "LPCA", + "created_by": { + "kind": "admin", + "value": "19455a3c-595f-457f-97b3-64a2b5aeae96" + }, + "event_id": "01GTVS4FVS2KJDTPYH2WM6NAXF", + "note": "\"Transaction fdf18af2-142b-409e-8aad-d1731e276af0 cancelled for LPCA-12345678\n", + "type": "transaction_cancel_admin_event", + "visibility": "external" + } + } + }, + "AdminActionType": { + "description": "Type of the Admin Action", + "enum": [ + "liquidation_admin_event", + "legacy_note_admin_event", + "transaction_cancel_admin_event" + ] + }, + "AdminActionVisibility": { + "description": "Visibility of the Admin Action", + "enum": [ + "internal", + "external", + "correspondent_only" + ] + }, + "AdminConfigurations": { + "description": "These configurations show account properties that are overriden either by Alpaca Broker Operations or an automated process.\n\nThese values cannot be modified by the Broker Partners.\n\n\nFor the **events** interface we are only broadcasting changes to admin configurations. In case nothing changed for a flag that will not be included in unrelated update events.\n\nDepending on the type of the Admin Configurations the sent event will behave differently. For bool flags we are only sending the new value.\n\nFor example the following payload means, that the disable_shorting flag was set to true from false:\n\n```\n{\n \"disable_shorting\": false\n}\n```\n\nFor other data types, we are embeding the old and new values into the payload. For example changing the max_margin_multiplier from 4 to 1 will yield this payload:\n\n```\n{\n \"max_margin_multiplier\": {\n \"old\": 4,\n \"new\": 1,\n }\n}\n```\n\nIntroducing an override value from the default will yield a null value as old. For example restricting the max_margin_multipler to 1 from default will yield the following payload:\n\n```\n{\n \"max_margin_multiplier\": {\n \"old\": null,\n \"new\": 1,\n }\n}\n```", + "properties": { + "acct_daily_transfer_limit": { + "description": "Override the correspondent level daily transfer limits", + "format": "decimal", + "type": "string" + }, + "allow_instant_ach": { + "description": "If true, the account is allowed to perform instant ACH", + "type": "boolean" + }, + "disable_algodash_access": { + "description": "If true, the account is allowed to access algo dash", + "type": "boolean" + }, + "disable_api_key": { + "description": "If true, the account's API key will be disabled", + "type": "boolean" + }, + "disable_crypto": { + "description": "If true, the account is not allowed to trade cryptos", + "type": "boolean" + }, + "disable_day_trading": { + "description": "If true, the account is not allowed to day trade (e.g. buy and sell the same security on the same day)", + "type": "boolean" + }, + "disable_fractional": { + "description": "If true, the account cannot create orders for fractional share positions", + "type": "boolean" + }, + "disable_shorting": { + "description": "If true the account is not allowed to create short position orders", + "type": "boolean" + }, + "incoming_transfers_blocked": { + "description": "If true, incoming transfers to this account are rejected", + "type": "boolean" + }, + "max_margin_multiplier": { + "description": "Max margin multipler is set by admin to this value", + "properties": { + "from": { + "description": "Old value of margin multiplier", + "type": "string" + }, + "to": { + "description": "New value of margin multiplier", + "type": "string" + } + }, + "type": "object" + }, + "max_options_trading_level": { + "description": "Max options trading level is set by admin to this value. It can be 0, 1 or 2.", + "properties": { + "from": { + "description": "New value of max options trading level", + "type": "string" + }, + "to": { + "description": "New value of max options trading level", + "type": "string" + } + }, + "type": "object" + }, + "outgoing_transfers_blocked": { + "description": "If true, outgoing transfers from this account are rejected", + "type": "boolean" + }, + "pdt_check_mode": { + "deprecated": true, + "description": "PDT check mode", + "type": "string", + "x-deprecation": { + "reason": "See https://docs.alpaca.markets/us/docs/understanding-finras-new-intraday-margin-rule-and-the-end-of-pdt", + "since": "2026-04-27T00:00:00Z", + "sunset": "2026-07-06T00:00:00Z" + } + }, + "restrict_to_liquidation_reasons": { + "$ref": "#/components/schemas/RestrictToLiquidationReasons" + } + }, + "title": "AdminConfigurations", + "type": "object" + }, + "AggregatePositionResponse": { + "properties": { + "asset_type": { + "$ref": "#/components/schemas/AssetClass" + }, + "closing_price": { + "description": "EOD asset price per share at session close", + "format": "decimal", + "type": "string" + }, + "cusip": { + "description": "Cusip (9 digits, can start with 0's)", + "format": "decimal", + "type": "string" + }, + "long_market_value": { + "description": "Aggregate notional dollar amount of the partner's long positions", + "format": "decimal", + "type": "string" + }, + "long_qty": { + "description": "Aggregate number of shares that the partner is long", + "format": "decimal", + "type": "string" + }, + "num_accounts": { + "description": "Number of accounts that have a position in this asset (either long or short)", + "format": "decimal", + "type": "integer" + }, + "short_market_value": { + "description": "Aggregate notional dollar amount of the partner's short positions", + "format": "decimal", + "type": "string" + }, + "short_qty": { + "description": "Aggregate number of shares that the partner is short", + "format": "decimal", + "type": "string" + }, + "symbol": { + "description": "Symbol of asset", + "type": "string" + } + }, + "title": "", + "type": "object", + "x-examples": { + "Example 1": { + "asset_type": "us_equity", + "closing_price": "148.4700", + "cusip": "037833100", + "long_market_value": "148.4700", + "long_qty": "1", + "num_accounts": "1", + "short_market_value": "0", + "short_qty": "0", + "symbol": "AAPL" + } + } + }, + "Agreement": { + "properties": { + "agreement": { + "$ref": "#/components/schemas/AgreementType" + }, + "ip_address": { + "description": "The ip_address the signed agreements were sent from by the user.", + "example": "185.13.21.99", + "format": "ipv4", + "type": "string" + }, + "revision": { + "description": "The agreement revision.\nThe format is XX.YYYY.MM where XX is an incrementing revision number, YYYY is the year and MM is the month.\nIf the revision is not specified in a POST or PATCH request, the active revision will be used, which will align with the [Alpaca Documents Library](https://alpaca.markets/disclosures).\n", + "type": "string" + }, + "signed_at": { + "description": "The timestamp the agreement was signed.", + "example": "2019-09-11T18:09:33Z", + "format": "date-time", + "type": "string" + } + }, + "required": [ + "agreement", + "signed_at", + "ip_address" + ], + "type": "object" + }, + "AgreementType": { + "description": "- margin_agreement: Alpaca Margin Agreement\n- account_agreement: Alpaca Account Agreement\n- customer_agreement: Alpaca Customer Agreement\n- crypto_agreement: Alpaca Crypto agreement\n- options_agreement: Alpaca Option agreement\n- custodial_customer_agreement: Alpaca Custodial Customer agreement\n", + "enum": [ + "margin_agreement", + "account_agreement", + "customer_agreement", + "crypto_agreement", + "options_agreement" + ], + "example": "customer_agreement", + "title": "AgreementType", + "type": "string" + }, + "Announcement": { + "description": "The announcements endpoint contains public information on previous and upcoming dividends, mergers, spinoffs, and stock splits.\n\nAnnouncement data is made available through the API as soon as it is ingested by Alpaca, which is typically the following trading day after the declaration date. This provides insight into future account stock position and cash balance changes that will take effect on an announcement's payable date. Additionally, viewing previous announcement details can improve bookkeeping and reconciling previous account cash and position changes.", + "properties": { + "ca_sub_type": { + "$ref": "#/components/schemas/AnnouncementCASubType" + }, + "ca_type": { + "$ref": "#/components/schemas/AnnouncementCAType" + }, + "cash": { + "description": "The amount of cash to be paid per share held by an account on the record date.", + "minLength": 1, + "nullable": true, + "type": "string" + }, + "corporate_action_id": { + "description": "ID that remains consistent across all announcements for the same corporate action. Unlike 'id', this can be used to connect multiple announcements to see how the terms have changed throughout the lifecycle of the corporate action event.", + "minLength": 1, + "type": "string" + }, + "declaration_date": { + "description": "Date the corporate action or subsequent terms update was announced.", + "minLength": 1, + "type": "string" + }, + "ex_date": { + "description": "The first date that purchasing a security will not result in a corporate action entitlement.", + "minLength": 1, + "nullable": true, + "type": "string" + }, + "id": { + "description": "ID that is specific to a single announcement.", + "minLength": 1, + "type": "string" + }, + "initiating_original_cusip": { + "description": "CUSIP of the company initiating the announcement.", + "minLength": 1, + "type": "string" + }, + "initiating_symbol": { + "description": "Symbol of the company initiating the announcement.", + "minLength": 1, + "type": "string" + }, + "new_rate": { + "description": "The numerator to determine any quantity change ratios in positions.", + "minLength": 1, + "nullable": true, + "type": "string" + }, + "old_rate": { + "description": "The denominator to determine any quantity change ratios in positions.", + "minLength": 1, + "nullable": true, + "type": "string" + }, + "payable_date": { + "description": "The date the announcement will take effect. On this date, account stock and cash balances are expected to be processed accordingly.", + "minLength": 1, + "type": "string" + }, + "record_date": { + "description": "The date an account must hold a settled position in the security in order to receive the corporate action entitlement.", + "minLength": 1, + "nullable": true, + "type": "string" + }, + "target_original_cusip": { + "description": "CUSIP of the child company involved in the announcement.", + "minLength": 1, + "nullable": true, + "type": "string" + }, + "target_symbol": { + "description": "Symbol of the child company involved in the announcement.", + "minLength": 1, + "nullable": true, + "type": "string" + } + }, + "required": [ + "id", + "corporate_action_id", + "ca_type", + "ca_sub_type", + "initiating_symbol", + "initiating_original_cusip", + "target_symbol", + "target_original_cusip", + "declaration_date", + "ex_date", + "record_date", + "payable_date", + "cash", + "old_rate", + "new_rate" + ], + "title": "Announcement", + "type": "object", + "x-examples": { + "example-1": { + "ca_sub_type": "cash", + "ca_type": "dividend", + "cash": "0", + "corporate_action_id": "78467X109_AA22", + "declaration_date": "2021-12-19", + "ex_date": "2022-01-21", + "id": "bebc5ece-34be-47e9-b944-687e69a102be", + "initiating_original_cusip": "252787106", + "initiating_symbol": "DIA", + "new_rate": "1", + "old_rate": "1", + "payable_date": "2022-02-14", + "record_date": "2022-01-24", + "target_original_cusip": "252787106", + "target_symbol": "DIA" + } + } + }, + "AnnouncementCASubType": { + "description": "Announcements have both a type and a subtype to categorize them. This model represents the lowever level abstract \"sub types\" of Announcement. Please see the AnnouncementCAType model for higher level descriptions of the possible types\n\nPossible values are:\n\n- from the `dividend` type:\n - **cash**\n\n A cash payment based on the number of shares the account holds on the record date.\n - **stock**\n\n A stock payment based on the number of shares the account holds on the record date.\n\n- from the `merger` type:\n - **merger_update**\n\n An update to the terms of an upcoming merger. This can happen any number of times before the merger is completed and can be tracked by using the id parameter.\n\n - **merger_completion**\n\n A final update in the terms of the merger in which the intiating_symbol will acquire the target_symbol. Any previous terms updates for this announcement will have the same id value.\n\n- from the `split` type:\n - **stock_split**\n\n An increase in the number of shares outstanding with a decrease in the dollar value of each share. The new_rate and old_rate parameters will be returned in order to derive the ratio of the split\n - **until_split**\n\n An increase in the number of shares outstanding with a decrease in the dollar value of each share. The new_rate and old_rate parameters will be returned in order to derive the ratio of the split.\n - **reverse_split**\n\n A decrease in the number of shares outstanding with an increase in the dollar value of each share. The new_rate and old_rate parameters will be returned in order to derive the ratio of the spli\n - **recapitalization**\n\n A stock recapitalization, typically used by a company to adjust debt and equity ratios.\n\n- from the `spinoff` type:\n - **spinoff**\n\n A disbursement of a newly tradable security when the intiating_symbol creates the target_symbol.", + "enum": [ + "cash", + "stock", + "merger_update", + "merger_completion", + "stock_split", + "until_split", + "reverse_split", + "recapitalization", + "spinoff" + ], + "title": "", + "type": "string" + }, + "AnnouncementCAType": { + "description": "Announcements have both a type and a subtype to categorize them. This model represents the higher level abstract \"types\" of Announcement. Please see the AnnouncementCASubType model for finer grain descriptions of the subtypes\n\nPossible values are:\n- dividend\n can have `cash` and `stock` subtypes\n- merger\n has `merger_update` and `merger_completion` sub types\n- split\n has `stock_split`, `until_split`, `reverse_split`, and `recapitalization` sub types\n- spinoff\n currently has only the `spinoff` subtype and thus is just this higher level category for now. A disbursement of a newly tradable security when the intiating_symbol creates the target_symbol.", + "enum": [ + "dividend", + "merger", + "split", + "spinoff" + ], + "example": "dividend", + "title": "", + "type": "string" + }, + "Asset": { + "description": "Assets are sorted by asset class, exchange and symbol. Some assets are not tradable with Alpaca. These assets will be marked with the flag tradable=false", + "properties": { + "attributes": { + "description": "Unique characteristics of the asset. Supported values:\n- `ptp_no_exception`: Asset is a Publicly Traded Partnership (PTP) without a qualified notice; non-U.S. customers may incur 10% withholding on gross proceeds as per IRS guidance, and are blocked from being purchased by default.\n- `ptp_with_exception`: Users can open positions in these PTPs without general restrictions.\n- `ipo`: Accepting limit orders only before the stock begins trading on the secondary market.\n- `has_options`: The underlying equity has listed options available on the platform. Note: if the equity had inactive/expired contracts in the past, this will still show up.\n- `options_late_close`: Indicates the underlying asset's options contracts close at 4:15pm ET instead of the standard 4:00pm ET.\n- `fractional_eh_enabled`: Indicates the asset accepts fractional orders during extended hours sessions (pre-market, post-market, and overnight if enabled).\n- `overnight_tradable`: Asset is eligible for overnight (24x5) trading in supported venues on the platform.\n- `overnight_halted`: Asset is eligible for overnight trading but is currently halted/blocked for overnight sessions due to risk, corporate action, compliance, or venue constraints.", + "example": [ + "ptp_no_exception", + "ipo" + ], + "items": { + "enum": [ + "ptp_no_exception", + "ptp_with_exception", + "ipo", + "has_options", + "options_late_close", + "fractional_eh_enabled", + "overnight_tradable", + "overnight_halted" + ], + "type": "string" + }, + "type": "array" + }, + "borrow_status": { + "description": "Borrow status for US equity assets. This field is omitted for non-US-equity assets.", + "enum": [ + "easy_to_borrow", + "hard_to_borrow" + ], + "example": "easy_to_borrow", + "type": "string" + }, + "class": { + "$ref": "#/components/schemas/AssetClass" + }, + "easy_to_borrow": { + "deprecated": true, + "description": "**deprecated**: Please use borrow_status instead.\nAsset is easy-to-borrow or not (filtering for easy_to_borrow = True is the best way to check whether the name is currently available to short at Alpaca).\n", + "example": true, + "type": "boolean", + "x-deprecation": { + "reason": "Use borrow_status instead.", + "since": "2026-06-22", + "sunset": "2026-09-22" + } + }, + "exchange": { + "$ref": "#/components/schemas/Exchange" + }, + "fractionable": { + "description": "Asset is fractionable or not", + "example": true, + "type": "boolean" + }, + "id": { + "description": "Asset ID", + "example": "904837e3-3b76-47ec-b432-046db621571b", + "format": "uuid", + "type": "string" + }, + "maintenance_margin_requirement": { + "deprecated": true, + "description": "**deprecated**: Please use margin_requirement_long or margin_requirement_short instead. Note that these fields are of type string.\nShows the margin requirement percentage for the asset (equities only).\n", + "type": "number" + }, + "margin_requirement_long": { + "description": "The margin requirement percentage for the asset's long positions (equities only).", + "type": "string" + }, + "margin_requirement_short": { + "description": "The margin requirement percentage for the asset's short positions (equities only).", + "type": "string" + }, + "marginable": { + "description": "Asset is marginable or not", + "example": true, + "type": "boolean" + }, + "min_order_size": { + "description": "Minimum order size. Field available for crypto only.", + "type": "string" + }, + "min_trade_increment": { + "description": "Amount a trade quantity can be incremented by. Field available for crypto only.", + "type": "string" + }, + "name": { + "description": "The official name of the asset", + "example": "Apple Inc. Common Stock", + "type": "string" + }, + "price_increment": { + "description": "Amount the price can be incremented by. Field available for crypto only.", + "type": "string" + }, + "shortable": { + "description": "Asset is shortable or not", + "example": true, + "type": "boolean" + }, + "status": { + "description": "active or inactive", + "enum": [ + "active", + "inactive" + ], + "example": "active", + "type": "string" + }, + "symbol": { + "description": "The symbol of the asset", + "example": "AAPL", + "type": "string" + }, + "tradable": { + "description": "Asset is tradable on Alpaca or not", + "example": true, + "type": "boolean" + } + }, + "required": [ + "id", + "class", + "exchange", + "symbol", + "name", + "status", + "tradable", + "marginable", + "shortable", + "easy_to_borrow", + "fractionable", + "maintenance_margin_requirement", + "margin_requirement_long", + "margin_requirement_short" + ], + "title": "Asset", + "type": "object" + }, + "AssetClass": { + "description": "This represents the category to which the asset belongs to. It serves to identify the nature of the financial instrument, with options including \"us_equity\" for U.S. equities, \"us_option\" for U.S. options, \"crypto\" for cryptocurrencies, and \"ipo\" for IPO indications of interest. This `asset_class: ipo` value is distinct from the assets API `attributes: [\"ipo\"]` flag.", + "enum": [ + "us_equity", + "us_option", + "crypto", + "ipo" + ], + "type": "string" + }, + "AssetEntryRequirements": { + "description": "Defines the necessary conditions that must be met to initiate a position for a specific asset", + "properties": { + "dtbp_long": { + "deprecated": true, + "description": "The percentage of the asset's market value required as Day Trading (4x) buying power to open a long position", + "example": "0.25", + "format": "decimal", + "type": "string", + "x-deprecation": { + "reason": "See https://docs.alpaca.markets/us/docs/understanding-finras-new-intraday-margin-rule-and-the-end-of-pdt", + "since": "2026-04-27T00:00:00Z", + "sunset": "2026-07-06T00:00:00Z" + } + }, + "dtbp_short": { + "deprecated": true, + "description": "The percentage of the asset's market value required as Day Trading (4x) buying power to open a short position", + "example": "0.3", + "format": "decimal", + "type": "string", + "x-deprecation": { + "reason": "See https://docs.alpaca.markets/us/docs/understanding-finras-new-intraday-margin-rule-and-the-end-of-pdt", + "since": "2026-04-27T00:00:00Z", + "sunset": "2026-07-06T00:00:00Z" + } + }, + "regt_long": { + "description": "The percentage of the asset's market value required as Reg T (2x) buying power to open a long position", + "example": "0.5", + "format": "decimal", + "type": "string" + }, + "regt_short": { + "description": "The percentage of the asset's market value required as Reg T (2x) buying power to open a short position", + "example": "0.5", + "format": "decimal", + "type": "string" + }, + "symbol": { + "description": "The symbol (or asset id) of the requested asset", + "example": "AAPL", + "type": "string" + } + }, + "required": [ + "symbol" + ], + "title": "AssetEntryRequirements", + "type": "object" + }, + "AuthorizeOAuthTokenResponse": { + "description": "", + "properties": { + "client_id": { + "description": "OAuth `client_id`", + "minLength": 1, + "type": "string" + }, + "code": { + "description": "OAuth code to exchange with token", + "minLength": 1, + "type": "string" + }, + "redirect_uri": { + "description": "Redirect URI of OAuth flow", + "minLength": 1, + "type": "string" + }, + "scope": { + "description": "Granted scopes", + "type": "string" + } + }, + "required": [ + "code", + "client_id", + "redirect_uri", + "scope" + ], + "type": "object", + "x-examples": { + "example-1": { + "client_id": "7a3c52a910e1dc2abbb14da2b6b8e711", + "code": "912b5502-c983-40f7-a01d-6a66f13a754d", + "redirect_uri": "http://localhost", + "scope": "" + } + } + }, + "Bank": { + "properties": { + "account_id": { + "format": "uuid", + "type": "string" + }, + "account_number": { + "type": "string" + }, + "bank_code": { + "description": "9-Digit ABA RTN (Routing Number) or BIC", + "type": "string" + }, + "bank_code_type": { + "description": "ABA (Domestic) or BIC (International)", + "enum": [ + "ABA", + "BIC" + ], + "type": "string" + }, + "city": { + "description": "Only for international banks", + "type": "string" + }, + "country": { + "description": "Only for international banks", + "type": "string" + }, + "created_at": { + "description": "Format: 2020-01-01T01:01:01Z", + "example": "2021-03-16T18:38:01.942282Z", + "format": "date-time", + "type": "string" + }, + "extra_fields": { + "$ref": "#/components/schemas/BankAdditionalFields" + }, + "id": { + "example": "61e69015-8549-4bfd-b9c3-01e75843f47d", + "format": "uuid", + "type": "string" + }, + "name": { + "description": "Name of recipient bank", + "type": "string" + }, + "postal_code": { + "description": "Only for international banks", + "type": "string" + }, + "state_province": { + "description": "Only for international banks", + "type": "string" + }, + "status": { + "description": "QUEUED, SENT_TO_CLEARING, APPROVED, REJECTED, CANCELED", + "enum": [ + "QUEUED", + "SENT_TO_CLEARING", + "APPROVED", + "REJECTED", + "CANCELED" + ], + "type": "string" + }, + "street_address": { + "description": "Only for international banks", + "type": "string" + }, + "updated_at": { + "description": "Format: 2020-01-01T01:01:01Z", + "example": "2021-03-16T18:38:01.942282Z", + "format": "date-time", + "type": "string" + } + }, + "required": [ + "id", + "created_at", + "updated_at", + "name", + "bank_code", + "bank_code_type", + "account_number" + ], + "type": "object", + "x-examples": { + "example-1": { + "account_id": "56712986-9ff7-4d8f-8e52-077e099e533e", + "account_number": "123456789abc", + "bank_code": "123456789", + "bank_code_type": "ABA", + "city": "", + "country": "", + "created_at": "2022-02-11T21:35:19.268681613Z", + "id": "8475c676-68e3-4cfc-a683-9ca2f47a6172", + "name": "Bank XYZ", + "postal_code": "", + "state_province": "", + "status": "QUEUED", + "street_address": "", + "updated_at": "2022-02-11T21:35:19.268681613Z" + } + } + }, + "BankAdditionalFields": { + "description": "Additional wire instructions used to explicitly specify intermediary (correspondent) banks for international wire transfers. If omitted, intermediary banks may be automatically selected by downstream institutions, which can result in delays or additional charges.\n", + "properties": { + "intermediary_bank1_bic": { + "description": "The primary intermediary (correspondent) bank to be used when routing the international wire transfer to the beneficiary bank.", + "type": "string" + }, + "intermediary_bank2_bic": { + "description": "An additional intermediary bank to be used if the wire transfer requires multiple correspondent banks before reaching the beneficiary bank.", + "type": "string" + }, + "intermediary_bank3_bic": { + "description": "A tertiary intermediary bank used in complex international wire routes that require three correspondent banks prior to reaching the beneficiary bank.", + "type": "string" + } + }, + "type": "object" + }, + "BatchCreateFundingWalletRequest": { + "properties": { + "account_ids": { + "description": "list of UUID account ids which will have funding wallets created", + "items": { + "type": "string" + }, + "type": "array" + } + }, + "type": "object" + }, + "BatchCreateFundingWalletResponse": { + "properties": { + "funding_wallets": { + "items": { + "$ref": "#/components/schemas/FundingWallet" + }, + "type": "array" + } + }, + "type": "object" + }, + "BatchJournalRequest": { + "description": "Journals API allows you to move cash or securities from one account to another.\n\nThis model represents the fields you can specify when creating a request of many Journals out of one account to many others at once.", + "properties": { + "description": { + "description": "Journal description, gets returned in the response", + "type": "string" + }, + "entries": { + "description": "An array of objects describing which accounts you want to move funds into and how much to move into each account", + "items": { + "properties": { + "amount": { + "description": "Journal amount in USD", + "type": "string" + }, + "currency": { + "description": "Currency", + "type": "string" + }, + "description": { + "description": "Journal entry description, gets returned in the response", + "type": "string" + }, + "to_account": { + "description": "The ID of the account that you want to journal funds into", + "format": "uuid", + "type": "string" + }, + "transmitter_account_number": { + "description": "Only valid for JNLC journals. Null for JNLS.max 255 characters", + "type": "string" + }, + "transmitter_address": { + "description": "Only valid for JNLC journals. Null for JNLS.max 255 characters", + "type": "string" + }, + "transmitter_financial_institution": { + "description": "Only valid for JNLC journals. Null for JNLS.max 255 characters", + "type": "string" + }, + "transmitter_name": { + "description": "Only valid for JNLC journals. Null for JNLS. Max 255 characters.", + "type": "string" + }, + "transmitter_timestamp": { + "format": "date-time", + "type": "string" + } + }, + "required": [ + "to_account", + "amount" + ], + "type": "object" + }, + "minItems": 1, + "type": "array" + }, + "entry_type": { + "description": "Only supports `JNLC` for now", + "enum": [ + "JNLC" + ], + "type": "string" + }, + "from_account": { + "description": "The account id that is the originator of the funds being moved. Most likely is your Sweep Firm Account", + "format": "uuid", + "type": "string" + } + }, + "required": [ + "entry_type", + "from_account", + "entries" + ], + "title": "BatchJournalRequest", + "type": "object" + }, + "BatchJournalResponse": { + "allOf": [ + { + "$ref": "#/components/schemas/Journal" + }, + { + "properties": { + "error_message": { + "description": "Description of why this journal transaction failed", + "type": "string" + } + }, + "required": [ + "error_message" + ], + "type": "object" + } + ], + "description": "A Journal object with an extra attribute error_message in the case when a specific account fails to receive a journal.", + "title": "BatchJournalResponse", + "x-examples": { + "example-1": { + "description": "", + "entry_type": "JNLC", + "error_message": "", + "from_account": "8f8c8cee-2591-4f83-be12-82c659b5e748", + "id": "56f106e5-25a4-4eee-96fa-25bb05dc86bc", + "net_amount": "1000", + "price": null, + "qty": null, + "settle_date": null, + "status": "pending", + "symbol": "", + "system_date": null, + "to_account": "399f85f1-cbbd-4eaa-a934-70027fb5c1de" + } + } + }, + "Beneficiary": { + "description": "Beneficiary of an account", + "example": { + "date_of_birth": "1970-01-01", + "family_name": "Doe", + "given_name": "Jane", + "middle_name": "P", + "relationship": "spouse", + "share_pct": "100", + "tax_id": "xxx-xx-xxxx", + "tax_id_type": "USA_SSN", + "type": "primary" + }, + "properties": { + "date_of_birth": { + "example": "1970-01-01", + "type": "string" + }, + "family_name": { + "example": "Doe", + "type": "string" + }, + "given_name": { + "example": "Jane", + "type": "string" + }, + "middle_name": { + "example": "P", + "type": "string" + }, + "relationship": { + "example": "spouse", + "type": "string" + }, + "share_pct": { + "example": "100", + "type": "string" + }, + "tax_id": { + "example": "xxx-xx-xxxx", + "type": "string" + }, + "tax_id_type": { + "example": "USA_SSN", + "type": "string" + }, + "type": { + "example": "primary", + "type": "string" + } + }, + "required": [ + "given_name", + "middle_name", + "family_name", + "date_of_birth", + "tax_id", + "tax_id_type", + "relationship", + "type", + "share_pct" + ], + "type": "object" + }, + "CDIVActivityV2": { + "allOf": [ + { + "$ref": "#/components/schemas/CommonCaActivityV2" + }, + { + "$ref": "#/components/schemas/CommonCDIVActivityV2" + }, + { + "properties": { + "cash_payout": { + "description": "Total cash amount paid", + "type": "string" + }, + "entitled_qty": { + "description": "Quantity of shares entitled to receive the dividend", + "type": "string" + } + }, + "required": [ + "entitled_qty", + "cash_payout" + ], + "type": "object" + } + ], + "description": "Cash dividend", + "type": "object" + }, + "CIPDocument": { + "description": "Represents results of checking a document for CIPInfo\n", + "properties": { + "age_validation": { + "$ref": "#/components/schemas/CIPResult" + }, + "comprised_document": { + "$ref": "#/components/schemas/CIPResult" + }, + "created_at": { + "description": "Datetime for when this check was done", + "format": "date-time", + "type": "string" + }, + "data_comparison": { + "$ref": "#/components/schemas/CIPResult" + }, + "data_comparison_breakdown": { + "description": "json object representing the results of the various sub-checks\n done when calculating the result on `data_comparison`. Example: {\"date_of_birth\": \"clear\",\n \"date_of_expiry\": \"clear\" \"document_numbers\": \"clear\", \"document_type\": \"clear\", \"first_name\": \"clear\",\n \"gender\": \"clear\", \"issuing_country\": \"clear\", \"last_name\": \"clear\"}", + "type": "string" + }, + "date_of_birth": { + "description": "Datetime for when this check was done", + "format": "date", + "type": "string" + }, + "date_of_expiry": { + "description": "Datetime for when this check was done", + "format": "date", + "type": "string" + }, + "document_numbers": { + "description": "Number of the document that was checked", + "items": { + "type": "string" + }, + "type": "array" + }, + "document_type": { + "description": "Type of the document that was checked", + "type": "string" + }, + "first_name": { + "description": "First name extracted from the document", + "type": "string" + }, + "gender": { + "description": "Gender info extracted from the document", + "type": "string" + }, + "id": { + "description": "Your internal ID of check", + "type": "string" + }, + "image_integrity": { + "$ref": "#/components/schemas/CIPResult" + }, + "image_integrity_breakdown": { + "description": "json object representing the results of the various sub-checks done\n when calculating the result on `image_integrity`. Example: example: {\"colour_picture\": \"clear\",\n \"conclusive_document_quality\": \"clear\", \"image_quality\": \"clear\", \"supported_document\": \"clear\"}", + "type": "string" + }, + "issuing_country": { + "description": "Country for which issued the document", + "type": "string" + }, + "last_name": { + "description": "Last name extracted from the document", + "type": "string" + }, + "nationality": { + "description": "Nationality extracted from the document", + "type": "string" + }, + "police_record": { + "$ref": "#/components/schemas/CIPStatus" + }, + "result": { + "$ref": "#/components/schemas/CIPResult" + }, + "status": { + "$ref": "#/components/schemas/CIPStatus" + }, + "visual_authenticity": { + "description": "json object representing the various sub-checks done when determining\n whether visual (non-textual) elements are correct given the document type. Example: {\n \"digital_tampering\": \"clear\", \"face_detection\": \"clear\", \"fonts\": \"clear\", \"original_document_present\":\n \"clear\", \"picture_face_integrity\": \"clear\", \"security_features\": \"clear\", \"template\": \"clear\"}", + "type": "string" + } + }, + "required": [ + "id" + ], + "type": "object", + "x-examples": { + "Example 1": { + "created_at": "2021-06-10T15:37:03Z", + "id": "55B9931A-3BE6-4BC0-9BDD-0B954E4A4632", + "image_integrity": "clear", + "result": "clear", + "status": "complete" + } + } + }, + "CIPIdentity": { + "properties": { + "address": { + "$ref": "#/components/schemas/CIPResult" + }, + "address_breakdown": { + "description": "a json object representing the breakdown of the `address` field. For example:\n {\"credit_agencies\": {\"result\": \"clear\",\"properties\":{\"number_of_matches\":\"1\"}}", + "type": "string" + }, + "created_at": { + "description": "datetime when identity check happened", + "type": "string" + }, + "date_of_birth": { + "$ref": "#/components/schemas/CIPResult" + }, + "date_of_birth_breakdown": { + "description": "a json object representing the breakdown of the `date_of_birth` field.\n For example: example: {\"credit_agencies\":{\"result\": \"clear\",\"properties\": {\"number_of_matches\": \"1\"}}", + "type": "string" + }, + "id": { + "description": "Your internal ID of check", + "type": "string" + }, + "matched_address": { + "$ref": "#/components/schemas/CIPResult" + }, + "matched_addresses": { + "description": "datetime when identity check happened", + "type": "string" + }, + "result": { + "$ref": "#/components/schemas/CIPResult" + }, + "sources": { + "$ref": "#/components/schemas/CIPResult" + }, + "sources_breakdown": { + "description": "a json object representing the breakdown of `sources` field. For example:\n {\"total_sources\": {\"result\": \"clear\",\"properties\": {\"total_number_of_sources\": \"3\"}}}", + "type": "string" + }, + "status": { + "$ref": "#/components/schemas/CIPStatus" + }, + "tax_id": { + "$ref": "#/components/schemas/CIPResult" + }, + "tax_id_breakdown": { + "description": "a json object representing the breakdown of the `tax_id` field", + "type": "string" + } + }, + "required": [ + "id" + ], + "type": "object", + "x-examples": { + "Example 1": { + "address": "clear", + "created_at": "2021-06-10T15:37:03Z", + "date_of_birth": "clear", + "id": "28E1CCE8-1B1A-4472-9AD4-C6C5B7C3A6AF", + "result": "clear", + "sources": "clear", + "status": "complete" + } + } + }, + "CIPInfo": { + "description": "Customer Identification Program (CIP) information for an account applicant.", + "properties": { + "account_id": { + "description": "UUID of the Account instance this CIPInfo is for", + "format": "uuid", + "type": "string" + }, + "created_at": { + "format": "date-time", + "type": "string" + }, + "document": { + "$ref": "#/components/schemas/CIPDocument" + }, + "id": { + "description": "ID of this CIPInfo", + "format": "uuid", + "type": "string" + }, + "identity": { + "$ref": "#/components/schemas/CIPIdentity" + }, + "kyc": { + "$ref": "#/components/schemas/CIPKYC" + }, + "photo": { + "$ref": "#/components/schemas/CIPPhoto" + }, + "provider_name": { + "description": "List of KYC providers this information came from", + "items": { + "$ref": "#/components/schemas/CIPProvider" + }, + "type": "array" + }, + "updated_at": { + "format": "date-time", + "type": "string" + }, + "watchlist": { + "$ref": "#/components/schemas/CIPWatchlist" + } + }, + "title": "CIPInfo", + "type": "object" + }, + "CIPKYC": { + "description": "Represents Know Your Customer (KYC) info for a CIPInfo", + "properties": { + "address": { + "description": "Concatenated street address, city, state and country of applicant", + "type": "string" + }, + "applicant_name": { + "description": "Given and family name of applicant", + "type": "string" + }, + "approval_status": { + "description": "Approval status of KYC check", + "enum": [ + "approved", + "rejected" + ], + "example": "approved", + "type": "string" + }, + "approved_at": { + "description": "Reason for approving this KYC check", + "format": "date-time", + "type": "string" + }, + "approved_by": { + "description": "Identifier of who approved KYC check", + "type": "string" + }, + "approved_reason": { + "description": "Datetime that this KYC check was approved", + "type": "string" + }, + "check_completed_at": { + "description": "completion datetime of KYC check", + "format": "date-time", + "type": "string" + }, + "check_initiated_at": { + "description": "start datetime of KYC check", + "format": "date-time", + "type": "string" + }, + "country_of_residency": { + "description": "country for `address` field", + "type": "string" + }, + "date_of_birth": { + "description": "DOB of applicant", + "format": "date", + "type": "string" + }, + "email_address": { + "description": "email address of applicant", + "type": "string" + }, + "id": { + "description": "Your internal ID of check", + "format": "uuid", + "type": "string" + }, + "ip_address": { + "description": "IP address of applicant at time of KYC check", + "type": "string" + }, + "kyc_completed_at": { + "description": "Datetime that KYC check was completed at", + "format": "date-time", + "type": "string" + }, + "nationality": { + "description": "nationality of applicant", + "type": "string" + }, + "postal_code": { + "description": "postal code for `address` field", + "type": "string" + }, + "risk_categories": { + "description": "The list of risk categories returned by the KYC provider or assessed", + "items": { + "type": "string" + }, + "type": "array" + }, + "risk_level": { + "description": "Overall risk level returned by KYC provider or assessed", + "type": "string" + }, + "risk_score": { + "description": "Overall risk score returned by KYC provider or assessed", + "type": "integer" + } + }, + "required": [ + "id" + ], + "title": "CIPKYCInfo", + "type": "object", + "x-examples": { + "Example 1": { + "address": "42 Faux St", + "applicant_name": "John Doe", + "approval_status": "approved", + "approved_at": "2021-06-10T15:38:03Z", + "approved_by": "Jane Doe", + "check_completed_at": "2021-06-10T15:37:03Z", + "check_initiated_at": "2021-06-10T15:37:03Z", + "country_of_residency": "USA", + "date_of_birth": "1970-12-01", + "email_address": "johndoe@example.com", + "id": "CBDAD1C4-1047-450E-BAE5-B6C406F509B4", + "id_number": "jd0000123456789", + "ip_address": "127.0.0.1", + "kyc_completed_at": "2021-06-10T15:37:03Z", + "nationality": "American", + "postal_code": "94401", + "risk_level": "LOW" + } + } + }, + "CIPPhoto": { + "description": "Represents the results of checking a Photo for CIPInfo", + "properties": { + "created_at": { + "description": "datetime of when the check happened", + "type": "string" + }, + "face_comparison": { + "$ref": "#/components/schemas/CIPResult" + }, + "face_comparison_breakdown": { + "description": "a json object representing the breakdown of sub-checks done in\n `face_comparison`. Example: {\"face_match\":{\"result\": \"clear\",\"properties\":{\"score\": \"80\"}}}", + "type": "string" + }, + "id": { + "description": "Your internal ID of the check", + "type": "string" + }, + "image_integrity": { + "$ref": "#/components/schemas/CIPResult" + }, + "image_integrity_breakdown": { + "description": "a json object representing the breakdown of sub-checks done in\n `image_integrity`. Example {\"face_detected\":{\"result\": \"clear\"},\"source_integrity\": {\"result\": \"clear\"}}", + "type": "string" + }, + "result": { + "$ref": "#/components/schemas/CIPResult" + }, + "status": { + "description": "Overall status of the check. Either `complete` or `withdrawn`.", + "enum": [ + "complete", + "withdrawn" + ], + "type": "string" + }, + "visual_authenticity": { + "$ref": "#/components/schemas/CIPResult" + }, + "visual_authenticity_breakdown": { + "description": "a json object representing the breakdown of sub-checks don in\n `visual_authenticity`. Example {\"spoofing_detection\": {\"result\": \"clear\",\"properties\": {\"score\": \"26\"}}}}", + "type": "string" + } + }, + "required": [ + "id" + ], + "type": "object", + "x-examples": { + "Example 1": { + "created_at": "2021-06-10T15:37:03Z", + "face_comparison": "clear", + "id": "0DD13020-F0FD-4B5A-B58F-BC1885E90A6D", + "result": "clear", + "status": "complete" + } + } + }, + "CIPProvider": { + "description": "\"alloy\" - ALLOY\n\"trulioo\" - TRULIOO\n\"onfido\" - ONFIDO\n\"veriff\" - VERIFF\n\"jumio\" - JUMIO\n\"getmati\" - GETMATI", + "title": "CIPProvider", + "type": "string" + }, + "CIPResult": { + "description": "The result of the check. Either `clear` or `consider`.", + "title": "CIPResult", + "type": "string" + }, + "CIPStatus": { + "description": "An enum representing the status of the CIPInfo\n\n\"complete\"\n\n\"withdrawn\"", + "title": "CIPStatus", + "type": "string" + }, + "CIPWatchlist": { + "description": "Represents the result of checking to see if the applicant is in any watchlists for a CIPInfo", + "properties": { + "adverse_media": { + "$ref": "#/components/schemas/CIPResult" + }, + "created_at": { + "description": "datetime when check happened", + "type": "string" + }, + "id": { + "description": "Your internal ID of check", + "type": "string" + }, + "monitored_lists": { + "$ref": "#/components/schemas/CIPResult" + }, + "politically_exposed_person": { + "$ref": "#/components/schemas/CIPResult" + }, + "records": { + "description": "a json object. Example [{\"text\": \"Record info\"}]", + "type": "string" + }, + "result": { + "$ref": "#/components/schemas/CIPResult" + }, + "sanction": { + "$ref": "#/components/schemas/CIPResult" + }, + "status": { + "$ref": "#/components/schemas/CIPStatus" + } + }, + "required": [ + "id" + ], + "type": "object", + "x-examples": { + "Example 1": { + "adverse_media": "clear", + "created_at": "2021-06-10T15:37:03Z", + "id": "7572B870-EB4C-46A2-8B88-509194CCEE7E", + "monitored_lists": "clear", + "politically_exposed_person": "clear", + "result": "clear", + "sanction": "clear", + "status": "complete" + } + } + }, + "CSWActivityV2": { + "allOf": [ + { + "$ref": "#/components/schemas/CommonNTAActivityV2" + }, + { + "properties": { + "bank_transaction_id": { + "description": "The bank transaction's ID", + "format": "uuid", + "type": "string" + } + }, + "type": "object" + } + ], + "description": "Cash withdrawal", + "type": "object" + }, + "CommissionType": { + "default": "notional", + "description": "An enum to select how to interpret the value provided in the commission field.\n\n- notional:\nCharge commission on a per order basis. (When the `commission_type` field is omitted from the order request, this is used as the default).\n\n- qty:\nCharge commission on a per qty/contract basis, pro rated.\n\n- bps:\nThe percent commission you want to charge the end user on the order (expressed in bps). Alpaca will convert the order to a notional amount for purposes of calculating commission.\nCommission value in bps can have up to two decimal places.", + "enum": [ + "notional", + "qty", + "bps" + ], + "example": "qty", + "title": "CommissionType", + "type": "string" + }, + "CommonAcatActivityV2": { + "properties": { + "external_id": { + "description": "The ID that DTCC assigned to this transfer", + "type": "string" + }, + "hold_date": { + "description": "Hold date when the transfers settle", + "format": "date", + "minLength": 1, + "type": "string" + }, + "request_id": { + "description": "The ID for original ACATS request", + "format": "uuid", + "type": "string" + } + }, + "required": [ + "external_id", + "request_id" + ], + "type": "object" + }, + "CommonCDIVActivityV2": { + "properties": { + "cusip": { + "description": "The CUSIP of the security involved with the activity", + "type": "string" + }, + "due_bill_off_date": { + "description": "When due bills stop applying for this event", + "format": "date", + "minLength": 1, + "type": "string" + }, + "due_bill_on_date": { + "description": "When due bills begin to apply for this event", + "format": "date", + "minLength": 1, + "type": "string" + }, + "ex_date": { + "description": "The ex_date for this corporate action", + "format": "date", + "minLength": 1, + "type": "string" + }, + "foreign": { + "description": "Indicates if related to a non-US security", + "type": "boolean" + }, + "payable_date": { + "description": "The payable_date for this corporate action", + "format": "date", + "minLength": 1, + "type": "string" + }, + "rate": { + "description": "Dividend rate per share", + "type": "string" + }, + "record_date": { + "description": "The record_date for this corporate action", + "format": "date", + "minLength": 1, + "type": "string" + }, + "special": { + "description": "Indicates if this is a special dividend", + "type": "boolean" + }, + "symbol": { + "description": "The symbol of the security involved with the activity", + "type": "string" + } + }, + "required": [ + "symbol", + "cusip", + "rate", + "foreign", + "special" + ], + "type": "object" + }, + "CommonCaActivityV2": { + "allOf": [ + { + "$ref": "#/components/schemas/CommonNTAActivityV2" + }, + { + "properties": { + "ca_id": { + "description": "The unique identifier for this corporate action", + "format": "uuid", + "type": "string" + }, + "position_date": { + "description": "The position_date for this corporate action", + "format": "date", + "minLength": 1, + "type": "string" + }, + "reorg_id": { + "description": "The reorg identifier, if present in the source corporate action definition", + "type": "string" + } + }, + "required": [ + "position_date" + ], + "type": "object" + } + ], + "type": "object" + }, + "CommonJournalActivityV2": { + "allOf": [ + { + "$ref": "#/components/schemas/CommonNTAActivityV2" + }, + { + "properties": { + "journal_id": { + "description": "The journal's ID", + "format": "uuid", + "type": "string" + } + }, + "type": "object" + } + ], + "type": "object" + }, + "CommonMAActivityV2": { + "properties": { + "acquiree_cusip": { + "description": "CUSIP of the acquiree", + "type": "string" + }, + "acquiree_rate": { + "description": "Rate of the acquiree", + "type": "string" + }, + "acquiree_symbol": { + "description": "Symbol of the acquiree", + "type": "string" + }, + "acquirer_cusip": { + "description": "CUSIP of the acquirer", + "type": "string" + }, + "acquirer_rate": { + "description": "Rate of the acquirer", + "type": "string" + }, + "acquirer_symbol": { + "description": "Symbol of the acquirer", + "type": "string" + }, + "effective_date": { + "description": "When the merger/acquisition becomes effective", + "format": "date", + "minLength": 1, + "type": "string" + }, + "payable_date": { + "description": "The payable date", + "format": "date", + "minLength": 1, + "type": "string" + } + }, + "required": [ + "acquiree_cusip", + "acquiree_symbol", + "effective_date", + "payable_date" + ], + "type": "object" + }, + "CommonNCActivityV2": { + "properties": { + "new_cusip": { + "description": "New CUSIP for the name change", + "type": "string" + }, + "new_symbol": { + "description": "New symbol for the name change", + "type": "string" + }, + "old_cusip": { + "description": "Old CUSIP for the name change", + "type": "string" + }, + "old_symbol": { + "description": "Old symbol for the name change", + "type": "string" + } + }, + "required": [ + "old_cusip", + "old_symbol", + "new_cusip", + "new_symbol" + ], + "type": "object" + }, + "CommonNTAActivityV2": { + "properties": { + "group_id": { + "description": "Optional group ID which can help grouping together related activities", + "format": "uuid", + "type": "string" + }, + "system_date": { + "description": "The date when the activity was booked", + "format": "date", + "minLength": 1, + "type": "string" + } + }, + "required": [ + "system_date" + ], + "type": "object" + }, + "CommonOPCAActivityV2": { + "allOf": [ + { + "$ref": "#/components/schemas/CommonCaActivityV2" + }, + { + "properties": { + "new_contract_symbol": { + "description": "The new contract symbol", + "type": "string" + }, + "new_qty": { + "description": "used when the old contract's quantity is not equal to the new contract's quantity. Mutually exclusive with 'qty'.", + "type": "string" + }, + "old_contract_symbol": { + "description": "The old contract symbol", + "type": "string" + }, + "old_qty": { + "description": "used when the old contract's quantity is not equal to the new contract's quantity. Mutually exclusive with 'qty'.", + "type": "string" + }, + "qty": { + "description": "used when the old contract's quantity is equal to the new contract's quantity. Mutually exclusive with 'old_qty' and 'new_qty'", + "type": "string" + } + }, + "required": [ + "old_contract_symbol", + "new_contract_symbol" + ], + "type": "object" + } + ], + "type": "object" + }, + "CommonOptionsActivityV2": { + "allOf": [ + { + "$ref": "#/components/schemas/CommonNTAActivityV2" + } + ], + "required": [ + "group_id" + ], + "type": "object" + }, + "CommonSDIVActivityV2": { + "properties": { + "cusip": { + "description": "The CUSIP of the security involved with the activity", + "type": "string" + }, + "ex_date": { + "description": "The ex_date for this corporate action", + "format": "date", + "minLength": 1, + "type": "string" + }, + "payable_date": { + "description": "The payable_date for this corporate action", + "format": "date", + "minLength": 1, + "type": "string" + }, + "rate": { + "description": "Dividend rate per share", + "type": "string" + }, + "record_date": { + "description": "The record_date for this corporate action", + "format": "date", + "minLength": 1, + "type": "string" + }, + "symbol": { + "description": "The symbol of the security involved with the activity", + "type": "string" + } + }, + "required": [ + "symbol", + "cusip", + "rate" + ], + "type": "object" + }, + "CommonSpinoffActivityV2": { + "properties": { + "due_bill_redemption_date": { + "description": "When due bills related to the spinoff are redeemed", + "format": "date", + "minLength": 1, + "type": "string" + }, + "ex_date": { + "description": "The ex_date for this corporate action", + "format": "date", + "minLength": 1, + "type": "string" + }, + "new_cusip": { + "description": "CUSIP of the new security", + "type": "string" + }, + "new_price": { + "description": "Market price of new shares after the spinoff", + "type": "string" + }, + "new_rate": { + "description": "Ratio of new shares received", + "type": "string" + }, + "new_symbol": { + "description": "Symbol of the new security", + "type": "string" + }, + "payable_date": { + "description": "The payable_date for this corporate action", + "format": "date", + "minLength": 1, + "type": "string" + }, + "record_date": { + "description": "The record_date for this corporate action", + "format": "date", + "minLength": 1, + "type": "string" + }, + "source_cusip": { + "description": "CUSIP of the parent security", + "type": "string" + }, + "source_price": { + "description": "Market price of parent shares before the spinoff", + "type": "string" + }, + "source_rate": { + "description": "Ratio of parent shares", + "type": "string" + }, + "source_symbol": { + "description": "Symbol of the parent security", + "type": "string" + } + }, + "required": [ + "source_cusip", + "source_symbol", + "source_rate", + "source_price", + "new_cusip", + "new_symbol", + "new_rate", + "new_price" + ], + "type": "object" + }, + "CommonSplitActivityV2": { + "properties": { + "new_cusip": { + "description": "CUSIP of the new security after the split", + "type": "string" + }, + "new_rate": { + "description": "Ratio of new shares received", + "type": "string" + }, + "old_cusip": { + "description": "CUSIP of the old security before the split", + "type": "string" + }, + "old_rate": { + "description": "Ratio of old shares exchanged", + "type": "string" + }, + "payable_date": { + "description": "The payable_date for this corporate action", + "format": "date", + "minLength": 1, + "type": "string" + } + }, + "required": [ + "old_cusip", + "new_cusip", + "old_rate", + "new_rate" + ], + "type": "object" + }, + "CommonSplitStockActivityV2": { + "allOf": [ + { + "$ref": "#/components/schemas/CommonCaActivityV2" + }, + { + "$ref": "#/components/schemas/CommonSplitActivityV2" + }, + { + "properties": { + "new_qty": { + "description": "The new quantity after the split", + "type": "string" + }, + "old_qty": { + "description": "The old quantity before the split", + "type": "string" + } + }, + "required": [ + "old_qty", + "new_qty" + ], + "type": "object" + } + ], + "type": "object" + }, + "CommonVOFSubtypeActivityV2": { + "properties": { + "new_cusip": { + "description": "CUSIP of the new security", + "type": "string" + }, + "new_symbol": { + "description": "Symbol of the new security", + "type": "string" + }, + "source_cusip": { + "description": "CUSIP of the parent security", + "type": "string" + }, + "source_symbol": { + "description": "Symbol of the parent security", + "type": "string" + } + }, + "required": [ + "source_cusip", + "source_symbol" + ], + "type": "object" + }, + "Contact": { + "description": "Contact is the model for the account owner contact information.\n", + "properties": { + "city": { + "example": "San Mateo", + "type": "string" + }, + "country": { + "description": "country code in ISO 3166-1 alpha-3 format, representing the country the person/entity resides in.", + "example": "USA", + "type": "string" + }, + "email_address": { + "example": "john.doe@example.com", + "format": "email", + "type": "string" + }, + "phone_number": { + "description": "Phone number should include the country code, format: \"+15555555555\"", + "example": "+15556667788", + "type": "string" + }, + "postal_code": { + "example": "94401", + "type": "string" + }, + "state": { + "description": "Required if the country or country_of_tax_residence (in the identity model below) is 'USA'.", + "example": "CA", + "type": "string" + }, + "street_address": { + "description": "The user's street address. If multiple lines in address, pass in as additional array elements. Maximum of 3 objects in array", + "items": { + "$ref": "#/components/schemas/StreetAddress" + }, + "type": "array" + }, + "unit": { + "description": "The specific apartment number if applicable", + "type": "string" + } + }, + "required": [ + "email_address", + "phone_number", + "street_address", + "city" + ], + "type": "object" + }, + "CorrespondentLimit": { + "properties": { + "amount_available": { + "format": "decimal", + "type": "string" + }, + "amount_in_use": { + "format": "decimal", + "type": "string" + }, + "amount_limit": { + "format": "decimal", + "type": "string" + } + }, + "type": "object" + }, + "CountryInfo": { + "description": "Represents the info for a country.", + "properties": { + "crypto_risk_rating": { + "description": "crypto risk rating of the country", + "enum": [ + "low", + "medium", + "high", + "prohibited" + ], + "type": "string" + }, + "crypto_supported_states": { + "description": "states where Alpaca supports crypto trading", + "items": { + "description": "the state code defined by ISO 3166-2", + "type": "string" + }, + "type": "array" + }, + "full_name": { + "description": "The full name of the country definded by ISO 3166-1", + "example": "Åland Islands", + "minLength": 1, + "type": "string" + }, + "securities_risk_rating": { + "description": "the securities risk rating of the country", + "enum": [ + "low", + "medium", + "high", + "prohibited" + ], + "type": "string" + } + }, + "required": [ + "full_name", + "securities_risk_rating", + "crypto_risk_rating" + ], + "title": "CountryInfo", + "type": "object", + "x-examples": { + "example": "\"ZAF\": {\n \"full_name\": \"South Africa\",\n \"crypto_risk_rating\": \"high\",\n \"securities_risk_rating\": \"high\"\n}\n", + "example-1": "\"USA\": {\n \"full_name\": \"United States of America\",\n \"crypto_risk_rating\": \"medium\",\n \"securities_risk_rating\": \"medium\",\n \"crypto_supported_states\": [\n \"CA\",\n \"CT\",\n \"GA\",\n \"IA\",\n \"ID\",\n \"IL\",\n \"IN\",\n \"KS\",\n \"KY\",\n \"MA\",\n \"MD\",\n \"ME\",\n \"MI\",\n \"MS\",\n \"MO\",\n \"MT\",\n \"NC\",\n \"ND\",\n \"NE\",\n \"OH\",\n \"RI\",\n \"SD\",\n \"UT\",\n \"VT\",\n \"WA\"\n ]\n}\n" + } + }, + "CountryInfos": { + "additionalProperties": { + "$ref": "#/components/schemas/CountryInfo" + }, + "description": "Represents the info for countries as a map where the keys are the ISO 3166 country codes.", + "title": "CountryInfos", + "type": "object" + }, + "CreateACHRelationshipRequest": { + "description": "Represents the fields used in creation of a new ACHRelationship.\n\nYou can create an ACHRelationship by passing the required fields here or if you have an account with Plaid you can use our integration with Plaid to create a relationship.\n\nPlease see the documentation [here](https://alpaca.markets/docs/api-references/broker-api/funding/ach/#plaid-integration-for-bank-transfers) for more info on using Plaid with Alpaca", + "properties": { + "account_owner_name": { + "minLength": 1, + "type": "string" + }, + "bank_account_number": { + "description": "In sandbox, this still must be a valid format", + "minLength": 1, + "type": "string" + }, + "bank_account_type": { + "description": "Must be `CHECKING` or `SAVINGS`", + "enum": [ + "CHECKING", + "SAVINGS" + ], + "minLength": 1, + "type": "string" + }, + "bank_routing_number": { + "description": "In sandbox, this still must be a valid format", + "minLength": 1, + "type": "string" + }, + "instant": { + "type": "boolean" + }, + "nickname": { + "minLength": 1, + "type": "string" + }, + "processor_token": { + "description": "If using Plaid, you can specify a Plaid processor token here ", + "type": "string" + } + }, + "required": [ + "account_owner_name", + "bank_account_type", + "bank_account_number", + "bank_routing_number" + ], + "title": "CreateACHRelationshipRequest", + "type": "object" + }, + "CreateBankRequest": { + "description": "Represents the possible fields to send when creating a new associated Bank resource for an account", + "properties": { + "account_number": { + "type": "string" + }, + "bank_code": { + "description": "9-Digit ABA RTN (Routing Number) or BIC", + "type": "string" + }, + "bank_code_type": { + "description": "ABA (Domestic) or BIC (International)", + "enum": [ + "ABA", + "BIC" + ], + "type": "string" + }, + "city": { + "description": "Only for international banks, ie if bank_code_type = BIC", + "type": "string" + }, + "country": { + "description": "Only for international banks, ie if bank_code_type = BIC", + "type": "string" + }, + "extra_fields": { + "$ref": "#/components/schemas/BankAdditionalFields" + }, + "name": { + "description": "Name of recipient bank", + "type": "string" + }, + "postal_code": { + "description": "Only for international banks, ie if bank_code_type = BIC. Minimum of 3 characters", + "type": "string" + }, + "state_province": { + "description": "Only for international banks, ie if bank_code_type = BIC", + "type": "string" + }, + "street_address": { + "description": "Only for international banks, ie if bank_code_type = BIC", + "type": "string" + } + }, + "required": [ + "name", + "bank_code", + "bank_code_type", + "account_number" + ], + "title": "CreateBankRequest", + "type": "object" + }, + "CreateCryptoTransferRequest": { + "properties": { + "address": { + "description": "The destination wallet address", + "type": "string" + }, + "amount": { + "description": "The amount, denoted in the specified asset, to be withdrawn from the user's wallet", + "type": "string" + }, + "asset": { + "type": "string" + }, + "chain": { + "description": "Optional chain identifier. The blockchain network used for the withdrawal.", + "enum": [ + "SOL", + "ETH", + "BTC", + "XRP", + "ARB" + ], + "type": "string" + } + }, + "required": [ + "amount", + "address", + "asset" + ], + "title": "CreateCryptoTransferRequest", + "type": "object" + }, + "CreateFundingWalletWithdrawalRequest": { + "properties": { + "desired_currency": { + "type": "string" + }, + "payment_type": { + "$ref": "#/components/schemas/FundingDetailPaymentType" + }, + "usd_amount": { + "format": "decimal", + "type": "string" + } + }, + "type": "object" + }, + "CreateIFTransferRequest": { + "description": "Request to create a new instant funding transfer\n", + "properties": { + "account_no": { + "type": "string" + }, + "amount": { + "format": "decimal", + "type": "string" + }, + "source_account_no": { + "type": "string" + } + }, + "required": [ + "account_no", + "source_account_no", + "amount" + ], + "type": "object" + }, + "CreateJITSettlementRequest": { + "description": "Request to create a new settlement. All transfers included must use the same source account, and the total amount received must be in the same currency as the base currency of the source account.\n", + "properties": { + "accounts": { + "items": { + "$ref": "#/components/schemas/SettlementAccount" + }, + "type": "array" + }, + "additional_info": { + "description": "Additional remittance information. Include if the funding source may require additional information to associate to the source account or settlement.\n", + "type": "string" + }, + "asset_class": { + "$ref": "#/components/schemas/JITAssetClass" + }, + "currency": { + "description": "The currency of the settlement. All accounts identified should have this base currency.\n", + "type": "string" + } + }, + "required": [ + "currency", + "asset_class", + "accounts" + ], + "type": "object" + }, + "CreateJournalRequest": { + "description": "Journals API allows you to move cash or securities from one account to another.\n\nThis model represents the fields you can specify when creating a Journal\n\nFixture Rules\n\n- No Fixtures\n - anything below limit is executed immediately\n - anything above limit is pending until executed at EOD,\n- With Fixtures\n - any status = rejected will be rejected EOD\n - any status = pending will be pending forever", + "properties": { + "amount": { + "description": "Required if `entry_type` = `JNLC`", + "type": "string" + }, + "currency": { + "type": "string" + }, + "description": { + "description": "Max 1024 characters. Can include fixtures for amounts that are above the transaction limit", + "maxLength": 1024, + "type": "string" + }, + "entry_type": { + "$ref": "#/components/schemas/JournalEntryType" + }, + "from_account": { + "description": "The account_id you wish to journal from", + "format": "uuid", + "minLength": 1, + "type": "string" + }, + "qty": { + "description": "Required if `entry_type` = `JNLS`", + "type": "string" + }, + "symbol": { + "description": "Required if `entry_type` = `JNLS`", + "type": "string" + }, + "to_account": { + "description": "The account_id you wish to journal to", + "format": "uuid", + "minLength": 1, + "type": "string" + }, + "transmitter_account_number": { + "description": "Max 255 characters. See more details about [Travel Rule](https://alpaca.markets/docs/broker/integration/funding/#travel-rule) in our main documentation.", + "maxLength": 255, + "type": "string" + }, + "transmitter_address": { + "description": "Max 255 characters. See more details about [Travel Rule](https://alpaca.markets/docs/broker/integration/funding/#travel-rule) in our main documentation.", + "maxLength": 255, + "type": "string" + }, + "transmitter_financial_institution": { + "description": "Max 255 characters. See more details about [Travel Rule](https://alpaca.markets/docs/broker/integration/funding/#travel-rule) in our main documentation.", + "maxLength": 255, + "type": "string" + }, + "transmitter_name": { + "description": "Max 255 characters. See more details about [Travel Rule](https://alpaca.markets/docs/broker/integration/funding/#travel-rule) in our main documentation.", + "maxLength": 255, + "type": "string" + }, + "transmitter_timestamp": { + "description": "RFC 3339 format. See more details about [Travel Rule](https://alpaca.markets/docs/broker/integration/funding/#travel-rule) in our main documentation.", + "format": "date-time", + "type": "string" + } + }, + "required": [ + "to_account", + "from_account", + "entry_type" + ], + "type": "object", + "x-examples": { + "example-1": { + "amount": "51", + "description": "test text /fixtures/status=rejected/fixtures/", + "entry_type": "JNLC", + "from_account": "c94bu7rn-4483-4199-840f-6c5fe0b7ca24", + "to_account": "fn68sbrk-6f2a-433c-8c33-17b66b8941fa" + } + } + }, + "CreateOrderRequest": { + "properties": { + "client_order_id": { + "description": "A unique identifier for the order. Automatically generated if not sent. (<= 128 characters)", + "example": "eb9e2aaa-f71a-4f51-b5b4-52a6c565dad4", + "maxLength": 128, + "type": "string" + }, + "commission": { + "description": "The commission you want to collect from the user.", + "example": "1.0", + "format": "decimal", + "type": "string" + }, + "commission_bps": { + "deprecated": true, + "description": "**deprecated**: Please use the commission_type = bps instead and set the desired bps value in the `commission` field.\nThe percent commission you want to charge the end user on the order (expressed in bps). Alpaca will convert the order to a notional amount for purposes of calculating commission.\n", + "example": "10", + "format": "decimal", + "type": "string" + }, + "commission_type": { + "$ref": "#/components/schemas/CommissionType" + }, + "extended_hours": { + "description": "Defaults to false. If true, order will be eligible for execution in the pre-market, after-hours, and overnight sessions. Only works with type `limit` and time_in_force set to either `day` or `gtc`.", + "example": false, + "type": "boolean" + }, + "instructions": { + "type": "string" + }, + "legs": { + "description": "list of order legs (<= 4)", + "items": { + "$ref": "#/components/schemas/MLegOrderLeg" + }, + "maxItems": 4, + "type": "array" + }, + "limit_price": { + "description": "Required if type is `limit` or `stop_limit`.\n- In case of `mleg`, the limit_price parameter is expressed with the following notation:\n - A positive value indicates a debit, representing a cost or payment to be made.\n - A negative value signifies a credit, reflecting an amount to be received.\n- In case of Fied Income, the price is expressed in percentage of par value (face value).\nPrice is always clean price, meaning it does not include accrued interest.", + "example": "3.14", + "format": "decimal", + "type": "string" + }, + "notional": { + "description": "Dollar amount to trade. Cannot work with qty. Only market and limit orders supported with time_in_force = day; Only limit orders for extended hours.", + "example": "3", + "format": "decimal", + "type": "string" + }, + "order_class": { + "$ref": "#/components/schemas/OrderClass" + }, + "position_intent": { + "$ref": "#/components/schemas/PositionIntent" + }, + "qty": { + "description": "- For equities, the number of shares to trade. Can be fractionable for only market and day order types.\n- Required for `mleg` order class, represents the number of units to trade of this strategy.\n- For Fixed Income securities, qty represents the order size in par value (face value).\nFor example, to place an order for 1 bond with a face value of $1,000, provide a qty of 1000.\n", + "example": "4.124", + "format": "decimal", + "type": "string" + }, + "side": { + "$ref": "#/components/schemas/OrderSide" + }, + "source": { + "type": "string" + }, + "stop_loss": { + "description": "Takes in a string/number values for stop_price and limit_price", + "properties": { + "limit_price": { + "example": "3.14", + "format": "decimal", + "type": "string" + }, + "stop_price": { + "example": "3.14", + "format": "decimal", + "type": "string" + } + }, + "type": "object" + }, + "stop_price": { + "description": "Required if type is stop or stop_limit", + "example": "3.14", + "format": "decimal", + "type": "string" + }, + "subtag": { + "type": "string" + }, + "swap_fee_bps": { + "format": "decimal", + "type": "string" + }, + "symbol": { + "description": "Symbol or asset ID to identify the asset to trade. Required for all order classes except for `mleg`.", + "example": "AAPL", + "type": "string" + }, + "take_profit": { + "description": "Takes in a string/number value for limit_price", + "properties": { + "limit_price": { + "example": "3.14", + "format": "decimal", + "type": "string" + } + }, + "type": "object" + }, + "time_in_force": { + "$ref": "#/components/schemas/TimeInForce" + }, + "trail_percent": { + "description": "If type is trailing_stop, then one of trail_price or trail_percent is required", + "example": "5.0", + "format": "decimal", + "type": "string" + }, + "trail_price": { + "description": "If type is trailing_stop, then one of trail_price or trail_percent is required", + "example": "3.14", + "format": "decimal", + "type": "string" + }, + "type": { + "$ref": "#/components/schemas/OrderType" + } + }, + "required": [ + "type", + "time_in_force" + ], + "type": "object" + }, + "CreateSettlementRequest": { + "description": "Request to create a new settlement. All transfers included must use the same source account, and the total amount received must be in the same currency as the base currency of the source account as well as the transmitter information.\n", + "properties": { + "additional_info": { + "description": "Additional remittance information. Include if the funding source may require additional information to associate to the source account or settlement.\n", + "type": "string" + }, + "transfers": { + "items": { + "$ref": "#/components/schemas/SettlementTransfer" + }, + "type": "array" + } + }, + "required": [ + "transfers" + ], + "type": "object" + }, + "CreateTransferRequest": { + "description": "[See main docs here](https://alpaca.markets/docs/api-references/broker-api/funding/transfers/#creating-a-transfer-entity)", + "properties": { + "additional_information": { + "description": "Additional details for when type = `wire`", + "nullable": true, + "type": "string" + }, + "amount": { + "description": "Must be > 0.00", + "format": "decimal", + "type": "string" + }, + "bank_id": { + "description": "Required if type = `wire`\n\nThe bank_relationship created for the account_id [here](https://alpaca.markets/docs/api-references/broker-api/funding/bank/#creating-a-new-bank-relationship)", + "format": "uuid", + "type": "string" + }, + "direction": { + "$ref": "#/components/schemas/TransferDirection" + }, + "fee_payment_method": { + "$ref": "#/components/schemas/FeePaymentMethod" + }, + "ira": { + "$ref": "#/components/schemas/TransferIRA" + }, + "relationship_id": { + "description": "Required if type = `ach`\n\nThe ach_relationship created for the account_id [here](https://alpaca.markets/docs/api-references/broker-api/funding/ach/#creating-an-ach-relationship)", + "format": "uuid", + "type": "string" + }, + "timing": { + "deprecated": true, + "description": "Deprecated. Not honored by the API; values sent are silently ignored and this field will be removed.", + "enum": [ + "immediate" + ], + "example": "immediate", + "type": "string", + "x-deprecation": { + "reason": "Not honored by the API since January 2021; any value sent is silently ignored. Stop including this field in requests.", + "since": "2026-05-26T00:00:00Z", + "sunset": "2026-08-26T00:00:00Z" + } + }, + "transfer_type": { + "$ref": "#/components/schemas/TransferType" + } + }, + "required": [ + "transfer_type", + "amount", + "direction" + ], + "title": "CreateTransferRequest", + "type": "object" + }, + "CreateWatchlistRequest": { + "description": "This model represents the fields you can specify when Creating or Updating/Replacing a Watchlist", + "properties": { + "name": { + "description": "The watchlist name", + "type": "string" + }, + "symbols": { + "description": "The new list of symbol names to watch", + "items": { + "example": "[\"AAPL\", \"TSLA\"]", + "type": "string" + }, + "type": "array" + } + }, + "required": [ + "name", + "symbols" + ], + "title": "CreateWatchlistRequest", + "type": "object" + }, + "CryptoTransfer": { + "description": "Transfers allow you to transfer assets into your end customer's account (deposits) or out (withdrawal).", + "properties": { + "amount": { + "description": "Amount of transfer denominated in the underlying crypto asset", + "type": "string" + }, + "asset": { + "description": "Symbol of crypto asset for given transfer (e.g. BTC)", + "type": "string" + }, + "chain": { + "description": "Underlying network for given transfer", + "type": "string" + }, + "created_at": { + "description": "Timestamp when transfer was created", + "format": "date-time", + "type": "string" + }, + "direction": { + "$ref": "#/components/schemas/TransferDirection" + }, + "fees": { + "type": "string" + }, + "from_address": { + "description": "Originating address of the transfer", + "type": "string" + }, + "id": { + "description": "The crypto transfer ID", + "format": "uuid", + "type": "string" + }, + "network_fee": { + "type": "string" + }, + "status": { + "$ref": "#/components/schemas/CryptoTransferStatus" + }, + "to_address": { + "description": "Destination address of the transfer", + "type": "string" + }, + "tx_hash": { + "description": "On-chain transaction hash (e.g. 0xabc...xyz)", + "type": "string" + }, + "usd_value": { + "description": "Equivalent USD value at time of transfer", + "type": "string" + } + }, + "type": "object" + }, + "CryptoTransferStatus": { + "enum": [ + "PROCESSING", + "FAILED", + "COMPLETE" + ], + "example": "PROCESSING", + "type": "string" + }, + "CryptoWallet": { + "properties": { + "address": { + "type": "string" + }, + "chain": { + "type": "string" + }, + "created_at": { + "description": "Timestamp (RFC3339) of account creation.", + "format": "date-time", + "type": "string" + } + }, + "type": "object" + }, + "Currency": { + "description": "\"USD\" // US Dollar\n\"JPY\" // Japanese Yen\n\"EUR\" // Euro\n\"CAD\" // Canadian Dollar\n\"GBP\" // British Pound Sterling\n\"CHF\" // Swiss Franc\n\"TRY\" // Turkish Lira\n\"AUD\" // Australian Dollar\n\"CZK\" // Czech Koruna\n\"SEK\" // Swedish Krona\n\"DKK\" // Danish Krone\n\"SGD\" // Singapore Dollar\n\"HKD\" // Hong Kong Dollar\n\"HUF\" // Hungarian Forint\n\"NZD\" // New Zealand Dollar\n\"NOK\" // Norwegian Krone\n\"PLN\" // Poland Złoty", + "title": "Currency", + "type": "string" + }, + "CustodialAccountMinorIdentity": { + "description": "Represents Identity information for a minor that an account of type \"custodial\" is for", + "properties": { + "country_of_birth": { + "type": "string" + }, + "country_of_citizenship": { + "type": "string" + }, + "country_of_tax_residence": { + "type": "string" + }, + "date_of_birth": { + "format": "date", + "type": "string" + }, + "email": { + "format": "email", + "type": "string" + }, + "family_name": { + "type": "string" + }, + "given_name": { + "type": "string" + }, + "state": { + "type": "string" + }, + "tax_id": { + "type": "string" + }, + "tax_id_type": { + "$ref": "#/components/schemas/TaxIdType" + } + }, + "required": [ + "given_name", + "family_name", + "date_of_birth", + "country_of_tax_residence", + "state", + "email" + ], + "type": "object", + "x-examples": {} + }, + "CustodialAccountType": { + "description": "Represents the type of custodial account based on the state where the beneficiary resides.\nThis value is returned only when the `country_of_tax_residence` is `USA`. For other countries, this property is not included in the response.\n\n**Possible Return Values**\n\n`CustodianTypeUTMA` - Indicates that the custodial account is governed by the Uniform Transfers to Minors Act.\n`CustodianTypeUGMA` - Indicates that the custodial account is governed by the Uniform Gifts to Minors Act.", + "title": "CustodialAccountType", + "type": "string" + }, + "DIVNRAActivityV2": { + "allOf": [ + { + "$ref": "#/components/schemas/CommonNTAActivityV2" + }, + { + "properties": { + "cusip": { + "description": "The CUSIP of the security involved with the activity", + "type": "string" + }, + "parent_id": { + "description": "The ref_id of the parent dividend", + "type": "string" + }, + "symbol": { + "description": "The symbol of the security involved with the activity", + "type": "string" + } + }, + "required": [ + "cusip", + "symbol", + "parent_id" + ], + "type": "object" + } + ], + "description": "Dividend withholding for non resident aliens", + "type": "object" + }, + "DIVSPDActivityV2": { + "allOf": [ + { + "$ref": "#/components/schemas/CommonCaActivityV2" + }, + { + "properties": { + "cash_payout": { + "description": "Total cash amount paid", + "type": "string" + }, + "cusip": { + "description": "The CUSIP of the security involved with the activity", + "type": "string" + }, + "due_bill_off_date": { + "description": "When due bills stop applying for this event", + "format": "date", + "minLength": 1, + "type": "string" + }, + "due_bill_on_date": { + "description": "When due bills begin to apply for this event", + "format": "date", + "minLength": 1, + "type": "string" + }, + "entitled_qty": { + "description": "Quantity of shares entitled to receive cash in lieu", + "type": "string" + }, + "ex_date": { + "description": "The ex_date for this corporate action", + "format": "date", + "minLength": 1, + "type": "string" + }, + "foreign": { + "description": "Indicates if related to a non-US security", + "type": "boolean" + }, + "payable_date": { + "description": "The payable_date for this corporate action", + "format": "date", + "minLength": 1, + "type": "string" + }, + "rate": { + "description": "Cash payout per share", + "type": "string" + }, + "record_date": { + "description": "The record_date for this corporate action", + "format": "date", + "minLength": 1, + "type": "string" + }, + "special": { + "description": "Indicates if this is a special dividend", + "type": "boolean" + }, + "symbol": { + "description": "The symbol of the security involved with the activity", + "type": "string" + } + }, + "required": [ + "entitled_qty", + "cash_payout", + "symbol", + "cusip", + "rate", + "foreign", + "special" + ], + "type": "object" + } + ], + "description": "Substitute payment in lieu of dividend", + "type": "object" + }, + "DailyCashInterest": { + "properties": { + "account_accrued_interest": { + "example": "1.2152", + "format": "decimal", + "type": "string" + }, + "account_id": { + "example": "02e5c5ba-36d2-467a-8699-81dc24271291", + "format": "uuid", + "type": "string" + }, + "account_rate_bps": { + "example": 423, + "type": "integer" + }, + "apr_tier_id": { + "example": "13d149c8-d620-43e0-978b-88af8abb2ef1", + "format": "uuid", + "type": "string" + }, + "apr_tier_name": { + "example": "gold", + "type": "string" + }, + "cash_balance": { + "example": "10485.76", + "format": "decimal", + "type": "string" + }, + "correspondent_fee": { + "example": "0.0718", + "format": "decimal", + "type": "string" + }, + "correspondent_rate_bps": { + "example": 25, + "type": "integer" + }, + "currency": { + "example": "USD", + "type": "string" + }, + "date": { + "example": "2024-09-27", + "format": "date", + "type": "string" + } + }, + "type": "object" + }, + "DailyTradingLimit": { + "properties": { + "daily_net_limit": { + "description": "The net buying limit that can be reached before further cash outflow trading activity is restricted. Please reach out to learn more about how this limit is determined.", + "format": "decimal", + "type": "string" + }, + "daily_net_limit_in_use": { + "description": "The real time net value of cash inflows (buy trades, etc.) with cash outflows (sell trades, dividends, etc). This will be dynamic throughout the day based on user activity, with executed orders being reset at the start of the next trading day.", + "format": "decimal", + "type": "string" + } + }, + "title": "DailyTradingLimit", + "type": "object" + }, + "DemoFundingTransfer": { + "properties": { + "amount": { + "format": "decimal", + "type": "string" + }, + "currency": { + "type": "string" + }, + "receiver_account_number": { + "type": "string" + }, + "receiver_routing_code": { + "type": "string" + } + }, + "type": "object" + }, + "DisclosureContextAnnotation": { + "properties": { + "company_city": { + "description": "Required for FINRA affiliations and controlled firms.", + "type": "string" + }, + "company_compliance_email": { + "description": "Required for FINRA affiliations and controlled firms.", + "type": "string" + }, + "company_country": { + "description": "Required for FINRA affiliations and controlled firms.", + "type": "string" + }, + "company_name": { + "description": "Required for FINRA affiliations and controlled firms.", + "type": "string" + }, + "company_state": { + "description": "Required if and only if `company_country` is `USA`.", + "type": "string" + }, + "company_street_address": { + "description": "Required for FINRA affiliations and controlled firms.", + "type": "string" + }, + "context_type": { + "description": "Specifies the type of disclosure annotation. Valid types are FINRA affiliations, for users affiliated with or employed by a FINRA member firm, a Stock Exchange Member, FINRA, Registered Investment Advisor, or a Municipal Securities Broker/Dealer; Company control relationships, for senior executives, and 10% or greater shareholders, of a publicly traded company; and immediate family members of politically exposed individuals.", + "enum": [ + "CONTROLLED_FIRM", + "IMMEDIATE_FAMILY_EXPOSED", + "AFFILIATE_FIRM" + ], + "type": "string" + }, + "family_name": { + "description": "Required for immediate family members of politically exposed persons.", + "type": "string" + }, + "given_name": { + "description": "Required for immediate family members of politically exposed persons.", + "type": "string" + } + }, + "required": [ + "context_type" + ], + "title": "DisclosureContextAnnotation", + "type": "object" + }, + "Disclosures": { + "description": "Disclosures fields denote if the account owner falls under\neach category defined by FINRA rule. The client has to ask\nquestions for the end user and the values should reflect\ntheir answers.\nIf one of the answers is true (yes), the account goes into\nACTION_REQUIRED status.\n", + "example": { + "immediate_family_exposed": false, + "is_affiliated_exchange_or_finra": false, + "is_control_person": false, + "is_politically_exposed": false + }, + "properties": { + "context": { + "description": "Array of annotations describing the rationale for marking `is_control_person`, `is_affiliated_exchange_or_finra`, and/or `immediate_family_exposed` as true", + "items": { + "$ref": "#/components/schemas/DisclosureContextAnnotation" + }, + "nullable": true, + "type": "array" + }, + "employer_address": { + "description": "The employer's address if the user is employed.", + "type": "string" + }, + "employer_name": { + "description": "The name of the employer if the user is employed.", + "type": "string" + }, + "employment_position": { + "description": "The user's position if they are employed.", + "type": "string" + }, + "employment_sector": { + "description": "The industry sector of employment.\nIf the `employment_status` is `unemployed` or `student`, set this property to `not_employed`.\nIf the `employment_status` is `retired`, set this to `self_employed`.\n", + "enum": [ + "agriculture", + "business_management", + "computers_and_it", + "construction", + "education", + "finance", + "government", + "healthcare", + "hospitality", + "manufacturing", + "marketing", + "media", + "other", + "science", + "self_employed", + "transportation", + "not_employed" + ], + "type": "string" + }, + "employment_status": { + "description": "One of the following: `employed`, `unemployed`, `retired`, or `student`.", + "enum": [ + "unemployed", + "employed", + "student", + "retired" + ], + "type": "string" + }, + "immediate_family_exposed": { + "description": "If your user's immediate family member (sibling, husband/wife, child, parent) is either politically exposed or holds a control position.", + "type": "boolean" + }, + "is_affiliated_exchange_or_finra": { + "description": "Whether user is affiliated with any exchanges or FINRA.", + "type": "boolean" + }, + "is_control_person": { + "description": "Whether user holds a controlling position in a publicly traded company, member of the board of directors or has policy making abilities in a publicly traded company.", + "type": "boolean" + }, + "is_politically_exposed": { + "description": "Whether the user is politically exposed.", + "type": "boolean" + } + }, + "required": [ + "is_control_person", + "is_affiliated_exchange_or_finra", + "is_politically_exposed", + "immediate_family_exposed" + ], + "type": "object" + }, + "DoNotExerciseRequest": { + "description": "Request to submit a do-not-exercise (DNE) instruction for an option contract for an account.\nThe request body is empty. A successful request will mark the entire position as do not exercise.", + "title": "DoNotExerciseRequest", + "type": "object" + }, + "DoNotExerciseResponse": { + "description": "Response to a do-not-exercise request.\nA 200 response indicates that the entire position is set as do not exercise.\nThe response body is empty.", + "title": "DoNotExerciseResponse", + "type": "object" + }, + "EoDCashInterestReportResponse": { + "properties": { + "interest": { + "items": { + "$ref": "#/components/schemas/DailyCashInterest" + }, + "type": "array" + }, + "next_page_token": { + "example": "293639be-8807-423f-b00d-68c781e8bb56", + "nullable": true, + "type": "string" + } + }, + "type": "object" + }, + "Error": { + "properties": { + "code": { + "type": "number" + }, + "message": { + "type": "string" + } + }, + "required": [ + "code", + "message" + ], + "title": "Error", + "type": "object" + }, + "Exchange": { + "description": "Represents the exchange where an asset is traded.\n\nFor Stocks:\n- AMEX\n- ARCA\n- BATS\n- NYSE\n- NASDAQ\n- NYSEARCA\n- OTC\n\nFor Crypto:\n- CRYPTO", + "enum": [ + "AMEX", + "ARCA", + "BATS", + "NYSE", + "NASDAQ", + "NYSEARCA", + "OTC" + ], + "example": "NASDAQ", + "title": "Exchange", + "type": "string" + }, + "ExchangeOfferActivityV2": { + "allOf": [ + { + "$ref": "#/components/schemas/CommonNTAActivityV2" + }, + { + "$ref": "#/components/schemas/CommonVOFSubtypeActivityV2" + } + ], + "type": "object" + }, + "ExerciseRequest": { + "description": "Request to exercise an option contract for an account.\nThis can be omitted or left empty, but it also allows for optionally charging a commission for exercising or\nspecifying a specific quantity of contracts to exercise (rather than the full quantity of contracts held).", + "properties": { + "commission": { + "description": "The commission you want to collect from the user. (notional amount)", + "example": "0.25", + "format": "decimal", + "type": "string" + }, + "qty": { + "description": "The number of contracts to exercise. If not provided, all contracts will be exercised.", + "example": "1", + "format": "decimal", + "type": "string" + } + }, + "title": "ExerciseRequest", + "type": "object" + }, + "ExerciseResponse": { + "description": "Response to an exercise request.\nThis will return the total quantity of contracts exercised as well as the quantity of remaining contracts.", + "properties": { + "qty_exercised": { + "description": "The total quantity of contracts exercised.", + "format": "decimal", + "type": "string" + }, + "qty_remaining": { + "description": "The total quantity of contracts remaining after the exercise.", + "format": "decimal", + "type": "string" + } + }, + "title": "ExerciseResponse", + "type": "object" + }, + "FEEActivityV2": { + "allOf": [ + { + "$ref": "#/components/schemas/CommonNTAActivityV2" + }, + { + "properties": { + "parent_id": { + "description": "The parent transaction's ID", + "format": "uuid", + "type": "string" + } + }, + "required": [ + "parent_id" + ], + "type": "object" + } + ], + "description": "Fee", + "type": "object" + }, + "FOPTActivityV2": { + "allOf": [ + { + "$ref": "#/components/schemas/CommonNTAActivityV2" + }, + { + "properties": { + "contra": { + "description": "Contra for the transfer", + "type": "string" + }, + "external_id": { + "description": "External ID of the transfer", + "type": "string" + }, + "symbol": { + "description": "The symbol of the security involved with the activity", + "type": "string" + } + }, + "required": [ + "external_id", + "contra", + "symbol" + ], + "type": "object" + } + ], + "description": "Free-of-payment (FOP) transfers", + "type": "object" + }, + "FPSLAnalyticsLoansResponse": { + "description": "Aggregated FPSL interest and loan-activity counts for one account over a date range.\n", + "properties": { + "account_number": { + "description": "Account's number at Alpaca", + "example": "286873545", + "type": "string" + }, + "in_progress_lending_activities": { + "description": "The number of FPSL lending activities currently in progress for the account.", + "example": 2, + "format": "int64", + "type": "integer" + }, + "interest": { + "$ref": "#/components/schemas/FPSLInterest" + }, + "total_lending_activities": { + "description": "The total number of FPSL lending activities for the account within the specified date range.", + "example": 7, + "format": "int64", + "type": "integer" + } + }, + "required": [ + "account_number", + "total_lending_activities", + "in_progress_lending_activities" + ], + "type": "object" + }, + "FPSLError": { + "description": "FPSL API error response", + "properties": { + "message": { + "example": "Internal Server Error", + "type": "string" + } + }, + "type": "object" + }, + "FPSLInterest": { + "description": "The interest's details.\nPlease note that the object might not be set if interest has not yet been calculated for the loan.\nAdditionally, interest details may be adjusted retroactively at any time until interests are finalized for the month.\nThe loan's changed `updated_at` field can indicate such an adjustment.\n", + "properties": { + "customer": { + "description": "The interest accrued by the customer for this loan.", + "example": 1.23, + "format": "double", + "type": "number" + }, + "partner": { + "description": "The interest accrued by the partner for this loan.", + "example": 1.65, + "format": "double", + "type": "number" + } + }, + "required": [ + "customer", + "partner" + ], + "type": "object" + }, + "FPSLLoan": { + "description": "A loan of a security by an account on a date.", + "properties": { + "account_id": { + "description": "Account's ID at Alpaca", + "example": "06d83e79-5bea-4ce9-a362-a25251a700ec", + "type": "string" + }, + "account_number": { + "description": "Account's number at Alpaca", + "example": "286873545", + "type": "string" + }, + "collateral": { + "description": "The collateral posted for the loan.", + "example": 1161.015, + "format": "double", + "type": "number" + }, + "correspondent": { + "description": "Account's correspondent", + "example": "LPCA", + "type": "string" + }, + "date": { + "example": "2006-01-02", + "format": "date", + "type": "string" + }, + "interest": { + "$ref": "#/components/schemas/FPSLInterest" + }, + "market_value": { + "description": "The total market value of the shares on loan.", + "example": 1138.25, + "format": "double", + "type": "number" + }, + "quantity": { + "description": "The number of shares in the loan. This may be less than the total number of eligible shares held by the account.", + "example": 5, + "format": "int64", + "type": "integer" + }, + "symbol": { + "description": "Stock/Ticker symbol of a stock or security", + "example": "AAPL", + "type": "string" + }, + "updated_at": { + "description": "Timestamp of the last update to this loan in RFC-3339 format with microsecond precision with timezone.\nAn updated value may indicate adjusted interest values.\n", + "example": "2025-03-21T15:54:19.857034+01:00", + "format": "date-time", + "type": "string" + } + }, + "required": [ + "date", + "account_id", + "account_number", + "correspondent", + "symbol", + "quantity", + "market_value", + "collateral" + ], + "type": "object" + }, + "FPSLTier": { + "properties": { + "created_at": { + "description": "The timestamp the FPSL tier was created, in RFC 3339 format.", + "example": "2024-09-16T20:06:32.207729Z", + "format": "date-time", + "type": "string" + }, + "customer_split": { + "description": "Percentage of the customer split represented as a value between 0 and 1.00. Max 2 decimal places are allowed.", + "example": 0.25, + "type": "number" + }, + "id": { + "description": "The unique identifier of the FPSL tier", + "example": "150df7eb-31e7-46b3-8848-a54ca21ff3bc", + "format": "uuid", + "type": "string" + }, + "market": { + "description": "The market of the FPSL tier", + "example": "US", + "type": "string" + }, + "partner_split": { + "description": "Percentage of the partner split represented as a value between 0 and 1.00. Max 2 decimal places are allowed.", + "example": 0.3, + "type": "number" + }, + "tier_name": { + "description": "The name of the FPSL tier", + "example": "gold", + "type": "string" + }, + "updated_at": { + "description": "The timestamp of the last update to the FPSL tier, in RFC 3339 format.", + "example": "2024-09-16T20:06:32.207729Z", + "format": "date-time", + "type": "string" + } + }, + "type": "object" + }, + "FeePaymentMethod": { + "description": "Only outgoing wire fees are currently supported for automated processing.\n\n\n**user**\tThe end user will pay any applicable fees\n**invoice**\tAny applicable fees will be billed to the client in the following monthly invoice", + "title": "FeePaymentMethod", + "type": "string" + }, + "FeePaymentType": { + "description": "Status:\n * `invoice`\n", + "enum": [ + "invoice" + ], + "type": "string" + }, + "FeeType": { + "description": "Status:\n * `withdrawal_fee`: Withdrawal fee\n * `fx_fee`: FX Fee\n * `network_fee`\n * `deposit_fee`\n * `ach_return_fee`\n * `parnter_fee`\n * `alpaca_fee`\n", + "enum": [ + "withdrawal_fee", + "fx_fee", + "network_fee", + "deposit_fee", + "ach_return_fee", + "parnter_fee", + "alpaca_fee" + ], + "type": "string" + }, + "FixedIncomeRedemptionActivityV2": { + "allOf": [ + { + "$ref": "#/components/schemas/CommonNTAActivityV2" + }, + { + "properties": { + "ca_id": { + "description": "The unique identifier for this corporate action", + "type": "string" + }, + "cash_payout": { + "description": "The cash payout", + "type": "string" + }, + "cusip": { + "description": "The CUSIP of the security involved with the activity", + "type": "string" + }, + "payment_date": { + "description": "The payment date", + "format": "date", + "minLength": 1, + "type": "string" + }, + "qty": { + "description": "Quantity for the redemption", + "type": "string" + } + }, + "required": [ + "ca_id", + "payment_date", + "cusip", + "qty", + "cash_payout" + ], + "type": "object" + } + ], + "description": "Redemption", + "type": "object" + }, + "ForwardSplitActivityV2": { + "allOf": [ + { + "$ref": "#/components/schemas/CommonSplitStockActivityV2" + }, + { + "properties": { + "due_bill_redemption_date": { + "description": "When due bills related to the split are redeemed", + "format": "date", + "minLength": 1, + "type": "string" + }, + "ex_date": { + "description": "The ex_date for this corporate action", + "format": "date", + "minLength": 1, + "type": "string" + }, + "record_date": { + "description": "The record_date for this corporate action", + "format": "date", + "minLength": 1, + "type": "string" + }, + "symbol": { + "description": "The symbol of the security being split", + "type": "string" + } + }, + "required": [ + "symbol" + ], + "type": "object" + } + ], + "description": "Forward stock split", + "type": "object" + }, + "FundingDetail": { + "description": "Gets funding details that can be used to settle and collect funds in each available currency.\n", + "properties": { + "account_holder_name": { + "type": "string" + }, + "account_number": { + "type": "string" + }, + "account_number_type": { + "type": "string" + }, + "bank_address": { + "type": "string" + }, + "bank_country": { + "type": "string" + }, + "bank_name": { + "type": "string" + }, + "currency": { + "type": "string" + }, + "payment_type": { + "$ref": "#/components/schemas/FundingDetailPaymentType" + }, + "routing_code": { + "type": "string" + }, + "routing_code_type": { + "$ref": "#/components/schemas/FundingDetailRoutingCodeType" + } + }, + "type": "object" + }, + "FundingDetailPaymentType": { + "description": "Status:\n * `swift_wire`: SWIFT wire\n * `local_rails`: Local scheme\n", + "enum": [ + "swift_wire", + "local_rails" + ], + "type": "string" + }, + "FundingDetailRoutingCodeType": { + "description": "Status:\n * `BIC`: bic_swift\n * `ACH`: ach_routing_code\n * `ABA`: aba\n * `ROUTING`: routing_code\n * `SORT_CODE`: sort_code\n", + "enum": [ + "BIC", + "ACH_ROUTING", + "ABA", + "ROUTING", + "SORT_CODE" + ], + "type": "string" + }, + "FundingWallet": { + "properties": { + "account_id": { + "format": "uuid", + "type": "string" + }, + "created_at": { + "format": "date-time", + "type": "string" + }, + "status": { + "$ref": "#/components/schemas/FundingWalletStatus" + } + }, + "required": [ + "account_id", + "status", + "created_at" + ], + "type": "object" + }, + "FundingWalletRecipientBank": { + "properties": { + "account_number": { + "description": "Bank account number.", + "type": "string" + }, + "bic_swift": { + "description": "BIC/SWIFT code", + "type": "string" + }, + "city": { + "description": "City", + "type": "string" + }, + "company_name": { + "type": "string" + }, + "country": { + "description": "Two-letter ISO country code.", + "type": "string" + }, + "created_at": { + "description": "Date the beneficiary record was created.", + "format": "date-time", + "type": "string" + }, + "currency": { + "description": "Currency in which money is held in the beneficiary's bank account. Three-digit currency code.", + "type": "string" + }, + "first_name": { + "type": "string" + }, + "iban": { + "description": "IBAN code", + "type": "string" + }, + "id": { + "format": "uuid", + "type": "string" + }, + "last_name": { + "type": "string" + }, + "payment_types": { + "items": { + "$ref": "#/components/schemas/FundingDetailPaymentType" + }, + "type": "array" + }, + "postal_code": { + "description": "Postal code", + "type": "string" + }, + "routing_code": { + "description": "Value for \"routing_code_type\".", + "type": "string" + }, + "routing_code_type": { + "description": "Local payment routing system.", + "type": "string" + }, + "state_or_province": { + "description": "State or province.", + "type": "string" + }, + "street_address": { + "type": "string" + }, + "updated_at": { + "description": "Date the beneficiary record was last updated.", + "format": "date-time", + "type": "string" + } + }, + "type": "object" + }, + "FundingWalletStatus": { + "description": "Status:\n * `active`: The funding wallet is ready\n * `pending`: The funding wallet is being processed\n * `disabled`: The funding wallet is disabled\n", + "enum": [ + "active", + "pending", + "disabled" + ], + "type": "string" + }, + "FundingWalletTransfer": { + "properties": { + "account_id": { + "format": "uuid", + "type": "string" + }, + "created_at": { + "format": "date-time", + "type": "string" + }, + "direction": { + "$ref": "#/components/schemas/FundingWalletTransferDirection" + }, + "fees": { + "items": { + "$ref": "#/components/schemas/TransferFee" + }, + "type": "array" + }, + "id": { + "format": "uuid", + "type": "string" + }, + "original_amount": { + "description": "The amount you should expect to receive, calculated as requested amount - fees", + "format": "decimal", + "type": "string" + }, + "original_currency": { + "description": "The currency of the withdrawn amount, 3-letter ISO code", + "type": "string" + }, + "payment_type": { + "$ref": "#/components/schemas/FundingDetailPaymentType" + }, + "requested_amount": { + "description": "The amount sent as part of the withdrawal creation", + "format": "decimal", + "type": "string" + }, + "status": { + "$ref": "#/components/schemas/FundingWalletTransferStatus" + }, + "updated_at": { + "format": "date-time", + "type": "string" + }, + "usd": { + "$ref": "#/components/schemas/Usd" + } + }, + "type": "object" + }, + "FundingWalletTransferDirection": { + "description": "Status:\n * `incoming`: incoming amount\n * `outgoing`: outgoing amount\n", + "enum": [ + "incoming", + "outgoing" + ], + "type": "string" + }, + "FundingWalletTransferStatus": { + "description": "Status:\n * `PENDING`: Created and waiting to be processed\n * `CANCELED`: Canceled\n * `FAILED`: Failed mostly due to technical reasons\n * `COMPLETE`: Transfer has settled\n", + "enum": [ + "PENDING", + "CANCELED", + "EXECUTED", + "FAILED", + "COMPLETE" + ], + "type": "string" + }, + "IFFee": { + "properties": { + "amount": { + "format": "decimal", + "type": "string" + }, + "id": { + "format": "uuid", + "type": "string" + }, + "type": { + "$ref": "#/components/schemas/IFFeeType" + } + }, + "required": [ + "id", + "amount", + "type" + ], + "type": "object" + }, + "IFFeeType": { + "description": "Status:\n * `partner`: The fee on a transfer that was allocated to the partner\n * `alpaca`: The fee on a transfer that was allocated to Alpaca\n", + "enum": [ + "partner", + "alpaca" + ], + "type": "string" + }, + "IPOEvent": { + "description": "Represents an IPO offering lifecycle event delivered over the IPO events streaming API (`/v2/events/ipos`).\n\nIPO events come in six `verb` types:\n- **`Offering`** - initial publication of an offering. The `payload` carries `name`, `available_to_order`, `ticker_symbol`, `min_price`, `max_price` (and optional `source`).\n- **`OfferingUpdate`** - update to a previously published offering. The `payload` shape is identical to `Offering`.\n- **`Prospectus`** - prospectus document is now available. The `payload` carries `prospectus_url` and `subject`.\n- **`SixtyMinMail`** - 60-minute pricing window has opened, no new orders accepted. The `payload` carries `sixty_minute_expiration_time` and `subject`.\n- **`Allocation`** - final allocation result for a specific account. The `payload` carries `cusip_id`, `final_price`, `allocated_shares`, `allocated_amount`, `subject`. **This is the only verb that is account-scoped**: `account_id` and `correspondent` are populated.\n- **`OfferingCancellation`** - offering was cancelled. **No `payload` is present.**\n\nSee the example payloads below for a concrete example per verb.\n", + "properties": { + "account_id": { + "description": "The account this event applies to. Only populated for `Allocation` events; omitted for system-wide verbs (`Offering`, `OfferingUpdate`, `Prospectus`, `SixtyMinMail`, `OfferingCancellation`).\n", + "format": "uuid", + "type": "string" + }, + "at": { + "description": "Timestamp the event was emitted by the streaming service.", + "format": "date-time", + "type": "string" + }, + "correspondent": { + "description": "The correspondent that owns the account. Only populated for `Allocation` events.\n", + "example": "COR1", + "type": "string" + }, + "event_id": { + "description": "Lexically sortable, monotonically increasing identifier for this event. Use this value with `since_id`/`until_id` to resume the stream from a known point.", + "format": "ulid", + "type": "string" + }, + "offering_reference": { + "description": "The IPO offering this event refers to. Use this value with `GET /v1/ipos/{offering_reference}` to fetch the full offering metadata.\n", + "example": "FI111225", + "type": "string" + }, + "payload": { + "description": "Verb-specific payload. The shape varies by `verb` - see schema description and `components/examples` for the structure of each. **Omitted entirely for `OfferingCancellation`.**\n", + "type": "object" + }, + "received_at": { + "description": "Timestamp the upstream event was first received by Alpaca's IPO ingestion pipeline (before fanout).", + "format": "date-time", + "type": "string" + }, + "verb": { + "description": "The IPO event type. Determines the shape of `payload`.", + "enum": [ + "Allocation", + "Offering", + "OfferingUpdate", + "Prospectus", + "SixtyMinMail", + "OfferingCancellation" + ], + "type": "string" + } + }, + "required": [ + "event_id", + "at", + "verb", + "offering_reference", + "received_at" + ], + "title": "IPOEvent", + "type": "object", + "x-examples": { + "Allocation": { + "account_id": "6221407b-32b2-3e6b-a7fe-cd79e1b68ba3", + "at": "2026-03-20T00:15:42.318901Z", + "correspondent": "COR1", + "event_id": "01J9RTPZGHKVB8M3X4F5J7LCQ9", + "offering_reference": "FI111225", + "payload": { + "allocated_amount": "1764", + "allocated_shares": "42", + "cusip_id": "123456789", + "final_price": "42", + "subject": "IPO Allocation: Example Corp" + }, + "received_at": "2026-03-20T00:15:42.103572Z", + "verb": "Allocation" + }, + "Offering": { + "at": "2026-03-20T12:24:58.807230Z", + "event_id": "01J9RPMV5TKB8WX3M4F1KZ7QH2", + "offering_reference": "FI111225", + "payload": { + "available_to_order": "Available", + "max_price": "42", + "min_price": "42", + "name": "Example Corp", + "source": "example source", + "ticker_symbol": "EXMP" + }, + "received_at": "2026-03-20T12:24:58.500000Z", + "verb": "Offering" + }, + "OfferingCancellation": { + "at": "2026-03-20T12:24:52.325158Z", + "event_id": "01J9RVB6Y4ZK8M3N7QD2WX1RFP", + "offering_reference": "FI111225", + "received_at": "2026-03-20T12:24:52.110430Z", + "verb": "OfferingCancellation" + }, + "OfferingUpdate": { + "at": "2026-03-20T15:42:11.118274Z", + "event_id": "01J9RQ5HNZQK7M3RDVJ8XBPCT1", + "offering_reference": "FI111225", + "payload": { + "available_to_order": "Available", + "max_price": "44", + "min_price": "40", + "name": "Example Corp", + "source": "example source", + "ticker_symbol": "EXMP" + }, + "received_at": "2026-03-20T15:42:10.892140Z", + "verb": "OfferingUpdate" + }, + "Prospectus": { + "at": "2026-03-19T23:26:46.853262Z", + "event_id": "01J9RSGD8JR2A4K7MZNTPVQ5XW", + "offering_reference": "FI111225", + "payload": { + "prospectus_url": "https://example.com/prospectus/example-corp.pdf", + "subject": "IPO Prospectus: Example Corp" + }, + "received_at": "2026-03-19T23:26:46.612880Z", + "verb": "Prospectus" + }, + "SixtyMinMail": { + "at": "2026-03-19T22:50:11.729329Z", + "event_id": "01J9RT9P3WYH8KCJM2LFQX5ZRD", + "offering_reference": "FI111225", + "payload": { + "sixty_minute_expiration_time": "2026-03-19T23:50:11.729329Z", + "subject": "IPO 60 Minute Mail: Example Corp" + }, + "received_at": "2026-03-19T22:50:11.512640Z", + "verb": "SixtyMinMail" + } + } + }, + "IPOOffering": { + "description": "An IPO (Initial Public Offering) offering exposed via the IPO discovery REST API. Returned by `GET /v1/ipos` and `GET /v1/ipos/{offering_reference}`.\n\nMost IPO lifecycle changes (new offerings, prospectus availability, 60-minute mail, allocations, cancellations) are pushed in real time over the [IPO Events Stream](#operation/subscribeToIPOEventsSSE).\n", + "properties": { + "anticipated_shares": { + "description": "Anticipated total share count for the offering, when known.", + "format": "int64", + "type": "integer" + }, + "availability": { + "description": "Whether the offering is currently accepting orders.\n- `available` - accepting new orders.\n- `not_available` - not yet open for orders.\n- `closed` - no longer accepting orders.\n\nNote: the IPO Events Stream uses a different casing for the analogous `payload.available_to_order` field on `Offering`/`OfferingUpdate` events (`Available`, `NotAvailable`, `Closed`).\n", + "enum": [ + "available", + "not_available", + "closed" + ], + "example": "available", + "type": "string" + }, + "cusip_id": { + "description": "CUSIP identifier of the offering.", + "example": "123456789", + "type": "string" + }, + "description": { + "description": "A longer human-readable description of the offering, when available.", + "type": "string" + }, + "ipo_reference": { + "description": "The unique offering identifier used across the IPO Events Stream and `/v1/ipos/{offering_reference}`. Note that this value is also used as the path parameter, where it is named `offering_reference`.\n", + "example": "FI111225", + "type": "string" + }, + "logo_small": { + "description": "URL to a small logo asset for the issuer.", + "type": "string" + }, + "max_price": { + "description": "Upper bound of the indicated price range.", + "example": "42", + "format": "decimal", + "type": "string" + }, + "max_ticket_size": { + "description": "Maximum allowed order amount.", + "type": "string" + }, + "min_price": { + "description": "Lower bound of the indicated price range.", + "example": "42", + "format": "decimal", + "type": "string" + }, + "min_ticket_size": { + "description": "Minimum allowed order amount.", + "type": "string" + }, + "name": { + "description": "The official name of the offering.", + "example": "Example Corp", + "type": "string" + }, + "no_new_orders": { + "description": "When `true`, the offering is in its 60-minute pricing window and is not accepting new orders. This mirrors the `SixtyMinMail` event on the IPO Events Stream.\n", + "example": false, + "type": "boolean" + }, + "offering_type": { + "description": "The type of offering. Currently always `IPO`.", + "example": "IPO", + "type": "string" + }, + "prospectus_url": { + "description": "URL to the prospectus document. Mirrors the `prospectus_url` payload of the corresponding `Prospectus` event on the IPO Events Stream.\n", + "type": "string" + }, + "settlement_date": { + "description": "Anticipated settlement date.", + "format": "date", + "type": "string" + }, + "ticker_symbol": { + "description": "The ticker symbol that will be used once the security begins trading on the secondary market.", + "example": "EXMP", + "type": "string" + }, + "trade_date": { + "description": "Anticipated first trading date on the secondary market.", + "format": "date", + "type": "string" + }, + "underwriters": { + "description": "List of underwriter names participating in the offering.", + "items": { + "type": "string" + }, + "type": "array" + }, + "unit_step_size": { + "description": "The minimum increment in which order quantities can be specified.", + "type": "string" + } + }, + "required": [ + "name", + "ipo_reference", + "offering_type", + "availability", + "max_price", + "min_price", + "no_new_orders" + ], + "title": "IPOOffering", + "type": "object" + }, + "IPOOfferingListResponse": { + "description": "Wrapper response returned by `GET /v1/ipos`.", + "properties": { + "data": { + "description": "The page of IPO offerings matching the query.", + "items": { + "$ref": "#/components/schemas/IPOOffering" + }, + "type": "array" + }, + "next_page_token": { + "description": "Opaque cursor for the next page of results. When `null`, there are no further pages. Pass this value back as the `page_token` query parameter to fetch the next page.\n", + "nullable": true, + "type": "string" + } + }, + "required": [ + "data", + "next_page_token" + ], + "title": "IPOOfferingListResponse", + "type": "object" + }, + "IPOOfferingResponse": { + "description": "Wrapper response returned by `GET /v1/ipos/{offering_reference}`.", + "properties": { + "data": { + "$ref": "#/components/schemas/IPOOffering" + } + }, + "required": [ + "data" + ], + "title": "IPOOfferingResponse", + "type": "object" + }, + "Identity": { + "description": "Identity is the model to provide account owner's identity information.\n", + "example": { + "country_of_birth": "AUS", + "country_of_citizenship": "AUS", + "country_of_tax_residence": "USA", + "date_of_birth": "1990-01-01", + "family_name": "Doe", + "funding_source": [ + "employment_income" + ], + "given_name": "John", + "tax_id": "666-55-4321", + "tax_id_type": "USA_SSN" + }, + "properties": { + "annual_income_max": { + "description": "The upper bound of the user's annual income.", + "type": "number" + }, + "annual_income_min": { + "description": "The lower bound of the user's annual income.", + "type": "number" + }, + "country_of_birth": { + "description": "[ISO 3166-1 alpha-3](https://www.iso.org/iso-3166-country-codes.html).\n", + "example": "USA", + "type": "string" + }, + "country_of_citizenship": { + "description": "[ISO 3166-1 alpha-3](https://www.iso.org/iso-3166-country-codes.html).\n", + "example": "USA", + "type": "string" + }, + "country_of_tax_residence": { + "description": "[ISO 3166-1 alpha-3](https://www.iso.org/iso-3166-country-codes.html).\n", + "example": "USA", + "type": "string" + }, + "date_of_birth": { + "description": "The date of birth in \"YYYY-MM-DD\" format.", + "example": "1990-01-01", + "format": "date", + "type": "string" + }, + "date_of_departure_from_usa": { + "description": "Required if `visa_type` = B1 or B2", + "format": "date", + "type": "string" + }, + "family_name": { + "description": "The last name (surname) of the user.", + "example": "Doe", + "type": "string" + }, + "funding_source": { + "description": "Can be one or more of the following: `employment_income`, `investments`, `inheritance`, `business_income`, `savings`, `family`.", + "items": { + "enum": [ + "employment_income", + "investments", + "inheritance", + "business_income", + "savings", + "family" + ], + "type": "string" + }, + "type": "array" + }, + "given_name": { + "description": "The first/given name of the user.", + "example": "John", + "type": "string" + }, + "investment_experience_with_options": { + "description": "The user's level of expertise and familiarity with investing in Options.\n", + "enum": [ + "none", + "1_to_5_years", + "over_5_years" + ], + "type": "string" + }, + "investment_experience_with_stocks": { + "description": "The user's level of expertise and familiarity with investing in US Equities.\n", + "enum": [ + "none", + "1_to_5_years", + "over_5_years" + ], + "type": "string" + }, + "investment_objective": { + "deprecated": true, + "description": "The user's investment objective. This field is deprecated. Please use the top level `investment_objective` field.\n", + "enum": [ + "generate_income", + "preserve_wealth", + "market_speculation", + "growth", + "balance_preserve_wealth_with_growth" + ], + "type": "string" + }, + "investment_time_horizon": { + "deprecated": true, + "description": "The expected period of time the user plan to invest to achieve his/her financial goal(s). This field is deprecated. Please use the top level `investment_time_horizon` field.\n", + "enum": [ + "less_than_1_year", + "1_to_2_years", + "3_to_5_years", + "6_to_10_years", + "more_than_10_years" + ], + "type": "string" + }, + "liquid_net_worth_max": { + "description": "The upper bound of the user's liquid net worth.", + "type": "number" + }, + "liquid_net_worth_min": { + "description": "The lower bound of the user's liquid net worth.", + "type": "number" + }, + "liquidity_needs": { + "deprecated": true, + "description": "The user's ability to quickly and easily convert all or part of their investments in this account to cash without significant loss in value. This field is deprecated. Please use the top level `liquidity_needs` field.\n", + "enum": [ + "very_important", + "important", + "somewhat_important", + "does_not_matter" + ], + "type": "string" + }, + "marital_status": { + "description": "The marital status of the user.\n", + "enum": [ + "SINGLE", + "MARRIED", + "DIVORCED", + "WIDOWED" + ], + "type": "string" + }, + "middle_name": { + "description": "The middle name of the user.", + "type": "string" + }, + "number_of_dependents": { + "description": "The number of dependents the user has.\n", + "type": "integer" + }, + "permanent_resident": { + "description": "Only used to collect permanent residence status in the USA.", + "type": "boolean" + }, + "risk_tolerance": { + "deprecated": true, + "description": "The user's investment risk tolerance. This field is deprecated. Please use the top level `risk_tolerance` field.\n", + "enum": [ + "conservative", + "moderate", + "significant_risk" + ], + "type": "string" + }, + "tax_id": { + "description": "Required if tax_id_type is set.", + "example": "666-55-4321", + "type": "string" + }, + "tax_id_type": { + "$ref": "#/components/schemas/TaxIdType" + }, + "total_net_worth_max": { + "description": "The upper bound of the user's total net worth.", + "type": "number" + }, + "total_net_worth_min": { + "description": "The lower bound of the user's total net worth.", + "type": "number" + }, + "visa_expiration_date": { + "description": "Required if `visa_type` is set.", + "format": "date", + "type": "string" + }, + "visa_type": { + "description": "Only used to collect visa types for users residing in the USA.", + "type": "string" + } + }, + "required": [ + "given_name", + "family_name", + "date_of_birth", + "tax_id_type", + "country_of_tax_residence", + "funding_source" + ], + "type": "object" + }, + "InstantFunding": { + "properties": { + "account_no": { + "type": "string" + }, + "amount": { + "format": "decimal", + "type": "string" + }, + "created_at": { + "format": "date-time", + "type": "string" + }, + "deadline": { + "format": "date", + "type": "string" + }, + "fees": { + "items": { + "$ref": "#/components/schemas/IFFee" + }, + "type": "array" + }, + "id": { + "format": "uuid", + "type": "string" + }, + "interests": { + "items": { + "$ref": "#/components/schemas/Interest" + }, + "type": "array" + }, + "remaining_payable": { + "format": "decimal", + "type": "string" + }, + "source_account_no": { + "type": "string" + }, + "status": { + "$ref": "#/components/schemas/InstantFundingStatus" + }, + "system_date": { + "format": "date", + "type": "string" + }, + "total_interest": { + "format": "decimal", + "type": "string" + } + }, + "required": [ + "id", + "amount", + "account_no", + "source_account_no", + "status", + "system_date", + "deadline", + "created_at", + "total_interest", + "remaining_payable", + "interests", + "fees" + ], + "type": "object" + }, + "InstantFundingStatus": { + "description": "Status:\n * `PENDING`: Created and waiting to be processed\n * `CANCELED`: Canceled\n * `EXECUTED`: All fees (including Partner fee) are transacted\n * `FAILED`: Failed mostly due to technical reasons\n * `COMPLETED`: All transactions are settled\n", + "enum": [ + "PENDING", + "CANCELED", + "EXECUTED", + "FAILED", + "COMPLETED" + ], + "type": "string" + }, + "Interest": { + "properties": { + "amount": { + "format": "decimal", + "type": "string" + }, + "created_at": { + "format": "date-time", + "type": "string" + }, + "date": { + "format": "date", + "type": "string" + }, + "id": { + "format": "uuid", + "type": "string" + }, + "reconciled_at": { + "format": "date-time", + "type": "string" + }, + "status": { + "$ref": "#/components/schemas/InstantFundingStatus" + } + }, + "required": [ + "id", + "date", + "amount", + "status", + "created_at", + "reconciled_at" + ], + "type": "object" + }, + "IssueOAuthTokenResponse": { + "description": "", + "properties": { + "access_token": { + "description": "OAuth token", + "type": "string" + }, + "scope": { + "description": "Token's scope", + "type": "string" + }, + "token_type": { + "description": "Always `Bearer`", + "enum": [ + "Bearer" + ], + "example": "Bearer", + "type": "string" + } + }, + "required": [ + "access_token", + "token_type", + "scope" + ], + "type": "object", + "x-examples": { + "example-1": { + "access_token": "87586f14-c3f4-4912-b107-f75bc17ff87a", + "scope": "general", + "token_type": "Bearer" + } + } + }, + "JITAssetClass": { + "description": "Values:\n * `crypto`: Used to identify a crypto only account\n * `us_equity`: Used to identify an account that trades US equities, or both crypto and\n US equities.\n", + "enum": [ + "crypto", + "us_equity" + ], + "type": "string" + }, + "JITLedgerAccount": { + "properties": { + "created_at": { + "description": "Creation time in UNIX format", + "type": "string" + }, + "id": { + "description": "The ledger ID", + "type": "string" + }, + "ledger_name": { + "description": "The ledger name", + "type": "string" + }, + "status": { + "type": "string" + } + }, + "title": "JITLedgerAccount", + "type": "object", + "x-examples": { + "Example 1": { + "created_at": "1653532627", + "id": "2896b9e2-3198-44cd-a08e-4cc4079aee33", + "ledger_name": "Securities JIT IN", + "status": "active" + } + } + }, + "JITReport": { + "description": "JIT Securities reports are made available through the API and can be accessed within the hour after 11:30 PM EST on the trade date (T+0). The reports communicate transaction-level details as well as overall settlement amounts, transfer direction, and payment timing.", + "properties": { + "detail": { + "description": "Contains all activities that impact cash throughout the trading session including executed trades, trading fees, and corporate actions that involve cash allocations.\n\ncontent-type = application/csv", + "type": "string" + }, + "net_payment": { + "description": "Highlights the net amount due to Alpaca by settlement or to the partner on the date of settlement in a formalized invoice format.\n\ncontent-type = application/pdf", + "type": "string" + }, + "net_payment_final": { + "description": "Includes additional information to account for T+0 and T+1 settling activity to clarify settlement journaling reconciliation. This report is generated after trading session close on T+1.\n\ncontent-type = application/pdf", + "type": "string" + }, + "net_summary": { + "description": "Consists of three columns and a single row, which lists the net money movement to or from Alpaca for T0, T1, and T2.\n\ncontent-type = application/csv", + "type": "string" + }, + "obligation": { + "description": "Lists of all open obligations towards the partner that are to be settled.\n\ncontent-type = application/csv", + "type": "string" + } + }, + "title": "JITReport", + "type": "object" + }, + "JITReportDownloadURL": { + "properties": { + "expires_at": { + "description": "Timestamp when the signed URL expires.", + "format": "date-time", + "type": "string" + }, + "filename": { + "description": "Name of the generated report file.", + "type": "string" + }, + "url": { + "description": "Temporary signed URL used to download the report.", + "format": "uri", + "type": "string" + } + }, + "required": [ + "url", + "filename", + "expires_at" + ], + "type": "object" + }, + "JNLC": { + "description": "Journal information specific to cash transfers. This field is required for `Journal`s with an `entry_type` of `jnlc` (cash transfers), but will be null for those with `jnls` (securities transfers).", + "example": { + "entry_type": "JNLC", + "from_account": "8fjkjn-4483-4199-840f-6c5fe0b7ca24", + "id": "f45g67h8-d1fc-4136-aa4f-cf4460aecdfc", + "net_amount": "115.5", + "status": "pending", + "to_account": "3gtt65jd-6f2a-433c-8c33-17b66b8941fa" + }, + "properties": { + "currency": { + "description": "Currency denomination of the journal. USD by default.", + "type": "string" + }, + "description": { + "description": "ID the amount goes to. Only valid for JNLC journals. Null for JNLS.", + "type": "string" + }, + "entry_type": { + "description": "JNLC", + "type": "string" + }, + "net_amount": { + "description": "Only valid for JNLC journals. Null for JNLS.", + "format": "decimal", + "type": "string" + }, + "transmitter_account_number": { + "description": "Only valid for JNLC journals. Null for JNLS.max 255 characters", + "type": "string" + }, + "transmitter_address": { + "description": "Only valid for JNLC journals. Null for JNLS.max 255 characters", + "type": "string" + }, + "transmitter_financial_institution": { + "description": "Only valid for JNLC journals. Null for JNLS.max 255 characters", + "type": "string" + }, + "transmitter_name": { + "description": "Only valid for JNLC journals. Null for JNLS. Max 255 characters.", + "type": "string" + }, + "transmitter_timestamp": { + "description": "Only valid for JNLC journals. Null for JNLS.", + "format": "date-time", + "type": "string" + } + }, + "required": [ + "entry_type", + "net_amount" + ], + "title": "JNLC", + "type": "object", + "x-examples": {} + }, + "JNLCActivityV2": { + "allOf": [ + { + "$ref": "#/components/schemas/CommonJournalActivityV2" + } + ], + "description": "Journal entry (cash)", + "type": "object" + }, + "JNLS": { + "properties": { + "currency": { + "description": "Currency denomination of the journal. USD by default.", + "type": "string" + }, + "description": { + "type": "string" + }, + "entry_type": { + "description": "JNLS", + "type": "string" + }, + "from_account": { + "description": "The account ID that initiates the journal - account_status must equal to ACTIVE or CLOSE", + "type": "string" + }, + "id": { + "description": "The journal ID", + "type": "string" + }, + "price": { + "description": "The price of the security journaled", + "type": "string" + }, + "qty": { + "description": "The quantity of the securities journaled", + "type": "string" + }, + "settle_date": { + "description": "Date string in \"%Y-%m-%d\" format", + "type": "string" + }, + "status": { + "$ref": "#/components/schemas/JournalStatus" + }, + "symbol": { + "description": "The symbol of the security journaled", + "type": "string" + }, + "system_date": { + "description": "Date string in \"%Y-%m-%d\" format", + "type": "string" + }, + "to_account": { + "description": "The account ID that received the journal - account_status must equal to ACTIVE", + "type": "string" + } + }, + "required": [ + "entry_type" + ], + "type": "object", + "x-examples": { + "Example 1": { + "description": "this is a test journal", + "entry_type": "JNLS", + "from_account": "8k4f3d-4483-4199-840f-6c5fe0b7ca24", + "id": "f45g67h8-d1fc-4136-aa4f-cf4460aecdfc", + "price": "128.23", + "qty": "0.5", + "settle_date": "2020-12-24", + "status": "executed", + "symbol": "AAPL", + "system_date": "2020-12-24", + "to_account": "g7h7rg66-6f2a-433c-8c33-17b66b8941fa" + } + } + }, + "JNLSActivityV2": { + "allOf": [ + { + "$ref": "#/components/schemas/CommonJournalActivityV2" + }, + { + "properties": { + "symbol": { + "description": "The symbol of the security involved with the activity", + "type": "string" + } + }, + "required": [ + "symbol" + ], + "type": "object" + } + ], + "description": "Journal entry (stock)", + "type": "object" + }, + "Journal": { + "allOf": [ + { + "properties": { + "created_at": { + "format": "date-time", + "type": "string" + }, + "entry_type": { + "$ref": "#/components/schemas/JournalEntryType" + }, + "from_account": { + "description": "account ID the shares go from", + "format": "uuid", + "type": "string" + }, + "id": { + "description": "journal ID", + "format": "uuid", + "type": "string" + }, + "settle_date": { + "format": "date", + "type": "string" + }, + "status": { + "$ref": "#/components/schemas/JournalStatus" + }, + "to_account": { + "description": "account ID the shares go to", + "format": "uuid", + "type": "string" + }, + "transmitter_info": { + "$ref": "#/components/schemas/TransmitterInfo" + } + }, + "required": [ + "id", + "entry_type", + "from_account", + "to_account", + "settle_date" + ], + "type": "object" + }, + { + "oneOf": [ + { + "$ref": "#/components/schemas/JNLS" + }, + { + "$ref": "#/components/schemas/JNLC" + } + ] + } + ], + "description": "Represents a cash or security transfer between accounts, as specified by the `entry_type` parameter.", + "discriminator": { + "mapping": { + "JNLC": "#/components/schemas/JNLC", + "JNLS": "#/components/schemas/JNLS" + }, + "propertyName": "entry_type" + }, + "example": { + "entry_type": "JNLS", + "from_account": "8fjkjn-4483-4199-840f-6c5fe0b7ca24", + "id": "h7h5g33f-ef01-4458-9a4b-9598727a406f", + "price": "128.23", + "qty": "2", + "settle_date": "2020-12-24", + "status": "executed", + "symbol": "AAPL", + "to_account": "3gtt65jd-6f2a-433c-8c33-17b66b8941fa" + }, + "title": "Journal", + "x-examples": { + "example": { + "entry_type": "JNLS", + "from_account": "8fjkjn-4483-4199-840f-6c5fe0b7ca24", + "id": "h7h5g33f-ef01-4458-9a4b-9598727a406f", + "price": "128.23", + "qty": "2", + "settle_date": "2020-12-24", + "status": "executed", + "symbol": "AAPL", + "to_account": "3gtt65jd-6f2a-433c-8c33-17b66b8941fa" + }, + "example-pending": { + "description": "", + "entry_type": "JNLC", + "from_account": "3dcb795c-3ccc-402a-abb9-07e26a1b1326", + "id": "6d2cba43-cb57-4534-9603-a6e159167c0a", + "net_amount": "645", + "price": "0", + "qty": null, + "settle_date": "2022-02-17", + "status": "pending", + "symbol": null, + "system_date": "2022-02-17", + "to_account": "2a87c088-ffb6-472b-a4a3-cd9305c8605c" + }, + "example-queued": { + "description": "", + "entry_type": "JNLC", + "from_account": "3dcb795c-3ccc-402a-abb9-07e26a1b1326", + "id": "6d2cba43-cb57-4534-9603-a6e159167c0a", + "net_amount": "645", + "price": "0", + "qty": null, + "settle_date": null, + "status": "queued", + "symbol": "", + "system_date": null, + "to_account": "2a87c088-ffb6-472b-a4a3-cd9305c8605c" + } + } + }, + "JournalEntryType": { + "description": "This enum represents the various kinds of Journal alpaca supports.\n\nCurrent values are:\n\n- **JNLC**\n\n Journal Cash between accounts\n\n- **JNLS**\n\n Journal Securities between accounts", + "enum": [ + "JNLC", + "JNLS" + ], + "title": "", + "type": "string" + }, + "JournalStatus": { + "description": "Represents the status that a Journal instance can be in.\n\n**Current Values**\n\nqueued\tJournal in queue to be processed. Journal is not processed yet.\n\nsent_to_clearing\tJournal sent to be processed by Alpaca's booking system. The journal is not processed yet.\n\npending\t Journal pending to be processed as it requires manual approval from Alpaca operations (for example due to hitting JNLC daily limits).\n\nexecuted\tJournal executed and balances updated for both sides of \nthe journal transaction. This is not a final status, journals can be reversed if there is an error.\n\nrejected\tJournal rejected. Please try again.\n\ncanceled\tJournal canceled. This is a **FINAL** status.\n\nrefused\tJournal refused. Please try again.\n\ndeleted\tJournal deleted. This is a **FINAL** status.\n\ncorrect\tJournal is corrected. Previously executed journal is cancelled and a new journal is corrected amount is created. This is a **FINAL** status.", + "enum": [ + "pending", + "canceled", + "executed", + "queued", + "rejected", + "deleted", + "refused", + "sent_to_clearing", + "correct" + ], + "type": "string" + }, + "JournalStatusEvent": { + "description": "Represents a change in a Journal's status, sent over the events streaming api.\n", + "properties": { + "at": { + "description": "Timestamp of event", + "format": "date-time", + "minLength": 1, + "type": "string" + }, + "entry_type": { + "$ref": "#/components/schemas/JournalEntryType" + }, + "event_id": { + "description": "Monotonically increasing 64bit integer", + "type": "integer" + }, + "event_ulid": { + "description": "lexically sortable, monotonically increasing character array", + "format": "ulid", + "type": "string" + }, + "journal_id": { + "description": "The UUID of the related Journal", + "format": "uuid", + "type": "string" + }, + "status_from": { + "$ref": "#/components/schemas/JournalStatus" + }, + "status_to": { + "$ref": "#/components/schemas/JournalStatus" + } + }, + "required": [ + "at", + "entry_type", + "event_id", + "event_ulid", + "journal_id", + "status_from", + "status_to" + ], + "title": "JournalStatusEvent", + "type": "object", + "x-examples": { + "example-1": { + "at": "2021-05-07T10:28:23.163857Z", + "entry_type": "JNLC", + "event_id": 1406, + "event_ulid": "01F535Y1FVY8WZHE763HCNS8SZ", + "journal_id": "2f144d2a-91e6-46ff-8e37-959a701cc58d", + "status_from": "", + "status_to": "queued" + } + } + }, + "JournalStatusEventV2": { + "description": "Represents a change in a Journal's status, sent over the events streaming api.\n", + "properties": { + "at": { + "description": "Timestamp of event", + "format": "date-time", + "minLength": 1, + "type": "string" + }, + "batch_error_message": { + "description": "If journal submitted in batch (that is, idempotency_key_type is batch), this is the error message of the batch journal execution.", + "type": "string" + }, + "description": { + "description": "The description of the journal event when submitted", + "example": "Journal of cash between accounts", + "type": "string" + }, + "entry_type": { + "$ref": "#/components/schemas/JournalEntryType" + }, + "event_id": { + "description": "lexically sortable, monotonically increasing character array", + "format": "ulid", + "type": "string" + }, + "idempotency_key": { + "description": "The idempotency key of the journal event", + "format": "uuid", + "type": "string" + }, + "idempotency_key_type": { + "description": "The type of idempotency key", + "enum": [ + "single", + "batch" + ], + "type": "string" + }, + "journal_id": { + "description": "The UUID of the related Journal", + "format": "uuid", + "type": "string" + }, + "status_from": { + "$ref": "#/components/schemas/JournalStatus" + }, + "status_to": { + "$ref": "#/components/schemas/JournalStatus" + } + }, + "required": [ + "at", + "entry_type", + "event_id", + "journal_id", + "status_from", + "status_to" + ], + "title": "JournalStatusEvent", + "type": "object", + "x-examples": { + "example-1": { + "at": "2021-05-07T10:28:23.163857Z", + "description": "Journal of cash between accounts", + "entry_type": "JNLC", + "event_id": "01F535Y1FVY8WZHE763HCNS8SZ", + "journal_id": "2f144d2a-91e6-46ff-8e37-959a701cc58d", + "status_from": "", + "status_to": "queued" + }, + "example-idempotency-key": { + "at": "2021-05-07T10:28:23.163857Z", + "description": "Journal of cash between accounts", + "entry_type": "JNLC", + "event_id": "01F535Y1FVY8WZHE763HCNS8SZ", + "idempotency_key": "72bdf18f-7410-43ec-b4e8-b6e41c5c0160", + "idempotency_key_type": "single", + "journal_id": "2f144d2a-91e6-46ff-8e37-959a701cc58d", + "status_from": "queued", + "status_to": "executed" + } + } + }, + "KYCResultType": { + "description": "IDENTITY_VERIFICATION\tIdentity needs to be verified\n\nTAX_IDENTIFICATION\tTax ID number needs to be verified\n\nADDRESS_VERIFICATION\tAddress needs to be verified\n\nDATE_OF_BIRTH\tDate of birth needs to be verified\n\nINVALID_IDENTITY_PASSPORT\tIdentity needs to be verified via a \ngovernment issued ID. This is commonly used in conjuction with OTHER to describe the exact document needed.\n\nSELFIE_VERIFICATION\tIdentity needs to be verified via a live selfie of the account owner\n\nPEP\tFurther information needs to be submitted if account owner is politically exposed person\n\nFAMILY_MEMBER_PEP\tFurther information needs to be submitted if family member is a politically exposed person\n\nCONTROL_PERSON\tFurther information needs to be submitted if account owner is a control person\n\nAFFILIATED\tFurther information needs to be submitted if account owner is affiliated to finra or an exchange\n\nVISA_TYPE_OTHER\tFurther information needs to be submitted about account owner's visa\n\nW8BEN_CORRECTION\tIdenfitying information submitted by the user was incorrect so a new, corrected, W8BEN needs to be submitted\nCOUNTRY_NOT_SUPPORTED\tThe account owner's country of tax residence is not supported by our KYC providers. In this case, we'll manully perform KYC on the user\n\nWATCHLIST_HIT\tResults from the watchlist screening need further investigation before account opening. No action is needed from the user\n\nOTHER\tA custom message will be sent to describe exactly what is needed from the account owner. The message will be displayed in the additional_information attribute.\n\nOTHER_PARTNER\tA custom message will be sent to relay information relevant only to the partner. The message will be displayed in the additional_information attribute.", + "title": "KYCResultType", + "type": "string" + }, + "KYCResults": { + "description": "Hold information about the result of KYC. Please see the documentation [here](https://alpaca.markets/docs/api-references/broker-api/events/#kyc-results) for more indepth details", + "properties": { + "accept": { + "$ref": "#/components/schemas/KYCResultType" + }, + "additional_information": { + "description": "Used to display a custom message.", + "type": "string" + }, + "indeterminate": { + "$ref": "#/components/schemas/KYCResultType" + }, + "reject": { + "$ref": "#/components/schemas/KYCResultType" + }, + "summary": { + "description": "Either `pass` or `fail`. Used to indicate if KYC has completed and passed or not. This field is used for internal purposes only.", + "type": "string" + } + }, + "type": "object" + }, + "ListAPRTiersResponse": { + "description": "Response to a successful request for a list of APR tiers.\n", + "properties": { + "apr_tiers": { + "description": "All configured APR tiers available for assignment to accounts", + "items": { + "$ref": "#/components/schemas/APRTier" + }, + "type": "array" + } + }, + "type": "object" + }, + "ListFPSLLoansResponse": { + "description": "Response to a successful request for a list of FPSL loans.\n", + "properties": { + "loans": { + "description": "All FPSL loans matching the filter criteria", + "items": { + "$ref": "#/components/schemas/FPSLLoan" + }, + "type": "array" + }, + "next_page_token": { + "description": "The token to use to retrieve the next page of results. If `null`, there are no more pages.\n", + "nullable": true, + "type": "string" + } + }, + "required": [ + "loans", + "next_page_token" + ], + "type": "object" + }, + "ListFundingDetails": { + "properties": { + "funding_details": { + "items": { + "$ref": "#/components/schemas/FundingDetail" + }, + "type": "array" + } + }, + "type": "object" + }, + "ListSortBy": { + "enum": [ + "created_at", + "amount", + "deadline" + ], + "type": "string" + }, + "ListTransfersResponse": { + "properties": { + "transfers": { + "items": { + "$ref": "#/components/schemas/FundingWalletTransfer" + }, + "type": "array" + } + }, + "type": "object" + }, + "MAActivityV2": { + "allOf": [ + { + "$ref": "#/components/schemas/CommonCaActivityV2" + }, + { + "$ref": "#/components/schemas/CommonMAActivityV2" + }, + { + "properties": { + "acquiree_qty": { + "description": "Quantity of the acquiree", + "type": "string" + }, + "acquirer_qty": { + "description": "Quantity of the acquirer", + "type": "string" + }, + "cash_payout": { + "description": "The cash payout", + "type": "string" + }, + "cash_rate": { + "description": "The cash rate", + "type": "string" + } + }, + "required": [ + "acquiree_qty" + ], + "type": "object" + } + ], + "description": "Merger and acquisition", + "type": "object" + }, + "MLegOrderLeg": { + "description": "Represents an individual leg of a multileg options order.", + "properties": { + "position_intent": { + "$ref": "#/components/schemas/PositionIntent" + }, + "ratio_qty": { + "description": "proportional quantity of this leg in relation to the overall multileg order qty", + "example": "1", + "type": "string" + }, + "side": { + "$ref": "#/components/schemas/OrderSide" + }, + "symbol": { + "description": "symbol or asset ID to identify the asset to trade", + "type": "string" + } + }, + "required": [ + "symbol", + "ratio_qty" + ], + "title": "MLegOrderLeg", + "type": "object" + }, + "NCActivityV2": { + "allOf": [ + { + "$ref": "#/components/schemas/CommonCaActivityV2" + }, + { + "$ref": "#/components/schemas/CommonNCActivityV2" + }, + { + "properties": { + "position_qty": { + "description": "The position quantity", + "type": "string" + } + }, + "required": [ + "position_qty" + ], + "type": "object" + } + ], + "description": "Name change", + "type": "object" + }, + "NextPageToken": { + "description": "Use this token in your next API call to paginate through the dataset and retrieve the next page of results. A null token indicates there are no more data to fetch.\n", + "example": "MTAwMA==", + "nullable": true, + "type": "string" + }, + "NonTradeActivity": { + "properties": { + "activity_sub_type": { + "$ref": "#/components/schemas/ActivitySubType" + }, + "created_at": { + "description": "Valid only for non-trading activity types. Null for trading activites.", + "example": "2021-05-10T14:01:04.650275Z", + "format": "date-time", + "type": "string" + }, + "currency": { + "description": "Currency denomination of the activity. USD by default.", + "type": "string" + }, + "cusip": { + "description": "Valid only for non-trading activity types. Null for trading activites.", + "example": "037833100", + "type": "string" + }, + "date": { + "description": "Valid only for non-trading activity types. Null for trading activites.", + "example": "2021-05-21", + "format": "date", + "type": "string" + }, + "description": { + "description": "Valid only for non-trading activity types. Null for trading activites.", + "example": "Example description", + "type": "string" + }, + "group_id": { + "description": "ID used to link activities who share a sibling relationship", + "example": "13d96cf3-1cbe-4632-b2f2-86a9df5a3b9d", + "format": "uuid", + "type": "string" + }, + "net_amount": { + "description": "Valid only for non-trading activity types. Null for trading activites.", + "example": "1234", + "format": "decimal", + "type": "string" + }, + "per_share_amount": { + "description": "Valid only for non-trading activity types. Null for trading activites.", + "example": "0.38921", + "format": "decimal", + "type": "string" + }, + "qty": { + "description": "Valid only for non-trading activity types. Null for trading activites.", + "example": "0.38921", + "format": "decimal", + "type": "string" + }, + "status": { + "description": "Valid only for non-trading activity types. Null for trading activites.", + "enum": [ + "executed", + "correct", + "canceled" + ], + "example": "executed", + "type": "string" + }, + "symbol": { + "description": "Valid only for non-trading activity types. Null for trading activites.", + "example": "AAPL", + "type": "string" + } + }, + "title": "NonTradeActivity", + "type": "object" + }, + "NonTradeActivityEvent": { + "description": "Represents a non-trade activity SSE event", + "properties": { + "account_id": { + "description": "Account UUID", + "format": "uuid", + "type": "string" + }, + "at": { + "description": "Timestamp of when the event was emitted", + "format": "date-time", + "type": "string" + }, + "cusip": { + "description": "CUSIP the event is associated with, not present when no CUSIP is applicable", + "example": "037833100", + "type": "string" + }, + "description": { + "description": "Additional information about the event, empty string if not applicable", + "example": "Example description", + "type": "string" + }, + "entry_type": { + "description": "Type of entry for e.g JNLC, FEE, INT, DIVNRA etc", + "example": "JNLC", + "type": "string" + }, + "event_id": { + "description": "Monotonically increasing 64bit integer", + "type": "integer" + }, + "event_ulid": { + "description": "lexically sortable, monotonically increasing character array", + "format": "ulid", + "type": "string" + }, + "id": { + "description": "Record UUID", + "format": "uuid", + "type": "string" + }, + "net_amount": { + "description": "Net amount if applicable, 0 otherwise", + "example": 1, + "format": "decimal", + "type": "number" + }, + "per_share_amount": { + "description": "Per share amount if applicable", + "example": 0.3, + "format": "decimal", + "type": "number" + }, + "price": { + "description": "Price if applicable.", + "example": "0.38921", + "format": "decimal", + "type": "string" + }, + "qty": { + "description": "Quantity of the stock affected. 0 for cash events", + "format": "decimal", + "type": "number" + }, + "settle_date": { + "description": "Date of settlement if applicable", + "format": "date", + "type": "string" + }, + "status": { + "description": "Status of the event", + "enum": [ + "executed", + "correct", + "canceled" + ], + "example": "executed", + "type": "string" + }, + "symbol": { + "description": "Symbol the event is associated with, empty string when no symbol is applicable", + "example": "AAPL", + "type": "string" + }, + "system_date": { + "description": "Date of the event recorded in the system", + "format": "date", + "type": "string" + } + }, + "required": [ + "account_id", + "at", + "id", + "event_ulid", + "system_date", + "settle_date", + "net_amount", + "entry_type", + "description" + ], + "title": "NonTradeActivityEvent", + "type": "object", + "x-examples": { + "example-1": { + "account_id": "8e00606a-c9ac-409a-ba45-f55e8f77984a", + "at": "2024-11-26T15:25:17.803914Z", + "description": null, + "entry_type": "JNLC", + "event_ulid": "01JDMH7BKCCB4XY5F11HN63NZX", + "id": "afb78fa2-7e4c-4e0a-bca2-2e0d26a87f88", + "net_amount": 10, + "qty": 0, + "settle_date": "2024-11-26", + "status": "executed", + "symbol": null, + "system_date": "2024-11-26" + }, + "example-2": { + "account_id": "11629972-5fd6-4e14-ad9e-0f0cabd2777f", + "at": "2024-11-12T01:15:53.132425Z", + "description": "TAF fee for proceed of 0.010052895 shares (1 trades) on 2024-11-11 by 613651765", + "entry_sub_type": "TAF", + "entry_type": "FEE", + "event_ulid": "01JCEZ1ZDCE44GFX6WZ75N4199", + "id": "d96cea8a-6a77-46cd-bb7b-df6d74442653", + "net_amount": -0.01, + "qty": 0, + "settle_date": "2024-11-12", + "status": "executed", + "symbol": null, + "system_date": "2024-11-11" + } + } + }, + "OAuthTokenRequest": { + "description": "This model is used for both the Issue and Authorize OAuth token routes", + "example": { + "account_id": "0d18ae51-3c94-4511-b209-101e1666416b", + "client_id": "7a3c52a910e1dc2abbb14da2b6b8e711", + "client_secret": "bbb14da2b6b8e7117a3c52a910e1dc2a", + "redirect_uri": "http://localhost", + "scope": "general" + }, + "properties": { + "account_id": { + "description": "end-user account ID", + "format": "uuid", + "type": "string" + }, + "client_id": { + "description": "OAuth client ID", + "type": "string" + }, + "client_secret": { + "description": "OAuth client secret", + "type": "string" + }, + "redirect_uri": { + "description": "redirect URI for the OAuth flow", + "type": "string" + }, + "scope": { + "description": "scopes requested by the OAuth flow", + "type": "string" + } + }, + "required": [ + "client_id", + "client_secret", + "redirect_uri", + "scope", + "account_id" + ], + "title": "OAuthTokenRequest", + "type": "object" + }, + "OPASNActivityV2": { + "allOf": [ + { + "$ref": "#/components/schemas/CommonOptionsActivityV2" + } + ], + "description": "Option assignment", + "type": "object" + }, + "OPEXCActivityV2": { + "allOf": [ + { + "$ref": "#/components/schemas/CommonOptionsActivityV2" + } + ], + "description": "Option exercise", + "type": "object" + }, + "OPEXPActivityV2": { + "allOf": [ + { + "$ref": "#/components/schemas/CommonOptionsActivityV2" + } + ], + "description": "Option expiry", + "type": "object" + }, + "OPTRDActivityV2": { + "allOf": [ + { + "$ref": "#/components/schemas/CommonOptionsActivityV2" + } + ], + "description": "Trading activity that is paired with the assignment/exercise", + "type": "object" + }, + "OathClientResponse": { + "example": { + "client_id": "7a3c52a910e1dc2abbb14da2b6b8e711", + "description": "Sample description", + "live_trading_approved": true, + "name": "TradingApp", + "privacy_policy": "", + "redirect_uri": [ + "http://localhost" + ], + "status": "ACTIVE", + "terms_of_use": "", + "url": "http://test.com" + }, + "properties": { + "client_id": { + "description": "OAuth client id", + "type": "string" + }, + "description": { + "type": "string" + }, + "live_trading_approved": { + "example": true, + "type": "boolean" + }, + "name": { + "description": "Broker name (your name)", + "type": "string" + }, + "privacy_policy": { + "description": "URL of Privacy Policy", + "type": "string" + }, + "redirect_uri": { + "items": { + "type": "string" + }, + "type": "array" + }, + "status": { + "description": "ACTIVE or DISABLED", + "enum": [ + "ACTIVE", + "DISABLED" + ], + "example": "ACTIVE", + "type": "string" + }, + "terms_of_use": { + "description": "URL of Terms of Use", + "type": "string" + }, + "url": { + "type": "string" + } + }, + "title": "OathClientResponse", + "type": "object", + "x-examples": { + "example-1": { + "client_id": "7a3c52a910e1dc2abbb14da2b6b8e711", + "description": "Sample description", + "live_trading_approved": true, + "name": "TradingApp", + "privacy_policy": "", + "redirect_uri": [ + "http://localhost" + ], + "status": "ACTIVE", + "terms_of_use": "", + "url": "http://test.com" + } + } + }, + "OnfidoSDKOutcome": { + "description": "\"NOT_STARTED\"\tThe user has not started the SDK flow yet. outcome is set to this default value upon token generation\n\"USER_EXITED\"\tThe user exited the SDK flow\n\"SDK_ERROR\"\tAn error occurred in the SDK flow\n\"USER_COMPLETED\"\tThe user completed the SDK flow", + "title": "OnfidoSDKOutcome", + "type": "string" + }, + "OpcaCDIVActivityV2": { + "allOf": [ + { + "$ref": "#/components/schemas/CommonOPCAActivityV2" + }, + { + "$ref": "#/components/schemas/CommonCDIVActivityV2" + } + ], + "description": "Options corporate action of Cash dividend", + "type": "object" + }, + "OpcaFSPLITActivityV2": { + "allOf": [ + { + "$ref": "#/components/schemas/CommonOPCAActivityV2" + }, + { + "$ref": "#/components/schemas/CommonSplitActivityV2" + }, + { + "properties": { + "due_bill_redemption_date": { + "description": "When due bills related to the split are redeemed", + "format": "date", + "minLength": 1, + "type": "string" + }, + "ex_date": { + "description": "The ex_date for this corporate action", + "format": "date", + "minLength": 1, + "type": "string" + }, + "record_date": { + "description": "The record_date for this corporate action", + "format": "date", + "minLength": 1, + "type": "string" + }, + "symbol": { + "description": "The symbol of the security involved with the activity", + "type": "string" + } + }, + "required": [ + "symbol" + ], + "type": "object" + } + ], + "description": "Options corporate action of forward-splits", + "type": "object" + }, + "OpcaMAActivityV2": { + "allOf": [ + { + "$ref": "#/components/schemas/CommonOPCAActivityV2" + }, + { + "$ref": "#/components/schemas/CommonMAActivityV2" + } + ], + "description": "Options corporate action of Mergers & Acquisitions", + "type": "object" + }, + "OpcaNCActivityV2": { + "allOf": [ + { + "$ref": "#/components/schemas/CommonOPCAActivityV2" + }, + { + "$ref": "#/components/schemas/CommonNCActivityV2" + } + ], + "description": "Options corporate action of name changes", + "type": "object" + }, + "OpcaRSPLITActivityV2": { + "allOf": [ + { + "$ref": "#/components/schemas/CommonOPCAActivityV2" + }, + { + "$ref": "#/components/schemas/CommonSplitActivityV2" + }, + { + "properties": { + "ex_date": { + "description": "The ex_date for this corporate action", + "format": "date", + "minLength": 1, + "type": "string" + }, + "new_symbol": { + "description": "Symbol of the new security after the split", + "type": "string" + }, + "record_date": { + "description": "The record_date for this corporate action", + "format": "date", + "minLength": 1, + "type": "string" + }, + "symbol": { + "description": "The symbol of the security involved with the activity", + "type": "string" + } + }, + "required": [ + "symbol" + ], + "type": "object" + } + ], + "description": "Options corporate action of reverse-splits", + "type": "object" + }, + "OpcaSDIVActivityV2": { + "allOf": [ + { + "$ref": "#/components/schemas/CommonOPCAActivityV2" + }, + { + "$ref": "#/components/schemas/CommonSDIVActivityV2" + } + ], + "description": "Options corporate action of stock dividend", + "type": "object" + }, + "OpcaSPINActivityV2": { + "allOf": [ + { + "$ref": "#/components/schemas/CommonOPCAActivityV2" + }, + { + "$ref": "#/components/schemas/CommonSpinoffActivityV2" + } + ], + "description": "Options corporate action of spin-offs", + "type": "object" + }, + "OpcaUSPLITActivityV2": { + "allOf": [ + { + "$ref": "#/components/schemas/CommonOPCAActivityV2" + }, + { + "$ref": "#/components/schemas/CommonSplitActivityV2" + }, + { + "properties": { + "alternate_cusip": { + "description": "CUSIP for the alternate security after the split", + "type": "string" + }, + "alternate_rate": { + "description": "Ratio of alternate shares received", + "type": "string" + }, + "alternate_symbol": { + "description": "Symbol for the alternate security after the split", + "type": "string" + }, + "effective_date": { + "description": "When the unit split becomes effective", + "format": "date", + "minLength": 1, + "type": "string" + }, + "new_symbol": { + "description": "Symbol of the new security after the unit split", + "type": "string" + }, + "old_symbol": { + "description": "The old symbol of the security involved with the activity", + "type": "string" + } + }, + "required": [ + "old_symbol", + "new_symbol", + "alternate_cusip", + "alternate_symbol", + "alternate_rate", + "effective_date" + ], + "type": "object" + } + ], + "description": "Options corporate action of unit-splits", + "type": "object" + }, + "OptionContract": { + "properties": { + "close_price": { + "description": "The close price of the option contract.", + "example": "148.38", + "type": "string" + }, + "close_price_date": { + "description": "The date of the close price data.", + "example": "2023-12-11", + "format": "date", + "type": "string" + }, + "deliverables": { + "description": "Represents the deliverables tied to the option contract. While standard contracts entail a single deliverable, non-standard ones can encompass multiple deliverables, each potentially customized with distinct parameters.\nThis array is included in the list contracts response only if the query parameter show_deliverables=true is provided.\n", + "items": { + "$ref": "#/components/schemas/OptionDeliverable" + }, + "type": "array" + }, + "expiration_date": { + "description": "The expiration date of the option contract.", + "example": "2025-06-20", + "format": "date", + "type": "string" + }, + "id": { + "description": "The unique identifier of the option contract.", + "example": "98359ef7-5124-49f3-85ea-5cf02df6defa", + "type": "string" + }, + "multiplier": { + "description": "The multiplier of the option contract is crucial for calculating both the trade premium and the extended strike price. In standard contracts, the multiplier is always set to 100.\nFor instance, if a contract is traded at $1.50 and the multiplier is 100, the total amount debited when buying the contract would be $150.00.\nSimilarly, when exercising a call contract, the total cost will be equal to the strike price times the multiplier.", + "example": "100", + "type": "string" + }, + "name": { + "description": "The name of the option contract.", + "example": "AAPL Jun 20 2025 100 Call", + "type": "string" + }, + "open_interest": { + "description": "The open interest of the option contract.", + "example": "237", + "type": "string" + }, + "open_interest_date": { + "description": "The date of the open interest data.", + "example": "2023-12-11", + "format": "date", + "type": "string" + }, + "root_symbol": { + "description": "The root symbol of the option contract.", + "example": "AAPL", + "type": "string" + }, + "size": { + "description": "Represents the number of underlying shares to be delivered in case the contract is exercised/assigned. For standard contracts, this is always 100.\nThis field should **not** be used as a multiplier, specially for non-standard contracts.", + "example": "100", + "type": "string" + }, + "status": { + "description": "The status of the option contract.", + "enum": [ + "active", + "inactive" + ], + "example": "active", + "type": "string" + }, + "strike_price": { + "description": "The strike price of the option contract.", + "example": "100", + "type": "string" + }, + "style": { + "description": "The style of the option contract.", + "enum": [ + "american", + "european" + ], + "example": "american", + "type": "string" + }, + "symbol": { + "description": "The symbol representing the option contract.", + "example": "AAPL250620C00100000", + "type": "string" + }, + "tradable": { + "description": "Indicates whether the option contract is tradable.", + "example": true, + "type": "boolean" + }, + "type": { + "description": "The type of the option contract.", + "enum": [ + "call", + "put" + ], + "example": "call", + "type": "string" + }, + "underlying_asset_id": { + "description": "The unique identifier of the underlying asset.", + "example": "b0b6dd9d-8b9b-48a9-ba46-b9d54906e415", + "type": "string" + }, + "underlying_symbol": { + "description": "The underlying symbol of the option contract.", + "example": "AAPL", + "type": "string" + } + }, + "required": [ + "id", + "symbol", + "name", + "status", + "tradable", + "expiration_date", + "underlying_symbol", + "underlying_asset_id", + "type", + "style", + "strike_price", + "multiplier", + "size" + ], + "type": "object" + }, + "OptionDeliverable": { + "properties": { + "allocation_percentage": { + "description": "Cost allocation percentage of the deliverable.\nThis is used to determine the cost basis of the equity shares received from the exercise, specially for non-standard contracts with multiple deliverables.\n", + "example": "100", + "type": "string" + }, + "amount": { + "description": "The deliverable amount. For cash deliverables, this is the cash amount.\nFor standard contract, this is always 100.\nThis field can be null in case the deliverable settlement is delayed and the amount is yet to be determined.\n", + "example": "100", + "type": "string" + }, + "asset_id": { + "description": "Unique identifier of the deliverable asset. For standard contracts, this is equivalent to underlying_asset_id of the contracts.\nThis field is not returned for cash deliverables.\n", + "example": "b0b6dd9d-8b9b-48a9-ba46-b9d54906e415", + "type": "string" + }, + "delayed_settlement": { + "description": "If true, the settlement of the deliverable will be delayed.\nFor instance, in the event of a contract with a delayed deliverable being exercised, both the availability of the deliverable and its settlement may be postponed beyond the typical timeframe.\n", + "example": false, + "type": "boolean" + }, + "settlement_method": { + "description": "Indicates the settlement method that will be used:\n- **BTOB**: Broker to Broker\n- **CADF**: Cash Difference\n- **CAFX**: Cash Fixed\n- **CCC**: Correspondent Clearing Corp\n", + "enum": [ + "BTOB", + "CADF", + "CAFX", + "CCC" + ], + "example": "CCC", + "type": "string" + }, + "settlement_type": { + "description": "Indicates when the deliverable will be settled if the contract is exercised/assigned.\n", + "enum": [ + "T+0", + "T+1", + "T+2", + "T+3", + "T+4", + "T+5" + ], + "example": "T+2", + "type": "string" + }, + "symbol": { + "description": "Symbol of the deliverable. For standard contracts, this is equivalent to the underlying symbol of the contract.\n", + "example": "AAPL", + "type": "string" + }, + "type": { + "description": "Type of deliverable, indicating whether it's cash or equity. For standard contracts, it is always \"equity\".\n", + "enum": [ + "cash", + "equity" + ], + "example": "equity", + "type": "string" + } + }, + "required": [ + "type", + "symbol", + "amount", + "allocation_percentage", + "settlement_type", + "settlement_method", + "delayed_settlement" + ], + "type": "object" + }, + "OptionsApprovalEvent": { + "description": "This property is included when the account's approved options level changes.\n", + "properties": { + "approved_level_from": { + "description": "The approved options level before the change", + "type": "integer" + }, + "approved_level_to": { + "description": "The approved options level after the change", + "type": "integer" + } + }, + "type": "object" + }, + "OptionsApprovalRequest": { + "properties": { + "level": { + "description": "The desired option trading level. 1=Covered Call/Cash-Secured Put, 2=Long Call/Put, 3=Spreads/Straddles.", + "enum": [ + 1, + 2, + 3 + ], + "example": 3, + "type": "integer" + } + }, + "type": "object" + }, + "OptionsApprovalResponse": { + "properties": { + "account_id": { + "description": "The account ID.", + "example": "c8f1ef5d-edc0-4f23-9ee4-378f19cb92a4", + "format": "uuid", + "type": "string" + }, + "approved_level": { + "description": "The option trading level approved for this request. Only present once the request has completed processiing.\nNote that a subsequent request may be approved for a different level.\n0=Disabled, 1=Covered Call/Cash-Secured Put, 2=Long Call/Put, 3=Spreads/Straddles.\"\n", + "enum": [ + 0, + 1, + 2, + 3 + ], + "example": 3, + "type": "integer" + }, + "created_at": { + "description": "The time when the request was submitted.", + "example": "2021-03-16T18:38:01.942282Z", + "format": "date-time", + "type": "string" + }, + "id": { + "description": "The request ID.", + "example": "88b5f678-fef5-447b-af15-f21e367e6d8c", + "format": "uuid", + "type": "string" + }, + "requested_level": { + "description": "The request option trading level. 0=Disabled, 1=Covered Call/Cash-Secured Put, 2=Long Call/Put, 3=Spreads/Straddles.", + "enum": [ + 0, + 1, + 2, + 3 + ], + "example": 3, + "type": "integer" + }, + "requester": { + "description": "The requester of the options approval request.", + "enum": [ + "CORRESPONDENT", + "ALPACA_ADMIN" + ], + "example": "CORRESPONDENT", + "type": "string" + }, + "status": { + "$ref": "#/components/schemas/OptionsApprovalStatus" + }, + "updated_at": { + "description": "The time when the request was last updated.", + "example": "2021-03-16T18:38:01.942282Z", + "format": "date-time", + "type": "string" + } + }, + "type": "object" + }, + "OptionsApprovalStatus": { + "description": "The request status.\n- PENDING: The request is under review.\n- APPROVED: The request has been successfully approved, the account is now able to trade options.\n- LOWER_LEVEL_APPROVED: The request has been approved for a level lower than the requested one.\n- REJECTED: The request has been rejected.\n", + "enum": [ + "PENDING", + "APPROVED", + "LOWER_LEVEL_APPROVED", + "REJECTED" + ], + "example": "PENDING", + "type": "string" + }, + "OptionsApprovalsList": { + "description": "A list of options approval requests.", + "properties": { + "next_page_token": { + "$ref": "#/components/schemas/NextPageToken" + }, + "options_approvals": { + "description": "An array of options approval requests.", + "items": { + "$ref": "#/components/schemas/OptionsApprovalResponse" + }, + "type": "array" + } + }, + "type": "object" + }, + "Order": { + "properties": { + "asset_class": { + "$ref": "#/components/schemas/AssetClass" + }, + "asset_id": { + "description": "The asset ID (For options this represents the option contract ID)", + "example": "61e69015-8549-4bfd-b9c3-01e75843f47d", + "format": "uuid", + "type": "string" + }, + "cancel_requested_at": { + "description": "Time when cancellation or bust was requested (if applicable)", + "example": "2021-03-16T18:38:01.942282Z", + "format": "date-time", + "nullable": true, + "type": "string" + }, + "canceled_at": { + "description": "Can be null", + "example": "2021-03-16T18:38:01.942282Z", + "format": "date-time", + "nullable": true, + "type": "string" + }, + "client_order_id": { + "description": "Client unique order ID", + "example": "61e69015-8549-4bfd-b9c3-01e75843f47d", + "maxLength": 128, + "type": "string" + }, + "commission": { + "description": "The dollar value commission for this order.", + "example": "3.14", + "format": "decimal", + "type": "string" + }, + "commission_bps": { + "deprecated": true, + "description": "**deprecated**: Please use the commission_type = bps instead and set the desired bps value in the `commission` field.\nThe percent commission you want to charge the end user on the order (expressed in bps). Alpaca will convert the order to a notional amount for purposes of calculating commission.\n", + "example": "10", + "format": "decimal", + "type": "string" + }, + "commission_type": { + "$ref": "#/components/schemas/CommissionType" + }, + "created_at": { + "description": "Time when order was entered", + "example": "2021-03-16T18:38:01.942282Z", + "format": "date-time", + "type": "string" + }, + "expired_at": { + "description": "Can be null", + "example": "2021-03-16T18:38:01.942282Z", + "format": "date-time", + "nullable": true, + "type": "string" + }, + "extended_hours": { + "example": true, + "type": "boolean" + }, + "failed_at": { + "description": "Can be null", + "example": "2021-03-16T18:38:01.942282Z", + "format": "date-time", + "nullable": true, + "type": "string" + }, + "filled_at": { + "description": "Time the order was filled. Can be null if not filled", + "example": "2021-03-16T18:38:01.942282Z", + "format": "date-time", + "nullable": true, + "type": "string" + }, + "filled_avg_price": { + "description": "Filled average price. Can be 0 until order is processed in case order is passed outside of market hours", + "example": "4.2", + "format": "decimal", + "nullable": true, + "type": "string" + }, + "filled_qty": { + "description": "Filled quantity", + "example": "4.2", + "format": "decimal", + "type": "string" + }, + "hwm": { + "description": "The highest (lowest) market price seen since the trailing stop order was submitted.", + "example": "3.14", + "format": "decimal", + "nullable": true, + "type": "string" + }, + "id": { + "description": "Order ID generated by Alpaca", + "example": "61e69015-8549-4bfd-b9c3-01e75843f47d", + "format": "uuid", + "type": "string" + }, + "legs": { + "description": "When querying non-simple order_class orders in a nested style, an array of Order entities associated with this order. Otherwise, null.", + "items": { + "$ref": "#/components/schemas/OrderLeg" + }, + "nullable": true, + "type": "array" + }, + "limit_price": { + "description": "Limit price", + "example": "3.14", + "format": "decimal", + "nullable": true, + "type": "string" + }, + "notional": { + "description": "Ordered notional amount. If entered, qty will be null. Can take up to 2 decimal points.", + "example": "4.2", + "format": "decimal", + "nullable": true, + "type": "string" + }, + "order_class": { + "$ref": "#/components/schemas/OrderClass" + }, + "order_type": { + "$ref": "#/components/schemas/OrderType" + }, + "position_intent": { + "$ref": "#/components/schemas/PositionIntent" + }, + "qty": { + "description": "Ordered quantity. If entered, notional will be null. Can take up to 2 decimal points.", + "example": "4.2", + "format": "decimal", + "nullable": true, + "type": "string" + }, + "replaced_at": { + "description": "Can be null", + "example": "2021-03-16T18:38:01.942282Z", + "format": "date-time", + "nullable": true, + "type": "string" + }, + "replaced_by": { + "description": "The order ID that this order was replaced by. (Can be null)", + "example": "61e69015-8549-4bfd-b9c3-01e75843f47d", + "format": "uuid", + "nullable": true, + "type": "string" + }, + "replaces": { + "description": "The order ID that this order replaces. (Can be null)", + "example": "61e69015-8549-4bfd-b9c3-01e75843f47d", + "format": "uuid", + "nullable": true, + "type": "string" + }, + "side": { + "$ref": "#/components/schemas/OrderSide" + }, + "status": { + "$ref": "#/components/schemas/OrderStatus" + }, + "stop_price": { + "description": "Stop price", + "example": "3.14", + "format": "decimal", + "nullable": true, + "type": "string" + }, + "submitted_at": { + "description": "Time the order was submitted for execution or, if not yet submitted the created_at time. Because orders are submitted for execution asynchronous to database updates, at times this may be before the created_at time.", + "example": "2021-03-16T18:38:01.942282Z", + "format": "date-time", + "type": "string" + }, + "swap_fee_bps": { + "description": "Fee in basis points on top swap rate charged by the correspondent on every order", + "type": "string" + }, + "swap_rate": { + "description": "Swap rate is the exchange rate (without mark-up) used to convert the price into local currency or crypto asset", + "type": "string" + }, + "symbol": { + "description": "The asset symbol", + "example": "AALP", + "type": "string" + }, + "time_in_force": { + "$ref": "#/components/schemas/TimeInForce" + }, + "trail_percent": { + "description": "The percent value away from the high water mark for trailing stop orders.", + "example": "5.0", + "format": "decimal", + "nullable": true, + "type": "string" + }, + "trail_price": { + "description": "The dollar value away from the high water mark for trailing stop orders.", + "example": "3.14", + "format": "decimal", + "nullable": true, + "type": "string" + }, + "type": { + "$ref": "#/components/schemas/OrderType" + }, + "updated_at": { + "description": "Time of most recent change to the order", + "example": "2021-03-16T18:38:01.942282Z", + "format": "date-time", + "type": "string" + }, + "usd": { + "description": "Nested object to encompass the USD equivalent fields for the local currency fields", + "type": "object" + } + }, + "required": [ + "id", + "symbol" + ], + "type": "object" + }, + "OrderClass": { + "description": "The order classes supported by Alpaca vary based on the order's security type. The following provides a comprehensive breakdown of the supported order classes for each category:\n - Equity trading: simple (or \"\"), oco, oto, bracket.\n - Options trading:\n - simple (or \"\")\n - mleg (required for multi-leg complex option strategies)\n - Crypto trading: simple (or \"\").", + "enum": [ + "simple", + "bracket", + "oco", + "oto", + "mleg" + ], + "example": "bracket", + "type": "string" + }, + "OrderClosedResponse": { + "description": "Represents the result of asking the api to cancel an Order.\n", + "properties": { + "body": { + "$ref": "#/components/schemas/Order" + }, + "id": { + "description": "UUID of the order that was canceled", + "format": "uuid", + "type": "string" + }, + "status": { + "description": "Http status code for the attempt to close this Order", + "type": "integer" + } + }, + "required": [ + "id", + "status" + ], + "title": "OrderClosedResponse", + "type": "object", + "x-examples": {} + }, + "OrderLeg": { + "properties": { + "asset_class": { + "$ref": "#/components/schemas/AssetClass" + }, + "asset_id": { + "description": "The asset ID (For options this represents the option contract ID)", + "example": "61e69015-8549-4bfd-b9c3-01e75843f47d", + "format": "uuid", + "type": "string" + }, + "canceled_at": { + "description": "Can be null", + "example": "2021-03-16T18:38:01.942282Z", + "format": "date-time", + "nullable": true, + "type": "string" + }, + "client_order_id": { + "description": "Client unique order ID", + "example": "61e69015-8549-4bfd-b9c3-01e75843f47d", + "maxLength": 128, + "type": "string" + }, + "commission": { + "description": "The dollar value commission you want to charge the end user.", + "example": "3.14", + "format": "decimal", + "type": "string" + }, + "commission_bps": { + "deprecated": true, + "description": "**deprecated**: Please use the commission_type = bps instead and set the desired bps value in the `commission` field.\nThe percent commission you want to charge the end user on the order (expressed in bps). Alpaca will convert the order to a notional amount for purposes of calculating commission.\n", + "example": "10", + "format": "decimal", + "type": "string" + }, + "commission_type": { + "$ref": "#/components/schemas/CommissionType" + }, + "created_at": { + "description": "Time when order was entered", + "example": "2021-03-16T18:38:01.942282Z", + "format": "date-time", + "type": "string" + }, + "expired_at": { + "description": "Can be null", + "example": "2021-03-16T18:38:01.942282Z", + "format": "date-time", + "nullable": true, + "type": "string" + }, + "extended_hours": { + "example": true, + "type": "boolean" + }, + "failed_at": { + "description": "Can be null", + "example": "2021-03-16T18:38:01.942282Z", + "format": "date-time", + "nullable": true, + "type": "string" + }, + "filled_at": { + "description": "Time the order was filled. Can be null if not filled", + "example": "2021-03-16T18:38:01.942282Z", + "format": "date-time", + "nullable": true, + "type": "string" + }, + "filled_avg_price": { + "description": "Filled average price. Can be 0 until order is processed in case order is passed outside of market hours", + "example": "4.2", + "format": "decimal", + "nullable": true, + "type": "string" + }, + "filled_qty": { + "description": "Filled quantity", + "example": "4.2", + "format": "decimal", + "type": "string" + }, + "hwm": { + "description": "The highest (lowest) market price seen since the trailing stop order was submitted.", + "example": "3.14", + "format": "decimal", + "nullable": true, + "type": "string" + }, + "id": { + "description": "Order ID generated by Alpaca", + "example": "61e69015-8549-4bfd-b9c3-01e75843f47d", + "format": "uuid", + "type": "string" + }, + "legs": { + "description": "Always null for an order leg; legs are not nested beyond one level.", + "example": null, + "items": { + "type": "object" + }, + "nullable": true, + "type": "array" + }, + "limit_price": { + "description": "Limit price", + "example": "3.14", + "format": "decimal", + "nullable": true, + "type": "string" + }, + "notional": { + "description": "Ordered notional amount. If entered, qty will be null. Can take up to 2 decimal points.", + "example": "4.2", + "format": "decimal", + "nullable": true, + "type": "string" + }, + "order_class": { + "$ref": "#/components/schemas/OrderClass" + }, + "order_type": { + "$ref": "#/components/schemas/OrderType" + }, + "position_intent": { + "$ref": "#/components/schemas/PositionIntent" + }, + "qty": { + "description": "Ordered quantity. If entered, notional will be null. Can take up to 2 decimal points.", + "example": "4.2", + "format": "decimal", + "nullable": true, + "type": "string" + }, + "replaced_at": { + "description": "Can be null", + "example": "2021-03-16T18:38:01.942282Z", + "format": "date-time", + "nullable": true, + "type": "string" + }, + "replaced_by": { + "description": "The order ID that this order was replaced by. (Can be null)", + "example": "61e69015-8549-4bfd-b9c3-01e75843f47d", + "format": "uuid", + "nullable": true, + "type": "string" + }, + "replaces": { + "description": "The order ID that this order replaces. (Can be null)", + "example": "61e69015-8549-4bfd-b9c3-01e75843f47d", + "format": "uuid", + "nullable": true, + "type": "string" + }, + "side": { + "$ref": "#/components/schemas/OrderSide" + }, + "status": { + "$ref": "#/components/schemas/OrderStatus" + }, + "stop_price": { + "description": "Stop price", + "example": "3.14", + "format": "decimal", + "nullable": true, + "type": "string" + }, + "submitted_at": { + "description": "Time the order was submitted for execution or, if not yet submitted the created_at time. Because orders are submitted for execution asynchronous to database updates, at times this may be before the created_at time.", + "example": "2021-03-16T18:38:01.942282Z", + "format": "date-time", + "type": "string" + }, + "swap_fee_bps": { + "description": "Fee in basis points on top swap rate charged by the correspondent on every order", + "type": "string" + }, + "swap_rate": { + "description": "Swap rate is the exchange rate (without mark-up) used to convert the price into local currency or crypto asset", + "type": "string" + }, + "symbol": { + "description": "The asset symbol", + "example": "AALP", + "type": "string" + }, + "time_in_force": { + "$ref": "#/components/schemas/TimeInForce" + }, + "trail_percent": { + "description": "The percent value away from the high water mark for trailing stop orders.", + "example": "5.0", + "format": "decimal", + "nullable": true, + "type": "string" + }, + "trail_price": { + "description": "The dollar value away from the high water mark for trailing stop orders.", + "example": "3.14", + "format": "decimal", + "nullable": true, + "type": "string" + }, + "type": { + "$ref": "#/components/schemas/OrderType" + }, + "updated_at": { + "description": "Time of most recent change to the order", + "example": "2021-03-16T18:38:01.942282Z", + "format": "date-time", + "type": "string" + }, + "usd": { + "description": "Nested object to encompass the USD equivalent fields for the local currency fields", + "type": "object" + } + }, + "required": [ + "id", + "symbol" + ], + "type": "object" + }, + "OrderSide": { + "description": "Represents what side of the transaction an order was on. Required for all order classes except for `mleg`.", + "enum": [ + "buy", + "sell", + "buy_minus", + "sell_plus", + "sell_short", + "sell_short_exempt", + "undisclosed", + "cross", + "cross_short" + ], + "example": "buy", + "type": "string" + }, + "OrderStatus": { + "enum": [ + "new", + "partially_filled", + "filled", + "done_for_day", + "canceled", + "expired", + "replaced", + "pending_cancel", + "pending_replace", + "accepted", + "pending_new", + "accepted_for_bidding", + "stopped", + "rejected", + "suspended", + "calculated" + ], + "example": "filled", + "type": "string" + }, + "OrderType": { + "description": "The order types supported by Alpaca vary based on the order's security type. The following provides a comprehensive breakdown of the supported order types for each category:\n - Equity trading: market, limit, stop, stop_limit, trailing_stop.\n - Options trading: market, limit.\n - Options Multileg trading: market, limit.\n - Crypto trading: market, limit, stop_limit.", + "enum": [ + "market", + "limit", + "stop", + "stop_limit", + "trailing_stop" + ], + "example": "market", + "title": "OrderType", + "type": "string" + }, + "OvercontributedIRAAccount": { + "properties": { + "account_id": { + "description": "The account id", + "type": "string" + }, + "tax_year": { + "description": "The tax year", + "type": "number" + }, + "total_contribution_amount": { + "description": "The total conntribution amount", + "type": "number" + } + }, + "type": "object" + }, + "OwnerDocument": { + "description": "A document associated with an owner of the account", + "example": { + "created_at": "2019-09-30T23:55:31.185998Z", + "document_sub_type": "passport", + "document_type": "identity_verification", + "id": "0d18ae51-3c94-4511-b209-101e1666416b", + "mime_type": "image/jpeg" + }, + "properties": { + "created_at": { + "format": "date-time", + "type": "string" + }, + "document_sub_type": { + "description": "The sub-type of the document. This is a free-form property.", + "type": "string" + }, + "document_type": { + "$ref": "#/components/schemas/OwnerDocumentType" + }, + "id": { + "format": "uuid", + "type": "string" + }, + "mime_type": { + "type": "string" + } + }, + "required": [ + "id", + "document_type", + "created_at" + ], + "type": "object" + }, + "OwnerDocumentType": { + "description": "The type of the owner document", + "enum": [ + "account_approval_letter", + "address_verification", + "cip_result", + "company_formation", + "date_of_birth_verification", + "entity_operating_document", + "entity_registration", + "hio_declaration_form", + "identity_verification", + "limited_trading_authorization", + "pep_declaration_form", + "tax_id_verification", + "w8ben", + "w9" + ], + "example": "identity_verification", + "type": "string" + }, + "OwnerDocumentUploadRequest": { + "description": "The request to upload a document for an owner of the account", + "example": { + "content": "/9j/Cg==", + "document_sub_type": "passport", + "document_type": "identity_verification", + "mime_type": "image/jpeg" + }, + "properties": { + "content": { + "description": "The base64 string encoding of the document contents. This property is required unless content_data is provided.", + "example": "/9j/Cg==", + "format": "base64", + "type": "string" + }, + "content_data": { + "$ref": "#/components/schemas/W8benDocument" + }, + "document_sub_type": { + "description": "The specific type of document, e.g. passport. This is a free-form property.", + "example": "passport", + "type": "string" + }, + "document_type": { + "$ref": "#/components/schemas/OwnerDocumentType" + }, + "mime_type": { + "description": "This field is required if content is specified. ENUM: application/pdf, image/png, or image/jpeg. If document_type is w8ben then application/json is also accepted", + "example": "image/jpeg", + "type": "string" + } + }, + "required": [ + "document_type", + "content" + ], + "title": "OwnerDocumentUploadRequest", + "type": "object", + "x-examples": { + "example-1": { + "content": "/9j/Cg==", + "document_sub_type": "passport", + "document_type": "identity_verification", + "mime_type": "image/jpeg" + } + } + }, + "PDTRemoval": { + "deprecated": true, + "properties": { + "account_id": { + "description": "Account ID", + "example": "93f58d0b-6c53-432d-b8ce-2bad264dbd94", + "format": "uuid", + "type": "string" + }, + "created_at": { + "description": "The request date and time", + "format": "date-time", + "type": "string" + }, + "id": { + "description": "Unique ID", + "example": "52f8574c-96d5-49b6-94c1-2570a268434e", + "format": "uuid", + "type": "string" + }, + "reason": { + "description": "Reason of rejection", + "example": "account is already approved once", + "type": "string" + }, + "status": { + "description": "The request status", + "enum": [ + "approved", + "rejected" + ], + "example": "rejected", + "type": "string" + } + }, + "type": "object", + "x-deprecation": { + "reason": "See https://docs.alpaca.markets/us/docs/understanding-finras-new-intraday-margin-rule-and-the-end-of-pdt", + "since": "2026-04-27T00:00:00Z", + "sunset": "2026-07-06T00:00:00Z" + } + }, + "PDTStatus": { + "deprecated": true, + "properties": { + "account_id": { + "description": "Account ID", + "example": "93f58d0b-6c53-432d-b8ce-2bad264dbd94", + "format": "uuid", + "type": "string" + }, + "pdt": { + "description": "Account is currently flagged as Pattern Day Trader", + "example": true, + "type": "boolean" + }, + "pdt_removed": { + "description": "The account has previously utilized its one-time PDT removal entitlement, and is not allowed to submit any further removal requests", + "example": true, + "type": "boolean" + }, + "pdt_removed_at": { + "description": "Date and time when PDT was removed for the account. Null if PDT flag has never been lifted.", + "format": "date-time", + "nullable": true, + "type": "string" + } + }, + "type": "object", + "x-deprecation": { + "reason": "See https://docs.alpaca.markets/us/docs/understanding-finras-new-intraday-margin-rule-and-the-end-of-pdt", + "since": "2026-04-27T00:00:00Z", + "sunset": "2026-07-06T00:00:00Z" + } + }, + "Portfolio": { + "properties": { + "cooldown_days": { + "description": "Count of calendar days following a rebalance before a subscription is eligible to trigger another rebalance", + "type": "integer" + }, + "created_at": { + "description": "Portfolio creation timestamp", + "type": "string" + }, + "description": { + "description": "Text to describe portfolio", + "type": "string" + }, + "id": { + "description": "Portfolio ID", + "type": "string" + }, + "name": { + "description": "Name of portfolio", + "type": "string" + }, + "rebalancing_conditions": { + "description": "Rebalancing conditions for portfolio", + "type": "string" + }, + "status": { + "description": "Current status of portfolio", + "enum": [ + "active", + "inactive", + "needs_adjustment" + ], + "type": "string" + }, + "updated_at": { + "description": "Portfolio updated timestamp", + "type": "string" + }, + "weights": { + "description": "Weight configuration to portfolio. Sum of \"percent\" values in the weights array must be 100.00", + "items": { + "$ref": "#/components/schemas/PortfolioWeights" + }, + "type": "array" + } + }, + "required": [ + "cooldown_days" + ], + "title": "Portfolio", + "type": "object" + }, + "PortfolioHistory": { + "description": "Timeseries data for equity and profit loss information of the account.", + "properties": { + "base_value": { + "description": "basis in dollar of the profit loss calculation", + "type": "number" + }, + "base_value_asof": { + "description": "If included, then it indicates that the base_value is the account's closing\nequity value at this trading date.\n\nIf not specified, then the baseline calculation is done against the earliest returned data item. This could happen for\naccounts without prior closing balances (e.g. new account) or for queries with 1D timeframes, where the first data point\nis used as a reference point.\n", + "example": "2023-10-20", + "format": "date", + "type": "string" + }, + "cashflow": { + "description": "accumulated value in dollar amount as of the end of each time window", + "type": "object" + }, + "equity": { + "description": "equity value of the account in dollar amount as of the end of each time window", + "items": { + "type": "number" + }, + "type": "array" + }, + "profit_loss": { + "description": "profit/loss in dollar from the base value", + "items": { + "type": "number" + }, + "type": "array" + }, + "profit_loss_pct": { + "description": "profit/loss in percentage from the base value", + "example": [ + 0.001, + 0.002 + ], + "items": { + "type": "number" + }, + "type": "array" + }, + "timeframe": { + "description": "time window size of each data element", + "example": "15Min", + "type": "string" + }, + "timestamp": { + "description": "Time of each data element, left-labeled (the beginning of time window).\n\nThe values returned are in [UNIX epoch format](https://en.wikipedia.org/wiki/Unix_time).\n", + "items": { + "type": "integer" + }, + "type": "array" + } + }, + "required": [ + "timestamp", + "equity", + "profit_loss", + "profit_loss_pct", + "base_value", + "timeframe" + ], + "title": "PortfolioHistory", + "type": "object", + "x-examples": { + "example-cashflows": { + "base_value": 8413.04, + "base_value_asof": "2026-04-06", + "cashflow": { + "CSD": [ + 9.93, + 10.13, + 10.16, + 0 + ], + "DIV": [ + 0, + 0, + 0.63, + 0 + ] + }, + "equity": [ + 8425.21, + 8639.77, + 8766.1, + 8835.31 + ], + "profit_loss": [ + 12.17, + 214.56, + 126.33, + 69.21 + ], + "profit_loss_pct": [ + 0.0014, + 0.0255, + 0.0146, + 0.0079 + ], + "timeframe": "1D", + "timestamp": [ + 1775606400, + 1775692800, + 1775779200, + 1775865600 + ] + }, + "example-intraday-query-15min-1d": { + "base_value": 2774.16, + "base_value_asof": "2023-10-18", + "equity": [ + 2773.79, + 2769.04, + 2768.65, + 2765.11, + 2763.03, + 2763.17, + 2763.17, + 2763.47, + 2763.91, + 2768.13, + 2774.98, + 2757.94, + 2757.65, + 2774.54, + 2775.58, + 2775.28, + 2767.9, + 2762.26, + 2762.56, + 2756.99, + 2756.84, + 2752.43, + 2752.13, + 2748.44, + 2751.23, + 2747.54, + 2748.74 + ], + "profit_loss": [ + -0.37, + -5.12, + -5.51, + -9.05, + -11.13, + -10.99, + -10.99, + -10.69, + -10.25, + -6.03, + 0.82, + -16.22, + -16.51, + 0.38, + 1.42, + 1.12, + -6.26, + -11.9, + -11.6, + -17.17, + -17.32, + -21.73, + -22.03, + -25.72, + -22.93, + -26.62, + -25.42 + ], + "profit_loss_pct": [ + -0.0001, + -0.0018, + -0.002, + -0.0033, + -0.004, + -0.004, + -0.004, + -0.0039, + -0.0037, + -0.0022, + 0.0003, + -0.0058, + -0.006, + 0.0001, + 0.0005, + 0.0004, + -0.0023, + -0.0043, + -0.0042, + -0.0062, + -0.0062, + -0.0078, + -0.0079, + -0.0093, + -0.0083, + -0.0096, + -0.0092 + ], + "timeframe": "15Min", + "timestamp": [ + 1697722200, + 1697723100, + 1697724000, + 1697724900, + 1697725800, + 1697726700, + 1697727600, + 1697728500, + 1697729400, + 1697730300, + 1697731200, + 1697732100, + 1697733000, + 1697733900, + 1697734800, + 1697735700, + 1697736600, + 1697737500, + 1697738400, + 1697739300, + 1697740200, + 1697741100, + 1697742000, + 1697742900, + 1697743800, + 1697744700, + 1697745600 + ] + }, + "example-query-1d-7d": { + "base_value": 2784.79, + "equity": [ + 2784.79, + 2794.79, + 2805.46, + 2774.16, + 2748.73 + ], + "profit_loss": [ + 0, + 10.0022, + 10.6692, + -31.2996, + -25.4232 + ], + "profit_loss_pct": [ + 0, + 0.0035, + 0.0074, + -0.0038, + -0.0129 + ], + "timeframe": "1D", + "timestamp": [ + 1697241600, + 1697500800, + 1697587200, + 1697673600, + 1697760000 + ] + } + } + }, + "PortfolioRun": { + "properties": { + "account_id": { + "description": "Account ID for given run", + "type": "string" + }, + "canceled_at": { + "description": "RFC3339 format", + "type": "string" + }, + "completed_at": { + "description": "RFC3339 format", + "type": "string" + }, + "failed_orders": { + "description": "Array of failed orders for this run", + "items": { + "$ref": "#/components/schemas/Order" + }, + "type": "array" + }, + "id": { + "description": "Run ID", + "type": "string" + }, + "initiated_from": { + "description": "system or api", + "enum": [ + "system", + "api" + ], + "type": "string" + }, + "orders": { + "description": "Array of executed orders for this run", + "items": { + "$ref": "#/components/schemas/Order" + }, + "type": "array" + }, + "portfolio_id": { + "description": "Portfolio ID for given run", + "type": "string" + }, + "reason": { + "description": "Explainer text in case of failed runs", + "type": "string" + }, + "skipped_orders": { + "description": "Array of skipped order for this run", + "items": { + "$ref": "#/components/schemas/SkippedOrder" + }, + "type": "array" + }, + "status": { + "$ref": "#/components/schemas/PortfolioRunStatus" + }, + "type": { + "description": "full_rebalance or invest_cash", + "enum": [ + "full_rebalance", + "invest_cash" + ], + "type": "string" + }, + "updated_at": { + "description": "RFC3339 format", + "type": "string" + }, + "weights": { + "description": "Considered weighting for this run", + "items": { + "$ref": "#/components/schemas/PortfolioWeights" + }, + "type": "array" + } + }, + "title": "PortfolioRun", + "type": "object" + }, + "PortfolioRunStatus": { + "description": "| Status | Final | Represented State | Notes |\n|:------------------:|:-----:|:--------------------------------------------------------------------------------:|:--------------------------------------------------------------------------------------------------------------------:|\n| QUEUED | No | The run has been queued, waiting for our system to process it. | Runs only executed when the US market is open and there's at least 15 minutes before the market closes. |\n| IN_PROGRESS | No | Portfolio adjustment is in progress. | |\n| CANCELED | Yes | Portfolio run canceled, before being picked up by Alpaca's background processing | |\n| CANCELED_MID_RUN | Yes | Portfolio run canceled while executing. | The portfolio's state is in between the pre-run and post-run state, manual remediation or job re-run is recommended. |\n| ERROR | Yes | There was an error while rebalancing the portfolio. | The portfolio's state is in between the pre-run and post-run state, manual remediation or job re-run is recommended. |\n| TIMEOUT | Yes | A timeout occurred while rebalancing the portfolio. | The portfolio's state is in between the pre-run and post-run state, manual remediation or job re-run is recommended. |\n| COMPLETED_ADJUSTED | Yes | The portfolio has been adjusted | The adjustments have been prepared, but the run details haven't yet updated with the list of resulting orders |\n| COMPLETED_SUCCESS | Yes | The portfolio has been adjusted, run status updated | |", + "enum": [ + "QUEUED", + "IN_PROGRESS", + "CANCELED", + "CANCELED_MID_RUN", + "ERROR", + "TIMEOUT", + "COMPLETED_ADJUSTED", + "COMPLETED_SUCCESS" + ], + "title": "PortfolioRunStatus", + "type": "string" + }, + "PortfolioSubscription": { + "properties": { + "account_id": { + "description": "Account ID subscribing to portfolio", + "type": "string" + }, + "created_at": { + "description": "Subscription creation timestamp", + "format": "date-time", + "type": "string" + }, + "id": { + "description": "Subscription ID", + "type": "string" + }, + "last_rebalanced_at": { + "description": "Last rebalancing event for this subscription. Can be null.", + "type": "string" + }, + "portfolio_id": { + "description": "Portfolio ID for subscription", + "type": "string" + } + }, + "title": "PortfolioSubscription", + "type": "object" + }, + "PortfolioWeights": { + "properties": { + "percent": { + "description": "Must be a positive value, up to two decimal places", + "type": "string" + }, + "symbol": { + "description": "Must be fractionable asset. Only provided if type = \"asset\"", + "type": "string" + }, + "type": { + "description": "Possible values of cash or asset", + "type": "string" + } + }, + "title": "PortfolioWeights", + "type": "object" + }, + "Position": { + "properties": { + "asset_class": { + "$ref": "#/components/schemas/AssetClass" + }, + "asset_id": { + "description": "Asset ID (For options this represents the option contract ID)", + "example": "904837e3-3b76-47ec-b432-046db621571b", + "format": "uuid", + "type": "string" + }, + "asset_marginable": { + "description": "Indicates if this asset is marginable", + "type": "boolean" + }, + "avg_entry_price": { + "description": "Average entry price of the position", + "example": "100.0", + "type": "string" + }, + "avg_entry_swap_rate": { + "description": "The average swap rate of the position. This is only returned for LCT accounts.", + "example": "1.40", + "format": "decimal", + "type": "string" + }, + "change_today": { + "description": "Percent change from last day price (by a factor of 1)", + "example": "0.0084", + "format": "decimal", + "type": "string" + }, + "cost_basis": { + "description": "Total cost basis", + "example": "500.0", + "format": "decimal", + "type": "string" + }, + "current_price": { + "description": "Current asset price per share", + "example": "120.0", + "format": "decimal", + "type": "string" + }, + "exchange": { + "description": "Exchange name of the asset", + "example": "NASDAQ", + "type": "string" + }, + "lastday_price": { + "description": "Last day's asset price per share based on the closing value of the last trading day", + "example": "119.0", + "format": "decimal", + "type": "string" + }, + "market_value": { + "description": "Total market value of the position", + "example": "600.0", + "format": "decimal", + "type": "string" + }, + "qty": { + "description": "The number of shares", + "example": "5", + "type": "string" + }, + "qty_available": { + "description": "Total number of shares available minus open orders / locked for options covered call", + "example": "5", + "type": "string" + }, + "side": { + "enum": [ + "long", + "short" + ], + "example": "long", + "type": "string" + }, + "swap_rate": { + "description": "The latest swap rate. This is only returned for LCT accounts.", + "example": "1.50", + "format": "decimal", + "type": "string" + }, + "symbol": { + "description": "Asset symbol", + "example": "AAPL", + "type": "string" + }, + "unrealized_intraday_pl": { + "description": "Unrealized profit/loss for the day", + "example": "10.0", + "format": "decimal", + "type": "string" + }, + "unrealized_intraday_plpc": { + "description": "Unrealized interday profit/loss percent (by a factor of 1)", + "example": "0.0084", + "format": "decimal", + "type": "string" + }, + "unrealized_pl": { + "description": "Unrealized profit/loss", + "example": "100.0", + "format": "decimal", + "type": "string" + }, + "unrealized_plpc": { + "description": "Unrealized profit/loss percent (by a factor of 1)", + "example": "0.20", + "format": "decimal", + "type": "string" + }, + "usd": { + "$ref": "#/components/schemas/USDPosition" + } + }, + "required": [ + "asset_id", + "symbol", + "exchange", + "asset_class", + "avg_entry_price", + "qty", + "qty_available", + "side", + "market_value", + "cost_basis", + "unrealized_pl", + "unrealized_plpc", + "unrealized_intraday_pl", + "unrealized_intraday_plpc", + "current_price", + "lastday_price", + "change_today" + ], + "type": "object", + "x-examples": { + "example-1": { + "asset_class": "us_equity", + "asset_id": "93f58d0b-6c53-432d-b8ce-2bad264dbd94", + "asset_marginable": false, + "avg_entry_price": "172.08", + "change_today": "0.0189483657034581", + "cost_basis": "688.32", + "current_price": "172.08", + "exchange": "NASDAQ", + "lastday_price": "168.88", + "market_value": "688.32", + "qty": "4", + "qty_available": "4", + "side": "long", + "symbol": "AAPL", + "unrealized_intraday_pl": "0", + "unrealized_intraday_plpc": "0", + "unrealized_pl": "0", + "unrealized_plpc": "0" + } + } + }, + "PositionClosedResponse": { + "description": "Represents the result of asking the api to close a position.\n\n`body` is the Order used to close out the position.", + "properties": { + "body": { + "$ref": "#/components/schemas/Order" + }, + "status": { + "description": "Http status code for the attempt to close this position", + "type": "integer" + }, + "symbol": { + "description": "Symbol name of the asset", + "type": "string" + } + }, + "required": [ + "symbol", + "status" + ], + "title": "PositionClosedResponse", + "type": "object", + "x-examples": { + "example-1": { + "body": { + "asset_class": "us_equity", + "asset_id": "b0b6dd9d-8b9b-48a9-ba46-b9d54906e415", + "canceled_at": null, + "client_order_id": "52f8574c-96d5-49b6-94c1-2570a268434e", + "commision": "1.0", + "created_at": "2022-02-04T16:53:29.53427917Z", + "expired_at": null, + "extended_hours": false, + "failed_at": null, + "filled_at": null, + "filled_avg_price": null, + "filled_qty": "0", + "hwm": null, + "id": "f7f25e89-939a-4587-aaf6-414a6b3c341d", + "legs": null, + "limit_price": null, + "notional": null, + "order_class": "", + "order_type": "market", + "qty": "2", + "replaced_at": null, + "replaced_by": null, + "replaces": null, + "side": "sell", + "status": "accepted", + "stop_price": null, + "submitted_at": "2022-02-04T16:53:29.533738219Z", + "symbol": "AAPL", + "time_in_force": "day", + "trail_percent": null, + "trail_price": null, + "type": "market", + "updated_at": "2022-02-04T16:53:29.53427917Z" + }, + "status": 200, + "symbol": "AAPL" + } + } + }, + "PositionIntent": { + "description": "Represents the desired position strategy.", + "enum": [ + "buy_to_open", + "buy_to_close", + "sell_to_open", + "sell_to_close" + ], + "example": "buy_to_open", + "title": "PositionIntent", + "type": "string" + }, + "RebalancingConditions": { + "properties": { + "day": { + "description": "Used to specify the rebalancing day for conditions of type = calendar. Only permitted and required for type = calendar. In scenarios when the specified day aligns to a non-trading day, the rebalance will be triggered on the preceding trading day. For type= annually, the value must be passed in MM-DD format. If 02-29 is specified and the current year is not a leap year, the rebalance will occur on the trading day preceding 2/29. For type= quarterly and monthly the value must be an integer between 1 and 31 inclusive. This represents the day of month. For quarterly, rebalancing will trigger on this day in January, March, June, and December. If the specified day is non-existent for the month then the rebalancing will trigger on the preceding trading day. For type = weekly, permitted values are Monday, Tuesday, Wednesday, Thursday, Friday.", + "type": "string" + }, + "percent": { + "description": "Must be a positive value, up to two decimal places. Only permitted and required for type = drift_band. This is the max allowable drift percent from any target weight (+/-)", + "type": "string" + }, + "sub_type": { + "description": "For type = drift_band: absolute or relative. For type = calendar: weekly,monthly, quarterly or annually. For type = on_portfolio_update: there is no subtype", + "type": "string" + }, + "type": { + "description": "Possible values of drift_band, calendar or on_portfolio_update", + "type": "string" + } + }, + "title": "RebalancingConditions", + "type": "object" + }, + "ReportsResponse": { + "properties": { + "account_no": { + "type": "string" + }, + "deadline": { + "format": "date", + "type": "string" + }, + "instant_funding_transfers": { + "items": { + "$ref": "#/components/schemas/InstantFunding" + }, + "type": "array" + }, + "system_date": { + "format": "date", + "type": "string" + }, + "total_amount_owed": { + "format": "decimal", + "type": "string" + }, + "total_interest_penalty": { + "format": "decimal", + "type": "string" + } + }, + "required": [ + "system_date", + "account_no", + "total_amount_owed", + "total_interest_penalty", + "deadline" + ], + "type": "object" + }, + "RestrictToLiquidationReasons": { + "description": "Reasons why the liquidation only flag was set", + "properties": { + "ach_return": { + "description": "Set when an incoming ACH transfer gets rejected", + "type": "boolean" + }, + "pattern_day_trading": { + "deprecated": true, + "description": "Set when the trading account is marked as a PDT, but its equity falls below the $25k treshold", + "type": "boolean", + "x-deprecation": { + "reason": "See https://docs.alpaca.markets/us/docs/understanding-finras-new-intraday-margin-rule-and-the-end-of-pdt", + "since": "2026-04-27T00:00:00Z", + "sunset": "2026-07-06T00:00:00Z" + } + }, + "position_to_equity_ratio": { + "description": "Set when the position to equity ration exceeds the maximum limit", + "type": "boolean" + }, + "unspecified": { + "description": "Default value for unknown reason", + "type": "boolean" + } + }, + "title": "RestrictToLiquidationReasons", + "type": "object" + }, + "ReverseBatchJournalRequest": { + "properties": { + "entries": { + "items": { + "properties": { + "amount": { + "description": "Journal amount in USD", + "type": "string" + }, + "description": { + "description": "Journal description, gets returned in the response", + "type": "string" + }, + "from_account": { + "description": "The ID of the from_account that you want to journal from", + "type": "string" + } + }, + "type": "object" + }, + "type": "array" + }, + "entry_type": { + "description": "Only supports `JNLC` for now", + "enum": [ + "JNLC" + ], + "type": "string" + }, + "to_account": { + "description": "The destination of funds. Most likely is your Sweep Firm Account", + "type": "string" + } + }, + "type": "object", + "x-examples": { + "Example 1": { + "entries": [ + { + "amount": "10", + "from_account": "8e00606a-c9ac-409a-ba45-f55e8f77984a" + }, + { + "amount": "100", + "from_account": "b9b19618-22dd-4e80-8432-fc9e1ba0b27d" + }, + { + "amount": "100", + "from_account": "c96a5e16-7fca-425a-b67b-0814d064bfc0" + } + ], + "entry_type": "JNLC", + "to_account": "8f8c8cee-2591-4f83-be12-82c659b5e748" + } + } + }, + "ReverseSplitActivityV2": { + "allOf": [ + { + "$ref": "#/components/schemas/CommonSplitStockActivityV2" + }, + { + "properties": { + "ex_date": { + "description": "The ex_date for this corporate action", + "format": "date", + "minLength": 1, + "type": "string" + }, + "new_symbol": { + "description": "Symbol of the new security after the split", + "type": "string" + }, + "record_date": { + "description": "The record_date for this corporate action", + "format": "date", + "minLength": 1, + "type": "string" + }, + "symbol": { + "description": "The symbol of the security involved with the activity", + "type": "string" + } + }, + "required": [ + "symbol" + ], + "type": "object" + } + ], + "description": "Reverse stock split", + "type": "object" + }, + "RightsDistributionActivityV2": { + "allOf": [ + { + "$ref": "#/components/schemas/CommonCaActivityV2" + }, + { + "properties": { + "ex_date": { + "description": "The ex_date for this corporate action", + "format": "date", + "minLength": 1, + "type": "string" + }, + "expiration_date": { + "description": "The expiration date for the rights distribution", + "format": "date", + "type": "string" + }, + "new_cusip": { + "description": "The new CUSIP", + "type": "string" + }, + "new_qty": { + "description": "The new quantity", + "type": "string" + }, + "new_symbol": { + "description": "The new symbol", + "type": "string" + }, + "payable_date": { + "description": "The payable_date for this corporate action", + "format": "date", + "minLength": 1, + "type": "string" + }, + "rate": { + "description": "The rate for the rights distribution", + "type": "string" + }, + "record_date": { + "description": "The record_date for this corporate action", + "format": "date", + "minLength": 1, + "type": "string" + }, + "source_cusip": { + "description": "The source CUSIP", + "type": "string" + }, + "source_qty": { + "description": "The source quantity", + "type": "string" + }, + "source_symbol": { + "description": "The source symbol", + "type": "string" + } + }, + "required": [ + "source_cusip", + "source_symbol", + "source_qty", + "new_cusip", + "new_symbol", + "new_qty", + "rate" + ], + "type": "object" + } + ], + "description": "Rights distribution", + "type": "object" + }, + "RightsSubscriptionElectionActivityV2": { + "allOf": [ + { + "$ref": "#/components/schemas/CommonNTAActivityV2" + }, + { + "$ref": "#/components/schemas/CommonVOFSubtypeActivityV2" + } + ], + "type": "object" + }, + "SDIVActivityV2": { + "allOf": [ + { + "$ref": "#/components/schemas/CommonCaActivityV2" + }, + { + "$ref": "#/components/schemas/CommonSDIVActivityV2" + }, + { + "properties": { + "entitled_qty": { + "description": "Quantity of shares entitled to receive the dividend", + "type": "string" + }, + "new_qty": { + "description": "The total number of shares after the dividend", + "type": "string" + }, + "paid_qty": { + "description": "The paid quantity", + "type": "string" + } + }, + "required": [ + "entitled_qty", + "paid_qty", + "new_qty" + ], + "type": "object" + } + ], + "description": "Stock dividend", + "type": "object" + }, + "SettlementAccount": { + "properties": { + "account_number": { + "description": "The account number of the client account to settle\n", + "type": "string" + }, + "amount": { + "format": "decimal", + "type": "string" + }, + "transmitter_info": { + "$ref": "#/components/schemas/TransmitterInfo" + } + }, + "required": [ + "account_number", + "amount", + "transmitter_info" + ], + "type": "object" + }, + "SettlementResponse": { + "description": "A settlement response, either from creation or retrieval\n", + "properties": { + "additional_info": { + "type": "string" + }, + "asset_class": { + "$ref": "#/components/schemas/JITAssetClass" + }, + "completed_at": { + "format": "date-time", + "type": "string" + }, + "created_at": { + "format": "date-time", + "type": "string" + }, + "currency": { + "description": "The currency of the settlement. Only applicable to JIT settlements.\n", + "type": "string" + }, + "id": { + "format": "uuid", + "type": "string" + }, + "interest_amount": { + "description": "The total interest amount accrued on the transfers included in this settlement. Only applicable for instant funding settlements.\n", + "format": "decimal", + "type": "string" + }, + "reason": { + "description": "The reason for failure, if applicable\n", + "type": "string" + }, + "source_account_number": { + "type": "string" + }, + "status": { + "$ref": "#/components/schemas/SettlementStatus" + }, + "total_amount": { + "format": "decimal", + "type": "string" + }, + "updated_at": { + "format": "date-time", + "type": "string" + } + }, + "required": [ + "id", + "total_amount", + "status", + "created_at", + "updated_at" + ], + "type": "object" + }, + "SettlementStatus": { + "description": "Values:\n * `PENDING`: Created and waiting to be processed\n * `AWAITING_ADDITIONAL_FUNDS`: Waiting for additional funds to be deposited\n * `COMPLETED`: All transactions are reconciled\n * `FAILED`: Attempt to process this settlement has failed. If this is observed\n additional remittance information may be required, or one of the transfers being settled\n accrued additional interest since the settlement was created.\n", + "enum": [ + "PENDING", + "AWAITING_ADDITIONAL_FUNDS", + "COMPLETED", + "FAILED" + ], + "type": "string" + }, + "SettlementTransfer": { + "properties": { + "instant_transfer_id": { + "format": "uuid", + "type": "string" + }, + "transmitter_info": { + "$ref": "#/components/schemas/TransmitterInfo" + } + }, + "required": [ + "instant_transfer_id", + "transmitter_info" + ], + "type": "object" + }, + "SettlementsResponse": { + "description": "Response to a successful settlements request\n", + "properties": { + "settlements": { + "items": { + "$ref": "#/components/schemas/SettlementResponse" + }, + "type": "array" + } + }, + "required": [ + "settlements" + ], + "type": "object" + }, + "SkippedOrder": { + "description": "Skipped orders model contains information for such orders that the rebalancing engine didn't send to our order system due to some validation issues.", + "properties": { + "currency": { + "description": "Currency of the order", + "type": "string" + }, + "notional": { + "description": "Notional value of the order", + "type": "string" + }, + "reason": { + "description": "Reason for the order being skipped", + "type": "string" + }, + "reason_details": { + "description": "Formatted error message with the cause of the skip", + "type": "string" + }, + "side": { + "description": "Side of the order (buy, sell, sell_short)", + "type": "string" + }, + "symbol": { + "description": "Symbol for which the adjustment was skipped", + "type": "string" + } + }, + "required": [ + "symbol", + "reason", + "reason_details" + ], + "title": "SkippedOrder", + "type": "object" + }, + "SortOrder": { + "enum": [ + "ASC", + "DESC" + ], + "type": "string" + }, + "SpinoffActivityV2": { + "allOf": [ + { + "$ref": "#/components/schemas/CommonCaActivityV2" + }, + { + "$ref": "#/components/schemas/CommonSpinoffActivityV2" + }, + { + "properties": { + "new_qty": { + "description": "The new quantity", + "type": "string" + }, + "source_qty": { + "description": "The source quantity", + "type": "string" + } + }, + "required": [ + "source_qty", + "new_qty" + ], + "type": "object" + } + ], + "description": "Spinoff", + "type": "object" + }, + "StatusFundingEvent": { + "description": "Represents a change in a Funding entity's status, sent over the events streaming api. Currently, the suppported entities are: bank relationships, bank wires, transfers and funding wallets.", + "properties": { + "account_id": { + "description": "Account UUID", + "format": "uuid", + "minLength": 1, + "type": "string" + }, + "at": { + "description": "Timestamp of when the transfer status changed", + "minLength": 1, + "type": "string" + }, + "correspondent": { + "description": "Correspondent's code", + "format": "ABCD", + "maxLength": 4, + "minLength": 1, + "type": "string" + }, + "entity_id": { + "description": "Entity's UUID", + "format": "uuid", + "minLength": 1, + "type": "string" + }, + "entity_type": { + "description": "Valid values are BankRelationship, WireBank, Transfer and FundingWallet.", + "format": "uuid", + "minLength": 1, + "type": "string" + }, + "event_id": { + "description": "lexically sortable, monotonically increasing character array", + "format": "ulid", + "minLength": 1, + "type": "string" + }, + "reason": { + "description": "Used when an a bank relationship is rejected, a wire bank is canceled, etc.", + "type": "string" + }, + "status_from": { + "description": "Valid values are based on entity type:\n- BankRelationship:\n - QUEUED\n - CANCEL_REQUESTED\n - CANCEL_SENT\n - CANCEL_FAILED\n - PENDING\n - SENT_TO_CLEARING\n - APPROVED\n - CANCELED\n - REJECTED\n- WireBank:\n - QUEUED\n - SENT_TO_CLEARING\n - APPROVED\n - CANCELED\n - REJECTED\n- Transfer:\n - QUEUED\n - APPROVAL_PENDING\n - CANCELED\n - EXPIRED\n - APPROVED\n - REJECTED\n - SENT_TO_CLEARING\n - COMPLETE\n - RETURNED\n- FundingWallet:\n - active\n - pending\n", + "type": "string" + }, + "status_to": { + "description": "Valid values are based on entity type:\n- BankRelationship:\n - QUEUED\n - CANCEL_REQUESTED\n - CANCEL_SENT\n - CANCEL_FAILED\n - PENDING\n - SENT_TO_CLEARING\n - APPROVED\n - CANCELED\n - REJECTED\n- WireBank:\n - QUEUED\n - SENT_TO_CLEARING\n - APPROVED\n - CANCELED\n - REJECTED\n- Transfer:\n - QUEUED\n - APPROVAL_PENDING\n - CANCELED\n - EXPIRED\n - APPROVED\n - REJECTED\n - SENT_TO_CLEARING\n - COMPLETE\n - RETURNED\n- FundingWallet:\n - active\n - pending\n", + "minLength": 1, + "type": "string" + } + }, + "required": [ + "event_id", + "at", + "account_id", + "correspondent", + "entity_id", + "entity_type", + "status_to" + ], + "title": "StatusFundingEvent", + "type": "object", + "x-examples": { + "example-1": { + "account_id": "8e00606a-c9ac-409a-ba45-f55e8f77984a", + "at": "2025-04-11T19:52:24.066998Z", + "correspondent": "LPCA", + "entity_id": "c4ed4206-697b-4859-ab71-b9de6649859d", + "entity_type": "BankRelationship", + "event_id": "01F7VQQ782DM57SJNWAYMD14J9", + "status_from": null, + "status_to": "QUEUED" + }, + "example-2": { + "account_id": "8e00606a-c9ac-409a-ba45-f55e8f77984a", + "at": "2025-04-11T19:52:24.066998Z", + "correspondent": "LPCA", + "entity_id": "c4ed4206-697b-4859-ab71-b9de6649859d", + "entity_type": "BankRelationship", + "event_id": "01F7VQQ782DM57SJNWAYMD14J9", + "reason": "bank account owner name does not match brokerage account name", + "status_from": "QUEUED", + "status_to": "REJECTED" + } + } + }, + "StreetAddress": { + "example": "20 N San Mateo Dr", + "type": "string" + }, + "SystemEventV2": { + "description": "Represents that system event had occurred and sent over the events streaming api.\n", + "properties": { + "at": { + "description": "Timestamp of the event", + "format": "date-time", + "type": "string" + }, + "description": { + "description": "The human readable description of the system event", + "type": "string" + }, + "event_id": { + "description": "lexically sortable, monotonically increasing character array", + "format": "ulid", + "type": "string" + }, + "system_date": { + "description": "the system date to which this system event belongs to", + "format": "date", + "type": "string" + }, + "type": { + "description": "the machine readable type of the system event", + "enum": [ + "eod_balances_ready", + "eod_positions_ready" + ], + "type": "string" + } + }, + "required": [ + "at", + "event_id", + "type", + "system_date", + "description" + ], + "title": "SystemEvent", + "type": "object", + "x-examples": { + "example-1": { + "$ref": "#/components/examples/SystemEventV2" + } + } + }, + "TaxIdType": { + "description": "Required if `tax_id` is set.\n\nAn Enum of the various kinds of Tax ID formats Alpaca supports.\n\nPossible Values are:\n\n\n- **USA_SSN**\nUSA Social Security Number\n\n- **USA_ITIN**\nUSA Individual Taxpayer Identification Number\n\n- **ARG_AR_CUIT**\nArgentina CUIT\n\n- **AUS_TFN**\nAustralian Tax File Number\n\n- **AUS_ABN**\nAustralian Business Number\n\n- **BOL_NIT**\nBolivia NIT\n\n- **BRA_CPF**\nBrazil CPF\n\n- **CHL_RUT**\nChile RUT\n\n- **COL_NIT**\nColombia NIT\n\n- **CRI_NITE**\nCosta Rica NITE\n\n- **DEU_TAX_ID**\nGermany Tax ID (Identifikationsnummer)\n\n- **DOM_RNC**\nDominican Republic RNC\n\n- **ECU_RUC**\nEcuador RUC\n\n- **FRA_SPI**\nFrance SPI (Reference Tax Number)\n\n- **GBR_UTR**\nUK UTR (Unique Taxpayer Reference)\n\n- **GBR_NINO**\nUK NINO (National Insurance Number)\n\n- **GTM_NIT**\nGuatemala NIT\n\n- **HND_RTN**\nHonduras RTN\n\n- **HUN_TIN**\nHungary TIN Number\n\n- **IDN_KTP**\nIndonesia KTP\n\n- **IND_PAN**\nIndia PAN Number\n\n- **ISR_TAX_ID**\nIsrael Tax ID (Teudat Zehut)\n\n- **ITA_TAX_ID**\nItaly Tax ID (Codice Fiscale)\n\n- **JPN_TAX_ID**\nJapan Tax ID (Koijin Bango)\n\n- **MEX_RFC**\nMexico RFC\n\n- **NIC_RUC**\nNicaragua RUC\n\n- **NLD_TIN**\nNetherlands TIN Number\n\n- **PAN_RUC**\nPanama RUC\n\n- **PER_RUC**\nPeru RUC\n\n- **PRY_RUC**\nParaguay RUC\n\n- **SGP_NRIC**\nSingapore NRIC\n\n- **SGP_FIN**\nSingapore FIN\n\n- **SGP_ASGD**\nSingapore ASGD\n\n- **SGP_ITR**\nSingapore ITR\n\n- **SLV_NIT**\nEl Salvador NIT\n\n- **SWE_TAX_ID**\nSweden Tax ID (Personnummer)\n\n- **URY_RUT**\nUruguay RUT\n\n- **VEN_RIF**\nVenezuela RIF\n\n- **NATIONAL_ID**\nNational ID number, if a tax ID number is not available\n\n- **PASSPORT**\nPassport number, if a tax ID number is not available\n\n- **PERMANENT_RESIDENT**\nPermanent resident number, if a tax ID number is not available\n\n- **DRIVER_LICENSE**\nDriver's license number, if a tax ID number is not available\n\n- **OTHER_GOV_ID**\nOther government issued identifier, if a tax ID number is not available\n\n- **NOT_SPECIFIED**\nOther Tax IDs", + "enum": [ + "USA_SSN", + "USA_ITIN", + "ARG_AG_CUIT", + "AUS_TFN", + "AUS_ABN", + "BOL_NIT", + "BRA_CPF", + "CHL_RUT", + "COL_NIT", + "CRI_NITE", + "DEU_TAX_ID", + "DOM_RNC", + "ECU_RUC", + "FRA_SPI", + "GBR_UTR", + "GBR_NINO", + "GTM_NIT", + "HND_RTN", + "HUN_TIN", + "IDN_KTP", + "IND_PAN", + "ISR_TAX_ID", + "ITA_TAX_ID", + "JPN_TAX_ID", + "MEX_RFC", + "NIC_RUC", + "NLD_TIN", + "PAN_RUC", + "PER_RUC", + "PRY_RUC", + "SGP_NRIC", + "SGP_FIN", + "SGP_ASGD", + "SGP_ITR", + "SLV_NIT", + "SWE_TAX_ID", + "URY_RUT", + "VEN_RIF", + "NATIONAL_ID", + "PASSPORT", + "PERMANENT_RESIDENT", + "DRIVER_LICENSE", + "OTHER_GOV_ID", + "NOT_SPECIFIED" + ], + "example": "USA_SSN", + "title": "TaxIdType", + "type": "string" + }, + "TenderOfferActivityV2": { + "allOf": [ + { + "$ref": "#/components/schemas/CommonNTAActivityV2" + }, + { + "$ref": "#/components/schemas/CommonVOFSubtypeActivityV2" + } + ], + "type": "object" + }, + "TimeInForce": { + "description": "The Time-In-Force values supported by Alpaca vary based on the order's security type. Here is a breakdown of the supported TIFs for each specific security type:\n- Equity trading: day, gtc, opg, cls, ioc, fok.\n- Options trading: day.\n- Crypto trading: gtc, ioc.\n\nBelow are the descriptions of each TIF:\n- day:\n A day order is eligible for execution only on the day it is live. By default, the order is only valid during Regular Trading Hours (9:30am - 4:00pm ET). If unfilled after the closing auction, it is automatically canceled. If submitted after the close, it is queued and submitted the following trading day. However, if marked as eligible for extended hours, the order can also execute during supported extended hours.\n\n- gtc:\n The order is good until canceled. Non-marketable GTC limit orders are subject to price adjustments to offset corporate actions affecting the issue. We do not currently support Do Not Reduce (DNR) orders to opt out of such price adjustments.\n\n- opg:\n Use this TIF with a market/limit order type to submit \"market on open\" (MOO) and \"limit on open\" (LOO) orders. This order is eligible to execute only in the market opening auction. Any unfilled orders after the open will be cancelled. OPG orders submitted after 9:28am but before 7:00pm ET will be rejected. OPG orders submitted after 7:00pm will be queued and routed to the following day's opening auction. On open/on close orders are routed to the primary exchange. Such orders do not necessarily execute exactly at 9:30am / 4:00pm ET but execute per the exchange's auction rules.\n\n- cls:\n Use this TIF with a market/limit order type to submit \"market on close\" (MOC) and \"limit on close\" (LOC) orders. This order is eligible to execute only in the market closing auction. Any unfilled orders after the close will be cancelled. CLS orders submitted after 3:50pm but before 7:00pm ET will be rejected. CLS orders submitted after 7:00pm will be queued and routed to the following day's closing auction.\n\n- ioc:\n An Immediate Or Cancel (IOC) order requires all or part of the order to be executed immediately. Any unfilled portion of the order is canceled. Most market makers who receive IOC orders will attempt to fill the order on a principal basis only, and cancel any unfilled balance. On occasion, this can result in the entire order being cancelled if the market maker does not have any existing inventory of the security in question.\n\n- fok:\n A Fill or Kill (FOK) order is only executed if the entire order quantity can be filled, otherwise the order is canceled.", + "enum": [ + "day", + "gtc", + "opg", + "cls", + "ioc", + "fok" + ], + "example": "gtc", + "title": "TimeInForce", + "type": "string" + }, + "TokenizationIssuer": { + "description": "The tokenized asset's issuer", + "enum": [ + "xstocks", + "st0x" + ], + "example": "xstocks", + "title": "TokenizationIssuer", + "type": "string" + }, + "TokenizationMintCallback": { + "properties": { + "account_id": { + "description": "Alpaca account ID (UUID) of the Authorized Participant. Exactly one of `client_id` or `account_id` must be provided, matching the identifier used on the original mint request.", + "format": "uuid", + "type": "string" + }, + "client_id": { + "description": "Customer's client identifier on the issuer's platform. Exactly one of `client_id` or `account_id` must be provided, matching the identifier used on the original mint request.", + "type": "string" + }, + "network": { + "$ref": "#/components/schemas/TokenizationNetwork" + }, + "tokenization_request_id": { + "description": "Unique identifier of the tokenization request", + "format": "uuid", + "type": "string" + }, + "tx_hash": { + "description": "Transaction hash of the completed request on the blockchain", + "type": "string" + }, + "wallet_address": { + "description": "The wallet address to receive the tokenized asset", + "type": "string" + } + }, + "required": [ + "tokenization_request_id", + "tx_hash" + ], + "title": "TokenizationMintCallback", + "type": "object" + }, + "TokenizationMintRequest": { + "properties": { + "issuer": { + "$ref": "#/components/schemas/TokenizationIssuer" + }, + "network": { + "$ref": "#/components/schemas/TokenizationNetwork" + }, + "qty": { + "description": "The underlying quantity to convert into the tokenized asset. It can be fractional.", + "type": "string" + }, + "underlying_symbol": { + "description": "The underlying asset symbol", + "type": "string" + }, + "wallet_address": { + "description": "The wallet address to receive the tokenized asset", + "type": "string" + } + }, + "required": [ + "underlying_symbol", + "qty", + "issuer", + "network", + "wallet_address" + ], + "title": "TokenizationMintRequest", + "type": "object" + }, + "TokenizationMintResponse": { + "properties": { + "created_at": { + "format": "date-time", + "type": "string" + }, + "issuer": { + "$ref": "#/components/schemas/TokenizationIssuer" + }, + "network": { + "$ref": "#/components/schemas/TokenizationNetwork" + }, + "qty": { + "description": "The quantity to convert for this tokenization request. It can be fractional.", + "type": "string" + }, + "status": { + "$ref": "#/components/schemas/TokenizationRequestStatus" + }, + "token_symbol": { + "description": "The tokenized asset symbol", + "type": "string" + }, + "tokenization_request_id": { + "description": "Unique identifier of the tokenization request set by Alpaca", + "type": "string" + }, + "underlying_symbol": { + "description": "The underlying asset symbol", + "type": "string" + } + }, + "required": [ + "tokenization_request_id", + "status", + "underlying_symbol", + "token_symbol", + "qty", + "created_at", + "issuer", + "network" + ], + "title": "TokenizationMintResponse", + "type": "object" + }, + "TokenizationNetwork": { + "description": "The token's blockchain network", + "enum": [ + "solana", + "arbitrum", + "ethereum", + "binance", + "base", + "ton", + "tron", + "mantle" + ], + "example": "solana", + "title": "TokenizationNetwork", + "type": "string" + }, + "TokenizationRedeemRequest": { + "properties": { + "account_id": { + "description": "Alpaca account ID (UUID) of the Authorized Participant. Exactly one of `client_id` or `account_id` must be provided to identify the Authorized Participant whose account will receive the underlying asset.", + "format": "uuid", + "type": "string" + }, + "client_id": { + "description": "Customer's client identifier on the issuer's platform. Exactly one of `client_id` or `account_id` must be provided to identify the Authorized Participant whose account will receive the underlying asset.", + "type": "string" + }, + "issuer_request_id": { + "description": "Unique identifier of the redemption request set by the issuer", + "type": "string" + }, + "network": { + "$ref": "#/components/schemas/TokenizationNetwork" + }, + "qty": { + "description": "The quantity to convert into the underlying asset. It can be fractional.", + "type": "string" + }, + "token_symbol": { + "description": "The tokenized asset symbol", + "type": "string" + }, + "tx_hash": { + "description": "Transaction hash of the completed request on the blockchain", + "type": "string" + }, + "underlying_symbol": { + "description": "The underlying asset symbol", + "type": "string" + }, + "wallet_address": { + "description": "The address where the redeemed tokens were originally held", + "type": "string" + } + }, + "required": [ + "issuer_request_id", + "underlying_symbol", + "token_symbol", + "qty", + "network", + "wallet_address", + "tx_hash" + ], + "title": "TokenizationRedeemRequest", + "type": "object" + }, + "TokenizationRedeemResponse": { + "properties": { + "created_at": { + "format": "date-time", + "type": "string" + }, + "issuer": { + "$ref": "#/components/schemas/TokenizationIssuer" + }, + "issuer_request_id": { + "description": "Unique identifier of the redemption request set by the issuer", + "type": "string" + }, + "network": { + "$ref": "#/components/schemas/TokenizationNetwork" + }, + "qty": { + "description": "The quantity to convert into the underlying asset. It can be fractional.", + "type": "string" + }, + "status": { + "$ref": "#/components/schemas/TokenizationRequestStatus" + }, + "token_symbol": { + "description": "The tokenized asset symbol", + "type": "string" + }, + "tokenization_request_id": { + "description": "Unique identifier of the tokenization request set by Alpaca", + "type": "string" + }, + "tx_hash": { + "description": "Transaction hash of the completed request on the blockchain", + "type": "string" + }, + "type": { + "$ref": "#/components/schemas/TokenizationRequestType" + }, + "underlying_symbol": { + "description": "The underlying asset symbol", + "type": "string" + }, + "wallet_address": { + "description": "The address where the redeemed tokens were originally held", + "type": "string" + } + }, + "required": [ + "tokenization_request_id", + "issuer_request_id", + "created_at", + "type", + "status", + "underlying_symbol", + "token_symbol", + "qty", + "issuer", + "network", + "wallet_address", + "tx_hash" + ], + "title": "TokenizationRedeemResponse", + "type": "object" + }, + "TokenizationRequest": { + "properties": { + "account": { + "description": "Alpaca account ID associated with this tokenization request", + "type": "string" + }, + "created_at": { + "format": "date-time", + "type": "string" + }, + "fees": { + "description": "Fees charged for this tokenization request", + "nullable": true, + "type": "string" + }, + "issuer": { + "$ref": "#/components/schemas/TokenizationIssuer" + }, + "issuer_account": { + "description": "Issuer's account ID associated with this tokenization request", + "type": "string" + }, + "issuer_request_id": { + "description": "Unique identifier of the tokenization request set by the issuer", + "nullable": true, + "type": "string" + }, + "network": { + "$ref": "#/components/schemas/TokenizationNetwork" + }, + "qty": { + "description": "The quantity to convert for this tokenization request. It can be fractional.", + "type": "string" + }, + "status": { + "$ref": "#/components/schemas/TokenizationRequestStatus" + }, + "token_symbol": { + "description": "The tokenized asset symbol", + "type": "string" + }, + "tokenization_request_id": { + "description": "Unique identifier of the tokenization request set by Alpaca", + "type": "string" + }, + "tx_hash": { + "description": "Transaction hash of the completed request on the blockchain", + "nullable": true, + "type": "string" + }, + "type": { + "$ref": "#/components/schemas/TokenizationRequestType" + }, + "underlying_symbol": { + "description": "The underlying asset symbol", + "type": "string" + }, + "updated_at": { + "format": "date-time", + "nullable": true, + "type": "string" + }, + "wallet_address": { + "description": "The wallet address associated with this tokenization request", + "type": "string" + } + }, + "required": [ + "tokenization_request_id", + "created_at", + "type", + "status", + "underlying_symbol", + "token_symbol", + "qty", + "issuer", + "network", + "wallet_address" + ], + "title": "TokenizationRequest", + "type": "object" + }, + "TokenizationRequestStatus": { + "description": "Status of the tokenization request", + "enum": [ + "pending", + "rejected", + "completed" + ], + "example": "completed", + "title": "TokenizationRequestStatus", + "type": "string" + }, + "TokenizationRequestType": { + "description": "Tokenization request type", + "enum": [ + "mint", + "redeem" + ], + "example": "mint", + "title": "TokenizationRequestType", + "type": "string" + }, + "TradeAccount": { + "description": "This is an extended version of the Account model found [in the trading api](https://alpaca.markets/docs/api-references/trading-api/account/#account-entity).\n\nExtra data has been added that would be useful for brokers.", + "properties": { + "account_blocked": { + "description": "If true, the account activity by user is prohibited.", + "example": false, + "type": "boolean" + }, + "account_number": { + "description": "The account number", + "example": "927584925", + "nullable": true, + "type": "string" + }, + "accrued_fees": { + "description": "Accrued fees", + "example": "12345.6789", + "format": "decimal", + "type": "string" + }, + "admin_configurations": { + "$ref": "#/components/schemas/AdminConfigurations" + }, + "balance_asof": { + "description": "The date of the snapshot for `last_*` fields", + "example": "2021-04-01", + "type": "string" + }, + "bod_dtbp": { + "deprecated": true, + "description": "bod_dtbp", + "example": "12345.6789", + "format": "decimal", + "type": "string", + "x-deprecation": { + "reason": "See https://docs.alpaca.markets/us/docs/understanding-finras-new-intraday-margin-rule-and-the-end-of-pdt", + "since": "2026-04-27T00:00:00Z", + "sunset": "2026-07-06T00:00:00Z" + } + }, + "buying_power": { + "description": "Current available cash buying power. If multiplier = 2 then buying_power = max(equity-initial_margin(0) * 2). If multiplier = 1 then buying_power = cash.", + "example": "12345.6789", + "format": "decimal", + "type": "string" + }, + "cash": { + "description": "Cash balance", + "example": "12345.6789", + "format": "decimal", + "type": "string" + }, + "cash_transferable": { + "description": "Cash available for transfer (JNLC)", + "example": "12345.6789", + "type": "string" + }, + "cash_withdrawable": { + "description": "Cash available for withdrawal", + "example": "12345.6789", + "format": "decimal", + "type": "string" + }, + "clearing_broker": { + "description": "Clearing broker", + "example": "Velox", + "type": "string" + }, + "created_at": { + "description": "Timestamp this account was created at", + "example": "2021-03-01T13:28:49.270232Z", + "type": "string" + }, + "crypto_status": { + "$ref": "#/components/schemas/AccountStatus" + }, + "currency": { + "description": "Always USD", + "example": "USD", + "type": "string" + }, + "daytrade_count": { + "deprecated": true, + "description": "The current number of daytrades that have been made in the last 5 trading days (inclusive of today)", + "example": 0, + "type": "integer", + "x-deprecation": { + "reason": "See https://docs.alpaca.markets/us/docs/understanding-finras-new-intraday-margin-rule-and-the-end-of-pdt", + "since": "2026-04-27T00:00:00Z", + "sunset": "2026-07-06T00:00:00Z" + } + }, + "daytrading_buying_power": { + "deprecated": true, + "description": "Your buying power for day trades (continuously updated value)", + "example": "12345.6789", + "format": "decimal", + "type": "string", + "x-deprecation": { + "reason": "See https://docs.alpaca.markets/us/docs/understanding-finras-new-intraday-margin-rule-and-the-end-of-pdt", + "since": "2026-04-27T00:00:00Z", + "sunset": "2026-07-06T00:00:00Z" + } + }, + "effective_buying_power": { + "description": "Effective buying power (duplicate of buying power)", + "example": "12345.6789", + "format": "decimal", + "type": "string" + }, + "equity": { + "description": "cash + long_market_value + short_market_value", + "example": "12345.6789", + "format": "decimal", + "type": "string" + }, + "id": { + "description": "The account ID", + "example": "c8f1ef5d-edc0-4f23-9ee4-378f19cb92a4", + "format": "uuid", + "type": "string" + }, + "initial_margin": { + "description": "Reg T initial margin requirement (continuously updated value)", + "example": "12345.6789", + "format": "decimal", + "type": "string" + }, + "intraday_adjustments": { + "description": "The intraday adjustment by non_trade_activities such as fund deposit/withdraw.\n", + "example": "0", + "type": "string" + }, + "last_buying_power": { + "description": "Value of buying_power as of previous trading day at 16:00:00 ET", + "example": "12345.6789", + "type": "string" + }, + "last_cash": { + "description": "Value of all cash as of previous trading day at 16:00:00 ET", + "example": "12345.6789", + "type": "string" + }, + "last_daytrade_count": { + "deprecated": true, + "description": "Value of daytrade count as of previous trading day at 16:00:00 ET", + "example": 0, + "type": "integer", + "x-deprecation": { + "reason": "See https://docs.alpaca.markets/us/docs/understanding-finras-new-intraday-margin-rule-and-the-end-of-pdt", + "since": "2026-04-27T00:00:00Z", + "sunset": "2026-07-06T00:00:00Z" + } + }, + "last_daytrading_buying_power": { + "deprecated": true, + "description": "Value of daytrading buying power as of previous trading day at 16:00:00 ET", + "example": "12345.6789", + "type": "string", + "x-deprecation": { + "reason": "See https://docs.alpaca.markets/us/docs/understanding-finras-new-intraday-margin-rule-and-the-end-of-pdt", + "since": "2026-04-27T00:00:00Z", + "sunset": "2026-07-06T00:00:00Z" + } + }, + "last_equity": { + "description": "Equity as of previous trading day at 16:00:00 ET", + "example": "12345.6789", + "format": "decimal", + "type": "string" + }, + "last_initial_margin": { + "description": "Value of Reg T margin as of previous trading day at 16:00:00 ET", + "example": "12345.6789", + "type": "string" + }, + "last_long_market_value": { + "description": "Value of all long positions as of previous trading day at 16:00:00 ET", + "example": "12345.6789", + "type": "string" + }, + "last_maintenance_margin": { + "description": "Maintenance margin requirement on the previous trading day", + "example": "12345.6789", + "format": "decimal", + "type": "string" + }, + "last_options_buying_power": { + "description": "Value of option buying power as of previous trading day at 16:00:00 ET", + "example": "12345.6789", + "type": "string" + }, + "last_regt_buying_power": { + "description": "Value of Reg T buying power as of previous trading day at 16:00:00 ET", + "example": "12345.6789", + "type": "string" + }, + "last_short_market_value": { + "description": "Value of all short positions as of previous trading day at 16:00:00 ET", + "example": "0", + "type": "string" + }, + "long_market_value": { + "description": "Real-time MtM value of all long positions held in the account", + "example": "12345.6789", + "format": "decimal", + "type": "string" + }, + "maintenance_margin": { + "description": "Maintenance margin requirement (continuously updated value)", + "example": "12345.6789", + "format": "decimal", + "type": "string" + }, + "memoposts": { + "description": "Outstanding memopost value", + "example": "100", + "type": "string" + }, + "multiplier": { + "description": "\"1\", \"2\", \"3\", or \"4\"", + "example": "2", + "format": "decimal", + "type": "string" + }, + "non_marginable_buying_power": { + "description": "Non-marginable buying power (currently used for only crypto trading)", + "example": "12345.6789", + "format": "decimal", + "type": "string" + }, + "options_approved_level": { + "description": "The options trading level that was approved for this account.\n0=disabled, 1=Covered Call/Cash-Secured Put, 2=Long Call/Put, 3=Spreads/Straddles.\n", + "enum": [ + 0, + 1, + 2, + 3 + ], + "example": 3, + "type": "integer" + }, + "options_buying_power": { + "description": "Your buying power for options trading", + "example": "12345.6789", + "format": "decimal", + "type": "string" + }, + "options_trading_level": { + "description": "The effective options trading level of the account.\nThis is the minimum between account options_approved_level and account configurations max_options_trading_level.\n0=disabled, 1=Covered Call/Cash-Secured Put, 2=Long Call/Put, 3=Spreads/Straddles.\n", + "enum": [ + 0, + 1, + 2, + 3 + ], + "example": 3, + "type": "integer" + }, + "pattern_day_trader": { + "deprecated": true, + "description": "Whether account is flagged as pattern day trader or not", + "example": false, + "type": "boolean", + "x-deprecation": { + "reason": "See https://docs.alpaca.markets/us/docs/understanding-finras-new-intraday-margin-rule-and-the-end-of-pdt", + "since": "2026-04-27T00:00:00Z", + "sunset": "2026-07-06T00:00:00Z" + } + }, + "pending_reg_taf_fees": { + "description": "Pending regulatory fees for the account.", + "example": "0.01", + "type": "string" + }, + "pending_transfer_out": { + "description": "Cash pending transfer out", + "example": "12345.6789", + "type": "string" + }, + "portfolio_value": { + "description": "Total value of cash + holding positions. (This field is deprecated. It is equivalent to the equity field.)", + "example": "12345.6789", + "format": "decimal", + "type": "string" + }, + "position_market_value": { + "description": "Real-time MtM value of all the positions held in the account", + "example": "0", + "format": "decimal", + "type": "string" + }, + "previous_close": { + "description": "Previous sessions close time", + "example": "2021-04-01T19:00:00-04:00", + "type": "string" + }, + "regt_buying_power": { + "description": "User's buying power under Regulation T (excess equity - (equity - margin value) - * margin multiplier)", + "example": "12345.6789", + "format": "decimal", + "type": "string" + }, + "short_market_value": { + "description": "Real-time MtM value of all short positions held in the account", + "example": "0", + "format": "decimal", + "type": "string" + }, + "shorting_enabled": { + "description": "Flag to denote whether or not the account is permitted to short", + "example": false, + "type": "boolean" + }, + "sma": { + "description": "Value of Special Memorandum Account (will be used at a later date to provide additional buying_power)", + "example": "12345.6789", + "format": "decimal", + "type": "string" + }, + "status": { + "$ref": "#/components/schemas/AccountStatus" + }, + "trade_suspended_by_user": { + "description": "If true, the account is not allowed to place orders.", + "example": false, + "type": "boolean" + }, + "trading_blocked": { + "description": "If true, the account is not allowed to place orders.", + "example": false, + "type": "boolean" + }, + "transfers_blocked": { + "description": "If true, the account is not allowed to request money transfers.", + "example": false, + "type": "boolean" + }, + "user_configurations": { + "$ref": "#/components/schemas/AccountConfigurations" + } + }, + "required": [ + "id", + "admin_configurations", + "user_configurations", + "account_number", + "status", + "crypto_status", + "currency", + "buying_power", + "regt_buying_power", + "daytrading_buying_power", + "effective_buying_power", + "non_marginable_buying_power", + "bod_dtbp", + "accrued_fees", + "portfolio_value", + "pattern_day_trader", + "trading_blocked", + "transfers_blocked", + "account_blocked", + "created_at", + "trade_suspended_by_user", + "multiplier", + "shorting_enabled", + "equity", + "last_equity", + "long_market_value", + "short_market_value", + "position_market_value", + "initial_margin", + "maintenance_margin", + "last_maintenance_margin", + "sma", + "daytrade_count", + "balance_asof", + "cash" + ], + "type": "object", + "x-examples": { + "example-1": { + "account_blocked": false, + "account_number": "927584925", + "accrued_fees": "0", + "balance_asof": "2021-04-01", + "buying_power": "103556.8572572922", + "cash": "24861.91", + "cash_transferable": "24861.91", + "cash_withdrawable": "17861.91", + "clearing_broker": "VELOX", + "created_at": "2021-03-01T13:28:49.270232Z", + "currency": "USD", + "daytrade_count": 0, + "daytrading_buying_power": "103556.8572572922", + "equity": "28059.3882330664", + "id": "c8f1ef5d-edc0-4f23-9ee4-378f19cb92a4", + "initial_margin": "1598.7391165332", + "intraday_adjustments": "0", + "last_buying_power": "104433.9158860662", + "last_cash": "23861.91", + "last_daytrade_count": 0, + "last_daytrading_buying_power": "104433.9158860662", + "last_equity": "26977.323677655", + "last_initial_margin": "1557.7068388275", + "last_long_market_value": "3115.413677655", + "last_maintenance_margin": "934.6241032965", + "last_options_buying_power": "25419.62", + "last_regt_buying_power": "50839.233677655", + "last_short_market_value": "0", + "long_market_value": "3197.4782330664", + "maintenance_margin": "959.24346991992", + "multiplier": "2", + "options_approved_level": 0, + "options_buying_power": "26460.65", + "options_trading_level": 0, + "pattern_day_trader": true, + "pending_reg_taf_fees": "0", + "pending_transfer_out": "0", + "portfolio_value": "28059.3882330664", + "previous_close": "2021-04-01T19:00:00-04:00", + "regt_buying_power": "52921.2982330664", + "short_market_value": "0", + "shorting_enabled": true, + "sma": "26758.0590204615", + "status": "ACTIVE", + "trade_suspended_by_user": false, + "trading_blocked": false, + "transfers_blocked": false + }, + "example-2": { + "account_blocked": false, + "account_number": "601612064", + "accrued_fees": "0", + "balance_asof": "2022-02-08", + "buying_power": "83567.42", + "cash": "83567.42", + "cash_transferable": "41783.71", + "cash_withdrawable": "0", + "clearing_broker": "VELOX", + "created_at": "2022-01-21T21:25:26.713802Z", + "crypto_status": "PAPER_ONLY", + "currency": "USD", + "daytrade_count": 0, + "daytrading_buying_power": "0", + "equity": "83567.42", + "id": "56712986-9ff7-4d8f-8e52-077e099e533e", + "initial_margin": "0", + "intraday_adjustments": "0", + "last_buying_power": "41783.71", + "last_cash": "41783.71", + "last_daytrade_count": 0, + "last_daytrading_buying_power": "0", + "last_equity": "41783.71", + "last_initial_margin": "0", + "last_long_market_value": "0", + "last_maintenance_margin": "0", + "last_options_buying_power": "41783.71", + "last_regt_buying_power": "41783.71", + "last_short_market_value": "0", + "long_market_value": "0", + "maintenance_margin": "0", + "multiplier": "1", + "non_marginable_buying_power": "41783.71", + "options_approved_level": 2, + "options_buying_power": "83567.42", + "options_trading_level": 1, + "pattern_day_trader": false, + "pending_reg_taf_fees": "0.01", + "pending_transfer_in": "0", + "pending_transfer_out": "0", + "portfolio_value": "83567.42", + "previous_close": "2022-02-08T19:00:00-05:00", + "regt_buying_power": "83567.42", + "short_market_value": "0", + "shorting_enabled": false, + "sma": "0", + "status": "ACTIVE", + "trade_suspended_by_user": false, + "trading_blocked": false, + "transfers_blocked": false + } + } + }, + "TradeActivity": { + "properties": { + "cum_qty": { + "description": "Valid only for trading activity types. Null for non-trading activites.", + "example": "0.9723", + "format": "decimal", + "type": "string" + }, + "leaves_qty": { + "description": "Valid only for trading activity types. Null for non-trading activites.", + "example": "0.5123", + "format": "decimal", + "type": "string" + }, + "order_id": { + "description": "Valid only for trading activity types. Null for non-trading activites.", + "example": "fe060a1b-5b45-4eba-ba46-c3a3345d8255", + "format": "uuid", + "type": "string" + }, + "order_status": { + "$ref": "#/components/schemas/OrderStatus" + }, + "price": { + "description": "Valid only for trading activity types. Null for non-trading activites.", + "example": "3.1415", + "format": "decimal", + "type": "string" + }, + "qty": { + "description": "Valid only for trading activity types. Null for non-trading activites.", + "example": "0.38921", + "format": "decimal", + "type": "string" + }, + "side": { + "$ref": "#/components/schemas/OrderSide" + }, + "symbol": { + "description": "Valid only for trading activity types. Null for non-trading activites.", + "example": "AAPL", + "type": "string" + }, + "transaction_time": { + "description": "Valid only for trading activity types. Null for non-trading activites.", + "example": "2021-05-10T14:01:04.650275Z", + "format": "date-time", + "type": "string" + }, + "type": { + "description": "Valid only for trading activity types. Null for non-trading activites.", + "enum": [ + "fill", + "partial_fill" + ], + "example": "fill", + "type": "string" + } + }, + "title": "TradeActivity", + "type": "object" + }, + "TradeUpdateEvent": { + "description": "Represents an update to an order/trade, sent over the events streaming api.", + "properties": { + "account_id": { + "description": "Account UUID", + "format": "uuid", + "minLength": 1, + "type": "string" + }, + "at": { + "description": "Timestamp of event", + "format": "date-time", + "minLength": 1, + "type": "string" + }, + "event": { + "$ref": "#/components/schemas/TradeUpdateEventType" + }, + "event_id": { + "description": "Monotonically increasing 64bit integer", + "format": "int64", + "type": "integer" + }, + "event_ulid": { + "description": "lexically sortable, monotonically increasing character array", + "format": "ulid", + "type": "string" + }, + "execution_id": { + "description": "Corresponding execution of an order. If an order gets filled over two executions (a partial_fill for example), you will receive two events with different IDs.", + "format": "uuid", + "type": "string" + }, + "order": { + "$ref": "#/components/schemas/Order" + }, + "position_qty": { + "description": "Only present when event is either `fill` or `partial_fill`. The size of your total position, after this fill event, in shares. Positive for long positions, negative for short positions.", + "type": "string" + }, + "previous_execution_id": { + "description": "ID of the original execution that was busted or corrected (present only in trade_bust and trade_correct events).", + "format": "uuid", + "type": "string" + }, + "price": { + "description": "Only present when event is either `fill` or `partial_fill`. The average price per share at which the order was filled.", + "type": "string" + }, + "qty": { + "description": "Only present when event is either `fill` or `partial_fill`. The amount of shares this Trade order was for", + "type": "string" + }, + "timestamp": { + "description": "Has various different meanings depending on the value of `event`, please see the [Trading Events](https://alpaca.markets/docs/api-references/broker-api/events/#trade-events)\nEnum in the documentation or the TradeUpdateEventType model for more details on when it means different things.\n", + "format": "date-time", + "type": "string" + } + }, + "required": [ + "account_id", + "at", + "event", + "event_id", + "event_ulid", + "execution_id", + "order", + "timestamp" + ], + "title": "TradeUpdateEvent", + "type": "object", + "x-examples": { + "example-1": { + "account_id": "529248ad-c4cc-4a50-bea4-6bfd2953f83a", + "at": "2022-04-19T14:12:30.656741Z", + "event": "new", + "event_id": 37782, + "event_ulid": "01G112NTT0XAXKDZK3AABK68TH", + "execution_id": "7e544af3-3104-4e1a-8cbc-dab2624949ff", + "order": { + "asset_class": "us_equity", + "asset_id": "a4778bc8-fad1-47b7-87fe-d5cde10d43f4", + "canceled_at": null, + "client_order_id": "6d873193-dac6-4f72-8e13-c57853a9339d", + "commission": "1", + "created_at": "2022-04-19T10:12:30.57117938-04:00", + "expired_at": null, + "extended_hours": false, + "failed_at": null, + "filled_at": null, + "filled_avg_price": null, + "filled_qty": "0", + "hwm": null, + "id": "edada91a-8b55-4916-a153-8c7a9817e708", + "legs": null, + "limit_price": "700", + "notional": null, + "order_class": "", + "order_type": "limit", + "qty": "4", + "replaced_at": null, + "replaced_by": null, + "replaces": null, + "side": "buy", + "status": "new", + "stop_price": null, + "submitted_at": "2022-04-19T10:12:30.403135025-04:00", + "symbol": "TSLA", + "time_in_force": "day", + "trail_percent": null, + "trail_price": null, + "type": "limit", + "updated_at": "2022-04-19T10:12:30.609783218-04:00" + }, + "timestamp": "2022-04-19T14:12:30.602193534Z" + } + } + }, + "TradeUpdateEventType": { + "description": "**Common events**\n\nThese are the events that are the expected results of actions you may have taken by sending API requests.\n\nThe meaning of the `timestamp` field changes for each type; the meanings have been specified here for which types the\ntimestamp field will be present.\n\n- `new`: Sent when an order has been routed to exchanges for execution.\n- `fill`: Sent when your order has been completely filled.\n - `timestamp`: The time at which the order was filled.\n- `partial_fill`: Sent when a number of shares less than the total remaining quantity on your order has been filled.\n - `timestamp`: The time at which the shares were filled.\n- `canceled`: Sent when your requested cancellation of an order is processed.\n - `timestamp`: The time at which the order was canceled.\n- `expired`: Sent when an order has reached the end of its lifespan, as determined by the order's time in force value.\n - `timestamp`: The time at which the order expired.\n- `done_for_day`: Sent when the order is done executing for the day, and will not receive further updates until the next trading day.\n- `replaced`: Sent when your requested replacement of an order is processed.\n - `timestamp`: The time at which the order was replaced.\n\n**Rarer events**\n\nThese are events that may rarely be sent due to unexpected circumstances on the exchanges. It is unlikely you will need to design your code around them, but you may still wish to account for the possibility that they will occur.\n\n- `rejected`: Sent when your order has been rejected.\n - `timestamp`: The time at which the rejection occurred.\n- `pending_new`: Sent when the order has been received by Alpaca and routed to the exchanges, but has not yet been accepted for execution.\n- `stopped`: Sent when your order has been stopped, and a trade is guaranteed for the order, usually at a stated price or better, but has not yet occurred.\n- `pending_cancel`: Sent when the order is awaiting cancellation. Most cancellations will occur without the order entering this state.\n- `pending_replace`: Sent when the order is awaiting replacement.\n- `calculated`: Sent when the order has been completed for the day - it is either `filled` or `done_for_day` - but remaining settlement calculations are still pending.\n- `suspended`: Sent when the order has been suspended and is not eligible for trading.\n- `order_replace_rejected`: Sent when the order replace has been rejected.\n- `order_cancel_rejected`: Sent when the order cancel has been rejected.\n- `trade_bust`: Sent when a previously reported execution has been canceled (\"busted\") by the upstream exchange.\n- `trade_correct`: Sent when a previously reported trade has been corrected. For example, the exchange may have updated the price, quantity, or another execution parameter after the trade was initially reported.\n- `restated`: Sent when the order is manually modified.\n", + "enum": [ + "new", + "fill", + "partial_fill", + "canceled", + "expired", + "done_for_day", + "replaced", + "rejected", + "pending_new", + "stopped", + "pending_cancel", + "pending_replace", + "calculated", + "suspended", + "order_replace_rejected", + "order_cancel_rejected", + "trade_bust", + "trade_correct" + ], + "type": "string" + }, + "TradeUpdateEventV2": { + "description": "Represents an update to an order/trade, sent over the events streaming api.", + "properties": { + "account_id": { + "description": "Account UUID", + "format": "uuid", + "minLength": 1, + "type": "string" + }, + "at": { + "description": "Timestamp of event", + "format": "date-time", + "minLength": 1, + "type": "string" + }, + "event": { + "$ref": "#/components/schemas/TradeUpdateEventType" + }, + "event_id": { + "description": "lexically sortable, monotonically increasing character array", + "format": "ulid", + "type": "string" + }, + "execution_id": { + "description": "Corresponding execution of an order. If an order gets filled over two executions (a partial_fill for example), you will receive two events with different IDs.\nNot present for `MultilegOptions`\n", + "format": "uui", + "type": "string" + }, + "legs": { + "description": "Only present when event is for `MultilegOptions`. Represents filled qty/price of legs.\n", + "items": { + "$ref": "#/components/schemas/TradeUpdateEventV2Leg" + }, + "type": "array" + }, + "order": { + "$ref": "#/components/schemas/Order" + }, + "position_qty": { + "description": "Only present when event is either `fill` or `partial_fill` other than `MultilegOptions`. The size of your total position, after this fill event, in shares. Positive for long positions, negative for short positions.\n", + "type": "string" + }, + "position_qtys": { + "description": "Only present when event is either `fill` or `partial_fill` for `MultilegOptions`. The size of your total position, after this fill event, in shares. Positive for long positions, negative for short positions.\n", + "type": "string" + }, + "previous_execution_id": { + "description": "ID of the original execution that was busted or corrected (present only in trade_bust and trade_correct events).", + "format": "uuid", + "type": "string" + }, + "price": { + "description": "Only present when event is either `fill` or `partial_fill`. The average price per share at which the order was filled.", + "type": "string" + }, + "qty": { + "description": "Only present when event is either `fill`, `partial_fill`, `trade_bust` and `trade_correct`. The amount of shares this Trade order was for.
For `trade_bust` events, the qty field may be negative, representing a reversal of the original quantity.", + "type": "string" + }, + "settle_date": { + "description": "Settlement date of the trade in format `YYYY-MM-DD` for `fill` and `partial_fill` events", + "example": "2026-03-03", + "format": "date", + "type": "string" + }, + "swap_rate": { + "description": "Only present for `local currency trading account` or `crypto asset trade` when event is either `fill` or `partial_fill`. The swap rate at which the current trade was filled.", + "type": "string" + }, + "timestamp": { + "description": "Has various different meanings depending on the value of `event`, please see the [Trading Events](https://alpaca.markets/docs/api-references/broker-api/events/#trade-events)\nEnum in the documentation or the TradeUpdateEventType model for more details on when it means different things.\n", + "format": "date-time", + "type": "string" + } + }, + "title": "TradeUpdateEvent", + "type": "object" + }, + "TradeUpdateEventV2Leg": { + "description": "Represents filled qty/price of legs.", + "properties": { + "execution_id": { + "description": "Corresponding execution of an order. If an order gets filled over two executions (a partial_fill for example), you will receive two events with different IDs.", + "format": "uuid", + "type": "string" + }, + "order_id": { + "description": "Order UUID", + "format": "uuid", + "minLength": 1, + "type": "string" + }, + "price": { + "description": "The average price per share for this event.", + "type": "string" + }, + "qty": { + "description": "The amount of shares filled for this event", + "type": "string" + }, + "symbol": { + "description": "Symbol of an asset", + "type": "string" + }, + "timestamp": { + "description": "Timestamp of this event leg\n", + "format": "date-time", + "type": "string" + } + }, + "title": "TradeUpdateEventV2Leg", + "type": "object" + }, + "Transaction": { + "properties": { + "account_id": { + "description": "The ledger ID", + "type": "string" + }, + "account_name": { + "description": "The ledger name", + "type": "string" + }, + "account_no": { + "description": "The ledger account number", + "type": "string" + }, + "amount": { + "description": "Total amount of the transaction", + "type": "string" + }, + "balance": { + "description": "Ending balance after thetransaction has been applied", + "type": "string" + }, + "contra_account_name": { + "description": "Contra account of transaction", + "type": "string" + }, + "description": { + "description": "Plain text overview of the transaction", + "type": "string" + }, + "entry_type": { + "description": "Type of transaction", + "type": "string" + }, + "system_date": { + "description": "Date of transaction", + "type": "string" + } + }, + "type": "object", + "x-examples": { + "Example 1": { + "account_id": "06b9d76e-1733-460c-844e-48d1ae0f10c3", + "account_name": "Just In Time Receivable - JITS", + "account_no": "JTRJITS00", + "amount": "1", + "balance": "4186.36", + "contra_account_name": "Just In Time Interest Income", + "description": "debit balance = 4185.36, base_rate = 0.0375, spread = 0.05", + "entry_type": "JNLC", + "system_date": "2022-06-10" + } + } + }, + "Transfer": { + "description": "Transfers allow you to transfer money/balance into your end customers' account (deposits) or out (withdrawal).\n\n[Main docs here](https://alpaca.markets/docs/api-references/broker-api/funding/transfers/#the-transfer-object)", + "properties": { + "account_id": { + "description": "The account ID", + "format": "uuid", + "type": "string" + }, + "additional_information": { + "description": "Additional information. Only applies when type = \"wire\".", + "nullable": true, + "type": "string" + }, + "amount": { + "description": "Must be > 0.00", + "format": "decimal", + "type": "string" + }, + "bank_id": { + "description": "The ID of the Bank, only present if type = \"wire\"", + "format": "uuid", + "type": "string" + }, + "created_at": { + "description": "Timestamp when transfer was created", + "format": "date-time", + "type": "string" + }, + "direction": { + "$ref": "#/components/schemas/TransferDirection" + }, + "expires_at": { + "description": "Timestamp when transfer expires", + "format": "date-time", + "type": "string" + }, + "fee": { + "description": "Fee amount to be collected. Only applies when type = \"wire\".", + "format": "decimal", + "nullable": true, + "type": "string" + }, + "fee_payment_method": { + "description": "Either \"user\" or \"invoice\". Only applies when type = \"wire\".", + "nullable": true, + "type": "string" + }, + "hold_until": { + "format": "date-time", + "type": "string" + }, + "id": { + "description": "The transfer ID", + "format": "uuid", + "type": "string" + }, + "instant_amount": { + "type": "string" + }, + "ira": { + "$ref": "#/components/schemas/TransferIRADetails" + }, + "reason": { + "description": "Cause of the status", + "nullable": true, + "type": "string" + }, + "relationship_id": { + "description": "The ACH relationship ID only present if type = \"ach\"", + "format": "uuid", + "type": "string" + }, + "requested_amount": { + "description": "Must be > 0.00. Only applies when type = \"wire\".", + "format": "decimal", + "nullable": true, + "type": "string" + }, + "status": { + "$ref": "#/components/schemas/TransferStatus" + }, + "type": { + "$ref": "#/components/schemas/TransferType" + }, + "updated_at": { + "description": "Timestamp when transfer was updated", + "format": "date-time", + "type": "string" + } + }, + "required": [ + "id", + "account_id", + "type", + "status", + "amount", + "direction", + "created_at" + ], + "title": "Transfer", + "type": "object", + "x-examples": { + "example-1": { + "account_id": "449e7a5c-69d3-4b8a-aaaf-5c9b713ebc65", + "additional_information": "string", + "amount": "string", + "bank_id": "f1ae96de-94c1-468e-93a3-6b7213930ca8", + "created_at": "2019-08-24T14:15:22Z", + "direction": "INCOMING", + "expires_at": "2019-08-24T14:15:22Z", + "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08", + "ira": { + "distribution_reason": "normal", + "fed_withholding_amount": "102.5", + "fed_withholding_pct": "10.25", + "state_withholding_amount": "97.5", + "state_withholding_pct": "9.75", + "tax_year": "2024" + }, + "reason": "string", + "relationship_id": "81412018-ffa2-43f9-a3eb-d39f1c5e0f87", + "status": "QUEUED", + "type": "ach", + "updated_at": "2019-08-24T14:15:22Z" + } + } + }, + "TransferDirection": { + "description": "- **INCOMING**\nFunds incoming to user's account (deposit).\n- **OUTGOING**\nFunds outgoing from user's account (withdrawal).\n", + "enum": [ + "INCOMING", + "OUTGOING" + ], + "example": "INCOMING", + "type": "string" + }, + "TransferFee": { + "properties": { + "amount": { + "format": "decimal", + "type": "string" + }, + "currency": { + "$ref": "#/components/schemas/Currency" + }, + "payment_type": { + "$ref": "#/components/schemas/FeePaymentType" + }, + "type": { + "$ref": "#/components/schemas/FeeType" + } + }, + "required": [ + "type", + "amount", + "currency", + "payment_type" + ], + "type": "object" + }, + "TransferIRA": { + "description": "This field is used for IRA Account only", + "properties": { + "distribution_reason": { + "example": "normal", + "type": "string" + }, + "tax_withholding": { + "$ref": "#/components/schemas/TransferIRATaxWithholding" + }, + "tax_year": { + "example": "2024", + "type": "string" + } + }, + "type": "object" + }, + "TransferIRADetails": { + "properties": { + "distribution_reason": { + "example": "normal", + "type": "string" + }, + "fed_withholding_amount": { + "example": "102.5", + "type": "string" + }, + "fed_withholding_pct": { + "example": "10.25", + "type": "string" + }, + "state_withholding_amount": { + "example": "97.5", + "type": "string" + }, + "state_withholding_pct": { + "example": "9.75", + "type": "string" + }, + "tax_year": { + "example": "2024", + "type": "string" + } + }, + "type": "object" + }, + "TransferIRATaxWithholding": { + "properties": { + "fed_pct": { + "example": "10.25", + "type": "string" + }, + "state_pct": { + "example": "8.25", + "type": "string" + } + }, + "type": "object" + }, + "TransferStatus": { + "description": "- **QUEUED**\nTransfer is in queue to be processed.\n- **APPROVAL_PENDING**\nTransfer is pending approval.\n- **PENDING**\nTransfer is pending processing.\n- **SENT_TO_CLEARING**\nTransfer is being processed by the clearing firm.\n- **REJECTED**\nTransfer is rejected.\n- **CANCELED**\nClient initiated transfer cancellation.\n- **APPROVED**\nTransfer is approved.\n- **COMPLETE**\nTransfer is completed.\n- **RETURNED**\nThe bank issued an ACH return for the transfer.\n", + "enum": [ + "QUEUED", + "APPROVAL_PENDING", + "PENDING", + "SENT_TO_CLEARING", + "REJECTED", + "CANCELED", + "APPROVED", + "COMPLETE", + "RETURNED" + ], + "example": "QUEUED", + "type": "string" + }, + "TransferStatusEvent": { + "description": "Represents a change in a Transfer's status, sent over the events streaming api.", + "properties": { + "account_id": { + "description": "Account UUID", + "format": "uuid", + "minLength": 1, + "type": "string" + }, + "at": { + "description": "Timestamp of when the transfer status changed", + "format": "date-time", + "minLength": 1, + "type": "string" + }, + "event_id": { + "description": "Monotonically increasing 64bit integer", + "type": "integer" + }, + "event_ulid": { + "description": "lexically sortable, monotonically increasing character array", + "format": "ulid", + "type": "string" + }, + "status_from": { + "$ref": "#/components/schemas/TransferStatus" + }, + "status_to": { + "$ref": "#/components/schemas/TransferStatus" + }, + "transfer_id": { + "description": "Transfer UUID", + "format": "uuid", + "minLength": 1, + "type": "string" + } + }, + "required": [ + "account_id", + "at", + "event_id", + "event_ulid", + "status_from", + "status_to", + "transfer_id" + ], + "title": "TransferStatusEvent", + "type": "object", + "x-examples": { + "example-1": { + "account_id": "8e00606a-c9ac-409a-ba45-f55e8f77984a", + "at": "2021-06-10T19:52:24.066998Z", + "event_id": 15961, + "event_ulid": "01F7VQQ782DM57SJNWAYMD14J9", + "status_from": "QUEUED", + "status_to": "SENT_TO_CLEARING", + "transfer_id": "c4ed4206-697b-4859-ab71-b9de6649859d" + }, + "example-2": { + "account_id": "8e00606a-c9ac-409a-ba45-f55e8f77984a", + "at": "2021-06-10T20:02:24.280178Z", + "event_id": 15962, + "status_from": "SENT_TO_CLEARING", + "status_to": "COMPLETE", + "transfer_id": "c4ed4206-697b-4859-ab71-b9de6649859d" + } + } + }, + "TransferType": { + "description": "- **ach**\nTransfer via ACH (US Only). Supports both `INCOMING` (deposit) and `OUTGOING` (withdrawal) directions.\n- **wire**\nTransfer via wire. `OUTGOING` (withdrawal) only.\n", + "enum": [ + "ach", + "wire" + ], + "example": "ach", + "title": "TransferType", + "type": "string" + }, + "TransmitterInfo": { + "description": "Information about the transmitter to satisfy travel rule requirements. Required if the requesting correspondent qualifies as a financial institution\n", + "properties": { + "originator_bank_account_number": { + "description": "Required if the requesting correspondent qualifies as a financial institution\n", + "type": "string" + }, + "originator_bank_name": { + "description": "Required if the requesting correspondent qualifies as a financial institution\n", + "type": "string" + }, + "originator_city": { + "type": "string" + }, + "originator_country": { + "description": "Required if the requesting correspondent qualifies as a financial institution\n", + "type": "string" + }, + "originator_full_name": { + "description": "Required if the requesting correspondent qualifies as a financial institution\n", + "type": "string" + }, + "originator_postal_code": { + "type": "string" + }, + "originator_state": { + "type": "string" + }, + "originator_street_address": { + "type": "string" + }, + "other_identifying_information": { + "description": "Used to facilitate transfer lookup in the event it is required. Recommended to be the originating bank's reference number for the transfer\n", + "type": "string" + } + }, + "type": "object" + }, + "TrustedContact": { + "description": "This model input is optional. However, the client should\nmake reasonable effort to obtain the trusted contact information.\nSee more details in [FINRA Notice 17-11](https://www.finra.org/sites/default/files/Regulatory-Notice-17-11.pdf)\n\nOnly one of the following is required:\n\n* email_address\t\n* phone_number\t\n* street_address\t\n", + "example": { + "email_address": "jane.doe@example.com", + "family_name": "Doe", + "given_name": "Jane" + }, + "properties": { + "city": { + "description": "required if `street_address` is set\n", + "type": "string" + }, + "country": { + "description": "[ISO 3166-1 alpha-3](https://www.iso.org/iso-3166-country-codes.html).\nrequired if `street_address` is set\n", + "type": "string" + }, + "email_address": { + "description": "at least one of `email_address`, `phone_number` or\n`street_address` is required\n", + "example": "jane.doe@example.com", + "format": "email", + "type": "string" + }, + "family_name": { + "example": "Doe", + "type": "string" + }, + "given_name": { + "example": "Jane", + "type": "string" + }, + "phone_number": { + "description": "at least one of `email_address`, `phone_number` or\n`street_address` is required\n", + "type": "string" + }, + "postal_code": { + "description": "required if `street_address` is set\n", + "type": "string" + }, + "state": { + "description": "required if `street_address` is set\n", + "type": "string" + }, + "street_address": { + "description": "at least one of `email_address`, `phone_number` or\n`street_address` is required\n", + "items": { + "type": "string" + }, + "type": "array" + } + }, + "required": [ + "given_name", + "family_name" + ], + "type": "object" + }, + "USDAccountTradingLimit": { + "properties": { + "available": { + "description": "The remaining net buying limit that can be used for trading.", + "format": "decimal", + "type": "string" + }, + "daily_net_limit": { + "description": "The net buying limit that can be reached before further trading activity is restricted. Please reach out to learn more about how this limit is determined.", + "format": "decimal", + "type": "string" + }, + "held": { + "description": "The limit that is currently being held for open orders", + "format": "decimal", + "type": "string" + }, + "used": { + "description": "The real time net value of cash inflows (buy trades, etc.) with cash outflows (sell trades, etc). This will be dynamic throughout the day based on user activity, with executed orders being reset at the start of the next trading day.", + "format": "decimal", + "type": "string" + } + }, + "title": "USDAccountTradingLimit", + "type": "object" + }, + "USDPosition": { + "description": "Position values in USD. This is returned for LCT (non-USD) accounts only.", + "properties": { + "avg_entry_price": { + "description": "Average entry price of the position in USD", + "example": "71.43", + "format": "decimal", + "type": "string" + }, + "change_today": { + "description": "Percent change from last day price (by a factor of 1)", + "example": "0.67", + "format": "decimal", + "type": "string" + }, + "cost_basis": { + "description": "Total cost basis in USD", + "example": "333.33", + "format": "decimal", + "type": "string" + }, + "current_price": { + "description": "Current asset price per share in USD", + "example": "80.0", + "format": "decimal", + "type": "string" + }, + "lastday_price": { + "description": "Last day's asset price per share based on the closing value of the last trading day in USD", + "example": "79.33", + "format": "decimal", + "type": "string" + }, + "market_value": { + "description": "Total market value of the position in USD", + "example": "400.00", + "format": "decimal", + "type": "string" + }, + "unrealized_intraday_pl": { + "description": "Unrealized profit/loss in USD for the day", + "example": "6.67", + "format": "decimal", + "type": "string" + }, + "unrealized_intraday_plpc": { + "description": "Unrealized interday profit/loss percent (by a factor of 1)", + "example": "0.0084", + "format": "decimal", + "type": "string" + }, + "unrealized_pl": { + "description": "Unrealized profit/loss in USD", + "example": "66.67", + "format": "decimal", + "type": "string" + }, + "unrealized_plpc": { + "description": "Unrealized profit/loss percent (by a factor of 1)", + "example": "0.2", + "format": "decimal", + "type": "string" + } + }, + "title": "USDPosition", + "type": "object" + }, + "UnitSplitActivityV2": { + "allOf": [ + { + "$ref": "#/components/schemas/CommonSplitStockActivityV2" + }, + { + "properties": { + "alternate_cusip": { + "description": "CUSIP for the alternate security after the split", + "type": "string" + }, + "alternate_qty": { + "description": "Quantity of alternate shares received", + "type": "string" + }, + "alternate_rate": { + "description": "Ratio of alternate shares received", + "type": "string" + }, + "alternate_symbol": { + "description": "Symbol for the alternate security after the split", + "type": "string" + }, + "effective_date": { + "description": "When the unit split becomes effective", + "format": "date", + "minLength": 1, + "type": "string" + }, + "new_symbol": { + "description": "Symbol of the new security after the split", + "type": "string" + }, + "old_symbol": { + "description": "Symbol of the old security before the split", + "type": "string" + } + }, + "required": [ + "old_symbol", + "new_symbol", + "alternate_cusip", + "alternate_symbol", + "alternate_rate", + "alternate_qty", + "effective_date" + ], + "type": "object" + } + ], + "description": "Unit split", + "type": "object" + }, + "UpdateOrderRequest": { + "description": "Represents the fields that are editable in an order replace/update call.\n\nNote: client_order_id is currently not editable on its own, one of the other fields must be changed at the same time to effectively replace the order.\n\nNote: `qty` and `notional` are mutually exclusive on a single replace request. `notional` is only valid for IPO indications of interest (`asset_class: \"ipo\"`); non-IPO notional orders cannot be replaced at all.", + "properties": { + "client_order_id": { + "description": "A unique identifier for the new order. Automatically generated if not sent. (<= 128 characters)", + "example": "61e69015-8549-4bfd-b9c3-01e75843f47d", + "maxLength": 128, + "type": "string" + }, + "limit_price": { + "description": "Required if original order's `type` field was `limit` or `stop_limit`.\nIn case of `mleg`, the limit_price parameter is expressed with the following notation:\n- A positive value indicates a debit, representing a cost or payment to be made.\n- A negative value signifies a credit, reflecting an amount to be received.", + "example": "3.14", + "format": "decimal", + "type": "string" + }, + "notional": { + "description": "New notional (dollar amount) for the order. Only valid for IPO indications of interest (`asset_class: \"ipo\"`); will be rejected for any other asset class.\nMutually exclusive with `qty` on the same replace request.", + "example": "750", + "format": "decimal", + "type": "string" + }, + "qty": { + "description": "You can only patch full shares for now.\n\nQty of equity fractional orders are not allowed to change. Non-IPO notional orders cannot be replaced at all - no fields (qty, limit_price, stop_price, etc.) can be modified; cancel and resubmit instead.\nIn case of multi-leg orders represents the number of units to trade of this strategy.", + "example": "4", + "format": "decimal", + "type": "string" + }, + "stop_price": { + "description": "Required if original order's `type` field was stop or stop_limit", + "example": "3.14", + "format": "decimal", + "type": "string" + }, + "time_in_force": { + "$ref": "#/components/schemas/TimeInForce" + }, + "trail": { + "description": "The new value of the trail_price or trail_percent", + "example": "3.14", + "format": "decimal", + "type": "string" + } + }, + "title": "OrderUpdateRequest", + "type": "object" + }, + "Usd": { + "properties": { + "amount": { + "format": "decimal", + "type": "string" + } + }, + "required": [ + "amount" + ], + "type": "object" + }, + "W8benDocument": { + "description": "Use this property (instead of the content property) to upload W-8 BEN data in JSON format.", + "properties": { + "additional_conditions": { + "description": "Any additional conditions to specify", + "type": "string" + }, + "country_citizen": { + "description": "The country that the applicant is a citizen of", + "type": "string" + }, + "date": { + "description": "date signed", + "format": "date", + "type": "string" + }, + "date_of_birth": { + "description": "date of birth of applicant", + "format": "date", + "type": "string" + }, + "foreign_tax_id": { + "description": "Applicant's tax id in their home country", + "type": "string" + }, + "ftin_not_required": { + "description": "Required if foreign_tax_id and tax_id_ssn are empty.", + "type": "boolean" + }, + "full_name": { + "description": "Full name of applicant", + "type": "string" + }, + "income_type": { + "description": "Income type of applicant", + "type": "string" + }, + "ip_address": { + "description": "IP address of applicant when signed", + "type": "string" + }, + "mailing_address_city_state": { + "description": "Mailing city/state of applicant", + "type": "string" + }, + "mailing_address_country": { + "description": "Mailing country for applicant", + "type": "string" + }, + "mailing_address_street": { + "description": "Mailing street address for applicant", + "type": "string" + }, + "paragraph_number": { + "type": "string" + }, + "percent_rate_withholding": { + "type": "integer" + }, + "permanent_address_city_state": { + "description": "Permanent city/state of applicant", + "type": "string" + }, + "permanent_address_country": { + "description": "Permanent country of residence of applicant", + "type": "string" + }, + "permanent_address_street": { + "description": "Permanent street address of applicant", + "type": "string" + }, + "reference_number": { + "type": "string" + }, + "residency": { + "description": "Country of residency of applicant", + "type": "string" + }, + "revision": { + "description": "Revision of the W8BEN form", + "type": "string" + }, + "signer_full_name": { + "description": "Full name of signing user", + "type": "string" + }, + "tax_id_ssn": { + "description": "TaxID/SSN of applicant", + "type": "string" + }, + "timestamp": { + "description": "Timestamp when form data was gathered", + "format": "time", + "type": "string" + } + }, + "required": [ + "country_citizen", + "date", + "date_of_birth", + "full_name", + "ip_address", + "permanent_address_city_state", + "permanent_address_country", + "permanent_address_street", + "revision", + "timestamp", + "signer_full_name" + ], + "type": "object", + "x-examples": { + "Example 1": { + "additional_conditions": "None", + "country_citizen": "Australia", + "date": "2021-06-14", + "date_of_birth": "1970-01-01", + "foreign_tax_id": "123 456 789", + "ftin_not_required": false, + "full_name": "John Doe", + "income_type": "interest", + "ip_address": "127.0.0.1", + "mailing_address_city_state": "Adelaide, South Australia", + "mailing_address_country": "Australia", + "mailing_address_street": "51 Main St", + "paragraph_number": "15", + "percent_rate_withholding": 5, + "permanent_address_city_state": "Adelaide, South Australia", + "permanent_address_country": "Australia", + "permanent_address_street": "20 Main St", + "reference_number": "abc123", + "residency": "Australia", + "revision": "10-2021", + "signer_full_name": "Mr. Signing User", + "tax_id_ssn": "123-00-456", + "timestamp": "2021-06-14T09:31:05Z" + } + } + }, + "WRMActivityV2": { + "allOf": [ + { + "$ref": "#/components/schemas/CommonCaActivityV2" + }, + { + "properties": { + "cusip": { + "description": "The CUSIP of the security involved with the activity", + "type": "string" + }, + "removed_qty": { + "description": "The removed quantity", + "type": "string" + }, + "symbol": { + "description": "The symbol of the security involved with the activity", + "type": "string" + } + }, + "required": [ + "cusip", + "symbol", + "removed_qty" + ], + "type": "object" + } + ], + "description": "Worthless Removal", + "type": "object" + }, + "WarrantExerciseElectionActivityV2": { + "allOf": [ + { + "$ref": "#/components/schemas/CommonNTAActivityV2" + }, + { + "$ref": "#/components/schemas/CommonVOFSubtypeActivityV2" + } + ], + "type": "object" + }, + "Watchlist": { + "description": "Represents a set of securities observed by a user.", + "properties": { + "account_id": { + "description": "Unique identifier of the account that owns this watchlist.\n", + "format": "uuid", + "type": "string" + }, + "assets": { + "description": "The contents of the watchlist, in the order as registered", + "items": { + "$ref": "#/components/schemas/Asset" + }, + "type": "array" + }, + "created_at": { + "description": "When watchlist was created", + "format": "date-time", + "type": "string" + }, + "id": { + "description": "Unique identifier of the watchlist itself.\n", + "format": "uuid", + "type": "string" + }, + "name": { + "description": "User friendly Name of watchlist", + "pattern": "^[a-zA-Z0-9]+$", + "type": "string" + }, + "updated_at": { + "description": "When watchlist was last updated", + "format": "date-time", + "type": "string" + } + }, + "required": [ + "id", + "account_id", + "created_at", + "updated_at", + "name" + ], + "title": "Watchlist", + "type": "object" + }, + "WatchlistWithoutAsset": { + "description": "Represents a set of securities observed by a user.", + "properties": { + "account_id": { + "description": "Unique identifier of the account that owns this watchlist.\n", + "format": "uuid", + "type": "string" + }, + "created_at": { + "description": "When watchlist was created", + "format": "date-time", + "type": "string" + }, + "id": { + "description": "Unique identifier of the watchlist itself.\n", + "format": "uuid", + "type": "string" + }, + "name": { + "description": "User friendly Name of watchlist", + "pattern": "^[a-zA-Z0-9]+$", + "type": "string" + }, + "updated_at": { + "description": "When watchlist was last updated", + "format": "date-time", + "type": "string" + } + }, + "required": [ + "id", + "account_id", + "created_at", + "updated_at", + "name" + ], + "title": "Watchlist", + "type": "object" + }, + "WhitelistedAddress": { + "properties": { + "address": { + "description": "The whitelisted address", + "type": "string" + }, + "asset": { + "description": "Symbol of underlying asset for the whitelisted address", + "type": "string" + }, + "chain": { + "description": "Underlying network this address represents", + "type": "string" + }, + "created_at": { + "description": "Timestamp (RFC3339) of account creation.", + "format": "date-time", + "type": "string" + }, + "id": { + "description": "Unique ID for whitelisted address", + "type": "string" + }, + "status": { + "description": "Status of whitelisted address which is either APPROVED or PENDING. Whitelisted addresses will be subjected to a 24 waiting period. After the waiting period is over the status will become APPROVED.", + "enum": [ + "APPROVED", + "PENDING" + ], + "type": "string" + } + }, + "type": "object" + }, + "bic": { + "description": "Business Identifier Code (BIC/SWIFT).", + "example": "IRVTUS3NXXX", + "maxLength": 11, + "minLength": 11, + "pattern": "^[A-Z0-9]{11}$", + "type": "string" + }, + "bond_status": { + "description": "Status of the bond.", + "enum": [ + "outstanding", + "matured", + "pre_issuance" + ], + "type": "string" + }, + "calendar_day": { + "description": "A calendar day.", + "properties": { + "core_end": { + "description": "The end time of the core market session.", + "example": "2025-01-02T16:00:00-05:00", + "format": "date-time", + "type": "string" + }, + "core_start": { + "description": "The start time of the core market session.", + "example": "2025-01-02T09:30:00-05:00", + "format": "date-time", + "type": "string" + }, + "date": { + "description": "The date of the calendar day.", + "example": "2025-01-02", + "format": "date", + "type": "string" + }, + "lunch_end": { + "description": "The end time of the lunch session.", + "format": "date-time", + "type": "string" + }, + "lunch_start": { + "description": "The start time of the lunch session.", + "format": "date-time", + "type": "string" + }, + "post_end": { + "description": "The end time of the after-hours session.", + "example": "2025-01-02T20:00:00-05:00", + "format": "date-time", + "type": "string" + }, + "post_start": { + "description": "The start time of the after-hours session.", + "example": "2025-01-02T16:00:00-05:00", + "format": "date-time", + "type": "string" + }, + "pre_end": { + "description": "The end time of the pre-market session.", + "example": "2025-01-02T09:30:00-05:00", + "format": "date-time", + "type": "string" + }, + "pre_start": { + "description": "The start time of the pre-market session.", + "example": "2025-01-02T04:00:00-05:00", + "format": "date-time", + "type": "string" + }, + "settlement_date": { + "description": "The settlement date.", + "example": "2025-01-03", + "format": "date", + "type": "string" + } + }, + "required": [ + "date", + "core_start", + "core_end" + ], + "type": "object" + }, + "call_type": { + "description": "The type of call on the bond refers to one of a variety of circumstances under which a callable bond may be called.", + "enum": [ + "ordinary", + "make_whole", + "regulatory", + "special" + ], + "type": "string" + }, + "clock": { + "properties": { + "is_market_day": { + "description": "Whether the clock is on a market day.", + "type": "boolean" + }, + "market": { + "$ref": "#/components/schemas/public_market" + }, + "next_market_close": { + "description": "Next market close timestamp", + "format": "date-time", + "type": "string" + }, + "next_market_open": { + "description": "Next market open timestamp", + "format": "date-time", + "type": "string" + }, + "phase": { + "$ref": "#/components/schemas/phase" + }, + "phase_until": { + "description": "The end of the current phase.", + "format": "date-time", + "type": "string" + }, + "timestamp": { + "description": "The time on the clock.", + "format": "date-time", + "type": "string" + } + }, + "required": [ + "market", + "timestamp", + "is_market_day", + "next_market_open", + "next_market_close", + "phase", + "phase_until" + ], + "type": "object" + }, + "clock_resp": { + "description": "Clock response.", + "properties": { + "clocks": { + "items": { + "$ref": "#/components/schemas/clock" + }, + "type": "array" + } + }, + "required": [ + "clocks" + ], + "type": "object", + "x-go-name": "ClockResp" + }, + "coupon_frequency": { + "description": "How often the coupon is paid", + "enum": [ + "annual", + "semi_annual", + "quarterly", + "monthly", + "zero" + ], + "type": "string" + }, + "coupon_type": { + "description": "The type of the coupon rate", + "enum": [ + "fixed", + "floating", + "zero" + ], + "type": "string" + }, + "day_count": { + "description": "The day count convention used to calculate accrued interest.\n\n- `A/360`: calculates the daily interest using a 360-day year and then multiplies that by the actual number of days in each time period.\n- `A/365`: calculates the daily interest using a 365-day year and then multiplies that by the actual number of days in each time period.\n- `30/360`: calculates the daily interest using a 360-day year and then multiplies that by 30 (standardized month).\n- `30/365`: calculates the daily interest using a 365-day year and then multiplies that by 30 (standardized month).\n- `A/A`: calculates the daily interest using the actual number of days in the year and then multiplies that by the actual number of days in each time period.\n- `30E/360`: number of days equals to the actual number of days (for February). If the start date or the end date of the period is the 31st of a month, that date is set to the 30th. The number of days in a year is 360.\n- `B/252`: calculates the daily interest using a 252-business-day year and then multiplies that by the actual number of days in each time period.\n- `A/364`: calculates the daily interest using a 364-day year and then multiplies that by the actual number of days in each time period.\n", + "enum": [ + "A/360", + "A/365", + "30/360", + "30/365", + "A/A", + "30E/360", + "B/252", + "A/364" + ], + "type": "string" + }, + "legacy_calendar_day": { + "description": "A calendar day.", + "properties": { + "close": { + "description": "The time the market closes at on this date in HH:MM format.", + "example": "16:00", + "type": "string" + }, + "date": { + "description": "Date string in YYYY-MM-DD format.", + "example": "2025-01-02", + "format": "date", + "type": "string" + }, + "open": { + "description": "The time the market opens at on this date in HH:MM format.", + "example": "09:30", + "type": "string" + }, + "session_close": { + "description": "The time the session closes at on this date in HHMM format.", + "example": "2000", + "type": "string" + }, + "session_open": { + "description": "The time the session opens at on this date in HHMM format.", + "example": "0400", + "type": "string" + }, + "settlement_date": { + "description": "Date string in YYYY-MM-DD format. Representing the settlement date for the trade date.", + "example": "2025-01-03", + "format": "date", + "type": "string" + } + }, + "required": [ + "date", + "open", + "close", + "session_open", + "session_close", + "settlement_date" + ], + "type": "object" + }, + "legacy_clock": { + "example": { + "is_open": true, + "next_close": "2025-06-24T16:00:00-04:00", + "next_open": "2025-06-25T09:30:00-04:00", + "timestamp": "2025-06-24T14:15:22-04:00" + }, + "properties": { + "is_open": { + "description": "Whether or not the market is open.", + "type": "boolean" + }, + "next_close": { + "description": "Next market close timestamp.", + "format": "date-time", + "type": "string" + }, + "next_open": { + "description": "Next market open timestamp.", + "format": "date-time", + "type": "string" + }, + "timestamp": { + "description": "Current timestamp.", + "format": "date-time", + "type": "string" + } + }, + "required": [ + "timestamp", + "is_open", + "next_open", + "next_close" + ], + "title": "Clock", + "type": "object" + }, + "legacy_public_calendar_resp": { + "items": { + "$ref": "#/components/schemas/legacy_calendar_day" + }, + "type": "array" + }, + "market": { + "description": "The market identifier (MIC, BIC, or acronym).", + "enum": [ + "BMO", + "BNYM", + "BOATS", + "CEUX", + "CHIX", + "HKEX", + "IEX", + "IEXG", + "ISE", + "LSE", + "MTA", + "MTAA", + "NASDAQ", + "NYSE", + "OCEA", + "OPRA", + "OTC", + "OTCM", + "SIFMA", + "TADAWUL", + "XAMS", + "XBRU", + "XDUB", + "XETR", + "XETRA", + "XHKG", + "XLIS", + "XLON", + "XNAS", + "XNYS", + "XPAR", + "XSAU" + ], + "type": "string" + }, + "market_acronym": { + "description": "The acronym of the market.", + "example": "NYSE", + "type": "string" + }, + "market_name": { + "description": "The full name of the market.", + "example": "New York Stock Exchange", + "type": "string" + }, + "market_timezone": { + "description": "The timezone of the market.", + "example": "America/New_York", + "type": "string" + }, + "mic": { + "description": "Market identifier code (ISO 10383).", + "example": "XNYS", + "maxLength": 4, + "minLength": 4, + "pattern": "^[A-Z0-9]{4}$", + "type": "string" + }, + "phase": { + "enum": [ + "closed", + "pre", + "core", + "lunch", + "post" + ], + "type": "string" + }, + "public_calendar_resp": { + "description": "Calendar response.", + "properties": { + "calendar": { + "description": "The market calendar.", + "items": { + "$ref": "#/components/schemas/calendar_day" + }, + "type": "array" + }, + "market": { + "$ref": "#/components/schemas/public_market" + } + }, + "required": [ + "market", + "calendar" + ], + "type": "object", + "x-go-name": "CalendarResp" + }, + "public_market": { + "description": "A market.", + "properties": { + "acronym": { + "$ref": "#/components/schemas/market_acronym" + }, + "bic": { + "$ref": "#/components/schemas/bic" + }, + "mic": { + "$ref": "#/components/schemas/mic" + }, + "name": { + "$ref": "#/components/schemas/market_name" + }, + "timezone": { + "$ref": "#/components/schemas/market_timezone" + } + }, + "required": [ + "acronym", + "name", + "timezone" + ], + "type": "object" + }, + "sp_outlook": { + "description": "A Standard & Poor's rating outlook indicates S&P's view regarding the potential direction of a long-term credit rating over the intermediate term (2 years for investment grade, 1 year for speculative grade)", + "enum": [ + "positive", + "negative", + "developing", + "stable", + "not_rated", + "not_meaningful" + ], + "type": "string" + }, + "treasury_subtype": { + "description": "The subtype of the treasury.", + "enum": [ + "bond", + "bill", + "note", + "strips", + "tips", + "floating" + ], + "type": "string" + }, + "us_corporate": { + "description": "A US corporate", + "properties": { + "accrued_interest": { + "description": "The interest that has accumulated on a bond in dollars per bond between the last interest payment and the present date that has not yet been paid to the bondholder", + "format": "double", + "type": "number", + "x-go-type": "decimal.Decimal", + "x-go-type-import": { + "name": "decimal", + "path": "github.com/alpacahq/alpacadecimal" + } + }, + "bond_status": { + "$ref": "#/components/schemas/bond_status" + }, + "call_type": { + "$ref": "#/components/schemas/call_type" + }, + "callable": { + "description": "Whether the bond is callable, meaning the issuer has the right, but not the obligation to redeem the bond - in other words, pay out the bondholder - before its maturity date at a set price (the call price)", + "type": "boolean" + }, + "close_price": { + "description": "The price of the last transaction of a security before the market closes for normal trading, shown as a percentage of par value", + "format": "double", + "type": "number", + "x-go-type": "decimal.Decimal", + "x-go-type-import": { + "name": "decimal", + "path": "github.com/alpacahq/alpacadecimal" + } + }, + "close_price_date": { + "description": "The date of the close price", + "format": "date", + "type": "string" + }, + "close_yield_to_maturity": { + "description": "Yield to maturity of the treasury after the last close", + "format": "double", + "type": "number", + "x-go-type": "decimal.Decimal", + "x-go-type-import": { + "name": "decimal", + "path": "github.com/alpacahq/alpacadecimal" + } + }, + "close_yield_to_worst": { + "description": "Yield to worst of the treasury after the last close", + "format": "double", + "type": "number", + "x-go-type": "decimal.Decimal", + "x-go-type-import": { + "name": "decimal", + "path": "github.com/alpacahq/alpacadecimal" + } + }, + "convertible": { + "description": "A flag indicating whether the bond is convertible", + "type": "boolean" + }, + "country_domicile": { + "description": "The country where the corporate is domiciled in the 2-alpha country code format (e.g., US, CA)", + "type": "string" + }, + "coupon": { + "description": "The annual interest rate paid on the bond as a percentage of par value", + "format": "double", + "type": "number", + "x-go-type": "decimal.Decimal", + "x-go-type-import": { + "name": "decimal", + "path": "github.com/alpacahq/alpacadecimal" + } + }, + "coupon_frequency": { + "$ref": "#/components/schemas/coupon_frequency" + }, + "coupon_type": { + "$ref": "#/components/schemas/coupon_type" + }, + "cusip": { + "description": "CUSIP is a nine-character alphanumeric code that uniquely identifies the security", + "maxLength": 12, + "minLength": 12, + "pattern": "^[A-Z0-9]{9}$", + "type": "string" + }, + "dated_date": { + "description": "The dated date marks the beginning of the period for which interest starts accruing on the bond", + "format": "date", + "type": "string" + }, + "day_count": { + "$ref": "#/components/schemas/day_count" + }, + "description": { + "description": "Description of the corporate bond", + "type": "string" + }, + "description_short": { + "description": "Short description of the corporate bond", + "type": "string" + }, + "first_coupon_date": { + "description": "The date of the first coupon payment", + "format": "date", + "type": "string" + }, + "isin": { + "description": "International Securities Identification Number", + "maxLength": 12, + "minLength": 12, + "pattern": "^[A-Z]{2}[A-Z0-9]{9}[0-9]$", + "type": "string" + }, + "issue_date": { + "description": "The date on which the bond was issued", + "format": "date", + "type": "string" + }, + "issue_minimum_denomination": { + "description": "The smallest unit of the bond that can be purchased at its initial offering", + "type": "number" + }, + "issue_price": { + "description": "The price at which the bond was originally issued as a percentage of par value", + "format": "double", + "type": "number", + "x-go-type": "decimal.Decimal", + "x-go-type-import": { + "name": "decimal", + "path": "github.com/alpacahq/alpacadecimal" + } + }, + "issue_size": { + "description": "The total size amount of the bond issue in the issuing currency", + "type": "number" + }, + "issuer": { + "description": "The name of the issuer of the corporate bond", + "type": "string" + }, + "last_coupon_date": { + "description": "The date of the last coupon payment", + "format": "date", + "type": "string" + }, + "liquidity_institutional_aggregate": { + "description": "Score (from 1-5 or null if the bond is not priced/tradable) reflecting the historical depth of executable liquidity to buy or sell (no minimum trading sizes)", + "type": "number" + }, + "liquidity_institutional_buy": { + "description": "Score (from 1-5 or null if the bond is not priced/tradable) reflecting the historical depth of executable liquidity to buy (no minimum trading sizes)", + "type": "number" + }, + "liquidity_institutional_sell": { + "description": "Score (from 1-5 or null if the bond is not priced/tradable) reflecting the historical depth of executable liquidity to sell (no minimum trading sizes)", + "type": "number" + }, + "liquidity_micro_aggregate": { + "description": "Score (from 1-5 or null if the bond is not priced/tradable) reflecting the historical depth of executable liquidity to buy or sell with minimum trading sizes less than or equal to $1,000.00", + "type": "number" + }, + "liquidity_micro_buy": { + "description": "Score (from 1-5 if the bond is priced, or null if the bond is not tradable) reflecting the historical depth of executable liquidity to buy with minimum trading sizes less than or equal to $1,000.00", + "type": "number" + }, + "liquidity_micro_sell": { + "description": "Score (from 1-5 or null if the bond is not priced/tradable) reflecting the historical depth of executable liquidity to sell with minimum trading sizes less than or equal to $1,000.00", + "type": "number" + }, + "liquidity_retail_aggregate": { + "description": "Score (from 1-5 or null if the bond is not priced/tradable) reflecting the historical depth of executable liquidity to buy or sell with minimum trading sizes less than or equal to 10,000", + "type": "number" + }, + "liquidity_retail_buy": { + "description": "Score (from 1-5 or null if the bond is not priced/tradable) reflecting the historical depth of executable liquidity to buy with minimum trading sizes less than or equal to $10,000.00", + "type": "number" + }, + "liquidity_retail_sell": { + "description": "Score (from 1-5 or null if the bond is not priced/tradable) reflecting the historical depth of executable liquidity to sell with minimum trading sizes less than or equal to 10,000", + "type": "number" + }, + "marginable": { + "description": "Whether the corporate is marginable", + "type": "boolean" + }, + "maturity_date": { + "description": "The date on which the bond matures", + "format": "date", + "type": "string" + }, + "next_call_date": { + "description": "The date of the next possible call on the bond.", + "format": "date", + "type": "string" + }, + "next_call_price": { + "description": "The price at which a callable bond can be redeemed by the issuer on the next call date, as a percentage of par.", + "format": "double", + "type": "number", + "x-go-type": "decimal.Decimal", + "x-go-type-import": { + "name": "decimal", + "path": "github.com/alpacahq/alpacadecimal" + } + }, + "next_coupon_date": { + "description": "The date of the next coupon payment", + "format": "date", + "type": "string" + }, + "par_value": { + "description": "The amount that the issuer of the bond will pay back to the bondholder upon maturity", + "type": "number" + }, + "perpetual": { + "description": "A flag representing whether a bond is perpetual", + "type": "boolean" + }, + "puttable": { + "description": "Whether the bond is puttable, meaning the bondholder has the right, but not the obligation to sell the bond back to the issuer at a set price (the put price) on specified dates before maturity", + "type": "boolean" + }, + "reg_s": { + "description": "Indicates whether the security falls under Regulation S, a rule that provides an exemption from the registration requirements for securities offerings made outside the United States", + "type": "boolean" + }, + "reissue_date": { + "description": "The date on which the corporate was reissued", + "format": "date", + "type": "string" + }, + "reissue_price": { + "description": "The price at which the corporate was reissued as a percentage of par value", + "format": "double", + "type": "number", + "x-go-type": "decimal.Decimal", + "x-go-type-import": { + "name": "decimal", + "path": "github.com/alpacahq/alpacadecimal" + } + }, + "reissue_size": { + "description": "The total size amount of the corporate reissue in the issuing currency", + "type": "number" + }, + "sector": { + "description": "The sector of the corporate bond", + "type": "string" + }, + "seniority": { + "description": "The seniority of the corporate bond", + "type": "string" + }, + "sp_creditwatch": { + "description": "S&P's CreditWatch highlights S&P's opinion regarding the potential direction of a short-term or long-term rating", + "type": "string" + }, + "sp_creditwatch_date": { + "description": "The date of the most recent Standard & Poor's CreditWatch for the bond in YYYY-MM-DD format", + "format": "date", + "type": "string" + }, + "sp_outlook": { + "$ref": "#/components/schemas/sp_outlook" + }, + "sp_outlook_date": { + "description": "The date of the most recent Standard & Poor's outlook for the bond in YYYY-MM-DD format", + "format": "date", + "type": "string" + }, + "sp_rating": { + "description": "Standard & Poor's rating for the bond in the standard AAA - D format", + "type": "string" + }, + "sp_rating_date": { + "description": "The date in the timezone of the issuing country of the most recent Standard & Poor's rating for the bond in YYYY-MM-DD format", + "format": "date", + "type": "string" + }, + "ticker": { + "description": "The ticker symbol of the corporate", + "type": "string" + }, + "tradable": { + "description": "Whether the treasury is tradable", + "type": "boolean" + } + }, + "required": [ + "isin", + "cusip", + "bond_status", + "tradable", + "marginable", + "issue_date", + "country_domicile", + "ticker", + "seniority", + "issuer", + "sector", + "description", + "description_short", + "coupon", + "coupon_type", + "coupon_frequency", + "perpetual", + "day_count", + "dated_date", + "issue_size", + "issue_price", + "issue_minimum_denomination", + "par_value", + "callable", + "puttable", + "convertible", + "reg_s" + ], + "type": "object" + }, + "us_corporates_resp": { + "properties": { + "us_corporates": { + "items": { + "$ref": "#/components/schemas/us_corporate" + }, + "type": "array" + } + }, + "required": [ + "us_corporates" + ], + "type": "object" + }, + "us_treasuries_resp": { + "properties": { + "us_treasuries": { + "items": { + "$ref": "#/components/schemas/us_treasury" + }, + "type": "array" + } + }, + "required": [ + "us_treasuries" + ], + "type": "object" + }, + "us_treasury": { + "description": "A US treasury", + "properties": { + "bond_status": { + "$ref": "#/components/schemas/bond_status" + }, + "close_price": { + "description": "The price of the last transaction of a security before the market closes for normal trading, shown as a percentage of par value", + "format": "double", + "type": "number" + }, + "close_price_date": { + "description": "The date of the close price", + "format": "date", + "type": "string" + }, + "close_yield_to_maturity": { + "description": "Yield to maturity of the treasury after the last close", + "format": "double", + "type": "number" + }, + "close_yield_to_worst": { + "description": "Yield to worst of the treasury after the last close", + "format": "double", + "type": "number" + }, + "coupon": { + "description": "The annual interest rate paid on the bond as a percentage of par value", + "format": "double", + "type": "number" + }, + "coupon_frequency": { + "$ref": "#/components/schemas/coupon_frequency" + }, + "coupon_type": { + "$ref": "#/components/schemas/coupon_type" + }, + "cusip": { + "description": "CUSIP is a nine-character alphanumeric code that uniquely identifies the security", + "maxLength": 12, + "minLength": 12, + "pattern": "^[A-Z0-9]{9}$", + "type": "string" + }, + "description": { + "description": "Description of the treasury", + "type": "string" + }, + "description_short": { + "description": "Short description of the treasury", + "type": "string" + }, + "first_coupon_date": { + "description": "The date of the first coupon payment", + "format": "date", + "type": "string" + }, + "isin": { + "description": "International Securities Identification Number", + "maxLength": 12, + "minLength": 12, + "pattern": "^[A-Z]{2}[A-Z0-9]{9}[0-9]$", + "type": "string" + }, + "issue_date": { + "description": "The date on which the bond was issued", + "format": "date", + "type": "string" + }, + "last_coupon_date": { + "description": "The date of the last coupon payment", + "format": "date", + "type": "string" + }, + "maturity_date": { + "description": "The date on which the bond matures", + "format": "date", + "type": "string" + }, + "next_coupon_date": { + "description": "The date of the next coupon payment", + "format": "date", + "type": "string" + }, + "subtype": { + "$ref": "#/components/schemas/treasury_subtype" + }, + "tradable": { + "description": "Whether the treasury is tradable", + "type": "boolean" + } + }, + "required": [ + "isin", + "cusip", + "bond_status", + "tradable", + "subtype", + "issue_date", + "maturity_date", + "description", + "description_short", + "coupon", + "coupon_type", + "coupon_frequency" + ], + "type": "object" + } + }, + "securitySchemes": { + "BasicAuth": { + "scheme": "basic", + "type": "http" + } + } + }, + "info": { + "contact": { + "email": "support@alpaca.markets", + "name": "Alpaca Support", + "url": "https://alpaca.markets/support" + }, + "description": "Open brokerage accounts, enable stock, options and crypto trading. Manage the ongoing user experience and brokerage customer lifecycle with the Alpaca Broker API", + "termsOfService": "https://s3.amazonaws.com/files.alpaca.markets/disclosures/library/TermsAndConditions.pdf", + "title": "Broker API", + "version": "1.1.1" + }, + "openapi": "3.0.0", + "paths": { + "/v1beta/accounts/{account_id}/funding_wallet": { + "get": { + "description": "Returns the funding wallet for the specified account. Responds with 404 if the account has not been onboarded to funding wallets or a wallet has not been created.", + "operationId": "getFundingWallet", + "parameters": [ + { + "description": "UUID alpaca account ID", + "in": "path", + "name": "account_id", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/FundingWallet" + } + } + }, + "description": "funding wallet" + }, + "default": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + }, + "description": "error" + } + }, + "summary": "Retrieve funding wallet", + "tags": [ + "Funding Wallets" + ] + }, + "post": { + "description": "Creates a funding wallet for the specified account. The request is rejected if the account already has a funding wallet.", + "operationId": "createFundingWallet", + "parameters": [ + { + "description": "UUID alpaca account ID", + "in": "path", + "name": "account_id", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/FundingWallet" + } + } + }, + "description": "funding wallet" + }, + "default": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + }, + "description": "error" + } + }, + "summary": "Creates a funding wallet", + "tags": [ + "Funding Wallets" + ] + } + }, + "/v1beta/accounts/{account_id}/funding_wallet/funding_details": { + "get": { + "description": "Returns a list of funding details if it exists. Query parameters must be passed to create a new funding details object if none exist.", + "operationId": "listFundingDetails", + "parameters": [ + { + "description": "UUID alpaca account ID", + "in": "path", + "name": "account_id", + "required": true, + "schema": { + "type": "string" + } + }, + { + "description": "The type of SSI to be returned, priority (SWIFT) or regular (local). A null value returns all payment types.", + "in": "query", + "name": "payment_type", + "required": false, + "schema": { + "$ref": "#/components/schemas/FundingDetailPaymentType" + } + }, + { + "description": "Should be provided in ISO 4217 standard", + "in": "query", + "name": "currency", + "required": false, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "items": { + "$ref": "#/components/schemas/ListFundingDetails" + } + } + } + }, + "description": "list of wallets" + }, + "default": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + }, + "description": "error" + } + }, + "summary": "Retrieve funding details", + "tags": [ + "Funding Wallets" + ] + } + }, + "/v1beta/accounts/{account_id}/funding_wallet/recipient_bank": { + "delete": { + "description": "deletes a recipient bank.", + "operationId": "deleteFundingWalletRecipientBank", + "parameters": [ + { + "description": "UUID alpaca account ID", + "in": "path", + "name": "account_id", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "recipient bank" + }, + "default": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + }, + "description": "Error response." + } + }, + "summary": "Delete a recipient bank", + "tags": [ + "Funding Wallets" + ] + }, + "get": { + "description": "Returns the recipient bank registered against the specified account's funding wallet. Responds with 404 if no recipient bank has been registered for the account.", + "operationId": "getFundingWalletRecipientBank", + "parameters": [ + { + "description": "UUID alpaca account ID", + "in": "path", + "name": "account_id", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "recipient bank" + }, + "default": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + }, + "description": "Error response." + } + }, + "summary": "Retrieve recipient bank", + "tags": [ + "Funding Wallets" + ] + }, + "post": { + "description": "Creates a new recipient bank. Returns the new recipient bank entity on success. entity.", + "operationId": "createFundingWalletRecipientBank", + "parameters": [ + { + "description": "UUID alpaca account ID", + "in": "path", + "name": "account_id", + "required": true, + "schema": { + "type": "string" + } + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "properties": { + "account_number": { + "description": "Bank account number.", + "type": "string" + }, + "account_type": { + "description": "Bank account type.", + "enum": [ + "checking", + "savings" + ], + "type": "string" + }, + "bank_account_holder_name": { + "description": "Bank account holder's name.", + "type": "string" + }, + "bank_country": { + "description": "Two-letter code for the country in which the beneficiary's bank account is held.", + "type": "string" + }, + "bank_name": { + "type": "string" + }, + "bic_swift": { + "description": "BIC/SWIFT code", + "type": "string" + }, + "city": { + "description": "City", + "type": "string" + }, + "currency": { + "description": "Currency in which money is held in the beneficiary's bank account. ISO-3 currency code.", + "type": "string" + }, + "iban": { + "description": "IBAN code", + "type": "string" + }, + "postal_code": { + "description": "Postal code", + "type": "string" + }, + "routing_code": { + "description": "Routing code for routing_code_type. If supplied, routing_code_type should also be supplied.", + "type": "string" + }, + "routing_code_type": { + "description": "Local payment routing system. If supplied, routing_code should also be supplied.", + "enum": [ + "sort_code", + "aba", + "bsb_code", + "institution_no", + "bank_code", + "branch_code", + "clabe", + "cnaps", + "ifsc" + ], + "type": "string" + }, + "state_or_province": { + "description": "State or province.", + "type": "string" + }, + "street_address": { + "description": "First line of address.", + "type": "string" + } + }, + "required": [ + "bank_country", + "currency", + "bank_name", + "street_address", + "city", + "account_number" + ] + } + } + } + }, + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/FundingWalletRecipientBank" + } + } + }, + "description": "Success." + }, + "default": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + }, + "description": "error" + } + }, + "summary": "Create a recipient bank", + "tags": [ + "Funding Wallets" + ] + } + }, + "/v1beta/accounts/{account_id}/funding_wallet/transfers": { + "get": { + "description": "Returns all funding wallet transfers (deposits and withdrawals) associated with the specified account.", + "operationId": "getFundingWalletTransfers", + "parameters": [ + { + "description": "UUID alpaca account ID", + "in": "path", + "name": "account_id", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ListTransfersResponse" + } + } + }, + "description": "transfers" + }, + "default": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + }, + "description": "error" + } + }, + "summary": "Retrieve funding wallet transfers", + "tags": [ + "Funding Wallets" + ] + } + }, + "/v1beta/accounts/{account_id}/funding_wallet/transfers/{transfer_id}": { + "get": { + "description": "Returns a single funding wallet transfer for the specified account by `transfer_id`. Responds with 404 if no matching transfer exists for the account.", + "operationId": "getFundingWalletTransferByID", + "parameters": [ + { + "description": "Alpaca account UUID", + "in": "path", + "name": "account_id", + "required": true, + "schema": { + "type": "string" + } + }, + { + "description": "transfer UUID", + "in": "path", + "name": "transfer_id", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/FundingWalletTransfer" + } + } + }, + "description": "transfer response if found" + }, + "404": { + "description": "error response if transfer is not found" + }, + "default": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + }, + "description": "error" + } + }, + "summary": "Retrieve funding wallet transfer by ID", + "tags": [ + "Funding Wallets" + ] + } + }, + "/v1beta/accounts/{account_id}/funding_wallet/withdrawal": { + "post": { + "description": "Creates a withdrawal", + "operationId": "createFundingWalletWithdrawal", + "parameters": [ + { + "description": "UUID alpaca account ID", + "in": "path", + "name": "account_id", + "required": true, + "schema": { + "type": "string" + } + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CreateFundingWalletWithdrawalRequest" + } + } + }, + "required": true + }, + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/FundingWalletTransfer" + } + } + }, + "description": "Transfer" + }, + "default": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + }, + "description": "Error response." + } + }, + "summary": "Create a withdrawal", + "tags": [ + "Funding Wallets" + ] + } + }, + "/v1beta/accounts/funding_wallet": { + "post": { + "description": "Creates funding wallets for a list of accounts in a single request. Every `account_id` in the request must belong to your firm, and the request is rejected if a funding wallet already exists for any of the supplied accounts.", + "operationId": "batchCreateFundingWallets", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BatchCreateFundingWalletRequest" + } + } + }, + "required": true + }, + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BatchCreateFundingWalletResponse" + } + } + }, + "description": "Funding wallets" + }, + "default": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + }, + "description": "Error response." + } + }, + "summary": "Batch create funding wallets", + "tags": [ + "Funding Wallets" + ] + } + }, + "/v1beta/demo/banking/funding": { + "post": { + "description": "Simulates an inbound deposit into an account's funding wallet for end-to-end testing of the deposit flow.\n\nThe transfer can only credit accounts that belong to your firm. Available in non-production (sandbox) environments only.", + "operationId": "demoDepositFunding", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DemoFundingTransfer" + } + } + }, + "required": true + }, + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DemoFundingTransfer" + } + } + }, + "description": "Demo deposit funding transfer" + }, + "default": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + }, + "description": "Error response." + } + }, + "summary": "Create sandbox deposit transfer", + "tags": [ + "Funding Wallets" + ] + } + }, + "/v1beta1/logos/{symbol}": { + "get": { + "description": "Alpaca's Logo API serves uniform logo images for select stock and crypto symbols.\n\nNote: For Logo API pricing details, reach out to sales@alpaca.markets\n\nThe API response will return the raw image as a binary", + "operationId": "get-v1beta1-logos-symbol", + "parameters": [ + { + "description": "True by default, will return sample placeholder images with the first letter of the asset symbol. If false, 404 will be returned for symbols which do not contain an image.", + "in": "query", + "name": "placeholder", + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "The API response will return the raw image as a binary" + } + }, + "summary": "Get Logo", + "tags": [ + "Logos" + ] + }, + "parameters": [ + { + "description": "Stock or crypto symbol (e.g. AAPL, BTCUSD, etc.)", + "in": "path", + "name": "symbol", + "required": true, + "schema": { + "type": "string" + } + } + ] + }, + "/v1/accounts": { + "get": { + "description": "Retrieves the first 1000 accounts that match the query parameters.\nSorting is based on creation time.\nThe created_after/created_before query parameters can be used to paginate the results.\nTo further limit the size of the response, the entities query parameter can be used to specify which properties are included in the response.\n", + "operationId": "getAllAccounts", + "parameters": [ + { + "description": "A space-delimited list of tokens. The response will contain accounts that match with all of the tokens (i.e. a logical AND). A match means the token is present in either the account's associated account number, phone number, name, or e-mail address (logical OR).", + "in": "query", + "name": "query", + "schema": { + "type": "string" + } + }, + { + "description": "Inclusive of timestamp. Example: 2019-10-12T07:20:50.52Z", + "in": "query", + "name": "created_after", + "schema": { + "format": "date-time", + "type": "string" + } + }, + { + "description": "Inclusive of timestamp. Example: 2019-10-12T07:20:50.52Z", + "in": "query", + "name": "created_before", + "schema": { + "format": "date-time", + "type": "string" + } + }, + { + "description": "See the AccountStatus model for values", + "in": "query", + "name": "status", + "schema": { + "type": "string" + } + }, + { + "$ref": "#/components/parameters/Sort" + }, + { + "description": "Comma-delimited entity names to include in the response. If not specified, all entities are included.", + "in": "query", + "name": "entities", + "schema": { + "enum": [ + "agreements", + "contact", + "disclosures", + "documents", + "identity", + "trading_configurations", + "trusted_contact" + ], + "type": "string" + } + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "items": { + "$ref": "#/components/schemas/AccountExtended" + }, + "type": "array" + } + } + }, + "description": "A list of accounts that match the query parameters.\n" + } + }, + "summary": "Get All Accounts", + "tags": [ + "Accounts" + ] + }, + "post": { + "description": "Submit an account application with KYC information. This will create a trading account for the end user. The account status may or may not be ACTIVE immediately and you will receive account status updates on the event API.\n\n**Multi-Live Accounts (MLA)**: To open an additional account for an existing account holder, supply `primary_account_holder_id` at the top level instead of `contact`/`identity` data. In that case, supplying `contact` or `identity` returns HTTP 400. Only `account_type` of `trading` and `ira` are supported via this flow.\n", + "operationId": "createAccount", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/AccountCreationRequest" + } + } + }, + "required": true + }, + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Account" + } + } + }, + "description": "OK" + }, + "400": { + "content": { + "application/json": { + "schema": { + "type": "string" + } + } + }, + "description": "The post body is not well formed. For Multi-Live Accounts (MLA), this also covers:\n- Supplying `primary_account_holder_id` together with `contact` or `identity`.\n- Supplying `primary_account_holder_id` with an `account_type` other than `trading` or `ira`.\n- The referenced primary account holder is not eligible to open an additional account.\n" + }, + "409": { + "description": "There is already an existing account registered with the same email address." + }, + "422": { + "content": { + "application/json": { + "schema": { + "type": "string" + } + } + }, + "description": "One of the input values is not a valid value." + } + }, + "summary": "Create an Account", + "tags": [ + "Accounts" + ] + } + }, + "/v1/accounts/{account_id}": { + "get": { + "description": "You can query a specific account that you submitted to Alpaca by passing into the query the account_id associated with the account you're retrieving.\n", + "operationId": "getAccount", + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/AccountExtended" + } + } + }, + "description": "Will return an AccountExtended if an account with account_id exists, otherwise will throw an error." + } + }, + "summary": "Get An Account by ID", + "tags": [ + "Accounts" + ] + }, + "parameters": [ + { + "$ref": "#/components/parameters/AccountID" + } + ], + "patch": { + "description": "This operation updates account information.\n\nIf all parameters are valid and updates have been made, it returns with status code 200. The response is the account model.\n\n**Multi-Live Accounts (MLA)**: The `primary_account_holder_id` field is read-only after the account is created. Omitting it or supplying the value already associated with the account is a no-op; supplying a different value returns HTTP 400.", + "operationId": "patchAccount", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/AccountUpdateRequest" + } + } + }, + "required": true + }, + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/AccountExtended" + } + } + }, + "description": "If all parameters are valid and updates have been made, it returns with status code 200. The response is the account model." + }, + "400": { + "content": { + "application/json": { + "schema": { + "type": "string" + } + } + }, + "description": "The post body is not well formed. This also includes attempts to change the primary account holder via `primary_account_holder_id`:\nthe field is immutable after account creation, so supplying a value different from the account's current primary account holder returns HTTP 400.\n" + }, + "422": { + "content": { + "application/json": { + "schema": { + "type": "string" + } + } + }, + "description": "The request body contains an attribute that is not permitted to be updated or you are attempting to set an invalid value." + } + }, + "summary": "Update an Account", + "tags": [ + "Accounts" + ] + } + }, + "/v1/accounts/{account_id}/ach_relationships": { + "get": { + "description": "Returns a list of ACH Relationships for an account", + "operationId": "getAccountACHRelationships", + "parameters": [ + { + "description": "Comma-separated status values", + "in": "query", + "name": "statuses", + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "items": { + "$ref": "#/components/schemas/ACHRelationship" + }, + "type": "array" + } + } + }, + "description": "Success" + } + }, + "summary": "Retrieve ACH Relationships for an account", + "tags": [ + "Funding" + ] + }, + "parameters": [ + { + "$ref": "#/components/parameters/AccountID" + } + ], + "post": { + "description": "Create a new ACHRelationship for an account\n\nIf successful, will return 200 code with a newly created ACH Relationship entity.", + "operationId": "createACHRelationshipForAccount", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CreateACHRelationshipRequest" + } + } + }, + "description": "Create ACH Relationship ", + "required": true + }, + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ACHRelationship" + } + } + }, + "description": "returns the newly created ACH Relationship entity." + }, + "400": { + "$ref": "#/components/responses/BadRequest" + }, + "401": { + "$ref": "#/components/responses/NotAuthorized" + }, + "409": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + }, + "description": "The account already has an active relationship." + } + }, + "summary": "Create an ACH Relationship", + "tags": [ + "Funding" + ] + } + }, + "/v1/accounts/{account_id}/ach_relationships/{ach_relationship_id}": { + "delete": { + "description": "Delete an existing ACH relationship for an account", + "operationId": "deleteACHRelationshipFromAccount", + "responses": { + "204": { + "description": "Success (No Content)" + }, + "400": { + "description": "the passed in account_id or relationship_id were invalid" + }, + "404": { + "description": "Relationship Not Found" + } + }, + "summary": "Delete an existing ACH relationship", + "tags": [ + "Funding" + ] + }, + "parameters": [ + { + "$ref": "#/components/parameters/AccountID" + }, + { + "description": "ACH relationship identifier", + "in": "path", + "name": "ach_relationship_id", + "required": true, + "schema": { + "format": "uuid", + "type": "string" + } + } + ] + }, + "/v1/accounts/{account_id}/actions/close": { + "parameters": [ + { + "description": "The id of the account to be closed", + "in": "path", + "name": "account_id", + "required": true, + "schema": { + "type": "string" + } + } + ], + "post": { + "description": "This operation closes an active account. The underlying records and information of the account are not deleted by this operation.\n\n**Before closing an account, you are responsible for closing all the positions and withdrawing all the money associated with that account. Learn more in the Positions Documentation.**", + "operationId": "post-v1-accounts-account_id-actions-close", + "responses": { + "204": { + "description": "Success" + }, + "404": { + "description": "Account Not Found" + } + }, + "summary": "Close an Account", + "tags": [ + "Accounts" + ] + } + }, + "/v1/accounts/{account_id}/cip": { + "get": { + "description": "You can retrieve the CIP information you've submitted for a given account.", + "operationId": "get-v1-accounts-account_id-cip", + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CIPInfo" + } + } + }, + "description": "OK" + } + }, + "summary": "Retrieve CIP information", + "tags": [ + "KYC" + ] + }, + "parameters": [ + { + "in": "path", + "name": "account_id", + "required": true, + "schema": { + "type": "string" + } + } + ], + "post": { + "description": "The customer identification program (CIP) API allows you to submit the CIP results received from your KYC provider.\n\nThe minimum requirements to open an individual financial account are delimited and you must verify the true identity of the account holder at account opening:\n\nName\nDate of birth\nAddress\nIdentification number (for a U.S. citizen, a taxpayer identification number)", + "operationId": "post-v1-accounts-account_id-cip", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CIPInfo" + } + } + } + }, + "responses": { + "200": { + "description": "OK" + } + }, + "summary": "Upload CIP information", + "tags": [ + "KYC" + ] + } + }, + "/v1/accounts/{account_id}/documents": { + "get": { + "description": "This endpoint allows you to query all the account document based on an account ID. You can filter by date, or type of document.\n\nThese account documents are tax statements, trade confirmations, etc, generated by the Alpaca system. They are distinct from the owner documents you upload and later access via the account object's documents property.", + "operationId": "getDocsForAccount", + "parameters": [ + { + "description": "optional date value to filter the list (inclusive).", + "in": "query", + "name": "start", + "schema": { + "format": "date", + "type": "string" + } + }, + { + "description": "optional date value to filter the list (inclusive).", + "in": "query", + "name": "end", + "schema": { + "format": "date", + "type": "string" + } + }, + { + "$ref": "#/components/parameters/AccountDocumentType" + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "examples": {}, + "schema": { + "items": { + "$ref": "#/components/schemas/AccountDocument" + }, + "type": "array" + } + } + }, + "description": "OK" + }, + "404": { + "content": { + "application/json": { + "schema": { + "type": "string" + } + } + }, + "description": "Not found" + } + }, + "summary": "Retrieve a List of Account Documents", + "tags": [ + "Documents" + ] + }, + "parameters": [ + { + "$ref": "#/components/parameters/AccountID" + } + ] + }, + "/v1/accounts/{account_id}/documents/{document_id}/download": { + "get": { + "description": "This endpoint downloads an account document based on the document ID. The document will be in PDF format.\n\nThe operation returns a pre-signed downloadable link as a redirect with HTTP status code 301 if one is found.\n\nYou can retrieve a JSON version of a monthly statement by passing in the header accept: application/json header.\n\nThese account documents are tax statements, trade confirmations, etc, generated by the Alpaca system. They are distinct from the owner documents you upload and later access via the account object's documents property.\n", + "operationId": "downloadDocFromAccount", + "responses": { + "301": { + "description": "Redirect to the pre-signed download link for the account document PDF file.\n" + }, + "404": { + "description": "The document is not found." + } + }, + "security": [ + { + "BasicAuth": [] + } + ], + "summary": "Download an Account Document", + "tags": [ + "Documents" + ] + }, + "parameters": [ + { + "$ref": "#/components/parameters/AccountID" + }, + { + "$ref": "#/components/parameters/DocumentID" + } + ] + }, + "/v1/accounts/{account_id}/documents/upload": { + "parameters": [ + { + "$ref": "#/components/parameters/AccountID" + } + ], + "post": { + "description": "Upload documents for the primary account owner of an account.\n\nDocuments are binary objects whose contents are encoded in base64. Each encoded content size is limited to 10MB if you use Alpaca for KYCaaS. If you perform your own KYC there are no document size limitations.\n\nAs a convenience, documents of type w8ben may be uploaded as a JSON object using the content_data request property. The Alpaca system will then generate a formatted W-8 BEN document for subsequent downloads.\n\nNote that these owner documents are distinct from the account documents generated by Alpaca, such as tax statements and trade confirmations.", + "operationId": "uploadDocToAccount", + "requestBody": { + "content": { + "application/json": { + "schema": { + "items": { + "$ref": "#/components/schemas/OwnerDocumentUploadRequest" + }, + "type": "array" + } + } + }, + "required": true + }, + "responses": { + "204": { + "description": "Success (No Content)" + }, + "400": { + "content": { + "application/json": { + "schema": { + "type": "string" + } + } + }, + "description": "Bad Request. The body in the request is not valid." + }, + "404": { + "content": { + "application/json": { + "schema": { + "type": "string" + } + } + }, + "description": "Not Found. No account was found for this account_id" + } + }, + "summary": "Upload Owner Documents for an Existing Account", + "tags": [ + "Documents" + ] + } + }, + "/v1/accounts/{account_id}/documents/w8ben/{document_id}/download": { + "get": { + "description": "This endpoint allows you to download a W-8 BEN document for the primary owner of an account based on the document_id passed as a path parameter. The returned document is in PDF format.\n\nFor certain individuals, a W-8 BEN form should be submitted at onboarding. If the individual is not a registered U.S. taxpayer (not subject to a W-9), the W-8 BEN form may need to be submitted. The IRS explains which individuals this applies to and provides instructions on completing the form. Every three years, in addition to the calendar year it was signed, a new W-8 BEN form must be submitted.\n\nThe form can be submitted in JSON, JSONC, PNG, JPEG or PDF. If submitting it in JSON, please see the W-8 BEN completed with the corresponding field names for the API here.\n\nNote: The dates collected on the form are in a slightly different format than how they need to be submitted via Accounts API. It is requested by the user on the form in MM-DD-YYYY, but should be submitted as YYYY-MM-DD.", + "operationId": "get-v1-accounts-account_id-documents-w8ben-document_id-download", + "responses": { + "301": { + "description": "Redirects to a presigned download link for the document PDF." + }, + "404": { + "description": "Document Not Found" + } + }, + "summary": "Download the W8BEN document for the primary owner of an account", + "tags": [ + "Documents" + ] + }, + "parameters": [ + { + "description": "The id of the related account", + "in": "path", + "name": "account_id", + "required": true, + "schema": { + "type": "string" + } + }, + { + "description": "The id of the W8BEN to download", + "in": "path", + "name": "document_id", + "required": true, + "schema": { + "type": "string" + } + } + ] + }, + "/v1/accounts/{account_id}/onfido/sdk": { + "parameters": [ + { + "in": "path", + "name": "account_id", + "required": true, + "schema": { + "type": "string" + } + } + ], + "patch": { + "description": "This request allows you to send Alpaca the result of the Onfido SDK flow in your app. A notification of a successful outcome is required for Alpaca to continue the KYC process.", + "operationId": "patch-v1-accounts-account_id-onfido-sdk", + "requestBody": { + "content": { + "application/json": { + "schema": { + "properties": { + "outcome": { + "$ref": "#/components/schemas/OnfidoSDKOutcome" + }, + "reason": { + "description": "Any additional information related to the outcome", + "type": "string" + }, + "token": { + "description": "The SDK token associated with the SDK flow you are updating the outcome for", + "type": "string" + } + }, + "required": [ + "outcome", + "token" + ], + "type": "object", + "x-examples": { + "Example 1": { + "outcome": "USER_EXITED", + "reason": "User denied consent", + "token": "header.payload.signature" + } + } + } + } + } + }, + "responses": { + "200": { + "description": "OK" + }, + "404": { + "description": "Account Not Found" + }, + "422": { + "description": "Invalid input value for outcome." + } + }, + "summary": "Update the Onfido SDK Outcome", + "tags": [ + "KYC" + ] + } + }, + "/v1/accounts/{account_id}/onfido/sdk/tokens": { + "get": { + "description": "Get an SDK token to activate the Onfido SDK flow within your app. You will have to keep track of the SDK token so you can pass it back when you upload the SDK outcome. We recommend storing the token in memory rather than persistent storage to reduce any unnecessary overhead in your app.", + "operationId": "get-v1-accounts-account_id-onfido-sdk-tokens", + "parameters": [ + { + "description": "The account ID", + "in": "path", + "name": "account_id", + "required": true, + "schema": { + "format": "uuid", + "type": "string" + } + }, + { + "description": "The referrer URL of your web app or the application ID of your mobile app. If not passed in, will default to the * wildcard", + "in": "query", + "name": "referrer", + "schema": { + "type": "string" + } + }, + { + "description": "Required if referrer provided. Enum values are either mobile or web", + "in": "query", + "name": "platform", + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "properties": { + "token": { + "type": "string" + } + }, + "type": "object", + "x-examples": { + "Example 1": { + "token": "header.payload.signature" + } + } + } + } + }, + "description": "OK" + }, + "422": { + "description": "Onfido applicant not yet created for account. If you haven't already contacted Alapca to enable Onfido, please do so." + } + }, + "summary": "Retrieve an Onfido SDK Token", + "tags": [ + "KYC" + ] + } + }, + "/v1/accounts/{account_id}/options/approval": { + "post": { + "description": "This endpoint requests options trading for an account.\nFollowing submission, an assigned administrator will review the request.\nUpon approval, the account's options_approved_level parameter will be modified, granting the account the ability to participate in options trading.\nNote: This endpoint is only available for partners who have been enabled for Options BETA.", + "operationId": "requestOptionsForAccount", + "parameters": [ + { + "$ref": "#/components/parameters/AccountID" + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/OptionsApprovalRequest" + } + } + }, + "required": true + }, + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/OptionsApprovalResponse" + } + } + }, + "description": "The request was submitted successfully." + }, + "400": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + }, + "description": "The request body is invalid." + }, + "401": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + }, + "description": "Client does not exist, you do not have access to the client, or \"client_secret\" is incorrect.\n" + }, + "422": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + }, + "description": "The request body did not pass all validations." + } + }, + "summary": "Request options trading for an account (BETA)", + "tags": [ + "Accounts" + ] + } + }, + "/v1/accounts/{account_id}/recipient_banks": { + "get": { + "description": "Retrieves Bank Relationships for an account", + "operationId": "getRecipientBanks", + "parameters": [ + { + "in": "query", + "name": "status", + "schema": { + "enum": [ + "ACTIVE", + "INACTIVE" + ], + "example": "ACTIVE", + "type": "string" + } + }, + { + "in": "query", + "name": "bank_name", + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "items": { + "$ref": "#/components/schemas/Bank" + }, + "type": "array" + } + } + }, + "description": "An array of Bank relationships attached to this Account.\n\nAn empty array will be returned if no Bank relationships have been attached to this account" + }, + "400": { + "description": "Bad request. The body in the request is not valid." + } + }, + "summary": "Retrieve Bank Relationships for an Account", + "tags": [ + "Funding", + "Accounts" + ] + }, + "parameters": [ + { + "$ref": "#/components/parameters/AccountID" + } + ], + "post": { + "description": "If successful, retrieves Bank Relationships for an account", + "operationId": "createRecipientBank", + "parameters": [ + { + "$ref": "#/components/parameters/AccountID" + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CreateBankRequest" + } + } + }, + "required": true + }, + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Bank" + } + } + }, + "description": "The created Bank relationship" + }, + "400": { + "description": "Bad Request" + }, + "409": { + "description": "A Bank relationship already exists for this account" + } + }, + "summary": "Create a Bank Relationship for an Account", + "tags": [ + "Funding", + "Accounts" + ] + } + }, + "/v1/accounts/{account_id}/recipient_banks/{bank_id}": { + "delete": { + "description": "If successful, deletes Bank Relationship for an account", + "operationId": "deleteRecipientBank", + "responses": { + "204": { + "description": "Success (No Content)" + }, + "400": { + "description": "Bad Request" + }, + "404": { + "description": "No Bank Relationship with the id specified by bank_id was found for this Account" + } + }, + "summary": "Delete a Bank Relationship for an Account", + "tags": [ + "Funding" + ] + }, + "parameters": [ + { + "$ref": "#/components/parameters/AccountID" + }, + { + "in": "path", + "name": "bank_id", + "required": true, + "schema": { + "format": "uuid", + "type": "string" + } + } + ] + }, + "/v1/accounts/{account_id}/tokenization/callback/mint": { + "post": { + "description": "This endpoint is used by the issuer to confirm the minting of tokenized assets previously requested by the Authorized Participant.", + "operationId": "postTokenizationCallbackMint", + "parameters": [ + { + "$ref": "#/components/parameters/AccountID" + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TokenizationMintCallback" + } + } + }, + "required": true + }, + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TokenizationRequest" + } + } + }, + "description": "Successfully confirmed minting of a tokenized asset." + }, + "400": { + "content": { + "application/json": { + "example": { + "code": 40010000, + "message": "failed to decode request body" + }, + "schema": { + "$ref": "#/components/schemas/Error" + } + } + }, + "description": "Bad request (e.g. failed to decode request body)." + }, + "401": { + "content": { + "application/json": { + "example": { + "code": 40110000, + "message": "unauthorized" + }, + "schema": { + "$ref": "#/components/schemas/Error" + } + } + }, + "description": "Authentication credentials are missing or invalid." + }, + "403": { + "content": { + "application/json": { + "example": { + "code": 40310000, + "message": "forbidden" + }, + "schema": { + "$ref": "#/components/schemas/Error" + } + } + }, + "description": "Caller is not authorized to perform this operation." + }, + "422": { + "content": { + "application/json": { + "example": { + "code": 40010001, + "message": "tokenization_request_id is required, tx_hash is required" + }, + "schema": { + "$ref": "#/components/schemas/Error" + } + } + }, + "description": "One or more request parameters are missing or invalid." + } + }, + "summary": "Confirm Tokenized Asset Minted", + "tags": [ + "Tokenization" + ] + } + }, + "/v1/accounts/{account_id}/tokenization/callback/redeem": { + "post": { + "description": "This endpoint is used by the tokenized asset issuer to confirm the redemption of tokens previously held in the Authorized Participant's wallet. When the issuer invokes this endpoint, Alpaca will initiate a journal of the underlying asset into the Authorized Participant's account.", + "operationId": "postTokenizationRedeem", + "parameters": [ + { + "$ref": "#/components/parameters/AccountID" + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TokenizationRedeemRequest" + } + } + }, + "required": true + }, + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TokenizationRedeemResponse" + } + } + }, + "description": "Successfully requested redemption of a tokenized asset." + }, + "400": { + "content": { + "application/json": { + "example": { + "code": 40010000, + "message": "failed to decode request body" + }, + "schema": { + "$ref": "#/components/schemas/Error" + } + } + }, + "description": "Bad request (e.g. malformed input, insufficient quantity, or account not authorized to redeem)." + }, + "401": { + "content": { + "application/json": { + "example": { + "code": 40110000, + "message": "unauthorized" + }, + "schema": { + "$ref": "#/components/schemas/Error" + } + } + }, + "description": "Authentication credentials are missing or invalid." + }, + "403": { + "content": { + "application/json": { + "example": { + "code": 40310000, + "message": "forbidden" + }, + "schema": { + "$ref": "#/components/schemas/Error" + } + } + }, + "description": "Caller is not authorized to perform this operation." + }, + "422": { + "content": { + "application/json": { + "example": { + "code": 40010001, + "message": "issuer_request_id is required, tx_hash is required" + }, + "schema": { + "$ref": "#/components/schemas/Error" + } + } + }, + "description": "One or more request parameters are missing or invalid." + } + }, + "summary": "Redeem a Tokenized Asset", + "tags": [ + "Tokenization" + ] + } + }, + "/v1/accounts/{account_id}/tokenization/mint": { + "post": { + "description": "This endpoint is used by an Authorized Participant to request the minting of a tokenized asset. Alpaca will initiate a journal of the underlying asset from the Authorized Participant's account to the issuer's account.", + "operationId": "postTokenizationMintBroker", + "parameters": [ + { + "$ref": "#/components/parameters/AccountID" + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TokenizationMintRequest" + } + } + }, + "required": true + }, + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TokenizationMintResponse" + } + } + }, + "description": "Successfully requested minting of a tokenized asset." + }, + "400": { + "content": { + "application/json": { + "example": { + "code": 40010000, + "message": "failed to decode request body" + }, + "schema": { + "$ref": "#/components/schemas/Error" + } + } + }, + "description": "Bad request (e.g. malformed input, insufficient position, or account not authorized to mint)." + }, + "401": { + "content": { + "application/json": { + "example": { + "code": 40110000, + "message": "unauthorized" + }, + "schema": { + "$ref": "#/components/schemas/Error" + } + } + }, + "description": "Authentication credentials are missing or invalid." + }, + "403": { + "content": { + "application/json": { + "example": { + "code": 40310000, + "message": "forbidden" + }, + "schema": { + "$ref": "#/components/schemas/Error" + } + } + }, + "description": "Caller is not authorized to perform this operation." + }, + "422": { + "content": { + "application/json": { + "example": { + "code": 40010001, + "message": "issuer is required, network is required" + }, + "schema": { + "$ref": "#/components/schemas/Error" + } + } + }, + "description": "One or more request parameters are missing or invalid." + } + }, + "summary": "Mint a Tokenized Asset", + "tags": [ + "Tokenization" + ] + } + }, + "/v1/accounts/{account_id}/tokenization/requests": { + "get": { + "description": "Retrieve tokenization requests for the account. This endpoint may be used by the tokenized asset issuer or by an Authorized Participant (AP). The list can contain both mint and redeem requests.", + "operationId": "getTokenizationRequestsBroker", + "parameters": [ + { + "$ref": "#/components/parameters/AccountID" + }, + { + "description": "Tokenization request type to be queried", + "in": "query", + "name": "type", + "schema": { + "enum": [ + "mint", + "redeem" + ], + "type": "string" + } + }, + { + "description": "Tokenization request status to be queried", + "in": "query", + "name": "status", + "schema": { + "enum": [ + "pending", + "rejected", + "completed" + ], + "type": "string" + } + }, + { + "description": "Underlying symbol of the tokenization requests to be queried", + "in": "query", + "name": "underlying_symbol", + "schema": { + "type": "string" + } + }, + { + "description": "Issuer of the tokenization requests to be queried", + "in": "query", + "name": "issuer", + "schema": { + "$ref": "#/components/schemas/TokenizationIssuer" + } + }, + { + "description": "Network of the tokenization requests to be queried", + "in": "query", + "name": "network", + "schema": { + "$ref": "#/components/schemas/TokenizationNetwork" + } + }, + { + "description": "The response will include only requests created after this timestamp (exclusive)", + "in": "query", + "name": "after", + "schema": { + "example": "2025-09-30T18:38:01.942282Z", + "format": "date-time", + "type": "string" + } + }, + { + "description": "The response will include only requests created before this timestamp (exclusive)", + "in": "query", + "name": "before", + "schema": { + "example": "2025-09-30T18:38:01.942282Z", + "format": "date-time", + "type": "string" + } + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "items": { + "$ref": "#/components/schemas/TokenizationRequest" + }, + "type": "array" + } + } + }, + "description": "Successful response\n\nA list of tokenization requests" + }, + "401": { + "content": { + "application/json": { + "example": { + "code": 40110000, + "message": "unauthorized" + }, + "schema": { + "$ref": "#/components/schemas/Error" + } + } + }, + "description": "Authentication credentials are missing or invalid." + }, + "403": { + "content": { + "application/json": { + "example": { + "code": 40310000, + "message": "forbidden" + }, + "schema": { + "$ref": "#/components/schemas/Error" + } + } + }, + "description": "Caller is not authorized to perform this operation." + }, + "422": { + "content": { + "application/json": { + "example": { + "code": 40010001, + "message": "failed to parse after" + }, + "schema": { + "$ref": "#/components/schemas/Error" + } + } + }, + "description": "One or more request parameters are missing or invalid." + } + }, + "summary": "List Tokenization Requests", + "tags": [ + "Tokenization" + ] + } + }, + "/v1/accounts/{account_id}/transfers": { + "get": { + "description": "You can query a list of transfers for an account.\n\n\nYou can filter requested transfers by values such as direction and status.\n\nReturns a list of transfer entities ordered by created_at\n", + "operationId": "getTransfersForAccount", + "parameters": [ + { + "description": "INCOMING or OUTGOING", + "in": "query", + "name": "direction", + "schema": { + "enum": [ + "INCOMING", + "OUTGOING" + ], + "type": "string" + } + }, + { + "in": "query", + "name": "limit", + "schema": { + "format": "int32", + "type": "integer" + } + }, + { + "in": "query", + "name": "offset", + "schema": { + "format": "int32", + "type": "integer" + } + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "examples": { + "example-1": { + "value": [ + { + "account_id": "449e7a5c-69d3-4b8a-aaaf-5c9b713ebc65", + "additional_information": "string", + "amount": "string", + "bank_id": "f1ae96de-94c1-468e-93a3-6b7213930ca8", + "created_at": "2019-08-24T14:15:22Z", + "direction": "INCOMING", + "expires_at": "2019-08-24T14:15:22Z", + "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08", + "ira": { + "distribution_reason": "normal", + "fed_withholding_amount": "102.5", + "fed_withholding_pct": "10.25", + "state_withholding_amount": "97.5", + "state_withholding_pct": "9.75", + "tax_year": "2024" + }, + "reason": "string", + "relationship_id": "81412018-ffa2-43f9-a3eb-d39f1c5e0f87", + "status": "QUEUED", + "type": "ach", + "updated_at": "2019-08-24T14:15:22Z" + } + ] + }, + "example-2": { + "value": [ + { + "account_id": "449e7a5c-69d3-4b8a-aaaf-5c9b713ebc65", + "additional_information": "string", + "amount": "string", + "bank_id": "f1ae96de-94c1-468e-93a3-6b7213930ca8", + "created_at": "2019-08-24T14:15:22Z", + "direction": "INCOMING", + "expires_at": "2019-08-24T14:15:22Z", + "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08", + "ira": { + "distribution_reason": "normal", + "fed_withholding_amount": "102.5", + "fed_withholding_pct": "10.25", + "state_withholding_amount": "97.5", + "state_withholding_pct": "9.75", + "tax_year": "2024" + }, + "reason": "string", + "relationship_id": "81412018-ffa2-43f9-a3eb-d39f1c5e0f87", + "status": "QUEUED", + "type": "ach", + "updated_at": "2019-08-24T14:15:22Z" + } + ] + } + }, + "schema": { + "items": { + "$ref": "#/components/schemas/Transfer" + }, + "type": "array" + } + } + }, + "description": "Success." + } + }, + "summary": "Retrieve List of Transfers for an Account.", + "tags": [ + "Funding" + ] + }, + "parameters": [ + { + "$ref": "#/components/parameters/AccountID" + } + ], + "post": { + "description": "Create a new transfer to deposit money into or withdraw money from an account.\n\nTwo `transfer_type` values are accepted, and both are available in sandbox and production:\n\n- **`ach`** — supports both `INCOMING` (deposit) and `OUTGOING` (withdrawal) directions. A `relationship_id` from a previously created [ACH Relationship](https://docs.alpaca.markets/reference/createachrelationshipforaccount) is required.\n- **`wire`** — supports the `OUTGOING` (withdrawal) direction only. Incoming wires cannot be initiated through this endpoint; they are pushed in by the sending bank and recorded automatically. A `bank_id` from a previously created [Bank Relationship](https://docs.alpaca.markets/reference/createrecipientbank) is required, and the bank must be in `APPROVED` status before the transfer will progress past `QUEUED`.\n\nIn the sandbox environment, ACH deposits and withdrawals settle instantly against virtual funds. Outgoing wire withdrawals are accepted and simulated end-to-end — no funds are transmitted to a bank, but the transfer progresses to `COMPLETE` and the account is debited against virtual funds. Sandbox wire withdrawals are asynchronous (not instant) and auto-complete on weekdays only; transfers submitted on a weekend will not progress until Monday. For more on funding accounts in sandbox, see [this tutorial](https://alpaca.markets/learn/fund-broker-api/).", + "operationId": "createTransferForAccount", + "parameters": [ + { + "in": "path", + "name": "account_id", + "required": true, + "schema": { + "format": "uuid", + "type": "string" + } + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CreateTransferRequest" + } + } + }, + "required": true + }, + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Transfer" + } + } + }, + "description": "Successfully requested a transfer." + }, + "400": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + }, + "description": "The request body is malformed (e.g. invalid JSON)." + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + }, + "description": "The account is not permitted to perform this transfer. Examples include:\n\n- `deposits are not permitted for this account` — the account's `depositable_status` is not `allowed` / `limited`.\n- `withdrawals are not permitted for this account` — the account's `withdrawable_status` is not `allowed` / `limited`.\n" + }, + "422": { + "content": { + "application/json": { + "examples": { + "wireIncomingRejected": { + "summary": "Wire transfer with direction = INCOMING", + "value": { + "code": 40010001, + "message": "cannot submit incoming wire transfer using this API" + } + } + }, + "schema": { + "$ref": "#/components/schemas/Error" + } + } + }, + "description": "The request was rejected by validation. Common reasons include:\n\n- `cannot submit incoming wire transfer using this API` — sent `transfer_type: wire` with `direction: INCOMING`. Incoming wires cannot be initiated through this endpoint.\n- `invalid relationship_id` / `bank_id required for wire transfer` — missing the required relationship/bank identifier for the chosen `transfer_type`.\n- `bank_id should be empty for ach transfer` / `relationship_id should be empty for wire transfer` — provided the wrong identifier for the chosen `transfer_type`.\n- `only wire transfer type can provide additional information for the transfer` — `additional_information` was supplied on a non-wire request.\n- `amount must be greater than 0.00` / `deposit amount must be greater than or equal to ` / `withdrawal amount must be greater than ` — amount fails the minimum-amount checks.\n- `transfer_type must be either ach or wire` / `direction must be either incoming or outgoing` — invalid enum values.\n" + } + }, + "summary": "Request a New Transfer", + "tags": [ + "Funding", + "Accounts" + ] + } + }, + "/v1/accounts/{account_id}/transfers/{transfer_id}": { + "delete": { + "description": "Request to close a transfer", + "operationId": "deleteTransfer", + "parameters": [ + { + "$ref": "#/components/parameters/AccountID" + }, + { + "description": "Transfer identifier", + "in": "path", + "name": "transfer_id", + "required": true, + "schema": { + "format": "uuid", + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "Success" + }, + "404": { + "description": "Transfer Not Found" + } + }, + "summary": "Request to Close a Transfer", + "tags": [ + "Funding" + ] + } + }, + "/v1/accounts/{account_id}/wallets": { + "get": { + "description": "Lists wallets for the account given in the path parameter. If an asset is specified and no wallet for the account and asset pair exists one will be created. If no asset is specified only existing wallets will be listed for the account. An account may have at most one wallet per asset.", + "operationId": "listCryptoFundingWallets", + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CryptoWallet" + } + } + }, + "description": "A single wallet object if an asset is specified or an array of wallet objects if no asset is specified" + }, + "default": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + }, + "description": "error" + } + }, + "summary": "Retrieve Crypto Funding Wallets", + "tags": [ + "Crypto Funding" + ] + }, + "parameters": [ + { + "$ref": "#/components/parameters/AccountID" + }, + { + "in": "query", + "name": "asset", + "schema": { + "type": "string" + } + }, + { + "description": "Optional chain identifier. Use to request wallets for a specific chain when asset is a multi-chain crypto asset. If not specified, network is considered.", + "in": "query", + "name": "chain", + "schema": { + "enum": [ + "SOL", + "ETH", + "BTC", + "XRP", + "ARB" + ], + "type": "string" + } + }, + { + "description": "Deprecated. Use chain instead. Optional network identifier which is used to request wallets for a specific network when asset is a multi-chain crypto asset. When both 'chain' and 'network' are specified, 'chain' will take precedence and 'network' will be ignored. If 'chain' and 'network' are not specified, the default 'network' (ethereum) will be used.", + "in": "query", + "name": "network", + "schema": { + "deprecated": true, + "enum": [ + "ethereum", + "solana" + ], + "type": "string" + } + } + ] + }, + "/v1/accounts/{account_id}/wallets/transfers": { + "get": { + "description": "Returns an array of all transfers associated with the given account across all wallets.", + "operationId": "listCryptoFundingTransfers", + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CryptoTransfer" + } + } + }, + "description": "An array of transfer objects" + }, + "default": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + }, + "description": "error" + } + }, + "summary": "Retrieve Crypto Funding Transfers", + "tags": [ + "Crypto Funding" + ] + }, + "parameters": [ + { + "$ref": "#/components/parameters/AccountID" + } + ], + "post": { + "description": "Creates a withdrawal request. Note that outgoing withdrawals must be sent to a whitelisted address and you must whitelist addresses at least 24 hours in advance. If you attempt to withdraw funds to a non-whitelisted address then the transfer will be rejected.", + "operationId": "createCryptoTransferForAccount", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CreateCryptoTransferRequest" + } + } + }, + "required": true + }, + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CryptoTransfer" + } + } + }, + "description": "Successfully requested a transfer." + } + }, + "summary": "Request a New Withdrawal", + "tags": [ + "Crypto Funding" + ] + } + }, + "/v1/accounts/{account_id}/wallets/transfers/{transfer_id}": { + "get": { + "description": "Returns a specific wallet transfer by passing into the query the transfer_id.", + "operationId": "getCryptoFundingTransfer", + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CryptoTransfer" + } + } + }, + "description": "A single crypto transfer object" + } + }, + "summary": "Retrieve a Crypto Funding Transfer", + "tags": [ + "Crypto Funding" + ] + }, + "parameters": [ + { + "$ref": "#/components/parameters/AccountID" + }, + { + "description": "The crypto transfer to retrieve", + "in": "path", + "name": "transfer_id", + "required": true, + "schema": { + "type": "string" + } + } + ] + }, + "/v1/accounts/{account_id}/wallets/whitelists": { + "get": { + "description": "Returns the list of whitelisted withdrawal addresses for the specified account.", + "operationId": "listWhitelistedAddress", + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/WhitelistedAddress" + } + } + }, + "description": "An array of whitelisted objects" + }, + "default": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + }, + "description": "error" + } + }, + "summary": "An array of whitelisted addresses", + "tags": [ + "Crypto Funding" + ] + }, + "parameters": [ + { + "$ref": "#/components/parameters/AccountID" + } + ], + "post": { + "description": "Submits a new whitelisted withdrawal address for the specified account. The chain is derived from the supplied asset symbol and address, and the same address cannot be whitelisted twice on the same chain.\n\nWhere travel rule reporting applies to the account, address details are validated against the travel rule provider before being persisted.\n\nNote that whitelisted addresses must wait at least 24 hours before they can be used as a withdrawal destination.", + "operationId": "createWhitelistedAddress", + "requestBody": { + "content": { + "application/json": { + "schema": { + "properties": { + "address": { + "description": "The address to be whitelisted", + "type": "string" + }, + "asset": { + "description": "Symbol of underlying asset for the whitelisted address", + "type": "string" + }, + "chain": { + "description": "Optional chain identifier. Use when the underlying asset for the whitelisted address is a multi-chain crypto asset.", + "enum": [ + "SOL", + "ETH", + "BTC", + "XRP", + "ARB" + ], + "type": "string" + } + }, + "type": "object" + } + } + }, + "required": true + }, + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/WhitelistedAddress" + } + } + }, + "description": "Successfully requested a whitelisted address" + } + }, + "summary": "Request a new whitelisted address", + "tags": [ + "Crypto Funding" + ] + } + }, + "/v1/accounts/{account_id}/wallets/whitelists/{whitelisted_address_id}": { + "delete": { + "description": "Deletes a whitelisted withdrawal address from the specified account by ID. Subsequent withdrawals targeting the deleted address will be rejected.", + "operationId": "deleteWhitelistedAddress", + "responses": { + "200": { + "description": "Successfully deleted a whitelisted address" + } + }, + "summary": "Delete a whitelisted address", + "tags": [ + "Crypto Funding" + ] + }, + "parameters": [ + { + "$ref": "#/components/parameters/AccountID" + }, + { + "description": "The whitelisted address to delete", + "in": "path", + "name": "whitelisted_address_id", + "required": true, + "schema": { + "type": "string" + } + } + ] + }, + "/v1/accounts/activities": { + "get": { + "description": "Returns a list of activities\n\nNotes:\n* Pagination is handled using the `page_token` and `page_size` parameters.\n* `page_token` represents the ID of the last item on your current page of results.\n For example, if the ID of the last activity in your first response is `20220203000000000::045b3b8d-c566-4bef-b741-2bf598dd6ae7`, you would pass that value as `page_token` to retrieve the next page of results.\n\n* If specified with a `direction` of `desc`, for example, the results will end before the activity with the specified ID.\n* If specified with a `direction` of `asc`, results will begin with the activity immediately after the one specified.\n* `page_size` is the maximum number of entries to return in the response.\n* If `date` is not specified, the default and maximum value is 100.\n* If `date` is specified, the default behavior is to return all results, and there is no maximum page size.", + "operationId": "getAccountActivities", + "parameters": [ + { + "description": "id of a single account to filter by", + "in": "query", + "name": "account_id", + "schema": { + "format": "uuid", + "type": "string" + } + }, + { + "description": "A comma-separated list of activity types used to filter the results.", + "explode": false, + "in": "query", + "name": "activity_types", + "schema": { + "items": { + "$ref": "#/components/schemas/ActivityType" + }, + "type": "array" + }, + "style": "form" + }, + { + "description": "The activity category. Cannot be used with \"activity_types\" parameter.", + "in": "query", + "name": "category", + "schema": { + "enum": [ + "trade_activity", + "non_trade_activity" + ], + "type": "string" + } + }, + { + "description": "Filter activities by their creation date (created_at), not the activity's settlement date. For non-trade activities such as fees, the creation date is typically the day after the trade date (in UTC). Both formats YYYY-MM-DD and YYYY-MM-DDTHH:MM:SSZ are supported.", + "in": "query", + "name": "date", + "schema": { + "format": "date-time", + "type": "string" + } + }, + { + "description": "Get activities created before this date. Both formats YYYY-MM-DD and YYYY-MM-DDTHH:MM:SSZ are supported.", + "in": "query", + "name": "until", + "schema": { + "format": "date-time", + "type": "string" + } + }, + { + "description": "Get activities created after this date. Both formats YYYY-MM-DD and YYYY-MM-DDTHH:MM:SSZ are supported.", + "in": "query", + "name": "after", + "schema": { + "format": "date-time", + "type": "string" + } + }, + { + "$ref": "#/components/parameters/Direction" + }, + { + "description": "The maximum number of entries to return in the response.", + "in": "query", + "name": "page_size", + "schema": { + "default": 100, + "maximum": 100, + "minimum": 1, + "type": "integer" + } + }, + { + "description": "Token used for pagination. Provide the ID of the last activity from the last page to retrieve the next set of results.", + "in": "query", + "name": "page_token", + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "items": { + "$ref": "#/components/schemas/Activity" + }, + "type": "array" + } + } + }, + "description": "Success" + } + }, + "summary": "Retrieve Account Activities", + "tags": [ + "Accounts" + ] + } + }, + "/v1/accounts/activities/{activity_type}": { + "get": { + "description": "Retrieves an array of Activity by type.\n\nIf {activity_type} is provided as part of the URL, category cannot be provided as query parameter. They are mutually exclusive.\n\nNotes:\n* Pagination is handled using the `page_token` and `page_size` parameters.\n* `page_token` represents the ID of the end of your current page of results.\n for example if in your first response the id of the last Activiy item returned in the array was `20220203000000000::045b3b8d-c566-4bef-b741-2bf598dd6ae7`, you'd pass that value as `page_token` to get the next page of results\n\n* If specified with a `direction` of `desc`, for example, the results will end before the activity with the specified ID.\n* If specified with a `direction` of `asc`, results will begin with the activity immediately after the one specified.\n* `page_size` is the maximum number of entries to return in the response.\n* If `date` is not specified, the default and maximum value is 100.\n* If `date` is specified, the default behavior is to return all results, and there is no maximum page size.", + "operationId": "getAccountActivitiesByType", + "parameters": [ + { + "description": "id of a single account to filter by", + "in": "query", + "name": "account_id", + "schema": { + "format": "uuid", + "type": "string" + } + }, + { + "description": "Filter activities by their creation date (created_at), not the activity's settlement date. For non-trade activities such as fees, the creation date is typically the day after the trade date (in UTC). Both formats YYYY-MM-DD and YYYY-MM-DDTHH:MM:SSZ are supported.", + "in": "query", + "name": "date", + "schema": { + "format": "date-time", + "type": "string" + } + }, + { + "description": "Get activities created before this date. Both formats YYYY-MM-DD and YYYY-MM-DDTHH:MM:SSZ are supported.", + "in": "query", + "name": "until", + "schema": { + "format": "date-time", + "type": "string" + } + }, + { + "description": "Get activities created after this date. Both formats YYYY-MM-DD and YYYY-MM-DDTHH:MM:SSZ are supported.", + "in": "query", + "name": "after", + "schema": { + "format": "date-time", + "type": "string" + } + }, + { + "$ref": "#/components/parameters/Direction" + }, + { + "description": "The maximum number of entries to return in the response.", + "in": "query", + "name": "page_size", + "schema": { + "default": 100, + "maximum": 100, + "minimum": 1, + "type": "integer" + } + }, + { + "description": "Token used for pagination. Provide the ID of the last activity from the last page to retrieve the next set of results.", + "in": "query", + "name": "page_token", + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "items": { + "$ref": "#/components/schemas/Activity" + }, + "type": "array" + } + } + }, + "description": "Success" + } + }, + "summary": "Retrieve Account Activities of Specific Type", + "tags": [ + "Accounts" + ] + }, + "parameters": [ + { + "$ref": "#/components/parameters/ActivityType" + } + ] + }, + "/v1/accounts/ira_excess_contributions": { + "get": { + "description": "Returns all overcontributed IRA accounts", + "operationId": "listIRAExcessContritbutions", + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "items": { + "$ref": "#/components/schemas/OvercontributedIRAAccount" + }, + "type": "array" + } + } + }, + "description": "list of overcontributed IRA accounts" + }, + "default": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + }, + "description": "error" + } + }, + "summary": "Retrieve Excess Contributions", + "tags": [ + "IRA" + ] + } + }, + "/v1/accounts/options/approvals": { + "get": { + "description": "This endpoint retrieves options trading level approval requests. Query parameters can be specified to filter the results. If multiple query parameters are specified, the results will be filtered to include only those that match all of the specified parameters. Each query parameter can only be specified once.", + "operationId": "requestListOptionsApprovals", + "parameters": [ + { + "description": "Only return results for the specified account.", + "in": "query", + "name": "account_id", + "schema": { + "example": "c8f1ef5d-edc0-4f23-9ee4-378f19cb92a4", + "format": "uuid", + "type": "string" + } + }, + { + "in": "query", + "name": "requested_level", + "schema": { + "description": "Only return requests with the specified requested level.\n0=Disabled, 1=Covered Call/Cash-Secured Put, 2=Long Call/Put, 3=Spreads/Straddles.\n", + "enum": [ + 0, + 1, + 2, + 3 + ], + "example": 3, + "type": "integer" + } + }, + { + "in": "query", + "name": "approved_level", + "schema": { + "description": "Only return requests with the specified approved level.\n0=Disabled, 1=Covered Call/Cash-Secured Put, 2=Long Call/Put, 3=Spreads/Straddles.\n", + "enum": [ + 0, + 1, + 2, + 3 + ], + "example": 3, + "type": "integer" + } + }, + { + "description": "Only return requests with the specified approval request status.", + "in": "query", + "name": "status", + "schema": { + "$ref": "#/components/schemas/OptionsApprovalStatus" + } + }, + { + "description": "The maximum number of results to return. The default (and maximum value) is 1000.", + "in": "query", + "name": "page_size", + "schema": { + "default": 1000, + "maximum": 1000, + "minimum": 1, + "type": "integer" + } + }, + { + "$ref": "#/components/parameters/PageToken" + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/OptionsApprovalsList" + } + } + }, + "description": "The request was submitted successfully." + }, + "400": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + }, + "description": "The request body is invalid." + }, + "401": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + }, + "description": "Client does not exist, you do not have access to the client, or \"client_secret\" is incorrect.\n" + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + }, + "description": "The correspondent entity does not have access to options approvals or the account does not exist.\n" + } + }, + "summary": "Retrieve options level approval requests (BETA)", + "tags": [ + "Accounts" + ] + } + }, + "/v1/accounts/positions": { + "get": { + "deprecated": true, + "description": "Retrieves a list of the account's open positions.\nThis endpoint is deprecated and will be removed in the future. Please use the [GET /v1/reporting/eod/positions endpoint](https://docs.alpaca.markets/reference/get-v1-reporting-eod-positions) instead.\n", + "operationId": "get-v1-accounts-positions", + "parameters": [ + { + "description": "The number of the page of the results to be fetched.", + "in": "query", + "name": "page", + "schema": { + "type": "integer" + } + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "examples": { + "Example 1": { + "value": { + "as_of": "2022-08-04T16:00:00-04:00", + "positions": { + "000c8a47-7487-430b-94e1-e628a71cd123": [ + { + "asset_class": "us_equity", + "asset_id": "b0b6dd9d-8b9b-48a9-ba46-b9d54906e415", + "asset_marginable": true, + "avg_entry_price": "172.34", + "change_today": "0", + "cost_basis": "13.63999992516", + "current_price": "166.13", + "exchange": "NASDAQ", + "lastday_price": "166.13", + "market_value": "13.14850404762", + "qty": "0.079145874", + "qty_available": "0.079145874", + "side": "long", + "symbol": "AAPL", + "unrealized_intraday_pl": "0", + "unrealized_intraday_plpc": "0", + "unrealized_pl": "-0.49149587754", + "unrealized_plpc": "-0.0360334223047464" + } + ] + } + } + } + }, + "schema": { + "properties": { + "as_of": { + "format": "date-time", + "type": "string" + }, + "positions": { + "type": "object" + } + }, + "type": "object" + } + } + }, + "description": "The response contains two fields:\n\nasof: the timestamp for which the positions are returned. It is always the last market close\n\npositions: an account-id to position list map, contains the requested page's accounts\nThe positions map is empty for the last page.\n\nNote: when fetching bulk positions, which can take multiple minutes depending on the number of accounts managed have, a market close can happen, and after that a new set of results might be returned. To make sure results are consistent, please always check that the as_of field didn't change during the whole fetching process." + } + }, + "summary": "Bulk Fetch All Accounts Positions", + "tags": [ + "Trading" + ] + } + }, + "/v1/assets": { + "get": { + "description": "Returns all assets", + "operationId": "getAssets", + "parameters": [ + { + "description": "Asset status to filter by, will default to `all`", + "in": "query", + "name": "status", + "schema": { + "default": "all", + "enum": [ + "active", + "inactive", + "all" + ], + "example": "all", + "type": "string" + } + }, + { + "description": "Asset class to filter by, `us_equity` or `crypto`. Defaults to `us_equity`", + "in": "query", + "name": "asset_class", + "schema": { + "default": "us_equity", + "enum": [ + "us_equity", + "crypto" + ], + "example": "us_equity", + "type": "string" + } + }, + { + "description": "Comma separated values to query for more than one attribute. Assets which have any of the given attributes will be included.\n\nSupported values:\n- `ptp_no_exception`: Asset is a Publicly Traded Partnership (PTP) without a qualified notice; non-U.S. customers may incur 10% withholding on gross proceeds as per IRS guidance, and are blocked from being purchased by default.\n- `ptp_with_exception`: Users can open positions in these PTPs without general restrictions.\n- `ipo`: Accepting limit orders only before the stock begins trading on the secondary market.\n- `has_options`: The underlying equity has listed options available on the platform. Note: if the equity had inactive/expired contracts in the past, this will still show up.\n- `options_late_close`: Indicates the underlying asset's options contracts close at 4:15pm ET instead of the standard 4:00pm ET.\n- `fractional_eh_enabled`: Indicates the asset accepts fractional orders during extended hours sessions (pre-market, post-market, and overnight if enabled).\n- `overnight_tradable`: Asset is eligible for overnight (24x5) trading in supported venues on the platform.\n- `overnight_halted`: Asset is eligible for overnight trading but is currently halted/blocked for overnight sessions due to risk, corporate action, compliance, or venue constraints.", + "explode": false, + "in": "query", + "name": "attributes", + "schema": { + "default": [], + "example": [ + "ptp_no_exception", + "ipo" + ], + "items": { + "enum": [ + "ptp_no_exception", + "ptp_with_exception", + "ipo", + "has_options", + "options_late_close", + "fractional_eh_enabled", + "overnight_tradable", + "overnight_halted" + ], + "type": "string" + }, + "type": "array" + } + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "items": { + "$ref": "#/components/schemas/Asset" + }, + "type": "array" + } + } + }, + "description": "An array of asset objects." + } + }, + "summary": "Retrieve All Assets", + "tags": [ + "Assets" + ] + } + }, + "/v1/assets/{symbol_or_asset_id}": { + "get": { + "description": "Returns the requested asset, if found", + "operationId": "getAssetBySymbolOrId", + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Asset" + } + } + }, + "description": "Returns asset" + }, + "404": { + "description": "Asset not found" + } + }, + "summary": "Retrieve an Asset by ID", + "tags": [ + "Assets" + ] + }, + "parameters": [ + { + "description": "you can use either the asset's Id or the symbol to search", + "in": "path", + "name": "symbol_or_asset_id", + "required": true, + "schema": { + "type": "string" + } + } + ] + }, + "/v1/assets/entry-requirements": { + "get": { + "description": "Returns all entry-requirements", + "operationId": "get-asset-entry-requirements", + "parameters": [ + { + "description": "Comma-separated symbols or asset ids. The symbols (or asset ids) for which asset entry requirements are to be requested. Maximum number of symbols allowed is 500.", + "example": "AAPL,SPY", + "in": "query", + "name": "symbols", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "items": { + "$ref": "#/components/schemas/AssetEntryRequirements" + }, + "type": "array" + } + } + }, + "description": "An array of asset entry requirement objects." + } + }, + "summary": "Retrieve Entry Requirements for requested assets", + "tags": [ + "Assets" + ] + } + }, + "/v1/assets/fixed_income/us_corporates": { + "get": { + "description": "Serves the list of US corporates available at Alpaca. The response is sorted by ISIN.", + "operationId": "UsCorporates", + "parameters": [ + { + "in": "query", + "name": "bond_status", + "schema": { + "$ref": "#/components/schemas/bond_status" + } + }, + { + "description": "A comma-separated list of ISINs with a limit of 1000.", + "example": "US912810UG12,US912797PM34", + "in": "query", + "name": "isins", + "schema": { + "type": "string" + } + }, + { + "description": "A comma-separated list of CUSIPs with a limit of 1000.", + "example": "912810UG1,912797PM3", + "in": "query", + "name": "cusips", + "schema": { + "type": "string" + } + }, + { + "description": "A comma-separated list of tickers with a limit of 1000.", + "example": "BAC,MSFT", + "in": "query", + "name": "tickers", + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/us_corporates_resp" + } + } + }, + "description": "OK", + "headers": { + "X-RateLimit-Limit": { + "$ref": "#/components/headers/ratelimit_limit" + }, + "X-RateLimit-Remaining": { + "$ref": "#/components/headers/ratelimit_remaining" + }, + "X-RateLimit-Reset": { + "$ref": "#/components/headers/ratelimit_reset" + } + } + }, + "400": { + "$ref": "#/components/responses/400" + }, + "401": { + "$ref": "#/components/responses/401" + }, + "403": { + "$ref": "#/components/responses/403" + }, + "429": { + "$ref": "#/components/responses/429" + }, + "500": { + "$ref": "#/components/responses/500" + } + }, + "summary": "Get US corporates", + "tags": [ + "Assets" + ] + } + }, + "/v1/assets/fixed_income/us_treasuries": { + "get": { + "description": "Serves the list of US treasuries available at Alpaca. The response is sorted by ISIN.", + "operationId": "UsTreasuries", + "parameters": [ + { + "in": "query", + "name": "subtype", + "schema": { + "$ref": "#/components/schemas/treasury_subtype" + } + }, + { + "in": "query", + "name": "bond_status", + "schema": { + "$ref": "#/components/schemas/bond_status" + } + }, + { + "description": "A comma-separated list of CUSIPs with a limit of 1000.", + "example": "912810UG1,912797PM3", + "in": "query", + "name": "cusips", + "schema": { + "type": "string" + } + }, + { + "description": "A comma-separated list of ISINs with a limit of 1000.", + "example": "US912810UG12,US912797PM34", + "in": "query", + "name": "isins", + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/us_treasuries_resp" + } + } + }, + "description": "OK", + "headers": { + "X-RateLimit-Limit": { + "$ref": "#/components/headers/ratelimit_limit" + }, + "X-RateLimit-Remaining": { + "$ref": "#/components/headers/ratelimit_remaining" + }, + "X-RateLimit-Reset": { + "$ref": "#/components/headers/ratelimit_reset" + } + } + }, + "400": { + "$ref": "#/components/responses/400" + }, + "403": { + "$ref": "#/components/responses/403" + }, + "429": { + "$ref": "#/components/responses/429" + }, + "500": { + "$ref": "#/components/responses/500" + } + }, + "summary": "Get US treasuries", + "tags": [ + "Assets" + ] + } + }, + "/v1/calendar": { + "get": { + "description": "The calendar API serves the full list of market days from 1970 to 2029. It can also be queried by specifying a start and/or end time to narrow down the results. In addition to the dates, the response also contains the specific open and close times for the market days, taking into account early closures.\n", + "operationId": "LegacyCalendar", + "parameters": [ + { + "$ref": "#/components/parameters/legacy_start" + }, + { + "$ref": "#/components/parameters/legacy_end" + }, + { + "$ref": "#/components/parameters/legacy_date_type" + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/legacy_public_calendar_resp" + } + } + }, + "description": "OK", + "headers": { + "X-RateLimit-Limit": { + "$ref": "#/components/headers/ratelimit_limit" + }, + "X-RateLimit-Remaining": { + "$ref": "#/components/headers/ratelimit_remaining" + }, + "X-RateLimit-Reset": { + "$ref": "#/components/headers/ratelimit_reset" + } + } + }, + "400": { + "$ref": "#/components/responses/400" + }, + "429": { + "$ref": "#/components/responses/429" + }, + "500": { + "$ref": "#/components/responses/500" + } + }, + "summary": "Get US Market Calendar", + "tags": [ + "Calendar" + ] + } + }, + "/v1/cash_interest/apr_tiers": { + "get": { + "description": "List all available APR tiers. These tiers may be assigned to an account, and will be used to determine the interest rate paid on uninvested cash balances.", + "operationId": "get-v1-list-apr-tiers", + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ListAPRTiersResponse" + } + } + }, + "description": "Successful response, returns a list of APR tiers or none if no tiers are available." + }, + "401": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + }, + "description": "Unauthorized. Please check your API key and try again." + }, + "429": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + }, + "description": "Too many requests. Please wait a moment and try again." + }, + "500": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + }, + "description": "A server error occurred. Please contact Alpaca." + } + }, + "summary": "List APR Tiers", + "tags": [ + "Cash Interest" + ] + } + }, + "/v1/clock": { + "get": { + "description": "The clock API serves the current market timestamp, whether or not the market is currently open, as well as the times of the next market open and close.\n", + "operationId": "LegacyClock", + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/legacy_clock" + } + } + }, + "description": "OK", + "headers": { + "X-RateLimit-Limit": { + "$ref": "#/components/headers/ratelimit_limit" + }, + "X-RateLimit-Remaining": { + "$ref": "#/components/headers/ratelimit_remaining" + }, + "X-RateLimit-Reset": { + "$ref": "#/components/headers/ratelimit_reset" + } + } + }, + "429": { + "$ref": "#/components/responses/429" + }, + "500": { + "$ref": "#/components/responses/500" + } + }, + "summary": "Get US Market Clock", + "tags": [ + "Calendar" + ] + } + }, + "/v1/corporate_actions/announcements": { + "get": { + "deprecated": true, + "description": "This endpoint is deprecated, please use [the new corporate actions endpoint](https://docs.alpaca.markets/reference/corporateactions-1) instead.", + "operationId": "getCorporateAnnouncements", + "parameters": [ + { + "description": "A comma-delimited list of CorporateActionType values", + "in": "query", + "name": "ca_types", + "required": true, + "schema": { + "type": "string" + } + }, + { + "description": "The start (inclusive) of the date range when searching corporate action announcements. This should follow the YYYY-MM-DD format. The date range is limited to 90 days.", + "in": "query", + "name": "since", + "required": true, + "schema": { + "format": "date", + "type": "string" + } + }, + { + "description": "The end (inclusive) of the date range when searching corporate action announcements. This should follow the YYYY-MM-DD format. The date range is limited to 90 days.", + "in": "query", + "name": "until", + "required": true, + "schema": { + "format": "date", + "type": "string" + } + }, + { + "description": "The symbol of the company initiating the announcement.", + "in": "query", + "name": "symbol", + "schema": { + "type": "string" + } + }, + { + "description": "The CUSIP of the company initiating the announcement.", + "in": "query", + "name": "cusip", + "schema": { + "type": "string" + } + }, + { + "description": "An emum of possible ways to use the `since` and `until` parameters to search by.\n\nthe types are:\n\n- **declaration_date**: The date of the preliminary announcement details or the date that any subsequent term updates took place.\n- **ex_date**: The date on which any security purchasing activity will not result in a corporate action entitlement. Any selling activity that takes place on or after this date will result in a corporate action entitlement.\n- **record_date**: The date the company checks its records to determine who is shareholder in order to allocate entitlements.\n- **payable_date**: The date that the stock and cash positions will update according to the account positions as of the record date.", + "in": "query", + "name": "date_type", + "schema": { + "enum": [ + "declaration_date", + "ex_date", + "record_date", + "payable_date" + ], + "type": "string" + } + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "items": { + "$ref": "#/components/schemas/Announcement" + }, + "type": "array" + } + } + }, + "description": "OK" + }, + "400": { + "$ref": "#/components/responses/BadRequest" + } + }, + "summary": "Retrieve Announcements", + "tags": [ + "Corporate Actions" + ] + } + }, + "/v1/corporate_actions/announcements/{id}": { + "get": { + "deprecated": true, + "description": "This endpoint is deprecated, please use [the new corporate actions endpoint](https://docs.alpaca.markets/reference/corporateactions-1) instead.", + "operationId": "get-GET-v1-corporate_actions-announcements-id", + "responses": { + "200": { + "description": "An Announcement object." + }, + "400": { + "description": "The body in the request is not valid" + }, + "422": { + "description": "Invalid input value." + }, + "500": { + "description": "A server error occurred. Please contact Alpaca." + } + }, + "summary": "Retrieve a Specific Announcement", + "tags": [ + "Corporate Actions" + ] + }, + "parameters": [ + { + "description": "The id of the corporate action announcement to be returned.", + "in": "path", + "name": "id", + "required": true, + "schema": { + "type": "string" + } + } + ] + }, + "/v1/country-info": { + "get": { + "description": "The Country Info API serves country information for every supported countries including risk ratings and supported crypto states where applicable.", + "operationId": "queryCountryInfos", + "responses": { + "200": { + "content": { + "application/json": { + "examples": {}, + "schema": { + "$ref": "#/components/schemas/CountryInfos" + } + } + }, + "description": "Returns the countries information as a map" + } + }, + "summary": "Retrieve countries information", + "tags": [ + "Country Info" + ] + } + }, + "/v1/events/accounts/status": { + "get": { + "description": "The accounts events API provides streaming of account changes as they occur, via SSE (server sent events). Past events can also be queried.\n\nEvents are generated for changes to the following account properties:\n- account_blocked\n- admin_configurations\n- cash_interest\n- crypto_status\n- kyc_results\n- options\n- pattern_day_trader\n- status\n- trading_blocked\n\nOnly the changed properties are included in the event payload.\n\nQuery Parameter Rules:\n- `since` is required if `until` specified\n- `since_id` is required if `until_id` specified\n- `since_ulid` is required if `until_ulid` specified\n- `since`, `since_id` and `since_ulid` can't be used at the same time\n\nBehavior:\nThis API supports querying a range of events, starting now or in the past. If the end of the range is in the future or not specified, the connection is kept open and future events are pushed.\n\nTo be specific:\n- if `since`, `since_id` or `since_ulid` is not specified, this will not return any historic data\n- if `until`, `until_id` or `until_ulid` is reached, the stream will end with a status of 200\n\n---\n\nNote for people using the clients generated from this OAS spec. Currently OAS-3 doesn't have full support for representing SSE style responses from an API, so if you are using a generated client and don't specify a `since` and `until` there is a good chance the generated clients will hang waiting for the response to end.\n\nIf you require the streaming capabilities we recommend not using the generated clients for this specific usecase until the OAS-3 standards come to a consensus on how to represent this correctly in OAS-3.\n", + "operationId": "suscribeToAccountStatusSSE", + "parameters": [ + { + "description": "Format: YYYY-MM-DD", + "in": "query", + "name": "since", + "schema": { + "format": "date", + "type": "string" + } + }, + { + "description": "Format: YYYY-MM-DD", + "in": "query", + "name": "until", + "schema": { + "format": "date", + "type": "string" + } + }, + { + "in": "query", + "name": "since_id", + "schema": { + "type": "integer" + } + }, + { + "in": "query", + "name": "until_id", + "schema": { + "type": "integer" + } + }, + { + "in": "query", + "name": "since_ulid", + "schema": { + "format": "ulid", + "type": "string" + } + }, + { + "in": "query", + "name": "until_ulid", + "schema": { + "format": "ulid", + "type": "string" + } + }, + { + "in": "query", + "name": "id", + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "content": { + "text/event-stream": { + "examples": {}, + "schema": { + "items": { + "$ref": "#/components/schemas/AccountStatusEvent" + }, + "type": "array" + } + } + }, + "description": "Connected. Events will now start streaming as long as you keep the connection open." + } + }, + "summary": "Subscribe to Account Status Events (SSE)", + "tags": [ + "Events" + ] + } + }, + "/v1/events/journals/status": { + "get": { + "description": "The Events API provides event push as well as historical queries via SSE (server sent events).\n\nYou can listen to journal status updates as they get processed by our backoffice.\n\nHistorical events are streamed immediately if queried, and updates are pushed as events occur.\n\nQuery Params Rules:\n- `since` required if `until` specified\n- `since_id` required if `until_id` specified\n- `since_ulid` required if `until_ulid` specified\n- `since`, `since_id` or `since_ulid` can't be used at the same time\nBehavior:\n- if `since`, `since_id` or `since_ulid` not specified this will not return any historic data\n- if `until`, `until_id` or `until_ulid` reached stream will end (status 200)\n\n---\n\nThere is no compatibility between /v1/events/journals/status and /v2/events/journals/status, the ids (ulid) are always different, and the number of events might also different\n\nPlease note that the new `/v2` endpoint, is the same as, and was originally available under `/v2beta1`.\nWe encourage all customers to adjust their codebase from that interim beta endpoint to the `/v2` stable endpoint.\nIn the near future we will setup permanent redirect from `/v2beta1` to `/v2` before we completely remove the beta endpoint.\n\n---\n\nNote for people using the clients generated from this OAS spec. Currently OAS-3 doesn't have full support for representing SSE style responses from an API, so if you are using a generated client and don't specify a `since` and `until` there is a good chance the generated clients will hang waiting for the response to end.\n\nIf you require the streaming capabilities we recommend not using the generated clients for this specific usecase until the OAS-3 standards come to a consensus on how to represent this correctly in OAS-3.\n", + "operationId": "subscribeToJournalStatusSSE", + "parameters": [ + { + "description": "Format: YYYY-MM-DD", + "in": "query", + "name": "since", + "schema": { + "format": "date", + "type": "string" + } + }, + { + "description": "Format: YYYY-MM-DD", + "in": "query", + "name": "until", + "schema": { + "format": "date", + "type": "string" + } + }, + { + "in": "query", + "name": "since_id", + "schema": { + "type": "integer" + } + }, + { + "in": "query", + "name": "until_id", + "schema": { + "type": "integer" + } + }, + { + "in": "query", + "name": "since_ulid", + "schema": { + "format": "ulid", + "type": "string" + } + }, + { + "in": "query", + "name": "until_ulid", + "schema": { + "format": "ulid", + "type": "string" + } + }, + { + "in": "query", + "name": "id", + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "content": { + "text/event-stream": { + "schema": { + "items": { + "$ref": "#/components/schemas/JournalStatusEvent" + }, + "type": "array" + } + } + }, + "description": "Connected. Events will now start streaming as long as you keep the connection open." + } + }, + "summary": "Subscribe to Journal Events (SSE) (Legacy)", + "tags": [ + "Events" + ] + } + }, + "/v1/events/nta": { + "get": { + "description": "The Events API provides event push as well as historical queries via SSE (server sent events).\n\nYou can listen to non-trading activities updates as they get processed by our backoffice, for both end-user and firm accounts.\n\nHistorical events are streamed immediately if queried, and updates are pushed as events occur.\n\nYou can listen to when NTAs are pushed such as CSDs, JNLC (journals) or FEEs.\n\nQuery Params Rules:\n- `since` required if `until` specified\n- `since_id` required if `until_id` specified\n- `since_ulid` required if `until_ulid` specified\n- `since`, `since_id` or `since_ulid` can't be used at the same time\nBehavior:\n- if `since`, `since_id` or `since_ulid` not specified this will not return any historic data\n- if `until`, `until_id` or `until_ulid` reached stream will end (status 200)'", + "operationId": "get-v1-events-nta", + "parameters": [ + { + "in": "query", + "name": "id", + "schema": { + "type": "string" + } + }, + { + "description": "Format: YYYY-MM-DD", + "in": "query", + "name": "since", + "schema": { + "format": "date", + "type": "string" + } + }, + { + "description": "Format: YYYY-MM-DD", + "in": "query", + "name": "until", + "schema": { + "format": "date", + "type": "string" + } + }, + { + "in": "query", + "name": "since_id", + "schema": { + "type": "integer" + } + }, + { + "in": "query", + "name": "until_id", + "schema": { + "type": "integer" + } + }, + { + "in": "query", + "name": "since_ulid", + "schema": { + "format": "ulid", + "type": "string" + } + }, + { + "in": "query", + "name": "until_ulid", + "schema": { + "format": "ulid", + "type": "string" + } + }, + { + "in": "query", + "name": "include_preprocessing", + "schema": { + "type": "boolean" + } + }, + { + "description": "ID used to link activities who share a sibling relationship", + "in": "query", + "name": "group_id", + "schema": { + "format": "uuid", + "type": "string" + } + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/NonTradeActivityEvent" + } + } + }, + "description": "OK" + } + }, + "summary": "Subscribe to Non-Trading Activities Events (SSE)", + "tags": [ + "Events" + ] + } + }, + "/v1/events/trades": { + "get": { + "deprecated": true, + "description": "**Deprecation notice**\n\nAs part of the deprecation process, the legacy trade events API is now only available for existing broker-partners at: `GET /v1/events/trades` only for compatibility reasons.\n\nAll new broker partners will not have the option for the legacy trade event endpoint.\n\nAll new broker partners will have to integrate with the new `/v2/events/trades` endpoint.\n\nAlso, all existing broker partners are now recommended to upgrade to the `/v2/events/trades` endpoint, which provides faster event delivery times.\n\nFor trade events of MLeg (multi-leg) order, please use the `/v2/events/trades` endpoint.\n\nPlease note that the new `/v2` endpoint, is the same as, and was originally available under `/v2beta1`.\nWe encourage all customers to adjust their codebase from that interim beta endpoint to the `/v2` stable endpoint.\nIn the near future we will setup permanent redirect from `/v2beta1` to `/v2` before we completely remove the beta endpoint.\n\n---\n\nThe Events API provides event push as well as historical queries via SSE (server sent events).\n\nYou can listen to events related to trade updates. Most market trades sent during market hours are filled instantly; you can listen to limit order updates through this endpoint.\n\nHistorical events are streamed immediately if queried, and updates are pushed as events occur.\n\nQuery Params Rules:\n- `since` required if `until` specified\n- `since_id` required if `until_id` specified\n- `since_ulid` required if `until_ulid` specified\n- `since`, `since_id` or `since_ulid` can't be used at the same time\nBehavior:\n- if `since`, `since_id` or `since_ulid` not specified this will not return any historic data\n- if `until`, `until_id` or `until_ulid` reached stream will end (status 200)\n\n---\n\nNote for people using the clients generated from this OAS spec. Currently OAS-3 doesn't have full support for representing SSE style responses from an API, so if you are using a generated client and don't specify a `since` and `until` there is a good chance the generated clients will hang waiting for the response to end.\n\nIf you require the streaming capabilities we recommend not using the generated clients for this specific usecase until the OAS-3 standards come to a consensus on how to represent this correctly in OAS-3.\n\n---\n\n**Deprecation note**\n\nLegacy event id (integer based) is unavailable for new broker partners, and it will be deprecated for existing brokers as well.\nThis deprecation is includes the `since_id` and `until_id` query parameter and the `event_id` field in the response.\n\n---\n\n**Common events**\n\nThese are the events that are the expected results of actions you may have taken by sending API requests.\n\nThe meaning of the timestamp field changes for each type; the meanings have been specified here for which types the timestamp field will be present.\n\n- `accepted` Sent when an order is received and accepted by Alpaca\n- `pending_new` Sent when the order has been received by Alpaca and routed to the exchanges, but has not yet been accepted for execution.\n- `new` Sent when an order has been routed to exchanges for execution.\n- `fill` Sent when your order has been completely filled.\n - timestamp: The time at which the order was filled.\n- `partial_fill` Sent when a number of shares less than the total remaining quantity on your order has been filled.\n - timestamp: The time at which the shares were filled.\n- `canceled` Sent when your requested cancellation of an order is processed.\n - timestamp: The time at which the order was canceled.\n- `expired` Sent when an order has reached the end of its lifespan, as determined by the order's time in force value.\n - timestamp: The time at which the order expired.\n- `done_for_day` Sent when the order is done executing for the day, and will not receive further updates until the next trading day.\n- `replaced` Sent when your requested replacement of an order is processed.\n - timestamp: The time at which the order was replaced.\n\n**Rarer events**\n\nThese are events that may rarely be sent due to unexpected circumstances on the exchanges. It is unlikely you will need to design your code around them, but you may still wish to account for the possibility that they will occur.\n\n- `rejected` Sent when your order has been rejected.\n- `held` For multi-leg orders, the secondary orders (stop loss, take profit) will enter this state while waiting to be triggered.\n- `stopped` Sent when your order has been stopped, and a trade is guaranteed for the order, usually at a stated price or better, but has not yet occurred.\n- `pending_cancel` Sent when the order is awaiting cancellation. Most cancellations will occur without the order entering this state.\n- `pending_replace` Sent when the order is awaiting replacement.\n- `calculated` Sent when the order has been completed for the day - it is either filled or done_for_day - but remaining settlement calculations are still pending.\n- `suspended` Sent when the order has been suspended and is not eligible for trading.\n- `order_replace_rejected` Sent when the order replace has been rejected.\n- `order_cancel_rejected` Sent when the order cancel has been rejected.\n- `trade_bust`: Sent when a previously reported execution has been canceled (\"busted\") by the upstream exchange.\n- `trade_correct`: Sent when a previously reported trade has been corrected. For example, the exchange may have updated the price, quantity, or another execution parameter after the trade was initially reported.", + "operationId": "subscribeToTradeSSE", + "parameters": [ + { + "description": "Format: YYYY-MM-DD", + "in": "query", + "name": "since", + "schema": { + "format": "date", + "type": "string" + } + }, + { + "description": "Format: YYYY-MM-DD", + "in": "query", + "name": "until", + "schema": { + "format": "date", + "type": "string" + } + }, + { + "in": "query", + "name": "since_id", + "schema": { + "type": "integer" + } + }, + { + "in": "query", + "name": "until_id", + "schema": { + "type": "integer" + } + }, + { + "in": "query", + "name": "since_ulid", + "schema": { + "format": "ulid", + "type": "string" + } + }, + { + "in": "query", + "name": "until_ulid", + "schema": { + "format": "ulid", + "type": "string" + } + } + ], + "responses": { + "200": { + "content": { + "text/event-stream": { + "schema": { + "items": { + "$ref": "#/components/schemas/TradeUpdateEvent" + }, + "type": "array" + } + } + }, + "description": "Connected. Events will now start streaming as long as you keep the connection open." + }, + "410": { + "description": "Deprecated. The endpoint is not available for this partner." + } + }, + "summary": "Subscribe to Trade Events (SSE) (Legacy)", + "tags": [ + "Events" + ] + } + }, + "/v1/events/transfers/status": { + "get": { + "deprecated": true, + "description": "**Deprecation notice**\n\nAs part of the deprecation process, the legacy transfer events API is now only available for existing broker-partners at `GET /v1/events/transfers/status` and for compatibility reasons.\n\nAll new broker partners will not have the option to use the legacy transfer events endpoint.\n\nThey should integrate with the new `/v2/events/funding/status` endpoint instead.\n\nAlso, all existing broker partners are now recommended to upgrade to the `/v2/events/funding/status` endpoint, which provides faster event delivery times.\n\n---\n\nThe Events API provides event push as well as historical queries via SSE (server sent events).\n\nYou can listen to transfer status updates as they get processed by our backoffice, for both end-user and firm accounts.\n\nHistorical events are streamed immediately if queried, and updates are pushed as events occur.\n\nQuery Params Rules:\n- `since` required if `until` specified\n- `since_id` required if `until_id` specified\n- `since_ulid` required if `until_ulid` specified\n- `since`, `since_id` or `since_ulid` can't be used at the same time\nBehavior:\n- if `since`, `since_id` or `since_ulid` not specified this will not return any historic data\n- if `until`, `until_id` or `until_ulid` reached stream will end (status 200)\n\n---\n\nNote for people using the clients generated from this OAS spec. Currently OAS-3 doesn't have full support for representing SSE style responses from an API, so if you are using a generated client and don't specify a `since` and `until` there is a good chance the generated clients will hang waiting for the response to end.\n\nIf you require the streaming capabilities we recommend not using the generated clients for this specific usecase until the OAS-3 standards come to a consensus on how to represent this correctly in OAS-3.", + "operationId": "subscribeToTransferStatusSSE", + "parameters": [ + { + "description": "Format: YYYY-MM-DD", + "in": "query", + "name": "since", + "schema": { + "format": "date", + "type": "string" + } + }, + { + "description": "Format: YYYY-MM-DD", + "in": "query", + "name": "until", + "schema": { + "format": "date", + "type": "string" + } + }, + { + "in": "query", + "name": "since_id", + "schema": { + "type": "integer" + } + }, + { + "in": "query", + "name": "until_id", + "schema": { + "type": "integer" + } + }, + { + "in": "query", + "name": "since_ulid", + "schema": { + "format": "ulid", + "type": "string" + } + }, + { + "in": "query", + "name": "until_ulid", + "schema": { + "format": "ulid", + "type": "string" + } + } + ], + "responses": { + "200": { + "content": { + "text/event-stream": { + "schema": { + "items": { + "$ref": "#/components/schemas/TransferStatusEvent" + }, + "type": "array" + } + } + }, + "description": "Connected. Events will now start streaming as long as you keep the connection open." + }, + "410": { + "description": "Deprecated. The endpoint is not available for this partner." + } + }, + "summary": "Subscribe to Transfer Events (SSE) (Legacy)", + "tags": [ + "Events" + ] + } + }, + "/v1/fpsl/analytics/{account_id}/loans": { + "get": { + "description": "Returns aggregated FPSL interest and loan-activity counts for one account over a date range. Use this for \"earned so far this month\" customer UIs and for supervisory spot-checks against your internal accrual model.\n\nThe `{account_id}` path parameter accepts a account UUID (aggregation for that account).", + "operationId": "get-v1-fpsl-analytics-account-loans", + "parameters": [ + { + "description": "The account identifier (account UUID).", + "in": "path", + "name": "account_id", + "required": true, + "schema": { + "format": "uuid", + "type": "string" + } + }, + { + "description": "Filter loans after or on this start date (inclusive). Defaults to the first day of the current month, New York timezone.", + "in": "query", + "name": "start", + "required": false, + "schema": { + "format": "date", + "type": "string" + } + }, + { + "description": "Filter loans before this end date (exclusive). Defaults to the day after the current date, New York timezone.", + "in": "query", + "name": "end", + "required": false, + "schema": { + "format": "date", + "type": "string" + } + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/FPSLAnalyticsLoansResponse" + } + } + }, + "description": "Returns the aggregated FPSL loan analytics for the account." + }, + "400": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/FPSLError" + } + } + }, + "description": "One of the request parameters is invalid. See the returned message for details." + }, + "401": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/FPSLError" + } + } + }, + "description": "Authentication headers are missing or invalid. Make sure you authenticate your request with a valid API key." + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/FPSLError" + } + } + }, + "description": "User has no access to a resource." + }, + "500": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/FPSLError" + } + } + }, + "description": "Internal server error. We recommend retrying these later. If the issue persists, please contact us on Slack or on the Community Forum." + } + }, + "summary": "Read FPSL Loans Analytics for an Account", + "tags": [ + "FPSL Program" + ] + } + }, + "/v1/fpsl/loans": { + "get": { + "description": "Returns a list of all FPSL loans that match the specified filter criteria, ordered in ascending order by `date`, `account_number`, and `symbol`. Each entry represents a loan of a `symbol` on a given `date`, made on behalf of the specified `account_number`.", + "operationId": "get-v1-list-fpsl-loans", + "parameters": [ + { + "description": "The account identifier", + "in": "query", + "name": "account_id", + "required": false, + "schema": { + "format": "uuid", + "type": "string" + } + }, + { + "description": "Filter loans after or on this start date.", + "in": "query", + "name": "start", + "required": false, + "schema": { + "format": "date", + "type": "string" + } + }, + { + "description": "Filter loans before this end date.", + "in": "query", + "name": "end", + "required": false, + "schema": { + "format": "date", + "type": "string" + } + }, + { + "description": "The pagination token used to continue retrieving results. This value is returned in certain responses when additional data is available, typically due to a response size limit.", + "in": "query", + "name": "page_token", + "required": false, + "schema": { + "type": "string" + } + }, + { + "description": "The maximum number of data points to return in the response page.", + "in": "query", + "name": "limit", + "required": false, + "schema": { + "default": 1000, + "maximum": 10000, + "minimum": 1, + "type": "integer" + } + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ListFPSLLoansResponse" + } + } + }, + "description": "Returns a list of FPSL loans for the account, or an empty list if no loans are found." + }, + "400": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/FPSLError" + } + } + }, + "description": "One of the request parameters is invalid. See the returned message for details." + }, + "401": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/FPSLError" + } + } + }, + "description": "Authentication headers are missing or invalid. Make sure you authenticate your request with a valid API key." + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/FPSLError" + } + } + }, + "description": "User has no access to a resource." + }, + "500": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/FPSLError" + } + } + }, + "description": "Internal server error. We recommend retrying these later. If the issue persists, please contact us on Slack or on the Community Forum." + } + }, + "summary": "List FPSL Loans", + "tags": [ + "FPSL Program" + ] + } + }, + "/v1/fpsl/tiers": { + "get": { + "description": "List all available FPSL tiers. These tiers may be assigned to an account.", + "operationId": "get-v1-list-fpsl-tiers", + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "items": { + "$ref": "#/components/schemas/FPSLTier" + }, + "type": "array" + } + } + }, + "description": "Successful response, returns a list of FPSL tiers or none if no tiers are available." + }, + "401": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + }, + "description": "Unauthorized. Please check your API key and try again." + }, + "429": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + }, + "description": "Too many requests. Please wait a moment and try again." + }, + "500": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + }, + "description": "A server error occurred. Please contact Alpaca." + } + }, + "summary": "List FPSL Tiers", + "tags": [ + "FPSL Program" + ] + } + }, + "/v1/instant_funding": { + "get": { + "description": "Returns a list of instant funding records based on the query parameters. The results are\npaginated, sorted, and filtered based on the parameters provided.", + "operationId": "get-v1-instant-funding-list", + "parameters": [ + { + "description": "sort results based on created_at, amount, and deadline", + "in": "query", + "name": "sort_by", + "required": false, + "schema": { + "$ref": "#/components/schemas/ListSortBy" + } + }, + { + "description": "Defines the ordering of sort_by query. Defaults to ASC when not specified.", + "in": "query", + "name": "sort_order", + "required": false, + "schema": { + "$ref": "#/components/schemas/SortOrder" + } + }, + { + "description": "defines the number of results that will be returned from the request", + "in": "query", + "name": "limit", + "required": false, + "schema": { + "maximum": 100, + "minimum": 1, + "type": "integer" + } + }, + { + "description": "defines the number of results that will be skipped before picking the results that will be returned", + "in": "query", + "name": "offset", + "required": false, + "schema": { + "minimum": 1, + "type": "integer" + } + }, + { + "description": "filter transfers based on system date", + "in": "query", + "name": "system_date", + "required": false, + "schema": { + "format": "date", + "type": "string" + } + }, + { + "description": "filter transfers based on settlement deadline", + "in": "query", + "name": "deadline", + "required": false, + "schema": { + "format": "date", + "type": "string" + } + }, + { + "description": "filter transfers based on status", + "in": "query", + "name": "status", + "required": false, + "schema": { + "$ref": "#/components/schemas/InstantFundingStatus" + } + }, + { + "description": "filter transfers based on the date of creation", + "in": "query", + "name": "before", + "required": false, + "schema": { + "format": "date-time", + "type": "string" + } + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "items": { + "$ref": "#/components/schemas/InstantFunding" + }, + "type": "array" + } + } + }, + "description": "list of transfers" + }, + "default": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + }, + "description": "error" + } + }, + "summary": "Get Instant Funding List", + "tags": [ + "Instant Funding" + ] + }, + "post": { + "description": "Creates an instant funding request. The request will be processed and the funds will be\nmade available to the account in the form of a Memopost non trade activity. Upon settlement\nthe Memoposted will be corrected to a CSD activity.\n\n**Idempotency**: When the `Idempotency-Key` header is supplied, this endpoint is\nidempotent. Multiple requests with the same key and identical request body will\ncreate only one transfer. A subsequent request returns the previously created\ntransfer with the same response (no duplicate is created). If the same key is used\nwith a different request body, the API returns `422 Unprocessable Entity`.\n\n**Recommended for production**: Always supply `Idempotency-Key` when creating\ntransfers. This allows safe retries on timeouts, network errors, or 5xx responses\nwithout risking duplicate transfers. Use a client-generated unique value (e.g. UUID).\n", + "operationId": "post-v1-instant-funding", + "parameters": [ + { + "description": "Unique key for idempotent create. When supplied, duplicate requests (same key\nand body) return the original transfer instead of creating a new one. Use a\nclient-generated value (e.g. UUID) per logical transfer. Strongly recommended\nfor production to prevent duplicate transfers on retries.\n", + "in": "header", + "name": "Idempotency-Key", + "schema": { + "maxLength": 128, + "type": "string" + } + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CreateIFTransferRequest" + } + } + }, + "description": "details of the instant funding request", + "required": true + }, + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/InstantFunding" + } + } + }, + "description": "Instant transfer request created." + }, + "422": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + }, + "description": "Unprocessable Entity. Returned when an idempotency key is reused with a different request body." + }, + "default": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + }, + "description": "Error response." + } + }, + "summary": "Create an instant funding request", + "tags": [ + "Instant Funding" + ] + } + }, + "/v1/instant_funding/{instant_funding_id}": { + "delete": { + "description": "Cancels a single instant funding request based on the ID supplied if possible.", + "operationId": "delete-v1-instant-funding-single", + "parameters": [ + { + "description": "UUID ID of the instant funding transfer to cancel", + "in": "path", + "name": "instant_funding_id", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "204": { + "description": "Successful response." + }, + "default": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + }, + "description": "Error response." + } + }, + "summary": "Cancel an instant funding request", + "tags": [ + "Instant Funding" + ] + }, + "get": { + "description": "Returns an instant funding transfer based on the ID supplied.", + "operationId": "get-v1-instant-funding-single", + "parameters": [ + { + "description": "ID of the instant funding transfer", + "in": "path", + "name": "instant_funding_id", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/InstantFunding" + } + } + }, + "description": "Successful response." + }, + "default": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + }, + "description": "Error response." + } + }, + "summary": "Get an instant funding request", + "tags": [ + "Instant Funding" + ] + } + }, + "/v1/instant_funding/limits": { + "get": { + "description": "Returns globally configured limits for the correspondent. These limits are used to determine\nthe maximum amount that can be extended to all accounts, and reaching this limit will result\nin further requests to create instant funding requests being rejected.", + "operationId": "get-v1-instant-funding-correspondent-limits", + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CorrespondentLimit" + } + } + }, + "description": "correspondent instant funding limits" + }, + "default": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + }, + "description": "error" + } + }, + "summary": "Get instant funding limits", + "tags": [ + "Instant Funding" + ] + } + }, + "/v1/instant_funding/limits/accounts": { + "get": { + "description": "Returns the limits for individual partner accounts.", + "operationId": "get-v1-instant-funding-account-limits", + "parameters": [ + { + "description": "Filter limits based on comma-separated account numbers", + "explode": false, + "in": "query", + "name": "account_numbers", + "required": true, + "schema": { + "items": { + "type": "string" + }, + "type": "array" + }, + "style": "form" + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "items": { + "$ref": "#/components/schemas/AccountLimit" + }, + "type": "array" + } + } + }, + "description": "invidual broker account limits" + }, + "default": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + }, + "description": "error" + } + }, + "summary": "Get instant funding account limits", + "tags": [ + "Instant Funding" + ] + } + }, + "/v1/instant_funding/reports": { + "get": { + "description": "Returns instant funding reports which are to be used for daily reconciliation reporting.", + "operationId": "get-v1-instant-funding-reports", + "parameters": [ + { + "description": "The report type to be returned; 'summary' or 'detail', defaults to 'summary'", + "in": "query", + "name": "report_type", + "required": false, + "schema": { + "type": "string" + } + }, + { + "description": "The system date to be used for the report. If not provided then the last system date will be used.\n", + "in": "query", + "name": "system_date", + "required": false, + "schema": { + "format": "date", + "type": "string" + } + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "items": { + "$ref": "#/components/schemas/ReportsResponse" + }, + "type": "array" + } + } + }, + "description": "Successful response." + }, + "default": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + }, + "description": "Error response." + } + }, + "summary": "Get instant funding report", + "tags": [ + "Instant Funding" + ] + } + }, + "/v1/instant_funding/settlements": { + "get": { + "description": "Returns all settlements filtered by status if provided.", + "operationId": "get-v1-instant-funding-settlements", + "parameters": [ + { + "description": "comma delimited list of statuses to use as a filter", + "in": "query", + "name": "statuses", + "required": false, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SettlementsResponse" + } + } + }, + "description": "Successful response." + }, + "default": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + }, + "description": "Error response." + } + }, + "summary": "List settlements", + "tags": [ + "Instant Funding" + ] + }, + "post": { + "description": "Creates a new settlement, which will trigger the reconciliation process for all included\ntransfers and their interests. There is a limit of 50,000 transfers per settlement. If more\nthan 50,000 transfers need to be settled, they should be batched in multiple settlements.", + "operationId": "post-v1-instant-funding-settlements", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CreateSettlementRequest" + } + } + }, + "description": "details of the settlement request", + "required": true + }, + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SettlementResponse" + } + } + }, + "description": "Settlement created." + }, + "default": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + }, + "description": "Error response." + } + }, + "summary": "Create a new settlement", + "tags": [ + "Instant Funding" + ] + } + }, + "/v1/instant_funding/settlements/{settlement_id}": { + "get": { + "description": "Returns the settlement specified by the path parameter.", + "operationId": "get-v1-instant-funding-settlements-single", + "parameters": [ + { + "description": "ID of the settlement", + "in": "path", + "name": "settlement_id", + "required": true, + "schema": { + "format": "uuid", + "type": "string" + } + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SettlementResponse" + } + } + }, + "description": "Successful response." + }, + "default": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + }, + "description": "Error response." + } + }, + "summary": "Get a single settlement", + "tags": [ + "Instant Funding" + ] + } + }, + "/v1/ipos": { + "get": { + "description": "Returns a paginated list of IPO offerings currently known to Alpaca.\n\nUse this endpoint to power IPO discovery surfaces (e.g. an \"Upcoming IPOs\" list). Real-time lifecycle changes (availability, prospectus, 60-minute mail, allocations, cancellations) are pushed over the [IPO Events Stream](#operation/subscribeToIPOEventsSSE).\n", + "operationId": "listIPOOfferings", + "parameters": [ + { + "description": "Filter by offering availability.\n- `available` - accepting new orders.\n- `not_available` - not yet open for orders.\n- `closed` - no longer accepting orders.\n", + "in": "query", + "name": "availability", + "schema": { + "enum": [ + "available", + "not_available", + "closed" + ], + "type": "string" + } + }, + { + "description": "Filter by ticker symbol (case-insensitive exact match).", + "example": "EXMP", + "in": "query", + "name": "ticker", + "schema": { + "type": "string" + } + }, + { + "description": "Maximum number of offerings to return per page. Capped at `200`.", + "in": "query", + "name": "limit", + "schema": { + "default": 50, + "maximum": 200, + "minimum": 1, + "type": "integer" + } + }, + { + "description": "Opaque cursor returned in the previous response's `next_page_token` field. Pass it back to fetch the next page.", + "in": "query", + "name": "page_token", + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "examples": { + "default": { + "$ref": "#/components/examples/IPOOfferingListExample" + } + }, + "schema": { + "$ref": "#/components/schemas/IPOOfferingListResponse" + } + } + }, + "description": "A page of IPO offerings." + } + }, + "summary": "List IPO Offerings", + "tags": [ + "IPO" + ] + } + }, + "/v1/ipos/{offering_reference}": { + "get": { + "description": "Returns a single IPO offering by `offering_reference`.\n\nThe `offering_reference` value matches the one delivered on the [IPO Events Stream](#operation/subscribeToIPOEventsSSE).\n", + "operationId": "getIPOOffering", + "responses": { + "200": { + "content": { + "application/json": { + "examples": { + "default": { + "$ref": "#/components/examples/IPOOfferingGetExample" + } + }, + "schema": { + "$ref": "#/components/schemas/IPOOfferingResponse" + } + } + }, + "description": "The requested IPO offering." + }, + "404": { + "description": "No IPO offering exists for the given `offering_reference`." + } + }, + "summary": "Retrieve an IPO Offering", + "tags": [ + "IPO" + ] + }, + "parameters": [ + { + "description": "The unique offering identifier (also referred to as `ipo_reference` in the response body).", + "example": "FI111225", + "in": "path", + "name": "offering_reference", + "required": true, + "schema": { + "type": "string" + } + } + ] + }, + "/v1/jit/settlements": { + "get": { + "description": "Returns all JIT settlements filtered by status if provided.", + "operationId": "get-v1-jit-settlements", + "parameters": [ + { + "description": "comma delimited list of statuses to use as a filter", + "in": "query", + "name": "statuses", + "required": false, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SettlementsResponse" + } + } + }, + "description": "Successful response." + }, + "default": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + }, + "description": "Error response." + } + }, + "summary": "List all JIT Settlements", + "tags": [ + "Funding" + ] + }, + "post": { + "description": "Creates a new JIT settlement, which will trigger the reconciliation process for all included accounts.\n", + "operationId": "post-v1-jit-settlements", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CreateJITSettlementRequest" + } + } + }, + "description": "details of the JIT settlement request", + "required": true + }, + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SettlementResponse" + } + } + }, + "description": "Settlement created." + }, + "default": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + }, + "description": "Error response." + } + }, + "summary": "Create a new JIT settlement", + "tags": [ + "Funding" + ] + } + }, + "/v1/jit/settlements/{settlement_id}": { + "get": { + "description": "Returns the JIT settlement specified by the path parameter.", + "operationId": "get-v1-jit-settlements-single", + "parameters": [ + { + "description": "ID of the JIT settlement", + "in": "path", + "name": "settlement_id", + "required": true, + "schema": { + "format": "uuid", + "type": "string" + } + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SettlementResponse" + } + } + }, + "description": "Successful response." + }, + "default": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + }, + "description": "Error response." + } + }, + "summary": "Get a single JIT settlement", + "tags": [ + "Funding" + ] + } + }, + "/v1/journals": { + "get": { + "description": "Returns an array of journal objects.", + "operationId": "getAllJournals", + "parameters": [ + { + "description": "By journal creation date. Format: 2020-01-01", + "in": "query", + "name": "after", + "schema": { + "format": "date", + "type": "string" + } + }, + { + "description": "By journal creation date. Format: 2020-01-01", + "in": "query", + "name": "before", + "schema": { + "format": "date", + "type": "string" + } + }, + { + "description": "See the JournalStatus model for more info", + "in": "query", + "name": "status", + "schema": { + "enum": [ + "pending", + "canceled", + "executed", + "queued", + "rejected", + "deleted", + "refused", + "sent_to_clearing", + "correct" + ], + "type": "string" + } + }, + { + "description": "JNLC or JNLS", + "in": "query", + "name": "entry_type", + "schema": { + "enum": [ + "JNLC", + "JNLS" + ], + "type": "string" + } + }, + { + "description": "The account id that received the journal", + "in": "query", + "name": "to_account", + "schema": { + "format": "uuid", + "type": "string" + } + }, + { + "description": "The account id that initiated the journal", + "in": "query", + "name": "from_account", + "schema": { + "format": "uuid", + "type": "string" + } + }, + { + "description": "The maximum number of results to return", + "in": "query", + "name": "limit", + "required": false, + "schema": { + "type": "integer" + } + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "items": { + "$ref": "#/components/schemas/Journal" + }, + "type": "array" + } + } + }, + "description": "OK" + }, + "400": { + "content": { + "application/json": { + "schema": { + "type": "string" + } + } + }, + "description": "One of the parameters is invalid." + }, + "422": { + "content": { + "application/json": { + "schema": { + "type": "string" + } + } + }, + "description": "Result set exceeds 100,000 records." + } + }, + "summary": "Retrieve a List Journals", + "tags": [ + "Journals" + ] + }, + "post": { + "description": "A journal can be JNLC (move cash) or JNLS (move shares), dictated by `entry_type`. Generally, journal requests are subject to approval and starts from the `pending` status. The status changes are propagated through the Event API. Under certain conditions agreed for the partner, such journal transactions that meet the criteria are executed right away.\n\n**Idempotency**: When the `Idempotency-Key` header is supplied, this endpoint is\nidempotent. Multiple requests with the same key and identical request body will\ncreate only one journal. A subsequent request returns the previously created\njournal with the same response (no duplicate is created). If the same key is used\nwith a different request body, the API returns `422 Unprocessable Entity`.\n\n**Recommended for production**: Always supply `Idempotency-Key` when creating\njournals. This allows safe retries on timeouts, network errors, or 5xx responses\nwithout risking duplicate journals. Use a client-generated unique value (e.g. UUID).\n", + "operationId": "createJournal", + "parameters": [ + { + "description": "Unique key for idempotent create. When supplied, duplicate requests (same key\nand body) return the original journal instead of creating a new one. Use a\nclient-generated value (e.g. UUID) per logical journal. Strongly recommended\nfor production to prevent duplicate journals on retries.\n", + "in": "header", + "name": "Idempotency-Key", + "schema": { + "maxLength": 128, + "type": "string" + } + } + ], + "requestBody": { + "content": { + "application/json": { + "example": { + "amount": "115.5", + "entry_type": "JNLC", + "from_account": "7c891489-574f-4f9a-82f0-4082a07f4736", + "to_account": "2d47a229-0c25-40a2-8cc7-b2c8821ff93a" + }, + "schema": { + "$ref": "#/components/schemas/CreateJournalRequest" + } + } + }, + "required": true + }, + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Journal" + } + } + }, + "description": "The New Journal object" + }, + "400": { + "content": { + "application/json": { + "schema": { + "type": "string" + } + } + }, + "description": "One of the parameters is invalid." + }, + "403": { + "content": { + "application/json": { + "schema": { + "type": "string" + } + } + }, + "description": "The amount requested to move is not available." + }, + "404": { + "content": { + "application/json": { + "schema": { + "type": "string" + } + } + }, + "description": "One of the account is not found." + }, + "422": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + }, + "description": "Unprocessable Entity. Returned when an idempotency key is reused with a different request body." + } + }, + "summary": "Create a Journal", + "tags": [ + "Journals" + ] + } + }, + "/v1/journals/{journal_id}": { + "delete": { + "description": "You can only delete a journal if the journal is still in a pending state, if a journal is executed you will not be able to delete. The alternative is to create a mirror journal entry to reverse the flow of funds.", + "operationId": "deleteJournalById", + "responses": { + "204": { + "description": "The cancel request succeeded. (No-content)\n" + }, + "404": { + "description": "The journal is not found.\n" + }, + "422": { + "description": "The journal is not in the pending status.\n" + } + }, + "summary": "Cancel a Pending Journal", + "tags": [ + "Journals" + ] + }, + "get": { + "description": "You can query a specific journal entry that you submitted to Alpaca by passing into the query the journal_id.\n\nWill return a journal entry if a journal entry with journal_id exists, otherwise will throw an error.", + "operationId": "get-v1-journals-journal_id", + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Journal" + } + } + }, + "description": "OK" + } + }, + "summary": "Retrieve a Single Journal Entry", + "tags": [ + "Journals" + ] + }, + "parameters": [ + { + "in": "path", + "name": "journal_id", + "required": true, + "schema": { + "format": "uuid", + "type": "string" + } + } + ] + }, + "/v1/journals/batch": { + "post": { + "description": "You can create a batch of journal requests by using this endpoint. This is enabled on JNLC type Journals for now only.\n\nEvery single request must be valid for the entire batch operation to succeed.\n\nIn the case of a successful request, the response will contain an array of journal objects with an extra attribute error_message in the case when a specific account fails to receive a journal.\n\n**Idempotency**: When the `Idempotency-Key` header is supplied, this endpoint is\nidempotent. Multiple requests with the same key and identical request body will\ncreate only one batch. A subsequent request returns the previously created\nbatch with the same response (no duplicate is created). If the same key is used\nwith a different request body, the API returns `422 Unprocessable Entity`.\n\n**Recommended for production**: Always supply `Idempotency-Key` when creating\njournal batches. This allows safe retries on timeouts, network errors, or 5xx responses\nwithout risking duplicate batches. Use a client-generated unique value (e.g. UUID).", + "operationId": "createBatchJournal", + "parameters": [ + { + "description": "The unique key for idempotent create. When supplied, duplicate requests (same key\nand body) return the original journal batch instead of creating a new one. Use a\nclient-generated value (e.g. UUID) per logical batch. Strongly recommended\nfor production to prevent duplicate journal batches on retries.\n", + "in": "header", + "name": "Idempotency-Key", + "schema": { + "maxLength": 128, + "type": "string" + } + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BatchJournalRequest" + } + } + }, + "description": "", + "required": true + }, + "responses": { + "200": { + "content": { + "application/json": { + "examples": {}, + "schema": { + "items": { + "$ref": "#/components/schemas/BatchJournalResponse" + }, + "type": "array" + } + } + }, + "description": "an array of journal objects with an extra attribute error_message in the case when a specific account fails to receive a journal." + }, + "422": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + }, + "description": "Unprocessable Entity. Returned when an idempotency key is reused with a different request body." + } + }, + "summary": "Create a Batch Journal Transaction (One-to-Many)", + "tags": [ + "Journals" + ] + } + }, + "/v1/journals/reverse_batch": { + "post": { + "description": "You can also create a batch journal request by using the following endpoint. This is enabled on JNLC for now only.\n\nNote that if there is an invalid account_id the whole batch operation will be canceled.\nEvery single request must be valid for the entire batch operation to succeed.\n\nIn the case of a successful request, the response will contain an array of journal objects with an extra attribute error_message in the case when a specific account fails to submit a journal.", + "operationId": "post-v1-journals-reverse_batch", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ReverseBatchJournalRequest" + } + } + } + }, + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "items": { + "$ref": "#/components/schemas/BatchJournalResponse" + }, + "type": "array" + } + } + }, + "description": "OK" + }, + "400": { + "description": "Invalid Request Body" + }, + "403": { + "description": "Insufficient Balance (JNLC) or Insufficient Assets (JNLS)" + }, + "404": { + "description": "Account not found" + } + }, + "summary": "Create a Reverse Batch Journal Transaction (Many-to-One)", + "tags": [ + "Journals" + ] + } + }, + "/v1/oauth/authorize": { + "post": { + "description": "The operation issues an OAuth code which can be used in the OAuth code flow.\n", + "operationId": "authorizeOAuthToken", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/OAuthTokenRequest" + } + } + }, + "required": true + }, + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/AuthorizeOAuthTokenResponse" + } + } + }, + "description": "Successfully issued a code." + }, + "401": { + "content": { + "application/json": { + "schema": { + "type": "string" + } + } + }, + "description": "Client does not exist, you do not have access to the client, or \"client_secret\" is incorrect.\n" + }, + "422": { + "content": { + "application/json": { + "schema": { + "type": "string" + } + } + }, + "description": "Redirect URI or scope is invalid.\n" + } + }, + "summary": "Authorize an OAuth Token", + "tags": [ + "OAuth" + ] + } + }, + "/v1/oauth/clients/{client_id}": { + "get": { + "description": "The endpoint returns the details of OAuth client to display in the authorization page.\n", + "operationId": "getOAuthClient", + "parameters": [ + { + "description": "code or token", + "in": "query", + "name": "response_type", + "schema": { + "enum": [ + "code", + "token" + ], + "example": "token", + "type": "string" + } + }, + { + "description": "Redirect URI of the OAuth flow", + "in": "query", + "name": "redirect_uri", + "schema": { + "example": "https://example.com/authorize", + "type": "string" + } + }, + { + "description": "Requested scopes by the OAuth flow", + "in": "query", + "name": "scope", + "schema": { + "example": "general", + "type": "string" + } + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "examples": { + "example-1": { + "value": { + "client_id": "7a3c52a910e1dc2abbb14da2b6b8e711", + "description": "Sample description", + "live_trading_approved": false, + "name": "TradingApp", + "privacy_policy": "", + "redirect_uri": [ + "http://localhost" + ], + "status": "ACTIVE", + "terms_of_use": "", + "url": "http://test.com" + } + } + }, + "schema": { + "$ref": "#/components/schemas/OathClientResponse" + } + } + }, + "description": "Success." + }, + "401": { + "content": { + "application/json": { + "schema": { + "type": "string" + } + } + }, + "description": "Client does not exist or you do not have access to the client.\n" + } + }, + "summary": "Get an OAuth client", + "tags": [ + "OAuth" + ] + }, + "parameters": [ + { + "in": "path", + "name": "client_id", + "required": true, + "schema": { + "format": "uuid", + "type": "string" + } + } + ] + }, + "/v1/oauth/token": { + "post": { + "description": "The operation issues an OAuth code which can be used in the OAuth code flow.\n", + "operationId": "issueOAuthToken", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/OAuthTokenRequest" + } + } + }, + "required": true + }, + "responses": { + "200": { + "content": { + "application/json": { + "examples": { + "example-1": { + "value": { + "access_token": "87586f14-c3f4-4912-b107-f75bc17ff87a", + "scope": "general", + "token_type": "Bearer" + } + } + }, + "schema": { + "$ref": "#/components/schemas/IssueOAuthTokenResponse" + } + } + }, + "description": "Success." + }, + "401": { + "content": { + "application/json": { + "schema": { + "type": "string" + } + } + }, + "description": "Client does not exist, you do not have access to the client, or \"client_secret\" is incorrect.\n" + }, + "422": { + "content": { + "application/json": { + "schema": { + "type": "string" + } + } + }, + "description": "Redirect URI or scope is invalid.\n" + } + }, + "summary": "Issue an OAuth token", + "tags": [ + "OAuth" + ] + } + }, + "/v1/options/contracts": { + "get": { + "description": "This endpoint allows you to retrieve a list of option contracts based on various filtering criteria.\nBy default only active contracts that expire before the upcoming weekend are returned.\n", + "operationId": "get-options-contracts", + "parameters": [ + { + "description": "Filter contracts by one or more underlying symbols.", + "example": "AAPL,SPY", + "in": "query", + "name": "underlying_symbols", + "schema": { + "type": "string" + } + }, + { + "description": "Include deliverables array in the response.", + "example": true, + "in": "query", + "name": "show_deliverables", + "schema": { + "type": "boolean" + } + }, + { + "description": "Filter contracts by status (active/inactive). By default only active contracts are returned.", + "example": "active", + "in": "query", + "name": "status", + "schema": { + "enum": [ + "active", + "inactive" + ], + "type": "string" + } + }, + { + "description": "Filter contracts by the exact expiration date (format: YYYY-MM-DD). If no expiration date filters (`expiration_date`, `expiration_date_gte`, or `expiration_date_lte`) are provided, the endpoint defaults to returning contracts with expiration dates on or before the upcoming weekend.", + "example": "2025-06-20", + "in": "query", + "name": "expiration_date", + "schema": { + "format": "date", + "type": "string" + } + }, + { + "description": "Filter contracts with expiration date greater than or equal to the specified date.", + "example": "2025-06-20", + "in": "query", + "name": "expiration_date_gte", + "schema": { + "format": "date", + "type": "string" + } + }, + { + "description": "Filter contracts with expiration date less than or equal to the specified date. By default this is set to the next weekend.", + "example": "2025-06-20", + "in": "query", + "name": "expiration_date_lte", + "schema": { + "format": "date", + "type": "string" + } + }, + { + "description": "Filter contracts by the root symbol.", + "example": "AAPL", + "in": "query", + "name": "root_symbol", + "schema": { + "type": "string" + } + }, + { + "description": "Filter contracts by the type (call/put).", + "example": "call", + "in": "query", + "name": "type", + "schema": { + "enum": [ + "call", + "put" + ], + "type": "string" + } + }, + { + "description": "Filter contracts by the style (american/european).", + "example": "american", + "in": "query", + "name": "style", + "schema": { + "enum": [ + "american", + "european" + ], + "type": "string" + } + }, + { + "description": "Filter contracts with strike price greater than or equal to the specified value.", + "example": 50, + "in": "query", + "name": "strike_price_gte", + "schema": { + "type": "number" + } + }, + { + "description": "Filter contracts with strike price less than or equal to the specified value.", + "example": 100, + "in": "query", + "name": "strike_price_lte", + "schema": { + "type": "number" + } + }, + { + "$ref": "#/components/parameters/PageToken" + }, + { + "description": "The number of contracts to limit per page (default=100, max=10000).", + "example": 100, + "in": "query", + "name": "limit", + "schema": { + "type": "integer" + } + }, + { + "description": "The ppind(Penny Program Indicator) field indicates whether an option contract is eligible for penny price increments,\nwith `true` meaning it is part of the Penny Program and `false` meaning it is not.", + "example": true, + "in": "query", + "name": "ppind", + "schema": { + "type": "boolean" + } + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "properties": { + "next_page_token": { + "$ref": "#/components/schemas/NextPageToken" + }, + "option_contracts": { + "items": { + "$ref": "#/components/schemas/OptionContract" + }, + "type": "array" + } + }, + "required": [ + "option_contracts" + ], + "type": "object" + } + } + }, + "description": "Successful Response." + } + }, + "summary": "Get Option Contracts (BETA)", + "tags": [ + "Assets" + ] + } + }, + "/v1/options/contracts/{symbol_or_id}": { + "get": { + "description": "Get an option contract by symbol or contract ID. The symbol or id should be passed in as a path parameter.", + "operationId": "get-option-contract-symbol_or_id", + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/OptionContract" + } + } + }, + "description": "An option contract" + }, + "404": { + "content": { + "application/json": { + "examples": { + "Contract Not Found": { + "value": { + "code": 40410000, + "message": "option contract TSLA231110C00021000 not found" + } + } + }, + "schema": { + "$ref": "#/components/schemas/Error" + } + } + }, + "description": "Not Found" + } + }, + "summary": "Get an option contract by ID or Symbol (BETA)", + "tags": [ + "Assets" + ] + }, + "parameters": [ + { + "description": "The contract symbol or ID.", + "in": "path", + "name": "symbol_or_id", + "required": true, + "schema": { + "type": "string" + } + } + ] + }, + "/v1/rebalancing/portfolios": { + "get": { + "description": "Lists portfolios.\n\nWhen more than one query parameter is passed, only portfolios meeting all provided values will be returned (logical AND between parameter values).", + "operationId": "get-v1-rebalancing-portfolios", + "parameters": [ + { + "description": "Name of portfolio", + "in": "query", + "name": "name", + "schema": { + "type": "string" + } + }, + { + "description": "Description of portfolio", + "in": "query", + "name": "description", + "schema": { + "type": "string" + } + }, + { + "description": "Symbol included in portfolio", + "in": "query", + "name": "symbol", + "schema": { + "type": "string" + } + }, + { + "description": "Portfolio ID", + "in": "query", + "name": "portfolio_id", + "schema": { + "type": "string" + } + }, + { + "description": "Portfolio Status", + "in": "query", + "name": "status", + "schema": { + "enum": [ + "active", + "inactive", + "needs_adjustment" + ], + "type": "string" + } + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "items": { + "$ref": "#/components/schemas/Portfolio" + }, + "type": "array" + } + } + }, + "description": "Array of Portfolios" + } + }, + "summary": "List Portfolios", + "tags": [ + "Rebalancing" + ] + }, + "post": { + "description": "Creates a portfolio allocation containing securities and/or cash. Having no rebalancing conditions is allowed but the rebalance event would need to be triggered manually. Portfolios created with API may have multiple rebalance_conditions, but only one of type calendar.", + "operationId": "post-v1-rebalancing-portfolios", + "requestBody": { + "content": { + "application/json": { + "schema": { + "properties": { + "cooldown_days": { + "description": "Count of calendar days following a rebalance before a subscription is eligible to trigger another rebalance", + "type": "integer" + }, + "description": { + "description": "Text to describe portfolio", + "type": "string" + }, + "name": { + "description": "Name of portfolio", + "type": "string" + }, + "rebalance_conditions": { + "description": "Rebalancing conditions for portfolio", + "items": { + "$ref": "#/components/schemas/RebalancingConditions" + }, + "type": "array" + }, + "weights": { + "description": "Weight configuration to portfolio. Sum of \"percent\" values in the weights array must be 100.00", + "items": { + "$ref": "#/components/schemas/PortfolioWeights" + }, + "type": "array" + } + }, + "type": "object" + } + } + }, + "description": "" + }, + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Portfolio" + } + } + }, + "description": "OK" + } + }, + "summary": "Create Portfolio", + "tags": [ + "Rebalancing" + ] + } + }, + "/v1/rebalancing/portfolios/{portfolio_id}": { + "delete": { + "description": "Sets a portfolio to \"inactive\", so it can be filtered out of the list request. Only permitted if there are no active subscriptions to this portfolio and this portfolio is not a listed in the weights of any active portfolios.\n\nInactive portfolios cannot be linked in new subscriptions or added as weights to new portfolios.", + "operationId": "delete-v1-rebalancing-portfolios-portfolio_id", + "responses": { + "204": { + "description": "No Content" + }, + "400": { + "description": "Bad Request" + }, + "422": { + "description": "Unprocessable Entity " + } + }, + "summary": "Inactivate Portfolio By ID", + "tags": [ + "Rebalancing" + ] + }, + "get": { + "description": "Get a portfolio by its ID.", + "operationId": "get-v1-rebalancing-portfolios-portfolio_id", + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Portfolio" + } + } + }, + "description": "OK" + } + }, + "summary": "Get Portfolio by ID", + "tags": [ + "Rebalancing" + ] + }, + "parameters": [ + { + "description": "The Portfolio ID", + "in": "path", + "name": "portfolio_id", + "required": true, + "schema": { + "type": "string" + } + } + ], + "patch": { + "description": "Updates a portfolio. If weights or conditions are changed, all subscribed accounts will be evaluated for rebalancing at the next opportunity (normal market hours). If a cooldown is active on the portfolio, the rebalancing will occur after the cooldown expired.", + "operationId": "patch-v1-rebalancing-portfolios-portfolio_id", + "requestBody": { + "content": { + "application/json": { + "schema": { + "properties": { + "cooldown_days": { + "description": "Used to update portfolio cooldown days.", + "type": "integer" + }, + "description": { + "description": "Used to update portfolio description.", + "type": "string" + }, + "name": { + "description": "Used to update portfolio name.", + "type": "string" + }, + "rebalancing_conditions": { + "description": "Used to update portfolio conditions.", + "items": { + "$ref": "#/components/schemas/RebalancingConditions" + }, + "type": "array" + }, + "weights": { + "description": "Used to update portfolio weights.", + "items": { + "$ref": "#/components/schemas/PortfolioWeights" + }, + "type": "array" + } + }, + "required": [ + "cooldown_days" + ], + "type": "object" + } + } + } + }, + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Portfolio" + } + } + }, + "description": "The updated portfolio" + } + }, + "summary": "Update Portfolio by ID", + "tags": [ + "Rebalancing" + ] + } + }, + "/v1/rebalancing/runs": { + "get": { + "description": "Lists runs.", + "operationId": "get-v1-rebalancing-runs", + "parameters": [ + { + "description": "Any runs for this account_id will be returned", + "in": "query", + "name": "account_id", + "schema": { + "type": "string" + } + }, + { + "description": "Status of portfolio run to filter by", + "in": "query", + "name": "status", + "schema": { + "type": "string" + } + }, + { + "description": "Type of portfolio run (full_rebalance, invest_cash)", + "in": "query", + "name": "type", + "schema": { + "type": "string" + } + }, + { + "description": "Pagination token", + "in": "query", + "name": "page_token", + "schema": { + "type": "string" + } + }, + { + "description": "Max number of runs to return per page", + "in": "query", + "name": "limit", + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "items": { + "$ref": "#/components/schemas/PortfolioRun" + }, + "type": "array" + } + } + }, + "description": "The runs which match the query" + } + }, + "summary": "List All Runs.", + "tags": [ + "Rebalancing" + ] + }, + "post": { + "description": "Manually creates a run.\n\nThe determination of a run's orders and the execution of a run take place during normal market hours\n\nRuns can be initiated either by the system (when the system evaluates the rebalance conditions specified at the portfolio level) or by API call (manual run creation using POST /v1/rebalancing/runs). Runs can be initiated manually outside of the normal market hours but will remain in the QUEUED status until normal market hours\n\nOnly 1 run in a non-terminal status is allowed at any time.\n\nManually executing a run is currently only allowed for accounts who do not have an active subscription.", + "operationId": "post-v1-rebalancing-runs", + "requestBody": { + "content": { + "application/json": { + "schema": { + "properties": { + "account_id": { + "description": "The account to create the run for.", + "type": "string" + }, + "type": { + "description": "`full_rebalance` or `invest_cash`", + "type": "string" + }, + "weights": { + "$ref": "#/components/schemas/PortfolioWeights" + } + }, + "type": "object", + "x-examples": { + "Example 1": { + "account_id": "bf2b0f93-f296-4276-a9cf-288586cf4fb7", + "type": "full_rebalance", + "weights": [ + { + "percent": "35", + "symbol": "AAPL", + "type": "asset" + }, + { + "percent": "20", + "symbol": "TSLA", + "type": "asset" + }, + { + "percent": "45", + "symbol": "SPY", + "type": "asset" + } + ] + } + } + } + } + } + }, + "responses": { + "200": { + "description": "OK" + } + }, + "summary": "Create Run (Manual rebalancing event)", + "tags": [ + "Rebalancing" + ] + } + }, + "/v1/rebalancing/runs/{run_id}": { + "delete": { + "description": "Cancels a run. Only runs within certain statuses (QUEUED, CANCELED, SELLS_IN_PROGRESS, BUYS_IN_PROGRESS) are cancelable. If this endpoint is called after orders have been submitted, we'll attempt to cancel the orders.", + "operationId": "delete-v1-rebalancing-runs-run_id", + "responses": { + "204": { + "description": "No Content" + } + }, + "summary": "Cancel Run by ID", + "tags": [ + "Rebalancing" + ] + }, + "get": { + "description": "Get a run by its ID.", + "operationId": "get-v1-rebalancing-runs-run_id", + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PortfolioRun" + } + } + }, + "description": "The run object" + } + }, + "summary": "Get Run by ID", + "tags": [ + "Rebalancing" + ] + }, + "parameters": [ + { + "description": "the id of the portfolio run", + "in": "path", + "name": "run_id", + "required": true, + "schema": { + "type": "string" + } + } + ] + }, + "/v1/rebalancing/subscriptions": { + "get": { + "description": "Lists subscriptions", + "operationId": "get-v1-rebalancing-subscriptions", + "parameters": [ + { + "description": "Any subscriptions for this account_id will be returned", + "in": "query", + "name": "account_id", + "schema": { + "type": "string" + } + }, + { + "description": "Portfolio ID", + "in": "query", + "name": "portfolio_id", + "schema": { + "type": "string" + } + }, + { + "description": "Pagination token", + "in": "query", + "name": "page_token", + "schema": { + "type": "string" + } + }, + { + "description": "Max number of subscriptions to return per page", + "in": "query", + "name": "limit", + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "properties": { + "next_page_token": { + "$ref": "#/components/schemas/NextPageToken" + }, + "subscriptions": { + "items": { + "$ref": "#/components/schemas/PortfolioSubscription" + }, + "type": "array" + } + }, + "required": [ + "subscriptions", + "next_page_token" + ], + "type": "object" + } + } + }, + "description": "OK" + } + }, + "summary": "List All Subscriptions", + "tags": [ + "Rebalancing" + ] + }, + "post": { + "description": "Creates a subscription between an account and a portfolio.", + "operationId": "post-v1-rebalancing-subscriptions", + "requestBody": { + "content": { + "application/json": { + "schema": { + "properties": { + "account_id": { + "description": "Account ID. Account must be active and the account must not have any active subscriptions already (only 1 is permitted at a time)", + "type": "string" + }, + "portfolio_id": { + "description": "Portfolio ID. Portfolio must be active", + "type": "string" + } + }, + "type": "object" + } + } + }, + "description": "" + }, + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PortfolioSubscription" + } + } + }, + "description": "OK" + } + }, + "summary": "Create Subscription", + "tags": [ + "Rebalancing" + ] + } + }, + "/v1/rebalancing/subscriptions/{subscription_id}": { + "delete": { + "description": "Deletes the subscription which stops the rebalancing of an account.", + "operationId": "delete-v1-rebalancing-subscriptions-subscription_id", + "responses": { + "204": { + "description": "No Content" + } + }, + "summary": "Unsubscribe Account (Delete Subscription)", + "tags": [ + "Rebalancing" + ] + }, + "get": { + "description": "Get a subscription by its ID.", + "operationId": "get-v1-rebalancing-subscriptions-subscription_id", + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PortfolioSubscription" + } + } + }, + "description": "The portfolio subscription" + } + }, + "summary": "Get Subscription by ID", + "tags": [ + "Rebalancing" + ] + }, + "parameters": [ + { + "in": "path", + "name": "subscription_id", + "required": true, + "schema": { + "type": "string" + } + } + ] + }, + "/v1/reporting/eod/aggregate_positions": { + "get": { + "description": "This API endpoint provides reporting data to partners for aggregate common stock and crypto positions across their account base. Partners can view historical snapshots of their holding across their entire account base. Please note that this API utilizes an 8:00 pm (EST) cutoff which aligns with the end of the Securities extended hours trading session as well as Alpaca's 24 hour Crypto trading window. Additionally, the endpoint supports indexing to help the partner efficiently filter by key information including date and symbol while being able to include or remove firm accounts.", + "operationId": "get-v1-reporting-eod-aggregate_positions", + "parameters": [ + { + "description": "\"YYYY-MM-DD\" format", + "in": "query", + "name": "date", + "required": true, + "schema": { + "format": "date", + "type": "string" + } + }, + { + "description": "Comma-separated symbols. If populated, then only the specified symbols will be returned. If null, then all symbols will be included in the response.", + "in": "query", + "name": "symbols", + "schema": { + "type": "string" + } + }, + { + "description": "Defaults to True which includes firm accounts. Passing False will exclude all firm accounts.", + "in": "query", + "name": "firm_accounts", + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "items": { + "$ref": "#/components/schemas/AggregatePositionResponse" + }, + "type": "array" + } + } + }, + "description": "Array of objects, each object pertains to the date specified in the request and a unique asset. See parameters below. Notes: Returns an empty array for non-trading days, assets with no positions are omitted." + } + }, + "summary": "Retrieve Aggregate Positions", + "tags": [ + "Reporting" + ] + } + }, + "/v1/reporting/eod/cash_interest": { + "get": { + "description": "This API retrieves a list of cash interest details for the given date(s) for a single account or all accounts. End-of-day (EOD) details are typically accessible after 8:00pm Eastern Time (ET) and reflect that day's ending state across cash balances, accrued interest, accrued fees, as well as additional ancillary details.", + "operationId": "get-v1-get-eod-cash-interest-report", + "parameters": [ + { + "description": "Account globally unique identifier. If not provided, the report will be generated for all accounts.", + "in": "query", + "name": "account_id", + "required": false, + "schema": { + "format": "uuid", + "type": "string" + } + }, + { + "description": "A date in the format YYYY-MM-DD. If not provided, the report will be generated for the most recent date this report is available.", + "in": "query", + "name": "date", + "required": false, + "schema": { + "format": "date", + "type": "string" + } + }, + { + "description": "A date in the format YYYY-MM-DD, valid only if account_id is provided and date is not provided. If not provided, this will use `before` value. If neither is provided the most recent available date is used for both `before` and `after`.", + "in": "query", + "name": "after", + "required": false, + "schema": { + "format": "date", + "type": "string" + } + }, + { + "description": "A date in the format YYYY-MM-DD, valid only if account_id is provided and date is not provided. If not provided, this will use the most recent available date.", + "in": "query", + "name": "before", + "required": false, + "schema": { + "format": "date", + "type": "string" + } + }, + { + "description": "The direction to use for sorting responses, either `asc` or `desc`. Only valid for account_id queries, for which only sorting by date is supported. Defaults to `desc`.", + "in": "query", + "name": "direction", + "required": false, + "schema": { + "type": "string" + } + }, + { + "description": "The page size, used for paginating responses. Defaults to 1000, with a maximum of 10,000.", + "in": "query", + "name": "page_size", + "required": false, + "schema": { + "default": 1000, + "maximum": 10000, + "minimum": 1, + "type": "integer" + } + }, + { + "description": "A token used to retrieve the next page for paginated queries. If provided the response will begin at the next page of the results for the response from which the `next_page_token` is used.", + "in": "query", + "name": "page_token", + "required": false, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/EoDCashInterestReportResponse" + } + } + }, + "description": "Successful response, returns a list of daily interest accruals and an optional next page token." + }, + "400": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + }, + "description": "A client error occurred. Please check the provided request parameters and try again." + }, + "401": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + }, + "description": "Unauthorized. Please check your API key and try again." + }, + "429": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + }, + "description": "Too many requests. Please wait a moment and try again." + }, + "500": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + }, + "description": "A server error occurred. Please contact Alpaca." + } + }, + "summary": "Retrieve EOD Cash Interest Details", + "tags": [ + "Reporting", + "Cash Interest" + ] + } + }, + "/v1/reporting/eod/positions": { + "get": { + "description": "This API retrieves a comprehensive list of end-of-day positions for all accounts. End-of-day (EOD) positions are typically accessible after 4:00 am Eastern Time (ET) on the following day, providing a comprehensive view of the day's closing positions across all accounts.\nThis API currently only supports retrieving EOD positions for the last trading date.", + "operationId": "get-v1-reporting-eod-positions", + "parameters": [ + { + "description": "Filter the results by account_id (optional).", + "in": "query", + "name": "account_id", + "schema": { + "format": "uuid", + "type": "string" + } + }, + { + "description": "Filter the results by asset ID or symbol (optional).", + "in": "query", + "name": "asset", + "schema": { + "type": "string" + } + }, + { + "description": "The positions date in 'YYYY-MM-DD' format. It's recommended to provide this parameter when the 'page' parameter is specified, to ensure precise data retrieval based on the desired date.\nOnly previous trading date is supported.", + "in": "query", + "name": "asof", + "schema": { + "format": "date", + "type": "string" + } + }, + { + "$ref": "#/components/parameters/PageToken" + }, + { + "description": "The number of accounts to display per page (default=1000, max=10000).", + "in": "query", + "name": "limit", + "schema": { + "type": "integer" + } + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "properties": { + "asof": { + "description": "The date of the snapshot in the 'YYYY-MM-DD' format.", + "format": "date", + "type": "string" + }, + "next_page_token": { + "$ref": "#/components/schemas/NextPageToken" + }, + "positions": { + "additionalProperties": { + "items": { + "$ref": "#/components/schemas/Position" + }, + "type": "array" + }, + "description": "A detailed map of account IDs to their respective positions.", + "type": "object" + } + }, + "type": "object" + } + } + }, + "description": "Successful response containing the end-of-day positions for the specified accounts." + }, + "422": { + "description": "This can be returned if the asof param does not match the latest snap available at Alpaca." + } + }, + "summary": "Retrieve EOD Positions", + "tags": [ + "Reporting" + ] + } + }, + "/v1/trading/accounts/{account_id}/account": { + "get": { + "description": "As a broker you can view more trading details about your users.\n\nThe response is a Trading Account model.", + "operationId": "getTradingAccount", + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TradeAccount" + } + } + }, + "description": "OK" + } + }, + "summary": "Retrieve Trading Details for an Account", + "tags": [ + "Accounts" + ] + }, + "parameters": [ + { + "$ref": "#/components/parameters/AccountID" + } + ] + }, + "/v1/trading/accounts/{account_id}/account/configurations": { + "parameters": [ + { + "in": "path", + "name": "account_id", + "required": true, + "schema": { + "type": "string" + } + } + ], + "patch": { + "description": "You can also set the margin settings for your users' account by passing a PATCH request. By default any account with funds under $2,000 is set a margin multiplier of 1.0, and accounts with over $2,000 are set to 2.0.", + "operationId": "patch-PATCH-v1-trading-accounts-account_id-account-configurations", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/AccountConfigurations" + } + } + } + }, + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/AccountConfigurations" + } + } + }, + "description": "Response will contain the account configuration settings for the account." + } + }, + "summary": "Update Trading Configurations for an Account", + "tags": [ + "Trading" + ] + } + }, + "/v1/trading/accounts/{account_id}/account/pdt/one-time-removal": { + "parameters": [ + { + "$ref": "#/components/parameters/AccountID" + } + ], + "post": { + "deprecated": true, + "description": "**Deprecation notice**\n\nFINRA's revised Rule 4210 replaces the legacy Pattern Day Trading (PDT) framework with an intraday margin regime, so this endpoint no longer reflects current regulatory behavior. See https://docs.alpaca.markets/us/docs/understanding-finras-new-intraday-margin-rule-and-the-end-of-pdt\n\nThis endpoint will be sunset July 6, 2026. Please remove calls to PDT-specific endpoints before this date.\n\n---\n\nRequests the exercise of pattern day trader one time removal for given account", + "operationId": "pdtOneTimeRemoval", + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PDTRemoval" + } + } + }, + "description": "Response to PDT one time removal request" + }, + "403": { + "content": { + "application/json": { + "examples": { + "Account is not PDT": { + "value": { + "code": 40310000, + "message": "account is not PDT" + } + } + }, + "schema": { + "$ref": "#/components/schemas/Error" + } + } + }, + "description": "Forbidden\n\nAccount is not flagged as PDT." + } + }, + "summary": "Exercise PDT one time removal", + "tags": [ + "Accounts" + ], + "x-deprecation": { + "reason": "See https://docs.alpaca.markets/us/docs/understanding-finras-new-intraday-margin-rule-and-the-end-of-pdt", + "since": "2026-04-27T00:00:00Z", + "sunset": "2026-07-06T00:00:00Z" + } + } + }, + "/v1/trading/accounts/{account_id}/account/pdt/status": { + "get": { + "deprecated": true, + "description": "**Deprecation notice**\n\nFINRA's revised Rule 4210 replaces the legacy Pattern Day Trading (PDT) framework with an intraday margin regime, so this endpoint no longer reflects current regulatory behavior. See https://docs.alpaca.markets/us/docs/understanding-finras-new-intraday-margin-rule-and-the-end-of-pdt\n\nThis endpoint will be sunset July 6, 2026. Please remove calls to PDT-specific endpoints before this date.\n\n---\n\nRetrieves the account's pattern day trader status", + "operationId": "pdtGetStatus", + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PDTStatus" + } + } + }, + "description": "The requested Pattern Day Trader Status" + } + }, + "summary": "Get Pattern Day Trader Status for account", + "tags": [ + "Accounts" + ], + "x-deprecation": { + "reason": "See https://docs.alpaca.markets/us/docs/understanding-finras-new-intraday-margin-rule-and-the-end-of-pdt", + "since": "2026-04-27T00:00:00Z", + "sunset": "2026-07-06T00:00:00Z" + } + }, + "parameters": [ + { + "$ref": "#/components/parameters/AccountID" + } + ] + }, + "/v1/trading/accounts/{account_id}/account/portfolio/history": { + "get": { + "description": "Returns timeseries data about equity and profit/loss (P/L) of the account in requested timespan.", + "operationId": "get-v1-trading-accounts-account_id-account-portfolio-history", + "parameters": [ + { + "in": "path", + "name": "account_id", + "required": true, + "schema": { + "type": "string" + } + }, + { + "description": "The duration of the data in `number` + `unit` format, such as 1D, where `unit` can be D for day, W for week, M for month and A for year. Defaults to 1M.\n\nOnly two of `start`, `end` and `period` can be specified at the same time.\n\nFor intraday timeframes (\\<1D) only 30 days or less can be queried, for 1D resolutions there is no such limit, data is available since the\ncreation of the account.\n", + "in": "query", + "name": "period", + "schema": { + "type": "string" + } + }, + { + "description": "The resolution of time window. 1Min, 5Min, 15Min, 1H, or 1D. If omitted, 1Min for less than 7 days period,\n15Min for less than 30 days, or otherwise 1D.\n\nFor queries with longer than 30 days of `period`, the system only accepts 1D as `timeframe`.\n", + "in": "query", + "name": "timeframe", + "schema": { + "type": "string" + } + }, + { + "description": "For intraday resolutions (<1D) this specifies which timestamps to return data points for:\n\nAllowed values are:\n- **market_hours**\n\n Only timestamps for the core equity trading hours are returned (usually 9:30am to 4:00pm, trading days only)\n\n- **extended_hours**\n\n Returns timestamps for the whole session including extended hours (usually 4:00am to 8:00pm, trading days only)\n\n- **continuous**\n\n Returns price data points 24/7 (for off-session times too). To calculate the equity values we are using the following prices:\n\n Between 4:00am and 10:00pm on trading days the valuation will be calculated based on the last trade (extended hours and normal hours respectively).\n\n After 10:00pm, until the next session open the equities will be valued at their official closing price on the primary exchange.\n", + "in": "query", + "name": "intraday_reporting", + "schema": { + "default": "market_hours", + "enum": [ + "market_hours", + "extended_hours", + "continuous" + ], + "type": "string" + } + }, + { + "description": "The timestamp the data is returned starting from in RFC3339 format (including timezone specification). Defaults to `end` minus `period`\n\nIf provided, the `start` value is always normalized to the `America/New_York` timezone and adjusted to the nearest `timeframe` interval, e.g. seconds are always truncated and the time is rounded backwards to the nearest interval of `1Min`, `5Min`, `15Min`, or `1H`.\n\nIf `timeframe=1D` and `start` is not a valid trading date, find the next available trading date. For example, if `start` occurs on Saturday or Sunday after converting to the America/New_York timezone, `start` is adjusted to the first weekday that is not a market holiday (e.g. Monday).\n\nIf `timeframe` is less than `1D` and `intraday_reporting` is not `continuous`, `start` always reflects the beginning of a market session. If `start` is between midnight and the end (inclusive) of an active trading day, `start` is set to the beginning of the session on the specified day. Otherwise, if `start` occurs outside of the market session, the next available market date is used.\n\nFor example, when `intraday_reporting=market_hours` and `start=2023-10-19T23:59:59-04:00`, the provided `start` date occurs outside of the regular market session. The effective `start` timestamp is adjusted to the beginning of the next session: `2023-10-20T09:30:00-04:00`\n\n`start` may be be combined with one of `end` or `period`.\n\nProviding all of `start`, `end`, and `period` is invalid.\n", + "in": "query", + "name": "start", + "schema": { + "example": "2021-03-16T18:38:01Z", + "format": "date-time", + "type": "string" + } + }, + { + "description": "`pnl_reset` defines how we are calculating the baseline values for Profit And Loss (pnl) for queries with `timeframe` less than 1D (intraday queries).\n\nThe default behavior for intraday queries is that we reset the pnl value to the previous day's closing equity for each **trading** day.\n\nIn case of crypto (given its continuous nature), this might not be desired: specifying \"no_reset\" disables this behavior and all pnl values\nreturned will be relative to the closing equity of the previous trading day.\n\nFor 1D resolution all PnL values are calculated relative to the `base_value`, we are not resetting the base value.\n", + "in": "query", + "name": "pnl_reset", + "schema": { + "default": "per_day", + "enum": [ + "no_reset", + "per_day" + ], + "type": "string" + } + }, + { + "description": "The timestamp the data is returned up to in RFC3339 format (including timezone specification). Defaults to the current time.\n\nIf provided, the `end` value is always normalized to the `America/New_York` timezone and adjusted to the nearest `timeframe` interval, e.g. seconds are always truncated and the time is rounded backwards to the nearest interval of `1Min`, `5Min`, `15Min`, or `1H`.\n\nWhen `intraday_reporting` is either `market_hours` or `extended_hours`, the `end` value is adjusted to not occur after session close on the specified day. For example if the `intraday_reporting` is `extended_hours`, and the timestamp specified is `2023-10-19T21:33:00-04:00`, `end` is adjusted to `2023-10-19T20:00:00-04:00`.\n\n`end` may be combined with `start` or `period`.\n\nProviding all of `start`, `end`, and `period` is invalid.\n", + "in": "query", + "name": "end", + "schema": { + "example": "2021-03-16T18:38:01Z", + "format": "date-time", + "type": "string" + } + }, + { + "description": "**deprecated**: Users are strongly advised to **rely on the `intraday_reporting` query parameter** for better control\nof the reporting range.\n\nIf true, include extended hours in the result. This is effective only for timeframe less than 1D.\n", + "in": "query", + "name": "extended_hours", + "schema": { + "type": "string" + } + }, + { + "description": "The cashflow activities to include in the report. One of 'ALL', 'NONE', or a comma-separated list of activity types.", + "examples": { + "all": { + "value": "ALL" + }, + "cash_transfers": { + "summary": "Include cash deposits, withdraws, and transaction fees", + "value": "CSD,CSW,FEE" + }, + "none": { + "value": "NONE" + } + }, + "in": "query", + "name": "cashflow_types", + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PortfolioHistory" + } + } + }, + "description": "Successful response" + } + }, + "summary": "Get Account Portfolio History", + "tags": [ + "Portfolio History" + ] + } + }, + "/v1/trading/accounts/{account_id}/limits": { + "get": { + "description": "This endpoint is only available to accounts with the trading limits feature enabled, and not on JIT.\nThe daily trading limit is set at the correspondent level (or the account level) and is used as the limit for the total amount due to Alpaca on the date of settlement.\nThe limit in use returns the real time usage of this limit, based on the setup it uses the usage is calculated differently.\nIf the limit in use reaches the `daily_net_limit` or `available` is zero, further purchasing activity will be halted, however, the limit can be adjusted by reaching out to Alpaca with the proposed new limit and the reason for the change.", + "operationId": "get-v1-account-trading-limits", + "parameters": [ + { + "in": "path", + "name": "account_id", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/AccountTradingLimit" + } + } + }, + "description": "Returns the trading limit set for the account and the real-time usage of the limit" + }, + "404": { + "description": "Returned when the account is not configured for trading limits" + } + }, + "summary": "Retrieve real-time Trading Limits for an Account", + "tags": [ + "Trading" + ] + } + }, + "/v1/trading/accounts/{account_id}/orders": { + "delete": { + "description": "Attempts to cancel all open orders. A response will be provided for each order that is attempted to be cancelled.", + "operationId": "deleteAllOrdersForAccount", + "responses": { + "207": { + "content": { + "application/json": { + "examples": { + "example-1": { + "value": [ + { + "id": "872994c5-0620-40ad-8ae0-f224b9948df5", + "status": 200 + } + ] + } + }, + "schema": { + "items": { + "$ref": "#/components/schemas/OrderClosedResponse" + }, + "type": "array" + } + } + }, + "description": "HTTP 207 Multi-Status with body, which is an array of objects that include the order id, http status code and an order object for each cancellation request." + }, + "400": { + "$ref": "#/components/responses/BadRequest" + }, + "404": { + "$ref": "#/components/responses/NotFound" + } + }, + "summary": "Cancel all Open Orders For an Account", + "tags": [ + "Trading" + ] + }, + "get": { + "description": "Retrieves a list of orders for the account, filtered by the supplied query parameters.\n\nEndpoint defaults to open orders if no parameters are provided.", + "operationId": "getAllOrdersForAccount", + "parameters": [ + { + "description": "Order status to be queried. open, closed or all. Defaults to open.", + "in": "query", + "name": "status", + "schema": { + "enum": [ + "open", + "closed", + "all" + ], + "type": "string" + } + }, + { + "description": "The maximum number of orders in response. Defaults to 50 and max is 500.", + "in": "query", + "name": "limit", + "schema": { + "example": 500, + "type": "integer" + } + }, + { + "$ref": "#/components/parameters/AssetClass" + }, + { + "$ref": "#/components/parameters/After" + }, + { + "$ref": "#/components/parameters/Until" + }, + { + "$ref": "#/components/parameters/Direction" + }, + { + "description": "If true, the result will roll up multi-leg orders under the legs field of primary order.", + "in": "query", + "name": "nested", + "schema": { + "type": "boolean" + } + }, + { + "$ref": "#/components/parameters/Symbols" + }, + { + "in": "query", + "name": "qty_above", + "schema": { + "type": "string" + } + }, + { + "in": "query", + "name": "qty_below", + "schema": { + "type": "string" + } + }, + { + "in": "query", + "name": "subtag", + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "items": { + "$ref": "#/components/schemas/Order" + }, + "type": "array" + } + } + }, + "description": "Retrieves a list of orders for the account, filtered by the supplied query parameters." + }, + "400": { + "$ref": "#/components/responses/BadRequest" + }, + "404": { + "$ref": "#/components/responses/NotFound" + } + }, + "summary": "Retrieve a List of Orders", + "tags": [ + "Trading" + ] + }, + "parameters": [ + { + "$ref": "#/components/parameters/AccountID" + } + ], + "post": { + "description": "Creating an order for your end customer. Each trading request must pass in the account_id in the URL.\n\n- Note that when submitting crypto orders, `market`, `limit` and `stop_limit` orders are supported while the supported `time_in_force` values are `gtc`, and `ioc`.\n- For equities and crypto we accept fractional orders as well with either `notional` or `qty` provided.\n- Note that submitting an options order is only available for partners who have been enabled for Options BETA.\n- In case of Fixed Income, only `market` and `limit` order types with `day` `time_in_force` are supported, and order replacement is not supported.\nNote that submitting Fixed Income orders is only available for partners who have been enabled for Fixed Income.\n- For IPO indications of interest (`asset_class: \"ipo\"`), the `symbol` is the offering reference returned by [`GET /v1/ipos`](#operation/listIPOOfferings) (e.g. `FI111225`). IPO orders are notional-only (`notional` required, `qty` must be omitted), buy-side, `market` type, and `gtc` time_in_force. Indications of interest can be replaced or canceled while the offering is open via [`PATCH /v1/trading/accounts/{account_id}/orders/{order_id}`](#operation/replaceOrderForAccount) and [`DELETE /v1/trading/accounts/{account_id}/orders/{order_id}`](#operation/deleteOrderForAccount).", + "operationId": "createOrderForAccount", + "requestBody": { + "content": { + "application/json": { + "examples": { + "Crypto": { + "summary": "Buy a crypto coin", + "value": { + "limit_price": "2100", + "qty": "0.02", + "side": "buy", + "symbol": "ETH/USD", + "time_in_force": "gtc", + "type": "limit" + } + }, + "Equity": { + "summary": "Buy an equity stock", + "value": { + "limit_price": "150", + "qty": "2", + "side": "buy", + "symbol": "AAPL", + "time_in_force": "gtc", + "type": "limit" + } + }, + "FixedIncome": { + "summary": "Buy a US Treasury Bill", + "value": { + "limit_price": "99.15", + "qty": "5000", + "side": "buy", + "symbol": "US912797QN08", + "time_in_force": "day", + "type": "limit" + } + }, + "IPO": { + "summary": "Submit an IPO indication of interest", + "value": { + "notional": "500", + "side": "buy", + "symbol": "FI111225", + "time_in_force": "gtc", + "type": "market" + } + }, + "Options": { + "summary": "Buy an option contract (BETA)", + "value": { + "limit_price": "10", + "qty": "2", + "side": "buy", + "symbol": "AAPL250620C00100000", + "time_in_force": "day", + "type": "limit" + } + } + }, + "schema": { + "$ref": "#/components/schemas/CreateOrderRequest" + } + } + }, + "required": true + }, + "responses": { + "200": { + "content": { + "application/json": { + "examples": { + "Crypto": { + "$ref": "#/components/examples/CryptoOrderResponse" + }, + "Equity": { + "$ref": "#/components/examples/EquityOrderResponse" + }, + "IPO": { + "$ref": "#/components/examples/IPOOrderResponse" + }, + "MultilegOptions": { + "$ref": "#/components/examples/MultilegOptionsOrderResponse" + }, + "Options": { + "$ref": "#/components/examples/OptionOrderResponse" + } + }, + "schema": { + "$ref": "#/components/schemas/Order" + } + } + }, + "description": "OK" + }, + "400": { + "$ref": "#/components/responses/BadRequest" + }, + "403": { + "$ref": "#/components/responses/Forbidden" + }, + "404": { + "$ref": "#/components/responses/NotFound" + }, + "422": { + "description": "Some parameters are not valid" + } + }, + "summary": "Create an Order for an Account", + "tags": [ + "Trading" + ] + } + }, + "/v1/trading/accounts/{account_id}/orders/{order_id}": { + "delete": { + "description": "Attempts to cancel an open order. If the order is no longer cancelable (for example if the status is \"filled\"), the server will respond with status 422, and reject the request.\n\nUpon acceptance of the cancel request, it returns status 204.", + "operationId": "deleteOrderForAccount", + "responses": { + "204": { + "description": "Success (No Content)" + }, + "400": { + "$ref": "#/components/responses/BadRequest" + }, + "404": { + "$ref": "#/components/responses/NotFound" + } + }, + "summary": "Cancel an Open Order", + "tags": [ + "Trading" + ] + }, + "get": { + "description": "Retrieves a single order for the given order_id.", + "operationId": "getOrderForAccount", + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Order" + } + } + }, + "description": "The requested Order object" + }, + "400": { + "$ref": "#/components/responses/BadRequest" + }, + "404": { + "$ref": "#/components/responses/NotFound" + } + }, + "summary": "Retrieve an Order by its ID", + "tags": [ + "Trading" + ] + }, + "parameters": [ + { + "$ref": "#/components/parameters/AccountID" + }, + { + "$ref": "#/components/parameters/OrderID" + } + ], + "patch": { + "description": "Replaces a single order with updated parameters. Each parameter overrides the corresponding attribute of the existing order. The other attributes remain the same as the existing order.\n\nA success return code from a replaced order does NOT guarantee the existing open order has been replaced. If the existing open order is filled before the replacing (new) order reaches the execution venue, the replacing (new) order is rejected, and these events are sent in the trade_updates stream channel found [here](https://docs.alpaca.markets/reference/subscribetotradev2sse).\n\nWhile an order is being replaced, the account's buying power is reduced by the larger of the two orders that have been placed (the old order being replaced, and the newly placed order to replace it). If you are replacing a buy entry order with a higher limit price than the original order, the buying power is calculated based on the newly placed order. If you are replacing it with a lower limit price, the buying power is calculated based on the old order.\n\nNote: Order cannot be replaced when the status is `accepted`, `pending_new`, `pending_cancel` or `pending_replace`.\n\nNote: Notional orders for non-IPO asset classes cannot be replaced. Any attempt to modify a non-IPO notional order via this endpoint will be rejected; cancel it and submit a new one instead.\n\nNote: IPO indications of interest (`asset_class: \"ipo\"`) **are** notional and **can** be replaced via this endpoint by providing a new `notional` value. `qty` and `notional` are mutually exclusive on a single replace request.", + "operationId": "replaceOrderForAccount", + "requestBody": { + "content": { + "application/json": { + "examples": { + "Equity": { + "summary": "Increase qty and limit price on an equity order", + "value": { + "limit_price": "155", + "qty": "4", + "time_in_force": "gtc" + } + }, + "IPO": { + "summary": "Update notional on an IPO indication of interest", + "value": { + "notional": "750" + } + }, + "Options": { + "summary": "Adjust an option order's limit price", + "value": { + "limit_price": "11.25", + "time_in_force": "day" + } + } + }, + "schema": { + "$ref": "#/components/schemas/UpdateOrderRequest" + } + } + }, + "required": true + }, + "responses": { + "200": { + "content": { + "application/json": { + "examples": { + "Equity": { + "$ref": "#/components/examples/EquityOrderResponse" + }, + "IPO": { + "$ref": "#/components/examples/IPOOrderResponse" + }, + "Options": { + "$ref": "#/components/examples/OptionOrderResponse" + } + }, + "schema": { + "$ref": "#/components/schemas/Order" + } + } + }, + "description": "A new Order object with a new order_id" + }, + "400": { + "$ref": "#/components/responses/BadRequest" + }, + "403": { + "description": "Buying power or shares are not sufficient" + }, + "404": { + "$ref": "#/components/responses/NotFound" + } + }, + "summary": "Replace an Order", + "tags": [ + "Trading" + ] + } + }, + "/v1/trading/accounts/{account_id}/orders/estimation": { + "parameters": [ + { + "in": "path", + "name": "account_id", + "required": true, + "schema": { + "type": "string" + } + } + ], + "post": { + "description": "Order estimation endpoint will display, based on user's account balance, the estimated quantity and price they will receive for their notional order.\n\nFor LCT - customer's order will include the Alpaca swap_fee, while correspondent side swap_fee is configurable in the API call. Utilising this API does not result in a real order and after the calculation - the user's buying power reverts to the previous state.\n\nResponses and Errors are the same as with the Orders API\n\nPlease note that the estimation is based on the market condition at the time of submission and a live order will differ. The output should be considered indicative.\n\n**Note:** This does not support Crypto or non-market orders at this time.", + "operationId": "get-v1-trading-accounts-account_id-orders-estimation", + "requestBody": { + "content": { + "application/json": { + "schema": { + "properties": { + "notional": { + "type": "string" + }, + "side": { + "$ref": "#/components/schemas/OrderSide" + }, + "swap_fee_bps": { + "type": "integer" + }, + "symbol": { + "type": "string" + }, + "time_in_force": { + "$ref": "#/components/schemas/TimeInForce" + }, + "type": { + "$ref": "#/components/schemas/OrderType" + } + }, + "type": "object", + "x-examples": { + "Example 1": { + "notional": "40", + "side": "buy", + "swap_fee_bps": 45, + "symbol": "AAPL", + "time_in_force": "day", + "type": "market" + } + } + } + } + } + }, + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Order" + } + } + }, + "description": "OK" + } + }, + "summary": "Estimate an Order", + "tags": [ + "Trading" + ] + } + }, + "/v1/trading/accounts/{account_id}/orders:by_client_order_id": { + "get": { + "description": "Retrieves a single order specified by the client order ID.", + "operationId": "getOrderByClientOrderIdForAccount", + "parameters": [ + { + "description": "The client-assigned order ID.", + "in": "query", + "name": "client_order_id", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Order" + } + } + }, + "description": "Successfully retrieved the order matching the client_order_id." + }, + "400": { + "$ref": "#/components/responses/BadRequest" + }, + "404": { + "$ref": "#/components/responses/NotFound" + } + }, + "summary": "Retrieves a single order specified by the client order ID.", + "tags": [ + "Trading" + ] + }, + "parameters": [ + { + "$ref": "#/components/parameters/AccountID" + } + ] + }, + "/v1/trading/accounts/{account_id}/positions": { + "delete": { + "description": "Closes (liquidates) all of the account's open long and short positions. A response will be provided for each order that is attempted to be cancelled. If an order is no longer cancelable, the server will respond with status 500 and reject the request.", + "operationId": "closeAllPositionsForAccount", + "parameters": [ + { + "description": "If true is specified, cancel all open orders before liquidating all positions.", + "in": "query", + "name": "cancel_orders", + "schema": { + "type": "boolean" + } + } + ], + "responses": { + "207": { + "content": { + "application/json": { + "examples": { + "example-1": { + "value": [ + { + "body": { + "asset_class": "us_equity", + "asset_id": "a4778bc8-fad1-47b7-87fe-d5cde10d43f4", + "canceled_at": null, + "client_order_id": "17dbfab4-cb86-4e0a-8fa6-f0606b0a9a4e", + "created_at": "2022-05-13T16:25:29.336330998Z", + "expired_at": null, + "extended_hours": false, + "failed_at": null, + "filled_at": null, + "filled_avg_price": null, + "filled_qty": "0", + "hwm": null, + "id": "d1143025-89fc-4952-8936-db2409d899f3", + "legs": null, + "limit_price": null, + "notional": null, + "order_class": "", + "order_type": "market", + "qty": "4", + "replaced_at": null, + "replaced_by": null, + "replaces": null, + "side": "sell", + "source": null, + "status": "accepted", + "stop_price": null, + "submitted_at": "2022-05-13T16:25:29.335776073Z", + "symbol": "TSLA", + "time_in_force": "day", + "trail_percent": null, + "trail_price": null, + "type": "market", + "updated_at": "2022-05-13T16:25:29.336330998Z" + }, + "status": 200, + "symbol": "TSLA" + } + ] + } + }, + "schema": { + "items": { + "$ref": "#/components/schemas/PositionClosedResponse" + }, + "type": "array" + } + } + }, + "description": "HTTP 207 Multi-Status with body; an array of objects that include the order id and http status code for each status request." + }, + "500": { + "description": "Failed to liquidate some positions" + } + }, + "summary": "Close All Positions for an Account", + "tags": [ + "Trading" + ] + }, + "get": { + "description": "List open positions for an account", + "operationId": "getPositionsForAccount", + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "items": { + "$ref": "#/components/schemas/Position" + }, + "type": "array" + } + } + }, + "description": "Success" + } + }, + "summary": "List Open Positions for an Account", + "tags": [ + "Trading" + ] + }, + "parameters": [ + { + "$ref": "#/components/parameters/AccountID" + } + ] + }, + "/v1/trading/accounts/{account_id}/positions/{symbol_or_asset_id}": { + "delete": { + "description": "Closes (liquidates) the account's open position for the given symbol. Works for both long and short positions.", + "operationId": "closePositionForAccountBySymbol", + "parameters": [ + { + "description": "Optional the number of shares to liquidate. Can accept up to 9 decimal points. Cannot work with percentage", + "in": "query", + "name": "qty", + "schema": { + "type": "string" + } + }, + { + "description": "percentage of position to liquidate. Must be between 0 and 100. Would only sell fractional if position is originally fractional. Can accept up to 9 decimal points. Cannot work with qty", + "in": "query", + "name": "percentage", + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Order" + } + } + }, + "description": "OK" + } + }, + "summary": "Close a Position for an Account", + "tags": [ + "Trading" + ] + }, + "get": { + "description": "Retrieves the account's open position for the given symbol or asset_id.", + "operationId": "getPositionsForAccountBySymbol", + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Position" + } + } + }, + "description": "The requested Position object" + }, + "404": { + "description": "Account doesn't have a position for this symbol or asset_id " + } + }, + "summary": "Get an Open Position for account by Symbol or AssetId", + "tags": [ + "Trading" + ] + }, + "parameters": [ + { + "$ref": "#/components/parameters/AccountID" + }, + { + "description": "The symbol or asset_id ", + "in": "path", + "name": "symbol_or_asset_id", + "required": true, + "schema": { + "type": "string" + } + } + ] + }, + "/v1/trading/accounts/{account_id}/positions/{symbol_or_contract_id}/do-not-exercise": { + "post": { + "description": "This endpoint enables users to submit a do-not-exercise (DNE) instruction for a held option contract, preventing automatic exercise at expiry.\nBy default, Alpaca will automatically exercise in-the-money (ITM) contracts at expiry. This endpoint allows users to override that behavior.\nDNE requests will be processed immediately once received. DNE requests submitted between market close and midnight will be rejected to avoid any confusion about when the instruction will take effect.\nTo cancel a DNE request or to submit an exercise instruction, please contact our support team.", + "operationId": "optionDoNotExercise", + "parameters": [ + { + "$ref": "#/components/parameters/AccountID" + }, + { + "description": "Option contract symbol or ID.", + "in": "path", + "name": "symbol_or_contract_id", + "required": true, + "schema": { + "format": "uuid", + "type": "string" + } + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DoNotExerciseRequest" + } + } + } + }, + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DoNotExerciseResponse" + } + } + }, + "description": "Successful Response" + }, + "403": { + "content": { + "application/json": { + "examples": { + "No Available Position": { + "value": { + "code": 40310000, + "message": "no available position for the specified contract" + } + }, + "Short Position": { + "value": { + "code": 40310001, + "message": "cannot submit DNE for short position" + } + } + }, + "schema": { + "$ref": "#/components/schemas/Error" + } + } + }, + "description": "Forbidden" + }, + "422": { + "content": { + "application/json": { + "examples": { + "Invalid Symbol": { + "value": { + "code": 42210000, + "message": "invalid symbol_or_contract_id" + } + } + }, + "schema": { + "$ref": "#/components/schemas/Error" + } + } + }, + "description": "Invalid Symbol\n\nThe specified symbol_or_contract_id is invalid." + } + }, + "summary": "Do Not Exercise an Options Position (BETA)", + "tags": [ + "Trading" + ] + } + }, + "/v1/trading/accounts/{account_id}/positions/{symbol_or_contract_id}/exercise": { + "post": { + "description": "This endpoint enables users to exercise a held option contract, converting it into the underlying asset based on the specified terms.\nAll available held shares of this option contract will be exercised.\nBy default, Alpaca will automatically exercise in-the-money (ITM) contracts at expiry.\nExercise requests will be processed immediately once received. Exercise requests submitted between market close and midnight will be rejected to avoid any confusion about when the exercise will settle.\nTo cancel an exercise request or to submit a Do-not-exercise (DNE) instruction, you can use the do-not-exercise endpoint or contact our support team.", + "operationId": "optionExercise", + "parameters": [ + { + "$ref": "#/components/parameters/AccountID" + }, + { + "description": "Option contract symbol or ID.", + "in": "path", + "name": "symbol_or_contract_id", + "required": true, + "schema": { + "format": "uuid", + "type": "string" + } + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ExerciseRequest" + } + } + } + }, + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ExerciseResponse" + } + } + }, + "description": "Successful Response" + }, + "403": { + "content": { + "application/json": { + "examples": { + "No Available Position": { + "value": { + "code": 40310000, + "message": "no available position for the specified contract" + } + }, + "Short Position": { + "value": { + "code": 40310001, + "message": "cannot exercise short position" + } + } + }, + "schema": { + "$ref": "#/components/schemas/Error" + } + } + }, + "description": "Forbidden\n\nAvailable position quantity is not sufficient." + } + }, + "summary": "Exercise an Options Position (BETA)", + "tags": [ + "Trading" + ] + } + }, + "/v1/trading/accounts/{account_id}/watchlists": { + "get": { + "description": "Fetch a list of all watchlists currently in an account.", + "operationId": "getAllWatchlistsForAccount", + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "items": { + "$ref": "#/components/schemas/WatchlistWithoutAsset" + }, + "type": "array" + } + } + }, + "description": "OK" + } + }, + "summary": "Retrieve all Watchlists for an Account", + "tags": [ + "Watchlist" + ] + }, + "parameters": [ + { + "description": "Unique identifier of an account.", + "in": "path", + "name": "account_id", + "required": true, + "schema": { + "format": "uuid", + "type": "string" + } + } + ], + "post": { + "description": "Returns the watchlist object", + "operationId": "createWatchlistForAccount", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CreateWatchlistRequest" + } + } + } + }, + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Watchlist" + } + } + }, + "description": "Newly created watchlist" + } + }, + "summary": "Create a New Watchlist for an Account", + "tags": [ + "Watchlist" + ] + } + }, + "/v1/trading/accounts/{account_id}/watchlists/{watchlist_id}": { + "delete": { + "description": "Irrevocably delete a watchlist.", + "operationId": "deleteWatchlistFromAccountById", + "responses": { + "200": { + "description": "Watchlist deleted." + } + }, + "summary": "Remove a Watchlist", + "tags": [ + "Watchlist" + ] + }, + "get": { + "description": "Retrieve Watchlist by ID", + "operationId": "getWatchlistForAccountById", + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Watchlist" + } + } + }, + "description": "OK" + } + }, + "summary": "Manage watchlists", + "tags": [ + "Watchlist" + ] + }, + "parameters": [ + { + "description": "Unique identifier of an account", + "in": "path", + "name": "account_id", + "required": true, + "schema": { + "format": "uuid", + "type": "string" + } + }, + { + "description": "Unique identifier of a watchlist", + "in": "path", + "name": "watchlist_id", + "required": true, + "schema": { + "format": "uuid", + "type": "string" + } + } + ], + "post": { + "description": "Adds an asset to an existing watchlist.", + "operationId": "post-v1-trading-accounts-account_id-watchlists-watchlist_id", + "requestBody": { + "content": { + "application/json": { + "schema": { + "properties": { + "symbol": { + "description": "The symbol name to add to the watchlist", + "type": "string" + } + }, + "type": "object" + } + } + }, + "description": "" + }, + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Watchlist" + } + } + }, + "description": "OK" + }, + "404": { + "description": "The requested watchlist is not found, or one of the symbols is not found in the assets" + }, + "422": { + "description": "Some parameters are not valid" + } + }, + "summary": "Add an Asset to a Watchlist", + "tags": [ + "Watchlist" + ] + }, + "put": { + "description": "Replace entirely the set of securities contained in the watchlist while optionally renaming it. Destructive operation.", + "operationId": "replaceWatchlistForAccountById", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CreateWatchlistRequest" + } + } + } + }, + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Watchlist" + } + } + }, + "description": "Updated watchlist." + } + }, + "summary": "Update a Watchlist", + "tags": [ + "Watchlist" + ] + } + }, + "/v1/trading/accounts/{account_id}/watchlists/{watchlist_id}/{symbol}": { + "delete": { + "description": "Delete one entry for an asset by symbol name", + "operationId": "delete-DELETE-v1-trading-accounts-account_id-watchlists-watchlist_id-symbol", + "responses": { + "200": { + "description": "OK" + }, + "404": { + "description": "The requested watchlist is not found" + } + }, + "summary": "Remove a Symbol from a Watchlist", + "tags": [ + "Watchlist" + ] + }, + "parameters": [ + { + "description": "Account identifier.", + "in": "path", + "name": "account_id", + "required": true, + "schema": { + "type": "string" + } + }, + { + "description": "The Watchlist ID", + "in": "path", + "name": "watchlist_id", + "required": true, + "schema": { + "type": "string" + } + }, + { + "description": "The symbol ", + "in": "path", + "name": "symbol", + "required": true, + "schema": { + "type": "string" + } + } + ] + }, + "/v1/transfers/jit/{ledger_id}/balances": { + "get": { + "description": "Returns an array of objects that correspond to each ledger account.", + "operationId": "get-v1-transfers-jit-ledger_id-balances", + "parameters": [ + { + "in": "path", + "name": "ledger_id", + "required": true, + "schema": { + "type": "string" + } + }, + { + "description": "The start date (inclusive) of the ledgerbalances and activities.", + "in": "query", + "name": "start_date", + "schema": { + "format": "date", + "type": "string" + } + }, + { + "description": "The end date (inclusive) of the ledgerbalances and activities.", + "in": "query", + "name": "end_date", + "schema": { + "format": "date", + "type": "string" + } + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "properties": { + "activity_amount": { + "description": "The number of transactions related to the ledger during the specified date range ", + "format": "decimal", + "type": "string" + }, + "ending_balance": { + "description": "Ledger balance at the end of the date range", + "format": "decimal", + "type": "string" + }, + "id": { + "description": "The ledger ID", + "type": "string" + }, + "ledger_name": { + "description": "The ledger name", + "type": "string" + }, + "ledger_no": { + "description": "The ledger account number", + "type": "string" + }, + "starting_balance": { + "description": "Ledger balance at the beginning of the date range", + "format": "decimal", + "type": "string" + }, + "transactions": { + "items": { + "$ref": "#/components/schemas/Transaction" + }, + "type": "array" + } + }, + "type": "object" + } + } + }, + "description": "OK" + } + }, + "summary": "Retrieve JIT Ledger Balances", + "tags": [ + "Funding" + ] + } + }, + "/v1/transfers/jit/ledgers": { + "get": { + "description": "Returns an array of objects that correspond to each ledger account, each of whichcontain the following attributes.", + "operationId": "get-v1-transfers-jit-ledgers", + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "items": { + "$ref": "#/components/schemas/JITLedgerAccount" + }, + "type": "array" + } + } + }, + "description": "OK" + } + }, + "summary": "Retrieve JIT Ledgers", + "tags": [ + "Funding" + ] + } + }, + "/v1/transfers/jit/limits": { + "get": { + "description": "The JIT Securities daily trading limit is set at the correspondent level and is used as the limit for the total amount due to Alpaca on the date of settlement. The limit in use returns the real time usage of this limit and is calculated by taking the net of trade and non-trade activity inflows and outflows. If the limit in use reaches the daily net limit, further purchasing activity will be halted, however, the limit can be adjusted by reaching out to Alpaca with the proposed new limit and the reason for the change.", + "operationId": "get-v1-transfers-jit-limits", + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DailyTradingLimit" + } + } + }, + "description": "Returns the JIT Securities Daily Trading Limit Object based off of real time calculations." + } + }, + "summary": "Retrieve Daily Trading Limits", + "tags": [ + "Funding" + ] + } + }, + "/v1/transfers/jit/reports": { + "get": { + "description": "Retrieves a JIT report for the requested report type and system date. By default, the report is returned inline. For large reports, use response_type=download_url to receive a temporary signed download URL.\n", + "operationId": "get-v1-transfers-jit-reports", + "parameters": [ + { + "description": "The type of report you want to get.", + "in": "query", + "name": "report_type", + "required": true, + "schema": { + "enum": [ + "detail", + "net_summary", + "net_payment", + "net_payment_final", + "gross_summary", + "gross_payment", + "gross_payment_final" + ], + "type": "string" + } + }, + { + "description": "Date of file generation.", + "in": "query", + "name": "system_date", + "required": true, + "schema": { + "format": "date", + "type": "string" + } + }, + { + "description": "The asset class to retrieve for.", + "in": "query", + "name": "asset_class", + "schema": { + "enum": [ + "us_equity", + "crypto" + ], + "type": "string" + } + }, + { + "description": "Controls how the report is returned. Use inline to return the report content directly in the response. Use download_url to return a temporary signed URL that can be used to download the generated report. If omitted, the default value is inline.\n", + "in": "query", + "name": "response_type", + "required": false, + "schema": { + "default": "inline", + "enum": [ + "inline", + "download_url" + ], + "type": "string" + } + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "examples": { + "download_url": { + "description": "Returned when response_type is download_url.", + "summary": "Download URL response", + "value": { + "expires_at": "2026-04-14T23:55:00Z", + "filename": "jit_detail_2026-04-14.csv", + "url": "" + } + }, + "inline": { + "description": "Returned when response_type is inline or omitted.", + "summary": "Inline report response", + "value": { + "detail": "" + } + } + }, + "schema": { + "oneOf": [ + { + "$ref": "#/components/schemas/JITReport" + }, + { + "$ref": "#/components/schemas/JITReportDownloadURL" + } + ] + } + } + }, + "description": "OK" + } + }, + "summary": "Retrieve JIT Reports", + "tags": [ + "Funding" + ] + } + }, + "/v1/wallets/fees/estimate": { + "get": { + "description": "Returns the estimated on-chain network (gas) fee for a proposed crypto withdrawal at current chain conditions. Pass the `asset`, `from_address`, `to_address`, and `amount` you intend to send; the response reports the network fee that would be charged for the corresponding transaction.", + "operationId": "getCryptoTransferEstimate", + "parameters": [ + { + "description": "The asset for the proposed transaction", + "in": "query", + "name": "asset", + "schema": { + "type": "string" + } + }, + { + "description": "The originating address of the proposed transaction", + "in": "query", + "name": "from_address", + "schema": { + "type": "string" + } + }, + { + "description": "The destination address of the proposed transaction", + "in": "query", + "name": "to_address", + "schema": { + "type": "string" + } + }, + { + "description": "The amount, denoted in the specified asset, of the proposed transaction", + "in": "query", + "name": "amount", + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "properties": { + "fee": { + "type": "string" + } + }, + "type": "object" + } + } + }, + "description": "OK" + }, + "default": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + }, + "description": "error" + } + }, + "summary": "Returns the estimated gas fee for a proposed transaction.", + "tags": [ + "Crypto Funding" + ] + } + }, + "/v2beta1/accounts/{account_id}/events/activities/{event_id}": { + "get": { + "description": "Returns a single account activity (V2) event for the specified account by ULID `event_id`, delivered as a server-sent event message.\n\nThis is the single-event counterpart to the `subscribeToActivitiesSSE` streaming endpoint, intended for re-fetching an individual activity that was previously observed on the activities stream.", + "operationId": "getAccountActivityEvent", + "responses": { + "200": { + "content": { + "text/event-stream": { + "schema": { + "$ref": "#/components/schemas/ActivityEventV2" + } + } + }, + "description": "Returns a single activity v2 event, identified by the provided event_id ULID." + } + }, + "summary": "Get a single activity (V2) event by event_id ULID.", + "tags": [ + "Accounts" + ] + }, + "parameters": [ + { + "$ref": "#/components/parameters/AccountID" + }, + { + "description": "The ULID that uniquely identifies the activity event.", + "in": "path", + "name": "event_id", + "required": true, + "schema": { + "format": "ulid", + "type": "string" + } + } + ] + }, + "/v2beta1/events/activities": { + "get": { + "description": "The Events API sends the real-time events and provides historical queries with SSE (Server Sent Events).\n\nThis endpoint streams events on account activities.\n\nHistorical events are streamed immediately if queried, and updates are pushed as events occur.\n\nQuery parameter rules:\n- If `until` is specified, `since` is required.\n- If `until_id` is specified, `since_id` is required.\n- You cannot use `since` and `since_id` together.\nBehavior:\n- If `since` or `since_id` is not specified, this will not return any historic data.\n- If `until` or `until_id` is specified, stream will end at the specified point with status 200.\n\n---\n\nWarning: Currently, OAS-3 does not fully support responses from an SSE API.\n\nIn case the client code is generated from this OAS spec, please do not specify `since` and `until`, as the generated client may hang forever waiting for the response to end.\n\nIf you require the streaming capabilities, we recommend not using the generated clients for this specific endpoint until the OAS-3 standards define how to represent this behavior.\n\n---\n\n### Comment messages\nAccording to the SSE specification, any line that starts with a colon is a comment which does not contain data. It is typically a free text that does not follow any data schema. A few examples mentioned below for comment messages.\n\n##### Slow client\n\nThe server sends a comment when the client is not consuming messages fast enough. Example: `: you are reading too slowly, dropped 10000 messages`\n\n##### Internal server error\n\nAn error message is sent as a comment when the server closes the connection on an internal server error (only sent by the v2 and v2beta1 endpoints). Example: `: internal server error`\n\n---", + "operationId": "subscribeToActivitiesSSE", + "parameters": [ + { + "description": "Format: RFC3339 or YYYY-MM-DD", + "in": "query", + "name": "since", + "schema": { + "format": "date-time", + "type": "string" + } + }, + { + "description": "Format: RFC3339 or YYYY-MM-DD", + "in": "query", + "name": "until", + "schema": { + "format": "date-time", + "type": "string" + } + }, + { + "in": "query", + "name": "since_id", + "schema": { + "format": "ulid", + "type": "string" + } + }, + { + "in": "query", + "name": "until_id", + "schema": { + "format": "ulid", + "type": "string" + } + } + ], + "responses": { + "200": { + "content": { + "text/event-stream": { + "schema": { + "items": { + "$ref": "#/components/schemas/ActivityEventV2" + }, + "type": "array" + } + } + }, + "description": "Connected. Events will now start streaming as long as you keep the connection open." + } + }, + "summary": "Subscribe to Activity Events (SSE)", + "tags": [ + "Events" + ] + } + }, + "/v2/calendar/{market}": { + "get": { + "description": "This endpoint returns the market calendar.", + "operationId": "Calendar", + "parameters": [ + { + "$ref": "#/components/parameters/market" + }, + { + "$ref": "#/components/parameters/start" + }, + { + "$ref": "#/components/parameters/end" + }, + { + "$ref": "#/components/parameters/timezone" + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/public_calendar_resp" + } + } + }, + "description": "OK", + "headers": { + "X-RateLimit-Limit": { + "$ref": "#/components/headers/ratelimit_limit" + }, + "X-RateLimit-Remaining": { + "$ref": "#/components/headers/ratelimit_remaining" + }, + "X-RateLimit-Reset": { + "$ref": "#/components/headers/ratelimit_reset" + } + } + }, + "400": { + "$ref": "#/components/responses/400" + }, + "429": { + "$ref": "#/components/responses/429" + }, + "500": { + "$ref": "#/components/responses/500" + } + }, + "summary": "Get Market Calendar", + "tags": [ + "Calendar" + ] + } + }, + "/v2/clock": { + "get": { + "description": "This API serves information about multiple markets: the current time, if it's a market day, the current phase of the market, etc.\n", + "operationId": "Clock", + "parameters": [ + { + "$ref": "#/components/parameters/markets" + }, + { + "$ref": "#/components/parameters/time" + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/clock_resp" + } + } + }, + "description": "OK", + "headers": { + "X-RateLimit-Limit": { + "$ref": "#/components/headers/ratelimit_limit" + }, + "X-RateLimit-Remaining": { + "$ref": "#/components/headers/ratelimit_remaining" + }, + "X-RateLimit-Reset": { + "$ref": "#/components/headers/ratelimit_reset" + } + } + }, + "400": { + "$ref": "#/components/responses/400" + }, + "429": { + "$ref": "#/components/responses/429" + }, + "500": { + "$ref": "#/components/responses/500" + } + }, + "summary": "Get Market Clock", + "tags": [ + "Calendar" + ] + } + }, + "/v2/events/admin-actions": { + "get": { + "description": "The Events API provides event push as well as historical queries via SSE (server sent events).\n\nThis endpoint streams events related to administrative actions performed by our systems.\n\nHistorical events are streamed immediately if queried, and updates are pushed as events occur.\n\nQuery Params Rules:\n- `since` required if `until` specified\n- `since_id` required if `until_id` specified\n- `since` and `since_id` can't be used at the same time\nBehavior:\n- if `since` or `since_id` not specified this will not return any historic data\n- if `until` or `until_id` reached stream will end (status 200)\n\n---\n\nWarning: Currently OAS-3 doesn't have full support for representing SSE style responses from an API.\n\nIn case the client code is generated from this OAS spec, don't specify a `since` and `until` there is a good chance the generated clients will hang forever waiting for the response to end.\n\nIf you require the streaming capabilities we recommend not using the generated clients for this specific endpoint until the OAS-3 standards come to a consensus on how to represent this behavior in OAS-3.\n\n---\n\n### Comment messages\nAccording to the SSE specification, any line that starts with a colon is a comment which does not contain data. It is typically a free text that does not follow any data schema. A few examples mentioned below for comment messages.\n\n##### Slow client\n\nThe server sends a comment when the client is not consuming messages fast enough. Example: `: you are reading too slowly, dropped 10000 messages`\n\n##### Internal server error\n\nAn error message is sent as a comment when the server closes the connection on an internal server error (only sent by the v2 and v2beta1 endpoints). Example: `: internal server error`\n\n---\n\n**Event Types**\n\n- **LegacyNote:** Old free text based admin notes\n- **Liquidation:** Event for a position liquidation which initialized by an admin\n- **TransactionCancel:** Event for a manually cancelled transaction", + "operationId": "subscribeToAdminActionSSE", + "parameters": [ + { + "description": "Format: RFC3339 or YYYY-MM-DD", + "in": "query", + "name": "since", + "schema": { + "format": "date-time", + "type": "string" + } + }, + { + "description": "Format: RFC3339 or YYYY-MM-DD", + "in": "query", + "name": "until", + "schema": { + "format": "date-time", + "type": "string" + } + }, + { + "in": "query", + "name": "since_id", + "schema": { + "format": "ulid", + "type": "string" + } + }, + { + "in": "query", + "name": "until_id", + "schema": { + "format": "ulid", + "type": "string" + } + } + ], + "responses": { + "200": { + "content": { + "text/event-stream": { + "schema": { + "items": { + "oneOf": [ + { + "$ref": "#/components/schemas/AdminActionLegacyNote" + }, + { + "$ref": "#/components/schemas/AdminActionLiquidation" + }, + { + "$ref": "#/components/schemas/AdminActionTransactionCancel" + } + ] + }, + "type": "array" + } + } + }, + "description": "Connected. Events will now start streaming as long as you keep the connection open." + } + }, + "summary": "Subscribe to Admin Action Events (SSE)", + "tags": [ + "Events" + ] + } + }, + "/v2/events/funding/status": { + "get": { + "description": "The Events API provides event push as well as historical queries via SSE (server sent events).\n\nYou can listen to funding status updates as they get processed by our backoffice, for both end-user and firm accounts.\n\nHistorical events are streamed immediately if queried, and updates are pushed as events occur.\n\nQuery Params Rules:\n- `since` required if `until` specified\n- `since_id` required if `until_id` specified\n- `since` and `since_id` can't be used at the same time\n- `until` and `until_id` can't be used at the same time\nBehavior:\n- if `since` or `since_id` not specified this will not return any historic data\n- if `until` or `until_id` reached stream will end (status 200)\n\n---\n\nNote for people using the clients generated from this OAS spec. Currently OAS-3 doesn't have full support for representing SSE style responses from an API, so if you are using a generated client and don't specify a `since` and `until` there is a good chance the generated clients will hang waiting for the response to end.\n\nIf you require the streaming capabilities we recommend not using the generated clients for this specific usecase until the OAS-3 standards come to a consensus on how to represent this correctly in OAS-3.", + "operationId": "subscribeToFundingStatusSSE", + "parameters": [ + { + "description": "Format: YYYY-MM-DD", + "in": "query", + "name": "since", + "schema": { + "format": "date", + "type": "string" + } + }, + { + "description": "Format: YYYY-MM-DD", + "in": "query", + "name": "until", + "schema": { + "format": "date", + "type": "string" + } + }, + { + "in": "query", + "name": "since_id", + "schema": { + "format": "ulid", + "type": "string" + } + }, + { + "in": "query", + "name": "until_id", + "schema": { + "format": "ulid", + "type": "string" + } + } + ], + "responses": { + "200": { + "content": { + "text/event-stream": { + "schema": { + "items": { + "oneOf": [ + { + "$ref": "#/components/schemas/StatusFundingEvent" + } + ] + }, + "type": "array" + } + } + }, + "description": "Connected. Events will now start streaming as long as you keep the connection open." + } + }, + "summary": "Subscribe to Funding Status Events (SSE)", + "tags": [ + "Events" + ] + } + }, + "/v2/events/ipos": { + "get": { + "description": "The Events API provides event push as well as historical queries via SSE (server sent events).\n\nYou can listen to IPO offering lifecycle updates as they happen - including new offerings, offering updates, prospectus availability, the 60-minute pricing window, account-scoped allocation results, and offering cancellations.\n\nHistorical events are streamed immediately if queried, and updates are pushed as events occur.\n\n---\n\n**Event types**\n\nEach event has a `verb` field that determines the shape of `payload`. See the `IPOEvent` schema for the full payload structure for each verb.\n\n- `Offering` - initial publication of an offering (system-wide).\n- `OfferingUpdate` - update to a previously published offering (system-wide).\n- `Prospectus` - prospectus document is now available (system-wide).\n- `SixtyMinMail` - 60-minute pricing window has opened, no new orders accepted (system-wide).\n- `Allocation` - final allocation result for a specific account. **Account-scoped** - `account_id` and `correspondent` are populated.\n- `OfferingCancellation` - offering was cancelled. **No `payload` field is present.**\n\nUse the `offering_reference` field on each event to fetch the full offering metadata via [`GET /v1/ipos/{offering_reference}`](#operation/getIPOOffering).\n\n---\n\nQuery Params Rules:\n- `since` required if `until` specified\n- `since_id` required if `until_id` specified\n- `since` and `since_id` can't be used at the same time\n- `until` and `until_id` can't be used at the same time\n\nBehavior:\n- if `since` or `since_id` not specified this will not return any historic data\n- if `until` or `until_id` reached stream will end (status 200)\n\n---\n\nNote for people using the clients generated from this OAS spec. Currently OAS-3 doesn't have full support for representing SSE style responses from an API, so if you are using a generated client and don't specify a `since` and `until` there is a good chance the generated clients will hang waiting for the response to end.\n\nIf you require the streaming capabilities we recommend not using the generated clients for this specific usecase until the OAS-3 standards come to a consensus on how to represent this correctly in OAS-3.\n", + "operationId": "subscribeToIPOEventsSSE", + "parameters": [ + { + "description": "Format: RFC3339 or YYYY-MM-DD", + "in": "query", + "name": "since", + "schema": { + "format": "date-time", + "type": "string" + } + }, + { + "description": "Format: RFC3339 or YYYY-MM-DD", + "in": "query", + "name": "until", + "schema": { + "format": "date-time", + "type": "string" + } + }, + { + "description": "ULID `event_id` to resume the stream from. The first event returned will be the one immediately after `since_id`.", + "in": "query", + "name": "since_id", + "schema": { + "format": "ulid", + "type": "string" + } + }, + { + "description": "ULID `event_id` at which the stream will end (inclusive). Useful for backfills.", + "in": "query", + "name": "until_id", + "schema": { + "format": "ulid", + "type": "string" + } + } + ], + "responses": { + "200": { + "content": { + "text/event-stream": { + "examples": { + "Allocation": { + "$ref": "#/components/examples/IPOEventAllocation" + }, + "Offering": { + "$ref": "#/components/examples/IPOEventOffering" + }, + "OfferingCancellation": { + "$ref": "#/components/examples/IPOEventOfferingCancellation" + }, + "OfferingUpdate": { + "$ref": "#/components/examples/IPOEventOfferingUpdate" + }, + "Prospectus": { + "$ref": "#/components/examples/IPOEventProspectus" + }, + "SixtyMinMail": { + "$ref": "#/components/examples/IPOEventSixtyMinMail" + } + }, + "schema": { + "items": { + "$ref": "#/components/schemas/IPOEvent" + }, + "type": "array" + } + } + }, + "description": "Connected. Events will now start streaming as long as you keep the connection open." + } + }, + "summary": "Subscribe to IPO Events (SSE)", + "tags": [ + "Events" + ] + } + }, + "/v2/events/journals/status": { + "get": { + "description": "The Events API provides event push as well as historical queries via SSE (server sent events).\n\nYou can listen to journal status updates as they get processed by our backoffice.\n\nHistorical events are streamed immediately if queried, and updates are pushed as events occur.\n\nQuery Params Rules:\n- `since` required if `until` specified\n- `since_id` required if `until_id` specified\n- `since` and `since_id` can't be used at the same time\n- `until` and `until_id` can't be used at the same time\nBehavior:\n- if `since` or `since_id` not specified this will not return any historic data\n- if `until` or `until_id` reached stream will end (status 200)\n\n---\n\nThere is no compatibility between /v1/events/journals/status and /v2/events/journals/status, the ids (ulid) are always different, and the number of events might also different\n\nPlease note that the new `/v2` endpoint, is the same as, and was originally available under `/v2beta1`.\nWe encourage all customers to adjust their codebase from that interim beta endpoint to the `/v2` stable endpoint.\nIn the near future we will setup permanent redirect from `/v2beta1` to `/v2` before we completely remove the beta endpoint.\n\n---\n\nNote for people using the clients generated from this OAS spec. Currently OAS-3 doesn't have full support for representing SSE style responses from an API, so if you are using a generated client and don't specify a `since` and `until` there is a good chance the generated clients will hang waiting for the response to end.\n\nIf you require the streaming capabilities we recommend not using the generated clients for this specific usecase until the OAS-3 standards come to a consensus on how to represent this correctly in OAS-3.\n", + "operationId": "subscribeToJournalStatusV2SSE", + "parameters": [ + { + "description": "Format: YYYY-MM-DD", + "in": "query", + "name": "since", + "schema": { + "format": "date-time", + "type": "string" + } + }, + { + "description": "Format: YYYY-MM-DD", + "in": "query", + "name": "until", + "schema": { + "format": "date-time", + "type": "string" + } + }, + { + "in": "query", + "name": "since_id", + "schema": { + "format": "ulid", + "type": "string" + } + }, + { + "in": "query", + "name": "until_id", + "schema": { + "format": "ulid", + "type": "string" + } + }, + { + "in": "query", + "name": "id", + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "content": { + "text/event-stream": { + "schema": { + "items": { + "$ref": "#/components/schemas/JournalStatusEventV2" + }, + "type": "array" + } + } + }, + "description": "Connected. Events will now start streaming as long as you keep the connection open." + } + }, + "summary": "Subscribe to Journal Events (SSE)", + "tags": [ + "Events" + ] + } + }, + "/v2/events/system": { + "get": { + "description": "The Events API provides event push as well as historical queries via SSE (server sent events).\n\nYou can listen to system event updates as they happen in our backend.\n\nHistorical events are streamed immediately if queried, and updates are pushed as events occur.\n\nQuery Params Rules:\n- `since` required if `until` specified\n- `since_id` required if `until_id` specified\n- `since` and `since_id` can't be used at the same time\n- `until` and `until_id` can't be used at the same time\nBehavior:\n- if `since` or `since_id` not specified this will not return any historic data\n- if `until` or `until_id` reached stream will end (status 200)\n\n---\n\nNote for people using the clients generated from this OAS spec. Currently OAS-3 doesn't have full support for representing SSE style responses from an API, so if you are using a generated client and don't specify a `since` and `until` there is a good chance the generated clients will hang waiting for the response to end.\n\nIf you require the streaming capabilities we recommend not using the generated clients for this specific usecase until the OAS-3 standards come to a consensus on how to represent this correctly in OAS-3.\n", + "operationId": "subscribeToSystemEventV2SSE", + "parameters": [ + { + "description": "Format: YYYY-MM-DD", + "in": "query", + "name": "since", + "schema": { + "format": "date-time", + "type": "string" + } + }, + { + "description": "Format: YYYY-MM-DD", + "in": "query", + "name": "until", + "schema": { + "format": "date-time", + "type": "string" + } + }, + { + "in": "query", + "name": "since_id", + "schema": { + "format": "ulid", + "type": "string" + } + }, + { + "in": "query", + "name": "until_id", + "schema": { + "format": "ulid", + "type": "string" + } + } + ], + "responses": { + "200": { + "content": { + "text/event-stream": { + "schema": { + "items": { + "$ref": "#/components/schemas/SystemEventV2" + }, + "type": "array" + } + } + }, + "description": "Connected. Events will now start streaming as long as you keep the connection open." + } + }, + "summary": "Subscribe to System Events (SSE)", + "tags": [ + "Events" + ] + } + }, + "/v2/events/trades": { + "get": { + "description": "The Events API provides event push as well as historical queries via SSE (server sent events).\n\nYou can listen to events related to trade updates. Most market trades sent during market hours are filled instantly; you can listen to limit order updates through this endpoint.\n\nHistorical events are streamed immediately if queried, and updates are pushed as events occur.\n\nQuery Params Rules:\n- `since` required if `until` specified\n- `since_id` required if `until_id` specified\n- `since` and `since_id` can't be used at the same time\nBehavior:\n- if `since` or `since_id` not specified this will not return any historic data\n- if `until` or `until_id` reached stream will end (status 200)\n\n---\n\nNote for people using the clients generated from this OAS spec. Currently OAS-3 doesn't have full support for representing SSE style responses from an API, so if you are using a generated client and don't specify a `since` and `until` there is a good chance the generated clients will hang waiting for the response to end.\n\nIf you require the streaming capabilities we recommend not using the generated clients for this specific usecase until the OAS-3 standards come to a consensus on how to represent this correctly in OAS-3.\n\n---\n\n**Legacy trade events API**\n\n**Deprecation notice**\n\nAs part of the deprecation process,\nthe legacy trade events API is now only available for existing broker-partners at: `GET /v1/events/trades` only for compatibility reasons.\n\nAll new broker partners will not have the option for the legacy trade event endpoint.\n\nAll new broker partners will have to integrate with the new `/v2/events/trades` endpoint.\n\nAlso, all existing broker partners are now recommended to upgrade to the `/v2/events/trades` endpoint, which provides faster event delivery times.\n\nThe legacy trade events api works the same way as the new one with the exception of the event_id which is an integer except of an ULID. This results in the request's since_id and until_id are also being integers. This integer is monotonically increasing over time for events.\n\nPlease note that the new `/v2` endpoint, is the same as, and was originally available under `/v2beta1`.\nWe encourage all customers to adjust their codebase from that interim beta endpoint to the `/v2` stable endpoint.\nIn the near future we will setup permanent redirect from `/v2beta1` to `/v2` before we completely remove the beta endpoint.\n\n---\n\n### Comment messages\nAccording to the SSE specification, any line that starts with a colon is a comment which does not contain data. It is typically a free text that does not follow any data schema. A few examples mentioned below for comment messages.\n\n##### Slow client\n\nThe server sends a comment when the client is not consuming messages fast enough. Example: `: you are reading too slowly, dropped 10000 messages`\n\n##### Internal server error\n\nAn error message is sent as a comment when the server closes the connection on an internal server error (only sent by the v2 and v2beta1 endpoints). Example: `: internal server error`\n\n---\n\n**Common events**\n\nThese are the events that are the expected results of actions you may have taken by sending API requests.\n\nThe meaning of the timestamp field changes for each type; the meanings have been specified here for which types the timestamp field will be present.\n\n- `accepted` Sent when an order is received and accepted by Alpaca\n- `pending_new` Sent when the order has been received by Alpaca and routed to the exchanges, but has not yet been accepted for execution.\n- `new` Sent when an order has been routed to exchanges for execution.\n- `fill` Sent when your order has been completely filled.\n - timestamp: The time at which the order was filled.\n- `partial_fill` Sent when a number of shares less than the total remaining quantity on your order has been filled.\n - timestamp: The time at which the shares were filled.\n- `canceled` Sent when your requested cancellation of an order is processed.\n - timestamp: The time at which the order was canceled.\n- `expired` Sent when an order has reached the end of its lifespan, as determined by the order's time in force value.\n - timestamp: The time at which the order expired.\n- `done_for_day` Sent when the order is done executing for the day, and will not receive further updates until the next trading day.\n- `replaced` Sent when your requested replacement of an order is processed.\n - timestamp: The time at which the order was replaced.\n\n**Rarer events**\n\nThese are events that may rarely be sent due to unexpected circumstances on the exchanges. It is unlikely you will need to design your code around them, but you may still wish to account for the possibility that they will occur.\n\n- `rejected` Sent when your order has been rejected.\n - timestamp: The time at which the rejection occurred.\n- `held` For multi-leg orders, the secondary orders (stop loss, take profit) will enter this state while waiting to be triggered.\n- `stopped` Sent when your order has been stopped, and a trade is guaranteed for the order, usually at a stated price or better, but has not yet occurred.\n- `pending_cancel` Sent when the order is awaiting cancellation. Most cancellations will occur without the order entering this state.\n- `pending_replace` Sent when the order is awaiting replacement.\n- `calculated` Sent when the order has been completed for the day - it is either filled or done_for_day - but remaining settlement calculations are still pending.\n- `suspended` Sent when the order has been suspended and is not eligible for trading.\n- `order_replace_rejected` Sent when the order replace has been rejected.\n- `order_cancel_rejected` Sent when the order cancel has been rejected.\n- `trade_bust`: Sent when a previously reported execution has been canceled (\"busted\") by the upstream exchange.\n- `trade_correct`: Sent when a previously reported trade has been corrected. For example, the exchange may have updated the price, quantity, or another execution parameter after the trade was initially reported.\n- `restated`: Sent when the order is manually modified.", + "operationId": "subscribeToTradeV2SSE", + "parameters": [ + { + "description": "Format: YYYY-MM-DD", + "in": "query", + "name": "since", + "schema": { + "format": "date", + "type": "string" + } + }, + { + "description": "Format: YYYY-MM-DD", + "in": "query", + "name": "until", + "schema": { + "format": "date", + "type": "string" + } + }, + { + "in": "query", + "name": "since_id", + "schema": { + "format": "ulid", + "type": "string" + } + }, + { + "in": "query", + "name": "until_id", + "schema": { + "format": "ulid", + "type": "string" + } + } + ], + "responses": { + "200": { + "content": { + "text/event-stream": { + "examples": { + "MultilegOptions-fill": { + "$ref": "#/components/examples/TradeUpdateEventV2MultilegOptionsFill" + }, + "new": { + "$ref": "#/components/examples/TradeUpdateEventV2New" + } + }, + "schema": { + "items": { + "$ref": "#/components/schemas/TradeUpdateEventV2" + }, + "type": "array" + } + } + }, + "description": "Connected. Events will now start streaming as long as you keep the connection open." + } + }, + "summary": "Subscribe to Trade Events (SSE)", + "tags": [ + "Events", + "Trading" + ] + } + } + }, + "security": [ + { + "BasicAuth": [] + } + ], + "servers": [ + { + "description": "Sandbox endpoint", + "url": "https://broker-api.sandbox.alpaca.markets" + }, + { + "description": "Production endpoint", + "url": "https://broker-api.alpaca.markets" + } + ], + "tags": [ + { + "name": "Accounts" + }, + { + "name": "Documents" + }, + { + "name": "Trading" + }, + { + "name": "Portfolio History" + }, + { + "name": "Assets" + }, + { + "name": "Calendar" + }, + { + "name": "Events" + }, + { + "name": "Funding" + }, + { + "name": "Funding Wallets" + }, + { + "name": "Instant Funding" + }, + { + "name": "OAuth" + }, + { + "name": "Clock" + }, + { + "name": "Journals" + }, + { + "name": "Corporate Actions" + }, + { + "name": "Watchlist" + }, + { + "name": "KYC" + }, + { + "name": "Rebalancing" + }, + { + "name": "Logos" + }, + { + "name": "Reporting" + }, + { + "name": "Country Info" + }, + { + "name": "Crypto Funding" + }, + { + "name": "IRA" + }, + { + "name": "Cash Interest" + }, + { + "name": "FPSL Program" + }, + { + "name": "Tokenization" + }, + { + "description": "Endpoints for discovering and tracking IPO (Initial Public Offering) lifecycle data. Use [`GET /v1/ipos`](#operation/listIPOOfferings) and [`GET /v1/ipos/{offering_reference}`](#operation/getIPOOffering) to query offerings on demand, and the [IPO Events Stream](#operation/subscribeToIPOEventsSSE) under the **Events** group to receive real-time and historical lifecycle updates (new offerings, prospectus, 60-minute mail, allocations, cancellations).\n", + "name": "IPO" + } + ], + "x-readme": { + "explorer-enabled": true, + "proxy-enabled": false + } +} diff --git a/module/market-data/openapi.json b/module/market-data/openapi.json new file mode 100644 index 00000000..7eb5a086 --- /dev/null +++ b/module/market-data/openapi.json @@ -0,0 +1,8634 @@ +{ + "components": { + "headers": { + "ratelimit_limit": { + "description": "Request limit per minute.", + "example": 100, + "schema": { + "type": "integer" + } + }, + "ratelimit_remaining": { + "description": "Request limit per minute remaining.", + "example": 90, + "schema": { + "type": "integer" + } + }, + "ratelimit_reset": { + "description": "The UNIX epoch when the remaining quota changes.", + "example": 1674044551, + "schema": { + "type": "integer" + } + } + }, + "parameters": { + "cas_cusips": { + "description": "A comma-separated list of CUSIPs.", + "example": "037833100,88160R101", + "in": "query", + "name": "cusips", + "schema": { + "type": "string" + } + }, + "cas_end": { + "description": "The inclusive end of the interval. The corporate actions are sorted by their `process_date`. Format: YYYY-MM-DD. Default: current day.\n", + "examples": { + "date": { + "summary": "Date", + "value": "2024-08-25" + } + }, + "in": "query", + "name": "end", + "schema": { + "format": "date", + "type": "string" + } + }, + "cas_ids": { + "description": "A comma-separated list of corporate action IDs. This parameter is mutually exclusive with all other filters (symbols, types, start, end).\n", + "example": "1dbc7685-9517-4a77-a236-8527d49cefdc,f8489167-4e4b-431d-a0be-6017ae1cf08a", + "in": "query", + "name": "ids", + "schema": { + "type": "string" + } + }, + "cas_limit": { + "description": "Maximum number of corporate actions to return in a response.\nThe limit applies to the total number of data points, not the count per symbol!\nUse `next_page_token` to fetch the next set of corporate actions.\n", + "in": "query", + "name": "limit", + "schema": { + "default": 100, + "maximum": 1000, + "minimum": 1, + "type": "integer" + } + }, + "cas_region": { + "description": "The region to filter corporate actions by.", + "in": "query", + "name": "region", + "schema": { + "$ref": "#/components/schemas/region" + } + }, + "cas_start": { + "description": "The inclusive start of the interval. The corporate actions are sorted by their `process_date`. Format: YYYY-MM-DD. Default: current day.\n", + "examples": { + "date": { + "value": "2024-08-14" + } + }, + "in": "query", + "name": "start", + "schema": { + "format": "date", + "type": "string" + } + }, + "cas_symbols": { + "description": "A comma-separated list of symbols.", + "example": "AAPL,TSLA", + "in": "query", + "name": "symbols", + "schema": { + "type": "string" + } + }, + "cas_types": { + "description": "A comma-separated list of types. If not provided, search all types.\n\nThe following types are supported:\n - reverse_split\n - forward_split\n - unit_split\n - cash_dividend\n - stock_dividend\n - spin_off\n - cash_merger\n - stock_merger\n - stock_and_cash_merger\n - redemption\n - name_change\n - worthless_removal\n - rights_distribution\n - partial_call\n - reorganization\n", + "example": "forward_split,reverse_split", + "in": "query", + "name": "types", + "schema": { + "type": "string" + } + }, + "crypto_historical_loc": { + "description": "Crypto location from where the historical market data is retrieved.\n- `us`: Alpaca US\n- `us-1`: Kraken US\n- `eu-1`: Kraken EU\n", + "in": "path", + "name": "loc", + "required": true, + "schema": { + "$ref": "#/components/schemas/crypto_historical_loc" + } + }, + "crypto_latest_loc": { + "description": "Crypto location from where the latest market data is retrieved.\n- `us`: Alpaca US\n- `us-1`: Kraken US\n- `eu-1`: Kraken EU\n", + "in": "path", + "name": "loc", + "required": true, + "schema": { + "$ref": "#/components/schemas/crypto_latest_loc" + } + }, + "crypto_perp_loc": { + "description": "Crypto perpetual location.", + "in": "path", + "name": "loc", + "required": true, + "schema": { + "$ref": "#/components/schemas/crypto_perp_loc" + } + }, + "crypto_perp_symbols": { + "description": "A comma-separated list of crypto symbols.", + "example": "BTCUSDT.P,LTCUSDT.P", + "in": "query", + "name": "symbols", + "required": true, + "schema": { + "type": "string" + } + }, + "crypto_symbols": { + "description": "A comma-separated list of crypto symbols.", + "example": "BTC/USD,LTC/USD", + "in": "query", + "name": "symbols", + "required": true, + "schema": { + "type": "string" + } + }, + "end": { + "description": "The inclusive end of the interval. Format: RFC-3339 or YYYY-MM-DD.\nDefault: the current time if the user has a real-time access for the feed, otherwise 15 minutes before the current time.\n", + "examples": { + "RFC-3339 nanosecond": { + "summary": "RFC-3339 date-time with nanosecond accuracy", + "value": "2024-01-04T01:02:03.123456789Z" + }, + "RFC-3339 second": { + "summary": "RFC-3339 date-time with second accuracy", + "value": "2024-01-04T00:00:00Z" + }, + "RFC-3339 with timezone": { + "summary": "RFC-3339 date-time with time zone", + "value": "2024-01-04T09:30:00-04:00" + }, + "date": { + "summary": "Date", + "value": "2024-01-04" + } + }, + "in": "query", + "name": "end", + "required": false, + "schema": { + "format": "date-time", + "type": "string" + } + }, + "forex_currency_pairs": { + "in": "query", + "name": "currency_pairs", + "required": true, + "schema": { + "$ref": "#/components/schemas/forex_currency_pairs" + } + }, + "forex_timeframe": { + "in": "query", + "name": "timeframe", + "required": false, + "schema": { + "$ref": "#/components/schemas/forex_timeframe" + } + }, + "index_symbols": { + "description": "A comma-separated list of index symbols.", + "example": "SPX,VIX", + "in": "query", + "name": "symbols", + "required": true, + "schema": { + "type": "string" + } + }, + "isins": { + "description": "A comma-separated list of ISINs with a limit of 1000.", + "example": "US912797KJ59,US912797KS58,US912797LB15", + "in": "query", + "name": "isins", + "required": true, + "schema": { + "type": "string" + } + }, + "limit": { + "description": "The maximum number of data points to return in the response page.\nThe API may return less, even if there are more available data points in the requested interval.\nAlways check the `next_page_token` for more pages.\nThe limit applies to the total number of data points, not per symbol!\n", + "in": "query", + "name": "limit", + "required": false, + "schema": { + "default": 1000, + "maximum": 10000, + "minimum": 1, + "type": "integer" + } + }, + "most_actives_by": { + "description": "The metric used for ranking the most active stocks.", + "in": "query", + "name": "by", + "required": false, + "schema": { + "default": "volume", + "enum": [ + "volume", + "trades" + ], + "type": "string" + } + }, + "most_actives_top": { + "description": "The number of top most active stocks to fetch per day.", + "in": "query", + "name": "top", + "required": false, + "schema": { + "default": 10, + "format": "int32", + "maximum": 100, + "minimum": 1, + "type": "integer" + } + }, + "movers_market_type": { + "description": "Screen-specific market (stocks or crypto).", + "in": "path", + "name": "market_type", + "required": true, + "schema": { + "$ref": "#/components/schemas/market_type" + } + }, + "movers_top": { + "description": "Number of top market movers to fetch (gainers and losers). Will return this number of results for each. By default, 10 gainers and 10 losers.\n", + "in": "query", + "name": "top", + "required": false, + "schema": { + "default": 10, + "format": "int32", + "maximum": 50, + "minimum": 1, + "type": "integer" + } + }, + "news_sort": { + "description": "Sort articles by updated date.", + "in": "query", + "name": "sort", + "schema": { + "default": "desc", + "enum": [ + "asc", + "desc" + ], + "type": "string" + } + }, + "option_feed": { + "description": "The source feed of the data. `opra` is the official OPRA feed, `indicative` is a free indicative feed where trades are delayed and quotes are modified. Default: `opra` if the user has a subscription, otherwise `indicative`.\n", + "in": "query", + "name": "feed", + "schema": { + "$ref": "#/components/schemas/option_feed" + } + }, + "option_limit": { + "description": "Number of maximum snapshots to return in a response.\nThe limit applies to the total number of data points, not the number per symbol!\nUse `next_page_token` to fetch the next set of responses.\n", + "in": "query", + "name": "limit", + "schema": { + "default": 100, + "maximum": 1000, + "minimum": 1, + "type": "integer" + } + }, + "option_symbols": { + "description": "A comma-separated list of contract symbols with a limit of 100.", + "example": "AAPL241220C00300000,AAPL240315C00225000", + "in": "query", + "name": "symbols", + "required": true, + "schema": { + "type": "string" + } + }, + "option_underlying_symbol": { + "description": "The financial instrument on which an option contract is based or derived.", + "example": "AAPL", + "in": "path", + "name": "underlying_symbol", + "required": true, + "schema": { + "type": "string" + } + }, + "option_updated_since": { + "description": "Filter to snapshots that were updated since this timestamp, meaning that the timestamp of the trade or the quote is greater than or equal to this value.\nFormat: RFC-3339 or YYYY-MM-DD. If missing, all values are returned.\n", + "in": "query", + "name": "updated_since", + "required": false, + "schema": { + "format": "date-time", + "type": "string" + } + }, + "page_token": { + "description": "The pagination token from which to continue. The value to pass here is returned in specific requests when more data is available, usually because of a response result limit.\n", + "in": "query", + "name": "page_token", + "schema": { + "type": "string" + } + }, + "sort": { + "description": "Sort data in ascending or descending order.", + "in": "query", + "name": "sort", + "schema": { + "$ref": "#/components/schemas/sort" + } + }, + "start": { + "description": "The inclusive start of the interval. Format: RFC-3339 or YYYY-MM-DD.\nDefault: the beginning of the current day, but at least 15 minutes ago if the user doesn't have real-time access for the feed.\n", + "examples": { + "RFC-3339 nanosecond": { + "summary": "RFC-3339 date-time with nanosecond accuracy", + "value": "2024-01-03T01:02:03.123456789Z" + }, + "RFC-3339 second": { + "summary": "RFC-3339 date-time with second accuracy", + "value": "2024-01-03T00:00:00Z" + }, + "RFC-3339 with timezone": { + "summary": "RFC-3339 date-time with time zone", + "value": "2024-01-03T09:30:00-04:00" + }, + "date": { + "summary": "Date", + "value": "2024-01-03" + } + }, + "in": "query", + "name": "start", + "required": false, + "schema": { + "format": "date-time", + "type": "string" + } + }, + "stock_adjustment": { + "description": "Specifies the adjustments for the bars.\n\n - `raw`: no adjustments\n - `split`: adjust price and volume for forward and reverse stock splits\n - `dividend`: adjust price for cash dividends\n - `spin-off`: adjust price for spin-offs\n - `all`: apply all above adjustments\n\nYou can combine multiple adjustments by separating them with a comma, e.g. `split,spin-off`.\n", + "in": "query", + "name": "adjustment", + "schema": { + "default": "raw", + "type": "string" + } + }, + "stock_asof": { + "description": "The as-of date of the queried stock symbol(s). Format: YYYY-MM-DD. Default: current day.\n\nThis date is used to identify the underlying entity of the provided symbol(s), so that name changes for this entity can be found. Data for past symbol(s) is returned if the query date range spans the name change.\n\nThe special value of \"-\" means symbol mapping is skipped. Data is returned based on the symbol alone without looking up previous names. The same happens if the queried symbol is not found on the given `asof` date.\n\nExample: FB was renamed to META in 2022-06-09. Querying META with an `asof` date after 2022-06-09 will also yield FB data. The data for the FB ticker will be labeled as META because they are considered the same underlying entity as of 2022-06-09. Querying FB with an `asof` date after 2022-06-09 will only return data with the FB ticker, not with META. But with an `asof` date before 2022-06-09, META will also be returned (as FB).\n", + "in": "query", + "name": "asof", + "schema": { + "type": "string" + } + }, + "stock_auction_feed": { + "description": "Only `sip` is valid for auctions.", + "in": "query", + "name": "feed", + "schema": { + "$ref": "#/components/schemas/stock_auction_feed" + } + }, + "stock_currency": { + "description": "The currency of all prices in ISO 4217 format. Default: USD.\n", + "in": "query", + "name": "currency", + "schema": { + "type": "string" + } + }, + "stock_historical_feed": { + "description": "The source feed of the data.\n - `sip`: all US exchanges\n - `iex`: Investors EXchange\n - `boats`: Blue Ocean ATS, overnight US trading data\n - `otc`: over-the-counter exchanges\n", + "in": "query", + "name": "feed", + "schema": { + "$ref": "#/components/schemas/stock_historical_feed" + } + }, + "stock_latest_feed": { + "description": "The source feed of the data.\n\n - `sip`: all US exchanges\n - `iex`: Investors EXchange\n - `delayed_sip`: SIP with a 15 minute delay\n - `boats`: Blue Ocean, overnight US trading data\n - `overnight`: derived overnight US trading data\n - `otc`: over-the-counter exchanges\n\nDefault: `sip` if the user has the unlimited subscription, otherwise `iex`.\n", + "in": "query", + "name": "feed", + "schema": { + "$ref": "#/components/schemas/stock_latest_feed" + } + }, + "stock_symbol": { + "description": "The symbol to query.", + "example": "AAPL", + "in": "path", + "name": "symbol", + "required": true, + "schema": { + "type": "string" + } + }, + "stock_symbols": { + "description": "A comma-separated list of stock symbols.", + "example": "AAPL,TSLA", + "in": "query", + "name": "symbols", + "required": true, + "schema": { + "type": "string" + } + }, + "symbol": { + "description": "A unique series of letters assigned to a security for trading purposes.", + "example": "AAPL", + "in": "path", + "name": "symbol", + "required": true, + "schema": { + "type": "string" + } + }, + "timeframe": { + "description": "The timeframe represented by each bar in aggregation.\nYou can use any of the following values:\n - `[1-59]Min` or `[1-59]T`, e.g. `5Min` or `5T` creates 5-minute aggregations\n - `[1-23]Hour` or `[1-23]H`, e.g. `12Hour` or `12H` creates 12-hour aggregations\n - `1Day` or `1D` creates 1-day aggregations\n - `1Week` or `1W` creates 1-week aggregations\n - `[1,2,3,4,6,12]Month` or `[1,2,3,4,6,12]M`, e.g. `3Month` or `3M` creates 3-month aggregations\n", + "example": "1Min", + "in": "query", + "name": "timeframe", + "required": true, + "schema": { + "type": "string" + }, + "x-go-name": "TimeFrame" + }, + "trade_size": { + "description": "Filters to best bid/ask where the minimum trade size is less than or equal to the given numeric value. For any negative value the best bid/ask will be returned for the smallest trade size on either side.\n", + "in": "query", + "name": "trade_size", + "required": false, + "schema": { + "format": "int32", + "type": "integer" + } + } + }, + "responses": { + "400": { + "description": "One of the request parameters is invalid. See the returned message for details.\n", + "headers": { + "X-RateLimit-Limit": { + "$ref": "#/components/headers/ratelimit_limit" + }, + "X-RateLimit-Remaining": { + "$ref": "#/components/headers/ratelimit_remaining" + }, + "X-RateLimit-Reset": { + "$ref": "#/components/headers/ratelimit_reset" + } + } + }, + "401": { + "description": "Authentication headers are missing or invalid. Make sure you authenticate your request with a valid API key.\n" + }, + "403": { + "description": "The requested resource is forbidden.\n" + }, + "429": { + "description": "Too many requests. You hit the rate limit. Use the X-RateLimit-... response headers to make sure you're under the rate limit.\n", + "headers": { + "X-RateLimit-Limit": { + "$ref": "#/components/headers/ratelimit_limit" + }, + "X-RateLimit-Remaining": { + "$ref": "#/components/headers/ratelimit_remaining" + }, + "X-RateLimit-Reset": { + "$ref": "#/components/headers/ratelimit_reset" + } + } + }, + "500": { + "description": "Internal server error. We recommend retrying these later. If the issue persists, please contact us on [Slack](https://alpaca.markets/slack) or on the [Community Forum](https://forum.alpaca.markets/).\n" + } + }, + "schemas": { + "ca_id": { + "description": "The internal Alpaca identifier of the corporate action.", + "format": "uuid", + "type": "string" + }, + "cash_dividend": { + "description": "Cash dividend.", + "example": { + "cusip": "319829107", + "ex_date": "2023-05-04", + "foreign": false, + "id": "11cfd108-292e-4cc6-bfbf-5999cdbc4029", + "payable_date": "2023-05-19", + "process_date": "2023-05-19", + "rate": 0.125, + "record_date": "2023-05-05", + "special": false, + "symbol": "FCF" + }, + "properties": { + "currency": { + "$ref": "#/components/schemas/currency" + }, + "cusip": { + "type": "string" + }, + "due_bill_off_date": { + "format": "date", + "type": "string" + }, + "due_bill_on_date": { + "format": "date", + "type": "string" + }, + "ex_date": { + "$ref": "#/components/schemas/ex_date" + }, + "foreign": { + "type": "boolean" + }, + "id": { + "$ref": "#/components/schemas/ca_id" + }, + "isin": { + "$ref": "#/components/schemas/isin" + }, + "payable_date": { + "$ref": "#/components/schemas/payable_date" + }, + "process_date": { + "$ref": "#/components/schemas/process_date" + }, + "rate": { + "format": "double", + "type": "number" + }, + "record_date": { + "$ref": "#/components/schemas/record_date" + }, + "special": { + "type": "boolean" + }, + "sub_type": { + "description": "Sub-type of the cash dividend.", + "enum": [ + "interest", + "return_of_capital" + ], + "type": "string" + }, + "symbol": { + "type": "string" + } + }, + "required": [ + "id", + "symbol", + "cusip", + "rate", + "special", + "foreign", + "process_date", + "ex_date" + ], + "type": "object" + }, + "cash_merger": { + "description": "Cash merger.", + "example": { + "acquiree_cusip": "Y2687W108", + "acquiree_symbol": "GLOP", + "effective_date": "2023-07-17", + "id": "3772bbd7-4ad5-44d4-9cc0-f69156a2f8f5", + "payable_date": "2023-07-17", + "process_date": "2023-07-17", + "rate": 5.37 + }, + "properties": { + "acquiree_cusip": { + "type": "string" + }, + "acquiree_isin": { + "$ref": "#/components/schemas/isin" + }, + "acquiree_symbol": { + "type": "string" + }, + "acquirer_cusip": { + "type": "string" + }, + "acquirer_isin": { + "$ref": "#/components/schemas/isin" + }, + "acquirer_symbol": { + "type": "string" + }, + "currency": { + "$ref": "#/components/schemas/currency" + }, + "effective_date": { + "$ref": "#/components/schemas/effective_date" + }, + "id": { + "$ref": "#/components/schemas/ca_id" + }, + "payable_date": { + "$ref": "#/components/schemas/payable_date" + }, + "process_date": { + "$ref": "#/components/schemas/process_date" + }, + "rate": { + "format": "double", + "type": "number" + } + }, + "required": [ + "id", + "acquiree_symbol", + "acquiree_cusip", + "rate", + "process_date", + "effective_date" + ], + "type": "object" + }, + "corporate_actions": { + "properties": { + "cash_dividends": { + "items": { + "$ref": "#/components/schemas/cash_dividend" + }, + "type": "array", + "x-go-type-skip-optional-pointer": true + }, + "cash_mergers": { + "items": { + "$ref": "#/components/schemas/cash_merger" + }, + "type": "array", + "x-go-type-skip-optional-pointer": true + }, + "forward_splits": { + "items": { + "$ref": "#/components/schemas/forward_split" + }, + "type": "array", + "x-go-type-skip-optional-pointer": true + }, + "name_changes": { + "items": { + "$ref": "#/components/schemas/name_change" + }, + "type": "array", + "x-go-type-skip-optional-pointer": true + }, + "partial_calls": { + "items": { + "$ref": "#/components/schemas/partial_call" + }, + "type": "array", + "x-go-type-skip-optional-pointer": true + }, + "redemptions": { + "items": { + "$ref": "#/components/schemas/redemption" + }, + "type": "array", + "x-go-type-skip-optional-pointer": true + }, + "reorganizations": { + "items": { + "$ref": "#/components/schemas/reorganization" + }, + "type": "array", + "x-go-type-skip-optional-pointer": true + }, + "reverse_splits": { + "items": { + "$ref": "#/components/schemas/reverse_split" + }, + "type": "array", + "x-go-type-skip-optional-pointer": true + }, + "rights_distributions": { + "items": { + "$ref": "#/components/schemas/rights_distribution" + }, + "type": "array", + "x-go-type-skip-optional-pointer": true + }, + "spin_offs": { + "items": { + "$ref": "#/components/schemas/spin_off" + }, + "type": "array", + "x-go-type-skip-optional-pointer": true + }, + "stock_and_cash_mergers": { + "items": { + "$ref": "#/components/schemas/stock_and_cash_merger" + }, + "type": "array", + "x-go-type-skip-optional-pointer": true + }, + "stock_dividends": { + "items": { + "$ref": "#/components/schemas/stock_dividend" + }, + "type": "array", + "x-go-type-skip-optional-pointer": true + }, + "stock_mergers": { + "items": { + "$ref": "#/components/schemas/stock_merger" + }, + "type": "array", + "x-go-type-skip-optional-pointer": true + }, + "unit_splits": { + "items": { + "$ref": "#/components/schemas/unit_split" + }, + "type": "array", + "x-go-type-skip-optional-pointer": true + }, + "worthless_removals": { + "items": { + "$ref": "#/components/schemas/worthless_removal" + }, + "type": "array", + "x-go-type-skip-optional-pointer": true + } + }, + "type": "object" + }, + "corporate_actions_resp": { + "properties": { + "corporate_actions": { + "$ref": "#/components/schemas/corporate_actions" + }, + "next_page_token": { + "$ref": "#/components/schemas/next_page_token" + } + }, + "required": [ + "corporate_actions", + "next_page_token" + ], + "type": "object" + }, + "crypto_bar": { + "description": "OHLC aggregate of all the trades in a given interval.", + "example": { + "c": 29003, + "h": 29003, + "l": 28999, + "n": 4, + "o": 28999, + "t": "2022-05-27T10:18:00Z", + "v": 0.01, + "vw": 29001 + }, + "properties": { + "c": { + "description": "Closing price.", + "format": "double", + "type": "number", + "x-go-name": "Close" + }, + "h": { + "description": "High price.", + "format": "double", + "type": "number", + "x-go-name": "High" + }, + "l": { + "description": "Low price.", + "format": "double", + "type": "number", + "x-go-name": "Low" + }, + "n": { + "description": "Trade count in the bar.", + "format": "int64", + "type": "integer", + "x-go-name": "TradeCount" + }, + "o": { + "description": "Opening price.", + "format": "double", + "type": "number", + "x-go-name": "Open" + }, + "t": { + "$ref": "#/components/schemas/timestamp" + }, + "v": { + "description": "Bar volume.", + "format": "double", + "type": "number", + "x-go-name": "Volume" + }, + "vw": { + "description": "Volume weighted average price.", + "format": "double", + "type": "number", + "x-go-name": "VWAP" + } + }, + "required": [ + "t", + "o", + "h", + "l", + "c", + "v", + "n", + "vw" + ], + "type": "object" + }, + "crypto_bars_resp": { + "properties": { + "bars": { + "additionalProperties": { + "items": { + "$ref": "#/components/schemas/crypto_bar" + }, + "type": "array" + }, + "type": "object" + }, + "next_page_token": { + "$ref": "#/components/schemas/next_page_token" + } + }, + "required": [ + "bars", + "next_page_token" + ], + "type": "object" + }, + "crypto_historical_loc": { + "description": "Crypto location from where the historical market data is retrieved.", + "enum": [ + "us", + "us-1", + "us-2", + "eu-1", + "bs-1" + ], + "type": "string", + "x-go-name": "TypeHistoricalLoc" + }, + "crypto_latest_bars_resp": { + "properties": { + "bars": { + "additionalProperties": { + "$ref": "#/components/schemas/crypto_bar" + }, + "type": "object" + } + }, + "required": [ + "bars" + ], + "type": "object" + }, + "crypto_latest_loc": { + "description": "Crypto location from where the latest market data is retrieved.", + "enum": [ + "us", + "us-1", + "us-2", + "eu-1", + "bs-1" + ], + "type": "string", + "x-go-name": "TypeLatestLoc" + }, + "crypto_latest_orderbooks_resp": { + "properties": { + "orderbooks": { + "additionalProperties": { + "$ref": "#/components/schemas/crypto_orderbook" + }, + "type": "object" + } + }, + "required": [ + "orderbooks" + ], + "type": "object" + }, + "crypto_latest_quotes_resp": { + "properties": { + "quotes": { + "additionalProperties": { + "$ref": "#/components/schemas/crypto_quote" + }, + "type": "object" + } + }, + "required": [ + "quotes" + ], + "type": "object" + }, + "crypto_latest_trades_resp": { + "properties": { + "trades": { + "additionalProperties": { + "$ref": "#/components/schemas/crypto_trade" + }, + "type": "object" + } + }, + "required": [ + "trades" + ], + "type": "object" + }, + "crypto_orderbook": { + "description": "Snapshot of the orderbook.", + "example": { + "a": [ + { + "p": 20902, + "s": 0.0097 + }, + { + "p": 21444, + "s": 0 + } + ], + "b": [ + { + "p": 20846, + "s": 0.1902 + }, + { + "p": 20350, + "s": 0 + } + ], + "t": "2022-06-24T08:00:14.137774336Z" + }, + "properties": { + "a": { + "items": { + "$ref": "#/components/schemas/crypto_orderbook_entry" + }, + "type": "array", + "x-go-name": "Asks" + }, + "b": { + "items": { + "$ref": "#/components/schemas/crypto_orderbook_entry" + }, + "type": "array", + "x-go-name": "Bids" + }, + "t": { + "$ref": "#/components/schemas/timestamp" + } + }, + "required": [ + "t", + "b", + "a" + ], + "type": "object" + }, + "crypto_orderbook_entry": { + "description": "A single entry in a crypto orderbook.", + "example": { + "p": 20846, + "s": 0.1902 + }, + "properties": { + "p": { + "description": "Price.", + "format": "double", + "type": "number", + "x-go-name": "Price" + }, + "s": { + "description": "Size.", + "format": "double", + "type": "number", + "x-go-name": "Size" + } + }, + "required": [ + "p", + "s" + ], + "type": "object" + }, + "crypto_perp_futures_pricing": { + "description": "Crypto perpetual futures pricing data.", + "example": { + "fr": 0.000565699, + "ft": "2022-05-27T10:18:00Z", + "ip": 50702.8, + "mp": 50652.3553, + "oi": 90.7367, + "t": "2022-05-27T10:18:00Z" + }, + "properties": { + "fr": { + "description": "Funding rate.", + "format": "double", + "type": "number", + "x-go-name": "FundingRate" + }, + "ft": { + "description": "Next funding time", + "format": "date-time", + "type": "string", + "x-go-name": "NextFundingTime" + }, + "ip": { + "description": "Index price.", + "format": "double", + "type": "number", + "x-go-name": "IndexPrice" + }, + "mp": { + "description": "Mark price.", + "format": "double", + "type": "number", + "x-go-name": "MarkPrice" + }, + "oi": { + "description": "Open interest.", + "format": "double", + "type": "number", + "x-go-name": "OpenInterest" + }, + "t": { + "$ref": "#/components/schemas/timestamp" + } + }, + "required": [ + "t", + "ft", + "oi", + "ip", + "mp", + "fr" + ], + "type": "object" + }, + "crypto_perp_latest_futures_pricing_resp": { + "properties": { + "pricing": { + "additionalProperties": { + "$ref": "#/components/schemas/crypto_perp_futures_pricing" + }, + "type": "object" + } + }, + "required": [ + "pricing" + ], + "type": "object" + }, + "crypto_perp_loc": { + "description": "Crypto perpetual location.", + "enum": [ + "global" + ], + "type": "string", + "x-go-name": "TypePerpLoc" + }, + "crypto_quote": { + "description": "The best bid and ask information for a given security.", + "example": { + "ap": 29059, + "as": 3.252, + "bp": 29058, + "bs": 0.3544, + "t": "2022-05-26T11:47:18.44347136Z" + }, + "properties": { + "ap": { + "description": "Ask price.", + "format": "double", + "type": "number", + "x-go-name": "AskPrice" + }, + "as": { + "description": "Ask size.", + "format": "double", + "type": "number", + "x-go-name": "AskSize" + }, + "bp": { + "description": "Bid price.", + "format": "double", + "type": "number", + "x-go-name": "BidPrice" + }, + "bs": { + "description": "Bid size.", + "format": "double", + "type": "number", + "x-go-name": "BidSize" + }, + "t": { + "$ref": "#/components/schemas/timestamp" + } + }, + "required": [ + "t", + "bp", + "bs", + "ap", + "as" + ], + "type": "object" + }, + "crypto_quotes_resp": { + "properties": { + "next_page_token": { + "$ref": "#/components/schemas/next_page_token" + }, + "quotes": { + "additionalProperties": { + "items": { + "$ref": "#/components/schemas/crypto_quote" + }, + "type": "array" + }, + "type": "object" + } + }, + "required": [ + "quotes", + "next_page_token" + ], + "type": "object" + }, + "crypto_snapshot": { + "description": "A snapshot provides the latest trade, latest quote, latest minute bar, latest daily bar and previous daily bar.\n", + "properties": { + "dailyBar": { + "$ref": "#/components/schemas/crypto_bar" + }, + "latestQuote": { + "$ref": "#/components/schemas/crypto_quote" + }, + "latestTrade": { + "$ref": "#/components/schemas/crypto_trade" + }, + "minuteBar": { + "$ref": "#/components/schemas/crypto_bar" + }, + "prevDailyBar": { + "$ref": "#/components/schemas/crypto_bar" + } + }, + "type": "object" + }, + "crypto_snapshots_resp": { + "properties": { + "snapshots": { + "additionalProperties": { + "$ref": "#/components/schemas/crypto_snapshot" + }, + "type": "object" + } + }, + "required": [ + "snapshots" + ], + "type": "object" + }, + "crypto_trade": { + "description": "A crypto trade.", + "example": { + "i": 31455277, + "p": 29798, + "s": 0.1209, + "t": "2022-05-18T12:00:05.225055Z", + "tks": "S" + }, + "properties": { + "i": { + "description": "Trade ID.", + "format": "int64", + "type": "integer", + "x-go-name": "ID" + }, + "p": { + "description": "Trade price.", + "format": "double", + "type": "number", + "x-go-name": "Price" + }, + "s": { + "description": "Trade size.", + "format": "double", + "type": "number", + "x-go-name": "Size" + }, + "t": { + "$ref": "#/components/schemas/timestamp" + }, + "tks": { + "description": "Taker side: B for buyer, S for seller\n", + "type": "string", + "x-go-name": "TakerSide" + } + }, + "required": [ + "t", + "p", + "s", + "i", + "tks" + ], + "type": "object" + }, + "crypto_trades_resp": { + "properties": { + "next_page_token": { + "$ref": "#/components/schemas/next_page_token" + }, + "trades": { + "additionalProperties": { + "items": { + "$ref": "#/components/schemas/crypto_trade" + }, + "type": "array" + }, + "type": "object" + } + }, + "required": [ + "trades", + "next_page_token" + ], + "type": "object" + }, + "currency": { + "description": "The ISO 4217 currency code associated with the corporate action.\nEmpty value can mean USD, non-applicable (e.g. for name changes) or unknown\n(can change later to a valid currency).\n", + "type": "string" + }, + "date": { + "description": "Date in RFC-3339.", + "format": "date", + "type": "string", + "x-go-name": "Date" + }, + "due_bill_redemption_date": { + "description": "The date when due bill obligations are redeemed.", + "format": "date", + "type": "string" + }, + "effective_date": { + "description": "The effective date marks the cutoff point for shareholders to be credited.", + "format": "date", + "type": "string" + }, + "ex_date": { + "description": "The ex-date marks the cutoff point for shareholders to be credited.", + "format": "date", + "type": "string" + }, + "expiration_date": { + "format": "date", + "type": "string" + }, + "fixed_income_latest_prices_resp": { + "properties": { + "prices": { + "additionalProperties": { + "$ref": "#/components/schemas/fixed_income_price" + }, + "type": "object" + } + }, + "required": [ + "prices" + ], + "type": "object" + }, + "fixed_income_latest_quotes_resp": { + "properties": { + "quotes": { + "additionalProperties": { + "$ref": "#/components/schemas/fixed_income_quote" + }, + "type": "object" + } + }, + "required": [ + "quotes" + ], + "type": "object" + }, + "fixed_income_price": { + "description": "The price of the instrument as a percentage of its par value.", + "example": { + "p": 99.6459, + "t": "2025-02-14T20:58:00.648Z", + "ytm": 4.249, + "ytw": 4.249 + }, + "properties": { + "p": { + "description": "Price", + "format": "double", + "type": "number", + "x-go-name": "Price" + }, + "t": { + "$ref": "#/components/schemas/timestamp" + }, + "ytm": { + "description": "Yield to maturity.", + "format": "double", + "type": "number", + "x-go-name": "YieldToMaturity" + }, + "ytw": { + "description": "Yield to worst.", + "format": "double", + "type": "number", + "x-go-name": "YieldToWorst" + } + }, + "required": [ + "t", + "p" + ], + "type": "object" + }, + "fixed_income_quote": { + "description": "The best bid and ask information for a given fixed income security. A value of 0 means there is no active bid or ask for that field.\n", + "example": { + "ams": 1000, + "ap": 99.91958333, + "as": 1000000, + "aytm": 2.226923, + "aytw": 2.226923, + "bms": 1000, + "bp": 99.81091667, + "bs": 1000000, + "bytm": 5.236154, + "bytw": 5.236154, + "t": "2026-05-21T06:56:01.882466873Z" + }, + "properties": { + "ams": { + "description": "Best ask minimum trade size in par value.", + "format": "int64", + "type": "integer", + "x-go-name": "AskMinTradeSize" + }, + "ap": { + "description": "Best ask price. 0 means there is no active ask.", + "format": "double", + "type": "number", + "x-go-name": "AskPrice" + }, + "as": { + "description": "Best ask size in par value. 0 means there is no active ask.", + "format": "int64", + "type": "integer", + "x-go-name": "AskSize" + }, + "aytm": { + "description": "Best ask yield to maturity.", + "format": "double", + "type": "number", + "x-go-name": "AskYieldToMaturity" + }, + "aytw": { + "description": "Best ask yield to worst.", + "format": "double", + "type": "number", + "x-go-name": "AskYieldToWorst" + }, + "bms": { + "description": "Best bid minimum trade size in par value.", + "format": "int64", + "type": "integer", + "x-go-name": "BidMinTradeSize" + }, + "bp": { + "description": "Best bid price. 0 means there is no active bid.", + "format": "double", + "type": "number", + "x-go-name": "BidPrice" + }, + "bs": { + "description": "Best bid size in par value. 0 means there is no active bid.", + "format": "int64", + "type": "integer", + "x-go-name": "BidSize" + }, + "bytm": { + "description": "Best bid yield to maturity.", + "format": "double", + "type": "number", + "x-go-name": "BidYieldToMaturity" + }, + "bytw": { + "description": "Best bid yield to worst.", + "format": "double", + "type": "number", + "x-go-name": "BidYieldToWorst" + }, + "t": { + "$ref": "#/components/schemas/timestamp" + } + }, + "required": [ + "t", + "bp", + "bs", + "bms", + "bytm", + "bytw", + "ap", + "as", + "ams", + "aytm", + "aytw" + ], + "type": "object" + }, + "forex_currency_pairs": { + "description": "A comma-separated string with currency pairs.", + "example": "USDJPY,USDMXN", + "type": "string", + "x-go-name": "TypeCurrencyPairs" + }, + "forex_latest_rates_resp": { + "description": "The response object of the latest forex rates.", + "properties": { + "rates": { + "additionalProperties": { + "$ref": "#/components/schemas/forex_rate" + }, + "type": "object" + } + }, + "required": [ + "rates" + ], + "type": "object" + }, + "forex_rate": { + "description": "A foreign exchange rate between two currencies at a given time.", + "example": { + "ap": 127.763, + "bp": 127.702, + "mp": 127.757, + "t": "2022-04-20T18:23:00Z" + }, + "properties": { + "ap": { + "description": "The last ask price value of the currency at the end of the timeframe.", + "format": "double", + "type": "number", + "x-go-name": "AskPrice" + }, + "bp": { + "description": "The last bid price value of the currency at the end of the timeframe.", + "format": "double", + "type": "number", + "x-go-name": "BidPrice" + }, + "mp": { + "description": "The last mid price value of the currency at the end of the timeframe.", + "format": "double", + "type": "number", + "x-go-name": "MidPrice" + }, + "t": { + "description": "Timestamp of the rate.", + "format": "date-time", + "type": "string", + "x-go-name": "Timestamp" + } + }, + "required": [ + "bp", + "mp", + "ap", + "t" + ], + "type": "object" + }, + "forex_rates_resp": { + "properties": { + "next_page_token": { + "$ref": "#/components/schemas/next_page_token" + }, + "rates": { + "additionalProperties": { + "items": { + "$ref": "#/components/schemas/forex_rate" + }, + "type": "array" + }, + "type": "object" + } + }, + "required": [ + "rates", + "next_page_token" + ], + "type": "object" + }, + "forex_timeframe": { + "default": "1Min", + "description": "The sampling interval of the currency rates. For example, 5S returns forex rates sampled every five seconds.\nYou can use the following values:\n - `5Sec` or `5S`\n - `1Min` or `1T`\n - `1Day` or `1D`\n", + "type": "string" + }, + "forward_split": { + "description": "Forward split.", + "example": { + "cusip": "816851109", + "due_bill_redemption_date": "2023-08-23", + "ex_date": "2023-08-22", + "id": "189bd849-ab9f-4b4d-aaaa-a6d415fd976d", + "new_rate": 2, + "old_rate": 1, + "payable_date": "2023-08-21", + "process_date": "2023-08-22", + "record_date": "2023-08-14", + "symbol": "SRE" + }, + "properties": { + "currency": { + "$ref": "#/components/schemas/currency" + }, + "cusip": { + "type": "string" + }, + "due_bill_redemption_date": { + "$ref": "#/components/schemas/due_bill_redemption_date" + }, + "ex_date": { + "$ref": "#/components/schemas/ex_date" + }, + "id": { + "$ref": "#/components/schemas/ca_id" + }, + "isin": { + "$ref": "#/components/schemas/isin" + }, + "new_rate": { + "format": "double", + "type": "number" + }, + "old_rate": { + "format": "double", + "type": "number" + }, + "payable_date": { + "$ref": "#/components/schemas/payable_date" + }, + "process_date": { + "$ref": "#/components/schemas/process_date" + }, + "record_date": { + "$ref": "#/components/schemas/record_date" + }, + "symbol": { + "type": "string" + } + }, + "required": [ + "id", + "symbol", + "cusip", + "new_rate", + "old_rate", + "process_date", + "ex_date" + ], + "type": "object" + }, + "index_latest_values_resp": { + "description": "The response object of the latest index values.", + "properties": { + "values": { + "additionalProperties": { + "$ref": "#/components/schemas/index_value" + }, + "type": "object" + } + }, + "required": [ + "values" + ], + "type": "object" + }, + "index_value": { + "description": "The value of an index at a given point in time.", + "example": { + "t": "2026-05-18T17:00:02.0256Z", + "v": 7383.46 + }, + "properties": { + "t": { + "$ref": "#/components/schemas/timestamp" + }, + "v": { + "description": "Index value.", + "format": "double", + "type": "number", + "x-go-name": "Value" + } + }, + "required": [ + "t", + "v" + ], + "type": "object" + }, + "index_values_resp": { + "properties": { + "next_page_token": { + "$ref": "#/components/schemas/next_page_token" + }, + "values": { + "additionalProperties": { + "items": { + "$ref": "#/components/schemas/index_value" + }, + "type": "array" + }, + "type": "object" + } + }, + "required": [ + "values", + "next_page_token" + ], + "type": "object" + }, + "isin": { + "description": "International Securities Identification Number (ISIN) as defined by ISO 6166.\nMay be empty for US corporate actions.\n", + "type": "string" + }, + "market_type": { + "description": "Market type (stocks or crypto).", + "enum": [ + "stocks", + "crypto" + ], + "type": "string" + }, + "most_active": { + "description": "A stock that is most active by either volume or trade count.", + "example": { + "symbol": "AAPL", + "trade_count": 639626, + "volume": 122709184 + }, + "properties": { + "symbol": { + "type": "string" + }, + "trade_count": { + "description": "Cumulative trade count for the current trading day.", + "format": "int64", + "type": "integer" + }, + "volume": { + "description": "Cumulative volume for the current trading day.", + "format": "int64", + "type": "integer" + } + }, + "required": [ + "symbol", + "volume", + "trade_count" + ], + "type": "object" + }, + "most_actives_resp": { + "properties": { + "last_updated": { + "description": "Time when the most actives were last computed. Formatted as a RFC-3339 date-time with nanosecond precision.\n", + "type": "string" + }, + "most_actives": { + "description": "List of top N most active symbols.", + "items": { + "$ref": "#/components/schemas/most_active" + }, + "type": "array" + } + }, + "required": [ + "most_actives", + "last_updated" + ], + "type": "object" + }, + "mover": { + "description": "A symbol whose price moved significantly.", + "example": { + "change": 2.46, + "percent_change": 145.56, + "price": 4.15, + "symbol": "AGRI" + }, + "properties": { + "change": { + "description": "Difference in change for the day.", + "format": "double", + "type": "number" + }, + "percent_change": { + "description": "Percentage difference change for the day.", + "format": "double", + "type": "number" + }, + "price": { + "description": "Current price of market moving asset.", + "format": "double", + "type": "number" + }, + "symbol": { + "description": "Symbol of market moving asset.", + "type": "string" + } + }, + "required": [ + "symbol", + "percent_change", + "change", + "price" + ], + "title": "Mover", + "type": "object" + }, + "movers_resp": { + "description": "Contains list of market movers.", + "properties": { + "gainers": { + "description": "List of top N gainers.", + "items": { + "$ref": "#/components/schemas/mover" + }, + "type": "array" + }, + "last_updated": { + "description": "Time when the movers were last computed. Formatted as a RFC-3339 date-time with nanosecond precision.\n", + "type": "string" + }, + "losers": { + "description": "List of top N losers.", + "items": { + "$ref": "#/components/schemas/mover" + }, + "type": "array" + }, + "market_type": { + "$ref": "#/components/schemas/market_type" + } + }, + "required": [ + "gainers", + "losers", + "market_type", + "last_updated" + ], + "type": "object" + }, + "name_change": { + "description": "Name change.", + "example": { + "id": "5a774c35-edec-4532-a812-a56d0bbb623a", + "new_cusip": "Y9390M103", + "new_symbol": "VFS", + "old_cusip": "G11537100", + "old_symbol": "BSAQ", + "process_date": "2023-08-15" + }, + "properties": { + "currency": { + "$ref": "#/components/schemas/currency" + }, + "id": { + "$ref": "#/components/schemas/ca_id" + }, + "new_cusip": { + "type": "string" + }, + "new_isin": { + "$ref": "#/components/schemas/isin" + }, + "new_symbol": { + "type": "string" + }, + "old_cusip": { + "type": "string" + }, + "old_isin": { + "$ref": "#/components/schemas/isin" + }, + "old_symbol": { + "type": "string" + }, + "process_date": { + "$ref": "#/components/schemas/process_date" + } + }, + "required": [ + "id", + "old_symbol", + "old_cusip", + "new_symbol", + "new_cusip", + "process_date" + ], + "type": "object" + }, + "news": { + "description": "Model representing a news article.", + "properties": { + "author": { + "description": "Original author of news article.", + "minLength": 1, + "type": "string" + }, + "content": { + "description": "Content of the news article (might contain HTML).", + "minLength": 1, + "type": "string" + }, + "created_at": { + "description": "Date article was created (RFC-3339).", + "format": "date-time", + "type": "string" + }, + "headline": { + "description": "Headline or title of the article.", + "minLength": 1, + "type": "string" + }, + "id": { + "description": "News article ID.", + "format": "int64", + "type": "integer" + }, + "images": { + "description": "List of images (URLs) related to given article (may be empty).", + "items": { + "$ref": "#/components/schemas/news_image" + }, + "type": "array", + "uniqueItems": true + }, + "source": { + "description": "Source where the news originated from (e.g. Benzinga).", + "minLength": 1, + "type": "string" + }, + "summary": { + "description": "Summary text for the article (may be first sentence of content).", + "minLength": 1, + "type": "string" + }, + "symbols": { + "description": "List of related or mentioned symbols.", + "items": { + "type": "string" + }, + "type": "array" + }, + "updated_at": { + "description": "Date article was updated (RFC-3339).", + "format": "date-time", + "type": "string" + }, + "url": { + "description": "URL of article (if applicable).", + "format": "uri", + "nullable": true, + "type": "string" + } + }, + "required": [ + "id", + "headline", + "author", + "created_at", + "updated_at", + "summary", + "content", + "images", + "symbols", + "source" + ], + "type": "object" + }, + "news_image": { + "description": "A model representing images for a news article. Simply a URL to the image along with a size parameter suggesting the display size of the image.", + "properties": { + "size": { + "description": "Possible values for size are thumb, small and large.", + "enum": [ + "thumb", + "small", + "large" + ], + "example": "thumb", + "minLength": 1, + "type": "string" + }, + "url": { + "description": "URL to image from news article.", + "format": "uri", + "minLength": 1, + "type": "string" + } + }, + "required": [ + "size", + "url" + ], + "type": "object" + }, + "news_resp": { + "properties": { + "news": { + "items": { + "$ref": "#/components/schemas/news" + }, + "type": "array" + }, + "next_page_token": { + "$ref": "#/components/schemas/next_page_token" + } + }, + "required": [ + "news", + "next_page_token" + ], + "type": "object" + }, + "next_page_token": { + "description": "Pagination token for the next page.", + "nullable": true, + "type": "string" + }, + "option_bar": { + "description": "OHLC aggregate of all the trades in a given interval.", + "example": { + "c": 0.23, + "h": 0.28, + "l": 0.23, + "n": 26, + "o": 0.28, + "t": "2024-01-18T05:00:00Z", + "v": 224, + "vw": 0.245045 + }, + "properties": { + "c": { + "description": "Closing price.", + "format": "double", + "type": "number", + "x-go-name": "Close" + }, + "h": { + "description": "High price.", + "format": "double", + "type": "number", + "x-go-name": "High" + }, + "l": { + "description": "Low price.", + "format": "double", + "type": "number", + "x-go-name": "Low" + }, + "n": { + "description": "Trade count in the bar.", + "format": "int64", + "type": "integer", + "x-go-name": "TradeCount" + }, + "o": { + "description": "Opening price.", + "format": "double", + "type": "number", + "x-go-name": "Open" + }, + "t": { + "$ref": "#/components/schemas/timestamp" + }, + "v": { + "description": "Bar volume.", + "format": "int64", + "type": "integer", + "x-go-name": "Volume" + }, + "vw": { + "description": "Volume weighted average price.", + "format": "double", + "type": "number", + "x-go-name": "VWAP" + } + }, + "required": [ + "t", + "o", + "h", + "l", + "c", + "v", + "n", + "vw" + ], + "type": "object" + }, + "option_bars_resp": { + "properties": { + "bars": { + "additionalProperties": { + "items": { + "$ref": "#/components/schemas/option_bar" + }, + "type": "array" + }, + "type": "object" + }, + "currency": { + "type": "string" + }, + "next_page_token": { + "$ref": "#/components/schemas/next_page_token" + } + }, + "required": [ + "bars", + "next_page_token" + ], + "type": "object" + }, + "option_conditions": { + "additionalProperties": { + "type": "string" + }, + "example": { + "a": "SLAN - Single Leg Auction Non ISO", + "e": "SLFT - Single Leg Floor Trade", + "g": "MLAT - Multi Leg Auction" + }, + "type": "object" + }, + "option_exchanges": { + "additionalProperties": { + "type": "string" + }, + "example": { + "A": "NYSE American Options", + "Q": "Nasdaq Options" + }, + "type": "object" + }, + "option_feed": { + "default": "opra", + "enum": [ + "opra", + "indicative" + ], + "type": "string" + }, + "option_greeks": { + "description": "The greeks for the contract calculated using the Black-Scholes model.", + "properties": { + "delta": { + "format": "double", + "type": "number" + }, + "gamma": { + "format": "double", + "type": "number" + }, + "rho": { + "format": "double", + "type": "number" + }, + "theta": { + "format": "double", + "type": "number" + }, + "vega": { + "format": "double", + "type": "number" + } + }, + "required": [ + "delta", + "gamma", + "theta", + "vega", + "rho" + ], + "type": "object" + }, + "option_latest_quotes_resp": { + "properties": { + "quotes": { + "additionalProperties": { + "$ref": "#/components/schemas/option_quote" + }, + "type": "object" + } + }, + "required": [ + "quotes" + ], + "type": "object" + }, + "option_latest_trades_resp": { + "properties": { + "trades": { + "additionalProperties": { + "$ref": "#/components/schemas/option_trade" + }, + "type": "object" + } + }, + "required": [ + "trades" + ], + "type": "object" + }, + "option_quote": { + "description": "The best bid and ask information for a given option.\n", + "example": { + "ap": 0.16, + "as": 669, + "ax": "w", + "bp": 0.15, + "bs": 164, + "bx": "W", + "c": "A", + "t": "2024-02-28T15:30:28.046330624Z" + }, + "properties": { + "ap": { + "description": "Ask price.", + "format": "double", + "type": "number", + "x-go-name": "AskPrice" + }, + "as": { + "description": "Ask size.", + "format": "uint32", + "type": "integer", + "x-go-name": "AskSize" + }, + "ax": { + "description": "Ask exchange.", + "type": "string", + "x-go-name": "AskExchange" + }, + "bp": { + "description": "Bid price.", + "format": "double", + "type": "number", + "x-go-name": "BidPrice" + }, + "bs": { + "description": "Bid size.", + "format": "uint32", + "type": "integer", + "x-go-name": "BidSize" + }, + "bx": { + "description": "Bid exchange.", + "type": "string", + "x-go-name": "BidExchange" + }, + "c": { + "description": "Quote condition.", + "type": "string", + "x-go-name": "Condition" + }, + "t": { + "$ref": "#/components/schemas/timestamp" + } + }, + "required": [ + "t", + "bx", + "bp", + "bs", + "ap", + "as", + "ax", + "c" + ], + "type": "object" + }, + "option_snapshot": { + "description": "A snapshot provides the latest trade and latest quote.", + "properties": { + "dailyBar": { + "$ref": "#/components/schemas/option_bar" + }, + "greeks": { + "$ref": "#/components/schemas/option_greeks" + }, + "impliedVolatility": { + "description": "Implied volatility calculated using the Black-Scholes model.", + "format": "double", + "type": "number" + }, + "latestQuote": { + "$ref": "#/components/schemas/option_quote" + }, + "latestTrade": { + "$ref": "#/components/schemas/option_trade" + }, + "minuteBar": { + "$ref": "#/components/schemas/option_bar" + }, + "prevDailyBar": { + "$ref": "#/components/schemas/option_bar" + } + }, + "type": "object" + }, + "option_snapshots_resp": { + "properties": { + "next_page_token": { + "$ref": "#/components/schemas/next_page_token" + }, + "snapshots": { + "additionalProperties": { + "$ref": "#/components/schemas/option_snapshot" + }, + "type": "object" + } + }, + "required": [ + "snapshots", + "next_page_token" + ], + "type": "object" + }, + "option_trade": { + "description": "An option trade.", + "example": { + "c": "I", + "p": 0.37, + "s": 1, + "t": "2024-01-18T15:03:44.56339456Z", + "x": "B" + }, + "properties": { + "c": { + "description": "Trade condition.", + "type": "string", + "x-go-name": "Condition" + }, + "p": { + "description": "Trade price.", + "format": "double", + "type": "number", + "x-go-name": "Price" + }, + "s": { + "description": "Trade size.", + "format": "uint32", + "type": "integer", + "x-go-name": "Size" + }, + "t": { + "$ref": "#/components/schemas/timestamp" + }, + "x": { + "type": "string", + "x-go-name": "Exchange" + } + }, + "required": [ + "t", + "x", + "p", + "s", + "c" + ], + "type": "object" + }, + "option_trades_resp": { + "properties": { + "currency": { + "type": "string" + }, + "next_page_token": { + "$ref": "#/components/schemas/next_page_token" + }, + "trades": { + "additionalProperties": { + "items": { + "$ref": "#/components/schemas/option_trade" + }, + "type": "array" + }, + "type": "object" + } + }, + "required": [ + "trades", + "next_page_token" + ], + "type": "object" + }, + "partial_call": { + "description": "Partial call.", + "properties": { + "currency": { + "$ref": "#/components/schemas/currency" + }, + "cusip": { + "type": "string" + }, + "dividend_rate": { + "format": "double", + "type": "number" + }, + "id": { + "$ref": "#/components/schemas/ca_id" + }, + "isin": { + "$ref": "#/components/schemas/isin" + }, + "lottery_date": { + "format": "date", + "type": "string" + }, + "lottery_type": { + "description": "The type of lottery for the partial call.", + "enum": [ + "original", + "supplemental" + ], + "type": "string" + }, + "payable_date": { + "$ref": "#/components/schemas/payable_date" + }, + "price": { + "format": "double", + "type": "number" + }, + "process_date": { + "$ref": "#/components/schemas/process_date" + }, + "record_date": { + "$ref": "#/components/schemas/record_date" + }, + "results_publication_date": { + "format": "date", + "type": "string" + }, + "symbol": { + "type": "string" + } + }, + "required": [ + "id", + "symbol", + "process_date" + ], + "type": "object" + }, + "payable_date": { + "description": "The date when the corporate action benefit is paid or distributed.", + "format": "date", + "type": "string" + }, + "process_date": { + "description": "The date when the corporate action is processed by Alpaca.", + "format": "date", + "type": "string" + }, + "record_date": { + "description": "The date shareholders must own shares to receive the benefit.", + "format": "date", + "type": "string" + }, + "redemption": { + "description": "Redemption.", + "example": { + "cusip": "687305102", + "id": "395da031-0e57-4918-a6fb-64a7c713aca4", + "payable_date": "2023-06-13", + "process_date": "2023-06-13", + "rate": 0.141134, + "symbol": "ORPHY" + }, + "properties": { + "currency": { + "$ref": "#/components/schemas/currency" + }, + "cusip": { + "type": "string" + }, + "id": { + "$ref": "#/components/schemas/ca_id" + }, + "isin": { + "$ref": "#/components/schemas/isin" + }, + "payable_date": { + "$ref": "#/components/schemas/payable_date" + }, + "process_date": { + "$ref": "#/components/schemas/process_date" + }, + "rate": { + "format": "double", + "type": "number" + }, + "symbol": { + "type": "string" + } + }, + "required": [ + "id", + "symbol", + "cusip", + "rate", + "process_date" + ], + "type": "object" + }, + "region": { + "default": "us", + "description": "The region to filter corporate actions by.\n\n- `us`: only US corporate actions\n- `non_us`: only non-US corporate actions\n- `all`: both US and non-US corporate actions\n", + "enum": [ + "us", + "non_us", + "all" + ], + "type": "string", + "x-go-name": "Region" + }, + "reorganization": { + "description": "Reorganization (cash and/or multiple stock allocations).", + "properties": { + "cash_rate": { + "format": "double", + "type": "number" + }, + "currency": { + "$ref": "#/components/schemas/currency" + }, + "cusip": { + "type": "string" + }, + "effective_date": { + "$ref": "#/components/schemas/effective_date" + }, + "id": { + "$ref": "#/components/schemas/ca_id" + }, + "isin": { + "type": "string" + }, + "payable_date": { + "$ref": "#/components/schemas/payable_date" + }, + "process_date": { + "$ref": "#/components/schemas/process_date" + }, + "stock_movements": { + "items": { + "$ref": "#/components/schemas/reorganization_stock_movement" + }, + "type": "array" + }, + "symbol": { + "type": "string" + } + }, + "required": [ + "id", + "symbol", + "cusip", + "process_date", + "effective_date" + ], + "type": "object" + }, + "reorganization_stock_movement": { + "description": "A stock allocation leg in a reorganization.", + "properties": { + "cusip": { + "type": "string" + }, + "isin": { + "type": "string" + }, + "new_rate": { + "format": "double", + "type": "number" + }, + "source_rate": { + "format": "double", + "type": "number" + }, + "symbol": { + "type": "string" + } + }, + "required": [ + "symbol", + "cusip", + "new_rate", + "source_rate" + ], + "type": "object" + }, + "reverse_split": { + "description": "Reverse split.", + "example": { + "ex_date": "2023-08-24", + "id": "913de862-c02c-46dc-a89c-fc8779a50d30", + "new_cusip": "60879E200", + "new_rate": 1, + "old_cusip": "60879E101", + "old_rate": 50, + "process_date": "2023-08-24", + "record_date": "2023-08-24", + "symbol": "MNTS" + }, + "properties": { + "currency": { + "$ref": "#/components/schemas/currency" + }, + "ex_date": { + "$ref": "#/components/schemas/ex_date" + }, + "id": { + "$ref": "#/components/schemas/ca_id" + }, + "new_cusip": { + "type": "string" + }, + "new_isin": { + "$ref": "#/components/schemas/isin" + }, + "new_rate": { + "format": "double", + "type": "number" + }, + "old_cusip": { + "type": "string" + }, + "old_isin": { + "$ref": "#/components/schemas/isin" + }, + "old_rate": { + "format": "double", + "type": "number" + }, + "payable_date": { + "$ref": "#/components/schemas/payable_date" + }, + "process_date": { + "$ref": "#/components/schemas/process_date" + }, + "record_date": { + "$ref": "#/components/schemas/record_date" + }, + "symbol": { + "type": "string" + } + }, + "required": [ + "id", + "symbol", + "old_cusip", + "new_cusip", + "new_rate", + "old_rate", + "process_date", + "ex_date" + ], + "type": "object" + }, + "rights_distribution": { + "description": "Rights distribution.", + "example": { + "ex_date": "2024-04-17", + "expiration_date": "2024-05-14", + "id": "69794cfd-0adc-4e11-9211-9210a9cf8932", + "new_cusip": "454089111", + "new_symbol": "IFN.RTWI", + "payable_date": "2024-04-19", + "process_date": "2024-04-19", + "rate": 1, + "record_date": "2024-04-18", + "source_cusip": "454089103", + "source_symbol": "IFN" + }, + "properties": { + "currency": { + "$ref": "#/components/schemas/currency" + }, + "ex_date": { + "$ref": "#/components/schemas/ex_date" + }, + "expiration_date": { + "$ref": "#/components/schemas/expiration_date" + }, + "id": { + "$ref": "#/components/schemas/ca_id" + }, + "new_cusip": { + "type": "string" + }, + "new_isin": { + "$ref": "#/components/schemas/isin" + }, + "new_symbol": { + "type": "string" + }, + "payable_date": { + "$ref": "#/components/schemas/payable_date" + }, + "process_date": { + "$ref": "#/components/schemas/process_date" + }, + "rate": { + "format": "double", + "type": "number" + }, + "record_date": { + "$ref": "#/components/schemas/record_date" + }, + "source_cusip": { + "type": "string" + }, + "source_isin": { + "$ref": "#/components/schemas/isin" + }, + "source_symbol": { + "type": "string" + } + }, + "required": [ + "id", + "source_symbol", + "source_cusip", + "new_symbol", + "new_cusip", + "rate", + "process_date", + "ex_date", + "payable_date" + ], + "type": "object" + }, + "sort": { + "default": "asc", + "description": "Sort data in ascending or descending order.", + "enum": [ + "asc", + "desc" + ], + "type": "string", + "x-go-name": "TypeSort" + }, + "spin_off": { + "description": "Spin-off.", + "example": { + "ex_date": "2023-08-15", + "id": "82e602f6-35bc-4651-a5dd-f6d88ff37c55", + "new_cusip": "85237B101", + "new_rate": 1, + "new_symbol": "SRM", + "process_date": "2023-08-15", + "record_date": "2023-08-15", + "source_cusip": "48208F105", + "source_rate": 19.35, + "source_symbol": "JUPW" + }, + "properties": { + "currency": { + "$ref": "#/components/schemas/currency" + }, + "due_bill_redemption_date": { + "$ref": "#/components/schemas/due_bill_redemption_date" + }, + "ex_date": { + "$ref": "#/components/schemas/ex_date" + }, + "id": { + "$ref": "#/components/schemas/ca_id" + }, + "new_cusip": { + "type": "string" + }, + "new_isin": { + "$ref": "#/components/schemas/isin" + }, + "new_rate": { + "format": "double", + "type": "number" + }, + "new_symbol": { + "type": "string" + }, + "payable_date": { + "$ref": "#/components/schemas/payable_date" + }, + "process_date": { + "$ref": "#/components/schemas/process_date" + }, + "record_date": { + "$ref": "#/components/schemas/record_date" + }, + "source_cusip": { + "type": "string" + }, + "source_isin": { + "$ref": "#/components/schemas/isin" + }, + "source_rate": { + "format": "double", + "type": "number" + }, + "source_symbol": { + "type": "string" + } + }, + "required": [ + "id", + "source_symbol", + "source_cusip", + "source_rate", + "new_symbol", + "new_cusip", + "new_rate", + "process_date", + "ex_date" + ], + "type": "object" + }, + "stock_and_cash_merger": { + "description": "Stock and cash merger.", + "example": { + "acquiree_cusip": "561409103", + "acquiree_rate": 1, + "acquiree_symbol": "MLVF", + "acquirer_cusip": "31931U102", + "acquirer_rate": 0.7733, + "acquirer_symbol": "FRBA", + "cash_rate": 7.8, + "effective_date": "2023-07-18", + "id": "e5248356-2c06-42cf-aeb9-1595bd616cdb", + "payable_date": "2023-07-18", + "process_date": "2023-07-18" + }, + "properties": { + "acquiree_cusip": { + "type": "string" + }, + "acquiree_isin": { + "$ref": "#/components/schemas/isin" + }, + "acquiree_rate": { + "format": "double", + "type": "number" + }, + "acquiree_symbol": { + "type": "string" + }, + "acquirer_cusip": { + "type": "string" + }, + "acquirer_isin": { + "$ref": "#/components/schemas/isin" + }, + "acquirer_rate": { + "format": "double", + "type": "number" + }, + "acquirer_symbol": { + "type": "string" + }, + "cash_rate": { + "format": "double", + "type": "number" + }, + "currency": { + "$ref": "#/components/schemas/currency" + }, + "effective_date": { + "$ref": "#/components/schemas/effective_date" + }, + "id": { + "$ref": "#/components/schemas/ca_id" + }, + "payable_date": { + "$ref": "#/components/schemas/payable_date" + }, + "process_date": { + "$ref": "#/components/schemas/process_date" + } + }, + "required": [ + "id", + "acquirer_symbol", + "acquirer_cusip", + "acquirer_rate", + "acquiree_symbol", + "acquiree_cusip", + "acquiree_rate", + "cash_rate", + "process_date", + "effective_date" + ], + "type": "object" + }, + "stock_auction": { + "description": "An auction\n", + "example": { + "c": "O", + "p": 135, + "t": "2022-10-13T13:30:01.688322951Z", + "x": "Q" + }, + "properties": { + "c": { + "description": "The condition flag indicating that this is an auction. See `v2/stocks/meta/conditions/trade` for more details.\n", + "type": "string", + "x-go-name": "Condition" + }, + "p": { + "description": "Auction price.", + "format": "double", + "type": "number", + "x-go-name": "Price" + }, + "s": { + "description": "Auction trade size.", + "format": "int64", + "type": "integer", + "x-go-name": "Size" + }, + "t": { + "$ref": "#/components/schemas/timestamp" + }, + "x": { + "description": "Exchange code. See `v2/stocks/meta/exchanges` for more details.", + "type": "string" + } + }, + "required": [ + "t", + "x", + "p", + "c" + ], + "type": "object" + }, + "stock_auction_feed": { + "default": "sip", + "type": "string" + }, + "stock_auctions_resp": { + "properties": { + "auctions": { + "additionalProperties": { + "items": { + "$ref": "#/components/schemas/stock_daily_auctions" + }, + "type": "array" + }, + "type": "object" + }, + "currency": { + "type": "string" + }, + "next_page_token": { + "$ref": "#/components/schemas/next_page_token" + } + }, + "required": [ + "auctions", + "next_page_token" + ], + "type": "object" + }, + "stock_auctions_resp_single": { + "properties": { + "auctions": { + "items": { + "$ref": "#/components/schemas/stock_daily_auctions" + }, + "type": "array" + }, + "currency": { + "type": "string" + }, + "next_page_token": { + "$ref": "#/components/schemas/next_page_token" + }, + "symbol": { + "type": "string" + } + }, + "required": [ + "auctions", + "next_page_token", + "symbol" + ], + "type": "object" + }, + "stock_bar": { + "description": "OHLC aggregate of all the trades in a given interval.\n", + "example": { + "c": 178.08, + "h": 178.34, + "l": 177.76, + "n": 1727, + "o": 178.26, + "t": "2022-01-03T09:00:00Z", + "v": 60937, + "vw": 177.954244 + }, + "properties": { + "c": { + "description": "Closing price.", + "format": "double", + "type": "number", + "x-go-name": "Close" + }, + "h": { + "description": "High price.", + "format": "double", + "type": "number", + "x-go-name": "High" + }, + "l": { + "description": "Low price.", + "format": "double", + "type": "number", + "x-go-name": "Low" + }, + "n": { + "description": "Trade count in the bar.", + "format": "int64", + "type": "integer", + "x-go-name": "TradeCount" + }, + "o": { + "description": "Opening price.", + "format": "double", + "type": "number", + "x-go-name": "Open" + }, + "t": { + "$ref": "#/components/schemas/timestamp" + }, + "v": { + "description": "Bar volume.", + "format": "int64", + "type": "integer", + "x-go-name": "Volume" + }, + "vw": { + "description": "Volume weighted average price.", + "format": "double", + "type": "number", + "x-go-name": "VWAP" + } + }, + "required": [ + "t", + "o", + "h", + "l", + "c", + "v", + "n", + "vw" + ], + "type": "object" + }, + "stock_bars_resp": { + "properties": { + "bars": { + "additionalProperties": { + "items": { + "$ref": "#/components/schemas/stock_bar" + }, + "type": "array" + }, + "type": "object" + }, + "currency": { + "type": "string" + }, + "next_page_token": { + "$ref": "#/components/schemas/next_page_token" + } + }, + "required": [ + "bars", + "next_page_token" + ], + "type": "object" + }, + "stock_bars_resp_single": { + "properties": { + "bars": { + "items": { + "$ref": "#/components/schemas/stock_bar" + }, + "type": "array" + }, + "currency": { + "type": "string" + }, + "next_page_token": { + "$ref": "#/components/schemas/next_page_token" + }, + "symbol": { + "type": "string" + } + }, + "required": [ + "bars", + "next_page_token", + "symbol" + ], + "type": "object" + }, + "stock_conditions": { + "additionalProperties": { + "type": "string" + }, + "example": { + "@": "Regular Sale", + "A": "Acquisition", + "B": "Bunched Trade" + }, + "type": "object" + }, + "stock_daily_auctions": { + "description": "Opening and closing auction prices for a given day.\n", + "properties": { + "c": { + "description": "Closing auctions. Every price / exchange / condition triplet is only shown once, with its earliest timestamp.", + "items": { + "$ref": "#/components/schemas/stock_auction" + }, + "type": "array", + "x-go-name": "Closes" + }, + "d": { + "$ref": "#/components/schemas/date" + }, + "o": { + "description": "Opening auctions.", + "items": { + "$ref": "#/components/schemas/stock_auction" + }, + "type": "array", + "x-go-name": "Opens" + } + }, + "required": [ + "d", + "o", + "c" + ], + "type": "object" + }, + "stock_dividend": { + "description": "Stock dividend.", + "example": { + "cusip": "605015106", + "ex_date": "2023-05-19", + "id": "3ae94c30-2d37-473a-bf29-5f7b4ab6d3ca", + "payable_date": "2023-05-05", + "process_date": "2023-05-19", + "rate": 0.05, + "record_date": "2023-05-22", + "symbol": "MSBC" + }, + "properties": { + "currency": { + "$ref": "#/components/schemas/currency" + }, + "cusip": { + "type": "string" + }, + "ex_date": { + "$ref": "#/components/schemas/ex_date" + }, + "id": { + "$ref": "#/components/schemas/ca_id" + }, + "isin": { + "$ref": "#/components/schemas/isin" + }, + "payable_date": { + "$ref": "#/components/schemas/payable_date" + }, + "process_date": { + "$ref": "#/components/schemas/process_date" + }, + "rate": { + "format": "double", + "type": "number" + }, + "record_date": { + "$ref": "#/components/schemas/record_date" + }, + "symbol": { + "type": "string" + } + }, + "required": [ + "id", + "symbol", + "cusip", + "rate", + "process_date", + "ex_date" + ], + "type": "object" + }, + "stock_exchanges": { + "additionalProperties": { + "type": "string" + }, + "example": { + "N": "New York Stock Exchange", + "V": "IEX" + }, + "type": "object" + }, + "stock_historical_feed": { + "default": "sip", + "enum": [ + "iex", + "otc", + "sip", + "boats" + ], + "type": "string" + }, + "stock_latest_bars_resp": { + "properties": { + "bars": { + "additionalProperties": { + "$ref": "#/components/schemas/stock_bar" + }, + "type": "object" + }, + "currency": { + "type": "string" + } + }, + "required": [ + "bars" + ], + "type": "object" + }, + "stock_latest_bars_resp_single": { + "properties": { + "bar": { + "$ref": "#/components/schemas/stock_bar" + }, + "currency": { + "type": "string" + }, + "symbol": { + "type": "string" + } + }, + "required": [ + "bar", + "symbol" + ], + "type": "object" + }, + "stock_latest_feed": { + "enum": [ + "delayed_sip", + "iex", + "otc", + "sip", + "boats", + "overnight" + ], + "type": "string" + }, + "stock_latest_quotes_resp": { + "properties": { + "currency": { + "type": "string" + }, + "quotes": { + "additionalProperties": { + "$ref": "#/components/schemas/stock_quote" + }, + "type": "object" + } + }, + "required": [ + "quotes" + ], + "type": "object" + }, + "stock_latest_quotes_resp_single": { + "properties": { + "currency": { + "type": "string" + }, + "quote": { + "$ref": "#/components/schemas/stock_quote" + }, + "symbol": { + "type": "string" + } + }, + "required": [ + "quote", + "symbol" + ], + "type": "object" + }, + "stock_latest_trades_resp": { + "properties": { + "currency": { + "type": "string" + }, + "trades": { + "additionalProperties": { + "$ref": "#/components/schemas/stock_trade" + }, + "type": "object" + } + }, + "required": [ + "trades" + ], + "type": "object" + }, + "stock_latest_trades_resp_single": { + "properties": { + "currency": { + "type": "string" + }, + "symbol": { + "type": "string" + }, + "trade": { + "$ref": "#/components/schemas/stock_trade" + } + }, + "required": [ + "trade", + "symbol" + ], + "type": "object" + }, + "stock_merger": { + "description": "Stock merger.", + "example": { + "acquiree_cusip": "53223X107", + "acquiree_rate": 1, + "acquiree_symbol": "LSI", + "acquirer_cusip": "30225T102", + "acquirer_rate": 0.895, + "acquirer_symbol": "EXR", + "effective_date": "2023-07-20", + "id": "728f8cb2-a00e-4bc7-ad14-d15fe82bbcff", + "payable_date": "2023-07-20", + "process_date": "2023-07-20" + }, + "properties": { + "acquiree_cusip": { + "type": "string" + }, + "acquiree_isin": { + "$ref": "#/components/schemas/isin" + }, + "acquiree_rate": { + "format": "double", + "type": "number" + }, + "acquiree_symbol": { + "type": "string" + }, + "acquirer_cusip": { + "type": "string" + }, + "acquirer_isin": { + "$ref": "#/components/schemas/isin" + }, + "acquirer_rate": { + "format": "double", + "type": "number" + }, + "acquirer_symbol": { + "type": "string" + }, + "currency": { + "$ref": "#/components/schemas/currency" + }, + "effective_date": { + "$ref": "#/components/schemas/effective_date" + }, + "id": { + "$ref": "#/components/schemas/ca_id" + }, + "payable_date": { + "$ref": "#/components/schemas/payable_date" + }, + "process_date": { + "$ref": "#/components/schemas/process_date" + } + }, + "required": [ + "id", + "acquirer_symbol", + "acquirer_cusip", + "acquirer_rate", + "acquiree_symbol", + "acquiree_cusip", + "acquiree_rate", + "process_date", + "effective_date" + ], + "type": "object" + }, + "stock_quote": { + "description": "The best bid and ask information for a given security.", + "example": { + "ap": 387.7, + "as": 1, + "ax": "C", + "bp": 387.67, + "bs": 1, + "bx": "N", + "c": [ + "R" + ], + "t": "2021-02-06T13:35:08.946977536Z", + "z": "C" + }, + "properties": { + "ap": { + "description": "Ask price. 0 means the security has no active ask.", + "format": "double", + "type": "number", + "x-go-name": "AskPrice" + }, + "as": { + "description": "Ask size in shares (round lots prior to November 3, 2025).", + "format": "uint32", + "type": "integer", + "x-go-name": "AskSize" + }, + "ax": { + "description": "Ask exchange. See `v2/stocks/meta/exchanges` for more details.", + "type": "string", + "x-go-name": "AskExchange" + }, + "bp": { + "description": "Bid price. 0 means the security has no active bid.", + "format": "double", + "type": "number", + "x-go-name": "BidPrice" + }, + "bs": { + "description": "Bid size in shares (round lots prior to November 3, 2025).", + "format": "uint32", + "type": "integer", + "x-go-name": "BidSize" + }, + "bx": { + "description": "Bid exchange. See `v2/stocks/meta/exchanges` for more details.", + "type": "string", + "x-go-name": "BidExchange" + }, + "c": { + "description": "Condition flags. See `v2/stocks/meta/conditions/quote` for more details. If the array contains one flag, it applies to both the bid and ask. If the array contains two flags, the first one applies to the bid and the second one to the ask.\n", + "items": { + "type": "string" + }, + "type": "array", + "x-go-name": "Conditions" + }, + "t": { + "$ref": "#/components/schemas/timestamp" + }, + "z": { + "$ref": "#/components/schemas/stock_tape" + } + }, + "required": [ + "t", + "bx", + "bp", + "bs", + "ap", + "as", + "ax", + "c", + "z" + ], + "type": "object" + }, + "stock_quotes_resp": { + "properties": { + "currency": { + "type": "string" + }, + "next_page_token": { + "$ref": "#/components/schemas/next_page_token" + }, + "quotes": { + "additionalProperties": { + "items": { + "$ref": "#/components/schemas/stock_quote" + }, + "type": "array" + }, + "type": "object" + } + }, + "required": [ + "quotes", + "next_page_token" + ], + "type": "object" + }, + "stock_quotes_resp_single": { + "properties": { + "currency": { + "type": "string" + }, + "next_page_token": { + "$ref": "#/components/schemas/next_page_token" + }, + "quotes": { + "items": { + "$ref": "#/components/schemas/stock_quote" + }, + "type": "array" + }, + "symbol": { + "type": "string" + } + }, + "required": [ + "quotes", + "next_page_token", + "symbol" + ], + "type": "object" + }, + "stock_snapshot": { + "description": "A snapshot provides the latest trade, latest quote, latest minute bar, current daily bar and previous daily bar.\n", + "properties": { + "dailyBar": { + "$ref": "#/components/schemas/stock_bar" + }, + "latestQuote": { + "$ref": "#/components/schemas/stock_quote" + }, + "latestTrade": { + "$ref": "#/components/schemas/stock_trade" + }, + "minuteBar": { + "$ref": "#/components/schemas/stock_bar" + }, + "prevDailyBar": { + "$ref": "#/components/schemas/stock_bar" + } + }, + "type": "object" + }, + "stock_snapshots_resp": { + "additionalProperties": { + "$ref": "#/components/schemas/stock_snapshot" + }, + "type": "object" + }, + "stock_snapshots_resp_single": { + "allOf": [ + { + "properties": { + "currency": { + "type": "string" + }, + "symbol": { + "type": "string" + } + }, + "type": "object" + }, + { + "$ref": "#/components/schemas/stock_snapshot" + } + ] + }, + "stock_tape": { + "description": "- A: New York Stock Exchange\n- B: NYSE Arca, Bats, IEX and other regional exchanges\n- C: NASDAQ\n- N: Overnight\n- O: OTC\n", + "enum": [ + "A", + "B", + "C", + "N", + "O" + ], + "type": "string", + "x-go-name": "Tape" + }, + "stock_trade": { + "description": "A stock trade.", + "example": { + "c": [ + "@", + "T" + ], + "i": 1, + "p": 178.26, + "s": 246, + "t": "2022-01-03T09:00:00.086175744Z", + "x": "P", + "z": "C" + }, + "properties": { + "c": { + "description": "Condition flags. See `v2/stocks/meta/conditions/trade` for more details.", + "items": { + "type": "string" + }, + "type": "array", + "x-go-name": "Conditions" + }, + "i": { + "description": "Trade ID sent by the exchange.", + "format": "uint64", + "type": "integer", + "x-go-name": "ID" + }, + "p": { + "description": "Trade price.", + "format": "double", + "type": "number", + "x-go-name": "Price" + }, + "s": { + "description": "Trade size.", + "format": "uint32", + "type": "integer", + "x-go-name": "Size" + }, + "t": { + "$ref": "#/components/schemas/timestamp" + }, + "u": { + "description": "Update to the trade. This field is optional, if it's missing, the trade is valid. Otherwise, it can have these values:\n - canceled: indicates that the trade has been canceled\n - incorrect: indicates that the trade has been corrected and the given trade is no longer valid\n - corrected: indicates that this trade is the correction of a previous (incorrect) trade\n", + "type": "string", + "x-go-name": "Update" + }, + "x": { + "description": "Exchange code. See `v2/stocks/meta/exchanges` for more details.", + "type": "string", + "x-go-name": "Exchange" + }, + "z": { + "$ref": "#/components/schemas/stock_tape" + } + }, + "required": [ + "t", + "i", + "x", + "p", + "s", + "c", + "z" + ], + "type": "object" + }, + "stock_trades_resp": { + "properties": { + "currency": { + "type": "string" + }, + "next_page_token": { + "$ref": "#/components/schemas/next_page_token" + }, + "trades": { + "additionalProperties": { + "items": { + "$ref": "#/components/schemas/stock_trade" + }, + "type": "array" + }, + "type": "object" + } + }, + "required": [ + "trades", + "next_page_token" + ], + "type": "object" + }, + "stock_trades_resp_single": { + "properties": { + "currency": { + "type": "string" + }, + "next_page_token": { + "$ref": "#/components/schemas/next_page_token" + }, + "symbol": { + "type": "string" + }, + "trades": { + "items": { + "$ref": "#/components/schemas/stock_trade" + }, + "type": "array" + } + }, + "required": [ + "trades", + "next_page_token", + "symbol" + ], + "type": "object" + }, + "timestamp": { + "description": "Timestamp in RFC-3339 format with nanosecond precision.", + "format": "date-time", + "type": "string", + "x-go-name": "Timestamp" + }, + "unit_split": { + "description": "Unit split.", + "example": { + "alternate_cusip": "G5391L110", + "alternate_rate": 0.3333, + "alternate_symbol": "LVROW", + "effective_date": "2023-03-01", + "id": "3e68e87e-ae95-4d68-91d1-715d52ef143a", + "new_cusip": "G5391L102", + "new_rate": 1, + "new_symbol": "LVRO", + "old_cusip": "G8990L119", + "old_rate": 1, + "old_symbol": "TPBAU", + "process_date": "2023-03-01" + }, + "properties": { + "alternate_cusip": { + "type": "string" + }, + "alternate_isin": { + "$ref": "#/components/schemas/isin" + }, + "alternate_rate": { + "format": "double", + "type": "number" + }, + "alternate_symbol": { + "type": "string" + }, + "currency": { + "$ref": "#/components/schemas/currency" + }, + "effective_date": { + "$ref": "#/components/schemas/effective_date" + }, + "id": { + "$ref": "#/components/schemas/ca_id" + }, + "new_cusip": { + "type": "string" + }, + "new_isin": { + "$ref": "#/components/schemas/isin" + }, + "new_rate": { + "format": "double", + "type": "number" + }, + "new_symbol": { + "type": "string" + }, + "old_cusip": { + "type": "string" + }, + "old_isin": { + "$ref": "#/components/schemas/isin" + }, + "old_rate": { + "format": "double", + "type": "number" + }, + "old_symbol": { + "type": "string" + }, + "payable_date": { + "$ref": "#/components/schemas/payable_date" + }, + "process_date": { + "$ref": "#/components/schemas/process_date" + } + }, + "required": [ + "id", + "old_symbol", + "old_cusip", + "old_rate", + "new_symbol", + "new_cusip", + "new_rate", + "alternate_symbol", + "alternate_cusip", + "alternate_rate", + "process_date", + "effective_date" + ], + "type": "object" + }, + "worthless_removal": { + "description": "Worthless removal.", + "example": { + "cusip": "078771300", + "id": "106c2149-ee04-4d2e-a943-98dbb4d21a3c", + "process_date": "2024-12-19", + "symbol": "BLPH" + }, + "properties": { + "currency": { + "$ref": "#/components/schemas/currency" + }, + "cusip": { + "type": "string" + }, + "id": { + "$ref": "#/components/schemas/ca_id" + }, + "isin": { + "$ref": "#/components/schemas/isin" + }, + "process_date": { + "$ref": "#/components/schemas/process_date" + }, + "symbol": { + "type": "string" + } + }, + "required": [ + "id", + "symbol", + "cusip", + "process_date" + ], + "type": "object" + } + }, + "securitySchemes": { + "apiKey": { + "in": "header", + "name": "APCA-API-KEY-ID", + "type": "apiKey" + }, + "apiSecret": { + "in": "header", + "name": "APCA-API-SECRET-KEY", + "type": "apiKey" + } + } + }, + "info": { + "contact": { + "email": "support@alpaca.markets", + "name": "Alpaca Support", + "url": "https://alpaca.markets/support" + }, + "description": "Access real-time and historical market data for US equities, options, crypto, and foreign exchange data through the Alpaca REST and WebSocket APIs. There are APIs for Stock Pricing, Option Pricing, Crypto Pricing, Forex, Logos, Fixed income, Corporate Actions, Screener, and News.\n", + "license": { + "name": "Creative Commons Attribution Share Alike 4.0 International", + "url": "https://spdx.org/licenses/CC-BY-SA-4.0.html" + }, + "termsOfService": "https://s3.amazonaws.com/files.alpaca.markets/disclosures/library/TermsAndConditions.pdf", + "title": "Market Data API", + "version": "1.1" + }, + "openapi": "3.0.0", + "paths": { + "/v1beta1/crypto-perps/{loc}/latest/bars": { + "get": { + "description": "The latest bars endpoint returns the latest bar data for the crypto perpetual futures symbols provided.\n", + "operationId": "CryptoPerpLatestBars", + "parameters": [ + { + "$ref": "#/components/parameters/crypto_perp_loc" + }, + { + "$ref": "#/components/parameters/crypto_perp_symbols" + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "examples": { + "bars": { + "value": { + "bars": { + "BTCUSDT.P": { + "c": 29003, + "h": 29003, + "l": 28999, + "n": 4, + "o": 28999, + "t": "2022-05-27T10:18:00Z", + "v": 0.01, + "vw": 29001 + } + } + } + } + }, + "schema": { + "$ref": "#/components/schemas/crypto_latest_bars_resp" + } + } + }, + "description": "OK", + "headers": { + "X-RateLimit-Limit": { + "$ref": "#/components/headers/ratelimit_limit" + }, + "X-RateLimit-Remaining": { + "$ref": "#/components/headers/ratelimit_remaining" + }, + "X-RateLimit-Reset": { + "$ref": "#/components/headers/ratelimit_reset" + } + } + }, + "400": { + "$ref": "#/components/responses/400" + }, + "401": { + "$ref": "#/components/responses/401" + }, + "403": { + "$ref": "#/components/responses/403" + }, + "429": { + "$ref": "#/components/responses/429" + }, + "500": { + "$ref": "#/components/responses/500" + } + }, + "security": [], + "summary": "Latest bars", + "tags": [ + "Crypto perpetual futures" + ] + } + }, + "/v1beta1/crypto-perps/{loc}/latest/orderbooks": { + "get": { + "description": "The latest orderbook endpoint returns the latest bid and ask orderbook for the crypto perpetual futures symbols provided.\n", + "operationId": "CryptoPerpLatestOrderbooks", + "parameters": [ + { + "$ref": "#/components/parameters/crypto_perp_loc" + }, + { + "$ref": "#/components/parameters/crypto_perp_symbols" + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "examples": { + "orderbooks": { + "value": { + "orderbooks": { + "BTCUSDT.P": { + "a": [ + { + "p": 20902, + "s": 0.0097 + }, + { + "p": 21444, + "s": 0 + } + ], + "b": [ + { + "p": 20846, + "s": 0.1902 + }, + { + "p": 20350, + "s": 0 + } + ], + "t": "2022-06-24T08:00:14.137774336Z" + } + } + } + } + }, + "schema": { + "$ref": "#/components/schemas/crypto_latest_orderbooks_resp" + } + } + }, + "description": "OK", + "headers": { + "X-RateLimit-Limit": { + "$ref": "#/components/headers/ratelimit_limit" + }, + "X-RateLimit-Remaining": { + "$ref": "#/components/headers/ratelimit_remaining" + }, + "X-RateLimit-Reset": { + "$ref": "#/components/headers/ratelimit_reset" + } + } + }, + "400": { + "$ref": "#/components/responses/400" + }, + "401": { + "$ref": "#/components/responses/401" + }, + "403": { + "$ref": "#/components/responses/403" + }, + "429": { + "$ref": "#/components/responses/429" + }, + "500": { + "$ref": "#/components/responses/500" + } + }, + "security": [], + "summary": "Latest orderbook", + "tags": [ + "Crypto perpetual futures" + ] + } + }, + "/v1beta1/crypto-perps/{loc}/latest/pricing": { + "get": { + "description": "The latest futures pricing endpoint returns the latest pricing data for the crypto perpetual futures symbols provided.\n", + "operationId": "CryptoPerpLatestFuturesPricing", + "parameters": [ + { + "$ref": "#/components/parameters/crypto_perp_loc" + }, + { + "$ref": "#/components/parameters/crypto_perp_symbols" + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "examples": { + "pricing": { + "value": { + "pricing": { + "BTCUSDT.P": { + "fr": 0.000565699, + "ft": "2022-05-27T10:18:00Z", + "ip": 50702.8, + "mp": 50652.3553, + "oi": 90.7367, + "t": "2022-05-27T10:18:00Z" + } + } + } + } + }, + "schema": { + "$ref": "#/components/schemas/crypto_perp_latest_futures_pricing_resp" + } + } + }, + "description": "OK", + "headers": { + "X-RateLimit-Limit": { + "$ref": "#/components/headers/ratelimit_limit" + }, + "X-RateLimit-Remaining": { + "$ref": "#/components/headers/ratelimit_remaining" + }, + "X-RateLimit-Reset": { + "$ref": "#/components/headers/ratelimit_reset" + } + } + }, + "400": { + "$ref": "#/components/responses/400" + }, + "401": { + "$ref": "#/components/responses/401" + }, + "403": { + "$ref": "#/components/responses/403" + }, + "429": { + "$ref": "#/components/responses/429" + }, + "500": { + "$ref": "#/components/responses/500" + } + }, + "security": [], + "summary": "Latest pricing", + "tags": [ + "Crypto perpetual futures" + ] + } + }, + "/v1beta1/crypto-perps/{loc}/latest/quotes": { + "get": { + "description": "The latest quotes endpoint returns the latest bid and ask prices for the crypto perpetual futures symbols provided.\n", + "operationId": "CryptoPerpLatestQuotes", + "parameters": [ + { + "$ref": "#/components/parameters/crypto_perp_loc" + }, + { + "$ref": "#/components/parameters/crypto_perp_symbols" + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "examples": { + "quotes": { + "value": { + "quotes": { + "BTCUSDT.P": { + "ap": 29059, + "as": 3.252, + "bp": 29058, + "bs": 0.3544, + "t": "2022-05-26T11:47:18.44347136Z" + }, + "ETHUSDT.P": { + "ap": 1817.7, + "as": 6.137, + "bp": 1817, + "bs": 4.76, + "t": "2022-05-26T11:47:18.499478272Z" + } + } + } + } + }, + "schema": { + "$ref": "#/components/schemas/crypto_latest_quotes_resp" + } + } + }, + "description": "OK", + "headers": { + "X-RateLimit-Limit": { + "$ref": "#/components/headers/ratelimit_limit" + }, + "X-RateLimit-Remaining": { + "$ref": "#/components/headers/ratelimit_remaining" + }, + "X-RateLimit-Reset": { + "$ref": "#/components/headers/ratelimit_reset" + } + } + }, + "400": { + "$ref": "#/components/responses/400" + }, + "401": { + "$ref": "#/components/responses/401" + }, + "403": { + "$ref": "#/components/responses/403" + }, + "429": { + "$ref": "#/components/responses/429" + }, + "500": { + "$ref": "#/components/responses/500" + } + }, + "security": [], + "summary": "Latest quotes", + "tags": [ + "Crypto perpetual futures" + ] + } + }, + "/v1beta1/crypto-perps/{loc}/latest/trades": { + "get": { + "description": "The latest trades endpoint returns the latest trade data for the crypto perpetual futures symbols provided.\n", + "operationId": "CryptoPerpLatestTrades", + "parameters": [ + { + "$ref": "#/components/parameters/crypto_perp_loc" + }, + { + "$ref": "#/components/parameters/crypto_perp_symbols" + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "examples": { + "trades": { + "value": { + "trades": { + "BTCUSDT.P": { + "i": 31455289, + "p": 29791, + "s": 0.0016, + "t": "2022-05-18T12:01:00.537052Z", + "tks": "S" + }, + "ETHUSDT.P": { + "i": 31455287, + "p": 2027.6, + "s": 0.06, + "t": "2022-05-18T12:01:00.363547Z", + "tks": "S" + } + } + } + } + }, + "schema": { + "$ref": "#/components/schemas/crypto_latest_trades_resp" + } + } + }, + "description": "OK", + "headers": { + "X-RateLimit-Limit": { + "$ref": "#/components/headers/ratelimit_limit" + }, + "X-RateLimit-Remaining": { + "$ref": "#/components/headers/ratelimit_remaining" + }, + "X-RateLimit-Reset": { + "$ref": "#/components/headers/ratelimit_reset" + } + } + }, + "400": { + "$ref": "#/components/responses/400" + }, + "401": { + "$ref": "#/components/responses/401" + }, + "403": { + "$ref": "#/components/responses/403" + }, + "429": { + "$ref": "#/components/responses/429" + }, + "500": { + "$ref": "#/components/responses/500" + } + }, + "security": [], + "summary": "Latest trades", + "tags": [ + "Crypto perpetual futures" + ] + } + }, + "/v1beta1/fixed_income/latest/prices": { + "get": { + "description": "This endpoint returns the latest prices for the given fixed income securities.\n", + "operationId": "FixedIncomeLatestPrices", + "parameters": [ + { + "$ref": "#/components/parameters/isins" + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "examples": { + "prices": { + "value": { + "prices": { + "US912797KJ59": { + "p": 99.6459, + "t": "2025-02-14T20:58:00.648Z", + "ytm": 4.249, + "ytw": 4.249 + }, + "US912797KS58": { + "p": 99.3193, + "t": "2025-02-14T20:58:00.648Z", + "ytm": 4.2245, + "ytw": 4.2245 + }, + "US912797LB15": { + "p": 98.9927, + "t": "2025-02-14T20:58:00.648Z", + "ytm": 4.2165, + "ytw": 4.2165 + } + } + } + } + }, + "schema": { + "$ref": "#/components/schemas/fixed_income_latest_prices_resp" + } + } + }, + "description": "OK", + "headers": { + "X-RateLimit-Limit": { + "$ref": "#/components/headers/ratelimit_limit" + }, + "X-RateLimit-Remaining": { + "$ref": "#/components/headers/ratelimit_remaining" + }, + "X-RateLimit-Reset": { + "$ref": "#/components/headers/ratelimit_reset" + } + } + }, + "400": { + "$ref": "#/components/responses/400" + }, + "401": { + "$ref": "#/components/responses/401" + }, + "403": { + "$ref": "#/components/responses/403" + }, + "429": { + "$ref": "#/components/responses/429" + }, + "500": { + "$ref": "#/components/responses/500" + } + }, + "summary": "Latest prices", + "tags": [ + "Fixed income" + ] + } + }, + "/v1beta1/fixed_income/latest/quotes": { + "get": { + "description": "This endpoint returns the latest quotes for the given fixed income securities.\n", + "operationId": "FixedIncomeLatestQuotes", + "parameters": [ + { + "description": "A comma-separated list of ISINs with a limit of 100.", + "example": "US912797SX61,US912810SK51", + "in": "query", + "name": "isins", + "required": true, + "schema": { + "type": "string" + } + }, + { + "$ref": "#/components/parameters/trade_size" + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "examples": { + "quotes": { + "value": { + "quotes": { + "US912797SX61": { + "ams": 1000, + "ap": 99.91958333, + "as": 1000000, + "aytm": 2.226923, + "aytw": 2.226923, + "bms": 1000, + "bp": 99.81091667, + "bs": 1000000, + "bytm": 5.236154, + "bytw": 5.236154, + "t": "2026-05-21T06:56:01.882466873Z" + }, + "US912810SK51": { + "ams": 1000, + "ap": 63.06436526, + "as": 1000000, + "aytm": 5.08819, + "aytw": 5.08819, + "bms": 1000, + "bp": 60.60009769, + "bs": 1000000, + "bytm": 5.338343, + "bytw": 5.338343, + "t": "2026-05-21T07:27:53.683424845Z" + } + } + } + } + }, + "schema": { + "$ref": "#/components/schemas/fixed_income_latest_quotes_resp" + } + } + }, + "description": "OK", + "headers": { + "X-RateLimit-Limit": { + "$ref": "#/components/headers/ratelimit_limit" + }, + "X-RateLimit-Remaining": { + "$ref": "#/components/headers/ratelimit_remaining" + }, + "X-RateLimit-Reset": { + "$ref": "#/components/headers/ratelimit_reset" + } + } + }, + "400": { + "$ref": "#/components/responses/400" + }, + "401": { + "$ref": "#/components/responses/401" + }, + "403": { + "$ref": "#/components/responses/403" + }, + "429": { + "$ref": "#/components/responses/429" + }, + "500": { + "$ref": "#/components/responses/500" + } + }, + "summary": "Latest quotes", + "tags": [ + "Fixed income" + ] + } + }, + "/v1beta1/forex/latest/rates": { + "get": { + "description": "Get the latest forex rates for the given currency pairs.\n", + "operationId": "LatestRates", + "parameters": [ + { + "$ref": "#/components/parameters/forex_currency_pairs" + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "examples": { + "USDJPY": { + "value": { + "rates": { + "USDJPY": { + "ap": 128.112, + "bp": 127.752, + "mp": 127.779, + "t": "2022-05-20T05:38:41.311530885Z" + } + } + } + } + }, + "schema": { + "$ref": "#/components/schemas/forex_latest_rates_resp" + } + } + }, + "description": "OK", + "headers": { + "X-RateLimit-Limit": { + "$ref": "#/components/headers/ratelimit_limit" + }, + "X-RateLimit-Remaining": { + "$ref": "#/components/headers/ratelimit_remaining" + }, + "X-RateLimit-Reset": { + "$ref": "#/components/headers/ratelimit_reset" + } + } + }, + "400": { + "$ref": "#/components/responses/400" + }, + "401": { + "$ref": "#/components/responses/401" + }, + "403": { + "$ref": "#/components/responses/403" + }, + "429": { + "$ref": "#/components/responses/429" + }, + "500": { + "$ref": "#/components/responses/500" + } + }, + "summary": "Latest rates for currency pairs", + "tags": [ + "Forex" + ] + } + }, + "/v1beta1/forex/rates": { + "get": { + "description": "Get historical forex rates for the given currency pairs in the given time interval and at the given timeframe (snapshot frequency).\n", + "operationId": "Rates", + "parameters": [ + { + "$ref": "#/components/parameters/forex_currency_pairs" + }, + { + "$ref": "#/components/parameters/forex_timeframe" + }, + { + "$ref": "#/components/parameters/start" + }, + { + "$ref": "#/components/parameters/end" + }, + { + "$ref": "#/components/parameters/limit" + }, + { + "$ref": "#/components/parameters/sort" + }, + { + "$ref": "#/components/parameters/page_token" + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "examples": { + "USDJPY": { + "value": { + "next_page_token": "VVNESlBZfDIwMjItMDEtMDNUMDA6MDM6MDBa", + "rates": { + "USDJPY": [ + { + "ap": 115.18, + "bp": 114.192, + "mp": 115.144, + "t": "2022-01-03T00:01:00Z" + }, + { + "ap": 115.185, + "bp": 114.189, + "mp": 115.138, + "t": "2022-01-03T00:02:00Z" + }, + { + "ap": 115.148, + "bp": 115.122, + "mp": 115.131, + "t": "2022-01-03T00:03:00Z" + } + ] + } + } + } + }, + "schema": { + "$ref": "#/components/schemas/forex_rates_resp" + } + } + }, + "description": "OK", + "headers": { + "X-RateLimit-Limit": { + "$ref": "#/components/headers/ratelimit_limit" + }, + "X-RateLimit-Remaining": { + "$ref": "#/components/headers/ratelimit_remaining" + }, + "X-RateLimit-Reset": { + "$ref": "#/components/headers/ratelimit_reset" + } + } + }, + "400": { + "$ref": "#/components/responses/400" + }, + "401": { + "$ref": "#/components/responses/401" + }, + "403": { + "$ref": "#/components/responses/403" + }, + "429": { + "$ref": "#/components/responses/429" + }, + "500": { + "$ref": "#/components/responses/500" + } + }, + "summary": "Historical rates for currency pairs", + "tags": [ + "Forex" + ] + } + }, + "/v1beta1/indices/latest/values": { + "get": { + "description": "Get the latest values for the given indices.", + "operationId": "IndexLatestValues", + "parameters": [ + { + "$ref": "#/components/parameters/index_symbols" + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "examples": { + "values": { + "value": { + "values": { + "SPX": { + "t": "2026-05-18T17:00:02.0256Z", + "v": 7383.46 + }, + "VIX": { + "t": "2026-05-18T17:00:46.354Z", + "v": 18.45 + } + } + } + } + }, + "schema": { + "$ref": "#/components/schemas/index_latest_values_resp" + } + } + }, + "description": "OK", + "headers": { + "X-RateLimit-Limit": { + "$ref": "#/components/headers/ratelimit_limit" + }, + "X-RateLimit-Remaining": { + "$ref": "#/components/headers/ratelimit_remaining" + }, + "X-RateLimit-Reset": { + "$ref": "#/components/headers/ratelimit_reset" + } + } + }, + "400": { + "$ref": "#/components/responses/400" + }, + "401": { + "$ref": "#/components/responses/401" + }, + "403": { + "$ref": "#/components/responses/403" + }, + "429": { + "$ref": "#/components/responses/429" + }, + "500": { + "$ref": "#/components/responses/500" + } + }, + "summary": "Latest values for indices", + "tags": [ + "Index" + ] + } + }, + "/v1beta1/indices/values": { + "get": { + "description": "Get historical values for the given indices in the given time interval.\n", + "operationId": "IndexValues", + "parameters": [ + { + "$ref": "#/components/parameters/index_symbols" + }, + { + "$ref": "#/components/parameters/start" + }, + { + "$ref": "#/components/parameters/end" + }, + { + "$ref": "#/components/parameters/limit" + }, + { + "$ref": "#/components/parameters/page_token" + }, + { + "$ref": "#/components/parameters/sort" + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "examples": { + "values": { + "value": { + "next_page_token": "U1BYfDIwMjYtMDUtMThUMTc6MDA6MDIuMDI1Nlo", + "values": { + "SPX": [ + { + "t": "2026-05-18T17:00:00.0302Z", + "v": 7384.41 + }, + { + "t": "2026-05-18T17:00:01.0918Z", + "v": 7384.07 + }, + { + "t": "2026-05-18T17:00:02.0256Z", + "v": 7383.46 + } + ], + "VIX": [ + { + "t": "2026-05-18T17:00:01.3355Z", + "v": 18.4 + }, + { + "t": "2026-05-18T17:00:16.3453Z", + "v": 18.41 + }, + { + "t": "2026-05-18T17:00:46.354Z", + "v": 18.45 + } + ] + } + } + } + }, + "schema": { + "$ref": "#/components/schemas/index_values_resp" + } + } + }, + "description": "OK", + "headers": { + "X-RateLimit-Limit": { + "$ref": "#/components/headers/ratelimit_limit" + }, + "X-RateLimit-Remaining": { + "$ref": "#/components/headers/ratelimit_remaining" + }, + "X-RateLimit-Reset": { + "$ref": "#/components/headers/ratelimit_reset" + } + } + }, + "400": { + "$ref": "#/components/responses/400" + }, + "401": { + "$ref": "#/components/responses/401" + }, + "403": { + "$ref": "#/components/responses/403" + }, + "429": { + "$ref": "#/components/responses/429" + }, + "500": { + "$ref": "#/components/responses/500" + } + }, + "summary": "Historical values for indices", + "tags": [ + "Index" + ] + } + }, + "/v1beta1/logos/{symbol}": { + "get": { + "description": "Get the image of the company logo for the given symbol.", + "operationId": "Logos", + "parameters": [ + { + "$ref": "#/components/parameters/symbol" + }, + { + "description": "If true, returns a placeholder image when no logo is available. Defaults to true.", + "in": "query", + "name": "placeholder", + "schema": { + "default": true, + "type": "boolean" + } + } + ], + "responses": { + "200": { + "content": { + "image/png": { + "schema": { + "format": "binary", + "type": "string" + } + } + }, + "description": "OK", + "headers": { + "X-RateLimit-Limit": { + "$ref": "#/components/headers/ratelimit_limit" + }, + "X-RateLimit-Remaining": { + "$ref": "#/components/headers/ratelimit_remaining" + }, + "X-RateLimit-Reset": { + "$ref": "#/components/headers/ratelimit_reset" + } + } + }, + "400": { + "$ref": "#/components/responses/400" + }, + "401": { + "$ref": "#/components/responses/401" + }, + "403": { + "$ref": "#/components/responses/403" + }, + "429": { + "$ref": "#/components/responses/429" + }, + "500": { + "$ref": "#/components/responses/500" + } + }, + "summary": "Logos", + "tags": [ + "Logos" + ] + } + }, + "/v1beta1/news": { + "get": { + "description": "Returns the latest news articles across stocks and crypto. By default, returns the latest 10 news articles.\n", + "operationId": "News", + "parameters": [ + { + "$ref": "#/components/parameters/start" + }, + { + "$ref": "#/components/parameters/end" + }, + { + "$ref": "#/components/parameters/news_sort" + }, + { + "description": "A comma-separated list of symbols for which to query news.", + "in": "query", + "name": "symbols", + "schema": { + "example": "AAPL,TSLA,BTCUSD", + "type": "string" + } + }, + { + "description": "Limit of news items to be returned for a result page.", + "example": 10, + "in": "query", + "name": "limit", + "schema": { + "maximum": 50, + "minimum": 1, + "type": "integer" + } + }, + { + "description": "Boolean indicator to include content for news articles (if available).", + "in": "query", + "name": "include_content", + "schema": { + "type": "boolean" + } + }, + { + "description": "Boolean indicator to exclude news articles that do not contain content.", + "in": "query", + "name": "exclude_contentless", + "schema": { + "type": "boolean" + } + }, + { + "$ref": "#/components/parameters/page_token" + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "examples": { + "news-response-example": { + "value": { + "news": [ + { + "author": "Charles Gross", + "content": "

This headline-only article is meant to show you why a stock is moving, the most difficult aspect of stock trading....

", + "created_at": "2021-12-31T11:08:42Z", + "headline": "Apple Leader in Phone Sales in China for Second Straight Month in November With 23.6% Share, According to Market Research Data", + "id": 24843171, + "images": [], + "source": "benzinga", + "summary": "This headline-only article is meant to show you why a stock is moving, the most difficult aspect of stock trading", + "symbols": [ + "AAPL" + ], + "updated_at": "2021-12-31T11:08:43Z", + "url": "https://www.benzinga.com/news/21/12/24843171/apple-leader-in-phone-sales-in-china-for-second-straight-month-in-november-with-23-6-share-according" + } + ], + "next_page_token": "MTY0MDk0ODkyMzAwMDAwMDAwMHwyNDg0MzE3MQ==" + } + } + }, + "schema": { + "$ref": "#/components/schemas/news_resp" + } + } + }, + "description": "OK" + }, + "400": { + "$ref": "#/components/responses/400" + }, + "401": { + "$ref": "#/components/responses/401" + }, + "403": { + "$ref": "#/components/responses/403" + }, + "429": { + "$ref": "#/components/responses/429" + }, + "500": { + "$ref": "#/components/responses/500" + } + }, + "summary": "News articles", + "tags": [ + "News" + ] + } + }, + "/v1beta1/options/bars": { + "get": { + "description": "The historical option bars API provides aggregates for a list of option symbols between the specified dates.\n\nThe returned results are sorted by symbol first, then by bar timestamp.\nThis means that you are likely to see only one symbol in your first response if there are enough bars for that symbol to hit the limit you requested.\n\nIn these situations, if you keep requesting again with the `next_page_token` from the previous response, you will eventually reach the other symbols if any bars were found for them.", + "operationId": "optionBars", + "parameters": [ + { + "$ref": "#/components/parameters/option_symbols" + }, + { + "$ref": "#/components/parameters/timeframe" + }, + { + "$ref": "#/components/parameters/start" + }, + { + "$ref": "#/components/parameters/end" + }, + { + "$ref": "#/components/parameters/limit" + }, + { + "$ref": "#/components/parameters/page_token" + }, + { + "$ref": "#/components/parameters/sort" + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "examples": { + "bars": { + "value": { + "bars": { + "AAPL240419P00140000": [ + { + "c": 0.34, + "h": 0.38, + "l": 0.34, + "n": 7, + "o": 0.38, + "t": "2024-01-18T05:00:00Z", + "v": 12, + "vw": 0.3525 + } + ] + }, + "next_page_token": "QUFQTHxNfDIwMjItMDEtMDNUMDk6MDA6MDAuMDAwMDAwMDAwWg==" + } + } + }, + "schema": { + "$ref": "#/components/schemas/option_bars_resp" + } + } + }, + "description": "OK", + "headers": { + "X-RateLimit-Limit": { + "$ref": "#/components/headers/ratelimit_limit" + }, + "X-RateLimit-Remaining": { + "$ref": "#/components/headers/ratelimit_remaining" + }, + "X-RateLimit-Reset": { + "$ref": "#/components/headers/ratelimit_reset" + } + } + }, + "400": { + "$ref": "#/components/responses/400" + }, + "401": { + "$ref": "#/components/responses/401" + }, + "403": { + "$ref": "#/components/responses/403" + }, + "429": { + "$ref": "#/components/responses/429" + }, + "500": { + "$ref": "#/components/responses/500" + } + }, + "summary": "Historical bars", + "tags": [ + "Option" + ] + } + }, + "/v1beta1/options/meta/conditions/{ticktype}": { + "get": { + "description": "Returns the mapping between the condition codes and names.", + "operationId": "OptionMetaConditions", + "parameters": [ + { + "description": "The type of ticks.", + "in": "path", + "name": "ticktype", + "required": true, + "schema": { + "enum": [ + "trade", + "quote" + ], + "type": "string" + } + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/option_conditions" + } + } + }, + "description": "OK", + "headers": { + "X-RateLimit-Limit": { + "$ref": "#/components/headers/ratelimit_limit" + }, + "X-RateLimit-Remaining": { + "$ref": "#/components/headers/ratelimit_remaining" + }, + "X-RateLimit-Reset": { + "$ref": "#/components/headers/ratelimit_reset" + } + } + }, + "400": { + "$ref": "#/components/responses/400" + }, + "401": { + "$ref": "#/components/responses/401" + }, + "403": { + "$ref": "#/components/responses/403" + }, + "429": { + "$ref": "#/components/responses/429" + }, + "500": { + "$ref": "#/components/responses/500" + } + }, + "summary": "Condition codes", + "tags": [ + "Option" + ] + } + }, + "/v1beta1/options/meta/exchanges": { + "get": { + "description": "Returns the mapping between the option exchange codes and the corresponding exchange names.", + "operationId": "OptionMetaExchanges", + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/option_exchanges" + } + } + }, + "description": "OK", + "headers": { + "X-RateLimit-Limit": { + "$ref": "#/components/headers/ratelimit_limit" + }, + "X-RateLimit-Remaining": { + "$ref": "#/components/headers/ratelimit_remaining" + }, + "X-RateLimit-Reset": { + "$ref": "#/components/headers/ratelimit_reset" + } + } + }, + "400": { + "$ref": "#/components/responses/400" + }, + "401": { + "$ref": "#/components/responses/401" + }, + "403": { + "$ref": "#/components/responses/403" + }, + "429": { + "$ref": "#/components/responses/429" + }, + "500": { + "$ref": "#/components/responses/500" + } + }, + "summary": "Exchange codes", + "tags": [ + "Option" + ] + } + }, + "/v1beta1/options/quotes/latest": { + "get": { + "description": "The latest multi-quotes endpoint provides the latest bid and ask prices for each given contract symbol.\n", + "operationId": "OptionLatestQuotes", + "parameters": [ + { + "$ref": "#/components/parameters/option_symbols" + }, + { + "$ref": "#/components/parameters/option_feed" + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "examples": { + "quotes": { + "value": { + "quotes": { + "AAPL240419P00140000": { + "ap": 0.16, + "as": 669, + "ax": "w", + "bp": 0.15, + "bs": 164, + "bx": "W", + "c": "A", + "t": "2024-02-28T15:30:28.046330624Z" + }, + "AAPL250321C00190000": { + "ap": 17, + "as": 622, + "ax": "X", + "bp": 16.75, + "bs": 368, + "bx": "X", + "c": " ", + "t": "2024-02-28T15:47:13.663636224Z" + } + } + } + } + }, + "schema": { + "$ref": "#/components/schemas/option_latest_quotes_resp" + } + } + }, + "description": "OK", + "headers": { + "X-RateLimit-Limit": { + "$ref": "#/components/headers/ratelimit_limit" + }, + "X-RateLimit-Remaining": { + "$ref": "#/components/headers/ratelimit_remaining" + }, + "X-RateLimit-Reset": { + "$ref": "#/components/headers/ratelimit_reset" + } + } + }, + "400": { + "$ref": "#/components/responses/400" + }, + "401": { + "$ref": "#/components/responses/401" + }, + "403": { + "$ref": "#/components/responses/403" + }, + "429": { + "$ref": "#/components/responses/429" + }, + "500": { + "$ref": "#/components/responses/500" + } + }, + "summary": "Latest quotes", + "tags": [ + "Option" + ] + } + }, + "/v1beta1/options/snapshots": { + "get": { + "description": "The snapshots endpoint provides the latest trade, latest quote and greeks for each given contract symbol.\n", + "operationId": "OptionSnapshots", + "parameters": [ + { + "$ref": "#/components/parameters/option_symbols" + }, + { + "$ref": "#/components/parameters/option_feed" + }, + { + "$ref": "#/components/parameters/option_updated_since" + }, + { + "$ref": "#/components/parameters/option_limit" + }, + { + "$ref": "#/components/parameters/page_token" + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "examples": { + "snapshots": { + "value": { + "next_page_token": "QUFQTDI0MDYyMTAwMDIyMHwx", + "snapshots": { + "AAPL240426C00162500": { + "greeks": { + "delta": 0.7521304109871954, + "gamma": 0.06241426404871288, + "rho": 0.009910739032549095, + "theta": -0.2847623059595503, + "vega": 0.047540520834498785 + }, + "impliedVolatility": 0.3372405712050441, + "latestQuote": { + "ap": 4.3, + "as": 91, + "ax": "B", + "bp": 4.15, + "bs": 16, + "bx": "C", + "c": "A", + "t": "2024-04-22T19:59:59.992734208Z" + }, + "latestTrade": { + "c": "I", + "p": 4.1, + "s": 1, + "t": "2024-04-22T19:57:32.589554432Z", + "x": "A" + } + } + } + } + } + }, + "schema": { + "$ref": "#/components/schemas/option_snapshots_resp" + } + } + }, + "description": "OK", + "headers": { + "X-RateLimit-Limit": { + "$ref": "#/components/headers/ratelimit_limit" + }, + "X-RateLimit-Remaining": { + "$ref": "#/components/headers/ratelimit_remaining" + }, + "X-RateLimit-Reset": { + "$ref": "#/components/headers/ratelimit_reset" + } + } + }, + "400": { + "$ref": "#/components/responses/400" + }, + "401": { + "$ref": "#/components/responses/401" + }, + "403": { + "$ref": "#/components/responses/403" + }, + "429": { + "$ref": "#/components/responses/429" + }, + "500": { + "$ref": "#/components/responses/500" + } + }, + "summary": "Snapshots", + "tags": [ + "Option" + ] + } + }, + "/v1beta1/options/snapshots/{underlying_symbol}": { + "get": { + "description": "The option chain endpoint provides the latest trade, latest quote, and greeks for each contract symbol of the underlying symbol.\n", + "operationId": "OptionChain", + "parameters": [ + { + "$ref": "#/components/parameters/option_underlying_symbol" + }, + { + "$ref": "#/components/parameters/option_feed" + }, + { + "$ref": "#/components/parameters/option_limit" + }, + { + "$ref": "#/components/parameters/option_updated_since" + }, + { + "$ref": "#/components/parameters/page_token" + }, + { + "description": "Filter contracts by the type (call or put).", + "in": "query", + "name": "type", + "schema": { + "enum": [ + "call", + "put" + ], + "type": "string" + } + }, + { + "description": "Filter contracts with strike price greater than or equal to the specified value.", + "in": "query", + "name": "strike_price_gte", + "schema": { + "format": "double", + "type": "number" + } + }, + { + "description": "Filter contracts with strike price less than or equal to the specified value.", + "in": "query", + "name": "strike_price_lte", + "schema": { + "format": "double", + "type": "number" + } + }, + { + "description": "Filter contracts by the exact expiration date (format: YYYY-MM-DD).", + "in": "query", + "name": "expiration_date", + "schema": { + "format": "date", + "type": "string" + } + }, + { + "description": "Filter contracts with expiration date greater than or equal to the specified date.", + "in": "query", + "name": "expiration_date_gte", + "schema": { + "format": "date", + "type": "string" + } + }, + { + "description": "Filter contracts with expiration date less than or equal to the specified date.", + "in": "query", + "name": "expiration_date_lte", + "schema": { + "format": "date", + "type": "string" + } + }, + { + "description": "Filter contracts by the root symbol.", + "example": "AAPL1", + "in": "query", + "name": "root_symbol", + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "examples": { + "snapshots": { + "value": { + "next_page_token": "QUFQTDI0MDYyMTAwMDIyMHwx", + "snapshots": { + "AAPL240426C00162500": { + "greeks": { + "delta": 0.7521304109871954, + "gamma": 0.06241426404871288, + "rho": 0.009910739032549095, + "theta": -0.2847623059595503, + "vega": 0.047540520834498785 + }, + "impliedVolatility": 0.3372405712050441, + "latestQuote": { + "ap": 4.3, + "as": 91, + "ax": "B", + "bp": 4.15, + "bs": 16, + "bx": "C", + "c": "A", + "t": "2024-04-22T19:59:59.992734208Z" + }, + "latestTrade": { + "c": "I", + "p": 4.1, + "s": 1, + "t": "2024-04-22T19:57:32.589554432Z", + "x": "A" + } + } + } + } + } + }, + "schema": { + "$ref": "#/components/schemas/option_snapshots_resp" + } + } + }, + "description": "OK", + "headers": { + "X-RateLimit-Limit": { + "$ref": "#/components/headers/ratelimit_limit" + }, + "X-RateLimit-Remaining": { + "$ref": "#/components/headers/ratelimit_remaining" + }, + "X-RateLimit-Reset": { + "$ref": "#/components/headers/ratelimit_reset" + } + } + }, + "400": { + "$ref": "#/components/responses/400" + }, + "401": { + "$ref": "#/components/responses/401" + }, + "403": { + "$ref": "#/components/responses/403" + }, + "429": { + "$ref": "#/components/responses/429" + }, + "500": { + "$ref": "#/components/responses/500" + } + }, + "summary": "Option chain", + "tags": [ + "Option" + ] + } + }, + "/v1beta1/options/trades": { + "get": { + "description": "The historical option trades API provides trade data for a list of contract symbols between the specified dates.\n\nThe returned results are sorted by symbol first then by trade timestamp.\nThis means that you are likely to see only one symbol in your first response if there are enough trades for that symbol to hit the limit you requested.\n\nIn these situations, if you keep requesting again with the `next_page_token` from the previous response, you will eventually reach the other symbols if any trades were found for them.", + "operationId": "OptionTrades", + "parameters": [ + { + "$ref": "#/components/parameters/option_symbols" + }, + { + "$ref": "#/components/parameters/start" + }, + { + "$ref": "#/components/parameters/end" + }, + { + "$ref": "#/components/parameters/limit" + }, + { + "$ref": "#/components/parameters/page_token" + }, + { + "$ref": "#/components/parameters/sort" + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "examples": { + "trades": { + "value": { + "next_page_token": "QUFQTHwyMDIyLTAxLTAzVDA5OjAwOjAwLjI0NDgzOTY4MFp8UHwwOTIyMzM3MjAzNjg1NDc3NTgxMA==", + "trades": { + "AAPL240112C00182500": [ + { + "c": "I", + "p": 0.37, + "s": 1, + "t": "2024-01-18T15:03:44.56339456Z", + "x": "B" + }, + { + "c": "g", + "p": 0.34, + "s": 1, + "t": "2024-01-18T16:02:38.994758144Z", + "x": "C" + } + ] + } + } + } + }, + "schema": { + "$ref": "#/components/schemas/option_trades_resp" + } + } + }, + "description": "OK", + "headers": { + "X-RateLimit-Limit": { + "$ref": "#/components/headers/ratelimit_limit" + }, + "X-RateLimit-Remaining": { + "$ref": "#/components/headers/ratelimit_remaining" + }, + "X-RateLimit-Reset": { + "$ref": "#/components/headers/ratelimit_reset" + } + } + }, + "400": { + "$ref": "#/components/responses/400" + }, + "401": { + "$ref": "#/components/responses/401" + }, + "403": { + "$ref": "#/components/responses/403" + }, + "429": { + "$ref": "#/components/responses/429" + }, + "500": { + "$ref": "#/components/responses/500" + } + }, + "summary": "Historical trades", + "tags": [ + "Option" + ] + } + }, + "/v1beta1/options/trades/latest": { + "get": { + "description": "The latest multi-trades endpoint provides the latest historical trade data for multiple given contract symbols.\n", + "operationId": "OptionLatestTrades", + "parameters": [ + { + "$ref": "#/components/parameters/option_symbols" + }, + { + "$ref": "#/components/parameters/option_feed" + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "examples": { + "trades": { + "value": { + "trades": { + "AAPL250321C00190000": { + "c": "e", + "p": 17.15, + "s": 900, + "t": "2024-02-28T15:26:12.728701696Z", + "x": "B" + } + } + } + } + }, + "schema": { + "$ref": "#/components/schemas/option_latest_trades_resp" + } + } + }, + "description": "OK", + "headers": { + "X-RateLimit-Limit": { + "$ref": "#/components/headers/ratelimit_limit" + }, + "X-RateLimit-Remaining": { + "$ref": "#/components/headers/ratelimit_remaining" + }, + "X-RateLimit-Reset": { + "$ref": "#/components/headers/ratelimit_reset" + } + } + }, + "400": { + "$ref": "#/components/responses/400" + }, + "401": { + "$ref": "#/components/responses/401" + }, + "403": { + "$ref": "#/components/responses/403" + }, + "429": { + "$ref": "#/components/responses/429" + }, + "500": { + "$ref": "#/components/responses/500" + } + }, + "summary": "Latest trades", + "tags": [ + "Option" + ] + } + }, + "/v1beta1/screener/{market_type}/movers": { + "get": { + "description": "Returns the top market movers (gainers and losers) based on real time SIP data.\nThe change for each symbol is calculated from the previous closing price and the latest closing price.\n\nFor stocks, the endpoint resets at market open. Until then, it shows the previous market day's movers.\nThe data is split-adjusted. Only tradable symbols in exchanges are included.\n\nFor crypto, the endpoint resets at midnight.", + "operationId": "Movers", + "parameters": [ + { + "$ref": "#/components/parameters/movers_market_type" + }, + { + "$ref": "#/components/parameters/movers_top" + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "examples": { + "movers": { + "value": { + "gainers": [ + { + "change": 2.46, + "percent_change": 145.56, + "price": 4.15, + "symbol": "AGRI" + }, + { + "change": 0.03, + "percent_change": 85.63, + "price": 0.0594, + "symbol": "GRCYW" + } + ], + "last_updated": "2022-03-10T17:53:30.088309839Z", + "losers": [ + { + "change": -0.26, + "percent_change": -63.07, + "price": 0.1502, + "symbol": "MTACW" + }, + { + "change": -3.61, + "percent_change": -51.21, + "price": 3.435, + "symbol": "TIG" + } + ], + "market_type": "stocks" + } + } + }, + "schema": { + "$ref": "#/components/schemas/movers_resp" + } + } + }, + "description": "OK", + "headers": { + "X-RateLimit-Limit": { + "$ref": "#/components/headers/ratelimit_limit" + }, + "X-RateLimit-Remaining": { + "$ref": "#/components/headers/ratelimit_remaining" + }, + "X-RateLimit-Reset": { + "$ref": "#/components/headers/ratelimit_reset" + } + } + }, + "400": { + "$ref": "#/components/responses/400" + }, + "401": { + "$ref": "#/components/responses/401" + }, + "403": { + "$ref": "#/components/responses/403" + }, + "429": { + "$ref": "#/components/responses/429" + }, + "500": { + "$ref": "#/components/responses/500" + } + }, + "summary": "Top market movers", + "tags": [ + "Screener" + ] + } + }, + "/v1beta1/screener/stocks/most-actives": { + "get": { + "description": "Returns the most active stocks by volume or trade count based on real time SIP data. By default, returns the top 10 symbols by volume.\n", + "operationId": "MostActives", + "parameters": [ + { + "$ref": "#/components/parameters/most_actives_by" + }, + { + "$ref": "#/components/parameters/most_actives_top" + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/most_actives_resp" + } + } + }, + "description": "OK", + "headers": { + "X-RateLimit-Limit": { + "$ref": "#/components/headers/ratelimit_limit" + }, + "X-RateLimit-Remaining": { + "$ref": "#/components/headers/ratelimit_remaining" + }, + "X-RateLimit-Reset": { + "$ref": "#/components/headers/ratelimit_reset" + } + } + }, + "400": { + "$ref": "#/components/responses/400" + }, + "401": { + "$ref": "#/components/responses/401" + }, + "403": { + "$ref": "#/components/responses/403" + }, + "429": { + "$ref": "#/components/responses/429" + }, + "500": { + "$ref": "#/components/responses/500" + } + }, + "summary": "Most active stocks", + "tags": [ + "Screener" + ] + } + }, + "/v1beta3/crypto/{loc}/bars": { + "get": { + "description": "The crypto bars API provides historical aggregates for a list of crypto symbols between the specified dates.\n", + "operationId": "CryptoBars", + "parameters": [ + { + "$ref": "#/components/parameters/crypto_historical_loc" + }, + { + "$ref": "#/components/parameters/crypto_symbols" + }, + { + "$ref": "#/components/parameters/timeframe" + }, + { + "$ref": "#/components/parameters/start" + }, + { + "$ref": "#/components/parameters/end" + }, + { + "$ref": "#/components/parameters/limit" + }, + { + "$ref": "#/components/parameters/page_token" + }, + { + "$ref": "#/components/parameters/sort" + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "examples": { + "bars": { + "value": { + "bars": { + "BTC/USD": [ + { + "c": 29003, + "h": 29003, + "l": 28999, + "n": 4, + "o": 28999, + "t": "2022-05-27T10:18:00Z", + "v": 0.01, + "vw": 29001 + } + ] + }, + "next_page_token": "MTY0MDk0ODkyMzAwMDAwMDAwMHwyNDg0MzE3MQ==" + } + } + }, + "schema": { + "$ref": "#/components/schemas/crypto_bars_resp" + } + } + }, + "description": "OK", + "headers": { + "X-RateLimit-Limit": { + "$ref": "#/components/headers/ratelimit_limit" + }, + "X-RateLimit-Remaining": { + "$ref": "#/components/headers/ratelimit_remaining" + }, + "X-RateLimit-Reset": { + "$ref": "#/components/headers/ratelimit_reset" + } + } + }, + "400": { + "$ref": "#/components/responses/400" + }, + "401": { + "$ref": "#/components/responses/401" + }, + "403": { + "$ref": "#/components/responses/403" + }, + "429": { + "$ref": "#/components/responses/429" + }, + "500": { + "$ref": "#/components/responses/500" + } + }, + "security": [], + "summary": "Historical bars", + "tags": [ + "Crypto" + ] + } + }, + "/v1beta3/crypto/{loc}/latest/bars": { + "get": { + "description": "The latest multi-bars endpoint returns the latest minute-aggregated historical bar data for each of the crypto symbols provided.\n", + "operationId": "CryptoLatestBars", + "parameters": [ + { + "$ref": "#/components/parameters/crypto_latest_loc" + }, + { + "$ref": "#/components/parameters/crypto_symbols" + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "examples": { + "bars": { + "value": { + "bars": { + "BTC/USD": { + "c": 29003, + "h": 29003, + "l": 28999, + "n": 4, + "o": 28999, + "t": "2022-05-27T10:18:00Z", + "v": 0.01, + "vw": 29001 + } + } + } + } + }, + "schema": { + "$ref": "#/components/schemas/crypto_latest_bars_resp" + } + } + }, + "description": "OK", + "headers": { + "X-RateLimit-Limit": { + "$ref": "#/components/headers/ratelimit_limit" + }, + "X-RateLimit-Remaining": { + "$ref": "#/components/headers/ratelimit_remaining" + }, + "X-RateLimit-Reset": { + "$ref": "#/components/headers/ratelimit_reset" + } + } + }, + "400": { + "$ref": "#/components/responses/400" + }, + "401": { + "$ref": "#/components/responses/401" + }, + "403": { + "$ref": "#/components/responses/403" + }, + "429": { + "$ref": "#/components/responses/429" + }, + "500": { + "$ref": "#/components/responses/500" + } + }, + "security": [], + "summary": "Latest bars", + "tags": [ + "Crypto" + ] + } + }, + "/v1beta3/crypto/{loc}/latest/orderbooks": { + "get": { + "description": "The latest orderbook endpoint returns the latest bid and ask orderbook for the crypto symbols provided.\n", + "operationId": "CryptoLatestOrderbooks", + "parameters": [ + { + "$ref": "#/components/parameters/crypto_latest_loc" + }, + { + "$ref": "#/components/parameters/crypto_symbols" + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "examples": { + "orderbooks": { + "value": { + "orderbooks": { + "BTC/USD": { + "a": [ + { + "p": 20902, + "s": 0.0097 + }, + { + "p": 21444, + "s": 0 + } + ], + "b": [ + { + "p": 20846, + "s": 0.1902 + }, + { + "p": 20350, + "s": 0 + } + ], + "t": "2022-06-24T08:00:14.137774336Z" + } + } + } + } + }, + "schema": { + "$ref": "#/components/schemas/crypto_latest_orderbooks_resp" + } + } + }, + "description": "OK", + "headers": { + "X-RateLimit-Limit": { + "$ref": "#/components/headers/ratelimit_limit" + }, + "X-RateLimit-Remaining": { + "$ref": "#/components/headers/ratelimit_remaining" + }, + "X-RateLimit-Reset": { + "$ref": "#/components/headers/ratelimit_reset" + } + } + }, + "400": { + "$ref": "#/components/responses/400" + }, + "401": { + "$ref": "#/components/responses/401" + }, + "403": { + "$ref": "#/components/responses/403" + }, + "429": { + "$ref": "#/components/responses/429" + }, + "500": { + "$ref": "#/components/responses/500" + } + }, + "security": [], + "summary": "Latest orderbook", + "tags": [ + "Crypto" + ] + } + }, + "/v1beta3/crypto/{loc}/latest/quotes": { + "get": { + "description": "The latest quotes endpoint returns the latest bid and ask prices for the crypto symbols provided.\n", + "operationId": "CryptoLatestQuotes", + "parameters": [ + { + "$ref": "#/components/parameters/crypto_latest_loc" + }, + { + "$ref": "#/components/parameters/crypto_symbols" + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "examples": { + "quotes": { + "value": { + "quotes": { + "BTC/USD": { + "ap": 29059, + "as": 3.252, + "bp": 29058, + "bs": 0.3544, + "t": "2022-05-26T11:47:18.44347136Z" + }, + "ETH/USD": { + "ap": 1817.7, + "as": 6.137, + "bp": 1817, + "bs": 4.76, + "t": "2022-05-26T11:47:18.499478272Z" + } + } + } + } + }, + "schema": { + "$ref": "#/components/schemas/crypto_latest_quotes_resp" + } + } + }, + "description": "OK", + "headers": { + "X-RateLimit-Limit": { + "$ref": "#/components/headers/ratelimit_limit" + }, + "X-RateLimit-Remaining": { + "$ref": "#/components/headers/ratelimit_remaining" + }, + "X-RateLimit-Reset": { + "$ref": "#/components/headers/ratelimit_reset" + } + } + }, + "400": { + "$ref": "#/components/responses/400" + }, + "401": { + "$ref": "#/components/responses/401" + }, + "403": { + "$ref": "#/components/responses/403" + }, + "429": { + "$ref": "#/components/responses/429" + }, + "500": { + "$ref": "#/components/responses/500" + } + }, + "security": [], + "summary": "Latest quotes", + "tags": [ + "Crypto" + ] + } + }, + "/v1beta3/crypto/{loc}/latest/trades": { + "get": { + "description": "The latest trades endpoint returns the latest trade data for the crypto symbols provided.\n", + "operationId": "CryptoLatestTrades", + "parameters": [ + { + "$ref": "#/components/parameters/crypto_latest_loc" + }, + { + "$ref": "#/components/parameters/crypto_symbols" + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "examples": { + "trades": { + "value": { + "trades": { + "BTC/USD": { + "i": 31455289, + "p": 29791, + "s": 0.0016, + "t": "2022-05-18T12:01:00.537052Z", + "tks": "S" + }, + "ETH/USD": { + "i": 31455287, + "p": 2027.6, + "s": 0.06, + "t": "2022-05-18T12:01:00.363547Z", + "tks": "S" + } + } + } + } + }, + "schema": { + "$ref": "#/components/schemas/crypto_latest_trades_resp" + } + } + }, + "description": "OK", + "headers": { + "X-RateLimit-Limit": { + "$ref": "#/components/headers/ratelimit_limit" + }, + "X-RateLimit-Remaining": { + "$ref": "#/components/headers/ratelimit_remaining" + }, + "X-RateLimit-Reset": { + "$ref": "#/components/headers/ratelimit_reset" + } + } + }, + "400": { + "$ref": "#/components/responses/400" + }, + "401": { + "$ref": "#/components/responses/401" + }, + "403": { + "$ref": "#/components/responses/403" + }, + "429": { + "$ref": "#/components/responses/429" + }, + "500": { + "$ref": "#/components/responses/500" + } + }, + "security": [], + "summary": "Latest trades", + "tags": [ + "Crypto" + ] + } + }, + "/v1beta3/crypto/{loc}/quotes": { + "get": { + "description": "The crypto quotes API provides historical quote data for a list of crypto symbols between the specified dates.\nThe oldest date to retrieve historical quotes of us-1 location is 14th October, 2025 12AM UTC.", + "operationId": "CryptoQuotes", + "parameters": [ + { + "$ref": "#/components/parameters/crypto_historical_loc" + }, + { + "$ref": "#/components/parameters/crypto_symbols" + }, + { + "$ref": "#/components/parameters/start" + }, + { + "$ref": "#/components/parameters/end" + }, + { + "$ref": "#/components/parameters/limit" + }, + { + "$ref": "#/components/parameters/page_token" + }, + { + "$ref": "#/components/parameters/sort" + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "examples": { + "quotes": { + "value": { + "next_page_token": "MTY1MzU2NzYzODQ5OTQ3ODI3Mnwx", + "quotes": { + "BTC/USD": [ + { + "ap": 29059, + "as": 3.252, + "bp": 29058, + "bs": 0.3544, + "t": "2022-05-26T11:47:18.44347136Z" + } + ], + "ETH/USD": [ + { + "ap": 1817.7, + "as": 6.137, + "bp": 1817, + "bs": 4.76, + "t": "2022-05-26T11:47:18.499478272Z" + } + ] + } + } + } + }, + "schema": { + "$ref": "#/components/schemas/crypto_quotes_resp" + } + } + }, + "description": "OK", + "headers": { + "X-RateLimit-Limit": { + "$ref": "#/components/headers/ratelimit_limit" + }, + "X-RateLimit-Remaining": { + "$ref": "#/components/headers/ratelimit_remaining" + }, + "X-RateLimit-Reset": { + "$ref": "#/components/headers/ratelimit_reset" + } + } + }, + "400": { + "$ref": "#/components/responses/400" + }, + "401": { + "$ref": "#/components/responses/401" + }, + "403": { + "$ref": "#/components/responses/403" + }, + "429": { + "$ref": "#/components/responses/429" + }, + "500": { + "$ref": "#/components/responses/500" + } + }, + "security": [], + "summary": "Historical quotes", + "tags": [ + "Crypto" + ] + } + }, + "/v1beta3/crypto/{loc}/snapshots": { + "get": { + "description": "The snapshots endpoint returns the latest trade, latest quote, latest minute bar, latest daily bar, and previous daily bar data for crypto symbols.\n", + "operationId": "CryptoSnapshots", + "parameters": [ + { + "$ref": "#/components/parameters/crypto_latest_loc" + }, + { + "$ref": "#/components/parameters/crypto_symbols" + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "examples": { + "snapshots": { + "value": { + "snapshots": { + "BTC/USD": { + "dailyBar": { + "c": 31744, + "h": 31807, + "l": 31416, + "n": 438, + "o": 31660, + "t": "2022-05-31T05:00:00Z", + "v": 67.3518, + "vw": 31582.7034526175 + }, + "latestQuote": { + "ap": 31742, + "as": 0.395, + "bp": 31741, + "bs": 0.395, + "t": "2022-05-31T11:55:58.507608832Z" + }, + "latestTrade": { + "i": 32396097, + "p": 31744, + "s": 0.0543, + "t": "2022-05-31T11:53:45.027481Z", + "tks": "B" + }, + "minuteBar": { + "c": 31744, + "h": 31744, + "l": 31744, + "n": 2, + "o": 31744, + "t": "2022-05-31T11:53:00Z", + "v": 0.0886, + "vw": 31744 + }, + "prevDailyBar": { + "c": 31649, + "h": 32251, + "l": 30251, + "n": 8221, + "o": 30310, + "t": "2022-05-30T05:00:00Z", + "v": 1856.4065, + "vw": 30877.2751897281 + } + } + } + } + } + }, + "schema": { + "$ref": "#/components/schemas/crypto_snapshots_resp" + } + } + }, + "description": "OK", + "headers": { + "X-RateLimit-Limit": { + "$ref": "#/components/headers/ratelimit_limit" + }, + "X-RateLimit-Remaining": { + "$ref": "#/components/headers/ratelimit_remaining" + }, + "X-RateLimit-Reset": { + "$ref": "#/components/headers/ratelimit_reset" + } + } + }, + "400": { + "$ref": "#/components/responses/400" + }, + "401": { + "$ref": "#/components/responses/401" + }, + "403": { + "$ref": "#/components/responses/403" + }, + "429": { + "$ref": "#/components/responses/429" + }, + "500": { + "$ref": "#/components/responses/500" + } + }, + "security": [], + "summary": "Snapshots", + "tags": [ + "Crypto" + ] + } + }, + "/v1beta3/crypto/{loc}/trades": { + "get": { + "description": "The crypto trades API provides historical trade data for a list of crypto symbols between the specified dates.\n", + "operationId": "CryptoTrades", + "parameters": [ + { + "$ref": "#/components/parameters/crypto_historical_loc" + }, + { + "$ref": "#/components/parameters/crypto_symbols" + }, + { + "$ref": "#/components/parameters/start" + }, + { + "$ref": "#/components/parameters/end" + }, + { + "$ref": "#/components/parameters/limit" + }, + { + "$ref": "#/components/parameters/page_token" + }, + { + "$ref": "#/components/parameters/sort" + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "examples": { + "trades": { + "value": { + "next_page_token": "MTY1MzA1MDgwMDAwMDAwMDAwMHwzMTQ1NTI4OQ==", + "trades": { + "BTC/USD": [ + { + "i": 31455289, + "p": 29791, + "s": 0.0016, + "t": "2022-05-18T12:01:00.537052Z", + "tks": "S" + } + ], + "ETH/USD": [ + { + "i": 31455287, + "p": 2027.6, + "s": 0.06, + "t": "2022-05-18T12:01:00.363547Z", + "tks": "S" + }, + { + "i": 31455288, + "p": 2027.6, + "s": 0.136, + "t": "2022-05-18T12:01:00.363547Z", + "tks": "S" + } + ] + } + } + } + }, + "schema": { + "$ref": "#/components/schemas/crypto_trades_resp" + } + } + }, + "description": "OK", + "headers": { + "X-RateLimit-Limit": { + "$ref": "#/components/headers/ratelimit_limit" + }, + "X-RateLimit-Remaining": { + "$ref": "#/components/headers/ratelimit_remaining" + }, + "X-RateLimit-Reset": { + "$ref": "#/components/headers/ratelimit_reset" + } + } + }, + "400": { + "$ref": "#/components/responses/400" + }, + "401": { + "$ref": "#/components/responses/401" + }, + "403": { + "$ref": "#/components/responses/403" + }, + "429": { + "$ref": "#/components/responses/429" + }, + "500": { + "$ref": "#/components/responses/500" + } + }, + "security": [], + "summary": "Historical trades", + "tags": [ + "Crypto" + ] + } + }, + "/v1/corporate-actions": { + "get": { + "description": "This endpoint provides data about the corporate actions for each given symbol over a specified time period.\n\n> ⚠️ Warning\n>\n> Currently Alpaca has no guarantees on the creation time of corporate actions. There may be delays in receiving corporate actions from our data providers, and there may be delays in processing and making them available via this API. As a result, corporate actions may not be available immediately after they are announced.\n", + "operationId": "CorporateActions", + "parameters": [ + { + "$ref": "#/components/parameters/cas_symbols" + }, + { + "$ref": "#/components/parameters/cas_cusips" + }, + { + "$ref": "#/components/parameters/cas_types" + }, + { + "$ref": "#/components/parameters/cas_region" + }, + { + "$ref": "#/components/parameters/cas_start" + }, + { + "$ref": "#/components/parameters/cas_end" + }, + { + "$ref": "#/components/parameters/cas_ids" + }, + { + "$ref": "#/components/parameters/cas_limit" + }, + { + "$ref": "#/components/parameters/page_token" + }, + { + "$ref": "#/components/parameters/sort" + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/corporate_actions_resp" + } + } + }, + "description": "OK", + "headers": { + "X-RateLimit-Limit": { + "$ref": "#/components/headers/ratelimit_limit" + }, + "X-RateLimit-Remaining": { + "$ref": "#/components/headers/ratelimit_remaining" + }, + "X-RateLimit-Reset": { + "$ref": "#/components/headers/ratelimit_reset" + } + } + }, + "400": { + "$ref": "#/components/responses/400" + }, + "401": { + "$ref": "#/components/responses/401" + }, + "403": { + "$ref": "#/components/responses/403" + }, + "429": { + "$ref": "#/components/responses/429" + }, + "500": { + "$ref": "#/components/responses/500" + } + }, + "summary": "Corporate actions", + "tags": [ + "Corporate actions" + ] + } + }, + "/v2/stocks/{symbol}/auctions": { + "get": { + "description": "The historical auctions endpoint provides auction prices for the given stock symbol between the specified dates.\n", + "operationId": "StockAuctionSingle", + "parameters": [ + { + "$ref": "#/components/parameters/stock_symbol" + }, + { + "$ref": "#/components/parameters/start" + }, + { + "$ref": "#/components/parameters/end" + }, + { + "$ref": "#/components/parameters/limit" + }, + { + "$ref": "#/components/parameters/stock_asof" + }, + { + "$ref": "#/components/parameters/stock_auction_feed" + }, + { + "$ref": "#/components/parameters/stock_currency" + }, + { + "$ref": "#/components/parameters/page_token" + }, + { + "$ref": "#/components/parameters/sort" + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "examples": { + "auctions": { + "value": { + "auctions": [ + { + "c": [ + { + "c": "M", + "p": 221.7, + "t": "2022-10-13T20:00:00.152902912Z", + "x": "P" + } + ], + "d": "2022-10-13", + "o": [ + { + "c": "Q", + "p": 208.37, + "t": "2022-10-13T13:30:00.068034304Z", + "x": "P" + }, + { + "c": "O", + "p": 208.49, + "t": "2022-10-13T13:30:01.079567733Z", + "x": "Q" + }, + { + "c": "Q", + "p": 208.49, + "t": "2022-10-13T13:30:01.090802222Z", + "x": "Q" + } + ] + } + ], + "next_page_token": "MjAyMi0xMC0xM1QyMDowMDowMC4xNTI5MDI5MTJafFA=", + "symbol": "TSLA" + } + } + }, + "schema": { + "$ref": "#/components/schemas/stock_auctions_resp_single" + } + } + }, + "description": "OK", + "headers": { + "X-RateLimit-Limit": { + "$ref": "#/components/headers/ratelimit_limit" + }, + "X-RateLimit-Remaining": { + "$ref": "#/components/headers/ratelimit_remaining" + }, + "X-RateLimit-Reset": { + "$ref": "#/components/headers/ratelimit_reset" + } + } + }, + "400": { + "$ref": "#/components/responses/400" + }, + "401": { + "$ref": "#/components/responses/401" + }, + "403": { + "$ref": "#/components/responses/403" + }, + "429": { + "$ref": "#/components/responses/429" + }, + "500": { + "$ref": "#/components/responses/500" + } + }, + "summary": "Historical auctions (single)", + "tags": [ + "Stock" + ] + } + }, + "/v2/stocks/{symbol}/bars": { + "get": { + "description": "The historical stock bars API provides aggregates for the stock symbol between the specified dates.", + "operationId": "StockBarSingle", + "parameters": [ + { + "$ref": "#/components/parameters/stock_symbol" + }, + { + "$ref": "#/components/parameters/timeframe" + }, + { + "$ref": "#/components/parameters/start" + }, + { + "$ref": "#/components/parameters/end" + }, + { + "$ref": "#/components/parameters/limit" + }, + { + "$ref": "#/components/parameters/stock_adjustment" + }, + { + "$ref": "#/components/parameters/stock_asof" + }, + { + "$ref": "#/components/parameters/stock_historical_feed" + }, + { + "$ref": "#/components/parameters/stock_currency" + }, + { + "$ref": "#/components/parameters/page_token" + }, + { + "$ref": "#/components/parameters/sort" + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "examples": { + "bars": { + "value": { + "bars": [ + { + "c": 178.21, + "h": 178.26, + "l": 178.21, + "n": 65, + "o": 178.26, + "t": "2022-01-03T09:00:00Z", + "v": 1118, + "vw": 178.235733 + } + ], + "next_page_token": "QUFQTHxNfDIwMjItMDEtMDNUMDk6MDA6MDAuMDAwMDAwMDAwWg==", + "symbol": "AAPL" + } + } + }, + "schema": { + "$ref": "#/components/schemas/stock_bars_resp_single" + } + } + }, + "description": "OK", + "headers": { + "X-RateLimit-Limit": { + "$ref": "#/components/headers/ratelimit_limit" + }, + "X-RateLimit-Remaining": { + "$ref": "#/components/headers/ratelimit_remaining" + }, + "X-RateLimit-Reset": { + "$ref": "#/components/headers/ratelimit_reset" + } + } + }, + "400": { + "$ref": "#/components/responses/400" + }, + "401": { + "$ref": "#/components/responses/401" + }, + "403": { + "$ref": "#/components/responses/403" + }, + "429": { + "$ref": "#/components/responses/429" + }, + "500": { + "$ref": "#/components/responses/500" + } + }, + "summary": "Historical bars (single symbol)", + "tags": [ + "Stock" + ] + } + }, + "/v2/stocks/{symbol}/bars/latest": { + "get": { + "description": "The latest bar endpoint returns the latest minute bar for the given ticker symbol.\n", + "operationId": "StockLatestBarSingle", + "parameters": [ + { + "$ref": "#/components/parameters/stock_symbol" + }, + { + "$ref": "#/components/parameters/stock_latest_feed" + }, + { + "$ref": "#/components/parameters/stock_currency" + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "examples": { + "bar": { + "value": { + "bar": { + "c": 173, + "h": 173.04, + "l": 172.98, + "n": 49, + "o": 172.98, + "t": "2022-08-17T09:07:00Z", + "v": 2748, + "vw": 173.007817 + }, + "symbol": "AAPL" + } + } + }, + "schema": { + "$ref": "#/components/schemas/stock_latest_bars_resp_single" + } + } + }, + "description": "OK", + "headers": { + "X-RateLimit-Limit": { + "$ref": "#/components/headers/ratelimit_limit" + }, + "X-RateLimit-Remaining": { + "$ref": "#/components/headers/ratelimit_remaining" + }, + "X-RateLimit-Reset": { + "$ref": "#/components/headers/ratelimit_reset" + } + } + }, + "400": { + "$ref": "#/components/responses/400" + }, + "401": { + "$ref": "#/components/responses/401" + }, + "403": { + "$ref": "#/components/responses/403" + }, + "429": { + "$ref": "#/components/responses/429" + }, + "500": { + "$ref": "#/components/responses/500" + } + }, + "summary": "Latest bar (single symbol)", + "tags": [ + "Stock" + ] + } + }, + "/v2/stocks/{symbol}/quotes": { + "get": { + "description": "The historical stock quotes API provides quote data for a stock symbol between the specified dates.", + "operationId": "StockQuoteSingle", + "parameters": [ + { + "$ref": "#/components/parameters/stock_symbol" + }, + { + "$ref": "#/components/parameters/start" + }, + { + "$ref": "#/components/parameters/end" + }, + { + "$ref": "#/components/parameters/limit" + }, + { + "$ref": "#/components/parameters/stock_asof" + }, + { + "$ref": "#/components/parameters/stock_historical_feed" + }, + { + "$ref": "#/components/parameters/stock_currency" + }, + { + "$ref": "#/components/parameters/page_token" + }, + { + "$ref": "#/components/parameters/sort" + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "examples": { + "quotes": { + "value": { + "next_page_token": "QUFQTHwyMDIyLTAxLTAzVDA5OjAwOjAwLjAyODI5NDQ1MVp8MjM3NjQ0Qzg=", + "quotes": [ + { + "ap": 0, + "as": 0, + "ax": " ", + "bp": 177.92, + "bs": 4, + "bx": "Q", + "c": [ + "Y" + ], + "t": "2022-01-03T09:00:00.028160898Z", + "z": "C" + }, + { + "ap": 178.8, + "as": 4, + "ax": "Q", + "bp": 177.92, + "bs": 4, + "bx": "Q", + "c": [ + "R" + ], + "t": "2022-01-03T09:00:00.028294451Z", + "z": "C" + } + ], + "symbol": "AAPL" + } + } + }, + "schema": { + "$ref": "#/components/schemas/stock_quotes_resp_single" + } + } + }, + "description": "OK", + "headers": { + "X-RateLimit-Limit": { + "$ref": "#/components/headers/ratelimit_limit" + }, + "X-RateLimit-Remaining": { + "$ref": "#/components/headers/ratelimit_remaining" + }, + "X-RateLimit-Reset": { + "$ref": "#/components/headers/ratelimit_reset" + } + } + }, + "400": { + "$ref": "#/components/responses/400" + }, + "401": { + "$ref": "#/components/responses/401" + }, + "403": { + "$ref": "#/components/responses/403" + }, + "429": { + "$ref": "#/components/responses/429" + }, + "500": { + "$ref": "#/components/responses/500" + } + }, + "summary": "Historical quotes (single symbol)", + "tags": [ + "Stock" + ] + } + }, + "/v2/stocks/{symbol}/quotes/latest": { + "get": { + "description": "The latest quote endpoint provides the latest best bid and ask prices for a given ticker symbol.\n", + "operationId": "StockLatestQuoteSingle", + "parameters": [ + { + "$ref": "#/components/parameters/stock_symbol" + }, + { + "$ref": "#/components/parameters/stock_latest_feed" + }, + { + "$ref": "#/components/parameters/stock_currency" + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "examples": { + "quotes": { + "value": { + "quote": { + "ap": 172.7, + "as": 1, + "ax": "Q", + "bp": 172.6, + "bs": 2, + "bx": "Q", + "c": [ + "R" + ], + "t": "2022-08-17T10:09:34.055031265Z", + "z": "C" + }, + "symbol": "AAPL" + } + } + }, + "schema": { + "$ref": "#/components/schemas/stock_latest_quotes_resp_single" + } + } + }, + "description": "OK", + "headers": { + "X-RateLimit-Limit": { + "$ref": "#/components/headers/ratelimit_limit" + }, + "X-RateLimit-Remaining": { + "$ref": "#/components/headers/ratelimit_remaining" + }, + "X-RateLimit-Reset": { + "$ref": "#/components/headers/ratelimit_reset" + } + } + }, + "400": { + "$ref": "#/components/responses/400" + }, + "401": { + "$ref": "#/components/responses/401" + }, + "403": { + "$ref": "#/components/responses/403" + }, + "429": { + "$ref": "#/components/responses/429" + }, + "500": { + "$ref": "#/components/responses/500" + } + }, + "summary": "Latest quote (single symbol)", + "tags": [ + "Stock" + ] + } + }, + "/v2/stocks/{symbol}/snapshot": { + "get": { + "description": "The snapshot endpoint provides the latest trade, latest quote, minute bar, daily bar, and previous daily bar data for a given ticker symbol.\n", + "operationId": "StockSnapshotSingle", + "parameters": [ + { + "$ref": "#/components/parameters/stock_symbol" + }, + { + "$ref": "#/components/parameters/stock_latest_feed" + }, + { + "$ref": "#/components/parameters/stock_currency" + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "examples": { + "snapshot": { + "value": { + "dailyBar": { + "c": 173.03, + "h": 173.71, + "l": 171.6618, + "n": 515139, + "o": 172.62, + "t": "2022-08-16T04:00:00Z", + "v": 56457696, + "vw": 172.743391 + }, + "latestQuote": { + "ap": 172.65, + "as": 1, + "ax": "Q", + "bp": 172.51, + "bs": 1, + "bx": "P", + "c": [ + "R" + ], + "t": "2022-08-17T10:19:30.805564086Z", + "z": "C" + }, + "latestTrade": { + "c": [ + "@", + "T" + ], + "i": 1040, + "p": 172.55, + "s": 229, + "t": "2022-08-17T10:19:30.735811394Z", + "x": "Q", + "z": "C" + }, + "minuteBar": { + "c": 172.6, + "h": 172.65, + "l": 172.6, + "n": 57, + "o": 172.65, + "t": "2022-08-17T10:18:00Z", + "v": 3746, + "vw": 172.618377 + }, + "prevDailyBar": { + "c": 173.19, + "h": 173.39, + "l": 171.345, + "n": 501626, + "o": 171.5, + "t": "2022-08-15T04:00:00Z", + "v": 54091719, + "vw": 172.625371 + }, + "symbol": "AAPL" + } + } + }, + "schema": { + "$ref": "#/components/schemas/stock_snapshots_resp_single" + } + } + }, + "description": "OK", + "headers": { + "X-RateLimit-Limit": { + "$ref": "#/components/headers/ratelimit_limit" + }, + "X-RateLimit-Remaining": { + "$ref": "#/components/headers/ratelimit_remaining" + }, + "X-RateLimit-Reset": { + "$ref": "#/components/headers/ratelimit_reset" + } + } + }, + "400": { + "$ref": "#/components/responses/400" + }, + "401": { + "$ref": "#/components/responses/401" + }, + "403": { + "$ref": "#/components/responses/403" + }, + "429": { + "$ref": "#/components/responses/429" + }, + "500": { + "$ref": "#/components/responses/500" + } + }, + "summary": "Snapshot (single symbol)", + "tags": [ + "Stock" + ] + } + }, + "/v2/stocks/{symbol}/trades": { + "get": { + "description": "The historical stock trades API provides trade data for a stock symbol between the specified dates.", + "operationId": "StockTradeSingle", + "parameters": [ + { + "$ref": "#/components/parameters/stock_symbol" + }, + { + "$ref": "#/components/parameters/start" + }, + { + "$ref": "#/components/parameters/end" + }, + { + "$ref": "#/components/parameters/limit" + }, + { + "$ref": "#/components/parameters/stock_asof" + }, + { + "$ref": "#/components/parameters/stock_historical_feed" + }, + { + "$ref": "#/components/parameters/stock_currency" + }, + { + "$ref": "#/components/parameters/page_token" + }, + { + "$ref": "#/components/parameters/sort" + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "examples": { + "trades": { + "value": { + "next_page_token": "QUFQTHwyMDIyLTAxLTAzVDE3OjAwOjAwLjAyOTU5MTQ3Nlp8UXwwOTIyMzM3MjAzNjg1NDg0OTY0Mg==", + "symbol": "AAPL", + "trades": [ + { + "c": [ + "@", + "I" + ], + "i": 73833, + "p": 181.47, + "s": 1, + "t": "2022-01-03T17:00:00.029591476Z", + "x": "Q", + "z": "C" + }, + { + "c": [ + "@", + "I" + ], + "i": 73834, + "p": 181.47, + "s": 1, + "t": "2022-01-03T17:00:00.029591476Z", + "x": "Q", + "z": "C" + } + ] + } + } + }, + "schema": { + "$ref": "#/components/schemas/stock_trades_resp_single" + } + } + }, + "description": "OK", + "headers": { + "X-RateLimit-Limit": { + "$ref": "#/components/headers/ratelimit_limit" + }, + "X-RateLimit-Remaining": { + "$ref": "#/components/headers/ratelimit_remaining" + }, + "X-RateLimit-Reset": { + "$ref": "#/components/headers/ratelimit_reset" + } + } + }, + "400": { + "$ref": "#/components/responses/400" + }, + "401": { + "$ref": "#/components/responses/401" + }, + "403": { + "$ref": "#/components/responses/403" + }, + "429": { + "$ref": "#/components/responses/429" + }, + "500": { + "$ref": "#/components/responses/500" + } + }, + "summary": "Historical trades (single symbol)", + "tags": [ + "Stock" + ] + } + }, + "/v2/stocks/{symbol}/trades/latest": { + "get": { + "description": "The latest trade endpoint provides the latest trade for the given ticker symbol.\n\nTrades with any conditions that causes them to not update the bar price are excluded. For example a trade with condition `I` (odd lot) will never appear on this endpoint. You can find the complete list of excluded conditions in [this FAQ](https://docs.alpaca.markets/docs/market-data-faq#how-are-bars-aggregated).", + "operationId": "StockLatestTradeSingle", + "parameters": [ + { + "$ref": "#/components/parameters/stock_symbol" + }, + { + "$ref": "#/components/parameters/stock_latest_feed" + }, + { + "$ref": "#/components/parameters/stock_currency" + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "examples": { + "trade": { + "value": { + "symbol": "AAPL", + "trade": { + "c": [ + "@", + "T" + ], + "i": 689, + "p": 172.6, + "s": 100, + "t": "2022-08-17T09:53:16.845580544Z", + "x": "P", + "z": "C" + } + } + } + }, + "schema": { + "$ref": "#/components/schemas/stock_latest_trades_resp_single" + } + } + }, + "description": "OK", + "headers": { + "X-RateLimit-Limit": { + "$ref": "#/components/headers/ratelimit_limit" + }, + "X-RateLimit-Remaining": { + "$ref": "#/components/headers/ratelimit_remaining" + }, + "X-RateLimit-Reset": { + "$ref": "#/components/headers/ratelimit_reset" + } + } + }, + "400": { + "$ref": "#/components/responses/400" + }, + "401": { + "$ref": "#/components/responses/401" + }, + "403": { + "$ref": "#/components/responses/403" + }, + "429": { + "$ref": "#/components/responses/429" + }, + "500": { + "$ref": "#/components/responses/500" + } + }, + "summary": "Latest trade (single symbol)", + "tags": [ + "Stock" + ] + } + }, + "/v2/stocks/auctions": { + "get": { + "description": "The historical auctions endpoint provides auction prices for a list of stock symbols between the specified dates.\n", + "operationId": "StockAuctions", + "parameters": [ + { + "$ref": "#/components/parameters/stock_symbols" + }, + { + "$ref": "#/components/parameters/start" + }, + { + "$ref": "#/components/parameters/end" + }, + { + "$ref": "#/components/parameters/limit" + }, + { + "$ref": "#/components/parameters/stock_asof" + }, + { + "$ref": "#/components/parameters/stock_auction_feed" + }, + { + "$ref": "#/components/parameters/stock_currency" + }, + { + "$ref": "#/components/parameters/page_token" + }, + { + "$ref": "#/components/parameters/sort" + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "examples": { + "auction": { + "value": { + "auctions": { + "AAPL": [ + { + "c": [ + { + "c": "6", + "p": 138.36, + "t": "2022-10-12T20:00:00.120649216Z", + "x": "P" + }, + { + "c": "M", + "p": 138.36, + "t": "2022-10-12T20:00:00.125925888Z", + "x": "P" + }, + { + "c": "6", + "p": 138.34, + "t": "2022-10-12T20:00:00.875570864Z", + "x": "Q" + }, + { + "c": "M", + "p": 138.34, + "t": "2022-10-12T20:00:00.875603021Z", + "x": "Q" + } + ], + "d": "2022-10-12", + "o": [ + { + "c": "Q", + "p": 139.12, + "t": "2022-10-12T13:30:00.188390144Z", + "x": "P" + }, + { + "c": "O", + "p": 138.99, + "t": "2022-10-12T13:30:01.474665705Z", + "x": "Q" + }, + { + "c": "Q", + "p": 138.99, + "t": "2022-10-12T13:30:01.475216565Z", + "x": "Q" + } + ] + }, + { + "c": [ + { + "c": "M", + "p": 142.94, + "t": "2022-10-13T20:00:00.166980864Z", + "x": "P" + } + ], + "d": "2022-10-13", + "o": [ + { + "c": "Q", + "p": 134.8, + "t": "2022-10-13T13:30:00.20304384Z", + "x": "P" + }, + { + "c": "O", + "p": 135, + "t": "2022-10-13T13:30:01.688322951Z", + "x": "Q" + }, + { + "c": "Q", + "p": 135, + "t": "2022-10-13T13:30:01.699259366Z", + "x": "Q" + } + ] + } + ], + "TSLA": [ + { + "c": [ + { + "c": "M", + "p": 217.23, + "t": "2022-10-12T20:00:00.124164096Z", + "x": "P" + }, + { + "c": "6", + "p": 217.24, + "t": "2022-10-12T20:00:00.469874365Z", + "x": "Q" + }, + { + "c": "M", + "p": 217.24, + "t": "2022-10-12T20:00:00.469912641Z", + "x": "Q" + } + ], + "d": "2022-10-12", + "o": [ + { + "c": "Q", + "p": 215.39, + "t": "2022-10-12T13:30:00.065736192Z", + "x": "P" + }, + { + "c": "O", + "p": 215.79, + "t": "2022-10-12T13:30:01.349399539Z", + "x": "Q" + }, + { + "c": "Q", + "p": 215.79, + "t": "2022-10-12T13:30:01.349965972Z", + "x": "Q" + } + ] + }, + { + "c": [ + { + "c": "M", + "p": 221.7, + "t": "2022-10-13T20:00:00.152902912Z", + "x": "P" + } + ], + "d": "2022-10-13", + "o": [ + { + "c": "Q", + "p": 208.37, + "t": "2022-10-13T13:30:00.068034304Z", + "x": "P" + }, + { + "c": "O", + "p": 208.49, + "t": "2022-10-13T13:30:01.079567733Z", + "x": "Q" + }, + { + "c": "Q", + "p": 208.49, + "t": "2022-10-13T13:30:01.090802222Z", + "x": "Q" + } + ] + } + ] + }, + "next_page_token": "MjAyMi0xMC0xM1QyMDowMDowMC4xNTI5MDI5MTJafFA=" + } + } + }, + "schema": { + "$ref": "#/components/schemas/stock_auctions_resp" + } + } + }, + "description": "OK", + "headers": { + "X-RateLimit-Limit": { + "$ref": "#/components/headers/ratelimit_limit" + }, + "X-RateLimit-Remaining": { + "$ref": "#/components/headers/ratelimit_remaining" + }, + "X-RateLimit-Reset": { + "$ref": "#/components/headers/ratelimit_reset" + } + } + }, + "400": { + "$ref": "#/components/responses/400" + }, + "401": { + "$ref": "#/components/responses/401" + }, + "403": { + "$ref": "#/components/responses/403" + }, + "429": { + "$ref": "#/components/responses/429" + }, + "500": { + "$ref": "#/components/responses/500" + } + }, + "summary": "Historical auctions", + "tags": [ + "Stock" + ] + } + }, + "/v2/stocks/bars": { + "get": { + "description": "The historical stock bars API provides aggregates for a list of stock symbols between the specified dates.\n\nThe returned results are sorted by symbol first, then by bar timestamp. This means that you are likely to see only one symbol in your first response if there are enough bars for that symbol to hit the limit you requested.\n\nIn these situations, if you keep requesting again with the `next_page_token` from the previous response, you will eventually reach the other symbols if any bars were found for them.", + "operationId": "StockBars", + "parameters": [ + { + "$ref": "#/components/parameters/stock_symbols" + }, + { + "$ref": "#/components/parameters/timeframe" + }, + { + "$ref": "#/components/parameters/start" + }, + { + "$ref": "#/components/parameters/end" + }, + { + "$ref": "#/components/parameters/limit" + }, + { + "$ref": "#/components/parameters/stock_adjustment" + }, + { + "$ref": "#/components/parameters/stock_asof" + }, + { + "$ref": "#/components/parameters/stock_historical_feed" + }, + { + "$ref": "#/components/parameters/stock_currency" + }, + { + "$ref": "#/components/parameters/page_token" + }, + { + "$ref": "#/components/parameters/sort" + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "examples": { + "bars": { + "value": { + "bars": { + "AAPL": [ + { + "c": 178.21, + "h": 178.26, + "l": 178.21, + "n": 65, + "o": 178.26, + "t": "2022-01-03T09:00:00Z", + "v": 1118, + "vw": 178.235733 + } + ] + }, + "next_page_token": "QUFQTHxNfDIwMjItMDEtMDNUMDk6MDA6MDAuMDAwMDAwMDAwWg==" + } + } + }, + "schema": { + "$ref": "#/components/schemas/stock_bars_resp" + } + } + }, + "description": "OK", + "headers": { + "X-RateLimit-Limit": { + "$ref": "#/components/headers/ratelimit_limit" + }, + "X-RateLimit-Remaining": { + "$ref": "#/components/headers/ratelimit_remaining" + }, + "X-RateLimit-Reset": { + "$ref": "#/components/headers/ratelimit_reset" + } + } + }, + "400": { + "$ref": "#/components/responses/400" + }, + "401": { + "$ref": "#/components/responses/401" + }, + "403": { + "$ref": "#/components/responses/403" + }, + "429": { + "$ref": "#/components/responses/429" + }, + "500": { + "$ref": "#/components/responses/500" + } + }, + "summary": "Historical bars", + "tags": [ + "Stock" + ] + } + }, + "/v2/stocks/bars/latest": { + "get": { + "description": "The latest bars endpoint provides the latest minute bar for the given ticker symbols.\n", + "operationId": "StockLatestBars", + "parameters": [ + { + "$ref": "#/components/parameters/stock_symbols" + }, + { + "$ref": "#/components/parameters/stock_latest_feed" + }, + { + "$ref": "#/components/parameters/stock_currency" + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "examples": { + "bars": { + "value": { + "bars": { + "AAPL": { + "c": 172.79, + "h": 172.81, + "l": 172.78, + "n": 20, + "o": 172.81, + "t": "2022-08-17T08:58:00Z", + "v": 1002, + "vw": 172.791417 + }, + "TSLA": { + "c": 914.3, + "h": 914.3, + "l": 914.3, + "n": 20, + "o": 914.3, + "t": "2022-08-17T08:57:00Z", + "v": 751, + "vw": 914.294634 + } + } + } + } + }, + "schema": { + "$ref": "#/components/schemas/stock_latest_bars_resp" + } + } + }, + "description": "OK", + "headers": { + "X-RateLimit-Limit": { + "$ref": "#/components/headers/ratelimit_limit" + }, + "X-RateLimit-Remaining": { + "$ref": "#/components/headers/ratelimit_remaining" + }, + "X-RateLimit-Reset": { + "$ref": "#/components/headers/ratelimit_reset" + } + } + }, + "400": { + "$ref": "#/components/responses/400" + }, + "401": { + "$ref": "#/components/responses/401" + }, + "403": { + "$ref": "#/components/responses/403" + }, + "429": { + "$ref": "#/components/responses/429" + }, + "500": { + "$ref": "#/components/responses/500" + } + }, + "summary": "Latest bars", + "tags": [ + "Stock" + ] + } + }, + "/v2/stocks/meta/conditions/{ticktype}": { + "get": { + "description": "Returns the mapping between the condition codes and names.", + "operationId": "StockMetaConditions", + "parameters": [ + { + "description": "The type of ticks.", + "in": "path", + "name": "ticktype", + "required": true, + "schema": { + "enum": [ + "trade", + "quote" + ], + "type": "string" + } + }, + { + "description": "The one character name of the tape.", + "in": "query", + "name": "tape", + "required": true, + "schema": { + "enum": [ + "A", + "B", + "C" + ], + "type": "string" + } + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/stock_conditions" + } + } + }, + "description": "OK", + "headers": { + "X-RateLimit-Limit": { + "$ref": "#/components/headers/ratelimit_limit" + }, + "X-RateLimit-Remaining": { + "$ref": "#/components/headers/ratelimit_remaining" + }, + "X-RateLimit-Reset": { + "$ref": "#/components/headers/ratelimit_reset" + } + } + }, + "400": { + "$ref": "#/components/responses/400" + }, + "401": { + "$ref": "#/components/responses/401" + }, + "403": { + "$ref": "#/components/responses/403" + }, + "429": { + "$ref": "#/components/responses/429" + }, + "500": { + "$ref": "#/components/responses/500" + } + }, + "summary": "Condition codes", + "tags": [ + "Stock" + ] + } + }, + "/v2/stocks/meta/exchanges": { + "get": { + "description": "Returns the mapping between the stock exchange codes and the corresponding exchange names.\n", + "operationId": "StockMetaExchanges", + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/stock_exchanges" + } + } + }, + "description": "OK", + "headers": { + "X-RateLimit-Limit": { + "$ref": "#/components/headers/ratelimit_limit" + }, + "X-RateLimit-Remaining": { + "$ref": "#/components/headers/ratelimit_remaining" + }, + "X-RateLimit-Reset": { + "$ref": "#/components/headers/ratelimit_reset" + } + } + }, + "400": { + "$ref": "#/components/responses/400" + }, + "401": { + "$ref": "#/components/responses/401" + }, + "403": { + "$ref": "#/components/responses/403" + }, + "429": { + "$ref": "#/components/responses/429" + }, + "500": { + "$ref": "#/components/responses/500" + } + }, + "summary": "Exchange codes", + "tags": [ + "Stock" + ] + } + }, + "/v2/stocks/quotes": { + "get": { + "description": "The historical stock quotes API provides quote data for a list of stock symbols between the specified dates.\n\nThe returned results are sorted by symbol first, then by the quote timestamp. This means that you are likely to see only one symbol in your first response if there are enough quotes for that symbol to hit the limit you requested.\n\nIn these situations, if you keep requesting again with the `next_page_token` from the previous response, you will eventually reach the other symbols if any quotes were found for them.", + "operationId": "StockQuotes", + "parameters": [ + { + "$ref": "#/components/parameters/stock_symbols" + }, + { + "$ref": "#/components/parameters/start" + }, + { + "$ref": "#/components/parameters/end" + }, + { + "$ref": "#/components/parameters/limit" + }, + { + "$ref": "#/components/parameters/stock_asof" + }, + { + "$ref": "#/components/parameters/stock_historical_feed" + }, + { + "$ref": "#/components/parameters/stock_currency" + }, + { + "$ref": "#/components/parameters/page_token" + }, + { + "$ref": "#/components/parameters/sort" + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "examples": { + "quotes": { + "value": { + "next_page_token": "QUFQTHwyMDIyLTAxLTAzVDA5OjAwOjAwLjAyODI5NDQ1MVp8MjM3NjQ0Qzg=", + "quotes": { + "AAPL": [ + { + "ap": 0, + "as": 0, + "ax": " ", + "bp": 177.92, + "bs": 4, + "bx": "Q", + "c": [ + "Y" + ], + "t": "2022-01-03T09:00:00.028160898Z", + "z": "C" + }, + { + "ap": 178.8, + "as": 4, + "ax": "Q", + "bp": 177.92, + "bs": 4, + "bx": "Q", + "c": [ + "R" + ], + "t": "2022-01-03T09:00:00.028294451Z", + "z": "C" + } + ] + } + } + } + }, + "schema": { + "$ref": "#/components/schemas/stock_quotes_resp" + } + } + }, + "description": "OK", + "headers": { + "X-RateLimit-Limit": { + "$ref": "#/components/headers/ratelimit_limit" + }, + "X-RateLimit-Remaining": { + "$ref": "#/components/headers/ratelimit_remaining" + }, + "X-RateLimit-Reset": { + "$ref": "#/components/headers/ratelimit_reset" + } + } + }, + "400": { + "$ref": "#/components/responses/400" + }, + "401": { + "$ref": "#/components/responses/401" + }, + "403": { + "$ref": "#/components/responses/403" + }, + "429": { + "$ref": "#/components/responses/429" + }, + "500": { + "$ref": "#/components/responses/500" + } + }, + "summary": "Historical quotes", + "tags": [ + "Stock" + ] + } + }, + "/v2/stocks/quotes/latest": { + "get": { + "description": "The latest quotes endpoint provides the latest best bid and ask prices for the given ticker symbols.\n", + "operationId": "StockLatestQuotes", + "parameters": [ + { + "$ref": "#/components/parameters/stock_symbols" + }, + { + "$ref": "#/components/parameters/stock_latest_feed" + }, + { + "$ref": "#/components/parameters/stock_currency" + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "examples": { + "quotes": { + "value": { + "quotes": { + "AAPL": { + "ap": 172.7, + "as": 1, + "ax": "Q", + "bp": 172.62, + "bs": 2, + "bx": "Q", + "c": [ + "R" + ], + "t": "2022-08-17T10:07:40.286587431Z", + "z": "C" + }, + "TSLA": { + "ap": 911.6, + "as": 1, + "ax": "Q", + "bp": 911.3, + "bs": 1, + "bx": "K", + "c": [ + "R" + ], + "t": "2022-08-17T10:07:49.387064037Z", + "z": "C" + } + } + } + } + }, + "schema": { + "$ref": "#/components/schemas/stock_latest_quotes_resp" + } + } + }, + "description": "OK", + "headers": { + "X-RateLimit-Limit": { + "$ref": "#/components/headers/ratelimit_limit" + }, + "X-RateLimit-Remaining": { + "$ref": "#/components/headers/ratelimit_remaining" + }, + "X-RateLimit-Reset": { + "$ref": "#/components/headers/ratelimit_reset" + } + } + }, + "400": { + "$ref": "#/components/responses/400" + }, + "401": { + "$ref": "#/components/responses/401" + }, + "403": { + "$ref": "#/components/responses/403" + }, + "429": { + "$ref": "#/components/responses/429" + }, + "500": { + "$ref": "#/components/responses/500" + } + }, + "summary": "Latest quotes", + "tags": [ + "Stock" + ] + } + }, + "/v2/stocks/snapshots": { + "get": { + "description": "The snapshot endpoint for multiple tickers provides the latest trade, latest quote, minute bar, daily bar, and previous daily bar data for each given ticker symbol.\n", + "operationId": "StockSnapshots", + "parameters": [ + { + "$ref": "#/components/parameters/stock_symbols" + }, + { + "$ref": "#/components/parameters/stock_latest_feed" + }, + { + "$ref": "#/components/parameters/stock_currency" + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "examples": { + "snapshots": { + "value": { + "AAPL": { + "dailyBar": { + "c": 173.03, + "h": 173.71, + "l": 171.6618, + "n": 515139, + "o": 172.62, + "t": "2022-08-16T04:00:00Z", + "v": 56457696, + "vw": 172.743391 + }, + "latestQuote": { + "ap": 172.7, + "as": 5, + "ax": "Q", + "bp": 172.6, + "bs": 2, + "bx": "Q", + "c": [ + "R" + ], + "t": "2022-08-17T10:18:27.052763263Z", + "z": "C" + }, + "latestTrade": { + "c": [ + "@", + "T" + ], + "i": 1011, + "p": 172.61, + "s": 160, + "t": "2022-08-17T10:18:24.114694956Z", + "x": "Q", + "z": "C" + }, + "minuteBar": { + "c": 172.69, + "h": 172.69, + "l": 172.69, + "n": 3, + "o": 172.69, + "t": "2022-08-17T10:16:00Z", + "v": 106, + "vw": 172.688113 + }, + "prevDailyBar": { + "c": 173.19, + "h": 173.39, + "l": 171.345, + "n": 501626, + "o": 171.5, + "t": "2022-08-15T04:00:00Z", + "v": 54091719, + "vw": 172.625371 + } + }, + "TSLA": { + "dailyBar": { + "c": 919.69, + "h": 944, + "l": 908.65, + "n": 805572, + "o": 935, + "t": "2022-08-16T04:00:00Z", + "v": 29378774, + "vw": 925.215087 + }, + "latestQuote": { + "ap": 911.75, + "as": 4, + "ax": "P", + "bp": 911.31, + "bs": 1, + "bx": "Q", + "c": [ + "R" + ], + "t": "2022-08-17T10:18:23.84717767Z", + "z": "C" + }, + "latestTrade": { + "c": [ + "@", + "T" + ], + "i": 2047, + "p": 911.99, + "s": 100, + "t": "2022-08-17T10:13:12.952851456Z", + "x": "P", + "z": "C" + }, + "minuteBar": { + "c": 911.99, + "h": 911.99, + "l": 911.99, + "n": 64, + "o": 911.99, + "t": "2022-08-17T10:13:00Z", + "v": 740, + "vw": 911.780405 + }, + "prevDailyBar": { + "c": 927.96, + "h": 939.4, + "l": 903.69, + "n": 825109, + "o": 905.32, + "t": "2022-08-15T04:00:00Z", + "v": 29786389, + "vw": 923.982755 + } + } + } + } + }, + "schema": { + "$ref": "#/components/schemas/stock_snapshots_resp" + } + } + }, + "description": "OK", + "headers": { + "X-RateLimit-Limit": { + "$ref": "#/components/headers/ratelimit_limit" + }, + "X-RateLimit-Remaining": { + "$ref": "#/components/headers/ratelimit_remaining" + }, + "X-RateLimit-Reset": { + "$ref": "#/components/headers/ratelimit_reset" + } + } + }, + "400": { + "$ref": "#/components/responses/400" + }, + "401": { + "$ref": "#/components/responses/401" + }, + "403": { + "$ref": "#/components/responses/403" + }, + "429": { + "$ref": "#/components/responses/429" + }, + "500": { + "$ref": "#/components/responses/500" + } + }, + "summary": "Snapshots", + "tags": [ + "Stock" + ] + } + }, + "/v2/stocks/trades": { + "get": { + "description": "The historical stock trades API provides trade data for a list of stock symbols between the specified dates.\n\nThe returned results are sorted by symbol first then by trade timestamp. This means that you are likely to see only one symbol in your first response if there are enough trades for that symbol to hit the limit you requested.\n\nIn these situations, if you keep requesting again with the `next_page_token` from the previous response, you will eventually reach the other symbols if any trades were found for them.", + "operationId": "StockTrades", + "parameters": [ + { + "$ref": "#/components/parameters/stock_symbols" + }, + { + "$ref": "#/components/parameters/start" + }, + { + "$ref": "#/components/parameters/end" + }, + { + "$ref": "#/components/parameters/limit" + }, + { + "$ref": "#/components/parameters/stock_asof" + }, + { + "$ref": "#/components/parameters/stock_historical_feed" + }, + { + "$ref": "#/components/parameters/stock_currency" + }, + { + "$ref": "#/components/parameters/page_token" + }, + { + "$ref": "#/components/parameters/sort" + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "examples": { + "trades": { + "value": { + "next_page_token": "QUFQTHwyMDIyLTAxLTAzVDA5OjAwOjAwLjI0NDgzOTY4MFp8UHwwOTIyMzM3MjAzNjg1NDc3NTgxMA==", + "trades": { + "AAPL": [ + { + "c": [ + "@", + "T" + ], + "i": 1, + "p": 178.26, + "s": 246, + "t": "2022-01-03T09:00:00.086175744Z", + "x": "P", + "z": "C" + }, + { + "c": [ + "@", + "F", + "T", + "I" + ], + "i": 2, + "p": 178.26, + "s": 1, + "t": "2022-01-03T09:00:00.24483968Z", + "x": "P", + "z": "C" + } + ] + } + } + } + }, + "schema": { + "$ref": "#/components/schemas/stock_trades_resp" + } + } + }, + "description": "OK", + "headers": { + "X-RateLimit-Limit": { + "$ref": "#/components/headers/ratelimit_limit" + }, + "X-RateLimit-Remaining": { + "$ref": "#/components/headers/ratelimit_remaining" + }, + "X-RateLimit-Reset": { + "$ref": "#/components/headers/ratelimit_reset" + } + } + }, + "400": { + "$ref": "#/components/responses/400" + }, + "401": { + "$ref": "#/components/responses/401" + }, + "403": { + "$ref": "#/components/responses/403" + }, + "429": { + "$ref": "#/components/responses/429" + }, + "500": { + "$ref": "#/components/responses/500" + } + }, + "summary": "Historical trades", + "tags": [ + "Stock" + ] + } + }, + "/v2/stocks/trades/latest": { + "get": { + "description": "The latest trades endpoint provides the latest trades for the given ticker symbols.\n\nTrades with any conditions that causes them to not update the bar price are excluded. For example a trade with condition `I` (odd lot) will never appear on this endpoint. You can find the complete list of excluded conditions in [this FAQ](https://docs.alpaca.markets/docs/market-data-faq#how-are-bars-aggregated).", + "operationId": "StockLatestTrades", + "parameters": [ + { + "$ref": "#/components/parameters/stock_symbols" + }, + { + "$ref": "#/components/parameters/stock_latest_feed" + }, + { + "$ref": "#/components/parameters/stock_currency" + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "examples": { + "trades": { + "value": { + "trades": { + "AAPL": { + "c": [ + "@", + "F", + "T" + ], + "i": 826, + "p": 172.78, + "s": 100, + "t": "2022-08-17T09:50:43.361102308Z", + "x": "Q", + "z": "C" + } + } + } + } + }, + "schema": { + "$ref": "#/components/schemas/stock_latest_trades_resp" + } + } + }, + "description": "OK", + "headers": { + "X-RateLimit-Limit": { + "$ref": "#/components/headers/ratelimit_limit" + }, + "X-RateLimit-Remaining": { + "$ref": "#/components/headers/ratelimit_remaining" + }, + "X-RateLimit-Reset": { + "$ref": "#/components/headers/ratelimit_reset" + } + } + }, + "400": { + "$ref": "#/components/responses/400" + }, + "401": { + "$ref": "#/components/responses/401" + }, + "403": { + "$ref": "#/components/responses/403" + }, + "429": { + "$ref": "#/components/responses/429" + }, + "500": { + "$ref": "#/components/responses/500" + } + }, + "summary": "Latest trades", + "tags": [ + "Stock" + ] + } + } + }, + "security": [ + { + "apiKey": [], + "apiSecret": [] + } + ], + "servers": [ + { + "description": "Production", + "url": "https://data.alpaca.markets" + }, + { + "description": "Sandbox", + "url": "https://data.sandbox.alpaca.markets" + } + ], + "tags": [ + { + "description": "Endpoints for stocks.", + "name": "Stock" + }, + { + "description": "Endpoints for option data.", + "name": "Option" + }, + { + "description": "Endpoints for indices.", + "name": "Index" + }, + { + "description": "Endpoints for cryptocurrencies.", + "name": "Crypto" + }, + { + "description": "Endpoints for crypto perpetual futures.", + "name": "Crypto perpetual futures" + }, + { + "description": "Endpoints for fixed income data.", + "name": "Fixed income" + }, + { + "description": "Endpoints for forex currency rates.", + "name": "Forex" + }, + { + "description": "Endpoints for getting company logo images.", + "name": "Logos" + }, + { + "description": "Endpoints for most active stocks and top movers.", + "name": "Screener" + }, + { + "description": "Endpoints for getting news articles about the stock market.", + "name": "News" + }, + { + "description": "Corporate actions (splits, dividends, etc.).", + "name": "Corporate actions" + } + ] +} diff --git a/module/trading/openapi.json b/module/trading/openapi.json new file mode 100644 index 00000000..013dbdfd --- /dev/null +++ b/module/trading/openapi.json @@ -0,0 +1,9291 @@ +{ + "components": { + "examples": { + "CryptoOrderResponse": { + "value": { + "asset_class": "crypto", + "asset_id": "a1733398-6acc-4e92-af24-0d0667f78713", + "canceled_at": null, + "client_order_id": "5b5d3d67-06ad-4ffa-af65-a117d0fc5a59", + "created_at": "2023-12-12T22:36:51.337711497Z", + "expired_at": null, + "extended_hours": false, + "failed_at": null, + "filled_at": null, + "filled_avg_price": null, + "filled_qty": "0", + "hwm": null, + "id": "38e482f3-79a8-4f75-a057-f07a1ec6a397", + "legs": null, + "limit_price": "2100", + "notional": null, + "order_class": "", + "order_type": "limit", + "qty": "0.02", + "replaced_at": null, + "replaced_by": null, + "replaces": null, + "side": "buy", + "source": null, + "status": "pending_new", + "stop_price": null, + "submitted_at": "2023-12-12T22:36:51.313261061Z", + "subtag": null, + "symbol": "ETH/USD", + "time_in_force": "gtc", + "trail_percent": null, + "trail_price": null, + "type": "limit", + "updated_at": "2023-12-12T22:36:51.337754768Z" + } + }, + "EquityOrderResponse": { + "value": { + "asset_class": "us_equity", + "asset_id": "b0b6dd9d-8b9b-48a9-ba46-b9d54906e415", + "canceled_at": null, + "client_order_id": "5680c4bc-9ac1-4a12-a44c-df427ba53032", + "created_at": "2023-12-12T22:31:24.668464435Z", + "expired_at": null, + "extended_hours": false, + "failed_at": null, + "filled_at": null, + "filled_avg_price": null, + "filled_qty": "0", + "hwm": null, + "id": "7b08df51-c1ac-453c-99f9-323a5f075f0d", + "legs": null, + "limit_price": "150", + "notional": null, + "order_class": "", + "order_type": "limit", + "qty": "2", + "replaced_at": null, + "replaced_by": null, + "replaces": null, + "side": "buy", + "source": null, + "status": "accepted", + "stop_price": null, + "submitted_at": "2023-12-12T22:31:24.577215743Z", + "subtag": null, + "symbol": "AAPL", + "time_in_force": "gtc", + "trail_percent": null, + "trail_price": null, + "type": "limit", + "updated_at": "2023-12-12T22:31:24.668464435Z" + } + }, + "IPOOrderResponse": { + "value": { + "asset_class": "ipo", + "asset_id": "9b6c7c1a-9eb2-4d4a-8a3a-1bf4c1d5cbaa", + "canceled_at": null, + "client_order_id": "0b3a90b6-6b5c-4d83-8c0e-9b3eaf4a8d11", + "created_at": "2025-09-15T13:30:42.117344821Z", + "expired_at": null, + "extended_hours": false, + "failed_at": null, + "filled_at": null, + "filled_avg_price": null, + "filled_qty": "0", + "hwm": null, + "id": "5e2a8f1a-9c10-4d3a-8c64-7f59c1d18c11", + "legs": null, + "limit_price": null, + "notional": "500", + "order_class": "", + "order_type": "market", + "qty": null, + "replaced_at": null, + "replaced_by": null, + "replaces": null, + "side": "buy", + "source": null, + "status": "accepted", + "stop_price": null, + "submitted_at": "2025-09-15T13:30:42.094518219Z", + "subtag": null, + "symbol": "FI111225", + "time_in_force": "gtc", + "trail_percent": null, + "trail_price": null, + "type": "market", + "updated_at": "2025-09-15T13:30:42.117344821Z" + } + }, + "MultilegOptionsOrderResponse": { + "value": { + "asset_class": "", + "asset_id": "", + "canceled_at": null, + "client_order_id": "646b1fe6-b212-4f54-94c6-429e7bcdee04", + "created_at": "2024-12-10T16:15:53.677230742Z", + "expired_at": null, + "extended_hours": false, + "failed_at": null, + "filled_at": "2024-12-10T16:15:53.694Z", + "filled_avg_price": "1.28", + "filled_qty": "1", + "hwm": null, + "id": "83f37e9f-6b1f-49ed-8fc6-3e6af716323f", + "legs": [ + { + "asset_class": "us_option", + "asset_id": "f0ea14b2-8a49-4e9b-89d1-894c6e518a76", + "canceled_at": null, + "client_order_id": "cc8cc104-fe43-476c-b25c-f62650fb73f9", + "created_at": "2024-12-10T16:15:53.677230742Z", + "expired_at": null, + "expires_at": "2024-12-10T21:00:00Z", + "extended_hours": false, + "failed_at": null, + "filled_at": "2024-12-10T16:15:53.694Z", + "filled_avg_price": "0.43", + "filled_qty": "3", + "hwm": null, + "id": "df4ff24a-c58a-4e37-8b9f-ef32b83a11f2", + "legs": null, + "limit_price": null, + "notional": null, + "order_class": "mleg", + "order_type": "", + "position_intent": "buy_to_open", + "qty": "3", + "ratio_qty": "3", + "replaced_at": null, + "replaced_by": null, + "replaces": null, + "side": "buy", + "source": null, + "status": "filled", + "stop_price": null, + "submitted_at": "2024-12-10T16:15:53.684952901Z", + "subtag": null, + "symbol": "AAPL241213C00250000", + "time_in_force": "day", + "trail_percent": null, + "trail_price": null, + "type": "", + "updated_at": "2024-12-10T16:15:53.725091158Z" + }, + { + "asset_class": "us_option", + "asset_id": "f89940db-eeb1-46e6-8f9b-bb1f27a0b395", + "canceled_at": null, + "client_order_id": "0bd2d36d-4af2-4dfb-8418-333a5d5026fa", + "created_at": "2024-12-10T16:15:53.677230742Z", + "expired_at": null, + "expires_at": "2024-12-10T21:00:00Z", + "extended_hours": false, + "failed_at": null, + "filled_at": "2024-12-10T16:15:53.694Z", + "filled_avg_price": "0.01", + "filled_qty": "1", + "hwm": null, + "id": "ecd91110-c34d-4e9d-a7bf-a9c27c40f8b5", + "legs": null, + "limit_price": null, + "notional": null, + "order_class": "mleg", + "order_type": "", + "position_intent": "sell_to_open", + "qty": "1", + "ratio_qty": "1", + "replaced_at": null, + "replaced_by": null, + "replaces": null, + "side": "sell", + "source": null, + "status": "filled", + "stop_price": null, + "submitted_at": "2024-12-10T16:15:53.684952901Z", + "subtag": null, + "symbol": "AAPL241213C00260000", + "time_in_force": "day", + "trail_percent": null, + "trail_price": null, + "type": "", + "updated_at": "2024-12-10T16:15:53.708983759Z" + } + ], + "limit_price": "10", + "notional": null, + "order_class": "mleg", + "order_type": "limit", + "qty": "1", + "replaced_at": null, + "replaced_by": null, + "replaces": null, + "side": "", + "source": null, + "status": "filled", + "stop_price": null, + "submitted_at": "2024-12-10T16:15:53.684952901Z", + "subtag": null, + "symbol": "", + "time_in_force": "day", + "trail_percent": null, + "trail_price": null, + "type": "limit", + "updated_at": "2024-12-10T16:15:53.725139688Z" + } + }, + "OptionOrderResponse": { + "value": { + "asset_class": "us_option", + "asset_id": "98359ef7-5124-49f3-85ea-5cf02df6defa", + "canceled_at": null, + "client_order_id": "58cd43a7-029e-457e-b77f-cd4f61f00f2a", + "created_at": "2023-12-12T21:35:49.102449524Z", + "expired_at": null, + "extended_hours": false, + "failed_at": null, + "filled_at": null, + "filled_avg_price": null, + "filled_qty": "0", + "hwm": null, + "id": "30a077fa-96f6-4f20-a052-4b921ee2f243", + "legs": null, + "limit_price": "10", + "notional": null, + "order_class": "simple", + "order_type": "limit", + "qty": "2", + "replaced_at": null, + "replaced_by": null, + "replaces": null, + "side": "buy", + "source": null, + "status": "pending_new", + "stop_price": null, + "submitted_at": "2023-12-12T21:35:49.056332248Z", + "subtag": null, + "symbol": "AAPL250620C00100000", + "time_in_force": "day", + "trail_percent": null, + "trail_price": null, + "type": "limit", + "updated_at": "2023-12-12T21:35:49.102504673Z" + } + } + }, + "headers": { + "ratelimit_limit": { + "description": "Request limit per minute.", + "example": 100, + "schema": { + "type": "integer" + } + }, + "ratelimit_remaining": { + "description": "Request limit per minute remaining.", + "example": 90, + "schema": { + "type": "integer" + } + }, + "ratelimit_reset": { + "description": "The UNIX epoch when the remaining quota changes.", + "example": 1674044551, + "schema": { + "type": "integer" + } + } + }, + "parameters": { + "PageToken": { + "description": "Used for pagination, this token retrieves the next page of results. It is obtained from the response of the preceding page when additional pages are available.", + "in": "query", + "name": "page_token", + "required": false, + "schema": { + "example": "MA==", + "type": "string" + } + }, + "end": { + "description": "The last date to retrieve data for (inclusive). Default: one week from the start date.\n", + "example": "2030-01-01", + "in": "query", + "name": "end", + "required": false, + "schema": { + "format": "date", + "type": "string" + } + }, + "legacy_date_type": { + "description": "Indicates what start and end mean. Default: TRADING. If TRADING is specified, returns a calendar whose trading date matches start, end. If SETTLEMENT is specified, returns the calendar whose settlement date matches start and end.\n", + "in": "query", + "name": "date_type", + "schema": { + "enum": [ + "TRADING", + "SETTLEMENT" + ], + "type": "string" + } + }, + "legacy_end": { + "description": "The last date to retrieve data for (inclusive).", + "in": "query", + "name": "end", + "schema": { + "format": "date-time", + "type": "string", + "x-go-type": "legacyTime" + } + }, + "legacy_start": { + "description": "The first date to retrieve data for (inclusive).", + "in": "query", + "name": "start", + "schema": { + "format": "date-time", + "type": "string", + "x-go-type": "legacyTime" + } + }, + "market": { + "description": "The market identifier. MIC, BIC, or acronym.\n\nAvailable market codes by region:\n\n**United States**\n- `BMO`: Bank of Montreal. US banking calendar (US operations).\n- `BNYM`: The Bank of New York Mellon. US banking calendar.\n- `BOATS`: Blue Ocean Alternative Trading System. US overnight trading.\n- `IEX`: Investors Exchange. US equities.\n- `IEXG`: Investors Exchange. US equities.\n- `NASDAQ`: National Association of Securities Dealers Automated Quotations. US equities.\n- `NYSE`: New York Stock Exchange. US equities.\n- `OCEA`: BlueOcean ATS. US overnight trading.\n- `OPRA`: Options Price Reporting Authority. US options.\n- `OTC`: Over-The-Counter. US OTC equities.\n- `OTCM`: OTC Markets. US OTC equities.\n- `SIFMA`: Securities Industry and Financial Markets Association. US bonds.\n- `XNAS`: NASDAQ. US equities.\n- `XNYS`: New York Stock Exchange. US equities.\n\n**Europe**\n- `CEUX`: Cboe CEUX Europe. European equities (Cboe Netherlands).\n- `CHIX`: Cboe CHIX Europe. European equities (Cboe UK).\n- `ISE`: Euronext Dublin. Irish equities.\n- `LSE`: London Stock Exchange. UK equities.\n- `MTA`: Euronext Milan. Italian equities.\n- `MTAA`: Euronext Milan. Italian equities.\n- `XAMS`: Euronext Amsterdam. Dutch equities.\n- `XBRU`: Euronext Brussels. Belgian equities.\n- `XDUB`: Euronext Dublin. Irish equities.\n- `XETR`: Frankfurt Stock Exchange. German equities.\n- `XETRA`: Frankfurt Stock Exchange. German equities.\n- `XLIS`: Euronext Lisbon. Portuguese equities.\n- `XLON`: London Stock Exchange. UK equities.\n- `XPAR`: Euronext Paris. French equities.\n\n**Asia & Middle East**\n- `HKEX`: Hong Kong Stock Exchange. Hong Kong equities.\n- `JPX`: Japan Exchange Group. Japanese equities.\n- `TADAWUL`: Saudi Stock Exchange. Saudi equities.\n- `XHKG`: Hong Kong Stock Exchange. Hong Kong equities.\n- `XSAU`: Saudi Stock Exchange. Saudi equities.\n- `XTKS`: Japan Exchange Group. Japanese equities.\n", + "in": "path", + "name": "market", + "required": true, + "schema": { + "$ref": "#/components/schemas/market" + } + }, + "markets": { + "description": "Comma-separated list of markets. Available market codes by region:\n\n**United States**\n- `BMO`: Bank of Montreal. US banking calendar (US operations).\n- `BNYM`: The Bank of New York Mellon. US banking calendar.\n- `BOATS`: Blue Ocean Alternative Trading System. US overnight trading.\n- `IEX`: Investors Exchange. US equities.\n- `IEXG`: Investors Exchange. US equities.\n- `NASDAQ`: National Association of Securities Dealers Automated Quotations. US equities.\n- `NYSE`: New York Stock Exchange. US equities.\n- `OCEA`: BlueOcean ATS. US overnight trading.\n- `OPRA`: Options Price Reporting Authority. US options.\n- `OTC`: Over-The-Counter. US OTC equities.\n- `OTCM`: OTC Markets. US OTC equities.\n- `SIFMA`: Securities Industry and Financial Markets Association. US bonds.\n- `XNAS`: NASDAQ. US equities.\n- `XNYS`: New York Stock Exchange. US equities.\n\n**Europe**\n- `CEUX`: Cboe CEUX Europe. European equities (Cboe Netherlands).\n- `CHIX`: Cboe CHIX Europe. European equities (Cboe UK).\n- `ISE`: Euronext Dublin. Irish equities.\n- `LSE`: London Stock Exchange. UK equities.\n- `MTA`: Euronext Milan. Italian equities.\n- `MTAA`: Euronext Milan. Italian equities.\n- `XAMS`: Euronext Amsterdam. Dutch equities.\n- `XBRU`: Euronext Brussels. Belgian equities.\n- `XDUB`: Euronext Dublin. Irish equities.\n- `XETR`: Frankfurt Stock Exchange. German equities.\n- `XETRA`: Frankfurt Stock Exchange. German equities.\n- `XLIS`: Euronext Lisbon. Portuguese equities.\n- `XLON`: London Stock Exchange. UK equities.\n- `XPAR`: Euronext Paris. French equities.\n\n**Asia & Middle East**\n- `HKEX`: Hong Kong Stock Exchange. Hong Kong equities.\n- `JPX`: Japan Exchange Group. Japanese equities.\n- `TADAWUL`: Saudi Stock Exchange. Saudi equities.\n- `XHKG`: Hong Kong Stock Exchange. Hong Kong equities.\n- `XSAU`: Saudi Stock Exchange. Saudi equities.\n- `XTKS`: Japan Exchange Group. Japanese equities.\n", + "example": "NYSE,LSE", + "in": "query", + "name": "markets", + "schema": { + "type": "string" + } + }, + "start": { + "description": "The first date to retrieve data for (inclusive). Default: today.\n", + "example": "2025-01-01", + "in": "query", + "name": "start", + "required": false, + "schema": { + "format": "date", + "type": "string" + } + }, + "time": { + "description": "Instead of the current time, use this time for the clock.", + "in": "query", + "name": "time", + "required": false, + "schema": { + "format": "date-time", + "type": "string" + } + }, + "timezone": { + "description": "Timezone of the times. Default: the timezone of the market.\n", + "in": "query", + "name": "timezone", + "required": false, + "schema": { + "enum": [ + "UTC" + ], + "type": "string" + } + } + }, + "responses": { + "400": { + "description": "One of the request parameters is invalid. See the returned message for details.\n", + "headers": { + "X-RateLimit-Limit": { + "$ref": "#/components/headers/ratelimit_limit" + }, + "X-RateLimit-Remaining": { + "$ref": "#/components/headers/ratelimit_remaining" + }, + "X-RateLimit-Reset": { + "$ref": "#/components/headers/ratelimit_reset" + } + } + }, + "401": { + "description": "Authentication headers are missing or invalid. Make sure you authenticate your request with a valid API key.\n" + }, + "403": { + "description": "The requested resource is forbidden.\n" + }, + "429": { + "description": "Too many requests. You hit the rate limit. Use the X-RateLimit-... response headers to make sure you're under the rate limit.\n", + "headers": { + "X-RateLimit-Limit": { + "$ref": "#/components/headers/ratelimit_limit" + }, + "X-RateLimit-Remaining": { + "$ref": "#/components/headers/ratelimit_remaining" + }, + "X-RateLimit-Reset": { + "$ref": "#/components/headers/ratelimit_reset" + } + } + }, + "500": { + "description": "Internal server error. We recommend retrying these later. If the issue persists, please contact us on Slack or on the Community Forum.\n" + } + }, + "schemas": { + "AcatcActivityV2": { + "allOf": [ + { + "$ref": "#/components/schemas/CommonNTAActivityV2" + }, + { + "$ref": "#/components/schemas/CommonAcatActivityV2" + } + ], + "description": "Automated customer account transfer service (cash)", + "type": "object" + }, + "AcatsActivityV2": { + "allOf": [ + { + "$ref": "#/components/schemas/CommonNTAActivityV2" + }, + { + "$ref": "#/components/schemas/CommonAcatActivityV2" + }, + { + "properties": { + "symbol": { + "description": "The symbol of the security involved with the activity", + "type": "string" + } + }, + "required": [ + "symbol" + ], + "type": "object" + } + ], + "description": "Automated customer account transfer service (stock)", + "type": "object" + }, + "Account": { + "description": "The account API serves important information related to an account, including account status, funds available for trade, funds available for withdrawal, and various flags relevant to an account's ability to trade. An account maybe be blocked for just for trades (trades_blocked flag) or for both trades and transfers (account_blocked flag) if Alpaca identifies the account to engaging in any suspicious activity. Please note that cryptocurrencies are not eligible assets to be used as collateral for margin accounts and will require the asset be traded using cash only. Prior to June 4th, 2026, in accordance with FINRA's pattern day trading rule, an account could be flagged for pattern day trading (pattern_day_trader flag), which would inhibit an account from placing any further day-trades. Fields related to pattern day trading have been deprecated following [FINRA's adoption of new Intraday Margin Standards](https://docs.alpaca.markets/us/docs/understanding-finras-new-intraday-margin-rule-and-the-end-of-pdt).\n", + "properties": { + "account_blocked": { + "description": "If true, the account activity by user is prohibited.", + "type": "boolean" + }, + "account_number": { + "description": "Account number.", + "type": "string" + }, + "accrued_fees": { + "description": "The fees collected.", + "type": "string" + }, + "balance_asof": { + "description": "The date of the snapshot for `last_*` fields", + "example": "2021-04-01", + "type": "string" + }, + "buying_power": { + "description": "Current available $ buying power; If multiplier = 4, this is your daytrade buying power which is calculated as (last_equity - (last) maintenance_margin) * 4; If multiplier = 2, buying_power = max(equity - initial_margin,0) * 2; If multiplier = 1, buying_power = cash", + "type": "string" + }, + "cash": { + "description": "Cash Balance\n", + "type": "string" + }, + "created_at": { + "description": "Timestamp this account was created at\n", + "format": "date-time", + "type": "string" + }, + "currency": { + "description": "USD\n", + "example": "USD", + "type": "string" + }, + "daytrade_count": { + "deprecated": true, + "description": "The current number of daytrades that have been made in the last 5 trading days (inclusive of today)", + "type": "integer", + "x-deprecation": { + "reason": "See https://docs.alpaca.markets/us/docs/understanding-finras-new-intraday-margin-rule-and-the-end-of-pdt", + "since": "2026-04-27T00:00:00Z", + "sunset": "2026-07-06T00:00:00Z" + } + }, + "daytrading_buying_power": { + "deprecated": true, + "description": "Your buying power for day trades (continuously updated value)", + "type": "string", + "x-deprecation": { + "reason": "See https://docs.alpaca.markets/us/docs/understanding-finras-new-intraday-margin-rule-and-the-end-of-pdt", + "since": "2026-04-27T00:00:00Z", + "sunset": "2026-07-06T00:00:00Z" + } + }, + "equity": { + "description": "Cash + long_market_value + short_market_value", + "type": "string" + }, + "id": { + "description": "Account Id.\n", + "format": "uuid", + "type": "string" + }, + "initial_margin": { + "description": "Reg T initial margin requirement (continuously updated value)", + "type": "string" + }, + "intraday_adjustments": { + "description": "The intraday adjustment by non_trade_activities such as fund deposit/withdraw.\n", + "example": "0", + "type": "string" + }, + "last_equity": { + "description": "Equity as of previous trading day at 16:00:00 ET", + "type": "string" + }, + "last_maintenance_margin": { + "description": "Your maintenance margin requirement on the previous trading day", + "type": "string" + }, + "long_market_value": { + "description": "Real-time MtM value of all long positions held in the account\n", + "type": "string" + }, + "maintenance_margin": { + "description": "Maintenance margin requirement (continuously updated value)", + "type": "string" + }, + "multiplier": { + "description": "Buying power multiplier that represents account margin classification; valid values 1 (standard limited margin account with 1x buying power), 2 (reg T margin account with 2x intraday and overnight buying power; this is the default for all non-PDT accounts with $2,000 or more equity), 4 (PDT account with 4x intraday buying power and 2x reg T overnight buying power)", + "type": "string" + }, + "non_marginable_buying_power": { + "description": "Current available non-margin dollar buying power", + "type": "string" + }, + "options_approved_level": { + "description": "The options trading level that was approved for this account.\n0=disabled, 1=Covered Call/Cash-Secured Put, 2=Long Call/Put, 3=Spreads/Straddles.\n", + "enum": [ + 0, + 1, + 2, + 3 + ], + "example": 3, + "type": "integer" + }, + "options_buying_power": { + "description": "Your buying power for options trading\n", + "type": "string" + }, + "options_trading_level": { + "description": "The effective options trading level of the account.\nThis is the minimum between account options_approved_level and account configurations max_options_trading_level.\n0=disabled, 1=Covered Call/Cash-Secured Put, 2=Long Call/Put, 3=Spreads/Straddles.\n", + "enum": [ + 0, + 1, + 2, + 3 + ], + "example": 3, + "type": "integer" + }, + "pattern_day_trader": { + "deprecated": true, + "description": "Whether or not the account has been flagged as a pattern day trader", + "type": "boolean", + "x-deprecation": { + "reason": "See https://docs.alpaca.markets/us/docs/understanding-finras-new-intraday-margin-rule-and-the-end-of-pdt", + "since": "2026-04-27T00:00:00Z", + "sunset": "2026-07-06T00:00:00Z" + } + }, + "pending_reg_taf_fees": { + "description": "Pending regulatory fees for the account.\n", + "type": "string" + }, + "pending_transfer_in": { + "description": "Cash pending transfer in.", + "type": "string" + }, + "pending_transfer_out": { + "description": "Cash pending transfer out.", + "type": "string" + }, + "portfolio_value": { + "description": "Total value of cash + holding positions (This field is deprecated. It is equivalent to the equity field.)", + "type": "string" + }, + "regt_buying_power": { + "description": "Your buying power under Regulation T (your excess equity - equity minus margin value - times your margin multiplier)\n", + "type": "string" + }, + "short_market_value": { + "description": "Real-time MtM value of all short positions held in the account", + "type": "string" + }, + "shorting_enabled": { + "description": "Flag to denote whether or not the account is permitted to short", + "type": "boolean" + }, + "sma": { + "description": "Value of special memorandum account (will be used at a later date to provide additional buying_power)", + "type": "string" + }, + "status": { + "$ref": "#/components/schemas/AccountStatus" + }, + "trade_suspended_by_user": { + "description": "User setting. If true, the account is not allowed to place orders.", + "type": "boolean" + }, + "trading_blocked": { + "description": "If true, the account is not allowed to place orders.\n", + "type": "boolean" + }, + "transfers_blocked": { + "description": "If true, the account is not allowed to request money transfers.", + "type": "boolean" + } + }, + "required": [ + "id", + "status" + ], + "title": "Account", + "type": "object", + "x-examples": { + "example-1": { + "account_blocked": false, + "account_number": "010203ABCD", + "balance_asof": "2023-09-27", + "buying_power": "262113.632", + "cash": "-23140.2", + "created_at": "2019-06-12T22:47:07.99658Z", + "currency": "USD", + "daytrade_count": 0, + "daytrading_buying_power": "262113.632", + "equity": "103820.56", + "id": "e6fe16f3-64a4-4921-8928-cadf02f92f98", + "initial_margin": "63480.38", + "intraday_adjustments": "0", + "last_equity": "103529.24", + "last_maintenance_margin": "38000.832", + "long_market_value": "126960.76", + "maintenance_margin": "38088.228", + "multiplier": "4", + "options_approved_level": 2, + "options_buying_power": "40340.18", + "options_trading_level": 1, + "pattern_day_trader": false, + "pending_reg_taf_fees": "0", + "portfolio_value": "103820.56", + "regt_buying_power": "80680.36", + "short_market_value": "0", + "shorting_enabled": true, + "sma": "0", + "status": "ACTIVE", + "trade_suspended_by_user": false, + "trading_blocked": false, + "transfers_blocked": false + } + } + }, + "AccountConfigurations": { + "description": "The account configuration API provides custom configurations about your trading account settings. These configurations control various allow you to modify settings to suit your trading needs.", + "properties": { + "disable_overnight_trading": { + "description": "If true, overnight trading is disabled.", + "type": "boolean" + }, + "dtbp_check": { + "deprecated": true, + "description": "both, entry, or exit. Controls Day Trading Margin Call (DTMC) checks.", + "enum": [ + "both", + "entry", + "exit" + ], + "type": "string", + "x-deprecation": { + "reason": "See https://docs.alpaca.markets/us/docs/understanding-finras-new-intraday-margin-rule-and-the-end-of-pdt", + "since": "2026-04-27T00:00:00Z", + "sunset": "2026-07-06T00:00:00Z" + } + }, + "fractional_trading": { + "description": "If true, account is able to participate in fractional trading", + "type": "boolean" + }, + "max_margin_multiplier": { + "description": "Can be \"1\", \"2\", or \"4\"", + "type": "string" + }, + "max_options_trading_level": { + "description": "The desired maximum options trading level. 0=disabled, 1=Covered Call/Cash-Secured Put, 2=Long Call/Put, 3=Spreads/Straddles.", + "enum": [ + 0, + 1, + 2, + 3 + ], + "type": "integer" + }, + "no_shorting": { + "description": "If true, account becomes long-only mode.", + "type": "boolean" + }, + "pdt_check": { + "deprecated": true, + "description": "`both`, `entry`, or `exit`. If entry orders will be rejected on entering a position if it could result in PDT being set for the account. exit will reject exiting orders if they would result in PDT being set.", + "example": "entry", + "type": "string", + "x-deprecation": { + "reason": "See https://docs.alpaca.markets/us/docs/understanding-finras-new-intraday-margin-rule-and-the-end-of-pdt", + "since": "2026-04-27T00:00:00Z", + "sunset": "2026-07-06T00:00:00Z" + } + }, + "ptp_no_exception_entry": { + "description": "If set to true then Alpaca will accept orders for PTP symbols with no exception. Default is false.", + "type": "boolean" + }, + "suspend_trade": { + "description": "If true, new orders are blocked.", + "type": "boolean" + }, + "trade_confirm_email": { + "description": "all or none. If none, emails for order fills are not sent.", + "type": "string" + } + }, + "title": "AccountConfigurations", + "type": "object", + "x-examples": { + "example-1": { + "disable_overnight_trading": false, + "dtbp_check": "entry", + "fractional_trading": true, + "max_margin_multiplier": "4", + "no_shorting": false, + "pdt_check": "entry", + "suspend_trade": false, + "trade_confirm_email": "all" + } + } + }, + "AccountStatus": { + "description": "An enum representing the various possible account status values.\n\nMost likely, the account status is ACTIVE unless there is any problem. The account status may get in ACCOUNT_UPDATED when personal information is being updated from the dashboard, in which case you may not be allowed trading for a short period of time until the change is approved.\n\n- ONBOARDING\n The account is onboarding.\n- SUBMISSION_FAILED\n The account application submission failed for some reason.\n- SUBMITTED\n The account application has been submitted for review.\n- ACCOUNT_UPDATED\n The account information is being updated.\n- APPROVAL_PENDING\n The final account approval is pending.\n- ACTIVE\n The account is active for trading.\n- REJECTED\n The account application has been rejected.", + "enum": [ + "ONBOARDING", + "SUBMISSION_FAILED", + "SUBMITTED", + "ACCOUNT_UPDATED", + "APPROVAL_PENDING", + "ACTIVE", + "REJECTED" + ], + "example": "ACTIVE", + "title": "AccountStatus", + "type": "string", + "x-examples": { + "example-1": "ACTIVE" + } + }, + "ActivityEventV2": { + "allOf": [ + { + "$ref": "#/components/schemas/ActivityEventV2CommonFields" + }, + { + "properties": { + "details": { + "oneOf": [ + { + "$ref": "#/components/schemas/ActivityV2DetailTRD" + }, + { + "$ref": "#/components/schemas/ActivityV2DetailNTA" + } + ] + } + }, + "required": [ + "details" + ], + "type": "object" + } + ], + "description": "Represents an account activity, sent over the Event Streaming API.", + "title": "ActivityEventV2", + "type": "object" + }, + "ActivityEventV2CommonFields": { + "description": "Represents the common fields for all Activity V2 Events", + "properties": { + "activity_subtype": { + "description": "Sub category for activity type, if any", + "type": "string" + }, + "activity_type": { + "description": "The type of the activity, which can be trade or any of the non trade activities", + "type": "string" + }, + "at": { + "description": "Timestamp of event", + "format": "date-time", + "minLength": 1, + "type": "string" + }, + "currency": { + "description": "Currency code in ISO format", + "type": "string" + }, + "event_id": { + "description": "Lexically sortable, monotonically increasing character string", + "format": "ulid", + "type": "string" + }, + "executed_at": { + "description": "Execution time for the activity event", + "format": "date-time", + "minLength": 1, + "type": "string" + }, + "net_amount": { + "description": "The net amount of money (positive or negative) associated with the activity", + "format": "decimal", + "type": "string" + }, + "previous_id": { + "description": "Previous ID is presented if this activity corrects or cancels a previous trade or non trade activity. It contains execution_id or trns_id respectively", + "format": "uuid", + "type": "string" + }, + "price": { + "description": "The price of the security involved with the activity", + "format": "decimal", + "type": "string" + }, + "qty": { + "description": "The quantity of the security involved with the activity", + "format": "decimal", + "type": "string" + }, + "ref_id": { + "description": "The unique identifier for the activity. For trades, the execution_id is used, for other activities the trns_id is used.", + "format": "uuid", + "type": "string" + }, + "settle_date": { + "description": "Date when the activity settled", + "format": "date", + "minLength": 1, + "type": "string" + }, + "status": { + "description": "Status of the activity", + "type": "string" + }, + "swap_fee_bps": { + "description": "Currency conversion fee rate base-point in case of local currency activity", + "format": "decimal", + "type": "string" + }, + "swap_rate": { + "description": "Conversion rate for local currency activities", + "format": "decimal", + "type": "string" + } + }, + "required": [ + "at", + "event_id", + "activity_type", + "executed_at", + "status", + "settle_date", + "currency", + "ref_id" + ], + "type": "object" + }, + "ActivitySubType": { + "description": "Represents a more specific classification to the `activity_type`.\nThis field is optional and may not always be populated, depending on the activity type and the available data.\nEach `activity_type` has a set of valid `activity_sub_type` values.\n\nFull mapping of `activity_type` to `activity_sub_type`:\n\n- **DIV**: Dividend activity sub-types:\n - **CDIV**: Cash Dividend\n - **SDIV**: Stock Dividend\n - **SPD**: Substitute Payment In Lieu Of Dividend\n\n- **FEE**: Fee-related activity sub-types:\n - **REG**: Regulatory Fee\n - **TAF**: Trading Activity Fee\n - **LCT**: Local Currency Trading Fee\n - **ORF**: Options Regulatory Fee\n - **OCC**: Options Clearing Corporation Fee\n - **NRC**: Non-Retail Commission Fee\n - **NRV**: Non-Retail Venue Fee\n - **COM**: Commission\n - **CAT**: Consolidated Audit Trail Fee\n\n- **INT**: Interest-related activity sub-types:\n - **MGN**: Margin Interest\n - **CDT**: Credit Interest\n - **SWP**: Sweep Interest\n - **QII**: Qualified Interest\n\n- **MA**: Merger and Acquisition activity sub-types:\n - **CMA**: Cash Merger\n - **SMA**: Stock Merger\n - **SCMA**: Stock & Cash Merger\n\n- **NC**: Name Change activity sub types\n - **SNC**: Symbol Name Change\n - **CNC**: CUSIP Name Change\n - **SCNC**: Symbol & CUSIP Name Change\n\n- **OPCA**: Option Corporate Action activity sub-types:\n - **DIV.CDIV**: Cash Dividend\n - **DIV.SDIV**: Stock Dividend\n - **MA.CMA**: Cash Merger\n - **MA.SMA**: Stock Merger\n - **MA.SCMA**: Stock & Cash Merger\n - **NC.CNC**: CUSIP Name Change\n - **NC.SNC**: Symbol Name Change\n - **NC.SCNC**: Symbol & CUSIP Name Change\n - **SPIN**: Spin-off\n - **SPLIT.FSPLIT**: Forward Stock Split\n - **SPLIT.RSPLIT**: Reverse Stock Split\n - **SPLIT.USPLIT**: Unit Split\n\n- **REORG**: Reorganization activity sub-types:\n - **WRM**: Worthless Removal\n\n- **SPLIT**: Stock Split activity sub-types:\n - **FSPLIT**: Forward Stock Split\n - **RSPLIT**: Reverse Stock Split\n - **USPLIT**: Unit Split\n\n- **VOF**: Voluntary Offering activity sub-types:\n - **VTND**: Tender Offer\n - **VWRT**: Warrant Exercise\n - **VRGT**: Rights Offer\n - **VEXH**: Exchange Offer\n\n- **WH**: Withholding activity sub-types:\n - **SWH**: State Withholding\n - **FWH**: Federal Withholding\n - **SLWH**: Sales Withholding", + "title": "ActivitySubType", + "type": "string" + }, + "ActivityType": { + "description": "- FILL\n Order fills (both partial and full fills)\n\n- TRANS\n Cash transactions (both CSD and CSW)\n\n- MISC\n Miscellaneous or rarely used activity types (All types except those in TRANS, DIV, or FILL)\n\n- ACATC\n ACATS IN/OUT (Cash)\n\n- ACATS\n ACATS IN/OUT (Securities)\n\n- CFEE\n Crypto fee\n\n- CGD\n Capital gain distribution\n\n- CSD\n Cash deposit(+)\n\n- CSW\n Cash withdrawal(-)\n\n- DIV\n Dividends\n\n- DIVCGL\n Dividend (capital gain long term)\n\n- DIVCGS\n Dividend (capital gain short term)\n\n- DIVFEE\n Dividend fee\n\n- DIVFT\n Dividend adjusted (Foreign Tax Withheld)\n\n- DIVNRA\n Dividend adjusted (NRA Withheld)\n\n- DIVROC\n Dividend return of capital\n\n- DIVTW\n Dividend adjusted (Tefra Withheld)\n\n- DIVTXEX\n Dividend (tax exempt)\n\n- FEE\n Fee denominated in USD\n\n- INT\n Interest (credit/margin)\n\n- INTNRA\n Interest adjusted (NRA Withheld)\n\n- INTTW\n Interest adjusted (Tefra Withheld)\n\n- JNL\n Journal entry\n\n- JNLC\n Journal entry (cash)\n\n- JNLS\n Journal entry (stock)\n\n- MA\n Merger/Acquisition\n\n- NC\n Name change\n\n- OPASN\n Option assignment\n\n- OPCA\n Option corporate action\n\n- OPCSH\n Option cash deliverable for non-standard contracts\n\n- OPEXC\n Option exercise\n\n- OPEXP\n Option expiration\n\n- OPTRD\n Option trade\n\n- PTC\n Pass Thru Charge\n\n- PTR\n Pass Thru Rebate\n\n- REORG\n Reorg CA\n\n- SPIN\n Stock spinoff\n\n- SPLIT\n Stock split\n\n- FOPT\n Free of Payment Transfers", + "enum": [ + "FILL", + "TRANS", + "MISC", + "ACATC", + "ACATS", + "CFEE", + "CGD", + "CSD", + "CSW", + "DIV", + "DIVCGL", + "DIVCGS", + "DIVFEE", + "DIVFT", + "DIVNRA", + "DIVROC", + "DIVTW", + "DIVTXEX", + "FEE", + "INT", + "INTNRA", + "INTTW", + "JNL", + "JNLC", + "JNLS", + "MA", + "NC", + "OPASN", + "OPCA", + "OPCSH", + "OPEXC", + "OPEXP", + "OPTRD", + "PTC", + "PTR", + "REORG", + "SPIN", + "SPLIT", + "FOPT" + ], + "title": "ActivityType", + "type": "string", + "x-examples": { + "example-1": "FILL" + } + }, + "ActivityV2DetailNTA": { + "allOf": [ + { + "oneOf": [ + { + "$ref": "#/components/schemas/DIVSPDActivityV2" + }, + { + "$ref": "#/components/schemas/CDIVActivityV2" + }, + { + "$ref": "#/components/schemas/SDIVActivityV2" + }, + { + "$ref": "#/components/schemas/ForwardSplitActivityV2" + }, + { + "$ref": "#/components/schemas/ReverseSplitActivityV2" + }, + { + "$ref": "#/components/schemas/UnitSplitActivityV2" + }, + { + "$ref": "#/components/schemas/SpinoffActivityV2" + }, + { + "$ref": "#/components/schemas/MAActivityV2" + }, + { + "$ref": "#/components/schemas/NCActivityV2" + }, + { + "$ref": "#/components/schemas/FixedIncomeRedemptionActivityV2" + }, + { + "$ref": "#/components/schemas/RightsDistributionActivityV2" + }, + { + "$ref": "#/components/schemas/RightsSubscriptionElectionActivityV2" + }, + { + "$ref": "#/components/schemas/TenderOfferActivityV2" + }, + { + "$ref": "#/components/schemas/ExchangeOfferActivityV2" + }, + { + "$ref": "#/components/schemas/WarrantExerciseElectionActivityV2" + }, + { + "$ref": "#/components/schemas/WRMActivityV2" + }, + { + "$ref": "#/components/schemas/OpcaCDIVActivityV2" + }, + { + "$ref": "#/components/schemas/OpcaSDIVActivityV2" + }, + { + "$ref": "#/components/schemas/OpcaMAActivityV2" + }, + { + "$ref": "#/components/schemas/OpcaNCActivityV2" + }, + { + "$ref": "#/components/schemas/OpcaSPINActivityV2" + }, + { + "$ref": "#/components/schemas/OpcaFSPLITActivityV2" + }, + { + "$ref": "#/components/schemas/OpcaUSPLITActivityV2" + }, + { + "$ref": "#/components/schemas/OpcaRSPLITActivityV2" + }, + { + "$ref": "#/components/schemas/OPASNActivityV2" + }, + { + "$ref": "#/components/schemas/OPEXCActivityV2" + }, + { + "$ref": "#/components/schemas/OPEXPActivityV2" + }, + { + "$ref": "#/components/schemas/OPTRDActivityV2" + }, + { + "$ref": "#/components/schemas/AcatsActivityV2" + }, + { + "$ref": "#/components/schemas/AcatcActivityV2" + }, + { + "$ref": "#/components/schemas/FOPTActivityV2" + }, + { + "$ref": "#/components/schemas/DIVNRAActivityV2" + }, + { + "$ref": "#/components/schemas/JNLSActivityV2" + }, + { + "$ref": "#/components/schemas/JNLCActivityV2" + }, + { + "$ref": "#/components/schemas/CSWActivityV2" + }, + { + "$ref": "#/components/schemas/FEEActivityV2" + } + ] + } + ], + "type": "object" + }, + "ActivityV2DetailTRD": { + "description": "Activity details for a fill or partial_fill event", + "properties": { + "asset_id": { + "description": "Asset ID (For options this represents the option contract ID)", + "format": "uuid", + "type": "string" + }, + "client_order_id": { + "description": "Order ID provided by the customer", + "type": "string" + }, + "commission": { + "description": "Commission to collect from the account holder", + "format": "decimal", + "type": "string" + }, + "cum_qty": { + "description": "Total filled quantity on the order", + "format": "decimal", + "type": "string" + }, + "cusip": { + "description": "CUSIP of the security involved with the activity", + "type": "string" + }, + "execution_type": { + "description": "The execution type", + "enum": [ + "fill", + "trade_correct", + "trade_bust" + ], + "type": "string" + }, + "leaves_qty": { + "description": "Unfilled quantity on the order, when order is filled value could be 0", + "format": "decimal", + "type": "string" + }, + "order_id": { + "description": "Order ID generated by Alpaca", + "format": "uuid", + "type": "string" + }, + "order_status": { + "description": "Identifies the current status of the order", + "type": "string" + }, + "side": { + "description": "Represents what side of the transaction an order was on", + "enum": [ + "buy", + "sell" + ], + "type": "string" + }, + "symbol": { + "description": "The symbol of the security involved with the activity", + "type": "string" + } + }, + "required": [ + "order_id", + "side", + "symbol", + "asset_id", + "leaves_qty", + "cum_qty", + "order_status", + "execution_type" + ], + "type": "object" + }, + "AdvancedInstructions": { + "description": "Advanced instructions for Elite Smart Router: https://docs.alpaca.markets/docs/alpaca-elite-smart-router", + "properties": { + "algorithm": { + "description": "The advanced routing algorithm to use for the order", + "enum": [ + "DMA", + "TWAP", + "VWAP" + ], + "example": "DMA", + "type": "string" + }, + "destination": { + "description": "Target exchange for order execution", + "enum": [ + "NYSE", + "NASDAQ", + "ARCA" + ], + "example": "NYSE", + "type": "string" + }, + "display_qty": { + "description": "Maximum shares/contracts displayed on the exchange at any time. Must be in round lot increments", + "example": "100", + "format": "decimal", + "type": "string" + }, + "end_time": { + "description": "When the algorithm is to be done executing. Must be within current market trading hours", + "example": "2025-07-21T15:30:00-04:00", + "format": "date-time", + "type": "string" + }, + "max_percentage": { + "description": "Maximum percentage of the ticker's period volume this order might participate in. Must be 0 < max_percentage < 1, with up to 3 decimal points precision.", + "example": "0.314", + "format": "decimal", + "type": "string" + }, + "start_time": { + "description": "When the algorithm is to start executing. Must be within current market trading hours", + "example": "2025-07-21T09:30:00-04:00", + "format": "date-time", + "type": "string" + } + }, + "title": "AdvancedInstructions", + "type": "object", + "x-examples": { + "example-1": { + "algorithm": "DMA", + "destination": "NYSE", + "display_qty": 100 + }, + "example-2": { + "algorithm": "TWAP", + "end_time": "2025-07-21T15:30:00-04:00", + "max_percentage": 0.314, + "start_time": "2025-07-21T09:30:00-04:00" + }, + "example-3": { + "algorithm": "VWAP", + "end_time": "2025-07-21T15:30:00-04:00", + "max_percentage": 0.314, + "start_time": "2025-07-21T09:30:00-04:00" + } + } + }, + "AssetClass": { + "description": "This represents the category to which the asset belongs to. It serves to identify the nature of the financial instrument, with options including \"us_equity\" for U.S. equities, \"us_option\" for U.S. options, \"crypto\" for cryptocurrencies, and \"ipo\" for IPO indications of interest/orders. This `asset_class: ipo` value is distinct from the assets API `attributes: [\"ipo\"]` flag.", + "enum": [ + "us_equity", + "us_option", + "crypto", + "ipo" + ], + "example": "us_equity", + "title": "AssetClass", + "type": "string", + "x-examples": { + "example-1": "us_equity" + } + }, + "Assets": { + "description": "The assets API serves as the master list of assets available for trade and data consumption from Alpaca. Assets are sorted by asset class, exchange and symbol. Some assets are only available for data consumption via Polygon, and are not tradable with Alpaca. These assets will be marked with the flag tradable=false.\n", + "properties": { + "attributes": { + "description": "Unique characteristics of the asset. Supported values:\n- `ptp_no_exception`: Asset is a Publicly Traded Partnership (PTP) without a qualified notice; non-U.S. customers may incur 10% withholding on gross proceeds as per IRS guidance, and are blocked from being purchased by default.\n- `ptp_with_exception`: Users can open positions in these PTPs without general restrictions.\n- `ipo`: Accepting limit orders only before the stock begins trading on the secondary market.\n- `has_options`: The underlying equity has listed options available on the platform. Note: if the equity had inactive/expired contracts in the past, this will still show up.\n- `options_late_close`: Indicates the underlying asset's options contracts close at 4:15pm ET instead of the standard 4:00pm ET.\n- `fractional_eh_enabled`: Indicates the asset accepts fractional orders during extended hours sessions (pre-market, post-market, and overnight if enabled).\n- `overnight_tradable`: Asset is eligible for overnight (24x5) trading in supported venues on the platform.\n- `overnight_halted`: Asset is eligible for overnight trading but is currently halted/blocked for overnight sessions due to risk, corporate action, compliance, or venue constraints.", + "example": [ + "ptp_no_exception", + "ipo" + ], + "items": { + "enum": [ + "ptp_no_exception", + "ptp_with_exception", + "ipo", + "has_options", + "options_late_close", + "fractional_eh_enabled", + "overnight_tradable", + "overnight_halted" + ], + "type": "string" + }, + "type": "array" + }, + "borrow_status": { + "description": "Borrow status for US equity assets. This field is omitted for non-US-equity assets.", + "enum": [ + "easy_to_borrow", + "hard_to_borrow" + ], + "example": "easy_to_borrow", + "type": "string" + }, + "class": { + "$ref": "#/components/schemas/AssetClass" + }, + "cusip": { + "description": "The CUSIP identifier for the asset (US Equities only).\nTo request a specific CUSIP, please reach out to Alpaca support.\n", + "example": "987654321", + "nullable": true, + "type": "string" + }, + "easy_to_borrow": { + "deprecated": true, + "description": "**deprecated**: Please use borrow_status instead.\nAsset is easy-to-borrow or not (filtering for easy_to_borrow = True is the best way to check whether the name is currently available to short at Alpaca).\n", + "type": "boolean", + "x-deprecation": { + "reason": "Use borrow_status instead.", + "since": "2026-06-22", + "sunset": "2026-09-22" + } + }, + "exchange": { + "$ref": "#/components/schemas/Exchange" + }, + "fractionable": { + "description": "Asset is fractionable or not", + "type": "boolean" + }, + "id": { + "description": "Asset ID", + "format": "uuid", + "type": "string" + }, + "maintenance_margin_requirement": { + "deprecated": true, + "description": "**deprecated**: Please use margin_requirement_long or margin_requirement_short instead. Note that these fields are of type string.\nShows the margin requirement percentage for the asset (equities only).\n", + "type": "number" + }, + "margin_requirement_long": { + "description": "The margin requirement percentage for the asset's long positions (equities only).", + "type": "string" + }, + "margin_requirement_short": { + "description": "The margin requirement percentage for the asset's short positions (equities only).", + "type": "string" + }, + "marginable": { + "description": "Asset is marginable or not", + "type": "boolean" + }, + "name": { + "description": "The official name of the asset", + "minLength": 1, + "type": "string" + }, + "shortable": { + "description": "Asset is shortable or not", + "type": "boolean" + }, + "status": { + "description": "active or inactive", + "enum": [ + "active", + "inactive" + ], + "example": "active", + "type": "string" + }, + "symbol": { + "description": "The symbol of the asset", + "example": "AAPL", + "type": "string" + }, + "tradable": { + "description": "Asset is tradable on Alpaca or not", + "type": "boolean" + } + }, + "required": [ + "id", + "class", + "exchange", + "symbol", + "name", + "status", + "tradable", + "marginable", + "shortable", + "easy_to_borrow", + "fractionable" + ], + "title": "Assets", + "type": "object", + "x-examples": { + "example-1": { + "borrow_status": "easy_to_borrow", + "class": "us_equity", + "easy_to_borrow": true, + "exchange": "NASDAQ", + "fractionable": true, + "id": "b0b6dd9d-8b9b-48a9-ba46-b9d54906e415", + "marginable": true, + "name": "Apple Inc. Common Stock", + "shortable": true, + "status": "active", + "symbol": "AAPL", + "tradable": true + } + } + }, + "CDIVActivityV2": { + "allOf": [ + { + "$ref": "#/components/schemas/CommonCaActivityV2" + }, + { + "$ref": "#/components/schemas/CommonCDIVActivityV2" + }, + { + "properties": { + "cash_payout": { + "description": "Total cash amount paid", + "type": "string" + }, + "entitled_qty": { + "description": "Quantity of shares entitled to receive the dividend", + "type": "string" + } + }, + "required": [ + "entitled_qty", + "cash_payout" + ], + "type": "object" + } + ], + "description": "Cash dividend", + "type": "object" + }, + "CSWActivityV2": { + "allOf": [ + { + "$ref": "#/components/schemas/CommonNTAActivityV2" + }, + { + "properties": { + "bank_transaction_id": { + "description": "The bank transaction's ID", + "format": "uuid", + "type": "string" + } + }, + "type": "object" + } + ], + "description": "Cash withdrawal", + "type": "object" + }, + "CanceledOrderResponse": { + "description": "Represents the result of a request to cancel and order", + "properties": { + "id": { + "description": "orderId", + "format": "uuid", + "type": "string" + }, + "status": { + "description": "http response code", + "example": 200, + "type": "integer" + } + }, + "title": "CanceledOrderResponse", + "type": "object", + "x-examples": { + "example-1": { + "id": "d56ba3ea-6d04-48ce-8175-817e242ee608", + "status": 200 + } + } + }, + "CommonAcatActivityV2": { + "properties": { + "external_id": { + "description": "The ID that DTCC assigned to this transfer", + "type": "string" + }, + "hold_date": { + "description": "Hold date when the transfers settle", + "format": "date", + "minLength": 1, + "type": "string" + }, + "request_id": { + "description": "The ID for original ACATS request", + "format": "uuid", + "type": "string" + } + }, + "required": [ + "external_id", + "request_id" + ], + "type": "object" + }, + "CommonCDIVActivityV2": { + "properties": { + "cusip": { + "description": "The CUSIP of the security involved with the activity", + "type": "string" + }, + "due_bill_off_date": { + "description": "When due bills stop applying for this event", + "format": "date", + "minLength": 1, + "type": "string" + }, + "due_bill_on_date": { + "description": "When due bills begin to apply for this event", + "format": "date", + "minLength": 1, + "type": "string" + }, + "ex_date": { + "description": "The ex_date for this corporate action", + "format": "date", + "minLength": 1, + "type": "string" + }, + "foreign": { + "description": "Indicates if related to a non-US security", + "type": "boolean" + }, + "payable_date": { + "description": "The payable_date for this corporate action", + "format": "date", + "minLength": 1, + "type": "string" + }, + "rate": { + "description": "Dividend rate per share", + "type": "string" + }, + "record_date": { + "description": "The record_date for this corporate action", + "format": "date", + "minLength": 1, + "type": "string" + }, + "special": { + "description": "Indicates if this is a special dividend", + "type": "boolean" + }, + "symbol": { + "description": "The symbol of the security involved with the activity", + "type": "string" + } + }, + "required": [ + "symbol", + "cusip", + "rate", + "foreign", + "special" + ], + "type": "object" + }, + "CommonCaActivityV2": { + "allOf": [ + { + "$ref": "#/components/schemas/CommonNTAActivityV2" + }, + { + "properties": { + "ca_id": { + "description": "The unique identifier for this corporate action", + "format": "uuid", + "type": "string" + }, + "position_date": { + "description": "The position_date for this corporate action", + "format": "date", + "minLength": 1, + "type": "string" + }, + "reorg_id": { + "description": "The reorg identifier, if present in the source corporate action definition", + "type": "string" + } + }, + "required": [ + "position_date" + ], + "type": "object" + } + ], + "type": "object" + }, + "CommonJournalActivityV2": { + "allOf": [ + { + "$ref": "#/components/schemas/CommonNTAActivityV2" + }, + { + "properties": { + "journal_id": { + "description": "The journal's ID", + "format": "uuid", + "type": "string" + } + }, + "type": "object" + } + ], + "type": "object" + }, + "CommonMAActivityV2": { + "properties": { + "acquiree_cusip": { + "description": "CUSIP of the acquiree", + "type": "string" + }, + "acquiree_rate": { + "description": "Rate of the acquiree", + "type": "string" + }, + "acquiree_symbol": { + "description": "Symbol of the acquiree", + "type": "string" + }, + "acquirer_cusip": { + "description": "CUSIP of the acquirer", + "type": "string" + }, + "acquirer_rate": { + "description": "Rate of the acquirer", + "type": "string" + }, + "acquirer_symbol": { + "description": "Symbol of the acquirer", + "type": "string" + }, + "effective_date": { + "description": "When the merger/acquisition becomes effective", + "format": "date", + "minLength": 1, + "type": "string" + }, + "payable_date": { + "description": "The payable date", + "format": "date", + "minLength": 1, + "type": "string" + } + }, + "required": [ + "acquiree_cusip", + "acquiree_symbol", + "effective_date", + "payable_date" + ], + "type": "object" + }, + "CommonNCActivityV2": { + "properties": { + "new_cusip": { + "description": "New CUSIP for the name change", + "type": "string" + }, + "new_symbol": { + "description": "New symbol for the name change", + "type": "string" + }, + "old_cusip": { + "description": "Old CUSIP for the name change", + "type": "string" + }, + "old_symbol": { + "description": "Old symbol for the name change", + "type": "string" + } + }, + "required": [ + "old_cusip", + "old_symbol", + "new_cusip", + "new_symbol" + ], + "type": "object" + }, + "CommonNTAActivityV2": { + "properties": { + "group_id": { + "description": "Optional group ID which can help grouping together related activities", + "format": "uuid", + "type": "string" + }, + "system_date": { + "description": "The date when the activity was booked", + "format": "date", + "minLength": 1, + "type": "string" + } + }, + "required": [ + "system_date" + ], + "type": "object" + }, + "CommonOPCAActivityV2": { + "allOf": [ + { + "$ref": "#/components/schemas/CommonCaActivityV2" + }, + { + "properties": { + "new_contract_symbol": { + "description": "The new contract symbol", + "type": "string" + }, + "new_qty": { + "description": "used when the old contract's quantity is not equal to the new contract's quantity. Mutually exclusive with 'qty'.", + "type": "string" + }, + "old_contract_symbol": { + "description": "The old contract symbol", + "type": "string" + }, + "old_qty": { + "description": "used when the old contract's quantity is not equal to the new contract's quantity. Mutually exclusive with 'qty'.", + "type": "string" + }, + "qty": { + "description": "used when the old contract's quantity is equal to the new contract's quantity. Mutually exclusive with 'old_qty' and 'new_qty'", + "type": "string" + } + }, + "required": [ + "old_contract_symbol", + "new_contract_symbol" + ], + "type": "object" + } + ], + "type": "object" + }, + "CommonOptionsActivityV2": { + "allOf": [ + { + "$ref": "#/components/schemas/CommonNTAActivityV2" + } + ], + "required": [ + "group_id" + ], + "type": "object" + }, + "CommonSDIVActivityV2": { + "properties": { + "cusip": { + "description": "The CUSIP of the security involved with the activity", + "type": "string" + }, + "ex_date": { + "description": "The ex_date for this corporate action", + "format": "date", + "minLength": 1, + "type": "string" + }, + "payable_date": { + "description": "The payable_date for this corporate action", + "format": "date", + "minLength": 1, + "type": "string" + }, + "rate": { + "description": "Dividend rate per share", + "type": "string" + }, + "record_date": { + "description": "The record_date for this corporate action", + "format": "date", + "minLength": 1, + "type": "string" + }, + "symbol": { + "description": "The symbol of the security involved with the activity", + "type": "string" + } + }, + "required": [ + "symbol", + "cusip", + "rate" + ], + "type": "object" + }, + "CommonSpinoffActivityV2": { + "properties": { + "due_bill_redemption_date": { + "description": "When due bills related to the spinoff are redeemed", + "format": "date", + "minLength": 1, + "type": "string" + }, + "ex_date": { + "description": "The ex_date for this corporate action", + "format": "date", + "minLength": 1, + "type": "string" + }, + "new_cusip": { + "description": "CUSIP of the new security", + "type": "string" + }, + "new_price": { + "description": "Market price of new shares after the spinoff", + "type": "string" + }, + "new_rate": { + "description": "Ratio of new shares received", + "type": "string" + }, + "new_symbol": { + "description": "Symbol of the new security", + "type": "string" + }, + "payable_date": { + "description": "The payable_date for this corporate action", + "format": "date", + "minLength": 1, + "type": "string" + }, + "record_date": { + "description": "The record_date for this corporate action", + "format": "date", + "minLength": 1, + "type": "string" + }, + "source_cusip": { + "description": "CUSIP of the parent security", + "type": "string" + }, + "source_price": { + "description": "Market price of parent shares before the spinoff", + "type": "string" + }, + "source_rate": { + "description": "Ratio of parent shares", + "type": "string" + }, + "source_symbol": { + "description": "Symbol of the parent security", + "type": "string" + } + }, + "required": [ + "source_cusip", + "source_symbol", + "source_rate", + "source_price", + "new_cusip", + "new_symbol", + "new_rate", + "new_price" + ], + "type": "object" + }, + "CommonSplitActivityV2": { + "properties": { + "new_cusip": { + "description": "CUSIP of the new security after the split", + "type": "string" + }, + "new_rate": { + "description": "Ratio of new shares received", + "type": "string" + }, + "old_cusip": { + "description": "CUSIP of the old security before the split", + "type": "string" + }, + "old_rate": { + "description": "Ratio of old shares exchanged", + "type": "string" + }, + "payable_date": { + "description": "The payable_date for this corporate action", + "format": "date", + "minLength": 1, + "type": "string" + } + }, + "required": [ + "old_cusip", + "new_cusip", + "old_rate", + "new_rate" + ], + "type": "object" + }, + "CommonSplitStockActivityV2": { + "allOf": [ + { + "$ref": "#/components/schemas/CommonCaActivityV2" + }, + { + "$ref": "#/components/schemas/CommonSplitActivityV2" + }, + { + "properties": { + "new_qty": { + "description": "The new quantity after the split", + "type": "string" + }, + "old_qty": { + "description": "The old quantity before the split", + "type": "string" + } + }, + "required": [ + "old_qty", + "new_qty" + ], + "type": "object" + } + ], + "type": "object" + }, + "CommonVOFSubtypeActivityV2": { + "properties": { + "new_cusip": { + "description": "CUSIP of the new security", + "type": "string" + }, + "new_symbol": { + "description": "Symbol of the new security", + "type": "string" + }, + "source_cusip": { + "description": "CUSIP of the parent security", + "type": "string" + }, + "source_symbol": { + "description": "Symbol of the parent security", + "type": "string" + } + }, + "required": [ + "source_cusip", + "source_symbol" + ], + "type": "object" + }, + "CreateCryptoTransferRequest": { + "properties": { + "address": { + "description": "The destination wallet address", + "type": "string" + }, + "amount": { + "description": "The amount, denoted in the specified asset, to be withdrawn from the user's wallet", + "type": "string" + }, + "asset": { + "description": "The crypto asset symbol, e.g. BTC, ETH, USDT.", + "type": "string" + }, + "chain": { + "description": "Optional chain identifier. The blockchain network used for the withdrawal.", + "enum": [ + "SOL", + "ETH", + "BTC", + "XRP", + "ARB" + ], + "type": "string" + } + }, + "required": [ + "amount", + "address", + "asset" + ], + "title": "CreateCryptoTransferRequest", + "type": "object" + }, + "CryptoTransfer": { + "description": "Transfers allow you to transfer assets into your end customer's account (deposits) or out (withdrawal).", + "properties": { + "amount": { + "description": "Amount of transfer denominated in the underlying crypto asset", + "type": "string" + }, + "asset": { + "description": "Symbol of crypto asset for given transfer (e.g. BTC)", + "type": "string" + }, + "chain": { + "description": "Underlying network for given transfer", + "type": "string" + }, + "created_at": { + "description": "Timestamp when transfer was created", + "format": "date-time", + "type": "string" + }, + "direction": { + "$ref": "#/components/schemas/TransferDirection" + }, + "fees": { + "type": "string" + }, + "from_address": { + "description": "Originating address of the transfer", + "type": "string" + }, + "id": { + "description": "The crypto transfer ID", + "format": "uuid", + "type": "string" + }, + "network_fee": { + "type": "string" + }, + "status": { + "$ref": "#/components/schemas/CryptoTransferStatus" + }, + "to_address": { + "description": "Destination address of the transfer", + "type": "string" + }, + "tx_hash": { + "description": "On-chain transaction hash (e.g. 0xabc...xyz)", + "type": "string" + }, + "usd_value": { + "description": "Equivalent USD value at time of transfer", + "type": "string" + } + }, + "type": "object" + }, + "CryptoTransferStatus": { + "enum": [ + "PROCESSING", + "FAILED", + "COMPLETE" + ], + "example": "PROCESSING", + "type": "string" + }, + "CryptoWallet": { + "properties": { + "address": { + "type": "string" + }, + "chain": { + "type": "string" + }, + "created_at": { + "description": "Timestamp (RFC3339) of account creation.", + "format": "date-time", + "type": "string" + } + }, + "type": "object" + }, + "DIVNRAActivityV2": { + "allOf": [ + { + "$ref": "#/components/schemas/CommonNTAActivityV2" + }, + { + "properties": { + "cusip": { + "description": "The CUSIP of the security involved with the activity", + "type": "string" + }, + "parent_id": { + "description": "The ref_id of the parent dividend", + "type": "string" + }, + "symbol": { + "description": "The symbol of the security involved with the activity", + "type": "string" + } + }, + "required": [ + "cusip", + "symbol", + "parent_id" + ], + "type": "object" + } + ], + "description": "Dividend withholding for non resident aliens", + "type": "object" + }, + "DIVSPDActivityV2": { + "allOf": [ + { + "$ref": "#/components/schemas/CommonCaActivityV2" + }, + { + "properties": { + "cash_payout": { + "description": "Total cash amount paid", + "type": "string" + }, + "cusip": { + "description": "The CUSIP of the security involved with the activity", + "type": "string" + }, + "due_bill_off_date": { + "description": "When due bills stop applying for this event", + "format": "date", + "minLength": 1, + "type": "string" + }, + "due_bill_on_date": { + "description": "When due bills begin to apply for this event", + "format": "date", + "minLength": 1, + "type": "string" + }, + "entitled_qty": { + "description": "Quantity of shares entitled to receive cash in lieu", + "type": "string" + }, + "ex_date": { + "description": "The ex_date for this corporate action", + "format": "date", + "minLength": 1, + "type": "string" + }, + "foreign": { + "description": "Indicates if related to a non-US security", + "type": "boolean" + }, + "payable_date": { + "description": "The payable_date for this corporate action", + "format": "date", + "minLength": 1, + "type": "string" + }, + "rate": { + "description": "Cash payout per share", + "type": "string" + }, + "record_date": { + "description": "The record_date for this corporate action", + "format": "date", + "minLength": 1, + "type": "string" + }, + "special": { + "description": "Indicates if this is a special dividend", + "type": "boolean" + }, + "symbol": { + "description": "The symbol of the security involved with the activity", + "type": "string" + } + }, + "required": [ + "entitled_qty", + "cash_payout", + "symbol", + "cusip", + "rate", + "foreign", + "special" + ], + "type": "object" + } + ], + "description": "Substitute payment in lieu of dividend", + "type": "object" + }, + "Error": { + "properties": { + "code": { + "type": "number" + }, + "message": { + "type": "string" + } + }, + "required": [ + "code", + "message" + ], + "title": "Error", + "type": "object" + }, + "Exchange": { + "description": "Represents the current exchanges Alpaca supports. List is currently:\n\n- AMEX\n- ARCA\n- BATS\n- NYSE\n- NASDAQ\n- NYSEARCA\n- OTC", + "enum": [ + "AMEX", + "ARCA", + "BATS", + "NYSE", + "NASDAQ", + "NYSEARCA", + "OTC" + ], + "example": "NYSE", + "title": "Exchange", + "type": "string" + }, + "ExchangeForPosition": { + "description": "Represents the current exchanges Alpaca supports. List is currently:\n\n- AMEX\n- ARCA\n- BATS\n- NYSE\n- NASDAQ\n- NYSEARCA\n- OTC\n\nCan be empty if not applicable (e.g., for options contracts)", + "enum": [ + "AMEX", + "ARCA", + "BATS", + "NYSE", + "NASDAQ", + "NYSEARCA", + "OTC", + "" + ], + "example": "NYSE", + "title": "Exchange", + "type": "string" + }, + "ExchangeOfferActivityV2": { + "allOf": [ + { + "$ref": "#/components/schemas/CommonNTAActivityV2" + }, + { + "$ref": "#/components/schemas/CommonVOFSubtypeActivityV2" + } + ], + "type": "object" + }, + "FEEActivityV2": { + "allOf": [ + { + "$ref": "#/components/schemas/CommonNTAActivityV2" + }, + { + "properties": { + "parent_id": { + "description": "The parent transaction's ID", + "format": "uuid", + "type": "string" + } + }, + "required": [ + "parent_id" + ], + "type": "object" + } + ], + "description": "Fee", + "type": "object" + }, + "FOPTActivityV2": { + "allOf": [ + { + "$ref": "#/components/schemas/CommonNTAActivityV2" + }, + { + "properties": { + "contra": { + "description": "Contra for the transfer", + "type": "string" + }, + "external_id": { + "description": "External ID of the transfer", + "type": "string" + }, + "symbol": { + "description": "The symbol of the security involved with the activity", + "type": "string" + } + }, + "required": [ + "external_id", + "contra", + "symbol" + ], + "type": "object" + } + ], + "description": "Free-of-payment (FOP) transfers", + "type": "object" + }, + "FixedIncomeRedemptionActivityV2": { + "allOf": [ + { + "$ref": "#/components/schemas/CommonNTAActivityV2" + }, + { + "properties": { + "ca_id": { + "description": "The unique identifier for this corporate action", + "type": "string" + }, + "cash_payout": { + "description": "The cash payout", + "type": "string" + }, + "cusip": { + "description": "The CUSIP of the security involved with the activity", + "type": "string" + }, + "payment_date": { + "description": "The payment date", + "format": "date", + "minLength": 1, + "type": "string" + }, + "qty": { + "description": "Quantity for the redemption", + "type": "string" + } + }, + "required": [ + "ca_id", + "payment_date", + "cusip", + "qty", + "cash_payout" + ], + "type": "object" + } + ], + "description": "Redemption", + "type": "object" + }, + "ForwardSplitActivityV2": { + "allOf": [ + { + "$ref": "#/components/schemas/CommonSplitStockActivityV2" + }, + { + "properties": { + "due_bill_redemption_date": { + "description": "When due bills related to the split are redeemed", + "format": "date", + "minLength": 1, + "type": "string" + }, + "ex_date": { + "description": "The ex_date for this corporate action", + "format": "date", + "minLength": 1, + "type": "string" + }, + "record_date": { + "description": "The record_date for this corporate action", + "format": "date", + "minLength": 1, + "type": "string" + }, + "symbol": { + "description": "The symbol of the security being split", + "type": "string" + } + }, + "required": [ + "symbol" + ], + "type": "object" + } + ], + "description": "Forward stock split", + "type": "object" + }, + "JNLCActivityV2": { + "allOf": [ + { + "$ref": "#/components/schemas/CommonJournalActivityV2" + } + ], + "description": "Journal entry (cash)", + "type": "object" + }, + "JNLSActivityV2": { + "allOf": [ + { + "$ref": "#/components/schemas/CommonJournalActivityV2" + }, + { + "properties": { + "symbol": { + "description": "The symbol of the security involved with the activity", + "type": "string" + } + }, + "required": [ + "symbol" + ], + "type": "object" + } + ], + "description": "Journal entry (stock)", + "type": "object" + }, + "MAActivityV2": { + "allOf": [ + { + "$ref": "#/components/schemas/CommonCaActivityV2" + }, + { + "$ref": "#/components/schemas/CommonMAActivityV2" + }, + { + "properties": { + "acquiree_qty": { + "description": "Quantity of the acquiree", + "type": "string" + }, + "acquirer_qty": { + "description": "Quantity of the acquirer", + "type": "string" + }, + "cash_payout": { + "description": "The cash payout", + "type": "string" + }, + "cash_rate": { + "description": "The cash rate", + "type": "string" + } + }, + "required": [ + "acquiree_qty" + ], + "type": "object" + } + ], + "description": "Merger and acquisition", + "type": "object" + }, + "MLegOrderLeg": { + "description": "Represents an individual leg of a multi-leg options order.", + "properties": { + "position_intent": { + "$ref": "#/components/schemas/PositionIntent" + }, + "ratio_qty": { + "description": "proportional quantity of this leg in relation to the overall multi-leg order qty", + "type": "string" + }, + "side": { + "$ref": "#/components/schemas/OrderSide" + }, + "symbol": { + "description": "symbol or asset ID to identify the asset to trade", + "type": "string" + } + }, + "required": [ + "symbol", + "ratio_qty" + ], + "title": "MLegOrderLeg", + "type": "object" + }, + "NCActivityV2": { + "allOf": [ + { + "$ref": "#/components/schemas/CommonCaActivityV2" + }, + { + "$ref": "#/components/schemas/CommonNCActivityV2" + }, + { + "properties": { + "position_qty": { + "description": "The position quantity", + "type": "string" + } + }, + "required": [ + "position_qty" + ], + "type": "object" + } + ], + "description": "Name change", + "type": "object" + }, + "NextPageToken": { + "description": "Use this token in your next API call to paginate through the dataset and retrieve the next page of results. A null token indicates there are no more data to fetch.\n", + "example": "MTAwMA==", + "nullable": true, + "type": "string" + }, + "NonTradeActivities": { + "properties": { + "activity_sub_type": { + "$ref": "#/components/schemas/ActivitySubType" + }, + "activity_type": { + "$ref": "#/components/schemas/ActivityType" + }, + "created_at": { + "description": "Valid only for non-trading activity types. Null for trading activites.", + "format": "date-time", + "type": "string" + }, + "cusip": { + "description": "The CUSIP of the security involved with the activity. Not present for all activity types.", + "type": "string" + }, + "date": { + "description": "The date on which the activity occurred or on which the transaction associated with the activity settled.", + "format": "date-time", + "type": "string" + }, + "group_id": { + "description": "ID used to link activities who share a sibling relationship.", + "type": "string" + }, + "id": { + "description": "An ID for the activity, always in \"::\" format. Can be sent as page_token in requests to facilitate the paging of results.", + "type": "string" + }, + "net_amount": { + "description": "The net amount of money (positive or negative) associated with the activity.", + "type": "string" + }, + "per_share_amount": { + "description": "For dividend activities, the average amount paid per share. Not present for other activity types.", + "type": "string" + }, + "qty": { + "description": "For dividend activities, the number of shares that contributed to the payment. Not present for other activity types.\n", + "type": "string" + }, + "status": { + "description": "The activity status.", + "enum": [ + "executed", + "correct", + "canceled" + ], + "type": "string" + }, + "symbol": { + "description": "The symbol of the security involved with the activity. Not present for all activity types.", + "type": "string" + } + }, + "title": "AccountNonTradeActivities", + "type": "object", + "x-examples": { + "example-1": { + "activity_sub_type": "SDIV", + "activity_type": "DIV", + "created_at": "2021-05-10T14:01:04.650275Z", + "date": "2019-08-01", + "id": "20190801011955195::5f596936-6f23-4cef-bdf1-3806aae57dbf", + "net_amount": "1.02", + "per_share_amount": "0.51", + "qty": "2", + "status": "executed", + "symbol": "T" + } + } + }, + "OPASNActivityV2": { + "allOf": [ + { + "$ref": "#/components/schemas/CommonOptionsActivityV2" + } + ], + "description": "Option assignment", + "type": "object" + }, + "OPEXCActivityV2": { + "allOf": [ + { + "$ref": "#/components/schemas/CommonOptionsActivityV2" + } + ], + "description": "Option exercise", + "type": "object" + }, + "OPEXPActivityV2": { + "allOf": [ + { + "$ref": "#/components/schemas/CommonOptionsActivityV2" + } + ], + "description": "Option expiry", + "type": "object" + }, + "OPTRDActivityV2": { + "allOf": [ + { + "$ref": "#/components/schemas/CommonOptionsActivityV2" + } + ], + "description": "Trading activity that is paired with the assignment/exercise", + "type": "object" + }, + "OpcaCDIVActivityV2": { + "allOf": [ + { + "$ref": "#/components/schemas/CommonOPCAActivityV2" + }, + { + "$ref": "#/components/schemas/CommonCDIVActivityV2" + } + ], + "description": "Options corporate action of Cash dividend", + "type": "object" + }, + "OpcaFSPLITActivityV2": { + "allOf": [ + { + "$ref": "#/components/schemas/CommonOPCAActivityV2" + }, + { + "$ref": "#/components/schemas/CommonSplitActivityV2" + }, + { + "properties": { + "due_bill_redemption_date": { + "description": "When due bills related to the split are redeemed", + "format": "date", + "minLength": 1, + "type": "string" + }, + "ex_date": { + "description": "The ex_date for this corporate action", + "format": "date", + "minLength": 1, + "type": "string" + }, + "record_date": { + "description": "The record_date for this corporate action", + "format": "date", + "minLength": 1, + "type": "string" + }, + "symbol": { + "description": "The symbol of the security involved with the activity", + "type": "string" + } + }, + "required": [ + "symbol" + ], + "type": "object" + } + ], + "description": "Options corporate action of forward-splits", + "type": "object" + }, + "OpcaMAActivityV2": { + "allOf": [ + { + "$ref": "#/components/schemas/CommonOPCAActivityV2" + }, + { + "$ref": "#/components/schemas/CommonMAActivityV2" + } + ], + "description": "Options corporate action of Mergers & Acquisitions", + "type": "object" + }, + "OpcaNCActivityV2": { + "allOf": [ + { + "$ref": "#/components/schemas/CommonOPCAActivityV2" + }, + { + "$ref": "#/components/schemas/CommonNCActivityV2" + } + ], + "description": "Options corporate action of name changes", + "type": "object" + }, + "OpcaRSPLITActivityV2": { + "allOf": [ + { + "$ref": "#/components/schemas/CommonOPCAActivityV2" + }, + { + "$ref": "#/components/schemas/CommonSplitActivityV2" + }, + { + "properties": { + "ex_date": { + "description": "The ex_date for this corporate action", + "format": "date", + "minLength": 1, + "type": "string" + }, + "new_symbol": { + "description": "Symbol of the new security after the split", + "type": "string" + }, + "record_date": { + "description": "The record_date for this corporate action", + "format": "date", + "minLength": 1, + "type": "string" + }, + "symbol": { + "description": "The symbol of the security involved with the activity", + "type": "string" + } + }, + "required": [ + "symbol" + ], + "type": "object" + } + ], + "description": "Options corporate action of reverse-splits", + "type": "object" + }, + "OpcaSDIVActivityV2": { + "allOf": [ + { + "$ref": "#/components/schemas/CommonOPCAActivityV2" + }, + { + "$ref": "#/components/schemas/CommonSDIVActivityV2" + } + ], + "description": "Options corporate action of stock dividend", + "type": "object" + }, + "OpcaSPINActivityV2": { + "allOf": [ + { + "$ref": "#/components/schemas/CommonOPCAActivityV2" + }, + { + "$ref": "#/components/schemas/CommonSpinoffActivityV2" + } + ], + "description": "Options corporate action of spin-offs", + "type": "object" + }, + "OpcaUSPLITActivityV2": { + "allOf": [ + { + "$ref": "#/components/schemas/CommonOPCAActivityV2" + }, + { + "$ref": "#/components/schemas/CommonSplitActivityV2" + }, + { + "properties": { + "alternate_cusip": { + "description": "CUSIP for the alternate security after the split", + "type": "string" + }, + "alternate_rate": { + "description": "Ratio of alternate shares received", + "type": "string" + }, + "alternate_symbol": { + "description": "Symbol for the alternate security after the split", + "type": "string" + }, + "effective_date": { + "description": "When the unit split becomes effective", + "format": "date", + "minLength": 1, + "type": "string" + }, + "new_symbol": { + "description": "Symbol of the new security after the unit split", + "type": "string" + }, + "old_symbol": { + "description": "The old symbol of the security involved with the activity", + "type": "string" + } + }, + "required": [ + "old_symbol", + "new_symbol", + "alternate_cusip", + "alternate_symbol", + "alternate_rate", + "effective_date" + ], + "type": "object" + } + ], + "description": "Options corporate action of unit-splits", + "type": "object" + }, + "OptionContract": { + "properties": { + "close_price": { + "description": "The close price of the option contract.", + "example": "148.38", + "type": "string" + }, + "close_price_date": { + "description": "The date of the close price data.", + "example": "2023-12-11", + "format": "date", + "type": "string" + }, + "deliverables": { + "description": "Represents the deliverables tied to the option contract. While standard contracts entail a single deliverable, non-standard ones can encompass multiple deliverables, each potentially customized with distinct parameters.\nThis array is included in the list contracts response only if the query parameter show_deliverables=true is provided.\n", + "items": { + "$ref": "#/components/schemas/OptionDeliverable" + }, + "type": "array" + }, + "expiration_date": { + "description": "The expiration date of the option contract.", + "example": "2025-06-20", + "format": "date", + "type": "string" + }, + "id": { + "description": "The unique identifier of the option contract.", + "example": "98359ef7-5124-49f3-85ea-5cf02df6defa", + "type": "string" + }, + "multiplier": { + "description": "The multiplier of the option contract is crucial for calculating both the trade premium and the extended strike price. In standard contracts, the multiplier is always set to 100.\nFor instance, if a contract is traded at $1.50 and the multiplier is 100, the total amount debited when buying the contract would be $150.00.\nSimilarly, when exercising a call contract, the total cost will be equal to the strike price times the multiplier.", + "example": "100", + "type": "string" + }, + "name": { + "description": "The name of the option contract.", + "example": "AAPL Jun 20 2025 100 Call", + "type": "string" + }, + "open_interest": { + "description": "The open interest of the option contract.", + "example": "237", + "type": "string" + }, + "open_interest_date": { + "description": "The date of the open interest data.", + "example": "2023-12-11", + "format": "date", + "type": "string" + }, + "root_symbol": { + "description": "The root symbol of the option contract.", + "example": "AAPL", + "type": "string" + }, + "size": { + "description": "Represents the number of underlying shares to be delivered in case the contract is exercised/assigned. For standard contracts, this is always 100.\nThis field should **not** be used as a multiplier, specially for non-standard contracts.", + "example": "100", + "type": "string" + }, + "status": { + "description": "The status of the option contract.", + "enum": [ + "active", + "inactive" + ], + "example": "active", + "type": "string" + }, + "strike_price": { + "description": "The strike price of the option contract.", + "example": "100", + "type": "string" + }, + "style": { + "description": "The style of the option contract.", + "enum": [ + "american", + "european" + ], + "example": "american", + "type": "string" + }, + "symbol": { + "description": "The symbol representing the option contract.", + "example": "AAPL250620C00100000", + "type": "string" + }, + "tradable": { + "description": "Indicates whether the option contract is tradable.", + "example": true, + "type": "boolean" + }, + "type": { + "description": "The type of the option contract.", + "enum": [ + "call", + "put" + ], + "example": "call", + "type": "string" + }, + "underlying_asset_id": { + "description": "The unique identifier of the underlying asset.", + "example": "b0b6dd9d-8b9b-48a9-ba46-b9d54906e415", + "type": "string" + }, + "underlying_symbol": { + "description": "The underlying symbol of the option contract.", + "example": "AAPL", + "type": "string" + } + }, + "required": [ + "id", + "symbol", + "name", + "status", + "tradable", + "expiration_date", + "underlying_symbol", + "underlying_asset_id", + "type", + "style", + "strike_price", + "multiplier", + "size" + ], + "type": "object" + }, + "OptionDeliverable": { + "properties": { + "allocation_percentage": { + "description": "Cost allocation percentage of the deliverable.\nThis is used to determine the cost basis of the equity shares received from the exercise, specially for non-standard contracts with multiple deliverables.\n", + "example": "100", + "type": "string" + }, + "amount": { + "description": "The deliverable amount. For cash deliverables, this is the cash amount.\nFor standard contract, this is always 100.\nThis field can be null in case the deliverable settlement is delayed and the amount is yet to be determined.\n", + "example": "100", + "type": "string" + }, + "asset_id": { + "description": "Unique identifier of the deliverable asset. For standard contracts, this is equivalent to underlying_asset_id of the contracts.\nThis field is not returned for cash deliverables.\n", + "example": "b0b6dd9d-8b9b-48a9-ba46-b9d54906e415", + "type": "string" + }, + "delayed_settlement": { + "description": "If true, the settlement of the deliverable will be delayed.\nFor instance, in the event of a contract with a delayed deliverable being exercised, both the availability of the deliverable and its settlement may be postponed beyond the typical timeframe.\n", + "example": false, + "type": "boolean" + }, + "settlement_method": { + "description": "Indicates the settlement method that will be used:\n- **BTOB**: Broker to Broker\n- **CADF**: Cash Difference\n- **CAFX**: Cash Fixed\n- **CCC**: Correspondent Clearing Corp\n", + "enum": [ + "BTOB", + "CADF", + "CAFX", + "CCC" + ], + "example": "CCC", + "type": "string" + }, + "settlement_type": { + "description": "Indicates when the deliverable will be settled if the contract is exercised/assigned.\n", + "enum": [ + "T+0", + "T+1", + "T+2", + "T+3", + "T+4", + "T+5" + ], + "example": "T+2", + "type": "string" + }, + "symbol": { + "description": "Symbol of the deliverable. For standard contracts, this is equivalent to the underlying symbol of the contract.\n", + "example": "AAPL", + "type": "string" + }, + "type": { + "description": "Type of deliverable, indicating whether it's cash or equity. For standard contracts, it is always \"equity\".\n", + "enum": [ + "cash", + "equity" + ], + "example": "equity", + "type": "string" + } + }, + "required": [ + "type", + "symbol", + "amount", + "allocation_percentage", + "settlement_type", + "settlement_method", + "delayed_settlement" + ], + "type": "object" + }, + "Order": { + "description": "The Orders API allows a user to monitor, place and cancel their orders with Alpaca.\n\nEach order has a unique identifier provided by the client. This client-side unique order ID will be automatically generated by the system if not provided by the client, and will be returned as part of the order object along with the rest of the fields described below. Once an order is placed, it can be queried using the client-side order ID to check the status.\n\nUpdates on open orders at Alpaca will also be sent over the streaming interface, which is the recommended method of maintaining order state.", + "properties": { + "asset_class": { + "$ref": "#/components/schemas/AssetClass" + }, + "asset_id": { + "description": "Asset ID (For options this represents the option contract ID)", + "format": "uuid", + "type": "string" + }, + "canceled_at": { + "format": "date-time", + "nullable": true, + "type": "string" + }, + "client_order_id": { + "description": "Client unique order ID", + "maxLength": 128, + "type": "string" + }, + "created_at": { + "format": "date-time", + "type": "string" + }, + "expired_at": { + "format": "date-time", + "nullable": true, + "type": "string" + }, + "extended_hours": { + "description": "If true, eligible for execution outside regular trading hours.", + "type": "boolean" + }, + "failed_at": { + "format": "date-time", + "nullable": true, + "type": "string" + }, + "filled_at": { + "format": "date-time", + "nullable": true, + "type": "string" + }, + "filled_avg_price": { + "description": "Filled average price", + "nullable": true, + "type": "string" + }, + "filled_qty": { + "description": "Filled quantity", + "minLength": 1, + "type": "string" + }, + "hwm": { + "description": "The highest (lowest) market price seen since the trailing stop order was submitted.", + "nullable": true, + "type": "string" + }, + "id": { + "description": "Order ID", + "type": "string" + }, + "legs": { + "description": "When querying non-simple order_class orders in a nested style, an array of Order entities associated with this order. Otherwise, null. Required if order class is `mleg`.", + "items": { + "$ref": "#/components/schemas/OrderLeg" + }, + "nullable": true, + "type": "array" + }, + "limit_price": { + "description": "Limit price", + "nullable": true, + "type": "string" + }, + "notional": { + "description": "Ordered notional amount. If entered, qty will be null. Can take up to 9 decimal points.", + "minLength": 1, + "nullable": true, + "type": "string" + }, + "order_class": { + "$ref": "#/components/schemas/OrderClass" + }, + "order_type": { + "deprecated": true, + "description": "Deprecated in favour of the field \"type\" ", + "type": "string" + }, + "position_intent": { + "$ref": "#/components/schemas/PositionIntent" + }, + "qty": { + "description": "Ordered quantity. If entered, notional will be null. Can take up to 9 decimal points. Required if order class is `mleg`.", + "minLength": 1, + "nullable": true, + "type": "string" + }, + "replaced_at": { + "format": "date-time", + "nullable": true, + "type": "string" + }, + "replaced_by": { + "description": "The order ID that this order was replaced by", + "format": "uuid", + "nullable": true, + "type": "string" + }, + "replaces": { + "description": "The order ID that this order replaces", + "format": "uuid", + "nullable": true, + "type": "string" + }, + "side": { + "$ref": "#/components/schemas/OrderSide" + }, + "status": { + "$ref": "#/components/schemas/OrderStatus" + }, + "stop_price": { + "description": "Stop price", + "nullable": true, + "type": "string" + }, + "submitted_at": { + "format": "date-time", + "nullable": true, + "type": "string" + }, + "symbol": { + "description": "Asset symbol, required for all order classes except for `mleg`", + "minLength": 1, + "type": "string" + }, + "time_in_force": { + "$ref": "#/components/schemas/TimeInForce" + }, + "trail_percent": { + "description": "The percent value away from the high water mark for trailing stop orders.", + "nullable": true, + "type": "string" + }, + "trail_price": { + "description": "The dollar value away from the high water mark for trailing stop orders.", + "nullable": true, + "type": "string" + }, + "type": { + "$ref": "#/components/schemas/OrderType" + }, + "updated_at": { + "format": "date-time", + "nullable": true, + "type": "string" + } + }, + "required": [ + "notional", + "type", + "time_in_force" + ], + "title": "Order", + "type": "object" + }, + "OrderClass": { + "description": "The order classes supported by Alpaca vary based on the order's security type. The following provides a comprehensive breakdown of the supported order classes for each category:\n - Equity trading: simple (or \"\"), oco, oto, bracket.\n - Options trading:\n - simple (or \"\")\n - mleg (required for multi-leg complex option strategies)\n - Crypto trading: simple (or \"\").", + "enum": [ + "simple", + "bracket", + "oco", + "oto", + "mleg", + "" + ], + "example": "bracket", + "title": "OrderClass", + "type": "string" + }, + "OrderLeg": { + "description": "This is copy of Order response schemas as a workaround of displaying issue of nested Order recursively for legs", + "properties": { + "asset_class": { + "$ref": "#/components/schemas/AssetClass" + }, + "asset_id": { + "description": "Asset ID (For options this represents the option contract ID)", + "format": "uuid", + "type": "string" + }, + "canceled_at": { + "format": "date-time", + "nullable": true, + "type": "string" + }, + "client_order_id": { + "description": "Client unique order ID", + "maxLength": 128, + "type": "string" + }, + "created_at": { + "format": "date-time", + "type": "string" + }, + "expired_at": { + "format": "date-time", + "nullable": true, + "type": "string" + }, + "extended_hours": { + "description": "If true, eligible for execution outside regular trading hours.", + "type": "boolean" + }, + "failed_at": { + "format": "date-time", + "nullable": true, + "type": "string" + }, + "filled_at": { + "format": "date-time", + "nullable": true, + "type": "string" + }, + "filled_avg_price": { + "description": "Filled average price", + "nullable": true, + "type": "string" + }, + "filled_qty": { + "description": "Filled quantity", + "minLength": 1, + "type": "string" + }, + "hwm": { + "description": "The highest (lowest) market price seen since the trailing stop order was submitted.", + "nullable": true, + "type": "string" + }, + "id": { + "description": "Order ID", + "type": "string" + }, + "legs": { + "description": "Always null for an order leg; legs are not nested beyond one level.", + "example": null, + "items": { + "type": "object" + }, + "nullable": true, + "type": "array" + }, + "limit_price": { + "description": "Limit price", + "nullable": true, + "type": "string" + }, + "notional": { + "description": "Ordered notional amount. If entered, qty will be null. Can take up to 9 decimal points.", + "minLength": 1, + "nullable": true, + "type": "string" + }, + "order_class": { + "$ref": "#/components/schemas/OrderClass" + }, + "order_type": { + "deprecated": true, + "description": "Deprecated in favour of the field \"type\" ", + "type": "string" + }, + "position_intent": { + "$ref": "#/components/schemas/PositionIntent" + }, + "qty": { + "description": "Ordered quantity. If entered, notional will be null. Can take up to 9 decimal points.", + "minLength": 1, + "nullable": true, + "type": "string" + }, + "replaced_at": { + "format": "date-time", + "nullable": true, + "type": "string" + }, + "replaced_by": { + "description": "The order ID that this order was replaced by", + "format": "uuid", + "nullable": true, + "type": "string" + }, + "replaces": { + "description": "The order ID that this order replaces", + "format": "uuid", + "nullable": true, + "type": "string" + }, + "side": { + "$ref": "#/components/schemas/OrderSide" + }, + "status": { + "$ref": "#/components/schemas/OrderStatus" + }, + "stop_price": { + "description": "Stop price", + "nullable": true, + "type": "string" + }, + "submitted_at": { + "format": "date-time", + "nullable": true, + "type": "string" + }, + "symbol": { + "description": "Asset symbol", + "minLength": 1, + "type": "string" + }, + "time_in_force": { + "$ref": "#/components/schemas/TimeInForce" + }, + "trail_percent": { + "description": "The percent value away from the high water mark for trailing stop orders.", + "nullable": true, + "type": "string" + }, + "trail_price": { + "description": "The dollar value away from the high water mark for trailing stop orders.", + "nullable": true, + "type": "string" + }, + "type": { + "$ref": "#/components/schemas/OrderType" + }, + "updated_at": { + "format": "date-time", + "nullable": true, + "type": "string" + } + }, + "required": [ + "symbol", + "notional", + "qty", + "type", + "side", + "time_in_force" + ], + "title": "Order", + "type": "object" + }, + "OrderSide": { + "description": "Represents which side this order was on:\n- buy\n- sell\nRequired for all order classes except for mleg.", + "enum": [ + "buy", + "sell" + ], + "example": "buy", + "title": "OrderSide", + "type": "string" + }, + "OrderStatus": { + "description": "An order executed through Alpaca can experience several status changes during its lifecycle. The most common statuses are described in detail below:\n\n- new\n The order has been received by Alpaca, and routed to exchanges for execution. This is the usual initial state of an order.\n\n- partially_filled\n The order has been partially filled.\n\n- filled\n The order has been filled, and no further updates will occur for the order.\n\n- done_for_day\n The order is done executing for the day, and will not receive further updates until the next trading day.\n\n- canceled\n The order has been canceled, and no further updates will occur for the order. This can be either due to a cancel request by the user, or the order has been canceled by the exchanges due to its time-in-force.\n\n- expired\n The order has expired, and no further updates will occur for the order.\n\n- replaced\n The order was replaced by another order, or was updated due to a market event such as corporate action.\n\n- pending_cancel\n The order is waiting to be canceled.\n\n- pending_replace\n The order is waiting to be replaced by another order. The order will reject cancel request while in this state.\n\nLess common states are described below. Note that these states only occur on very rare occasions, and most users will likely never see their orders reach these states:\n\n- accepted\n The order has been received by Alpaca, but hasn't yet been routed to the execution venue. This could be seen often out side of trading session hours.\n\n- pending_new\n The order has been received by Alpaca, and routed to the exchanges, but has not yet been accepted for execution. This state only occurs on rare occasions.\n\n- accepted_for_bidding\n The order has been received by exchanges, and is evaluated for pricing. This state only occurs on rare occasions.\n\n- stopped\n The order has been stopped, and a trade is guaranteed for the order, usually at a stated price or better, but has not yet occurred. This state only occurs on rare occasions.\n\n- rejected\n The order has been rejected, and no further updates will occur for the order. This state occurs on rare occasions and may occur based on various conditions decided by the exchanges.\n\n- suspended\n The order has been suspended, and is not eligible for trading. This state only occurs on rare occasions.\n\n- calculated\n The order has been completed for the day (either filled or done for day), but remaining settlement calculations are still pending. This state only occurs on rare occasions.\n\n\nAn order may be canceled through the API up until the point it reaches a state of either filled, canceled, or expired.", + "enum": [ + "new", + "partially_filled", + "filled", + "done_for_day", + "canceled", + "expired", + "replaced", + "pending_cancel", + "pending_replace", + "accepted", + "pending_new", + "accepted_for_bidding", + "stopped", + "rejected", + "suspended", + "calculated" + ], + "example": "new", + "title": "OrderStatus", + "type": "string" + }, + "OrderType": { + "description": "The order types supported by Alpaca vary based on the order's security type. The following provides a comprehensive breakdown of the supported order types for each category:\n - Equity trading: market, limit, stop, stop_limit, trailing_stop.\n - Options trading: market, limit.\n - Multileg Options trading: market, limit.\n - Crypto trading: market, limit, stop_limit.", + "enum": [ + "market", + "limit", + "stop", + "stop_limit", + "trailing_stop" + ], + "example": "market", + "title": "OrderType", + "type": "string" + }, + "PatchOrderRequest": { + "description": "Represents a request to patch an order.\n\nNote: `qty` and `notional` are mutually exclusive on a single replace request. `notional` is only valid for IPO indications of interest (`asset_class: \"ipo\"`); non-IPO notional orders cannot be replaced at all.", + "properties": { + "advanced_instructions": { + "$ref": "#/components/schemas/AdvancedInstructions" + }, + "client_order_id": { + "description": "A unique identifier for the new order. Automatically generated if not sent. (<= 128 characters)", + "maxLength": 128, + "type": "string" + }, + "limit_price": { + "description": "Required if original order's `type` field was `limit` or `stop_limit`.\nIn case of `mleg`, the limit_price parameter is expressed with the following notation:\n- A positive value indicates a debit, representing a cost or payment to be made.\n- A negative value signifies a credit, reflecting an amount to be received.", + "example": "3.14", + "type": "string" + }, + "notional": { + "description": "New notional (dollar amount) for the order. Only valid for IPO indications of interest (`asset_class: \"ipo\"`); will be rejected for any other asset class.\nMutually exclusive with `qty` on the same replace request.", + "example": "750", + "type": "string" + }, + "qty": { + "description": "number of shares to trade.\n\nYou can only patch full shares for now.\n\nQty of equity fractional orders are not allowed to change. Non-IPO notional orders cannot be replaced at all - no fields (qty, limit_price, stop_price, etc.) can be modified; cancel and resubmit instead.", + "example": "4", + "type": "string" + }, + "stop_price": { + "description": "required if original order type is limit or stop_limit", + "example": "3.14", + "type": "string" + }, + "time_in_force": { + "$ref": "#/components/schemas/TimeInForce" + }, + "trail": { + "description": "the new value of the trail_price or trail_percent value (works only for type=\"trailing_stop\")", + "example": "3.14", + "type": "string" + } + }, + "title": "PatchOrderRequest", + "type": "object" + }, + "PortfolioHistory": { + "description": "Timeseries data for equity and profit loss information of the account.", + "properties": { + "base_value": { + "description": "basis in dollar of the profit loss calculation", + "type": "number" + }, + "base_value_asof": { + "description": "If included, then it indicates that the base_value is the account's closing\nequity value at this trading date.\n\nIf not specified, then the baseline calculation is done against the earliest returned data item. This could happen for\naccounts without prior closing balances (e.g. new account) or for queries with 1D timeframes, where the first data point\nis used as a reference point.\n", + "example": "2023-10-20", + "format": "date", + "type": "string" + }, + "cashflow": { + "description": "accumulated value in dollar amount as of the end of each time window", + "type": "object" + }, + "equity": { + "description": "equity value of the account in dollar amount as of the end of each time window", + "items": { + "type": "number" + }, + "type": "array" + }, + "profit_loss": { + "description": "profit/loss in dollar from the base value", + "items": { + "type": "number" + }, + "type": "array" + }, + "profit_loss_pct": { + "description": "profit/loss in percentage from the base value", + "example": [ + 0.001, + 0.002 + ], + "items": { + "type": "number" + }, + "type": "array" + }, + "timeframe": { + "description": "time window size of each data element", + "example": "15Min", + "type": "string" + }, + "timestamp": { + "description": "Time of each data element, left-labeled (the beginning of time window).\n\nThe values returned are in [UNIX epoch format](https://en.wikipedia.org/wiki/Unix_time).\n", + "items": { + "type": "integer" + }, + "type": "array" + } + }, + "required": [ + "timestamp", + "equity", + "profit_loss", + "profit_loss_pct", + "base_value", + "timeframe" + ], + "title": "PortfolioHistory", + "type": "object", + "x-examples": { + "example-cashflows": { + "base_value": 8413.04, + "base_value_asof": "2026-04-06", + "cashflow": { + "CSD": [ + 9.93, + 10.13, + 10.16, + 0 + ], + "DIV": [ + 0, + 0, + 0.63, + 0 + ] + }, + "equity": [ + 8425.21, + 8639.77, + 8766.1, + 8835.31 + ], + "profit_loss": [ + 12.17, + 214.56, + 126.33, + 69.21 + ], + "profit_loss_pct": [ + 0.0014, + 0.0255, + 0.0146, + 0.0079 + ], + "timeframe": "1D", + "timestamp": [ + 1775606400, + 1775692800, + 1775779200, + 1775865600 + ] + }, + "example-intraday-query-15min-1d": { + "base_value": 2774.16, + "base_value_asof": "2023-10-18", + "equity": [ + 2773.79, + 2769.04, + 2768.65, + 2765.11, + 2763.03, + 2763.17, + 2763.17, + 2763.47, + 2763.91, + 2768.13, + 2774.98, + 2757.94, + 2757.65, + 2774.54, + 2775.58, + 2775.28, + 2767.9, + 2762.26, + 2762.56, + 2756.99, + 2756.84, + 2752.43, + 2752.13, + 2748.44, + 2751.23, + 2747.54, + 2748.74 + ], + "profit_loss": [ + -0.37, + -5.12, + -5.51, + -9.05, + -11.13, + -10.99, + -10.99, + -10.69, + -10.25, + -6.03, + 0.82, + -16.22, + -16.51, + 0.38, + 1.42, + 1.12, + -6.26, + -11.9, + -11.6, + -17.17, + -17.32, + -21.73, + -22.03, + -25.72, + -22.93, + -26.62, + -25.42 + ], + "profit_loss_pct": [ + -0.0001, + -0.0018, + -0.002, + -0.0033, + -0.004, + -0.004, + -0.004, + -0.0039, + -0.0037, + -0.0022, + 0.0003, + -0.0058, + -0.006, + 0.0001, + 0.0005, + 0.0004, + -0.0023, + -0.0043, + -0.0042, + -0.0062, + -0.0062, + -0.0078, + -0.0079, + -0.0093, + -0.0083, + -0.0096, + -0.0092 + ], + "timeframe": "15Min", + "timestamp": [ + 1697722200, + 1697723100, + 1697724000, + 1697724900, + 1697725800, + 1697726700, + 1697727600, + 1697728500, + 1697729400, + 1697730300, + 1697731200, + 1697732100, + 1697733000, + 1697733900, + 1697734800, + 1697735700, + 1697736600, + 1697737500, + 1697738400, + 1697739300, + 1697740200, + 1697741100, + 1697742000, + 1697742900, + 1697743800, + 1697744700, + 1697745600 + ] + }, + "example-query-1d-7d": { + "base_value": 2784.79, + "equity": [ + 2784.79, + 2794.79, + 2805.46, + 2774.16, + 2748.73 + ], + "profit_loss": [ + 0, + 10.0022, + 10.6692, + -31.2996, + -25.4232 + ], + "profit_loss_pct": [ + 0, + 0.0035, + 0.0074, + -0.0038, + -0.0129 + ], + "timeframe": "1D", + "timestamp": [ + 1697241600, + 1697500800, + 1697587200, + 1697673600, + 1697760000 + ] + } + } + }, + "Position": { + "description": "The positions API provides information about an account's current open positions. The response will include information such as cost basis, shares traded, and market value, which will be updated live as price information is updated. Once a position is closed, it will no longer be queryable through this API.", + "properties": { + "asset_class": { + "$ref": "#/components/schemas/AssetClass" + }, + "asset_id": { + "description": "Asset ID (For options this represents the option contract ID)", + "format": "uuid", + "type": "string" + }, + "asset_marginable": { + "type": "boolean" + }, + "avg_entry_price": { + "description": "Average entry price of the position", + "minLength": 1, + "type": "string" + }, + "change_today": { + "description": "Percent change from last day price (by a factor of 1)", + "minLength": 1, + "type": "string" + }, + "cost_basis": { + "description": "Total cost basis in dollar", + "minLength": 1, + "type": "string" + }, + "current_price": { + "description": "Current asset price per share", + "minLength": 1, + "type": "string" + }, + "exchange": { + "$ref": "#/components/schemas/ExchangeForPosition" + }, + "lastday_price": { + "description": "Last day's asset price per share based on the closing value of the last trading day", + "minLength": 1, + "type": "string" + }, + "market_value": { + "description": "Total dollar amount of the position", + "minLength": 1, + "type": "string" + }, + "qty": { + "description": "The number of shares", + "minLength": 1, + "type": "string" + }, + "qty_available": { + "description": "Total number of shares available minus open orders / locked for options covered call", + "minLength": 1, + "type": "string" + }, + "side": { + "description": "long", + "minLength": 1, + "type": "string" + }, + "symbol": { + "description": "Symbol name of the asset", + "example": "AAPL", + "type": "string" + }, + "unrealized_intraday_pl": { + "description": "Unrealized profit/loss in dollars for the day", + "minLength": 1, + "type": "string" + }, + "unrealized_intraday_plpc": { + "description": "Unrealized profit/loss percent (by a factor of 1)", + "minLength": 1, + "type": "string" + }, + "unrealized_pl": { + "description": "Unrealized profit/loss in dollars", + "minLength": 1, + "type": "string" + }, + "unrealized_plpc": { + "description": "Unrealized profit/loss percent (by a factor of 1)", + "minLength": 1, + "type": "string" + } + }, + "required": [ + "asset_id", + "symbol", + "exchange", + "asset_class", + "avg_entry_price", + "qty", + "side", + "market_value", + "cost_basis", + "unrealized_pl", + "unrealized_plpc", + "unrealized_intraday_pl", + "unrealized_intraday_plpc", + "current_price", + "lastday_price", + "change_today", + "asset_marginable" + ], + "title": "Position", + "type": "object", + "x-examples": { + "example-1": { + "asset_class": "us_equity", + "asset_id": "904837e3-3b76-47ec-b432-046db621571b", + "avg_entry_price": "100.0", + "change_today": "0.0084", + "cost_basis": "500.0", + "current_price": "120.0", + "exchange": "NASDAQ", + "lastday_price": "119.0", + "market_value": "600.0", + "qty": "5", + "qty_available": "4", + "side": "long", + "symbol": "AAPL", + "unrealized_intraday_pl": "10.0", + "unrealized_intraday_plpc": "0.0084", + "unrealized_pl": "100.0", + "unrealized_plpc": "0.20" + }, + "example-2": { + "asset_class": "us_equity", + "asset_id": "b0b6dd9d-8b9b-48a9-ba46-b9d54906e415", + "asset_marginable": false, + "avg_entry_price": "174.78", + "change_today": "-0.0018326556325525", + "cost_basis": "349.56", + "current_price": "174.29", + "exchange": "NASDAQ", + "lastday_price": "174.61", + "market_value": "348.58", + "qty": "2", + "qty_available": "2", + "side": "long", + "symbol": "AAPL", + "unrealized_intraday_pl": "-0.98", + "unrealized_intraday_plpc": "-0.0028035244307129", + "unrealized_pl": "-0.98", + "unrealized_plpc": "-0.0028035244307129" + } + } + }, + "PositionClosedReponse": { + "description": "Represents the result of asking the api to close a position. ", + "properties": { + "body": { + "$ref": "#/components/schemas/Order" + }, + "status": { + "description": "HTTP status code for the attempt to close this position", + "type": "integer" + }, + "symbol": { + "description": "Symbol name of the asset", + "type": "string" + } + }, + "required": [ + "symbol", + "status" + ], + "title": "PositionClosedReponse", + "type": "object", + "x-examples": { + "example-1": { + "body": { + "asset_class": "us_equity", + "asset_id": "b0b6dd9d-8b9b-48a9-ba46-b9d54906e415", + "canceled_at": null, + "client_order_id": "52f8574c-96d5-49b6-94c1-2570a268434e", + "created_at": "2022-02-04T16:53:29.53427917Z", + "expired_at": null, + "extended_hours": false, + "failed_at": null, + "filled_at": null, + "filled_avg_price": null, + "filled_qty": "0", + "hwm": null, + "id": "f7f25e89-939a-4587-aaf6-414a6b3c341d", + "legs": null, + "limit_price": null, + "notional": null, + "order_class": "", + "order_type": "market", + "qty": "2", + "replaced_at": null, + "replaced_by": null, + "replaces": null, + "side": "sell", + "status": "accepted", + "stop_price": null, + "submitted_at": "2022-02-04T16:53:29.533738219Z", + "symbol": "AAPL", + "time_in_force": "day", + "trail_percent": null, + "trail_price": null, + "type": "market", + "updated_at": "2022-02-04T16:53:29.53427917Z" + }, + "status": 200, + "symbol": "AAPL" + } + } + }, + "PositionIntent": { + "description": "Represents the desired position strategy.", + "enum": [ + "buy_to_open", + "buy_to_close", + "sell_to_open", + "sell_to_close" + ], + "example": "buy_to_open", + "title": "PositionIntent", + "type": "string" + }, + "ReverseSplitActivityV2": { + "allOf": [ + { + "$ref": "#/components/schemas/CommonSplitStockActivityV2" + }, + { + "properties": { + "ex_date": { + "description": "The ex_date for this corporate action", + "format": "date", + "minLength": 1, + "type": "string" + }, + "new_symbol": { + "description": "Symbol of the new security after the split", + "type": "string" + }, + "record_date": { + "description": "The record_date for this corporate action", + "format": "date", + "minLength": 1, + "type": "string" + }, + "symbol": { + "description": "The symbol of the security involved with the activity", + "type": "string" + } + }, + "required": [ + "symbol" + ], + "type": "object" + } + ], + "description": "Reverse stock split", + "type": "object" + }, + "RightsDistributionActivityV2": { + "allOf": [ + { + "$ref": "#/components/schemas/CommonCaActivityV2" + }, + { + "properties": { + "ex_date": { + "description": "The ex_date for this corporate action", + "format": "date", + "minLength": 1, + "type": "string" + }, + "expiration_date": { + "description": "The expiration date for the rights distribution", + "format": "date", + "type": "string" + }, + "new_cusip": { + "description": "The new CUSIP", + "type": "string" + }, + "new_qty": { + "description": "The new quantity", + "type": "string" + }, + "new_symbol": { + "description": "The new symbol", + "type": "string" + }, + "payable_date": { + "description": "The payable_date for this corporate action", + "format": "date", + "minLength": 1, + "type": "string" + }, + "rate": { + "description": "The rate for the rights distribution", + "type": "string" + }, + "record_date": { + "description": "The record_date for this corporate action", + "format": "date", + "minLength": 1, + "type": "string" + }, + "source_cusip": { + "description": "The source CUSIP", + "type": "string" + }, + "source_qty": { + "description": "The source quantity", + "type": "string" + }, + "source_symbol": { + "description": "The source symbol", + "type": "string" + } + }, + "required": [ + "source_cusip", + "source_symbol", + "source_qty", + "new_cusip", + "new_symbol", + "new_qty", + "rate" + ], + "type": "object" + } + ], + "description": "Rights distribution", + "type": "object" + }, + "RightsSubscriptionElectionActivityV2": { + "allOf": [ + { + "$ref": "#/components/schemas/CommonNTAActivityV2" + }, + { + "$ref": "#/components/schemas/CommonVOFSubtypeActivityV2" + } + ], + "type": "object" + }, + "SDIVActivityV2": { + "allOf": [ + { + "$ref": "#/components/schemas/CommonCaActivityV2" + }, + { + "$ref": "#/components/schemas/CommonSDIVActivityV2" + }, + { + "properties": { + "entitled_qty": { + "description": "Quantity of shares entitled to receive the dividend", + "type": "string" + }, + "new_qty": { + "description": "The total number of shares after the dividend", + "type": "string" + }, + "paid_qty": { + "description": "The paid quantity", + "type": "string" + } + }, + "required": [ + "entitled_qty", + "paid_qty", + "new_qty" + ], + "type": "object" + } + ], + "description": "Stock dividend", + "type": "object" + }, + "SpinoffActivityV2": { + "allOf": [ + { + "$ref": "#/components/schemas/CommonCaActivityV2" + }, + { + "$ref": "#/components/schemas/CommonSpinoffActivityV2" + }, + { + "properties": { + "new_qty": { + "description": "The new quantity", + "type": "string" + }, + "source_qty": { + "description": "The source quantity", + "type": "string" + } + }, + "required": [ + "source_qty", + "new_qty" + ], + "type": "object" + } + ], + "description": "Spinoff", + "type": "object" + }, + "TenderOfferActivityV2": { + "allOf": [ + { + "$ref": "#/components/schemas/CommonNTAActivityV2" + }, + { + "$ref": "#/components/schemas/CommonVOFSubtypeActivityV2" + } + ], + "type": "object" + }, + "TimeInForce": { + "description": "The Time-In-Force values supported by Alpaca vary based on the order's security type. Here is a breakdown of the supported TIFs for each specific security type:\n- Equity trading: day, gtc, opg, cls, ioc, fok.\n- Options trading: day.\n- Crypto trading: gtc, ioc.\n\nBelow are the descriptions of each TIF:\n- day:\n A day order is eligible for execution only on the day it is live. By default, the order is only valid during Regular Trading Hours (9:30am - 4:00pm ET). If unfilled after the closing auction, it is automatically canceled. If submitted after the close, it is queued and submitted the following trading day. However, if marked as eligible for extended hours, the order can also execute during supported extended hours.\n\n- gtc:\n The order is good until canceled. Non-marketable GTC limit orders are subject to price adjustments to offset corporate actions affecting the issue. We do not currently support Do Not Reduce (DNR) orders to opt out of such price adjustments.\n\n- opg:\n Use this TIF with a market/limit order type to submit \"market on open\" (MOO) and \"limit on open\" (LOO) orders. This order is eligible to execute only in the market opening auction. Any unfilled orders after the open will be cancelled. OPG orders submitted after 9:28am but before 7:00pm ET will be rejected. OPG orders submitted after 7:00pm will be queued and routed to the following day's opening auction. On open/on close orders are routed to the primary exchange. Such orders do not necessarily execute exactly at 9:30am / 4:00pm ET but execute per the exchange's auction rules.\n\n- cls:\n Use this TIF with a market/limit order type to submit \"market on close\" (MOC) and \"limit on close\" (LOC) orders. This order is eligible to execute only in the market closing auction. Any unfilled orders after the close will be cancelled. CLS orders submitted after 3:50pm but before 7:00pm ET will be rejected. CLS orders submitted after 7:00pm will be queued and routed to the following day's closing auction. Only available with API v2.\n\n- ioc:\n An Immediate Or Cancel (IOC) order requires all or part of the order to be executed immediately. Any unfilled portion of the order is canceled. Only available with API v2. Most market makers who receive IOC orders will attempt to fill the order on a principal basis only, and cancel any unfilled balance. On occasion, this can result in the entire order being cancelled if the market maker does not have any existing inventory of the security in question.\n\n- fok:\n A Fill or Kill (FOK) order is only executed if the entire order quantity can be filled, otherwise the order is canceled. Only available with API v2.", + "enum": [ + "day", + "gtc", + "opg", + "cls", + "ioc", + "fok" + ], + "example": "day", + "title": "TimeInForce", + "type": "string" + }, + "TokenizationIssuer": { + "description": "The tokenized asset's issuer", + "enum": [ + "xstocks", + "st0x" + ], + "example": "xstocks", + "title": "TokenizationIssuer", + "type": "string" + }, + "TokenizationMintRequest": { + "properties": { + "issuer": { + "$ref": "#/components/schemas/TokenizationIssuer" + }, + "network": { + "$ref": "#/components/schemas/TokenizationNetwork" + }, + "qty": { + "description": "The underlying quantity to convert into the tokenized asset. It can be fractional.", + "type": "string" + }, + "underlying_symbol": { + "description": "The underlying asset symbol", + "type": "string" + }, + "wallet_address": { + "description": "The wallet address to receive the tokenized asset", + "type": "string" + } + }, + "required": [ + "underlying_symbol", + "qty", + "issuer", + "network", + "wallet_address" + ], + "title": "TokenizationMintRequest", + "type": "object" + }, + "TokenizationMintResponse": { + "properties": { + "created_at": { + "format": "date-time", + "type": "string" + }, + "issuer": { + "$ref": "#/components/schemas/TokenizationIssuer" + }, + "network": { + "$ref": "#/components/schemas/TokenizationNetwork" + }, + "qty": { + "description": "The quantity to convert for this tokenization request. It can be fractional.", + "type": "string" + }, + "status": { + "$ref": "#/components/schemas/TokenizationRequestStatus" + }, + "token_symbol": { + "description": "The tokenized asset symbol", + "type": "string" + }, + "tokenization_request_id": { + "description": "Unique identifier of the tokenization request set by Alpaca", + "type": "string" + }, + "underlying_symbol": { + "description": "The underlying asset symbol", + "type": "string" + } + }, + "required": [ + "tokenization_request_id", + "status", + "underlying_symbol", + "token_symbol", + "qty", + "created_at", + "issuer", + "network" + ], + "title": "TokenizationMintResponse", + "type": "object" + }, + "TokenizationNetwork": { + "description": "The token's blockchain network", + "enum": [ + "solana", + "arbitrum", + "ethereum", + "binance", + "base", + "ton", + "tron", + "mantle" + ], + "example": "solana", + "title": "TokenizationNetwork", + "type": "string" + }, + "TokenizationRequest": { + "properties": { + "account": { + "description": "Alpaca account ID associated with this tokenization request", + "type": "string" + }, + "created_at": { + "format": "date-time", + "type": "string" + }, + "fees": { + "description": "Fees charged for this tokenization request", + "nullable": true, + "type": "string" + }, + "issuer": { + "$ref": "#/components/schemas/TokenizationIssuer" + }, + "issuer_account": { + "description": "Issuer's account ID associated with this tokenization request", + "type": "string" + }, + "issuer_request_id": { + "description": "Unique identifier of the tokenization request set by the issuer", + "nullable": true, + "type": "string" + }, + "network": { + "$ref": "#/components/schemas/TokenizationNetwork" + }, + "qty": { + "description": "The quantity to convert for this tokenization request. It can be fractional.", + "type": "string" + }, + "status": { + "$ref": "#/components/schemas/TokenizationRequestStatus" + }, + "token_symbol": { + "description": "The tokenized asset symbol", + "type": "string" + }, + "tokenization_request_id": { + "description": "Unique identifier of the tokenization request set by Alpaca", + "type": "string" + }, + "tx_hash": { + "description": "Transaction hash of the completed request on the blockchain", + "nullable": true, + "type": "string" + }, + "type": { + "$ref": "#/components/schemas/TokenizationRequestType" + }, + "underlying_symbol": { + "description": "The underlying asset symbol", + "type": "string" + }, + "updated_at": { + "format": "date-time", + "nullable": true, + "type": "string" + }, + "wallet_address": { + "description": "The wallet address associated with this tokenization request", + "type": "string" + } + }, + "required": [ + "tokenization_request_id", + "created_at", + "type", + "status", + "underlying_symbol", + "token_symbol", + "qty", + "issuer", + "network", + "wallet_address" + ], + "title": "TokenizationRequest", + "type": "object" + }, + "TokenizationRequestStatus": { + "description": "Status of the tokenization request", + "enum": [ + "pending", + "rejected", + "completed" + ], + "example": "completed", + "title": "TokenizationRequestStatus", + "type": "string" + }, + "TokenizationRequestType": { + "description": "Tokenization request type", + "enum": [ + "mint", + "redeem" + ], + "example": "mint", + "title": "TokenizationRequestType", + "type": "string" + }, + "TradingActivities": { + "properties": { + "activity_type": { + "$ref": "#/components/schemas/ActivityType" + }, + "cum_qty": { + "description": "The cumulative quantity of shares involved in the execution.", + "type": "string" + }, + "id": { + "description": "An id for the activity. Always in \"::\" format. Can be sent as page_token in requests to facilitate the paging of results.", + "type": "string" + }, + "leaves_qty": { + "description": "For partially_filled orders, the quantity of shares that are left to be filled.\n", + "type": "string" + }, + "order_id": { + "description": "The id for the order that filled.", + "format": "uuid", + "type": "string" + }, + "order_status": { + "$ref": "#/components/schemas/OrderStatus" + }, + "price": { + "description": "The per-share price that the trade was executed at.", + "type": "string" + }, + "qty": { + "description": "The number of shares involved in the trade execution.", + "type": "string" + }, + "side": { + "description": "buy or sell", + "type": "string" + }, + "symbol": { + "description": "The symbol of the security being traded.", + "example": "AAPL", + "type": "string" + }, + "transaction_time": { + "description": "The time at which the execution occurred.", + "format": "date-time", + "type": "string" + }, + "type": { + "description": "fill or partial_fill", + "enum": [ + "fill", + "partial_fill" + ], + "example": "fill", + "type": "string" + } + }, + "title": "AccountTradingActivities", + "type": "object", + "x-examples": { + "example-1": { + "activity_type": "FILL", + "cum_qty": "2", + "id": "20220202135509981::2d7be4ff-d1f3-43e9-856a-0f5cf5c5088e", + "leaves_qty": "0", + "order_id": "b5abe576-6a8a-49f3-a353-46b72c1ccae9", + "order_status": "filled", + "price": "174.78", + "qty": "2", + "side": "buy", + "symbol": "AAPL", + "transaction_time": "2022-02-02T18:55:09.981482Z", + "type": "fill" + } + } + }, + "TransferDirection": { + "enum": [ + "INCOMING", + "OUTGOING" + ], + "example": "INCOMING", + "type": "string" + }, + "UnitSplitActivityV2": { + "allOf": [ + { + "$ref": "#/components/schemas/CommonSplitStockActivityV2" + }, + { + "properties": { + "alternate_cusip": { + "description": "CUSIP for the alternate security after the split", + "type": "string" + }, + "alternate_qty": { + "description": "Quantity of alternate shares received", + "type": "string" + }, + "alternate_rate": { + "description": "Ratio of alternate shares received", + "type": "string" + }, + "alternate_symbol": { + "description": "Symbol for the alternate security after the split", + "type": "string" + }, + "effective_date": { + "description": "When the unit split becomes effective", + "format": "date", + "minLength": 1, + "type": "string" + }, + "new_symbol": { + "description": "Symbol of the new security after the split", + "type": "string" + }, + "old_symbol": { + "description": "Symbol of the old security before the split", + "type": "string" + } + }, + "required": [ + "old_symbol", + "new_symbol", + "alternate_cusip", + "alternate_symbol", + "alternate_rate", + "alternate_qty", + "effective_date" + ], + "type": "object" + } + ], + "description": "Unit split", + "type": "object" + }, + "UpdateWatchlistRequest": { + "description": "Request format used for creating a new watchlist or updating an existing watchlist with a set of assets and name.", + "properties": { + "name": { + "description": "The watchlist name.", + "type": "string" + }, + "symbols": { + "description": "List of asset symbols to include in the watchlist.", + "items": { + "nullable": true, + "type": "string" + }, + "type": "array" + } + }, + "required": [ + "name" + ], + "title": "PostWatchlistRequest", + "type": "object" + }, + "WRMActivityV2": { + "allOf": [ + { + "$ref": "#/components/schemas/CommonCaActivityV2" + }, + { + "properties": { + "cusip": { + "description": "The CUSIP of the security involved with the activity", + "type": "string" + }, + "removed_qty": { + "description": "The removed quantity", + "type": "string" + }, + "symbol": { + "description": "The symbol of the security involved with the activity", + "type": "string" + } + }, + "required": [ + "cusip", + "symbol", + "removed_qty" + ], + "type": "object" + } + ], + "description": "Worthless Removal", + "type": "object" + }, + "WarrantExerciseElectionActivityV2": { + "allOf": [ + { + "$ref": "#/components/schemas/CommonNTAActivityV2" + }, + { + "$ref": "#/components/schemas/CommonVOFSubtypeActivityV2" + } + ], + "type": "object" + }, + "Watchlist": { + "description": "The watchlist API provides CRUD operation for the account's watchlist. An account can have multiple watchlists and each is uniquely identified by id but can also be addressed by user-defined name. Each watchlist is an ordered list of assets.\n", + "properties": { + "account_id": { + "description": "account ID", + "format": "uuid", + "type": "string" + }, + "assets": { + "description": "the content of this watchlist, in the order as registered by the client", + "items": { + "$ref": "#/components/schemas/Assets" + }, + "type": "array" + }, + "created_at": { + "format": "date-time", + "type": "string" + }, + "id": { + "description": "watchlist id", + "format": "uuid", + "type": "string" + }, + "name": { + "description": "user-defined watchlist name (up to 64 characters)", + "minLength": 1, + "type": "string" + }, + "updated_at": { + "format": "date-time", + "type": "string" + } + }, + "required": [ + "id", + "account_id", + "created_at", + "updated_at", + "name" + ], + "title": "Watchlist", + "type": "object", + "x-examples": { + "example-1": { + "account_id": "abe25343-a7ba-4255-bdeb-f7e013e9ee5d", + "assets": [ + { + "class": "us_equity", + "easy_to_borrow": true, + "exchange": "NASDAQ", + "fractionable": true, + "id": "8ccae427-5dd0-45b3-b5fe-7ba5e422c766", + "marginable": true, + "name": "Tesla, Inc. Common Stock", + "shortable": true, + "status": "active", + "symbol": "TSLA", + "tradable": true + } + ], + "created_at": "2022-01-31T21:49:05.14628Z", + "id": "3174d6df-7726-44b4-a5bd-7fda5ae6e009", + "name": "Primary Watchlist", + "updated_at": "2022-01-31T21:49:05.14628Z" + } + } + }, + "WatchlistWithoutAsset": { + "description": "The watchlist API provides CRUD operation for the account's watchlist. An account can have multiple watchlists and each is uniquely identified by id but can also be addressed by user-defined name.\n", + "properties": { + "account_id": { + "description": "account ID", + "format": "uuid", + "type": "string" + }, + "created_at": { + "format": "date-time", + "type": "string" + }, + "id": { + "description": "watchlist id", + "format": "uuid", + "type": "string" + }, + "name": { + "description": "user-defined watchlist name (up to 64 characters)", + "minLength": 1, + "type": "string" + }, + "updated_at": { + "format": "date-time", + "type": "string" + } + }, + "required": [ + "id", + "account_id", + "created_at", + "updated_at", + "name" + ], + "title": "Watchlist", + "type": "object", + "x-examples": { + "example-1": { + "account_id": "abe25343-a7ba-4255-bdeb-f7e013e9ee5d", + "created_at": "2022-01-31T21:49:05.14628Z", + "id": "3174d6df-7726-44b4-a5bd-7fda5ae6e009", + "name": "Primary Watchlist", + "updated_at": "2022-01-31T21:49:05.14628Z" + } + } + }, + "WhitelistedAddress": { + "properties": { + "address": { + "description": "The whitelisted address", + "type": "string" + }, + "asset": { + "description": "Symbol of underlying asset for the whitelisted address", + "type": "string" + }, + "chain": { + "description": "Underlying network this address represents", + "type": "string" + }, + "created_at": { + "description": "Timestamp (RFC3339) of account creation.", + "format": "date-time", + "type": "string" + }, + "id": { + "description": "Unique ID for whitelisted address", + "type": "string" + }, + "status": { + "description": "Status of whitelisted address which is either APPROVED or PENDING. Whitelisted addresses will be subjected to a 24 waiting period. After the waiting period is over the status will become APPROVED.", + "enum": [ + "APPROVED", + "PENDING" + ], + "type": "string" + } + }, + "type": "object" + }, + "bic": { + "description": "Business Identifier Code (BIC/SWIFT).", + "example": "IRVTUS3NXXX", + "maxLength": 11, + "minLength": 11, + "pattern": "^[A-Z0-9]{11}$", + "type": "string" + }, + "bond_status": { + "description": "Status of the bond.", + "enum": [ + "outstanding", + "matured", + "pre_issuance" + ], + "type": "string" + }, + "calendar_day": { + "description": "A calendar day.", + "properties": { + "core_end": { + "description": "The end time of the core market session.", + "example": "2025-01-02T16:00:00-05:00", + "format": "date-time", + "type": "string" + }, + "core_start": { + "description": "The start time of the core market session.", + "example": "2025-01-02T09:30:00-05:00", + "format": "date-time", + "type": "string" + }, + "date": { + "description": "The date of the calendar day.", + "example": "2025-01-02", + "format": "date", + "type": "string" + }, + "lunch_end": { + "description": "The end time of the lunch session.", + "format": "date-time", + "type": "string" + }, + "lunch_start": { + "description": "The start time of the lunch session.", + "format": "date-time", + "type": "string" + }, + "post_end": { + "description": "The end time of the after-hours session.", + "example": "2025-01-02T20:00:00-05:00", + "format": "date-time", + "type": "string" + }, + "post_start": { + "description": "The start time of the after-hours session.", + "example": "2025-01-02T16:00:00-05:00", + "format": "date-time", + "type": "string" + }, + "pre_end": { + "description": "The end time of the pre-market session.", + "example": "2025-01-02T09:30:00-05:00", + "format": "date-time", + "type": "string" + }, + "pre_start": { + "description": "The start time of the pre-market session.", + "example": "2025-01-02T04:00:00-05:00", + "format": "date-time", + "type": "string" + }, + "settlement_date": { + "description": "The settlement date.", + "example": "2025-01-03", + "format": "date", + "type": "string" + } + }, + "required": [ + "date", + "core_start", + "core_end" + ], + "type": "object" + }, + "call_type": { + "description": "The type of call on the bond refers to one of a variety of circumstances under which a callable bond may be called.", + "enum": [ + "ordinary", + "make_whole", + "regulatory", + "special" + ], + "type": "string" + }, + "clock": { + "properties": { + "is_market_day": { + "description": "Whether the clock is on a market day.", + "type": "boolean" + }, + "market": { + "$ref": "#/components/schemas/public_market" + }, + "next_market_close": { + "description": "Next market close timestamp", + "format": "date-time", + "type": "string" + }, + "next_market_open": { + "description": "Next market open timestamp", + "format": "date-time", + "type": "string" + }, + "phase": { + "$ref": "#/components/schemas/phase" + }, + "phase_until": { + "description": "The end of the current phase.", + "format": "date-time", + "type": "string" + }, + "timestamp": { + "description": "The time on the clock.", + "format": "date-time", + "type": "string" + } + }, + "required": [ + "market", + "timestamp", + "is_market_day", + "next_market_open", + "next_market_close", + "phase", + "phase_until" + ], + "type": "object" + }, + "clock_resp": { + "description": "Clock response.", + "properties": { + "clocks": { + "items": { + "$ref": "#/components/schemas/clock" + }, + "type": "array" + } + }, + "required": [ + "clocks" + ], + "type": "object", + "x-go-name": "ClockResp" + }, + "coupon_frequency": { + "description": "How often the coupon is paid", + "enum": [ + "annual", + "semi_annual", + "quarterly", + "monthly", + "zero" + ], + "type": "string" + }, + "coupon_type": { + "description": "The type of the coupon rate", + "enum": [ + "fixed", + "floating", + "zero" + ], + "type": "string" + }, + "day_count": { + "description": "The day count convention used to calculate accrued interest.\n\n- `A/360`: calculates the daily interest using a 360-day year and then multiplies that by the actual number of days in each time period.\n- `A/365`: calculates the daily interest using a 365-day year and then multiplies that by the actual number of days in each time period.\n- `30/360`: calculates the daily interest using a 360-day year and then multiplies that by 30 (standardized month).\n- `30/365`: calculates the daily interest using a 365-day year and then multiplies that by 30 (standardized month).\n- `A/A`: calculates the daily interest using the actual number of days in the year and then multiplies that by the actual number of days in each time period.\n- `30E/360`: number of days equals to the actual number of days (for February). If the start date or the end date of the period is the 31st of a month, that date is set to the 30th. The number of days in a year is 360.\n- `B/252`: calculates the daily interest using a 252-business-day year and then multiplies that by the actual number of days in each time period.\n- `A/364`: calculates the daily interest using a 364-day year and then multiplies that by the actual number of days in each time period.\n", + "enum": [ + "A/360", + "A/365", + "30/360", + "30/365", + "A/A", + "30E/360", + "B/252", + "A/364" + ], + "type": "string" + }, + "legacy_calendar_day": { + "description": "A calendar day.", + "properties": { + "close": { + "description": "The time the market closes at on this date in HH:MM format.", + "example": "16:00", + "type": "string" + }, + "date": { + "description": "Date string in YYYY-MM-DD format.", + "example": "2025-01-02", + "format": "date", + "type": "string" + }, + "open": { + "description": "The time the market opens at on this date in HH:MM format.", + "example": "09:30", + "type": "string" + }, + "session_close": { + "description": "The time the session closes at on this date in HHMM format.", + "example": "2000", + "type": "string" + }, + "session_open": { + "description": "The time the session opens at on this date in HHMM format.", + "example": "0400", + "type": "string" + }, + "settlement_date": { + "description": "Date string in YYYY-MM-DD format. Representing the settlement date for the trade date.", + "example": "2025-01-03", + "format": "date", + "type": "string" + } + }, + "required": [ + "date", + "open", + "close", + "session_open", + "session_close", + "settlement_date" + ], + "type": "object" + }, + "legacy_clock": { + "example": { + "is_open": true, + "next_close": "2025-06-24T16:00:00-04:00", + "next_open": "2025-06-25T09:30:00-04:00", + "timestamp": "2025-06-24T14:15:22-04:00" + }, + "properties": { + "is_open": { + "description": "Whether or not the market is open.", + "type": "boolean" + }, + "next_close": { + "description": "Next market close timestamp.", + "format": "date-time", + "type": "string" + }, + "next_open": { + "description": "Next market open timestamp.", + "format": "date-time", + "type": "string" + }, + "timestamp": { + "description": "Current timestamp.", + "format": "date-time", + "type": "string" + } + }, + "required": [ + "timestamp", + "is_open", + "next_open", + "next_close" + ], + "title": "Clock", + "type": "object" + }, + "legacy_public_calendar_resp": { + "items": { + "$ref": "#/components/schemas/legacy_calendar_day" + }, + "type": "array" + }, + "market": { + "description": "The market identifier (MIC, BIC, or acronym).", + "enum": [ + "BMO", + "BNYM", + "BOATS", + "CEUX", + "CHIX", + "HKEX", + "IEX", + "IEXG", + "ISE", + "LSE", + "MTA", + "MTAA", + "NASDAQ", + "NYSE", + "OCEA", + "OPRA", + "OTC", + "OTCM", + "SIFMA", + "TADAWUL", + "XAMS", + "XBRU", + "XDUB", + "XETR", + "XETRA", + "XHKG", + "XLIS", + "XLON", + "XNAS", + "XNYS", + "XPAR", + "XSAU" + ], + "type": "string" + }, + "market_acronym": { + "description": "The acronym of the market.", + "example": "NYSE", + "type": "string" + }, + "market_name": { + "description": "The full name of the market.", + "example": "New York Stock Exchange", + "type": "string" + }, + "market_timezone": { + "description": "The timezone of the market.", + "example": "America/New_York", + "type": "string" + }, + "mic": { + "description": "Market identifier code (ISO 10383).", + "example": "XNYS", + "maxLength": 4, + "minLength": 4, + "pattern": "^[A-Z0-9]{4}$", + "type": "string" + }, + "phase": { + "enum": [ + "closed", + "pre", + "core", + "lunch", + "post" + ], + "type": "string" + }, + "public_calendar_resp": { + "description": "Calendar response.", + "properties": { + "calendar": { + "description": "The market calendar.", + "items": { + "$ref": "#/components/schemas/calendar_day" + }, + "type": "array" + }, + "market": { + "$ref": "#/components/schemas/public_market" + } + }, + "required": [ + "market", + "calendar" + ], + "type": "object", + "x-go-name": "CalendarResp" + }, + "public_market": { + "description": "A market.", + "properties": { + "acronym": { + "$ref": "#/components/schemas/market_acronym" + }, + "bic": { + "$ref": "#/components/schemas/bic" + }, + "mic": { + "$ref": "#/components/schemas/mic" + }, + "name": { + "$ref": "#/components/schemas/market_name" + }, + "timezone": { + "$ref": "#/components/schemas/market_timezone" + } + }, + "required": [ + "acronym", + "name", + "timezone" + ], + "type": "object" + }, + "sp_outlook": { + "description": "A Standard & Poor's rating outlook indicates S&P's view regarding the potential direction of a long-term credit rating over the intermediate term (2 years for investment grade, 1 year for speculative grade)", + "enum": [ + "positive", + "negative", + "developing", + "stable", + "not_rated", + "not_meaningful" + ], + "type": "string" + }, + "treasury_subtype": { + "description": "The subtype of the treasury.", + "enum": [ + "bond", + "bill", + "note", + "strips", + "tips", + "floating" + ], + "type": "string" + }, + "us_corporate": { + "description": "A US corporate", + "properties": { + "accrued_interest": { + "description": "The interest that has accumulated on a bond in dollars per bond between the last interest payment and the present date that has not yet been paid to the bondholder", + "format": "double", + "type": "number", + "x-go-type": "decimal.Decimal", + "x-go-type-import": { + "name": "decimal", + "path": "github.com/alpacahq/alpacadecimal" + } + }, + "bond_status": { + "$ref": "#/components/schemas/bond_status" + }, + "call_type": { + "$ref": "#/components/schemas/call_type" + }, + "callable": { + "description": "Whether the bond is callable, meaning the issuer has the right, but not the obligation to redeem the bond - in other words, pay out the bondholder - before its maturity date at a set price (the call price)", + "type": "boolean" + }, + "close_price": { + "description": "The price of the last transaction of a security before the market closes for normal trading, shown as a percentage of par value", + "format": "double", + "type": "number", + "x-go-type": "decimal.Decimal", + "x-go-type-import": { + "name": "decimal", + "path": "github.com/alpacahq/alpacadecimal" + } + }, + "close_price_date": { + "description": "The date of the close price", + "format": "date", + "type": "string" + }, + "close_yield_to_maturity": { + "description": "Yield to maturity of the treasury after the last close", + "format": "double", + "type": "number", + "x-go-type": "decimal.Decimal", + "x-go-type-import": { + "name": "decimal", + "path": "github.com/alpacahq/alpacadecimal" + } + }, + "close_yield_to_worst": { + "description": "Yield to worst of the treasury after the last close", + "format": "double", + "type": "number", + "x-go-type": "decimal.Decimal", + "x-go-type-import": { + "name": "decimal", + "path": "github.com/alpacahq/alpacadecimal" + } + }, + "convertible": { + "description": "A flag indicating whether the bond is convertible", + "type": "boolean" + }, + "country_domicile": { + "description": "The country where the corporate is domiciled in the 2-alpha country code format (e.g., US, CA)", + "type": "string" + }, + "coupon": { + "description": "The annual interest rate paid on the bond as a percentage of par value", + "format": "double", + "type": "number", + "x-go-type": "decimal.Decimal", + "x-go-type-import": { + "name": "decimal", + "path": "github.com/alpacahq/alpacadecimal" + } + }, + "coupon_frequency": { + "$ref": "#/components/schemas/coupon_frequency" + }, + "coupon_type": { + "$ref": "#/components/schemas/coupon_type" + }, + "cusip": { + "description": "CUSIP is a nine-character alphanumeric code that uniquely identifies the security", + "maxLength": 12, + "minLength": 12, + "pattern": "^[A-Z0-9]{9}$", + "type": "string" + }, + "dated_date": { + "description": "The dated date marks the beginning of the period for which interest starts accruing on the bond", + "format": "date", + "type": "string" + }, + "day_count": { + "$ref": "#/components/schemas/day_count" + }, + "description": { + "description": "Description of the corporate bond", + "type": "string" + }, + "description_short": { + "description": "Short description of the corporate bond", + "type": "string" + }, + "first_coupon_date": { + "description": "The date of the first coupon payment", + "format": "date", + "type": "string" + }, + "isin": { + "description": "International Securities Identification Number", + "maxLength": 12, + "minLength": 12, + "pattern": "^[A-Z]{2}[A-Z0-9]{9}[0-9]$", + "type": "string" + }, + "issue_date": { + "description": "The date on which the bond was issued", + "format": "date", + "type": "string" + }, + "issue_minimum_denomination": { + "description": "The smallest unit of the bond that can be purchased at its initial offering", + "type": "number" + }, + "issue_price": { + "description": "The price at which the bond was originally issued as a percentage of par value", + "format": "double", + "type": "number", + "x-go-type": "decimal.Decimal", + "x-go-type-import": { + "name": "decimal", + "path": "github.com/alpacahq/alpacadecimal" + } + }, + "issue_size": { + "description": "The total size amount of the bond issue in the issuing currency", + "type": "number" + }, + "issuer": { + "description": "The name of the issuer of the corporate bond", + "type": "string" + }, + "last_coupon_date": { + "description": "The date of the last coupon payment", + "format": "date", + "type": "string" + }, + "liquidity_institutional_aggregate": { + "description": "Score (from 1-5 or null if the bond is not priced/tradable) reflecting the historical depth of executable liquidity to buy or sell (no minimum trading sizes)", + "type": "number" + }, + "liquidity_institutional_buy": { + "description": "Score (from 1-5 or null if the bond is not priced/tradable) reflecting the historical depth of executable liquidity to buy (no minimum trading sizes)", + "type": "number" + }, + "liquidity_institutional_sell": { + "description": "Score (from 1-5 or null if the bond is not priced/tradable) reflecting the historical depth of executable liquidity to sell (no minimum trading sizes)", + "type": "number" + }, + "liquidity_micro_aggregate": { + "description": "Score (from 1-5 or null if the bond is not priced/tradable) reflecting the historical depth of executable liquidity to buy or sell with minimum trading sizes less than or equal to $1,000.00", + "type": "number" + }, + "liquidity_micro_buy": { + "description": "Score (from 1-5 if the bond is priced, or null if the bond is not tradable) reflecting the historical depth of executable liquidity to buy with minimum trading sizes less than or equal to $1,000.00", + "type": "number" + }, + "liquidity_micro_sell": { + "description": "Score (from 1-5 or null if the bond is not priced/tradable) reflecting the historical depth of executable liquidity to sell with minimum trading sizes less than or equal to $1,000.00", + "type": "number" + }, + "liquidity_retail_aggregate": { + "description": "Score (from 1-5 or null if the bond is not priced/tradable) reflecting the historical depth of executable liquidity to buy or sell with minimum trading sizes less than or equal to 10,000", + "type": "number" + }, + "liquidity_retail_buy": { + "description": "Score (from 1-5 or null if the bond is not priced/tradable) reflecting the historical depth of executable liquidity to buy with minimum trading sizes less than or equal to $10,000.00", + "type": "number" + }, + "liquidity_retail_sell": { + "description": "Score (from 1-5 or null if the bond is not priced/tradable) reflecting the historical depth of executable liquidity to sell with minimum trading sizes less than or equal to 10,000", + "type": "number" + }, + "marginable": { + "description": "Whether the corporate is marginable", + "type": "boolean" + }, + "maturity_date": { + "description": "The date on which the bond matures", + "format": "date", + "type": "string" + }, + "next_call_date": { + "description": "The date of the next possible call on the bond.", + "format": "date", + "type": "string" + }, + "next_call_price": { + "description": "The price at which a callable bond can be redeemed by the issuer on the next call date, as a percentage of par.", + "format": "double", + "type": "number", + "x-go-type": "decimal.Decimal", + "x-go-type-import": { + "name": "decimal", + "path": "github.com/alpacahq/alpacadecimal" + } + }, + "next_coupon_date": { + "description": "The date of the next coupon payment", + "format": "date", + "type": "string" + }, + "par_value": { + "description": "The amount that the issuer of the bond will pay back to the bondholder upon maturity", + "type": "number" + }, + "perpetual": { + "description": "A flag representing whether a bond is perpetual", + "type": "boolean" + }, + "puttable": { + "description": "Whether the bond is puttable, meaning the bondholder has the right, but not the obligation to sell the bond back to the issuer at a set price (the put price) on specified dates before maturity", + "type": "boolean" + }, + "reg_s": { + "description": "Indicates whether the security falls under Regulation S, a rule that provides an exemption from the registration requirements for securities offerings made outside the United States", + "type": "boolean" + }, + "reissue_date": { + "description": "The date on which the corporate was reissued", + "format": "date", + "type": "string" + }, + "reissue_price": { + "description": "The price at which the corporate was reissued as a percentage of par value", + "format": "double", + "type": "number", + "x-go-type": "decimal.Decimal", + "x-go-type-import": { + "name": "decimal", + "path": "github.com/alpacahq/alpacadecimal" + } + }, + "reissue_size": { + "description": "The total size amount of the corporate reissue in the issuing currency", + "type": "number" + }, + "sector": { + "description": "The sector of the corporate bond", + "type": "string" + }, + "seniority": { + "description": "The seniority of the corporate bond", + "type": "string" + }, + "sp_creditwatch": { + "description": "S&P's CreditWatch highlights S&P's opinion regarding the potential direction of a short-term or long-term rating", + "type": "string" + }, + "sp_creditwatch_date": { + "description": "The date of the most recent Standard & Poor's CreditWatch for the bond in YYYY-MM-DD format", + "format": "date", + "type": "string" + }, + "sp_outlook": { + "$ref": "#/components/schemas/sp_outlook" + }, + "sp_outlook_date": { + "description": "The date of the most recent Standard & Poor's outlook for the bond in YYYY-MM-DD format", + "format": "date", + "type": "string" + }, + "sp_rating": { + "description": "Standard & Poor's rating for the bond in the standard AAA - D format", + "type": "string" + }, + "sp_rating_date": { + "description": "The date in the timezone of the issuing country of the most recent Standard & Poor's rating for the bond in YYYY-MM-DD format", + "format": "date", + "type": "string" + }, + "ticker": { + "description": "The ticker symbol of the corporate", + "type": "string" + }, + "tradable": { + "description": "Whether the treasury is tradable", + "type": "boolean" + } + }, + "required": [ + "isin", + "cusip", + "bond_status", + "tradable", + "marginable", + "issue_date", + "country_domicile", + "ticker", + "seniority", + "issuer", + "sector", + "description", + "description_short", + "coupon", + "coupon_type", + "coupon_frequency", + "perpetual", + "day_count", + "dated_date", + "issue_size", + "issue_price", + "issue_minimum_denomination", + "par_value", + "callable", + "puttable", + "convertible", + "reg_s" + ], + "type": "object" + }, + "us_corporates_resp": { + "properties": { + "us_corporates": { + "items": { + "$ref": "#/components/schemas/us_corporate" + }, + "type": "array" + } + }, + "required": [ + "us_corporates" + ], + "type": "object" + }, + "us_treasuries_resp": { + "properties": { + "us_treasuries": { + "items": { + "$ref": "#/components/schemas/us_treasury" + }, + "type": "array" + } + }, + "required": [ + "us_treasuries" + ], + "type": "object" + }, + "us_treasury": { + "description": "A US treasury", + "properties": { + "bond_status": { + "$ref": "#/components/schemas/bond_status" + }, + "close_price": { + "description": "The price of the last transaction of a security before the market closes for normal trading, shown as a percentage of par value", + "format": "double", + "type": "number" + }, + "close_price_date": { + "description": "The date of the close price", + "format": "date", + "type": "string" + }, + "close_yield_to_maturity": { + "description": "Yield to maturity of the treasury after the last close", + "format": "double", + "type": "number" + }, + "close_yield_to_worst": { + "description": "Yield to worst of the treasury after the last close", + "format": "double", + "type": "number" + }, + "coupon": { + "description": "The annual interest rate paid on the bond as a percentage of par value", + "format": "double", + "type": "number" + }, + "coupon_frequency": { + "$ref": "#/components/schemas/coupon_frequency" + }, + "coupon_type": { + "$ref": "#/components/schemas/coupon_type" + }, + "cusip": { + "description": "CUSIP is a nine-character alphanumeric code that uniquely identifies the security", + "maxLength": 12, + "minLength": 12, + "pattern": "^[A-Z0-9]{9}$", + "type": "string" + }, + "description": { + "description": "Description of the treasury", + "type": "string" + }, + "description_short": { + "description": "Short description of the treasury", + "type": "string" + }, + "first_coupon_date": { + "description": "The date of the first coupon payment", + "format": "date", + "type": "string" + }, + "isin": { + "description": "International Securities Identification Number", + "maxLength": 12, + "minLength": 12, + "pattern": "^[A-Z]{2}[A-Z0-9]{9}[0-9]$", + "type": "string" + }, + "issue_date": { + "description": "The date on which the bond was issued", + "format": "date", + "type": "string" + }, + "last_coupon_date": { + "description": "The date of the last coupon payment", + "format": "date", + "type": "string" + }, + "maturity_date": { + "description": "The date on which the bond matures", + "format": "date", + "type": "string" + }, + "next_coupon_date": { + "description": "The date of the next coupon payment", + "format": "date", + "type": "string" + }, + "subtype": { + "$ref": "#/components/schemas/treasury_subtype" + }, + "tradable": { + "description": "Whether the treasury is tradable", + "type": "boolean" + } + }, + "required": [ + "cusip", + "isin", + "bond_status", + "tradable", + "subtype", + "issue_date", + "maturity_date", + "description", + "description_short", + "coupon", + "coupon_type", + "coupon_frequency" + ], + "type": "object" + } + }, + "securitySchemes": { + "API_Key": { + "description": "", + "in": "header", + "name": "APCA-API-KEY-ID", + "type": "apiKey" + }, + "API_Secret": { + "description": "", + "in": "header", + "name": "APCA-API-SECRET-KEY", + "type": "apiKey" + } + } + }, + "info": { + "contact": { + "email": "support@alpaca.markets", + "name": "Alpaca Support", + "url": "https://alpaca.markets/support" + }, + "description": "Alpaca's Trading API is a modern platform for algorithmic trading.", + "termsOfService": "https://s3.amazonaws.com/files.alpaca.markets/disclosures/library/TermsAndConditions.pdf", + "title": "Trading API", + "version": "2.0.1" + }, + "openapi": "3.0.0", + "paths": { + "/v2beta1/events/activities": { + "get": { + "description": "The Events API sends the real-time events and provides historical queries with SSE (Server Sent Events).\n\nThis endpoint streams events on account activities.\n\nHistorical events are streamed immediately if queried, and updates are pushed as events occur.\n\nQuery parameter rules:\n- If `until` is specified, `since` is required.\n- If `until_id` is specified, `since_id` is required.\n- You cannot use `since` and `since_id` together.\nBehavior:\n- If `since` or `since_id` is not specified, this will not return any historic data.\n- If `until` or `until_id` is specified, stream will end at the specified point with status 200.\n\n---\n\nWarning: Currently, OAS-3 does not fully support responses from an SSE API.\n\nIn case the client code is generated from this OAS spec, please do not specify `since` and `until`, as the generated client may hang forever waiting for the response to end.\n\nIf you require the streaming capabilities, we recommend not using the generated clients for this specific endpoint until the OAS-3 standards define how to represent this behavior.\n\n---\n\n### Comment messages\nAccording to the SSE specification, any line that starts with a colon is a comment which does not contain data. It is typically a free text that does not follow any data schema. A few examples mentioned below for comment messages.\n\n##### Slow client\n\nThe server sends a comment when the client is not consuming messages fast enough. Example: `: you are reading too slowly, dropped 10000 messages`\n\n##### Internal server error\n\nAn error message is sent as a comment when the server closes the connection on an internal server error (only sent by the v2 and v2beta1 endpoints). Example: `: internal server error`\n\n---", + "operationId": "subscribeToActivitiesSSE", + "parameters": [ + { + "description": "Format: RFC3339 or YYYY-MM-DD", + "in": "query", + "name": "since", + "schema": { + "format": "date-time", + "type": "string" + } + }, + { + "description": "Format: RFC3339 or YYYY-MM-DD", + "in": "query", + "name": "until", + "schema": { + "format": "date-time", + "type": "string" + } + }, + { + "in": "query", + "name": "since_id", + "schema": { + "format": "ulid", + "type": "string" + } + }, + { + "in": "query", + "name": "until_id", + "schema": { + "format": "ulid", + "type": "string" + } + } + ], + "responses": { + "200": { + "content": { + "text/event-stream": { + "schema": { + "items": { + "$ref": "#/components/schemas/ActivityEventV2" + }, + "type": "array" + } + } + }, + "description": "Connected. Events will now start streaming as long as you keep the connection open." + } + }, + "summary": "Subscribe to Activity Events (SSE)", + "tags": [ + "Events" + ] + } + }, + "/v2/account": { + "get": { + "description": "Returns your account details.", + "operationId": "getAccount", + "responses": { + "200": { + "content": { + "application/json": { + "example": { + "account_blocked": false, + "account_number": "PALPACA_123", + "accrued_fees": "0", + "admin_configurations": {}, + "balance_asof": "2023-09-27", + "bod_dtbp": 0, + "buying_power": "245432.61", + "cash": "122086.5", + "created_at": "2023-01-01T18:20:20.272275Z", + "crypto_status": "ACTIVE", + "crypto_tier": 1, + "currency": "USD", + "daytrade_count": 0, + "daytrading_buying_power": "0", + "effective_buying_power": "245432.61", + "equity": "123346.11", + "id": "1d9eed04-be39-4e01-9b84-a48ac5bbafcf", + "initial_margin": "629.8", + "intraday_adjustments": "0", + "last_equity": "122011.09751111286868", + "last_maintenance_margin": "480.73", + "long_market_value": "1259.61", + "maintenance_margin": "377.88", + "multiplier": "2", + "non_marginable_buying_power": "122086.5", + "options_buying_power": "122716.305", + "options_trading_level": 2, + "pattern_day_trader": true, + "pending_reg_taf_fees": "0", + "pending_transfer_in": "0", + "portfolio_value": "123346.11", + "position_market_value": "1259.61", + "regt_buying_power": "245432.61", + "short_market_value": "0", + "shorting_enabled": true, + "sma": "123369.74", + "status": "ACTIVE", + "trade_suspended_by_user": false, + "trading_blocked": false, + "transfers_blocked": false, + "user_configurations": null + }, + "schema": { + "$ref": "#/components/schemas/Account" + } + } + }, + "description": "OK" + } + }, + "summary": "Get Account", + "tags": [ + "Accounts" + ] + } + }, + "/v2/account/activities": { + "get": { + "description": "Returns a list of activities\n\nNotes:\n* Pagination is handled using the `page_token` and `page_size` parameters.\n* `page_token` represents the ID of the last item on your current page of results.\n For example, if the ID of the last activity in your first response is `20220203000000000::045b3b8d-c566-4bef-b741-2bf598dd6ae7`, you would pass that value as `page_token` to retrieve the next page of results.", + "operationId": "getAccountActivities", + "parameters": [ + { + "description": "A comma-separated list of activity types used to filter the results.", + "explode": false, + "in": "query", + "name": "activity_types", + "schema": { + "items": { + "$ref": "#/components/schemas/ActivityType" + }, + "type": "array" + }, + "style": "form" + }, + { + "description": "The activity category. Cannot be used with \"activity_types\" parameter.", + "in": "query", + "name": "category", + "schema": { + "enum": [ + "trade_activity", + "non_trade_activity" + ], + "type": "string" + } + }, + { + "description": "Filter activities by their creation date (created_at), not the activity's settlement date. For non-trade activities such as fees, the creation date is typically the day after the trade date (in UTC). Both formats YYYY-MM-DD and YYYY-MM-DDTHH:MM:SSZ are supported.", + "in": "query", + "name": "date", + "schema": { + "format": "date-time", + "type": "string" + } + }, + { + "description": "Get activities created before this date. Both formats YYYY-MM-DD and YYYY-MM-DDTHH:MM:SSZ are supported.", + "in": "query", + "name": "until", + "schema": { + "format": "date-time", + "type": "string" + } + }, + { + "description": "Get activities created after this date. Both formats YYYY-MM-DD and YYYY-MM-DDTHH:MM:SSZ are supported.", + "in": "query", + "name": "after", + "schema": { + "format": "date-time", + "type": "string" + } + }, + { + "description": "The chronological order of response based on the activity datetime.", + "in": "query", + "name": "direction", + "schema": { + "default": "desc", + "enum": [ + "asc", + "desc" + ], + "example": "desc", + "type": "string" + } + }, + { + "description": "The maximum number of entries to return in the response.", + "in": "query", + "name": "page_size", + "schema": { + "default": 100, + "maximum": 100, + "minimum": 1, + "type": "integer" + } + }, + { + "description": "Token used for pagination. Provide the ID of the last activity from the last page to retrieve the next set of results.", + "in": "query", + "name": "page_token", + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "items": { + "anyOf": [ + { + "$ref": "#/components/schemas/TradingActivities" + }, + { + "$ref": "#/components/schemas/NonTradeActivities" + } + ], + "description": "Will be a mix of TradingActivity or NonTradeActivity objects based on what is passed in the activity_types parameter" + }, + "type": "array" + } + } + }, + "description": "returns an array of Account activities" + } + }, + "summary": "Retrieve Account Activities", + "tags": [ + "Account Activities" + ] + } + }, + "/v2/account/activities/{activity_type}": { + "get": { + "description": "Returns account activity entries for a specific type of activity.", + "operationId": "getAccountActivitiesByActivityType", + "parameters": [ + { + "description": "Filter activities by their creation date (created_at), not the activity's settlement date. For non-trade activities such as fees, the creation date is typically the day after the trade date (in UTC). Both formats YYYY-MM-DD and YYYY-MM-DDTHH:MM:SSZ are supported.", + "in": "query", + "name": "date", + "schema": { + "format": "date-time", + "type": "string" + } + }, + { + "description": "Get activities created before this date. Both formats YYYY-MM-DD and YYYY-MM-DDTHH:MM:SSZ are supported.", + "in": "query", + "name": "until", + "schema": { + "format": "date-time", + "type": "string" + } + }, + { + "description": "Get activities created after this date. Both formats YYYY-MM-DD and YYYY-MM-DDTHH:MM:SSZ are supported.", + "in": "query", + "name": "after", + "schema": { + "format": "date-time", + "type": "string" + } + }, + { + "description": "The chronological order of response based on the activity datetime.", + "in": "query", + "name": "direction", + "schema": { + "default": "desc", + "enum": [ + "asc", + "desc" + ], + "example": "desc", + "type": "string" + } + }, + { + "description": "The maximum number of entries to return in the response.", + "in": "query", + "name": "page_size", + "schema": { + "default": 100, + "maximum": 100, + "minimum": 1, + "type": "integer" + } + }, + { + "description": "Token used for pagination. Provide the ID of the last activity from the last page to retrieve the next set of results.", + "in": "query", + "name": "page_token", + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "items": { + "description": "Will be one of a TradingActivity or NonTradeActivity based on activity_type used in path", + "oneOf": [ + { + "$ref": "#/components/schemas/TradingActivities" + }, + { + "$ref": "#/components/schemas/NonTradeActivities" + } + ] + }, + "type": "array" + } + } + }, + "description": "returns an array of Account activities" + } + }, + "summary": "Retrieve Account Activities of Specific Type", + "tags": [ + "Account Activities" + ] + }, + "parameters": [ + { + "description": "The activity type you want to view entries for. A list of valid activity types can be found at the bottom of this page.", + "in": "path", + "name": "activity_type", + "required": true, + "schema": { + "type": "string" + } + } + ] + }, + "/v2/account/configurations": { + "get": { + "description": "gets the current account configuration values", + "operationId": "getAccountConfig", + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/AccountConfigurations" + } + } + }, + "description": "Successful response" + } + }, + "summary": "Get Account Configurations", + "tags": [ + "Account Configurations" + ] + }, + "patch": { + "description": "Updates and returns the current account configuration values", + "operationId": "patchAccountConfig", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/AccountConfigurations" + } + } + } + }, + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/AccountConfigurations" + } + } + }, + "description": "Successful response" + } + }, + "summary": "Account Configurations", + "tags": [ + "Account Configurations" + ] + } + }, + "/v2/account/portfolio/history": { + "get": { + "description": "Returns timeseries data about equity and profit/loss (P/L) of the account in requested timespan.", + "operationId": "getAccountPortfolioHistory", + "parameters": [ + { + "description": "The duration of the data in `number` + `unit` format, such as 1D, where `unit` can be D for day, W for week, M for month and A for year. Defaults to 1M.\n\nOnly two of `start`, `end` and `period` can be specified at the same time.\n\nFor intraday timeframes (\\<1D) only 30 days or less can be queried, for 1D resolutions there is no such limit, data is available since the\ncreation of the account.\n", + "in": "query", + "name": "period", + "schema": { + "type": "string" + } + }, + { + "description": "The resolution of time window. 1Min, 5Min, 15Min, 1H, or 1D. If omitted, 1Min for less than 7 days period,\n15Min for less than 30 days, or otherwise 1D.\n\nFor queries with longer than 30 days of `period`, the system only accepts 1D as `timeframe`.\n", + "in": "query", + "name": "timeframe", + "schema": { + "type": "string" + } + }, + { + "description": "For intraday resolutions (<1D) this specifies which timestamps to return data points for:\n\nAllowed values are:\n- **market_hours**\n\n Only timestamps for the core equity trading hours are returned (usually 9:30am to 4:00pm, trading days only)\n\n- **extended_hours**\n\n Returns timestamps for the whole session including extended hours (usually 4:00am to 8:00pm, trading days only)\n\n- **continuous**\n\n Returns price data points 24/7 (for off-session times too). To calculate the equity values we are using the following prices:\n\n Between 4:00am and 10:00pm on trading days the valuation will be calculated based on the last trade (extended hours and normal hours respectively).\n\n After 10:00pm, until the next session open the equities will be valued at their official closing price on the primary exchange.\n", + "in": "query", + "name": "intraday_reporting", + "schema": { + "default": "market_hours", + "enum": [ + "market_hours", + "extended_hours", + "continuous" + ], + "type": "string" + } + }, + { + "description": "The timestamp the data is returned starting from in RFC3339 format (including timezone specification). Defaults to `end` minus `period`\n\nIf provided, the `start` value is always normalized to the `America/New_York` timezone and adjusted to the nearest `timeframe` interval, e.g. seconds are always truncated and the time is rounded backwards to the nearest interval of `1Min`, `5Min`, `15Min`, or `1H`.\n\nIf `timeframe=1D` and `start` is not a valid trading date, find the next available trading date. For example, if `start` occurs on Saturday or Sunday after converting to the America/New_York timezone, `start` is adjusted to the first weekday that is not a market holiday (e.g. Monday).\n\nIf `timeframe` is less than `1D` and `intraday_reporting` is not `continuous`, `start` always reflects the beginning of a market session. If `start` is between midnight and the end (inclusive) of an active trading day, `start` is set to the beginning of the session on the specified day. Otherwise, if `start` occurs outside of the market session, the next available market date is used.\n\nFor example, when `intraday_reporting=market_hours` and `start=2023-10-19T23:59:59-04:00`, the provided `start` date occurs outside of the regular market session. The effective `start` timestamp is adjusted to the beginning of the next session: `2023-10-20T09:30:00-04:00`\n\n`start` may be be combined with one of `end` or `period`.\n\nProviding all of `start`, `end`, and `period` is invalid.\n", + "in": "query", + "name": "start", + "schema": { + "example": "2021-03-16T18:38:01Z", + "format": "date-time", + "type": "string" + } + }, + { + "description": "`pnl_reset` defines how we are calculating the baseline values for Profit And Loss (pnl) for queries with `timeframe` less than 1D (intraday queries).\n\nThe default behavior for intraday queries is that we reset the pnl value to the previous day's closing equity for each **trading** day.\n\nIn case of crypto (given its continuous nature), this might not be desired: specifying \"no_reset\" disables this behavior and all pnl values\nreturned will be relative to the closing equity of the previous trading day.\n\nFor 1D resolution all PnL values are calculated relative to the `base_value`, we are not resetting the base value.\n", + "in": "query", + "name": "pnl_reset", + "schema": { + "default": "per_day", + "enum": [ + "no_reset", + "per_day" + ], + "type": "string" + } + }, + { + "description": "The timestamp the data is returned up to in RFC3339 format (including timezone specification). Defaults to the current time.\n\nIf provided, the `end` value is always normalized to the `America/New_York` timezone and adjusted to the nearest `timeframe` interval, e.g. seconds are always truncated and the time is rounded backwards to the nearest interval of `1Min`, `5Min`, `15Min`, or `1H`.\n\nWhen `intraday_reporting` is either `market_hours` or `extended_hours`, the `end` value is adjusted to not occur after session close on the specified day. For example if the `intraday_reporting` is `extended_hours`, and the timestamp specified is `2023-10-19T21:33:00-04:00`, `end` is adjusted to `2023-10-19T20:00:00-04:00`.\n\n`end` may be combined with `start` or `period`.\n\nProviding all of `start`, `end`, and `period` is invalid.\n", + "in": "query", + "name": "end", + "schema": { + "example": "2021-03-16T18:38:01Z", + "format": "date-time", + "type": "string" + } + }, + { + "description": "**deprecated**: Users are strongly advised to **rely on the `intraday_reporting` query parameter** for better control\nof the reporting range.\n\nIf true, include extended hours in the result. This is effective only for timeframe less than 1D.\n", + "in": "query", + "name": "extended_hours", + "schema": { + "type": "string" + } + }, + { + "description": "The cashflow activities to include in the report. One of 'ALL', 'NONE', or a comma-separated list of activity types.", + "examples": { + "all": { + "value": "ALL" + }, + "cash_transfers": { + "summary": "Include cash deposits, withdraws, and transaction fees", + "value": "CSD,CSW,FEE" + }, + "none": { + "value": "NONE" + } + }, + "in": "query", + "name": "cashflow_types", + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PortfolioHistory" + } + } + }, + "description": "Successful response" + } + }, + "summary": "Get Account Portfolio History", + "tags": [ + "Portfolio History" + ] + } + }, + "/v2/assets": { + "get": { + "description": "The assets API serves as the master list of assets available for trade and data consumption from Alpaca. Assets are sorted by asset class, exchange and symbol.", + "operationId": "get-v2-assets", + "parameters": [ + { + "description": "e.g. \"active\". By default, all statuses are included.", + "in": "query", + "name": "status", + "schema": { + "type": "string" + } + }, + { + "description": "Defaults to us_equity.", + "in": "query", + "name": "asset_class", + "schema": { + "type": "string" + } + }, + { + "description": "Optional AMEX, ARCA, BATS, NYSE, NASDAQ, NYSEARCA or OTC", + "in": "query", + "name": "exchange", + "schema": { + "type": "string" + } + }, + { + "description": "Comma separated values to query for more than one attribute. Assets which have any of the given attributes will be included.\n\nSupported values:\n- `ptp_no_exception`: Asset is a Publicly Traded Partnership (PTP) without a qualified notice; non-U.S. customers may incur 10% withholding on gross proceeds as per IRS guidance, and are blocked from being purchased by default.\n- `ptp_with_exception`: Users can open positions in these PTPs without general restrictions.\n- `ipo`: Accepting limit orders only before the stock begins trading on the secondary market.\n- `has_options`: The underlying equity has listed options available on the platform. Note: if the equity had inactive/expired contracts in the past, this will still show up.\n- `options_late_close`: Indicates the underlying asset's options contracts close at 4:15pm ET instead of the standard 4:00pm ET.\n- `fractional_eh_enabled`: Indicates the asset accepts fractional orders during extended hours sessions (pre-market, post-market, and overnight if enabled).\n- `overnight_tradable`: Asset is eligible for overnight (24x5) trading in supported venues on the platform.\n- `overnight_halted`: Asset is eligible for overnight trading but is currently halted/blocked for overnight sessions due to risk, corporate action, compliance, or venue constraints.", + "explode": false, + "in": "query", + "name": "attributes", + "schema": { + "default": [], + "example": [ + "ptp_no_exception", + "ipo" + ], + "items": { + "enum": [ + "ptp_no_exception", + "ptp_with_exception", + "ipo", + "has_options", + "options_late_close", + "fractional_eh_enabled", + "overnight_tradable", + "overnight_halted" + ], + "type": "string" + }, + "type": "array" + } + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "items": { + "$ref": "#/components/schemas/Assets" + }, + "type": "array" + } + } + }, + "description": "An array of asset objects" + } + }, + "summary": "Get Assets", + "tags": [ + "Assets" + ] + } + }, + "/v2/assets/{symbol_or_asset_id}": { + "get": { + "description": "Get the asset model for a given symbol or asset_id. The symbol or asset_id should be passed in as a path parameter.\n\n**Note**: For crypto, the symbol has to follow old symbology, e.g. BTCUSD.\n\n**Note**: For coin pairs, the symbol should be separated by a slash (/), e.g. BTC/USDT. Since the slash is a special character in HTTP, use the URL encoded version instead, e.g. /v2/assets/BTC%2FUSDT", + "operationId": "get-v2-assets-symbol_or_asset_id", + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Assets" + } + } + }, + "description": "An Asset object" + }, + "404": { + "description": "Not Found" + } + }, + "summary": "Get an Asset by ID or Symbol", + "tags": [ + "Assets" + ] + }, + "parameters": [ + { + "description": "symbol or assetId. CUSIP is also accepted for US equities.", + "in": "path", + "name": "symbol_or_asset_id", + "required": true, + "schema": { + "type": "string" + } + } + ] + }, + "/v2/assets/fixed_income/us_corporates": { + "get": { + "description": "Serves the list of US corporates available at Alpaca. The response is sorted by ISIN.", + "operationId": "UsCorporates", + "parameters": [ + { + "in": "query", + "name": "bond_status", + "schema": { + "$ref": "#/components/schemas/bond_status" + } + }, + { + "description": "A comma-separated list of ISINs with a limit of 1000.", + "example": "US912810UG12,US912797PM34", + "in": "query", + "name": "isins", + "schema": { + "type": "string" + } + }, + { + "description": "A comma-separated list of CUSIPs with a limit of 1000.", + "example": "912810UG1,912797PM3", + "in": "query", + "name": "cusips", + "schema": { + "type": "string" + } + }, + { + "description": "A comma-separated list of tickers with a limit of 1000.", + "example": "BAC,MSFT", + "in": "query", + "name": "tickers", + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/us_corporates_resp" + } + } + }, + "description": "OK", + "headers": { + "X-RateLimit-Limit": { + "$ref": "#/components/headers/ratelimit_limit" + }, + "X-RateLimit-Remaining": { + "$ref": "#/components/headers/ratelimit_remaining" + }, + "X-RateLimit-Reset": { + "$ref": "#/components/headers/ratelimit_reset" + } + } + }, + "400": { + "$ref": "#/components/responses/400" + }, + "401": { + "$ref": "#/components/responses/401" + }, + "403": { + "$ref": "#/components/responses/403" + }, + "429": { + "$ref": "#/components/responses/429" + }, + "500": { + "$ref": "#/components/responses/500" + } + }, + "summary": "Get US corporates", + "tags": [ + "Assets" + ] + } + }, + "/v2/assets/fixed_income/us_treasuries": { + "get": { + "description": "Serves the list of US treasuries available at Alpaca. The response is sorted by ISIN.", + "operationId": "UsTreasuries", + "parameters": [ + { + "in": "query", + "name": "subtype", + "schema": { + "$ref": "#/components/schemas/treasury_subtype" + } + }, + { + "in": "query", + "name": "bond_status", + "schema": { + "$ref": "#/components/schemas/bond_status" + } + }, + { + "description": "A comma-separated list of CUSIPs with a limit of 1000.", + "example": "912810UG1,912797PM3", + "in": "query", + "name": "cusips", + "schema": { + "type": "string" + } + }, + { + "description": "A comma-separated list of ISINs with a limit of 1000.", + "example": "US912810UG12,US912797PM34", + "in": "query", + "name": "isins", + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/us_treasuries_resp" + } + } + }, + "description": "OK", + "headers": { + "X-RateLimit-Limit": { + "$ref": "#/components/headers/ratelimit_limit" + }, + "X-RateLimit-Remaining": { + "$ref": "#/components/headers/ratelimit_remaining" + }, + "X-RateLimit-Reset": { + "$ref": "#/components/headers/ratelimit_reset" + } + } + }, + "400": { + "$ref": "#/components/responses/400" + }, + "403": { + "$ref": "#/components/responses/403" + }, + "429": { + "$ref": "#/components/responses/429" + }, + "500": { + "$ref": "#/components/responses/500" + } + }, + "summary": "Get US treasuries", + "tags": [ + "Assets" + ] + } + }, + "/v2/calendar": { + "get": { + "description": "The calendar API serves the full list of market days from 1970 to 2029. It can also be queried by specifying a start and/or end time to narrow down the results. In addition to the dates, the response also contains the specific open and close times for the market days, taking into account early closures.\n", + "operationId": "LegacyCalendar", + "parameters": [ + { + "$ref": "#/components/parameters/legacy_start" + }, + { + "$ref": "#/components/parameters/legacy_end" + }, + { + "$ref": "#/components/parameters/legacy_date_type" + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/legacy_public_calendar_resp" + } + } + }, + "description": "OK", + "headers": { + "X-RateLimit-Limit": { + "$ref": "#/components/headers/ratelimit_limit" + }, + "X-RateLimit-Remaining": { + "$ref": "#/components/headers/ratelimit_remaining" + }, + "X-RateLimit-Reset": { + "$ref": "#/components/headers/ratelimit_reset" + } + } + }, + "400": { + "$ref": "#/components/responses/400" + }, + "429": { + "$ref": "#/components/responses/429" + }, + "500": { + "$ref": "#/components/responses/500" + } + }, + "summary": "Get US Market Calendar", + "tags": [ + "Calendar" + ] + } + }, + "/v2/clock": { + "get": { + "description": "The clock API serves the current market timestamp, whether or not the market is currently open, as well as the times of the next market open and close.\n", + "operationId": "LegacyClock", + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/legacy_clock" + } + } + }, + "description": "OK", + "headers": { + "X-RateLimit-Limit": { + "$ref": "#/components/headers/ratelimit_limit" + }, + "X-RateLimit-Remaining": { + "$ref": "#/components/headers/ratelimit_remaining" + }, + "X-RateLimit-Reset": { + "$ref": "#/components/headers/ratelimit_reset" + } + } + }, + "429": { + "$ref": "#/components/responses/429" + }, + "500": { + "$ref": "#/components/responses/500" + } + }, + "summary": "Get US Market Clock", + "tags": [ + "Calendar" + ] + } + }, + "/v2/corporate_actions/announcements": { + "get": { + "deprecated": true, + "description": "This endpoint is deprecated, please use [the new corporate actions endpoint](https://docs.alpaca.markets/reference/corporateactions-1) instead.", + "operationId": "get-v2-corporate_actions-announcements", + "parameters": [ + { + "description": "A comma-delimited list of Dividend, Merger, Spinoff, or Split.", + "in": "query", + "name": "ca_types", + "required": true, + "schema": { + "type": "string" + } + }, + { + "description": "The start (inclusive) of the date range when searching corporate action announcements. This should follow the YYYY-MM-DD format. The date range is limited to 90 days.", + "in": "query", + "name": "since", + "required": true, + "schema": { + "type": "string" + } + }, + { + "description": "The end (inclusive) of the date range when searching corporate action announcements. This should follow the YYYY-MM-DD format. The date range is limited to 90 days.", + "in": "query", + "name": "until", + "required": true, + "schema": { + "type": "string" + } + }, + { + "description": "The symbol of the company initiating the announcement.", + "in": "query", + "name": "symbol", + "schema": { + "type": "string" + } + }, + { + "description": "The CUSIP of the company initiating the announcement.", + "in": "query", + "name": "cusip", + "schema": { + "type": "string" + } + }, + { + "description": "declaration_date, ex_date, record_date, or payable_date", + "in": "query", + "name": "date_type", + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "items": { + "properties": { + "ca_sub_type": { + "type": "string" + }, + "ca_type": { + "type": "string" + }, + "cash": { + "type": "string" + }, + "corporate_action_id": { + "type": "string" + }, + "corporate_actions_id": { + "type": "string" + }, + "declaration_date": { + "type": "string" + }, + "ex_date": { + "type": "string" + }, + "expiration_date": { + "type": "string" + }, + "id": { + "type": "string" + }, + "initiating_original_cusip": { + "type": "string" + }, + "initiating_symbol": { + "type": "string" + }, + "new_rate": { + "type": "string" + }, + "old_rate": { + "type": "string" + }, + "payable_date": { + "type": "string" + }, + "record_date": { + "type": "string" + }, + "target_original_cusip": { + "type": "string" + }, + "target_symbol": { + "type": "string" + } + }, + "type": "object" + }, + "type": "array", + "x-examples": { + "Example 1": [ + { + "ca_sub_type": "DIV", + "ca_type": "Dividend", + "cash": "0.018", + "corporate_actions_id": "F58684224_XY37", + "declaration_date": "2021-01-05", + "expiration_date": "2021-01-12", + "id": "be3c368a-4c7c-4384-808e-f02c9f5a8afe", + "initiating_original_cusip": 5.5275e+105, + "initiating_symbol": "MLLAX", + "new_rate": "1", + "old_rate": "1", + "payable_date": "2021-01-14", + "record_date": "2021-01-13", + "target_original_cusip": 5.5275e+105, + "target_symbol": "MLLAX" + }, + { + "ca_sub_type": "cash", + "ca_type": "Dividend", + "cash": "0.145", + "corporate_action_id": "48251W104_AD21", + "declaration_date": "2021-11-01", + "ex_date": "2021-11-12", + "initiating_original_cusip": "G52830109", + "initiating_symbol": "KKR", + "new_rate": "1", + "old_rate": "1", + "payable_date": "2021-11-30", + "record_date": "2021-11-15", + "target_original_cusip": "G52830109", + "target_symbol": "KKR" + } + ] + } + } + } + }, + "description": "OK" + } + }, + "summary": "Retrieve Announcements", + "tags": [ + "Corporate Actions" + ] + } + }, + "/v2/corporate_actions/announcements/{id}": { + "get": { + "deprecated": true, + "description": "This endpoint is deprecated, please use [the new corporate actions endpoint](https://docs.alpaca.markets/reference/corporateactions-1) instead.", + "operationId": "get-v2-corporate_actions-announcements-id", + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "properties": { + "ca_sub_type": { + "type": "string" + }, + "ca_type": { + "description": "A comma-delimited list of Dividend, Merger, Spinoff, or Split.", + "type": "string" + }, + "cash": { + "type": "string" + }, + "corporate_actions_id": { + "type": "string" + }, + "declaration_date": { + "type": "string" + }, + "expiration_date": { + "type": "string" + }, + "id": { + "type": "string" + }, + "initiating_original_cusip": { + "type": "string" + }, + "initiating_symbol": { + "type": "string" + }, + "new_rate": { + "type": "string" + }, + "old_rate": { + "type": "string" + }, + "payable_date": { + "type": "string" + }, + "record_date": { + "type": "string" + }, + "target_original_cusip": { + "type": "string" + }, + "target_symbol": { + "type": "string" + } + }, + "type": "object", + "x-examples": { + "Example 1": { + "ca_sub_type": "DIV", + "ca_type": "Dividend", + "cash": "0.018", + "corporate_actions_id": "F58684224_XY37", + "declaration_date": "2021-01-05", + "expiration_date": "2021-01-12", + "id": "be3c368a-4c7c-4384-808e-f02c9f5a8afe", + "initiating_original_cusip": 5.5275e+105, + "initiating_symbol": "MLLAX", + "new_rate": "1", + "old_rate": "1", + "payable_date": "2021-01-14", + "record_date": "2021-01-13", + "target_original_cusip": 5.5275e+105, + "target_symbol": "MLLAX" + } + } + } + } + }, + "description": "OK" + } + }, + "summary": "Retrieve a Specific Announcement", + "tags": [ + "Corporate Actions" + ] + }, + "parameters": [ + { + "description": "The corporate announcement's id", + "in": "path", + "name": "id", + "required": true, + "schema": { + "type": "string" + } + } + ] + }, + "/v2/options/contracts": { + "get": { + "description": "This endpoint allows you to retrieve a list of option contracts based on various filtering criteria.\nBy default only active contracts that expire before the upcoming weekend are returned.\n", + "operationId": "get-options-contracts", + "parameters": [ + { + "description": "Filter contracts by one or more underlying symbols.", + "example": "AAPL,SPY", + "in": "query", + "name": "underlying_symbols", + "schema": { + "type": "string" + } + }, + { + "description": "Include deliverables array in the response.", + "example": true, + "in": "query", + "name": "show_deliverables", + "schema": { + "type": "boolean" + } + }, + { + "description": "Filter contracts by status (active/inactive). By default only active contracts are returned.", + "example": "active", + "in": "query", + "name": "status", + "schema": { + "enum": [ + "active", + "inactive" + ], + "type": "string" + } + }, + { + "description": "Filter contracts by the exact expiration date (format: YYYY-MM-DD).", + "example": "2025-06-20", + "in": "query", + "name": "expiration_date", + "schema": { + "format": "date", + "type": "string" + } + }, + { + "description": "Filter contracts with expiration date greater than or equal to the specified date.", + "example": "2025-06-20", + "in": "query", + "name": "expiration_date_gte", + "schema": { + "format": "date", + "type": "string" + } + }, + { + "description": "Filter contracts with expiration date less than or equal to the specified date. By default this is set to the next weekend.", + "example": "2025-06-20", + "in": "query", + "name": "expiration_date_lte", + "schema": { + "format": "date", + "type": "string" + } + }, + { + "description": "Filter contracts by the root symbol.", + "example": "AAPL", + "in": "query", + "name": "root_symbol", + "schema": { + "type": "string" + } + }, + { + "description": "Filter contracts by the type (call/put).", + "example": "call", + "in": "query", + "name": "type", + "schema": { + "enum": [ + "call", + "put" + ], + "type": "string" + } + }, + { + "description": "Filter contracts by the style (american/european).", + "example": "american", + "in": "query", + "name": "style", + "schema": { + "enum": [ + "american", + "european" + ], + "type": "string" + } + }, + { + "description": "Filter contracts with strike price greater than or equal to the specified value.", + "example": 50, + "in": "query", + "name": "strike_price_gte", + "schema": { + "type": "number" + } + }, + { + "description": "Filter contracts with strike price less than or equal to the specified value.", + "example": 100, + "in": "query", + "name": "strike_price_lte", + "schema": { + "type": "number" + } + }, + { + "$ref": "#/components/parameters/PageToken" + }, + { + "description": "The number of contracts to limit per page (default=100, max=10000).", + "example": 100, + "in": "query", + "name": "limit", + "schema": { + "type": "integer" + } + }, + { + "description": "The ppind(Penny Program Indicator) field indicates whether an option contract is eligible for penny price increments,\nwith `true` meaning it is part of the Penny Program and `false` meaning it is not.", + "example": true, + "in": "query", + "name": "ppind", + "schema": { + "type": "boolean" + } + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "properties": { + "next_page_token": { + "$ref": "#/components/schemas/NextPageToken" + }, + "option_contracts": { + "items": { + "$ref": "#/components/schemas/OptionContract" + }, + "type": "array" + } + }, + "required": [ + "option_contracts" + ], + "type": "object" + } + } + }, + "description": "Successful Response." + } + }, + "summary": "Get Option Contracts", + "tags": [ + "Assets" + ] + } + }, + "/v2/options/contracts/{symbol_or_id}": { + "get": { + "description": "Get an option contract by symbol or contract ID. The symbol or id should be passed in as a path parameter.", + "operationId": "get-option-contract-symbol_or_id", + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/OptionContract" + } + } + }, + "description": "An option contract" + }, + "404": { + "content": { + "application/json": { + "examples": { + "Contract Not Found": { + "value": { + "code": 40410000, + "message": "option contract TSLA231110C00021000 not found" + } + } + }, + "schema": { + "$ref": "#/components/schemas/Error" + } + } + }, + "description": "Not Found" + } + }, + "summary": "Get an option contract by ID or Symbol", + "tags": [ + "Assets" + ] + }, + "parameters": [ + { + "description": "symbol or contract ID", + "in": "path", + "name": "symbol_or_id", + "required": true, + "schema": { + "type": "string" + } + } + ] + }, + "/v2/orders": { + "delete": { + "description": "Attempts to cancel all open orders. A response will be provided for each order that is attempted to be cancelled. If an order is no longer cancelable, the server will respond with status 500 and reject the request.", + "operationId": "deleteAllOrders", + "responses": { + "207": { + "content": { + "application/json": { + "schema": { + "items": { + "$ref": "#/components/schemas/CanceledOrderResponse" + }, + "type": "array" + } + } + }, + "description": "Multi-Status with body.\n\nan array of objects that include the order id and http status code for each status request." + }, + "500": { + "description": "Failed to cancel order." + } + }, + "summary": "Delete All Orders", + "tags": [ + "Orders" + ] + }, + "get": { + "description": "Retrieves a list of orders for the account, filtered by the supplied query parameters.", + "operationId": "getAllOrders", + "parameters": [ + { + "description": "Order status to be queried. open, closed or all. Defaults to open.", + "in": "query", + "name": "status", + "schema": { + "enum": [ + "open", + "closed", + "all" + ], + "example": "open", + "type": "string" + } + }, + { + "description": "The maximum number of orders in response. Defaults to 50 and max is 500.", + "in": "query", + "name": "limit", + "schema": { + "type": "integer" + } + }, + { + "description": "The response will include only ones submitted after this timestamp (exclusive.)", + "in": "query", + "name": "after", + "schema": { + "type": "string" + } + }, + { + "description": "The response will include only ones submitted until this timestamp (exclusive.)", + "in": "query", + "name": "until", + "schema": { + "type": "string" + } + }, + { + "description": "The chronological order of response based on the submission time. asc or desc. Defaults to desc.", + "in": "query", + "name": "direction", + "schema": { + "enum": [ + "asc", + "desc" + ], + "type": "string" + } + }, + { + "description": "If true, the result will roll up multi-leg orders under the legs field of primary order.", + "in": "query", + "name": "nested", + "schema": { + "type": "boolean" + } + }, + { + "description": "A comma-separated list of symbols to filter by (ex. \"AAPL,TSLA,MSFT\"). A currency pair is required for crypto orders (ex. \"BTCUSD,BCHUSD,LTCUSD,ETCUSD\").", + "in": "query", + "name": "symbols", + "schema": { + "type": "string" + } + }, + { + "description": "Filters down to orders that have a matching side field set.", + "in": "query", + "name": "side", + "schema": { + "type": "string" + } + }, + { + "description": "A comma-separated list of asset classes, the response will include only orders in the specified asset classes. By specifying `us_option` as the class, you can query option orders by underlying symbol using the symbols parameter.", + "in": "query", + "name": "asset_class", + "schema": { + "items": { + "enum": [ + "us_equity", + "us_option", + "crypto", + "all" + ], + "type": "string" + }, + "type": "array" + } + }, + { + "description": "Return orders submitted before the order with this ID (exclusive).\nMutually exclusive with `after_order_id`. Do not combine with `after`/`until`.\n", + "in": "query", + "name": "before_order_id", + "schema": { + "type": "string" + } + }, + { + "description": "Return orders submitted after the order with this ID (exclusive).\nMutually exclusive with `before_order_id`. Do not combine with `after`/`until`.\n", + "in": "query", + "name": "after_order_id", + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "examples": { + "Crypto": { + "$ref": "#/components/examples/CryptoOrderResponse" + }, + "Equity": { + "$ref": "#/components/examples/EquityOrderResponse" + }, + "IPO": { + "$ref": "#/components/examples/IPOOrderResponse" + }, + "MultilegOptions": { + "$ref": "#/components/examples/MultilegOptionsOrderResponse" + }, + "Options": { + "$ref": "#/components/examples/OptionOrderResponse" + } + }, + "schema": { + "items": { + "$ref": "#/components/schemas/Order" + }, + "type": "array" + } + } + }, + "description": "Successful response\n\nAn array of Order objects" + } + }, + "summary": "Get All Orders", + "tags": [ + "Orders" + ] + }, + "post": { + "description": "Places a new order for the given account. An order request may be rejected if the account is not authorized for trading, or if the tradable balance is insufficient to fill the order.", + "operationId": "postOrder", + "requestBody": { + "content": { + "application/json": { + "examples": { + "Crypto": { + "summary": "Buy a crypto coin", + "value": { + "limit_price": "2100", + "qty": "0.02", + "side": "buy", + "symbol": "ETH/USD", + "time_in_force": "gtc", + "type": "limit" + } + }, + "Equity": { + "summary": "Buy an equity stock", + "value": { + "limit_price": "150", + "qty": "2", + "side": "buy", + "symbol": "AAPL", + "time_in_force": "gtc", + "type": "limit" + } + }, + "IPO": { + "summary": "Submit an IPO indication of interest", + "value": { + "notional": "500", + "side": "buy", + "symbol": "FI111225", + "time_in_force": "gtc", + "type": "market" + } + }, + "MultilegOptions": { + "summary": "Option Call spread", + "value": { + "legs": [ + { + "position_intent": "buy_to_open", + "ratio_qty": "3", + "side": "buy", + "symbol": "AAPL241213C00250000" + }, + { + "position_intent": "sell_to_open", + "ratio_qty": "1", + "side": "sell", + "symbol": "AAPL241213C00260000" + } + ], + "limit_price": "10", + "order_class": "mleg", + "qty": "3", + "time_in_force": "day", + "type": "limit" + } + }, + "Options": { + "summary": "Buy an option contract", + "value": { + "limit_price": "10", + "qty": "2", + "side": "buy", + "symbol": "AAPL250620C00100000", + "time_in_force": "day", + "type": "limit" + } + } + }, + "schema": { + "properties": { + "advanced_instructions": { + "$ref": "#/components/schemas/AdvancedInstructions" + }, + "client_order_id": { + "description": "A unique identifier for the order. Automatically generated if not sent. (<= 128 characters)", + "maxLength": 128, + "type": "string" + }, + "extended_hours": { + "description": "(default) false. If true, order will be eligible for execution in the pre-market, after-hours, and overnight sessions. Only works with type `limit` and time_in_force set to either `day` or `gtc`", + "type": "boolean" + }, + "legs": { + "description": "list of order legs (<= 4)", + "items": { + "$ref": "#/components/schemas/MLegOrderLeg" + }, + "maxItems": 4, + "type": "array" + }, + "limit_price": { + "description": "Required if type is `limit` or `stop_limit`.\nIn case of `mleg`, the limit_price parameter is expressed with the following notation:\n- A positive value indicates a debit, representing a cost or payment to be made.\n- A negative value signifies a credit, reflecting an amount to be received.", + "type": "string" + }, + "notional": { + "description": "dollar amount to trade. Cannot work with `qty`. Can only work for market order types and day for time in force.", + "type": "string" + }, + "order_class": { + "$ref": "#/components/schemas/OrderClass" + }, + "position_intent": { + "$ref": "#/components/schemas/PositionIntent" + }, + "qty": { + "description": "number of shares to trade. Can be fractionable for only market and day order types. Required for `mleg` order class, represents the number of units to trade of this strategy.", + "type": "string" + }, + "side": { + "$ref": "#/components/schemas/OrderSide" + }, + "stop_loss": { + "description": "Takes in string/number values for stop_price and limit_price", + "properties": { + "limit_price": { + "example": "3.14", + "format": "decimal", + "type": "string" + }, + "stop_price": { + "example": "3.14", + "format": "decimal", + "type": "string" + } + }, + "type": "object" + }, + "stop_price": { + "description": "required if type is `stop` or `stop_limit`", + "type": "string" + }, + "symbol": { + "description": "symbol, asset ID, or currency pair to identify the asset to trade, required for all order classes except for `mleg`.", + "type": "string" + }, + "take_profit": { + "description": "Takes in a string/number value for limit_price", + "properties": { + "limit_price": { + "example": "3.14", + "format": "decimal", + "type": "string" + } + }, + "type": "object" + }, + "time_in_force": { + "$ref": "#/components/schemas/TimeInForce" + }, + "trail_percent": { + "description": "this or `trail_price` is required if type is `trailing_stop`", + "type": "string" + }, + "trail_price": { + "description": "this or `trail_percent` is required if type is `trailing_stop`", + "type": "string" + }, + "type": { + "$ref": "#/components/schemas/OrderType" + } + }, + "required": [ + "type", + "time_in_force" + ], + "type": "object" + } + } + }, + "description": "", + "required": true + }, + "responses": { + "200": { + "content": { + "application/json": { + "examples": { + "Crypto": { + "$ref": "#/components/examples/CryptoOrderResponse" + }, + "Equity": { + "$ref": "#/components/examples/EquityOrderResponse" + }, + "IPO": { + "$ref": "#/components/examples/IPOOrderResponse" + }, + "MultilegOptions": { + "$ref": "#/components/examples/MultilegOptionsOrderResponse" + }, + "Options": { + "$ref": "#/components/examples/OptionOrderResponse" + } + }, + "schema": { + "$ref": "#/components/schemas/Order" + } + } + }, + "description": "Successful response" + }, + "403": { + "description": "Forbidden\n\nBuying power or shares is not sufficient." + }, + "422": { + "description": "Unprocessable\n\nInput parameters are not recognized." + } + }, + "summary": "Create an Order", + "tags": [ + "Orders" + ] + } + }, + "/v2/orders/{order_id}": { + "delete": { + "description": "Attempts to cancel an Open Order. If the order is no longer cancelable, the request will be rejected with status 422; otherwise accepted with return status 204.", + "operationId": "deleteOrderByOrderID", + "responses": { + "204": { + "description": "No Content" + }, + "422": { + "description": "The order status is not cancelable." + } + }, + "summary": "Delete Order by ID", + "tags": [ + "Orders" + ] + }, + "get": { + "description": "Retrieves a single order for the given order_id.", + "operationId": "getOrderByOrderID", + "parameters": [ + { + "description": "If true, the result will roll up multi-leg orders under the legs field of primary order.", + "in": "query", + "name": "nested", + "schema": { + "type": "boolean" + } + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "examples": { + "Crypto": { + "$ref": "#/components/examples/CryptoOrderResponse" + }, + "Equity": { + "$ref": "#/components/examples/EquityOrderResponse" + }, + "IPO": { + "$ref": "#/components/examples/IPOOrderResponse" + }, + "MultilegOptions": { + "$ref": "#/components/examples/MultilegOptionsOrderResponse" + }, + "Options": { + "$ref": "#/components/examples/OptionOrderResponse" + } + }, + "schema": { + "$ref": "#/components/schemas/Order" + } + } + }, + "description": "Successful response" + } + }, + "summary": "Get Order by ID", + "tags": [ + "Orders" + ] + }, + "parameters": [ + { + "description": "order id", + "in": "path", + "name": "order_id", + "required": true, + "schema": { + "format": "uuid", + "type": "string" + } + } + ], + "patch": { + "description": "Replaces a single order with updated parameters. Each parameter overrides the corresponding attribute of the existing order. The other attributes remain the same as the existing order.\n\nA success return code from a replaced order does NOT guarantee the existing open order has been replaced. If the existing open order is filled before the replacing (new) order reaches the execution venue, the replacing (new) order is rejected, and these events are sent in the trade_updates stream channel.\n\nWhile an order is being replaced, buying power is reduced by the larger of the two orders that have been placed (the old order being replaced, and the newly placed order to replace it). If you are replacing a buy entry order with a higher limit price than the original order, the buying power is calculated based on the newly placed order. If you are replacing it with a lower limit price, the buying power is calculated based on the old order.\n\nNote: Order cannot be replaced when the status is `accepted`, `pending_new`, `pending_cancel` or `pending_replace`.\n\nNote: Notional orders for non-IPO asset classes cannot be replaced. Any attempt to modify a non-IPO notional order via this endpoint will be rejected; cancel it and submit a new one instead.\n\nNote: IPO indications of interest (`asset_class: \"ipo\"`) **are** notional and **can** be replaced via this endpoint by providing a new `notional` value. `qty` and `notional` are mutually exclusive on a single replace request.\n", + "operationId": "patchOrderByOrderId", + "requestBody": { + "content": { + "application/json": { + "examples": { + "Equity": { + "summary": "Increase qty and limit price on an equity order", + "value": { + "limit_price": "155", + "qty": "4", + "time_in_force": "gtc" + } + }, + "IPO": { + "summary": "Update notional on an IPO indication of interest", + "value": { + "notional": "750" + } + }, + "Options": { + "summary": "Adjust an option order's limit price", + "value": { + "limit_price": "11.25", + "time_in_force": "day" + } + } + }, + "schema": { + "$ref": "#/components/schemas/PatchOrderRequest" + } + } + }, + "required": true + }, + "responses": { + "200": { + "content": { + "application/json": { + "examples": { + "Equity": { + "$ref": "#/components/examples/EquityOrderResponse" + }, + "IPO": { + "$ref": "#/components/examples/IPOOrderResponse" + }, + "Options": { + "$ref": "#/components/examples/OptionOrderResponse" + } + }, + "schema": { + "$ref": "#/components/schemas/Order" + } + } + }, + "description": "Successful response\n\nThe new Order object with the new order ID." + } + }, + "summary": "Replace Order by ID", + "tags": [ + "Orders" + ] + } + }, + "/v2/orders:by_client_order_id": { + "get": { + "description": "Retrieves a single order specified by the client order ID.", + "operationId": "getOrderByClientOrderId", + "parameters": [ + { + "description": "The client-assigned order ID.", + "in": "query", + "name": "client_order_id", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Order" + } + } + }, + "description": "Successfully retrieved the order matching the client_order_id." + } + }, + "summary": "Get Order by Client Order ID", + "tags": [ + "Orders" + ] + } + }, + "/v2/perpetuals/account_vitals": { + "get": { + "description": "Fetches key financial metrics for the crypto perpetuals account, providing a snapshot of its current status by detailing the relationship between the user's positions and their collateral. The response includes maintenance margin (USDT), collateral balance (USDT), total collateral (USDT), and profit/loss (USDT)", + "operationId": "getCryptoPerpAccountVitals", + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "properties": { + "collateral_balance": { + "description": "CollateralBalance (USDT) is the sum of the unrealized PnL on each position plus TotalCollateral", + "type": "integer" + }, + "maintenance_margin": { + "description": "MaintenanceMargin (USDT) is the sum of each position's individual maintenance margin", + "type": "number" + }, + "profit_loss": { + "description": "ProfitAndLoss (USDT) is the net of realized and unrealized PnL across all positions", + "type": "integer" + }, + "total_collateral": { + "description": "TotalCollateral (USDT) across all collateral assets", + "type": "integer" + } + }, + "type": "object" + } + } + }, + "description": "OK" + } + }, + "summary": "Retrieve Account Vitals", + "tags": [ + "Crypto Perpetuals Account Vitals (Beta)" + ] + } + }, + "/v2/perpetuals/leverage": { + "get": { + "description": "Retrieves the current leverage setting for the crypto perpetuals account, specific to a given underlying asset. To use this endpoint, provide the 'symbol' of the asset as a query parameter. The system will return the asset's symbol and the integer value representing the current leverage applied to it within the account", + "operationId": "getCryptoPerpAccountLeverage", + "parameters": [ + { + "description": "Symbol of underlying asset", + "in": "query", + "name": "symbol", + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "properties": { + "leverage": { + "type": "integer" + }, + "symbol": { + "description": "Symbol of underlying asset", + "type": "string" + } + }, + "type": "object" + } + } + }, + "description": "OK" + } + }, + "summary": "Get Account Leverage for an Asset", + "tags": [ + "Crypto Perpetuals Leverage (Beta)" + ] + }, + "post": { + "description": "Updates the leverage for the crypto perpetuals account for a specific underlying asset. Provide the 'symbol' of the asset and the desired 'leverage' (as an integer) using query parameters. The system will return the asset's symbol and the newly set leverage value upon successful update", + "operationId": "setCryptoPerpAccountLeverage", + "parameters": [ + { + "description": "Symbol of underlying asset", + "in": "query", + "name": "symbol", + "schema": { + "type": "string" + } + }, + { + "description": "Leverage for the underlying asset", + "in": "query", + "name": "leverage", + "schema": { + "type": "integer" + } + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "properties": { + "leverage": { + "type": "integer" + }, + "symbol": { + "description": "Symbol of underlying asset", + "type": "string" + } + }, + "type": "object" + } + } + }, + "description": "OK" + } + }, + "summary": "Set Account Leverage for an Asset", + "tags": [ + "Crypto Perpetuals Leverage (Beta)" + ] + } + }, + "/v2/perpetuals/wallets": { + "get": { + "description": "Lists wallets for the account given in the path parameter. If an asset is specified and no wallet for the account and asset pair exists one will be created. If no asset is specified only existing wallets will be listed for the account. An account may have at most one wallet per asset", + "operationId": "listCryptoPerpFundingWallets", + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CryptoWallet" + } + } + }, + "description": "A single wallet object if an asset is specified or an array of wallet objects if no asset is specified" + } + }, + "summary": "Retrieve Crypto Funding Wallets", + "tags": [ + "Crypto Perpetuals Funding (Beta)" + ] + }, + "parameters": [ + { + "in": "query", + "name": "asset", + "schema": { + "type": "string" + } + } + ] + }, + "/v2/perpetuals/wallets/fees/estimate": { + "get": { + "description": "Returns the estimated on-chain network (gas) fee for a proposed withdrawal from a perpetuals wallet at current chain conditions. Pass the `asset`, `from_address`, `to_address`, and `amount` you intend to send; the response reports the network fee that would be charged for the corresponding transaction.", + "operationId": "getCryptoPerpTransferEstimate", + "parameters": [ + { + "description": "The asset for the proposed transaction", + "in": "query", + "name": "asset", + "schema": { + "type": "string" + } + }, + { + "description": "The originating address of the proposed transaction", + "in": "query", + "name": "from_address", + "schema": { + "type": "string" + } + }, + { + "description": "The destination address of the proposed transaction", + "in": "query", + "name": "to_address", + "schema": { + "type": "string" + } + }, + { + "description": "The amount, denoted in the specified asset, of the proposed transaction", + "in": "query", + "name": "amount", + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "properties": { + "fee": { + "type": "string" + } + }, + "type": "object" + } + } + }, + "description": "OK" + } + }, + "summary": "Returns the estimated gas fee for a proposed transaction", + "tags": [ + "Crypto Perpetuals Funding (Beta)" + ] + } + }, + "/v2/perpetuals/wallets/transfers": { + "get": { + "description": "Returns an array of all transfers associated with the given account across all wallets", + "operationId": "listCryptoPerpFundingTransfers", + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CryptoTransfer" + } + } + }, + "description": "An array of transfer objects" + } + }, + "summary": "Retrieve Crypto Funding Transfers", + "tags": [ + "Crypto Perpetuals Funding (Beta)" + ] + }, + "post": { + "description": "Creates a withdrawal request. Note that outgoing withdrawals must be sent to a whitelisted address and you must whitelist addresses at least 24 hours in advance. If you attempt to withdraw funds to a non-whitelisted address then the transfer will be rejected", + "operationId": "createCryptoPerpTransferForAccount", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CreateCryptoTransferRequest" + } + } + }, + "required": true + }, + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CryptoTransfer" + } + } + }, + "description": "Successfully requested a transfer." + } + }, + "summary": "Request a New Withdrawal", + "tags": [ + "Crypto Perpetuals Funding (Beta)" + ] + } + }, + "/v2/perpetuals/wallets/transfers/{transfer_id}": { + "get": { + "description": "Returns a specific wallet transfer by passing into the query the transfer_id", + "operationId": "getCryptoPerpFundingTransfer", + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CryptoTransfer" + } + } + }, + "description": "A single crypto transfer object" + } + }, + "summary": "Retrieve a Crypto Funding Transfer", + "tags": [ + "Crypto Perpetuals Funding (Beta)" + ] + }, + "parameters": [ + { + "description": "The crypto transfer to retrieve", + "in": "path", + "name": "transfer_id", + "required": true, + "schema": { + "type": "string" + } + } + ] + }, + "/v2/perpetuals/wallets/whitelists": { + "get": { + "description": "Returns the list of whitelisted withdrawal addresses for your perpetuals wallets.", + "operationId": "listWhitelistedPerpAddress", + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/WhitelistedAddress" + } + } + }, + "description": "An array of whitelisted objects" + } + }, + "summary": "An array of whitelisted addresses", + "tags": [ + "Crypto Perpetuals Funding (Beta)" + ] + }, + "post": { + "description": "Submits a new whitelisted withdrawal address for your perpetuals wallets. The chain is derived from the supplied asset symbol and address, and the same address cannot be whitelisted twice on the same chain.\n\nWhere travel rule reporting applies to your account, address details are validated against the travel rule provider before being persisted.\n\nNote that whitelisted addresses must wait at least 24 hours before they can be used as a withdrawal destination.", + "operationId": "createWhitelistedPerpAddress", + "requestBody": { + "content": { + "application/json": { + "schema": { + "properties": { + "address": { + "description": "The address to be whitelisted", + "type": "string" + }, + "asset": { + "description": "Symbol of underlying asset for the whitelisted address", + "type": "string" + } + }, + "type": "object" + } + } + }, + "required": true + }, + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/WhitelistedAddress" + } + } + }, + "description": "Successfully requested a whitelisted address" + } + }, + "summary": "Request a new whitelisted address", + "tags": [ + "Crypto Perpetuals Funding (Beta)" + ] + } + }, + "/v2/perpetuals/wallets/whitelists/{whitelisted_address_id}": { + "delete": { + "description": "Deletes a whitelisted withdrawal address from your perpetuals wallets by ID. Subsequent withdrawals targeting the deleted address will be rejected.", + "operationId": "deleteWhitelistedPerpAddress", + "responses": { + "200": { + "description": "Successfully deleted a whitelisted address" + } + }, + "summary": "Delete a whitelisted address", + "tags": [ + "Crypto Perpetuals Funding (Beta)" + ] + }, + "parameters": [ + { + "description": "The whitelisted address to delete", + "in": "path", + "name": "whitelisted_address_id", + "required": true, + "schema": { + "type": "string" + } + } + ] + }, + "/v2/positions": { + "delete": { + "description": "Closes (liquidates) all of the account's open long and short positions. A response will be provided for each order that is attempted to be cancelled. If an order is no longer cancelable, the server will respond with status 500 and reject the request.", + "operationId": "deleteAllOpenPositions", + "parameters": [ + { + "description": "If true is specified, cancel all open orders before liquidating all positions.", + "in": "query", + "name": "cancel_orders", + "schema": { + "type": "boolean" + } + } + ], + "responses": { + "207": { + "content": { + "application/json": { + "schema": { + "items": { + "$ref": "#/components/schemas/PositionClosedReponse" + }, + "type": "array" + } + } + }, + "description": "Multi-Status with body.\n\nan array of PositionClosed responses" + }, + "500": { + "description": "Failed to liquidate" + } + }, + "summary": "Close All Positions", + "tags": [ + "Positions" + ] + }, + "get": { + "description": "The positions API provides information about an account's current open positions. The response will include information such as cost basis, shares traded, and market value, which will be updated live as price information is updated. Once a position is closed, it will no longer be queryable through this API\n\nRetrieves a list of the account's open positions", + "operationId": "getAllOpenPositions", + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "items": { + "$ref": "#/components/schemas/Position" + }, + "type": "array" + } + } + }, + "description": "Successful response" + } + }, + "summary": "All Open Positions", + "tags": [ + "Positions" + ] + } + }, + "/v2/positions/{symbol_or_asset_id}": { + "delete": { + "description": "Closes (liquidates) the account's open position for the given symbol. Works for both long and short positions.", + "operationId": "deleteOpenPosition", + "parameters": [ + { + "description": "the number of shares to liquidate. Can accept up to 9 decimal points. Cannot work with percentage", + "in": "query", + "name": "qty", + "schema": { + "type": "number" + } + }, + { + "description": "percentage of position to liquidate. Must be between 0 and 100. Would only sell fractional if position is originally fractional. Can accept up to 9 decimal points. Cannot work with qty", + "in": "query", + "name": "percentage", + "schema": { + "type": "number" + } + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Order" + } + } + }, + "description": "Successful response\n\nReturns the order created to close out this position" + } + }, + "summary": "Close a Position", + "tags": [ + "Positions" + ] + }, + "get": { + "description": "Retrieves the account's open position for the given symbol or assetId.", + "operationId": "getOpenPosition", + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Position" + } + } + }, + "description": "Successful response" + } + }, + "summary": "Get an Open Position", + "tags": [ + "Positions" + ] + }, + "parameters": [ + { + "description": "symbol or assetId", + "in": "path", + "name": "symbol_or_asset_id", + "required": true, + "schema": { + "type": "string" + } + } + ] + }, + "/v2/positions/{symbol_or_contract_id}/do-not-exercise": { + "post": { + "description": "This endpoint enables users to submit a do-not-exercise (DNE) instruction for a held option contract, preventing automatic exercise at expiry.\nBy default, Alpaca will automatically exercise in-the-money (ITM) contracts at expiry. This endpoint allows users to override that behavior.\nTo override this behavior and submit an exercise instruction, please contact our support team.", + "operationId": "optionDoNotExercise", + "parameters": [ + { + "description": "Option contract symbol or ID.", + "in": "path", + "name": "symbol_or_contract_id", + "required": true, + "schema": { + "format": "uuid", + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "Successful Response\n\nDo-not-exercise instruction successfully submitted." + }, + "403": { + "content": { + "application/json": { + "examples": { + "No Available Position": { + "value": { + "code": 40310000, + "message": "no available position for the specified contract" + } + }, + "Short Position": { + "value": { + "code": 40310001, + "message": "cannot submit DNE for short position" + } + } + }, + "schema": { + "$ref": "#/components/schemas/Error" + } + } + }, + "description": "Forbidden\n\nAvailable position quantity is not sufficient or no position found." + }, + "422": { + "content": { + "application/json": { + "examples": { + "Invalid Symbol": { + "value": { + "code": 42210000, + "message": "invalid symbol" + } + } + }, + "schema": { + "$ref": "#/components/schemas/Error" + } + } + }, + "description": "Invalid Parameters.\n\nOne or more parameters provided are invalid." + } + }, + "summary": "Do Not Exercise an Options Position", + "tags": [ + "Positions" + ] + } + }, + "/v2/positions/{symbol_or_contract_id}/exercise": { + "post": { + "description": "This endpoint enables users to exercise a held option contract, converting it into the underlying asset based on the specified terms.\nAll available held shares of this option contract will be exercised.\nBy default, Alpaca will automatically exercise in-the-money (ITM) contracts at expiry.\nExercise requests will be processed immediately once received. Exercise requests submitted between market close and midnight will be rejected to avoid any confusion about when the exercise will settle.\nTo cancel an exercise request or to submit a Do-not-exercise (DNE) instruction, you can use the do-not-exercise endpoint or contact our support team.", + "operationId": "optionExercise", + "parameters": [ + { + "description": "Option contract symbol or ID.", + "in": "path", + "name": "symbol_or_contract_id", + "required": true, + "schema": { + "format": "uuid", + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "Successful Response\n\nExercise instruction successfully submitted." + }, + "403": { + "content": { + "application/json": { + "examples": { + "No Available Position": { + "value": { + "code": 40310000, + "message": "no available position for the specified contract" + } + }, + "Short Position": { + "value": { + "code": 40310001, + "message": "cannot exercise short position" + } + } + }, + "schema": { + "$ref": "#/components/schemas/Error" + } + } + }, + "description": "Forbidden\n\nAvailable position quantity is not sufficient." + }, + "422": { + "content": { + "application/json": { + "examples": { + "Invalid Symbol": { + "value": { + "code": 42210000, + "message": "invalid symbol" + } + } + }, + "schema": { + "$ref": "#/components/schemas/Error" + } + } + }, + "description": "Invalid Parameters.\n\nOne or more parameters provided are invalid." + } + }, + "summary": "Exercise an Options Position", + "tags": [ + "Positions" + ] + } + }, + "/v2/tokenization/mint": { + "post": { + "description": "This endpoint is used by an Authorized Participant to request the minting of a tokenized asset.", + "operationId": "postTokenizationMint", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TokenizationMintRequest" + } + } + }, + "required": true + }, + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TokenizationMintResponse" + } + } + }, + "description": "Successfully requested minting of a tokenized asset." + }, + "400": { + "content": { + "application/json": { + "example": { + "code": 40010000, + "message": "failed to decode request body" + }, + "schema": { + "$ref": "#/components/schemas/Error" + } + } + }, + "description": "Bad request (e.g. malformed input, insufficient position, or account not authorized to mint)." + }, + "401": { + "content": { + "application/json": { + "example": { + "code": 40110000, + "message": "unauthorized" + }, + "schema": { + "$ref": "#/components/schemas/Error" + } + } + }, + "description": "Authentication credentials are missing or invalid." + }, + "403": { + "content": { + "application/json": { + "example": { + "code": 40310000, + "message": "forbidden" + }, + "schema": { + "$ref": "#/components/schemas/Error" + } + } + }, + "description": "Caller is not authorized to perform this operation." + }, + "422": { + "content": { + "application/json": { + "example": { + "code": 40010001, + "message": "issuer is required, network is required" + }, + "schema": { + "$ref": "#/components/schemas/Error" + } + } + }, + "description": "One or more request parameters are missing or invalid." + } + }, + "summary": "Mint a Tokenized Asset", + "tags": [ + "Tokenization" + ] + } + }, + "/v2/tokenization/requests": { + "get": { + "description": "An Authorized Participant can use this endpoint to list the tokenization requests performed on the Instant Tokenization Network (ITN).", + "operationId": "getTokenizationRequests", + "parameters": [ + { + "description": "Tokenization request type to be queried", + "in": "query", + "name": "type", + "schema": { + "enum": [ + "mint", + "redeem" + ], + "example": "mint", + "type": "string" + } + }, + { + "description": "Tokenization request status to be queried", + "in": "query", + "name": "status", + "schema": { + "enum": [ + "pending", + "rejected", + "completed" + ], + "example": "pending", + "type": "string" + } + }, + { + "description": "Underlying symbol of the tokenization requests to be queried", + "in": "query", + "name": "underlying_symbol", + "schema": { + "type": "string" + } + }, + { + "description": "Issuer of the tokenization requests to be queried", + "in": "query", + "name": "issuer", + "schema": { + "enum": [ + "xstocks" + ], + "example": "xstocks", + "type": "string" + } + }, + { + "description": "Network of the tokenization requests to be queried", + "in": "query", + "name": "network", + "schema": { + "$ref": "#/components/schemas/TokenizationNetwork" + } + }, + { + "description": "The response will include only requests created after this timestamp (exclusive)", + "in": "query", + "name": "after", + "schema": { + "example": "2025-09-30T18:38:01.942282Z", + "format": "date-time", + "type": "string" + } + }, + { + "description": "The response will include only requests created before this timestamp (exclusive)", + "in": "query", + "name": "before", + "schema": { + "example": "2025-09-30T18:38:01.942282Z", + "format": "date-time", + "type": "string" + } + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "items": { + "$ref": "#/components/schemas/TokenizationRequest" + }, + "type": "array" + } + } + }, + "description": "Successful response\n\nA list of tokenization requests" + }, + "401": { + "content": { + "application/json": { + "example": { + "code": 40110000, + "message": "unauthorized" + }, + "schema": { + "$ref": "#/components/schemas/Error" + } + } + }, + "description": "Authentication credentials are missing or invalid." + }, + "403": { + "content": { + "application/json": { + "example": { + "code": 40310000, + "message": "forbidden" + }, + "schema": { + "$ref": "#/components/schemas/Error" + } + } + }, + "description": "Caller is not authorized to perform this operation." + }, + "422": { + "content": { + "application/json": { + "example": { + "code": 40010001, + "message": "failed to parse after" + }, + "schema": { + "$ref": "#/components/schemas/Error" + } + } + }, + "description": "One or more request parameters are missing or invalid." + } + }, + "summary": "List Tokenization Requests", + "tags": [ + "Tokenization" + ] + } + }, + "/v2/wallets": { + "get": { + "description": "Lists wallets for the account given in the path parameter. If an asset is specified and no wallet for the account and asset pair exists one will be created. If no asset is specified only existing wallets will be listed for the account. An account may have at most one wallet per asset.", + "operationId": "listCryptoFundingWallets", + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CryptoWallet" + } + } + }, + "description": "A single wallet object if an asset is specified or an array of wallet objects if no asset is specified" + } + }, + "summary": "Retrieve Crypto Funding Wallets", + "tags": [ + "Crypto Funding" + ] + }, + "parameters": [ + { + "description": "Filter by crypto asset symbol, e.g. BTC, ETH, USDT. If specified and no wallet exists, one will be created.", + "in": "query", + "name": "asset", + "schema": { + "type": "string" + } + }, + { + "description": "Optional chain identifier. Use to request wallets for a specific chain when asset is a multi-chain crypto asset. If not specified, network is considered.", + "in": "query", + "name": "chain", + "schema": { + "enum": [ + "SOL", + "ETH", + "BTC", + "XRP", + "ARB" + ], + "type": "string" + } + }, + { + "description": "Deprecated. Use chain instead. Optional network identifier which is used to request wallets for a specific network when asset is a multi-chain crypto asset. When both 'chain' and 'network' are specified, 'chain' will take precedence and 'network' will be ignored. If 'chain' and 'network' are not specified, the default 'network' (ethereum) will be used.", + "in": "query", + "name": "network", + "schema": { + "deprecated": true, + "enum": [ + "ethereum", + "solana" + ], + "type": "string" + } + } + ] + }, + "/v2/wallets/fees/estimate": { + "get": { + "description": "Returns the estimated on-chain network (gas) fee for a proposed crypto withdrawal at current chain conditions. Pass the `asset`, `from_address`, `to_address`, and `amount` you intend to send; the response reports the network fee that would be charged for the corresponding transaction.", + "operationId": "getCryptoTransferEstimate", + "parameters": [ + { + "description": "The asset for the proposed transaction", + "in": "query", + "name": "asset", + "schema": { + "type": "string" + } + }, + { + "description": "The originating address of the proposed transaction", + "in": "query", + "name": "from_address", + "schema": { + "type": "string" + } + }, + { + "description": "The destination address of the proposed transaction", + "in": "query", + "name": "to_address", + "schema": { + "type": "string" + } + }, + { + "description": "The amount, denoted in the specified asset, of the proposed transaction", + "in": "query", + "name": "amount", + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "properties": { + "fee": { + "type": "string" + } + }, + "type": "object" + } + } + }, + "description": "OK" + } + }, + "summary": "Returns the estimated gas fee for a proposed transaction.", + "tags": [ + "Crypto Funding" + ] + } + }, + "/v2/wallets/transfers": { + "get": { + "description": "Returns an array of all transfers associated with the given account across all wallets.", + "operationId": "listCryptoFundingTransfers", + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CryptoTransfer" + } + } + }, + "description": "An array of transfer objects" + } + }, + "summary": "Retrieve Crypto Funding Transfers", + "tags": [ + "Crypto Funding" + ] + }, + "post": { + "description": "Creates a withdrawal request. Note that outgoing withdrawals must be sent to a whitelisted address and you must whitelist addresses at least 24 hours in advance. If you attempt to withdraw funds to a non-whitelisted address then the transfer will be rejected.", + "operationId": "createCryptoTransferForAccount", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CreateCryptoTransferRequest" + } + } + }, + "required": true + }, + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CryptoTransfer" + } + } + }, + "description": "Successfully requested a transfer." + } + }, + "summary": "Request a New Withdrawal", + "tags": [ + "Crypto Funding" + ] + } + }, + "/v2/wallets/transfers/{transfer_id}": { + "get": { + "description": "Returns a specific wallet transfer by passing into the query the transfer_id.", + "operationId": "getCryptoFundingTransfer", + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CryptoTransfer" + } + } + }, + "description": "A single crypto transfer object" + } + }, + "summary": "Retrieve a Crypto Funding Transfer", + "tags": [ + "Crypto Funding" + ] + }, + "parameters": [ + { + "description": "The crypto transfer to retrieve", + "in": "path", + "name": "transfer_id", + "required": true, + "schema": { + "type": "string" + } + } + ] + }, + "/v2/wallets/whitelists": { + "get": { + "description": "Returns the list of whitelisted withdrawal addresses for your account.", + "operationId": "listWhitelistedAddress", + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/WhitelistedAddress" + } + } + }, + "description": "An array of whitelisted objects" + } + }, + "summary": "An array of whitelisted addresses", + "tags": [ + "Crypto Funding" + ] + }, + "post": { + "description": "Submits a new whitelisted withdrawal address for your account. The chain is derived from the supplied asset symbol and address, and the same address cannot be whitelisted twice on the same chain.\n\nWhere travel rule reporting applies to your account, address details are validated against the travel rule provider before being persisted.\n\nNote that whitelisted addresses must wait at least 24 hours before they can be used as a withdrawal destination.", + "operationId": "createWhitelistedAddress", + "requestBody": { + "content": { + "application/json": { + "schema": { + "properties": { + "address": { + "description": "The address to be whitelisted", + "type": "string" + }, + "asset": { + "description": "Symbol of underlying asset for the whitelisted address", + "type": "string" + }, + "chain": { + "description": "Optional chain identifier. Use when the underlying asset for the whitelisted address is a multi-chain crypto asset.", + "enum": [ + "SOL", + "ETH", + "BTC", + "XRP", + "ARB" + ], + "type": "string" + } + }, + "type": "object" + } + } + }, + "required": true + }, + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/WhitelistedAddress" + } + } + }, + "description": "Successfully requested a whitelisted address" + } + }, + "summary": "Request a new whitelisted address", + "tags": [ + "Crypto Funding" + ] + } + }, + "/v2/wallets/whitelists/{whitelisted_address_id}": { + "delete": { + "description": "Deletes a whitelisted withdrawal address by ID. Subsequent withdrawals targeting the deleted address will be rejected.", + "operationId": "deleteWhitelistedAddress", + "responses": { + "200": { + "description": "Successfully deleted a whitelisted address" + } + }, + "summary": "Delete a whitelisted address", + "tags": [ + "Crypto Funding" + ] + }, + "parameters": [ + { + "description": "The whitelisted address to delete", + "in": "path", + "name": "whitelisted_address_id", + "required": true, + "schema": { + "type": "string" + } + } + ] + }, + "/v2/watchlists": { + "get": { + "description": "Returns the list of watchlists registered under the account.", + "operationId": "getWatchlists", + "responses": { + "200": { + "content": { + "application/json": { + "examples": { + "example-1": { + "value": [ + { + "account_id": "abe25343-a7ba-4255-bdeb-f7e013e9ee5d", + "created_at": "2022-01-31T21:49:05.14628Z", + "id": "3174d6df-7726-44b4-a5bd-7fda5ae6e009", + "name": "Primary Watchlist", + "updated_at": "2022-01-31T21:49:05.14628Z" + } + ] + } + }, + "schema": { + "items": { + "$ref": "#/components/schemas/WatchlistWithoutAsset" + }, + "type": "array" + } + } + }, + "description": "Successful response" + } + }, + "summary": "Get All Watchlists", + "tags": [ + "Watchlists" + ] + }, + "post": { + "description": "Create a new watchlist with initial set of assets.", + "operationId": "postWatchlist", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UpdateWatchlistRequest" + } + } + }, + "required": true + }, + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Watchlist" + } + } + }, + "description": "Successful response" + } + }, + "summary": "Create Watchlist", + "tags": [ + "Watchlists" + ] + } + }, + "/v2/watchlists/{watchlist_id}": { + "delete": { + "description": "Delete a watchlist. This is a permanent deletion.", + "operationId": "deleteWatchlistById", + "responses": { + "204": { + "description": "No Content" + }, + "404": { + "description": "Watchlist not found" + } + }, + "summary": "Delete Watchlist By Id", + "tags": [ + "Watchlists" + ] + }, + "get": { + "description": "Returns a watchlist identified by the ID.", + "operationId": "getWatchlistById", + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Watchlist" + } + } + }, + "description": "Successful response" + } + }, + "summary": "Get Watchlist by ID", + "tags": [ + "Watchlists" + ] + }, + "parameters": [ + { + "description": "watchlist id", + "in": "path", + "name": "watchlist_id", + "required": true, + "schema": { + "format": "uuid", + "type": "string" + } + } + ], + "post": { + "description": "Append an asset for the symbol to the end of watchlist asset list", + "operationId": "addAssetToWatchlist", + "requestBody": { + "content": { + "application/json": { + "schema": { + "properties": { + "symbol": { + "description": "the symbol name to add to the watchlist", + "type": "string" + } + }, + "type": "object" + } + } + } + }, + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Watchlist" + } + } + }, + "description": "Successful response" + } + }, + "summary": "Add Asset to Watchlist", + "tags": [ + "Watchlists" + ] + }, + "put": { + "description": "Update the name and/or content of watchlist", + "operationId": "updateWatchlistById", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UpdateWatchlistRequest" + } + } + } + }, + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Watchlist" + } + } + }, + "description": "Successful response" + } + }, + "summary": "Update Watchlist By Id", + "tags": [ + "Watchlists" + ] + } + }, + "/v2/watchlists/{watchlist_id}/{symbol}": { + "delete": { + "description": "Delete one entry for an asset by symbol name", + "operationId": "removeAssetFromWatchlist", + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Watchlist" + } + } + }, + "description": "Returns the updated watchlist" + } + }, + "summary": "Delete Symbol from Watchlist", + "tags": [ + "Watchlists" + ] + }, + "parameters": [ + { + "description": "Watchlist ID", + "in": "path", + "name": "watchlist_id", + "required": true, + "schema": { + "format": "uuid", + "type": "string" + } + }, + { + "description": "symbol name to remove from the watchlist content", + "in": "path", + "name": "symbol", + "required": true, + "schema": { + "type": "string" + } + } + ] + }, + "/v2/watchlists:by_name": { + "delete": { + "description": "Delete a watchlist. This is a permanent deletion.", + "operationId": "deleteWatchlistByName", + "parameters": [ + { + "description": "name of the watchlist", + "in": "query", + "name": "name", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "204": { + "description": "No Content" + } + }, + "summary": "Delete Watchlist By Name", + "tags": [ + "Watchlists" + ] + }, + "get": { + "description": "You can also call GET, PUT, POST and DELETE with watchlist name with another endpoint /v2/watchlists:by_name and query parameter name=, instead of /v2/watchlists/{watchlist_id} endpoints\n\nReturns a watchlist by name", + "operationId": "getWatchlistByName", + "parameters": [ + { + "description": "name of the watchlist", + "in": "query", + "name": "name", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Watchlist" + } + } + }, + "description": "Successful response" + } + }, + "summary": "Get Watchlist by Name", + "tags": [ + "Watchlists" + ] + }, + "post": { + "description": "Append an asset for the symbol to the end of watchlist asset list", + "operationId": "addAssetToWatchlistByName", + "parameters": [ + { + "description": "name of the watchlist", + "in": "query", + "name": "name", + "required": true, + "schema": { + "type": "string" + } + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "properties": { + "symbol": { + "description": "the symbol name to add to the watchlist", + "type": "string" + } + }, + "type": "object" + } + } + } + }, + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Watchlist" + } + } + }, + "description": "Successful response" + } + }, + "summary": "Add Asset to Watchlist By Name", + "tags": [ + "Watchlists" + ] + }, + "put": { + "description": "Update the name and/or content of watchlist", + "operationId": "updateWatchlistByName", + "parameters": [ + { + "description": "name of the watchlist", + "in": "query", + "name": "name", + "required": true, + "schema": { + "type": "string" + } + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UpdateWatchlistRequest" + } + } + } + }, + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Watchlist" + } + } + }, + "description": "Successful response" + } + }, + "summary": "Update Watchlist By Name", + "tags": [ + "Watchlists" + ] + } + }, + "/v3/calendar/{market}": { + "get": { + "description": "This endpoint returns the market calendar.", + "operationId": "Calendar", + "parameters": [ + { + "$ref": "#/components/parameters/market" + }, + { + "$ref": "#/components/parameters/start" + }, + { + "$ref": "#/components/parameters/end" + }, + { + "$ref": "#/components/parameters/timezone" + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/public_calendar_resp" + } + } + }, + "description": "OK", + "headers": { + "X-RateLimit-Limit": { + "$ref": "#/components/headers/ratelimit_limit" + }, + "X-RateLimit-Remaining": { + "$ref": "#/components/headers/ratelimit_remaining" + }, + "X-RateLimit-Reset": { + "$ref": "#/components/headers/ratelimit_reset" + } + } + }, + "400": { + "$ref": "#/components/responses/400" + }, + "429": { + "$ref": "#/components/responses/429" + }, + "500": { + "$ref": "#/components/responses/500" + } + }, + "summary": "Get Market Calendar", + "tags": [ + "Calendar" + ] + } + }, + "/v3/clock": { + "get": { + "description": "This API serves information about multiple markets: the current time, if it's a market day, the current phase of the market, etc.\n", + "operationId": "Clock", + "parameters": [ + { + "$ref": "#/components/parameters/markets" + }, + { + "$ref": "#/components/parameters/time" + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/clock_resp" + } + } + }, + "description": "OK", + "headers": { + "X-RateLimit-Limit": { + "$ref": "#/components/headers/ratelimit_limit" + }, + "X-RateLimit-Remaining": { + "$ref": "#/components/headers/ratelimit_remaining" + }, + "X-RateLimit-Reset": { + "$ref": "#/components/headers/ratelimit_reset" + } + } + }, + "400": { + "$ref": "#/components/responses/400" + }, + "429": { + "$ref": "#/components/responses/429" + }, + "500": { + "$ref": "#/components/responses/500" + } + }, + "summary": "Get Market Clock", + "tags": [ + "Calendar" + ] + } + } + }, + "security": [ + { + "API_Key": [], + "API_Secret": [] + } + ], + "servers": [ + { + "description": "Paper", + "url": "https://paper-api.alpaca.markets" + }, + { + "description": "Live", + "url": "https://api.alpaca.markets" + } + ], + "tags": [ + { + "name": "Accounts" + }, + { + "name": "Assets" + }, + { + "name": "Corporate Actions" + }, + { + "name": "Orders" + }, + { + "name": "Positions" + }, + { + "name": "Portfolio History" + }, + { + "name": "Watchlists" + }, + { + "name": "Account Configurations" + }, + { + "name": "Account Activities" + }, + { + "name": "Calendar" + }, + { + "name": "Clock" + }, + { + "name": "Crypto Funding" + }, + { + "name": "Crypto Perpetuals Funding (Beta)" + }, + { + "name": "Crypto Perpetuals Account Vitals (Beta)" + }, + { + "name": "Crypto Perpetuals Leverage (Beta)" + }, + { + "description": "Request minting of tokenized assets and list tokenization requests on the Instant Tokenization Network (ITN).", + "name": "Tokenization" + }, + { + "name": "Events" + } + ], + "x-readme": { + "explorer-enabled": true, + "proxy-enabled": false + } +} From ffa4926076bec1f940f1ae7b32840bdef6b25683 Mon Sep 17 00:00:00 2001 From: Jacob Peterson <14127217+Petersoj@users.noreply.github.com> Date: Mon, 22 Jun 2026 19:57:46 -0700 Subject: [PATCH 243/271] Add new logo to `docs/` --- docs/logo.svg | 1 + 1 file changed, 1 insertion(+) create mode 100644 docs/logo.svg diff --git a/docs/logo.svg b/docs/logo.svg new file mode 100644 index 00000000..6a2c6b10 --- /dev/null +++ b/docs/logo.svg @@ -0,0 +1 @@ + From 750404b13576b863f960bc37acc7392485546b3e Mon Sep 17 00:00:00 2001 From: Jacob Peterson <14127217+Petersoj@users.noreply.github.com> Date: Tue, 23 Jun 2026 13:45:27 -0600 Subject: [PATCH 244/271] Remove unsupported `discriminator` key from `openapi.json` in `broker` module --- module/broker/openapi.json | 7 ------- 1 file changed, 7 deletions(-) diff --git a/module/broker/openapi.json b/module/broker/openapi.json index 0a0b338b..3e0955a8 100644 --- a/module/broker/openapi.json +++ b/module/broker/openapi.json @@ -7586,13 +7586,6 @@ } ], "description": "Represents a cash or security transfer between accounts, as specified by the `entry_type` parameter.", - "discriminator": { - "mapping": { - "JNLC": "#/components/schemas/JNLC", - "JNLS": "#/components/schemas/JNLS" - }, - "propertyName": "entry_type" - }, "example": { "entry_type": "JNLS", "from_account": "8fjkjn-4483-4199-840f-6c5fe0b7ca24", From d2cf931bfc73dea03bafb90a291878fb08013966 Mon Sep 17 00:00:00 2001 From: Jacob Peterson <14127217+Petersoj@users.noreply.github.com> Date: Tue, 23 Jun 2026 14:04:48 -0600 Subject: [PATCH 245/271] Add IntelliJ codestyle XML export to `.codestyle` --- .codestyle/IntelliJ IDEA.xml | 256 +++++++++++++++++++++++++++++++++++ 1 file changed, 256 insertions(+) create mode 100644 .codestyle/IntelliJ IDEA.xml diff --git a/.codestyle/IntelliJ IDEA.xml b/.codestyle/IntelliJ IDEA.xml new file mode 100644 index 00000000..8e25288f --- /dev/null +++ b/.codestyle/IntelliJ IDEA.xml @@ -0,0 +1,256 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file From d078acdf7df34ede1fd74cddf6cd4bdda7bde053 Mon Sep 17 00:00:00 2001 From: Jacob Peterson <14127217+Petersoj@users.noreply.github.com> Date: Tue, 23 Jun 2026 14:30:16 -0600 Subject: [PATCH 246/271] Rename `ApiEnvironment.SANDBOX` to `DEVELOPMENT`, minor Gradle build script improvements --- .github/FUNDING.yml | 2 +- .gitignore | 7 +++---- build.gradle.kts | 1 - buildSrc/src/main/kotlin/ModuleCommon.kt | 2 +- .../kotlin/OpenApiGeneratorAdaptedExtension.kt | 2 +- buildSrc/src/main/kotlin/module-common.gradle.kts | 2 -- .../kotlin/openapi-generator-adapted.gradle.kts | 15 ++++++++------- module/broker/build.gradle.kts | 2 +- .../alpacajava/common/ApiEnvironment.java | 4 ++-- module/market-data/build.gradle.kts | 2 +- module/trading/build.gradle.kts | 2 +- 11 files changed, 19 insertions(+), 22 deletions(-) diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml index 55ede737..f1180668 100644 --- a/.github/FUNDING.yml +++ b/.github/FUNDING.yml @@ -1 +1 @@ -github: Petersoj \ No newline at end of file +github: Petersoj diff --git a/.gitignore b/.gitignore index 12a5664a..499dc333 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,5 @@ -/bin/ -build/ -.gradle/ -.settings .DS_Store +.gradle/ +.kotlin/ .idea/ +build/ diff --git a/build.gradle.kts b/build.gradle.kts index 86685aa5..8f9b37a4 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -39,7 +39,6 @@ jreleaser { } } } - subprojects { tasks.configureEach { rootProject.tasks.jreleaserFullRelease.configure { diff --git a/buildSrc/src/main/kotlin/ModuleCommon.kt b/buildSrc/src/main/kotlin/ModuleCommon.kt index 477c7ecc..c93f8dc8 100644 --- a/buildSrc/src/main/kotlin/ModuleCommon.kt +++ b/buildSrc/src/main/kotlin/ModuleCommon.kt @@ -1,3 +1,3 @@ -const val PROJECT_GROUP = "net.jacobpeterson.alpacajava" +const val PROJECT_GROUP = "net.jacobpeterson.alpaca-java" const val PROJECT_VERSION = "11.0.0" const val JRELEASER_MAVEN_REPOSITORY_DIRECTORY = "jreleaser-staging" diff --git a/buildSrc/src/main/kotlin/OpenApiGeneratorAdaptedExtension.kt b/buildSrc/src/main/kotlin/OpenApiGeneratorAdaptedExtension.kt index 9efc9920..2e297991 100644 --- a/buildSrc/src/main/kotlin/OpenApiGeneratorAdaptedExtension.kt +++ b/buildSrc/src/main/kotlin/OpenApiGeneratorAdaptedExtension.kt @@ -3,5 +3,5 @@ import org.gradle.api.provider.Property interface OpenApiGeneratorAdaptedExtension { val environmentUrlProduction: Property - val environmentUrlSandbox: Property + val environmentUrlDevelopment: Property } diff --git a/buildSrc/src/main/kotlin/module-common.gradle.kts b/buildSrc/src/main/kotlin/module-common.gradle.kts index 4c8a5502..7c88e93e 100644 --- a/buildSrc/src/main/kotlin/module-common.gradle.kts +++ b/buildSrc/src/main/kotlin/module-common.gradle.kts @@ -74,8 +74,6 @@ tasks.withType(Javadoc::class).configureEach { "https://guava.dev/releases/$guavaVersion/api/docs/", "https://errorprone.info/api/latest/") } -} -tasks.withType(Javadoc::class).configureEach { project.configurations.flatMap { it.dependencies.withType(ProjectDependency::class) }.forEach { project(it.path).tasks.withType(Javadoc::class).forEach { dependencyJavadocTask -> this@configureEach.dependsOn(dependencyJavadocTask) diff --git a/buildSrc/src/main/kotlin/openapi-generator-adapted.gradle.kts b/buildSrc/src/main/kotlin/openapi-generator-adapted.gradle.kts index 2177a025..bd31d970 100644 --- a/buildSrc/src/main/kotlin/openapi-generator-adapted.gradle.kts +++ b/buildSrc/src/main/kotlin/openapi-generator-adapted.gradle.kts @@ -20,7 +20,7 @@ val extension = extensions.create("openApiGeneratorAdapted", OpenApiGeneratorAda tasks.openApiGenerate.configure { inputSpec = file("openapi.json") generatorName = "java" - val outputPackageName = "${project.group}.${project.name.replace("-", "")}" + val outputPackageName = "${project.group}.${project.name}".replace("-", "") configOptions.put("invokerPackage", outputPackageName) val apiPackageName = "api" configOptions.put("apiPackage", "$outputPackageName.$apiPackageName") @@ -38,7 +38,7 @@ tasks.openApiGenerate.configure { val projectName = project.name val projectDescription = project.description val environmentUrlProduction = extension.environmentUrlProduction.get() - val environmentUrlSandbox = extension.environmentUrlSandbox.get() + val environmentUrlDevelopment = extension.environmentUrlDevelopment.get() doLast { val srcMainJava = outputDir.get().asFile.resolve("src/main/java/") srcMainJava.walkTopDown().filter { it.isFile }.forEach { @@ -88,9 +88,9 @@ tasks.openApiGenerate.configure { public static final String ENVIRONMENT_URL_PRODUCTION ="$environmentUrlProduction"; /** - * The URL for {@link ApiEnvironment#SANDBOX}: "$environmentUrlSandbox" + * The URL for {@link ApiEnvironment#DEVELOPMENT}: "$environmentUrlDevelopment" */ - public static final String ENVIRONMENT_URL_SANDBOX = "$environmentUrlSandbox"; + public static final String ENVIRONMENT_URL_DEVELOPMENT = "$environmentUrlDevelopment"; private final HttpClient httpClient; private final ApiClient apiClient; @@ -125,8 +125,9 @@ tasks.openApiGenerate.configure { * @param authenticationSecretKey the {@link AlpacaHeader#API_SECRET_KEY} value * @param authorizationBearerToken the {@link AlpacaHeader#AUTHORIZATION_TOKEN_BEARER_PREFIX} suffix value * @param apiEnvironment the {@link ApiEnvironment}. If {@link ApiEnvironment#PRODUCTION}, then - * {@link #ENVIRONMENT_URL_PRODUCTION} is used. If {@link ApiEnvironment#SANDBOX}, - * then {@link #ENVIRONMENT_URL_SANDBOX} is used. + * {@link #ENVIRONMENT_URL_PRODUCTION} is used. If + * {@link ApiEnvironment#DEVELOPMENT}, then {@link #ENVIRONMENT_URL_DEVELOPMENT} + * is used. * @param requestInterceptor the {@link HttpRequest.Builder} {@link Consumer} to invoke before sending a * request * @param responseInterceptor the {@link HttpResponse} {@link Consumer} to invoke before processing a response @@ -138,7 +139,7 @@ tasks.openApiGenerate.configure { final @Nullable Consumer> responseInterceptor) { this.httpClient = httpClient != null ? httpClient : HttpClient.newBuilder().connectTimeout(ofSeconds(10)).build(); - final var baseUri = apiEnvironment == PRODUCTION ? ENVIRONMENT_URL_PRODUCTION : ENVIRONMENT_URL_SANDBOX; + final var baseUri = apiEnvironment == PRODUCTION ? ENVIRONMENT_URL_PRODUCTION : ENVIRONMENT_URL_DEVELOPMENT; apiClient = new ApiClient(null, objectMapper != null ? objectMapper : ApiClient.createDefaultObjectMapper(), null) { diff --git a/module/broker/build.gradle.kts b/module/broker/build.gradle.kts index bb5e64af..e2d27247 100644 --- a/module/broker/build.gradle.kts +++ b/module/broker/build.gradle.kts @@ -11,5 +11,5 @@ dependencies { openApiGeneratorAdapted { environmentUrlProduction = "https://broker-api.alpaca.markets" - environmentUrlSandbox = "https://broker-api.sandbox.alpaca.markets" + environmentUrlDevelopment = "https://broker-api.sandbox.alpaca.markets" } diff --git a/module/common/src/main/java/net/jacobpeterson/alpacajava/common/ApiEnvironment.java b/module/common/src/main/java/net/jacobpeterson/alpacajava/common/ApiEnvironment.java index 047dde96..59a20bc0 100644 --- a/module/common/src/main/java/net/jacobpeterson/alpacajava/common/ApiEnvironment.java +++ b/module/common/src/main/java/net/jacobpeterson/alpacajava/common/ApiEnvironment.java @@ -14,7 +14,7 @@ public enum ApiEnvironment { PRODUCTION, /** - * Sandbox/testing/paper environment. + * Development/sandbox/paper environment. */ - SANDBOX + DEVELOPMENT } diff --git a/module/market-data/build.gradle.kts b/module/market-data/build.gradle.kts index 848ba876..8e497a91 100644 --- a/module/market-data/build.gradle.kts +++ b/module/market-data/build.gradle.kts @@ -11,5 +11,5 @@ dependencies { openApiGeneratorAdapted { environmentUrlProduction = "https://data.alpaca.markets" - environmentUrlSandbox = "https://data.sandbox.alpaca.markets" + environmentUrlDevelopment = "https://data.sandbox.alpaca.markets" } diff --git a/module/trading/build.gradle.kts b/module/trading/build.gradle.kts index 3a73dfc0..1b5103db 100644 --- a/module/trading/build.gradle.kts +++ b/module/trading/build.gradle.kts @@ -11,5 +11,5 @@ dependencies { openApiGeneratorAdapted { environmentUrlProduction = "https://api.alpaca.markets" - environmentUrlSandbox = "https://paper-api.alpaca.markets" + environmentUrlDevelopment = "https://paper-api.alpaca.markets" } From 40b2733b99d25cf365fc8498786831d93fa69e7b Mon Sep 17 00:00:00 2001 From: Jacob Peterson <14127217+Petersoj@users.noreply.github.com> Date: Wed, 24 Jun 2026 09:58:42 -0600 Subject: [PATCH 247/271] Update OpenAPI specification JSON files --- module/market-data/openapi.json | 4 + module/trading/openapi.json | 1042 ++++++++++++++++++++++++++++++- 2 files changed, 1013 insertions(+), 33 deletions(-) diff --git a/module/market-data/openapi.json b/module/market-data/openapi.json index 7eb5a086..8d26e84b 100644 --- a/module/market-data/openapi.json +++ b/module/market-data/openapi.json @@ -2614,6 +2614,10 @@ "format": "double", "type": "number" }, + "new_symbol": { + "description": "The post-split ticker. Empty when the reverse split does not change the\nticker (the most common case); only populated when the issuer assigns a\ndifferent symbol after the split.\n", + "type": "string" + }, "old_cusip": { "type": "string" }, diff --git a/module/trading/openapi.json b/module/trading/openapi.json index 013dbdfd..2e136fdf 100644 --- a/module/trading/openapi.json +++ b/module/trading/openapi.json @@ -301,7 +301,7 @@ "name": "page_token", "required": false, "schema": { - "example": "MA==", + "example": "eyJpZCI6IjU1MGU4NDAwLWUyOWItNDFkNC1hNzE2LTQ0NjY1NTQ0MDAwMCJ9Cg", "type": "string" } }, @@ -509,6 +509,9 @@ "format": "date-time", "type": "string" }, + "crypto_status": { + "$ref": "#/components/schemas/AccountStatus" + }, "currency": { "description": "USD\n", "example": "USD", @@ -789,13 +792,20 @@ "AccountStatus": { "description": "An enum representing the various possible account status values.\n\nMost likely, the account status is ACTIVE unless there is any problem. The account status may get in ACCOUNT_UPDATED when personal information is being updated from the dashboard, in which case you may not be allowed trading for a short period of time until the change is approved.\n\n- ONBOARDING\n The account is onboarding.\n- SUBMISSION_FAILED\n The account application submission failed for some reason.\n- SUBMITTED\n The account application has been submitted for review.\n- ACCOUNT_UPDATED\n The account information is being updated.\n- APPROVAL_PENDING\n The final account approval is pending.\n- ACTIVE\n The account is active for trading.\n- REJECTED\n The account application has been rejected.", "enum": [ + "INACTIVE", + "PAPER_ONLY", "ONBOARDING", "SUBMISSION_FAILED", "SUBMITTED", "ACCOUNT_UPDATED", "APPROVAL_PENDING", "ACTIVE", - "REJECTED" + "REJECTED", + "ACCOUNT_CLOSED", + "APPROVED", + "ACCOUNT_CLOSED_PENDING", + "ACTION_REQUIRED", + "LIMITED" ], "example": "ACTIVE", "title": "AccountStatus", @@ -1333,11 +1343,26 @@ "description": "Asset is marginable or not", "type": "boolean" }, + "min_order_size": { + "description": "Minimum order size. Field available for crypto only.", + "format": "decimal", + "type": "string" + }, + "min_trade_increment": { + "description": "Amount a trade quantity can be incremented by. Field available for crypto only.", + "format": "decimal", + "type": "string" + }, "name": { "description": "The official name of the asset", "minLength": 1, "type": "string" }, + "price_increment": { + "description": "Amount the price can be incremented by. Field available for crypto only.", + "format": "decimal", + "type": "string" + }, "shortable": { "description": "Asset is shortable or not", "type": "boolean" @@ -1971,6 +1996,46 @@ "title": "CreateCryptoTransferRequest", "type": "object" }, + "CreateLocateRequest": { + "description": "Request to locate shares for a short sale.", + "example": { + "all_or_none": false, + "limit_price": "0.05", + "qty": 100, + "symbol": "TSLA" + }, + "properties": { + "all_or_none": { + "default": false, + "description": "Reject the locate unless the full requested quantity is available.", + "example": false, + "type": "boolean" + }, + "limit_price": { + "description": "Maximum acceptable locate fee per share, as a decimal string in USD.\nIf omitted, any quoted fee is accepted.\n", + "example": "0.05", + "nullable": true, + "type": "string" + }, + "qty": { + "description": "Number of shares to locate. Must be positive and in round lots of 100; invalid quantities return HTTP 400.", + "example": 100, + "format": "int64", + "minimum": 1, + "type": "integer" + }, + "symbol": { + "description": "Stock symbol.", + "example": "TSLA", + "type": "string" + } + }, + "required": [ + "symbol", + "qty" + ], + "type": "object" + }, "CryptoTransfer": { "description": "Transfers allow you to transfer assets into your end customer's account (deposits) or out (withdrawal).", "properties": { @@ -2180,6 +2245,16 @@ "title": "Error", "type": "object" }, + "ErrorResponse": { + "description": "API error response.", + "properties": { + "message": { + "example": "Internal Server Error", + "type": "string" + } + }, + "type": "object" + }, "Exchange": { "description": "Represents the current exchanges Alpaca supports. List is currently:\n\n- AMEX\n- ARCA\n- BATS\n- NYSE\n- NASDAQ\n- NYSEARCA\n- OTC", "enum": [ @@ -2284,6 +2359,7 @@ "properties": { "ca_id": { "description": "The unique identifier for this corporate action", + "format": "uuid", "type": "string" }, "cash_payout": { @@ -2387,6 +2463,295 @@ "description": "Journal entry (stock)", "type": "object" }, + "ListLocateQuotesResponse": { + "description": "Response to a successful request for locate quotes.", + "example": { + "errors": [ + { + "code": "symbol_not_found", + "message": "symbol not found", + "symbol": "META" + } + ], + "quotes": [ + { + "available_qty": 1000, + "price": "0.0123", + "quoted_at": "2026-01-02T15:04:05Z", + "symbol": "TSLA" + } + ] + }, + "properties": { + "errors": { + "description": "Symbols that could not be quoted.", + "items": { + "$ref": "#/components/schemas/LocateQuoteError" + }, + "type": "array" + }, + "quotes": { + "description": "Locate quotes returned for requested symbols.", + "items": { + "$ref": "#/components/schemas/LocateQuote" + }, + "type": "array" + } + }, + "required": [ + "quotes" + ], + "type": "object" + }, + "ListLocatesResponse": { + "description": "Response to a successful request for a list of locates.", + "example": { + "locates": [ + { + "all_or_none": false, + "created_at": "2026-01-02T15:04:05Z", + "expires_at": "2026-01-03T01:00:00Z", + "id": "550e8400-e29b-41d4-a716-446655440000", + "limit_price": "0.05", + "located_price": "0.05", + "located_qty": 100, + "requested_qty": 100, + "status": "active", + "symbol": "TSLA", + "total_fee": "5.00" + } + ], + "next_page_token": "eyJpZCI6IjU1MGU4NDAwLWUyOWItNDFkNC1hNzE2LTQ0NjY1NTQ0MDAwMCJ9Cg" + }, + "properties": { + "locates": { + "description": "Locates matching the filter criteria.", + "items": { + "$ref": "#/components/schemas/Locate" + }, + "type": "array" + }, + "next_page_token": { + "description": "The token to use to retrieve the next page of results. If `null`, there are no more pages.", + "nullable": true, + "type": "string" + } + }, + "required": [ + "locates", + "next_page_token" + ], + "type": "object" + }, + "Locate": { + "description": "A locate request and its current lifecycle status.", + "example": { + "all_or_none": false, + "created_at": "2026-01-02T15:04:05Z", + "expires_at": "2026-01-03T01:00:00Z", + "id": "550e8400-e29b-41d4-a716-446655440000", + "limit_price": "0.05", + "located_price": "0.05", + "located_qty": 100, + "requested_qty": 100, + "status": "active", + "symbol": "TSLA", + "total_fee": "5.00" + }, + "properties": { + "all_or_none": { + "description": "Whether the request required the full quantity.", + "example": false, + "type": "boolean" + }, + "created_at": { + "description": "Time when the locate was created.", + "example": "2026-01-02T15:04:05Z", + "format": "date-time", + "type": "string" + }, + "expires_at": { + "description": "Time when the active locate expires. Omitted when rejected.", + "example": "2026-01-03T01:00:00Z", + "format": "date-time", + "nullable": true, + "type": "string" + }, + "id": { + "description": "Locate ID.", + "example": "550e8400-e29b-41d4-a716-446655440000", + "format": "uuid", + "type": "string" + }, + "limit_price": { + "description": "Maximum acceptable fee per share from the request.", + "example": "0.05", + "nullable": true, + "type": "string" + }, + "located_price": { + "description": "Locate fee per share in USD. Omitted when rejected.", + "example": "0.05", + "nullable": true, + "type": "string" + }, + "located_qty": { + "description": "Number of shares located. Omitted when rejected.", + "example": 100, + "format": "int64", + "nullable": true, + "type": "integer" + }, + "rejection_reason": { + "description": "Machine-readable rejection reason.", + "example": "inventory_unavailable", + "nullable": true, + "type": "string" + }, + "requested_qty": { + "description": "Number of shares requested.", + "example": 100, + "format": "int64", + "type": "integer" + }, + "status": { + "description": "Locate status.", + "enum": [ + "active", + "expired", + "rejected" + ], + "example": "active", + "type": "string" + }, + "symbol": { + "description": "Stock symbol.", + "example": "TSLA", + "type": "string" + }, + "total_fee": { + "description": "Total locate fee in USD. Omitted when rejected.", + "example": "5.00", + "nullable": true, + "type": "string" + } + }, + "required": [ + "id", + "symbol", + "requested_qty", + "all_or_none", + "status", + "created_at" + ], + "type": "object" + }, + "LocateError": { + "description": "Locates API error response. Locate-specific 400 and create locate 422 errors include a machine-readable `code`.", + "properties": { + "code": { + "description": "Machine-readable error code. Present on locate-specific 400 and create locate 422 errors.", + "enum": [ + "invalid_input", + "invalid_request_body", + "invalid_limit_price", + "invalid_symbols", + "symbol_not_found", + "security_not_found", + "insufficient_buying_power", + "easy_to_borrow", + "threshold_security" + ], + "example": "invalid_symbols", + "type": "string" + }, + "message": { + "description": "Error message.", + "example": "symbols must contain at most 100 unique symbols", + "type": "string" + } + }, + "required": [ + "message" + ], + "type": "object" + }, + "LocateQuote": { + "description": "Current locate pricing and availability for a symbol.", + "example": { + "available_qty": 1000, + "price": "0.0123", + "quoted_at": "2026-01-02T15:04:05Z", + "symbol": "TSLA" + }, + "properties": { + "available_qty": { + "description": "Available locate quantity.", + "example": 1000, + "format": "int64", + "type": "integer" + }, + "price": { + "description": "Locate fee per share. Omitted when no quantity is available.", + "example": "0.0123", + "type": "string" + }, + "quoted_at": { + "description": "Time when the quote was issued.", + "example": "2026-01-02T15:04:05Z", + "format": "date-time", + "type": "string" + }, + "symbol": { + "description": "Stock symbol.", + "example": "TSLA", + "type": "string" + } + }, + "required": [ + "symbol", + "available_qty", + "quoted_at" + ], + "type": "object" + }, + "LocateQuoteError": { + "description": "Error returned for a symbol that could not be quoted.", + "example": { + "code": "symbol_not_found", + "message": "symbol not found", + "symbol": "META" + }, + "properties": { + "code": { + "description": "Error code.", + "enum": [ + "symbol_not_found", + "easy_to_borrow", + "threshold_security", + "corporate_action" + ], + "example": "symbol_not_found", + "type": "string" + }, + "message": { + "description": "Error message.", + "example": "symbol not found", + "type": "string" + }, + "symbol": { + "description": "Requested stock symbol that could not be quoted.", + "example": "FB", + "type": "string" + } + }, + "required": [ + "symbol", + "code", + "message" + ], + "type": "object" + }, "MAActivityV2": { "allOf": [ { @@ -2491,6 +2856,11 @@ "format": "date-time", "type": "string" }, + "currency": { + "description": "Currency denomination of the activity (e.g. USD). Included in NTA stream events and activity responses.", + "example": "USD", + "type": "string" + }, "cusip": { "description": "The CUSIP of the security involved with the activity. Not present for all activity types.", "type": "string" @@ -2541,6 +2911,7 @@ "activity_sub_type": "SDIV", "activity_type": "DIV", "created_at": "2021-05-10T14:01:04.650275Z", + "currency": "USD", "date": "2019-08-01", "id": "20190801011955195::5f596936-6f23-4cef-bdf1-3806aae57dbf", "net_amount": "1.02", @@ -2809,11 +3180,13 @@ "id": { "description": "The unique identifier of the option contract.", "example": "98359ef7-5124-49f3-85ea-5cf02df6defa", + "format": "uuid", "type": "string" }, "multiplier": { "description": "The multiplier of the option contract is crucial for calculating both the trade premium and the extended strike price. In standard contracts, the multiplier is always set to 100.\nFor instance, if a contract is traded at $1.50 and the multiplier is 100, the total amount debited when buying the contract would be $150.00.\nSimilarly, when exercising a call contract, the total cost will be equal to the strike price times the multiplier.", "example": "100", + "format": "decimal", "type": "string" }, "name": { @@ -2854,6 +3227,7 @@ "strike_price": { "description": "The strike price of the option contract.", "example": "100", + "format": "decimal", "type": "string" }, "style": { @@ -2887,6 +3261,7 @@ "underlying_asset_id": { "description": "The unique identifier of the underlying asset.", "example": "b0b6dd9d-8b9b-48a9-ba46-b9d54906e415", + "format": "uuid", "type": "string" }, "underlying_symbol": { @@ -2927,6 +3302,7 @@ "asset_id": { "description": "Unique identifier of the deliverable asset. For standard contracts, this is equivalent to underlying_asset_id of the contracts.\nThis field is not returned for cash deliverables.\n", "example": "b0b6dd9d-8b9b-48a9-ba46-b9d54906e415", + "format": "uuid", "type": "string" }, "delayed_settlement": { @@ -3014,6 +3390,10 @@ "nullable": true, "type": "string" }, + "expires_at": { + "format": "date-time", + "type": "string" + }, "extended_hours": { "description": "If true, eligible for execution outside regular trading hours.", "type": "boolean" @@ -3045,6 +3425,7 @@ }, "id": { "description": "Order ID", + "format": "uuid", "type": "string" }, "legs": { @@ -3083,6 +3464,12 @@ "nullable": true, "type": "string" }, + "ratio_qty": { + "description": "The proportional quantity of this leg in relation to the overall multi-leg order quantity.", + "format": "decimal", + "nullable": true, + "type": "string" + }, "replaced_at": { "format": "date-time", "nullable": true, @@ -3226,6 +3613,7 @@ }, "id": { "description": "Order ID", + "format": "uuid", "type": "string" }, "legs": { @@ -3327,11 +3715,11 @@ }, "required": [ "symbol", + "type", + "time_in_force", "notional", "qty", - "type", - "side", - "time_in_force" + "side" ], "title": "Order", "type": "object" @@ -3364,7 +3752,8 @@ "stopped", "rejected", "suspended", - "calculated" + "calculated", + "held" ], "example": "new", "title": "OrderStatus", @@ -3709,6 +4098,11 @@ "minLength": 1, "type": "string" }, + "avg_entry_swap_rate": { + "description": "The weighted-average exchange rate at the time the position was entered.", + "format": "decimal", + "type": "string" + }, "change_today": { "description": "Percent change from last day price (by a factor of 1)", "minLength": 1, @@ -3737,6 +4131,11 @@ "minLength": 1, "type": "string" }, + "prev_swap_rate": { + "description": "The exchange rate as of the previous close (i.e. yesterday's rate)", + "format": "decimal", + "type": "string" + }, "qty": { "description": "The number of shares", "minLength": 1, @@ -3749,7 +4148,15 @@ }, "side": { "description": "long", - "minLength": 1, + "enum": [ + "long", + "short" + ], + "type": "string" + }, + "swap_rate": { + "description": "The current exchange rate (without mark-up) used to convert local-currency position values into USD. Present only for LCT (Local Currency Trading) accounts", + "format": "decimal", "type": "string" }, "symbol": { @@ -3776,6 +4183,9 @@ "description": "Unrealized profit/loss percent (by a factor of 1)", "minLength": 1, "type": "string" + }, + "usd": { + "$ref": "#/components/schemas/USDPositionValues" } }, "required": [ @@ -4414,25 +4824,96 @@ "example": "INCOMING", "type": "string" }, - "UnitSplitActivityV2": { - "allOf": [ - { - "$ref": "#/components/schemas/CommonSplitStockActivityV2" + "USDPositionValues": { + "description": "Position values in USD. This is returned for LCT (non-USD) accounts only.", + "properties": { + "avg_entry_price": { + "description": "Average entry price of the position in USD", + "example": "71.43", + "format": "decimal", + "type": "string" }, - { - "properties": { - "alternate_cusip": { - "description": "CUSIP for the alternate security after the split", - "type": "string" - }, - "alternate_qty": { - "description": "Quantity of alternate shares received", - "type": "string" - }, - "alternate_rate": { - "description": "Ratio of alternate shares received", - "type": "string" - }, + "change_today": { + "description": "Percent change from last day price (by a factor of 1)", + "example": "0.67", + "format": "decimal", + "type": "string" + }, + "cost_basis": { + "description": "Total cost basis in USD", + "example": "333.33", + "format": "decimal", + "type": "string" + }, + "current_price": { + "description": "Current asset price per share in USD", + "example": "80.0", + "format": "decimal", + "type": "string" + }, + "lastday_price": { + "description": "Last day's asset price per share based on the closing value of the last trading day in USD", + "example": "79.33", + "format": "decimal", + "type": "string" + }, + "market_value": { + "description": "Total market value of the position in USD", + "example": "400.00", + "format": "decimal", + "type": "string" + }, + "unrealized_intraday_pl": { + "description": "Unrealized profit/loss in USD for the day", + "example": "6.67", + "format": "decimal", + "type": "string" + }, + "unrealized_intraday_plpc": { + "description": "Unrealized intraday profit/loss percent (by a factor of 1)", + "example": "0.0084", + "format": "decimal", + "type": "string" + }, + "unrealized_pl": { + "description": "Unrealized profit/loss in USD", + "example": "66.67", + "format": "decimal", + "type": "string" + }, + "unrealized_plpc": { + "description": "Unrealized profit/loss percent (by a factor of 1)", + "example": "0.2", + "format": "decimal", + "type": "string" + } + }, + "required": [ + "avg_entry_price", + "cost_basis" + ], + "title": "USDPositionValues", + "type": "object" + }, + "UnitSplitActivityV2": { + "allOf": [ + { + "$ref": "#/components/schemas/CommonSplitStockActivityV2" + }, + { + "properties": { + "alternate_cusip": { + "description": "CUSIP for the alternate security after the split", + "type": "string" + }, + "alternate_qty": { + "description": "Quantity of alternate shares received", + "type": "string" + }, + "alternate_rate": { + "description": "Ratio of alternate shares received", + "type": "string" + }, "alternate_symbol": { "description": "Symbol for the alternate security after the split", "type": "string" @@ -4520,6 +5001,22 @@ "description": "Worthless Removal", "type": "object" }, + "WalletFeeEstimateResponse": { + "description": "Estimated on-chain fee breakdown for a proposed crypto withdrawal, including the total fee and the underlying network (gas) fee.", + "properties": { + "fee": { + "example": "0.0505", + "format": "decimal", + "type": "string" + }, + "network_fee": { + "example": "0.0825", + "format": "decimal", + "type": "string" + } + }, + "type": "object" + }, "WarrantExerciseElectionActivityV2": { "allOf": [ { @@ -5588,6 +6085,479 @@ }, "openapi": "3.0.0", "paths": { + "/v1/locates": { + "get": { + "description": "Returns locates filtered by status, symbol, or date range. Results are sorted by `created_at` descending, with `id` descending as the tie-breaker.", + "operationId": "listLocates", + "parameters": [ + { + "$ref": "#/components/parameters/PageToken" + }, + { + "description": "Maximum number of results to return.", + "in": "query", + "name": "limit", + "required": false, + "schema": { + "default": 1000, + "maximum": 10000, + "minimum": 1, + "type": "integer" + } + }, + { + "description": "Filter by locate status.", + "example": "active", + "in": "query", + "name": "status", + "required": false, + "schema": { + "enum": [ + "active", + "expired", + "rejected" + ], + "type": "string" + } + }, + { + "description": "Filter by stock symbol.", + "example": "TSLA", + "in": "query", + "name": "symbol", + "required": false, + "schema": { + "type": "string" + } + }, + { + "description": "Filter locates with locate trading date on or after this date. Format: YYYY-MM-DD. The locate trading date uses America/New_York and rolls at 8pm ET.", + "in": "query", + "name": "start", + "required": false, + "schema": { + "format": "date", + "type": "string" + } + }, + { + "description": "Filter locates with locate trading date before this date (exclusive). Format: YYYY-MM-DD. The locate trading date uses America/New_York and rolls at 8pm ET.", + "in": "query", + "name": "end", + "required": false, + "schema": { + "format": "date", + "type": "string" + } + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ListLocatesResponse" + } + } + }, + "description": "Returns a list of locates for the account, or an empty list if no locates are found." + }, + "400": { + "content": { + "application/json": { + "examples": { + "invalid_input": { + "value": { + "code": "invalid_input", + "message": "invalid input" + } + } + }, + "schema": { + "$ref": "#/components/schemas/LocateError" + } + } + }, + "description": "Invalid locate request." + }, + "401": { + "content": { + "application/json": { + "example": { + "message": "Unauthorized" + }, + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + }, + "description": "Authentication headers are missing or invalid. Make sure you authenticate your request with a valid API key." + }, + "403": { + "content": { + "application/json": { + "example": { + "message": "Forbidden" + }, + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + }, + "description": "User has no access to a resource." + }, + "500": { + "content": { + "application/json": { + "example": { + "message": "Internal Server Error" + }, + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + }, + "description": "Internal server error. We recommend retrying these later. If the issue persists, please contact us on Slack or on the Community Forum." + } + }, + "summary": "List Locates", + "tags": [ + "Locates" + ] + }, + "post": { + "description": "Creates a locate request for a short sale.", + "operationId": "createLocates", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CreateLocateRequest" + } + } + }, + "description": "Locate request details.", + "required": true + }, + "responses": { + "201": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Locate" + } + } + }, + "description": "Created" + }, + "400": { + "content": { + "application/json": { + "examples": { + "invalid_input": { + "value": { + "code": "invalid_input", + "message": "invalid input: quantity must be in round lots of 100" + } + }, + "invalid_limit_price": { + "value": { + "code": "invalid_limit_price", + "message": "invalid limit_price" + } + }, + "invalid_request_body": { + "value": { + "code": "invalid_request_body", + "message": "invalid request body" + } + } + }, + "schema": { + "$ref": "#/components/schemas/LocateError" + } + } + }, + "description": "Invalid locate request." + }, + "401": { + "content": { + "application/json": { + "example": { + "message": "Unauthorized" + }, + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + }, + "description": "Authentication headers are missing or invalid. Make sure you authenticate your request with a valid API key." + }, + "403": { + "content": { + "application/json": { + "example": { + "message": "Forbidden" + }, + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + }, + "description": "User has no access to a resource." + }, + "422": { + "content": { + "application/json": { + "examples": { + "easy_to_borrow": { + "value": { + "code": "easy_to_borrow", + "message": "security is easy-to-borrow and does not require a locate" + } + }, + "insufficient_buying_power": { + "value": { + "code": "insufficient_buying_power", + "message": "insufficient buying power to cover locate fee" + } + }, + "symbol_not_found": { + "value": { + "code": "symbol_not_found", + "message": "symbol not found" + } + }, + "threshold_security": { + "value": { + "code": "threshold_security", + "message": "security is on the threshold list and cannot be located" + } + } + }, + "schema": { + "$ref": "#/components/schemas/LocateError" + } + } + }, + "description": "Cannot complete locate request." + }, + "500": { + "content": { + "application/json": { + "example": { + "message": "Internal Server Error" + }, + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + }, + "description": "Internal server error. We recommend retrying these later. If the issue persists, please contact us on Slack or on the Community Forum." + } + }, + "summary": "Create Locate", + "tags": [ + "Locates" + ] + } + }, + "/v1/locates/{locate_id}": { + "get": { + "description": "Returns a locate by ID.", + "operationId": "getLocate", + "parameters": [ + { + "description": "The locate ID.", + "example": "550e8400-e29b-41d4-a716-446655440000", + "in": "path", + "name": "locate_id", + "required": true, + "schema": { + "format": "uuid", + "type": "string" + } + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Locate" + } + } + }, + "description": "Returns the requested locate." + }, + "400": { + "content": { + "application/json": { + "examples": { + "invalid_input": { + "value": { + "code": "invalid_input", + "message": "invalid input" + } + } + }, + "schema": { + "$ref": "#/components/schemas/LocateError" + } + } + }, + "description": "Invalid locate request." + }, + "401": { + "content": { + "application/json": { + "example": { + "message": "Unauthorized" + }, + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + }, + "description": "Authentication headers are missing or invalid. Make sure you authenticate your request with a valid API key." + }, + "403": { + "content": { + "application/json": { + "example": { + "message": "Forbidden" + }, + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + }, + "description": "User has no access to a resource." + }, + "404": { + "content": { + "application/json": { + "example": { + "message": "locate not found" + }, + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + }, + "description": "Not found" + }, + "500": { + "content": { + "application/json": { + "example": { + "message": "Internal Server Error" + }, + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + }, + "description": "Internal server error. We recommend retrying these later. If the issue persists, please contact us on Slack or on the Community Forum." + } + }, + "summary": "Get Locate", + "tags": [ + "Locates" + ] + } + }, + "/v1/locates/quotes": { + "get": { + "description": "Returns locate availability and pricing for one or more symbols.", + "operationId": "listLocateQuotes", + "parameters": [ + { + "description": "Comma-separated list of stock symbols. Maximum 100 unique symbols.", + "example": "TSLA,AAPL", + "in": "query", + "name": "symbols", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ListLocateQuotesResponse" + } + } + }, + "description": "Returns locate quotes for the requested symbols." + }, + "400": { + "content": { + "application/json": { + "examples": { + "invalid_symbols": { + "value": { + "code": "invalid_symbols", + "message": "symbols must contain at most 100 unique symbols" + } + } + }, + "schema": { + "$ref": "#/components/schemas/LocateError" + } + } + }, + "description": "Invalid locate quote request." + }, + "401": { + "content": { + "application/json": { + "example": { + "message": "Unauthorized" + }, + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + }, + "description": "Authentication headers are missing or invalid. Make sure you authenticate your request with a valid API key." + }, + "403": { + "content": { + "application/json": { + "example": { + "message": "Forbidden" + }, + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + }, + "description": "User has no access to a resource." + }, + "500": { + "content": { + "application/json": { + "example": { + "message": "Internal Server Error" + }, + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + }, + "description": "Internal server error. We recommend retrying these later. If the issue persists, please contact us on Slack or on the Community Forum." + } + }, + "summary": "Get Locate Quotes", + "tags": [ + "Locates" + ] + } + }, "/v2beta1/events/activities": { "get": { "description": "The Events API sends the real-time events and provides historical queries with SSE (Server Sent Events).\n\nThis endpoint streams events on account activities.\n\nHistorical events are streamed immediately if queried, and updates are pushed as events occur.\n\nQuery parameter rules:\n- If `until` is specified, `since` is required.\n- If `until_id` is specified, `since_id` is required.\n- You cannot use `since` and `since_id` together.\nBehavior:\n- If `since` or `since_id` is not specified, this will not return any historic data.\n- If `until` or `until_id` is specified, stream will end at the specified point with status 200.\n\n---\n\nWarning: Currently, OAS-3 does not fully support responses from an SSE API.\n\nIn case the client code is generated from this OAS spec, please do not specify `since` and `until`, as the generated client may hang forever waiting for the response to end.\n\nIf you require the streaming capabilities, we recommend not using the generated clients for this specific endpoint until the OAS-3 standards define how to represent this behavior.\n\n---\n\n### Comment messages\nAccording to the SSE specification, any line that starts with a colon is a comment which does not contain data. It is typically a free text that does not follow any data schema. A few examples mentioned below for comment messages.\n\n##### Slow client\n\nThe server sends a comment when the client is not consuming messages fast enough. Example: `: you are reading too slowly, dropped 10000 messages`\n\n##### Internal server error\n\nAn error message is sent as a comment when the server closes the connection on an internal server error (only sent by the v2 and v2beta1 endpoints). Example: `: internal server error`\n\n---", @@ -6471,7 +7441,7 @@ }, "summary": "Get US Market Clock", "tags": [ - "Calendar" + "Clock" ] } }, @@ -7084,6 +8054,13 @@ "us_equity", "us_option", "crypto", + "crypto_perp", + "treasury", + "corporate", + "global_equity", + "us_index", + "us_equity_chain", + "ipo", "all" ], "type": "string" @@ -8537,12 +9514,7 @@ "content": { "application/json": { "schema": { - "properties": { - "fee": { - "type": "string" - } - }, - "type": "object" + "$ref": "#/components/schemas/WalletFeeEstimateResponse" } } }, @@ -9209,7 +10181,7 @@ }, "summary": "Get Market Clock", "tags": [ - "Calendar" + "Clock" ] } } @@ -9280,6 +10252,10 @@ "description": "Request minting of tokenized assets and list tokenization requests on the Instant Tokenization Network (ITN).", "name": "Tokenization" }, + { + "description": "Endpoints for locate requests and quotes.", + "name": "Locates" + }, { "name": "Events" } From c1211a7f258aaa3af5a7814165028646946b6b77 Mon Sep 17 00:00:00 2001 From: Jacob Peterson <14127217+Petersoj@users.noreply.github.com> Date: Wed, 24 Jun 2026 16:09:07 -0600 Subject: [PATCH 248/271] Add `release` GitHub Action workflow --- .github/workflows/release.yml | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) create mode 100644 .github/workflows/release.yml diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 00000000..f13589c7 --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,28 @@ +name: release +on: + workflow_dispatch: +jobs: + release: + name: Release + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v6 + with: + fetch-depth: 0 + - name: Setup Java + uses: actions/setup-java@v5 + with: + java-version: 25 + distribution: corretto + cache: gradle + cache-dependency-path: gradle/wrapper/gradle-wrapper.properties + - name: Gradle Build and Release + run: ./gradlew clean build publish jreleaserFullRelease --no-daemon --no-configuration-cache + env: + JRELEASER_GPG_PASSPHRASE: ${{ secrets.JRELEASER_GPG_PASSPHRASE }} + JRELEASER_GPG_PUBLIC_KEY: ${{ secrets.JRELEASER_GPG_PUBLIC_KEY }} + JRELEASER_GPG_SECRET_KEY: ${{ secrets.JRELEASER_GPG_SECRET_KEY }} + JRELEASER_MAVENCENTRAL_USERNAME: ${{ secrets.JRELEASER_MAVENCENTRAL_USERNAME }} + JRELEASER_MAVENCENTRAL_PASSWORD: ${{ secrets.JRELEASER_MAVENCENTRAL_PASSWORD }} + JRELEASER_GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} From c481f65d7b1ad2a444a314a70728b5f9c7da6fb6 Mon Sep 17 00:00:00 2001 From: Jacob Peterson <14127217+Petersoj@users.noreply.github.com> Date: Wed, 24 Jun 2026 21:26:19 -0600 Subject: [PATCH 249/271] Add `Accept-Encoding: gzip` request header in `openapi-generator-adapted` Gradle convention plugin --- buildSrc/src/main/kotlin/openapi-generator-adapted.gradle.kts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/buildSrc/src/main/kotlin/openapi-generator-adapted.gradle.kts b/buildSrc/src/main/kotlin/openapi-generator-adapted.gradle.kts index bd31d970..9c3b119b 100644 --- a/buildSrc/src/main/kotlin/openapi-generator-adapted.gradle.kts +++ b/buildSrc/src/main/kotlin/openapi-generator-adapted.gradle.kts @@ -69,6 +69,7 @@ tasks.openApiGenerate.configure { import java.net.http.HttpResponse; import java.util.function.Consumer; + import static com.google.common.net.HttpHeaders.ACCEPT_ENCODING; import static com.google.common.net.HttpHeaders.AUTHORIZATION; import static java.time.Duration.ofSeconds; import static net.jacobpeterson.alpacajava.common.AlpacaHeader.API_KEY_ID; @@ -156,6 +157,7 @@ tasks.openApiGenerate.configure { }; apiClient.setReadTimeout(ofSeconds(10)); apiClient.setRequestInterceptor(builder -> { + builder.header(ACCEPT_ENCODING, "gzip"); if (authenticationKeyID != null) { builder.header(API_KEY_ID, authenticationKeyID); } From 42f441c187e4eaadaa4befc36a81ad18ffe39b3f Mon Sep 17 00:00:00 2001 From: Jacob Peterson <14127217+Petersoj@users.noreply.github.com> Date: Fri, 26 Jun 2026 12:54:24 -0600 Subject: [PATCH 250/271] Fix `broker` module OpenAPI spec content type JSON for SSE --- module/broker/openapi.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/module/broker/openapi.json b/module/broker/openapi.json index 3e0955a8..c72d573f 100644 --- a/module/broker/openapi.json +++ b/module/broker/openapi.json @@ -17805,7 +17805,7 @@ "responses": { "200": { "content": { - "application/json": { + "text/event-stream": { "schema": { "$ref": "#/components/schemas/NonTradeActivityEvent" } From 979fac7b9b2db20619806e0e9be4cde95cc66d6b Mon Sep 17 00:00:00 2001 From: Jacob Peterson <14127217+Petersoj@users.noreply.github.com> Date: Fri, 26 Jun 2026 12:56:41 -0600 Subject: [PATCH 251/271] Add `SseResponse` in `common` module, support SSE in `openapi-generator-adapted` Gradle convention plugin --- .../openapi-generator-adapted.gradle.kts | 141 +++++++++++++++--- .../alpacajava/common/AlpacaHeader.java | 9 +- .../alpacajava/common/sse/SseListener.java | 15 ++ .../common/sse/SseListenerAdapter.java | 29 ++++ .../alpacajava/common/sse/SseResponse.java | 66 ++++++++ 5 files changed, 235 insertions(+), 25 deletions(-) create mode 100644 module/common/src/main/java/net/jacobpeterson/alpacajava/common/sse/SseListener.java create mode 100644 module/common/src/main/java/net/jacobpeterson/alpacajava/common/sse/SseListenerAdapter.java create mode 100644 module/common/src/main/java/net/jacobpeterson/alpacajava/common/sse/SseResponse.java diff --git a/buildSrc/src/main/kotlin/openapi-generator-adapted.gradle.kts b/buildSrc/src/main/kotlin/openapi-generator-adapted.gradle.kts index 9c3b119b..a4203387 100644 --- a/buildSrc/src/main/kotlin/openapi-generator-adapted.gradle.kts +++ b/buildSrc/src/main/kotlin/openapi-generator-adapted.gradle.kts @@ -1,9 +1,10 @@ import com.google.common.base.CaseFormat.LOWER_CAMEL import com.google.common.base.CaseFormat.LOWER_HYPHEN import com.google.common.base.CaseFormat.UPPER_CAMEL +import com.google.common.base.Splitter plugins { - java + `java-library` id("org.openapi.generator") } @@ -11,9 +12,9 @@ val jacksonVersion = "3.2.0" val jacksonAnnotationsVersion = "2.22" dependencies { - implementation("tools.jackson.core:jackson-core:$jacksonVersion") - implementation("tools.jackson.core:jackson-databind:$jacksonVersion") - implementation("com.fasterxml.jackson.core:jackson-annotations:$jacksonAnnotationsVersion") + api("tools.jackson.core:jackson-core:$jacksonVersion") + api("tools.jackson.core:jackson-databind:$jacksonVersion") + api("com.fasterxml.jackson.core:jackson-annotations:$jacksonAnnotationsVersion") } val extension = extensions.create("openApiGeneratorAdapted", OpenApiGeneratorAdaptedExtension::class) @@ -41,13 +42,106 @@ tasks.openApiGenerate.configure { val environmentUrlDevelopment = extension.environmentUrlDevelopment.get() doLast { val srcMainJava = outputDir.get().asFile.resolve("src/main/java/") - srcMainJava.walkTopDown().filter { it.isFile }.forEach { - it.writeText(it.readText() - .replace("@javax.annotation.Nullable", "@org.jspecify.annotations.Nullable") - .replace("@javax.annotation.Nonnull", "@org.jspecify.annotations.NonNull") - .replace("@javax.annotation.Generated", "@javax.annotation.processing.Generated")) + srcMainJava.walkTopDown().filter { it.isFile }.forEach { sourceFile -> + var source = sourceFile.readText() + .replaceFirst("\nimport", """ + import org.jspecify.annotations.*; + import net.jacobpeterson.alpacajava.common.sse.*; + import + """.trimIndent()) + .replace("@javax.annotation.Nullable", "@Nullable") + .replace("@javax.annotation.Nonnull", "@NonNull") + .replace("@javax.annotation.Generated", "@javax.annotation.processing.Generated") + .replace("if (memberVarResponseInterceptor != null) {\n " + + "memberVarResponseInterceptor.accept(localVarResponse);\n }\n " + + "InputStream localVarResponseBody = null;\n try {", + "InputStream localVarResponseBody = null;\n try {\n " + + "if (memberVarResponseInterceptor != null) {\n " + + "memberVarResponseInterceptor.accept(localVarResponse);\n }") + val headersCall = ".headers()" + source = source.replace("$headersCall.map()", headersCall) + val httpRequestNewBuilder = "HttpRequest.newBuilder()" + source = source.replace(httpRequestNewBuilder, + "$httpRequestNewBuilder.header(\"Accept-Encoding\", \"gzip\")") + var acceptCommaTextEventStreamIndex = 0 + while (source.indexOf("\"Accept\", \"text/event-stream\"", acceptCommaTextEventStreamIndex + 1) + .also { acceptCommaTextEventStreamIndex = it } != -1) { + val privateHttpRequestBuilder = "private HttpRequest.Builder " + val methodNameStartIndex = source.lastIndexOf(privateHttpRequestBuilder, + acceptCommaTextEventStreamIndex) + val methodName = source.substring(methodNameStartIndex + privateHttpRequestBuilder.length, + source.indexOf("RequestBuilder(", methodNameStartIndex)) + val methodIndex = source.indexOf(" $methodName(") + val methodJavadocAndAnnotations = source.substring(source.lastIndexOf("/**", methodIndex), + source.lastIndexOf("public", methodIndex)) + val methodLine = source.substring(source.lastIndexOf('\n', methodIndex) + 1, + source.indexOf('\n', methodIndex)) + val methodReturnType = Regex(" (?:List<)?([\\w_]*)>? $methodName").find(methodLine)!!.groupValues[1] + val methodArguments = Regex("$methodName\\((.*)\\)").find(methodLine)!!.groupValues[1] + val methodArgumentNames = Splitter.on(", ").split(methodArguments) + .joinToString { it.substring(it.lastIndexOf(' ')) } + val methodEndBrace = "\n }" + val methodRemove = IntRange(source.lastIndexOf("/**", methodIndex), + source.indexOf(methodEndBrace, source.indexOf(methodEndBrace, methodIndex) + 1) + + methodEndBrace.length) + source = source.removeRange(methodRemove) + acceptCommaTextEventStreamIndex -= methodRemove.last - methodRemove.first + val methodHttpInfoIndex = source.indexOf(" ${methodName}WithHttpInfo(") + val methodHttpInfoRemove = IntRange(source.lastIndexOf("/**", methodHttpInfoIndex), + source.indexOf(methodEndBrace, source.indexOf(methodEndBrace, methodHttpInfoIndex) + 1) + + methodEndBrace.length) + val sseMethods = """ + $methodJavadocAndAnnotations + public SseResponse $methodName($methodArguments, SseListener<$methodReturnType> sseListener) + throws ApiException { + return $methodName($methodArgumentNames, sseListener, null); + } + + $methodJavadocAndAnnotations + public SseResponse $methodName($methodArguments, SseListener<$methodReturnType> sseListener, + @Nullable Map headers) throws ApiException { + try { + var response = memberVarHttpClient.send( + ${methodName}RequestBuilder($methodArgumentNames, headers) + .setHeader("Accept-Encoding", "identity").build(), + HttpResponse.BodyHandlers.ofInputStream()); + try { + if (memberVarResponseInterceptor != null) { + memberVarResponseInterceptor.accept(response); + } + if (response.statusCode() / 100 != 2) { + throw getApiException("$methodName", response); + } + return new SseResponse(memberVarHttpClient, response, sseListener, s -> { + try { + return memberVarObjectMapper.readValue(s, new TypeReference<$methodReturnType>() {}); + } catch (JacksonException e) { + throw new ApiException(e); + } + }); + } catch (Throwable throwable) { + response.body().close(); + throw throwable; + } + } catch (IOException e) { + throw new ApiException(e); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + throw new ApiException(e); + } + } + """.trimIndent() + source = source.replaceRange(methodHttpInfoRemove, sseMethods) + acceptCommaTextEventStreamIndex -= methodHttpInfoRemove.last - methodHttpInfoRemove.first + acceptCommaTextEventStreamIndex += sseMethods.length + } + sourceFile.writeText(source) } val outputPackageDirectory = srcMainJava.resolve(outputPackageName.replace('.', '/')) + outputPackageDirectory.resolve("ApiException.java").apply { writeText(readText() + .replace("extends Exception {", "extends RuntimeException {")) } + outputPackageDirectory.resolve("ApiResponse.java").apply { writeText(readText() + .replace("Map>", "java.net.http.HttpHeaders")) } val apiClassNamesOfMethodNames = outputPackageDirectory.resolve(apiPackageName).list() .map { it.replace(".java", "") } .associateBy { UPPER_CAMEL.to(LOWER_CAMEL, it.substring(0, it.length - 3)) } @@ -62,6 +156,7 @@ tasks.openApiGenerate.configure { import org.jspecify.annotations.NullMarked; import org.jspecify.annotations.Nullable; import tools.jackson.databind.ObjectMapper; + import com.google.common.net.HttpHeaders; import java.io.InputStream; import java.net.http.HttpClient; @@ -69,12 +164,10 @@ tasks.openApiGenerate.configure { import java.net.http.HttpResponse; import java.util.function.Consumer; - import static com.google.common.net.HttpHeaders.ACCEPT_ENCODING; - import static com.google.common.net.HttpHeaders.AUTHORIZATION; import static java.time.Duration.ofSeconds; + import static java.util.concurrent.Executors.newVirtualThreadPerTaskExecutor; import static net.jacobpeterson.alpacajava.common.AlpacaHeader.API_KEY_ID; import static net.jacobpeterson.alpacajava.common.AlpacaHeader.API_SECRET_KEY; - import static net.jacobpeterson.alpacajava.common.AlpacaHeader.AUTHORIZATION_TOKEN_BEARER_PREFIX; import static net.jacobpeterson.alpacajava.common.ApiEnvironment.PRODUCTION; /** @@ -110,11 +203,10 @@ tasks.openApiGenerate.configure { /** * Calls {@link #${className}(HttpClient, ObjectMapper, String, String, String, ApiEnvironment, Consumer, Consumer)} - * with everything set to null except authorizationBearerToken and - * apiEnvironment. + * with everything set to null except authorizationToken and apiEnvironment. */ - public ${className}(final @Nullable String authorizationBearerToken, final @Nullable ApiEnvironment apiEnvironment) { - this(null, null, null, null, authorizationBearerToken, apiEnvironment, null, null); + public ${className}(final @Nullable String authorizationToken, final @Nullable ApiEnvironment apiEnvironment) { + this(null, null, null, null, authorizationToken, apiEnvironment, null, null); } /** @@ -124,7 +216,9 @@ tasks.openApiGenerate.configure { * @param objectMapper the {@link ObjectMapper}, or null to use a new default instance * @param authenticationKeyID the {@link AlpacaHeader#API_KEY_ID} value * @param authenticationSecretKey the {@link AlpacaHeader#API_SECRET_KEY} value - * @param authorizationBearerToken the {@link AlpacaHeader#AUTHORIZATION_TOKEN_BEARER_PREFIX} suffix value + * @param authorizationToken the {@link HttpHeader#AUTHORIZATION} value (should start with + * {@link AlpacaHeader#AUTHORIZATION_BASIC_PREFIX} or + * {@link AlpacaHeader#AUTHORIZATION_BEARER_PREFIX}) * @param apiEnvironment the {@link ApiEnvironment}. If {@link ApiEnvironment#PRODUCTION}, then * {@link #ENVIRONMENT_URL_PRODUCTION} is used. If * {@link ApiEnvironment#DEVELOPMENT}, then {@link #ENVIRONMENT_URL_DEVELOPMENT} @@ -135,11 +229,13 @@ tasks.openApiGenerate.configure { */ public ${className}(final @Nullable HttpClient httpClient, final @Nullable ObjectMapper objectMapper, final @Nullable String authenticationKeyID, final @Nullable String authenticationSecretKey, - final @Nullable String authorizationBearerToken, final @Nullable ApiEnvironment apiEnvironment, + final @Nullable String authorizationToken, final @Nullable ApiEnvironment apiEnvironment, final @Nullable Consumer requestInterceptor, final @Nullable Consumer> responseInterceptor) { - this.httpClient = httpClient != null ? httpClient : - HttpClient.newBuilder().connectTimeout(ofSeconds(10)).build(); + this.httpClient = httpClient != null ? httpClient : HttpClient.newBuilder() + .connectTimeout(ofSeconds(10)) + .executor(newVirtualThreadPerTaskExecutor()) + .build(); final var baseUri = apiEnvironment == PRODUCTION ? ENVIRONMENT_URL_PRODUCTION : ENVIRONMENT_URL_DEVELOPMENT; apiClient = new ApiClient(null, objectMapper != null ? objectMapper : ApiClient.createDefaultObjectMapper(), null) { @@ -157,15 +253,14 @@ tasks.openApiGenerate.configure { }; apiClient.setReadTimeout(ofSeconds(10)); apiClient.setRequestInterceptor(builder -> { - builder.header(ACCEPT_ENCODING, "gzip"); if (authenticationKeyID != null) { builder.header(API_KEY_ID, authenticationKeyID); } if (authenticationSecretKey != null) { builder.header(API_SECRET_KEY, authenticationSecretKey); } - if (authorizationBearerToken != null) { - builder.header(AUTHORIZATION, AUTHORIZATION_TOKEN_BEARER_PREFIX + authorizationBearerToken); + if (authorizationToken != null) { + builder.header(HttpHeaders.AUTHORIZATION, authorizationToken); } if (requestInterceptor != null) { requestInterceptor.accept(builder); diff --git a/module/common/src/main/java/net/jacobpeterson/alpacajava/common/AlpacaHeader.java b/module/common/src/main/java/net/jacobpeterson/alpacajava/common/AlpacaHeader.java index 7663fd18..23c1c2e2 100644 --- a/module/common/src/main/java/net/jacobpeterson/alpacajava/common/AlpacaHeader.java +++ b/module/common/src/main/java/net/jacobpeterson/alpacajava/common/AlpacaHeader.java @@ -20,9 +20,14 @@ public final class AlpacaHeader { public static final String API_SECRET_KEY = "APCA-API-SECRET-KEY"; /** - * The {@link HttpHeaders#AUTHORIZATION} token bearer prefix: "Bearer " + * The {@link HttpHeaders#AUTHORIZATION} basic prefix: "Basic " */ - public static final String AUTHORIZATION_TOKEN_BEARER_PREFIX = "Bearer "; + public static final String AUTHORIZATION_BASIC_PREFIX = "Basic "; + + /** + * The {@link HttpHeaders#AUTHORIZATION} bearer prefix: "Bearer " + */ + public static final String AUTHORIZATION_BEARER_PREFIX = "Bearer "; private AlpacaHeader() {} } diff --git a/module/common/src/main/java/net/jacobpeterson/alpacajava/common/sse/SseListener.java b/module/common/src/main/java/net/jacobpeterson/alpacajava/common/sse/SseListener.java new file mode 100644 index 00000000..55741db5 --- /dev/null +++ b/module/common/src/main/java/net/jacobpeterson/alpacajava/common/sse/SseListener.java @@ -0,0 +1,15 @@ +package net.jacobpeterson.alpacajava.common.sse; + +import org.jspecify.annotations.NullMarked; + +@NullMarked +public interface SseListener { + + void onData(final T data); + + void onComment(final String comment); + + void onClose(); + + void onError(final Throwable throwable); +} diff --git a/module/common/src/main/java/net/jacobpeterson/alpacajava/common/sse/SseListenerAdapter.java b/module/common/src/main/java/net/jacobpeterson/alpacajava/common/sse/SseListenerAdapter.java new file mode 100644 index 00000000..b123a7f8 --- /dev/null +++ b/module/common/src/main/java/net/jacobpeterson/alpacajava/common/sse/SseListenerAdapter.java @@ -0,0 +1,29 @@ +package net.jacobpeterson.alpacajava.common.sse; + +import lombok.extern.slf4j.Slf4j; +import org.jspecify.annotations.NullMarked; + +@Slf4j +@NullMarked +public class SseListenerAdapter implements SseListener { + + @Override + public void onData(final T data) { + LOGGER.info("SSE data: {}", data); + } + + @Override + public void onComment(final String comment) { + LOGGER.debug("SSE comment: {}", comment); + } + + @Override + public void onClose() { + LOGGER.info("SSE closed"); + } + + @Override + public void onError(final Throwable throwable) { + LOGGER.error("SSE error", throwable); + } +} diff --git a/module/common/src/main/java/net/jacobpeterson/alpacajava/common/sse/SseResponse.java b/module/common/src/main/java/net/jacobpeterson/alpacajava/common/sse/SseResponse.java new file mode 100644 index 00000000..49df3c55 --- /dev/null +++ b/module/common/src/main/java/net/jacobpeterson/alpacajava/common/sse/SseResponse.java @@ -0,0 +1,66 @@ +package net.jacobpeterson.alpacajava.common.sse; + +import lombok.Getter; +import lombok.RequiredArgsConstructor; +import org.jspecify.annotations.NullMarked; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.UncheckedIOException; +import java.net.http.HttpClient; +import java.net.http.HttpHeaders; +import java.net.http.HttpResponse; +import java.util.function.Function; + +import static java.nio.charset.StandardCharsets.UTF_8; + +@RequiredArgsConstructor +@NullMarked +public class SseResponse implements AutoCloseable { + + private static final String DATA_PREFIX = "data: "; + private static final String COMMENT_PREFIX = ": "; + + private final @Getter int statusCode; + private final @Getter HttpHeaders headers; + private final InputStream body; + private volatile boolean close; + + public SseResponse(final HttpClient httpClient, final HttpResponse response, + final SseListener sseListener, final Function dataFromJson) { + statusCode = response.statusCode(); + headers = response.headers(); + body = response.body(); + httpClient.executor().orElse(Thread::startVirtualThread).execute(() -> { + try (final var reader = new BufferedReader(new InputStreamReader(body, UTF_8))) { + String line; + while ((line = reader.readLine()) != null) { + if (line.startsWith(DATA_PREFIX)) { + sseListener.onData(dataFromJson.apply(line.substring(DATA_PREFIX.length()))); + } else if (!line.isBlank()) { + sseListener.onComment(line.startsWith(COMMENT_PREFIX) ? + line.substring(COMMENT_PREFIX.length()) : line); + } + } + } catch (final Throwable throwable) { + if (!close && !(throwable instanceof IOException)) { + sseListener.onError(throwable); + } + } finally { + sseListener.onClose(); + } + }); + } + + @Override + public void close() { + close = true; + try { + body.close(); + } catch (final IOException ioException) { + throw new UncheckedIOException(ioException); + } + } +} From 55981e8fe02f0277818fb5c3b2c605b1e405d040 Mon Sep 17 00:00:00 2001 From: Jacob Peterson <14127217+Petersoj@users.noreply.github.com> Date: Fri, 26 Jun 2026 15:34:55 -0600 Subject: [PATCH 252/271] Rename `AlpacaHeader` to `ApiHeader` in `common` module --- .../openapi-generator-adapted.gradle.kts | 22 +++++++++---------- .../alpacajava/common/ApiEnvironment.java | 2 +- .../{AlpacaHeader.java => ApiHeader.java} | 6 ++--- 3 files changed, 14 insertions(+), 16 deletions(-) rename module/common/src/main/java/net/jacobpeterson/alpacajava/common/{AlpacaHeader.java => ApiHeader.java} (85%) diff --git a/buildSrc/src/main/kotlin/openapi-generator-adapted.gradle.kts b/buildSrc/src/main/kotlin/openapi-generator-adapted.gradle.kts index a4203387..ea1c2f77 100644 --- a/buildSrc/src/main/kotlin/openapi-generator-adapted.gradle.kts +++ b/buildSrc/src/main/kotlin/openapi-generator-adapted.gradle.kts @@ -150,7 +150,7 @@ tasks.openApiGenerate.configure { package ${outputPackageName}; import com.google.errorprone.annotations.concurrent.LazyInit; - import net.jacobpeterson.alpacajava.common.AlpacaHeader; + import net.jacobpeterson.alpacajava.common.ApiHeader; import net.jacobpeterson.alpacajava.common.ApiEnvironment; import ${outputPackageName}.${apiPackageName}.*; import org.jspecify.annotations.NullMarked; @@ -166,9 +166,6 @@ tasks.openApiGenerate.configure { import static java.time.Duration.ofSeconds; import static java.util.concurrent.Executors.newVirtualThreadPerTaskExecutor; - import static net.jacobpeterson.alpacajava.common.AlpacaHeader.API_KEY_ID; - import static net.jacobpeterson.alpacajava.common.AlpacaHeader.API_SECRET_KEY; - import static net.jacobpeterson.alpacajava.common.ApiEnvironment.PRODUCTION; /** * {@link ${className}}: $projectDescription @@ -214,11 +211,11 @@ tasks.openApiGenerate.configure { * * @param httpClient the {@link HttpClient}, or null to use a new default instance * @param objectMapper the {@link ObjectMapper}, or null to use a new default instance - * @param authenticationKeyID the {@link AlpacaHeader#API_KEY_ID} value - * @param authenticationSecretKey the {@link AlpacaHeader#API_SECRET_KEY} value - * @param authorizationToken the {@link HttpHeader#AUTHORIZATION} value (should start with - * {@link AlpacaHeader#AUTHORIZATION_BASIC_PREFIX} or - * {@link AlpacaHeader#AUTHORIZATION_BEARER_PREFIX}) + * @param authenticationKeyID the {@link ApiHeader#API_KEY_ID} value + * @param authenticationSecretKey the {@link ApiHeader#API_SECRET_KEY} value + * @param authorizationToken the {@link HttpHeaders#AUTHORIZATION} value (should start with + * {@link ApiHeader#AUTHORIZATION_BASIC_PREFIX} or + * {@link ApiHeader#AUTHORIZATION_BEARER_PREFIX}) * @param apiEnvironment the {@link ApiEnvironment}. If {@link ApiEnvironment#PRODUCTION}, then * {@link #ENVIRONMENT_URL_PRODUCTION} is used. If * {@link ApiEnvironment#DEVELOPMENT}, then {@link #ENVIRONMENT_URL_DEVELOPMENT} @@ -236,7 +233,8 @@ tasks.openApiGenerate.configure { .connectTimeout(ofSeconds(10)) .executor(newVirtualThreadPerTaskExecutor()) .build(); - final var baseUri = apiEnvironment == PRODUCTION ? ENVIRONMENT_URL_PRODUCTION : ENVIRONMENT_URL_DEVELOPMENT; + final var baseUri = apiEnvironment == ApiEnvironment.PRODUCTION ? ENVIRONMENT_URL_PRODUCTION : + ENVIRONMENT_URL_DEVELOPMENT; apiClient = new ApiClient(null, objectMapper != null ? objectMapper : ApiClient.createDefaultObjectMapper(), null) { @@ -254,10 +252,10 @@ tasks.openApiGenerate.configure { apiClient.setReadTimeout(ofSeconds(10)); apiClient.setRequestInterceptor(builder -> { if (authenticationKeyID != null) { - builder.header(API_KEY_ID, authenticationKeyID); + builder.header(ApiHeader.API_KEY_ID, authenticationKeyID); } if (authenticationSecretKey != null) { - builder.header(API_SECRET_KEY, authenticationSecretKey); + builder.header(ApiHeader.API_SECRET_KEY, authenticationSecretKey); } if (authorizationToken != null) { builder.header(HttpHeaders.AUTHORIZATION, authorizationToken); diff --git a/module/common/src/main/java/net/jacobpeterson/alpacajava/common/ApiEnvironment.java b/module/common/src/main/java/net/jacobpeterson/alpacajava/common/ApiEnvironment.java index 59a20bc0..417fd8ee 100644 --- a/module/common/src/main/java/net/jacobpeterson/alpacajava/common/ApiEnvironment.java +++ b/module/common/src/main/java/net/jacobpeterson/alpacajava/common/ApiEnvironment.java @@ -3,7 +3,7 @@ import org.jspecify.annotations.NullMarked; /** - * {@link ApiEnvironment} is an enum that represents the API environment to use. + * {@link ApiEnvironment} is an enum that represents an API environment. */ @NullMarked public enum ApiEnvironment { diff --git a/module/common/src/main/java/net/jacobpeterson/alpacajava/common/AlpacaHeader.java b/module/common/src/main/java/net/jacobpeterson/alpacajava/common/ApiHeader.java similarity index 85% rename from module/common/src/main/java/net/jacobpeterson/alpacajava/common/AlpacaHeader.java rename to module/common/src/main/java/net/jacobpeterson/alpacajava/common/ApiHeader.java index 23c1c2e2..83bc03ca 100644 --- a/module/common/src/main/java/net/jacobpeterson/alpacajava/common/AlpacaHeader.java +++ b/module/common/src/main/java/net/jacobpeterson/alpacajava/common/ApiHeader.java @@ -4,10 +4,10 @@ import org.jspecify.annotations.NullMarked; /** - * {@link AlpacaHeader} contains Alpaca header {@link String} constants. + * {@link ApiHeader} contains API header {@link String} constants. */ @NullMarked -public final class AlpacaHeader { +public final class ApiHeader { /** * The API Key ID header {@link String}: "APCA-API-KEY-ID" @@ -29,5 +29,5 @@ public final class AlpacaHeader { */ public static final String AUTHORIZATION_BEARER_PREFIX = "Bearer "; - private AlpacaHeader() {} + private ApiHeader() {} } From ea19aaddcbcf028aa05b048594118a817eca8246 Mon Sep 17 00:00:00 2001 From: Jacob Peterson <14127217+Petersoj@users.noreply.github.com> Date: Sat, 27 Jun 2026 11:44:27 -0600 Subject: [PATCH 253/271] Add `BrokerApiUtil.getAuthorizationToken()` in `broker` module --- .../alpacajava/broker/BrokerApiUtil.java | 20 +++++++++++++++++++ 1 file changed, 20 insertions(+) create mode 100644 module/broker/src/main/java/net/jacobpeterson/alpacajava/broker/BrokerApiUtil.java diff --git a/module/broker/src/main/java/net/jacobpeterson/alpacajava/broker/BrokerApiUtil.java b/module/broker/src/main/java/net/jacobpeterson/alpacajava/broker/BrokerApiUtil.java new file mode 100644 index 00000000..29fc9fb7 --- /dev/null +++ b/module/broker/src/main/java/net/jacobpeterson/alpacajava/broker/BrokerApiUtil.java @@ -0,0 +1,20 @@ +package net.jacobpeterson.alpacajava.broker; + +import org.jspecify.annotations.NullMarked; + +import java.util.Base64; + +@NullMarked +public final class BrokerApiUtil { + + /** + * @return {@link Base64.Encoder#encodeToString(byte[])} apiKey + ":" + apiSecret + * + * @see docs.alpaca.markets + */ + public static String getAuthorizationToken(final String apiKey, final String apiSecret) { + return Base64.getEncoder().encodeToString((apiKey + ":" + apiSecret).getBytes()); + } + + private BrokerApiUtil() {} +} From f2ec2336f3d1835da8dbfebb232e3ba1d4b90158 Mon Sep 17 00:00:00 2001 From: Jacob Peterson <14127217+Petersoj@users.noreply.github.com> Date: Tue, 7 Jul 2026 10:13:44 -0600 Subject: [PATCH 254/271] Update OpenAPI specification JSON files --- module/broker/openapi.json | 434 ++++++++---------------------------- module/trading/openapi.json | 253 +++++++-------------- 2 files changed, 175 insertions(+), 512 deletions(-) diff --git a/module/broker/openapi.json b/module/broker/openapi.json index c72d573f..3db61c8e 100644 --- a/module/broker/openapi.json +++ b/module/broker/openapi.json @@ -1357,22 +1357,6 @@ "description": "If true, overnight trading is disabled.", "type": "boolean" }, - "dtbp_check": { - "deprecated": true, - "description": "both, entry, or exit. Controls Day Trading Margin Call (DTMC) checks.", - "enum": [ - "both", - "entry", - "exit" - ], - "example": "both", - "type": "string", - "x-deprecation": { - "reason": "See https://docs.alpaca.markets/us/docs/understanding-finras-new-intraday-margin-rule-and-the-end-of-pdt", - "since": "2026-04-27T00:00:00Z", - "sunset": "2026-07-06T00:00:00Z" - } - }, "fractional_trading": { "description": "If true, account is able to participate in fractional trading", "type": "boolean" @@ -1395,16 +1379,6 @@ "description": "If true, account becomes long-only mode.", "type": "boolean" }, - "pdt_check": { - "deprecated": true, - "example": "entry", - "type": "string", - "x-deprecation": { - "reason": "See https://docs.alpaca.markets/us/docs/understanding-finras-new-intraday-margin-rule-and-the-end-of-pdt", - "since": "2026-04-27T00:00:00Z", - "sunset": "2026-07-06T00:00:00Z" - } - }, "ptp_no_exception_entry": { "description": "If set to true then Alpaca will accept orders for PTP symbols with no exception. Default is false.", "type": "string" @@ -2012,7 +1986,7 @@ "type": "string" }, "admin_configurations": { - "$ref": "#/components/schemas/AdminConfigurations" + "$ref": "#/components/schemas/AdminConfigurationsEvent" }, "at": { "description": "timestamp of event", @@ -2048,16 +2022,6 @@ "options": { "$ref": "#/components/schemas/OptionsApprovalEvent" }, - "pattern_day_trader": { - "deprecated": true, - "description": "If true the pattern_day_trader flag was set for the account, if false, the flag was reset.", - "type": "boolean", - "x-deprecation": { - "reason": "See https://docs.alpaca.markets/us/docs/understanding-finras-new-intraday-margin-rule-and-the-end-of-pdt", - "since": "2026-04-27T00:00:00Z", - "sunset": "2026-07-06T00:00:00Z" - } - }, "reason": { "deprecated": true, "minLength": 1, @@ -2923,7 +2887,7 @@ ] }, "AdminConfigurations": { - "description": "These configurations show account properties that are overriden either by Alpaca Broker Operations or an automated process.\n\nThese values cannot be modified by the Broker Partners.\n\n\nFor the **events** interface we are only broadcasting changes to admin configurations. In case nothing changed for a flag that will not be included in unrelated update events.\n\nDepending on the type of the Admin Configurations the sent event will behave differently. For bool flags we are only sending the new value.\n\nFor example the following payload means, that the disable_shorting flag was set to true from false:\n\n```\n{\n \"disable_shorting\": false\n}\n```\n\nFor other data types, we are embeding the old and new values into the payload. For example changing the max_margin_multiplier from 4 to 1 will yield this payload:\n\n```\n{\n \"max_margin_multiplier\": {\n \"old\": 4,\n \"new\": 1,\n }\n}\n```\n\nIntroducing an override value from the default will yield a null value as old. For example restricting the max_margin_multipler to 1 from default will yield the following payload:\n\n```\n{\n \"max_margin_multiplier\": {\n \"old\": null,\n \"new\": 1,\n }\n}\n```", + "description": "These configurations show account properties that are overriden either by Alpaca Broker Operations or an automated process.\n\nThese values cannot be modified by the Broker Partners.\n\nThis schema represents the current, effective value of each configuration (e.g. as returned by the get trading account endpoint). For the shape of the payloads broadcast by the **events** streaming API when these values change, see [AdminConfigurationsEvent](#/components/schemas/AdminConfigurationsEvent).", "properties": { "acct_daily_transfer_limit": { "description": "Override the correspondent level daily transfer limits", @@ -2963,7 +2927,81 @@ "type": "boolean" }, "max_margin_multiplier": { - "description": "Max margin multipler is set by admin to this value", + "description": "The max margin multiplier set by admin for this account. Can be \"1\", \"2\", or \"4\".", + "type": "string" + }, + "max_options_trading_level": { + "description": "The max options trading level set by admin for this account.\n0=disabled, 1=Covered Call/Cash-Secured Put, 2=Long Call/Put, 3=Spreads/Straddles.\n", + "enum": [ + 0, + 1, + 2, + 3 + ], + "type": "integer" + }, + "outgoing_transfers_blocked": { + "description": "If true, outgoing transfers from this account are rejected", + "type": "boolean" + }, + "restrict_to_liquidation_reasons": { + "$ref": "#/components/schemas/RestrictToLiquidationReasons" + } + }, + "title": "AdminConfigurations", + "type": "object" + }, + "AdminConfigurationsEvent": { + "description": "Represents a change to admin configurations, as broadcast over the **events** streaming API. Only fields whose value changed are included; unrelated update events will not include a flag that did not change.\n\nDepending on the type of the Admin Configuration, the sent event will behave differently. For bool flags we are only sending the new value.\n\nFor example, the following payload means that the disable_shorting flag was set to true:\n\n```\n{\n \"disable_shorting\": true\n}\n```\n\nFor other data types, we are embedding the old and new values into the payload. For example changing the max_margin_multiplier from 4 to 1 will yield this payload:\n\n```\n{\n \"max_margin_multiplier\": {\n \"from\": 4,\n \"to\": 1\n }\n}\n```\n\nIntroducing an override value from the default will yield a null value as `from`. For example restricting the max_margin_multiplier to 1 from default will yield the following payload:\n\n```\n{\n \"max_margin_multiplier\": {\n \"from\": null,\n \"to\": 1\n }\n}\n```", + "properties": { + "acct_daily_transfer_limit": { + "description": "The correspondent level daily transfer limit override was changed", + "properties": { + "from": { + "description": "Old value of the daily transfer limit", + "type": "string" + }, + "to": { + "description": "New value of the daily transfer limit", + "type": "string" + } + }, + "type": "object" + }, + "allow_instant_ach": { + "description": "If true, the account is allowed to perform instant ACH", + "type": "boolean" + }, + "disable_algodash_access": { + "description": "If true, the account is allowed to access algo dash", + "type": "boolean" + }, + "disable_api_key": { + "description": "If true, the account's API key will be disabled", + "type": "boolean" + }, + "disable_crypto": { + "description": "If true, the account is not allowed to trade cryptos", + "type": "boolean" + }, + "disable_day_trading": { + "description": "If true, the account is not allowed to day trade (e.g. buy and sell the same security on the same day)", + "type": "boolean" + }, + "disable_fractional": { + "description": "If true, the account cannot create orders for fractional share positions", + "type": "boolean" + }, + "disable_shorting": { + "description": "If true the account is not allowed to create short position orders", + "type": "boolean" + }, + "incoming_transfers_blocked": { + "description": "If true, incoming transfers to this account are rejected", + "type": "boolean" + }, + "max_margin_multiplier": { + "description": "Max margin multiplier was changed by admin to this value", "properties": { "from": { "description": "Old value of margin multiplier", @@ -2977,10 +3015,10 @@ "type": "object" }, "max_options_trading_level": { - "description": "Max options trading level is set by admin to this value. It can be 0, 1 or 2.", + "description": "Max options trading level was changed by admin to this value. It can be 0, 1, 2, or 3.", "properties": { "from": { - "description": "New value of max options trading level", + "description": "Old value of max options trading level", "type": "string" }, "to": { @@ -2994,21 +3032,11 @@ "description": "If true, outgoing transfers from this account are rejected", "type": "boolean" }, - "pdt_check_mode": { - "deprecated": true, - "description": "PDT check mode", - "type": "string", - "x-deprecation": { - "reason": "See https://docs.alpaca.markets/us/docs/understanding-finras-new-intraday-margin-rule-and-the-end-of-pdt", - "since": "2026-04-27T00:00:00Z", - "sunset": "2026-07-06T00:00:00Z" - } - }, "restrict_to_liquidation_reasons": { "$ref": "#/components/schemas/RestrictToLiquidationReasons" } }, - "title": "AdminConfigurations", + "title": "AdminConfigurationsEvent", "type": "object" }, "AggregatePositionResponse": { @@ -3416,30 +3444,6 @@ "AssetEntryRequirements": { "description": "Defines the necessary conditions that must be met to initiate a position for a specific asset", "properties": { - "dtbp_long": { - "deprecated": true, - "description": "The percentage of the asset's market value required as Day Trading (4x) buying power to open a long position", - "example": "0.25", - "format": "decimal", - "type": "string", - "x-deprecation": { - "reason": "See https://docs.alpaca.markets/us/docs/understanding-finras-new-intraday-margin-rule-and-the-end-of-pdt", - "since": "2026-04-27T00:00:00Z", - "sunset": "2026-07-06T00:00:00Z" - } - }, - "dtbp_short": { - "deprecated": true, - "description": "The percentage of the asset's market value required as Day Trading (4x) buying power to open a short position", - "example": "0.3", - "format": "decimal", - "type": "string", - "x-deprecation": { - "reason": "See https://docs.alpaca.markets/us/docs/understanding-finras-new-intraday-margin-rule-and-the-end-of-pdt", - "since": "2026-04-27T00:00:00Z", - "sunset": "2026-07-06T00:00:00Z" - } - }, "regt_long": { "description": "The percentage of the asset's market value required as Reg T (2x) buying power to open a long position", "example": "0.5", @@ -7650,11 +7654,12 @@ "type": "string" }, "JournalStatus": { - "description": "Represents the status that a Journal instance can be in.\n\n**Current Values**\n\nqueued\tJournal in queue to be processed. Journal is not processed yet.\n\nsent_to_clearing\tJournal sent to be processed by Alpaca's booking system. The journal is not processed yet.\n\npending\t Journal pending to be processed as it requires manual approval from Alpaca operations (for example due to hitting JNLC daily limits).\n\nexecuted\tJournal executed and balances updated for both sides of \nthe journal transaction. This is not a final status, journals can be reversed if there is an error.\n\nrejected\tJournal rejected. Please try again.\n\ncanceled\tJournal canceled. This is a **FINAL** status.\n\nrefused\tJournal refused. Please try again.\n\ndeleted\tJournal deleted. This is a **FINAL** status.\n\ncorrect\tJournal is corrected. Previously executed journal is cancelled and a new journal is corrected amount is created. This is a **FINAL** status.", + "description": "Represents the status that a Journal instance can be in.\n\n**Current Values**\n\nqueued\tJournal in queue to be processed. Journal is not processed yet.\n\nsent_to_clearing\tJournal sent to be processed by Alpaca's booking system. The journal is not processed yet.\n\npending\t Journal pending to be processed as it requires manual approval from Alpaca operations (for example due to hitting JNLC daily limits).\n\nexecuted\tJournal executed and balances updated for both sides of \nthe journal transaction. This is not a final status, journals can be reversed if there is an error.\n\nactivity_created\tNon-trade activity has been created for journal (JNLC v2-only).\n\nrejected\tJournal rejected. Please try again.\n\ncanceled\tJournal canceled. This is a **FINAL** status.\n\nrefused\tJournal refused. Please try again.\n\ndeleted\tJournal deleted. This is a **FINAL** status.\n\ncorrect\tJournal is corrected. Previously executed journal is cancelled and a new journal is corrected amount is created. This is a **FINAL** status.", "enum": [ "pending", "canceled", "executed", + "activity_created", "queued", "rejected", "deleted", @@ -9539,81 +9544,6 @@ } } }, - "PDTRemoval": { - "deprecated": true, - "properties": { - "account_id": { - "description": "Account ID", - "example": "93f58d0b-6c53-432d-b8ce-2bad264dbd94", - "format": "uuid", - "type": "string" - }, - "created_at": { - "description": "The request date and time", - "format": "date-time", - "type": "string" - }, - "id": { - "description": "Unique ID", - "example": "52f8574c-96d5-49b6-94c1-2570a268434e", - "format": "uuid", - "type": "string" - }, - "reason": { - "description": "Reason of rejection", - "example": "account is already approved once", - "type": "string" - }, - "status": { - "description": "The request status", - "enum": [ - "approved", - "rejected" - ], - "example": "rejected", - "type": "string" - } - }, - "type": "object", - "x-deprecation": { - "reason": "See https://docs.alpaca.markets/us/docs/understanding-finras-new-intraday-margin-rule-and-the-end-of-pdt", - "since": "2026-04-27T00:00:00Z", - "sunset": "2026-07-06T00:00:00Z" - } - }, - "PDTStatus": { - "deprecated": true, - "properties": { - "account_id": { - "description": "Account ID", - "example": "93f58d0b-6c53-432d-b8ce-2bad264dbd94", - "format": "uuid", - "type": "string" - }, - "pdt": { - "description": "Account is currently flagged as Pattern Day Trader", - "example": true, - "type": "boolean" - }, - "pdt_removed": { - "description": "The account has previously utilized its one-time PDT removal entitlement, and is not allowed to submit any further removal requests", - "example": true, - "type": "boolean" - }, - "pdt_removed_at": { - "description": "Date and time when PDT was removed for the account. Null if PDT flag has never been lifted.", - "format": "date-time", - "nullable": true, - "type": "string" - } - }, - "type": "object", - "x-deprecation": { - "reason": "See https://docs.alpaca.markets/us/docs/understanding-finras-new-intraday-margin-rule-and-the-end-of-pdt", - "since": "2026-04-27T00:00:00Z", - "sunset": "2026-07-06T00:00:00Z" - } - }, "Portfolio": { "properties": { "cooldown_days": { @@ -10374,16 +10304,6 @@ "description": "Set when an incoming ACH transfer gets rejected", "type": "boolean" }, - "pattern_day_trading": { - "deprecated": true, - "description": "Set when the trading account is marked as a PDT, but its equity falls below the $25k treshold", - "type": "boolean", - "x-deprecation": { - "reason": "See https://docs.alpaca.markets/us/docs/understanding-finras-new-intraday-margin-rule-and-the-end-of-pdt", - "since": "2026-04-27T00:00:00Z", - "sunset": "2026-07-06T00:00:00Z" - } - }, "position_to_equity_ratio": { "description": "Set when the position to equity ration exceeds the maximum limit", "type": "boolean" @@ -11398,18 +11318,6 @@ "example": "2021-04-01", "type": "string" }, - "bod_dtbp": { - "deprecated": true, - "description": "bod_dtbp", - "example": "12345.6789", - "format": "decimal", - "type": "string", - "x-deprecation": { - "reason": "See https://docs.alpaca.markets/us/docs/understanding-finras-new-intraday-margin-rule-and-the-end-of-pdt", - "since": "2026-04-27T00:00:00Z", - "sunset": "2026-07-06T00:00:00Z" - } - }, "buying_power": { "description": "Current available cash buying power. If multiplier = 2 then buying_power = max(equity-initial_margin(0) * 2). If multiplier = 1 then buying_power = cash.", "example": "12345.6789", @@ -11451,29 +11359,6 @@ "example": "USD", "type": "string" }, - "daytrade_count": { - "deprecated": true, - "description": "The current number of daytrades that have been made in the last 5 trading days (inclusive of today)", - "example": 0, - "type": "integer", - "x-deprecation": { - "reason": "See https://docs.alpaca.markets/us/docs/understanding-finras-new-intraday-margin-rule-and-the-end-of-pdt", - "since": "2026-04-27T00:00:00Z", - "sunset": "2026-07-06T00:00:00Z" - } - }, - "daytrading_buying_power": { - "deprecated": true, - "description": "Your buying power for day trades (continuously updated value)", - "example": "12345.6789", - "format": "decimal", - "type": "string", - "x-deprecation": { - "reason": "See https://docs.alpaca.markets/us/docs/understanding-finras-new-intraday-margin-rule-and-the-end-of-pdt", - "since": "2026-04-27T00:00:00Z", - "sunset": "2026-07-06T00:00:00Z" - } - }, "effective_buying_power": { "description": "Effective buying power (duplicate of buying power)", "example": "12345.6789", @@ -11513,28 +11398,6 @@ "example": "12345.6789", "type": "string" }, - "last_daytrade_count": { - "deprecated": true, - "description": "Value of daytrade count as of previous trading day at 16:00:00 ET", - "example": 0, - "type": "integer", - "x-deprecation": { - "reason": "See https://docs.alpaca.markets/us/docs/understanding-finras-new-intraday-margin-rule-and-the-end-of-pdt", - "since": "2026-04-27T00:00:00Z", - "sunset": "2026-07-06T00:00:00Z" - } - }, - "last_daytrading_buying_power": { - "deprecated": true, - "description": "Value of daytrading buying power as of previous trading day at 16:00:00 ET", - "example": "12345.6789", - "type": "string", - "x-deprecation": { - "reason": "See https://docs.alpaca.markets/us/docs/understanding-finras-new-intraday-margin-rule-and-the-end-of-pdt", - "since": "2026-04-27T00:00:00Z", - "sunset": "2026-07-06T00:00:00Z" - } - }, "last_equity": { "description": "Equity as of previous trading day at 16:00:00 ET", "example": "12345.6789", @@ -11629,17 +11492,6 @@ "example": 3, "type": "integer" }, - "pattern_day_trader": { - "deprecated": true, - "description": "Whether account is flagged as pattern day trader or not", - "example": false, - "type": "boolean", - "x-deprecation": { - "reason": "See https://docs.alpaca.markets/us/docs/understanding-finras-new-intraday-margin-rule-and-the-end-of-pdt", - "since": "2026-04-27T00:00:00Z", - "sunset": "2026-07-06T00:00:00Z" - } - }, "pending_reg_taf_fees": { "description": "Pending regulatory fees for the account.", "example": "0.01", @@ -11709,7 +11561,14 @@ "type": "boolean" }, "user_configurations": { - "$ref": "#/components/schemas/AccountConfigurations" + "allOf": [ + { + "$ref": "#/components/schemas/AccountConfigurations" + } + ], + "description": "Custom user-level configuration overrides for this account.\n\nThis field is always present in the response, but its value will be `null` until user-level configurations have been explicitly set for the account.", + "nullable": true, + "type": "object" } }, "required": [ @@ -11722,13 +11581,10 @@ "currency", "buying_power", "regt_buying_power", - "daytrading_buying_power", "effective_buying_power", "non_marginable_buying_power", - "bod_dtbp", "accrued_fees", "portfolio_value", - "pattern_day_trader", "trading_blocked", "transfers_blocked", "account_blocked", @@ -11745,7 +11601,6 @@ "maintenance_margin", "last_maintenance_margin", "sma", - "daytrade_count", "balance_asof", "cash" ], @@ -11763,16 +11618,12 @@ "clearing_broker": "VELOX", "created_at": "2021-03-01T13:28:49.270232Z", "currency": "USD", - "daytrade_count": 0, - "daytrading_buying_power": "103556.8572572922", "equity": "28059.3882330664", "id": "c8f1ef5d-edc0-4f23-9ee4-378f19cb92a4", "initial_margin": "1598.7391165332", "intraday_adjustments": "0", "last_buying_power": "104433.9158860662", "last_cash": "23861.91", - "last_daytrade_count": 0, - "last_daytrading_buying_power": "104433.9158860662", "last_equity": "26977.323677655", "last_initial_margin": "1557.7068388275", "last_long_market_value": "3115.413677655", @@ -11786,7 +11637,6 @@ "options_approved_level": 0, "options_buying_power": "26460.65", "options_trading_level": 0, - "pattern_day_trader": true, "pending_reg_taf_fees": "0", "pending_transfer_out": "0", "portfolio_value": "28059.3882330664", @@ -11813,16 +11663,12 @@ "created_at": "2022-01-21T21:25:26.713802Z", "crypto_status": "PAPER_ONLY", "currency": "USD", - "daytrade_count": 0, - "daytrading_buying_power": "0", "equity": "83567.42", "id": "56712986-9ff7-4d8f-8e52-077e099e533e", "initial_margin": "0", "intraday_adjustments": "0", "last_buying_power": "41783.71", "last_cash": "41783.71", - "last_daytrade_count": 0, - "last_daytrading_buying_power": "0", "last_equity": "41783.71", "last_initial_margin": "0", "last_long_market_value": "0", @@ -11837,7 +11683,6 @@ "options_approved_level": 2, "options_buying_power": "83567.42", "options_trading_level": 1, - "pattern_day_trader": false, "pending_reg_taf_fees": "0.01", "pending_transfer_in": "0", "pending_transfer_out": "0", @@ -17562,7 +17407,7 @@ }, "/v1/events/accounts/status": { "get": { - "description": "The accounts events API provides streaming of account changes as they occur, via SSE (server sent events). Past events can also be queried.\n\nEvents are generated for changes to the following account properties:\n- account_blocked\n- admin_configurations\n- cash_interest\n- crypto_status\n- kyc_results\n- options\n- pattern_day_trader\n- status\n- trading_blocked\n\nOnly the changed properties are included in the event payload.\n\nQuery Parameter Rules:\n- `since` is required if `until` specified\n- `since_id` is required if `until_id` specified\n- `since_ulid` is required if `until_ulid` specified\n- `since`, `since_id` and `since_ulid` can't be used at the same time\n\nBehavior:\nThis API supports querying a range of events, starting now or in the past. If the end of the range is in the future or not specified, the connection is kept open and future events are pushed.\n\nTo be specific:\n- if `since`, `since_id` or `since_ulid` is not specified, this will not return any historic data\n- if `until`, `until_id` or `until_ulid` is reached, the stream will end with a status of 200\n\n---\n\nNote for people using the clients generated from this OAS spec. Currently OAS-3 doesn't have full support for representing SSE style responses from an API, so if you are using a generated client and don't specify a `since` and `until` there is a good chance the generated clients will hang waiting for the response to end.\n\nIf you require the streaming capabilities we recommend not using the generated clients for this specific usecase until the OAS-3 standards come to a consensus on how to represent this correctly in OAS-3.\n", + "description": "The accounts events API provides streaming of account changes as they occur, via SSE (server sent events). Past events can also be queried.\n\nEvents are generated for changes to the following account properties:\n- account_blocked\n- admin_configurations\n- cash_interest\n- crypto_status\n- kyc_results\n- options\n- status\n- trading_blocked\n\nOnly the changed properties are included in the event payload.\n\nQuery Parameter Rules:\n- `since` is required if `until` specified\n- `since_id` is required if `until_id` specified\n- `since_ulid` is required if `until_ulid` specified\n- `since`, `since_id` and `since_ulid` can't be used at the same time\n\nBehavior:\nThis API supports querying a range of events, starting now or in the past. If the end of the range is in the future or not specified, the connection is kept open and future events are pushed.\n\nTo be specific:\n- if `since`, `since_id` or `since_ulid` is not specified, this will not return any historic data\n- if `until`, `until_id` or `until_ulid` is reached, the stream will end with a status of 200\n\n---\n\nNote for people using the clients generated from this OAS spec. Currently OAS-3 doesn't have full support for representing SSE style responses from an API, so if you are using a generated client and don't specify a `since` and `until` there is a good chance the generated clients will hang waiting for the response to end.\n\nIf you require the streaming capabilities we recommend not using the generated clients for this specific usecase until the OAS-3 standards come to a consensus on how to represent this correctly in OAS-3.\n", "operationId": "suscribeToAccountStatusSSE", "parameters": [ { @@ -19031,18 +18876,7 @@ "in": "query", "name": "status", "schema": { - "enum": [ - "pending", - "canceled", - "executed", - "queued", - "rejected", - "deleted", - "refused", - "sent_to_clearing", - "correct" - ], - "type": "string" + "$ref": "#/components/schemas/JournalStatus" } }, { @@ -20705,90 +20539,6 @@ ] } }, - "/v1/trading/accounts/{account_id}/account/pdt/one-time-removal": { - "parameters": [ - { - "$ref": "#/components/parameters/AccountID" - } - ], - "post": { - "deprecated": true, - "description": "**Deprecation notice**\n\nFINRA's revised Rule 4210 replaces the legacy Pattern Day Trading (PDT) framework with an intraday margin regime, so this endpoint no longer reflects current regulatory behavior. See https://docs.alpaca.markets/us/docs/understanding-finras-new-intraday-margin-rule-and-the-end-of-pdt\n\nThis endpoint will be sunset July 6, 2026. Please remove calls to PDT-specific endpoints before this date.\n\n---\n\nRequests the exercise of pattern day trader one time removal for given account", - "operationId": "pdtOneTimeRemoval", - "responses": { - "200": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/PDTRemoval" - } - } - }, - "description": "Response to PDT one time removal request" - }, - "403": { - "content": { - "application/json": { - "examples": { - "Account is not PDT": { - "value": { - "code": 40310000, - "message": "account is not PDT" - } - } - }, - "schema": { - "$ref": "#/components/schemas/Error" - } - } - }, - "description": "Forbidden\n\nAccount is not flagged as PDT." - } - }, - "summary": "Exercise PDT one time removal", - "tags": [ - "Accounts" - ], - "x-deprecation": { - "reason": "See https://docs.alpaca.markets/us/docs/understanding-finras-new-intraday-margin-rule-and-the-end-of-pdt", - "since": "2026-04-27T00:00:00Z", - "sunset": "2026-07-06T00:00:00Z" - } - } - }, - "/v1/trading/accounts/{account_id}/account/pdt/status": { - "get": { - "deprecated": true, - "description": "**Deprecation notice**\n\nFINRA's revised Rule 4210 replaces the legacy Pattern Day Trading (PDT) framework with an intraday margin regime, so this endpoint no longer reflects current regulatory behavior. See https://docs.alpaca.markets/us/docs/understanding-finras-new-intraday-margin-rule-and-the-end-of-pdt\n\nThis endpoint will be sunset July 6, 2026. Please remove calls to PDT-specific endpoints before this date.\n\n---\n\nRetrieves the account's pattern day trader status", - "operationId": "pdtGetStatus", - "responses": { - "200": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/PDTStatus" - } - } - }, - "description": "The requested Pattern Day Trader Status" - } - }, - "summary": "Get Pattern Day Trader Status for account", - "tags": [ - "Accounts" - ], - "x-deprecation": { - "reason": "See https://docs.alpaca.markets/us/docs/understanding-finras-new-intraday-margin-rule-and-the-end-of-pdt", - "since": "2026-04-27T00:00:00Z", - "sunset": "2026-07-06T00:00:00Z" - } - }, - "parameters": [ - { - "$ref": "#/components/parameters/AccountID" - } - ] - }, "/v1/trading/accounts/{account_id}/account/portfolio/history": { "get": { "description": "Returns timeseries data about equity and profit/loss (P/L) of the account in requested timespan.", diff --git a/module/trading/openapi.json b/module/trading/openapi.json index 2e136fdf..a8748e87 100644 --- a/module/trading/openapi.json +++ b/module/trading/openapi.json @@ -477,7 +477,7 @@ "type": "object" }, "Account": { - "description": "The account API serves important information related to an account, including account status, funds available for trade, funds available for withdrawal, and various flags relevant to an account's ability to trade. An account maybe be blocked for just for trades (trades_blocked flag) or for both trades and transfers (account_blocked flag) if Alpaca identifies the account to engaging in any suspicious activity. Please note that cryptocurrencies are not eligible assets to be used as collateral for margin accounts and will require the asset be traded using cash only. Prior to June 4th, 2026, in accordance with FINRA's pattern day trading rule, an account could be flagged for pattern day trading (pattern_day_trader flag), which would inhibit an account from placing any further day-trades. Fields related to pattern day trading have been deprecated following [FINRA's adoption of new Intraday Margin Standards](https://docs.alpaca.markets/us/docs/understanding-finras-new-intraday-margin-rule-and-the-end-of-pdt).\n", + "description": "The account API serves important information related to an account, including account status, funds available for trade, funds available for withdrawal, and various flags relevant to an account's ability to trade. An account maybe be blocked for just for trades (trades_blocked flag) or for both trades and transfers (account_blocked flag) if Alpaca identifies the account to engaging in any suspicious activity. Please note that cryptocurrencies are not eligible assets to be used as collateral for margin accounts and will require the asset be traded using cash only.\n", "properties": { "account_blocked": { "description": "If true, the account activity by user is prohibited.", @@ -517,26 +517,6 @@ "example": "USD", "type": "string" }, - "daytrade_count": { - "deprecated": true, - "description": "The current number of daytrades that have been made in the last 5 trading days (inclusive of today)", - "type": "integer", - "x-deprecation": { - "reason": "See https://docs.alpaca.markets/us/docs/understanding-finras-new-intraday-margin-rule-and-the-end-of-pdt", - "since": "2026-04-27T00:00:00Z", - "sunset": "2026-07-06T00:00:00Z" - } - }, - "daytrading_buying_power": { - "deprecated": true, - "description": "Your buying power for day trades (continuously updated value)", - "type": "string", - "x-deprecation": { - "reason": "See https://docs.alpaca.markets/us/docs/understanding-finras-new-intraday-margin-rule-and-the-end-of-pdt", - "since": "2026-04-27T00:00:00Z", - "sunset": "2026-07-06T00:00:00Z" - } - }, "equity": { "description": "Cash + long_market_value + short_market_value", "type": "string" @@ -605,16 +585,6 @@ "example": 3, "type": "integer" }, - "pattern_day_trader": { - "deprecated": true, - "description": "Whether or not the account has been flagged as a pattern day trader", - "type": "boolean", - "x-deprecation": { - "reason": "See https://docs.alpaca.markets/us/docs/understanding-finras-new-intraday-margin-rule-and-the-end-of-pdt", - "since": "2026-04-27T00:00:00Z", - "sunset": "2026-07-06T00:00:00Z" - } - }, "pending_reg_taf_fees": { "description": "Pending regulatory fees for the account.\n", "type": "string" @@ -678,8 +648,6 @@ "cash": "-23140.2", "created_at": "2019-06-12T22:47:07.99658Z", "currency": "USD", - "daytrade_count": 0, - "daytrading_buying_power": "262113.632", "equity": "103820.56", "id": "e6fe16f3-64a4-4921-8928-cadf02f92f98", "initial_margin": "63480.38", @@ -692,7 +660,6 @@ "options_approved_level": 2, "options_buying_power": "40340.18", "options_trading_level": 1, - "pattern_day_trader": false, "pending_reg_taf_fees": "0", "portfolio_value": "103820.56", "regt_buying_power": "80680.36", @@ -713,21 +680,6 @@ "description": "If true, overnight trading is disabled.", "type": "boolean" }, - "dtbp_check": { - "deprecated": true, - "description": "both, entry, or exit. Controls Day Trading Margin Call (DTMC) checks.", - "enum": [ - "both", - "entry", - "exit" - ], - "type": "string", - "x-deprecation": { - "reason": "See https://docs.alpaca.markets/us/docs/understanding-finras-new-intraday-margin-rule-and-the-end-of-pdt", - "since": "2026-04-27T00:00:00Z", - "sunset": "2026-07-06T00:00:00Z" - } - }, "fractional_trading": { "description": "If true, account is able to participate in fractional trading", "type": "boolean" @@ -750,17 +702,6 @@ "description": "If true, account becomes long-only mode.", "type": "boolean" }, - "pdt_check": { - "deprecated": true, - "description": "`both`, `entry`, or `exit`. If entry orders will be rejected on entering a position if it could result in PDT being set for the account. exit will reject exiting orders if they would result in PDT being set.", - "example": "entry", - "type": "string", - "x-deprecation": { - "reason": "See https://docs.alpaca.markets/us/docs/understanding-finras-new-intraday-margin-rule-and-the-end-of-pdt", - "since": "2026-04-27T00:00:00Z", - "sunset": "2026-07-06T00:00:00Z" - } - }, "ptp_no_exception_entry": { "description": "If set to true then Alpaca will accept orders for PTP symbols with no exception. Default is false.", "type": "boolean" @@ -779,11 +720,9 @@ "x-examples": { "example-1": { "disable_overnight_trading": false, - "dtbp_check": "entry", "fractional_trading": true, "max_margin_multiplier": "4", "no_shorting": false, - "pdt_check": "entry", "suspend_trade": false, "trade_confirm_email": "all" } @@ -1247,12 +1186,33 @@ } } }, + "AssetAttribute": { + "description": "Unique characteristic of an asset. Supported values:\n- `ptp_no_exception`: Asset is a Publicly Traded Partnership (PTP) without a qualified notice; non-U.S. customers may incur 10% withholding on gross proceeds as per IRS guidance, and are blocked from being purchased by default.\n- `ptp_with_exception`: Users can open positions in these PTPs without general restrictions.\n- `ipo`: Accepting limit orders only before the stock begins trading on the secondary market.\n- `has_options`: The underlying equity has listed options available on the platform. Note: if the equity had inactive/expired contracts in the past, this will still show up.\n- `options_late_close`: Indicates the underlying asset's options contracts close at 4:15pm ET instead of the standard 4:00pm ET.\n- `fractional_eh_enabled`: Indicates the asset accepts fractional orders during extended hours sessions (pre-market, post-market, and overnight if enabled).\n- `overnight_tradable`: Asset is eligible for overnight (24x5) trading in supported venues on the platform.\n- `overnight_halted`: Asset is eligible for overnight trading but is currently halted/blocked for overnight sessions due to risk, corporate action, compliance, or venue constraints.", + "enum": [ + "ptp_no_exception", + "ptp_with_exception", + "ipo", + "has_options", + "options_late_close", + "fractional_eh_enabled", + "overnight_tradable", + "overnight_halted" + ], + "example": "ptp_no_exception", + "type": "string" + }, "AssetClass": { "description": "This represents the category to which the asset belongs to. It serves to identify the nature of the financial instrument, with options including \"us_equity\" for U.S. equities, \"us_option\" for U.S. options, \"crypto\" for cryptocurrencies, and \"ipo\" for IPO indications of interest/orders. This `asset_class: ipo` value is distinct from the assets API `attributes: [\"ipo\"]` flag.", "enum": [ "us_equity", "us_option", "crypto", + "crypto_perp", + "treasury", + "corporate", + "global_equity", + "us_index", + "us_equity_chain", "ipo" ], "example": "us_equity", @@ -1266,23 +1226,12 @@ "description": "The assets API serves as the master list of assets available for trade and data consumption from Alpaca. Assets are sorted by asset class, exchange and symbol. Some assets are only available for data consumption via Polygon, and are not tradable with Alpaca. These assets will be marked with the flag tradable=false.\n", "properties": { "attributes": { - "description": "Unique characteristics of the asset. Supported values:\n- `ptp_no_exception`: Asset is a Publicly Traded Partnership (PTP) without a qualified notice; non-U.S. customers may incur 10% withholding on gross proceeds as per IRS guidance, and are blocked from being purchased by default.\n- `ptp_with_exception`: Users can open positions in these PTPs without general restrictions.\n- `ipo`: Accepting limit orders only before the stock begins trading on the secondary market.\n- `has_options`: The underlying equity has listed options available on the platform. Note: if the equity had inactive/expired contracts in the past, this will still show up.\n- `options_late_close`: Indicates the underlying asset's options contracts close at 4:15pm ET instead of the standard 4:00pm ET.\n- `fractional_eh_enabled`: Indicates the asset accepts fractional orders during extended hours sessions (pre-market, post-market, and overnight if enabled).\n- `overnight_tradable`: Asset is eligible for overnight (24x5) trading in supported venues on the platform.\n- `overnight_halted`: Asset is eligible for overnight trading but is currently halted/blocked for overnight sessions due to risk, corporate action, compliance, or venue constraints.", "example": [ "ptp_no_exception", "ipo" ], "items": { - "enum": [ - "ptp_no_exception", - "ptp_with_exception", - "ipo", - "has_options", - "options_late_close", - "fractional_eh_enabled", - "overnight_tradable", - "overnight_halted" - ], - "type": "string" + "$ref": "#/components/schemas/AssetAttribute" }, "type": "array" }, @@ -1977,15 +1926,7 @@ "type": "string" }, "chain": { - "description": "Optional chain identifier. The blockchain network used for the withdrawal.", - "enum": [ - "SOL", - "ETH", - "BTC", - "XRP", - "ARB" - ], - "type": "string" + "$ref": "#/components/schemas/CryptoChain" } }, "required": [ @@ -2036,6 +1977,18 @@ ], "type": "object" }, + "CryptoChain": { + "description": "Chain identifier for multi-chain crypto assets.", + "enum": [ + "SOL", + "ETH", + "BTC", + "XRP", + "ARB" + ], + "example": "ETH", + "type": "string" + }, "CryptoTransfer": { "description": "Transfers allow you to transfer assets into your end customer's account (deposits) or out (withdrawal).", "properties": { @@ -2256,7 +2209,7 @@ "type": "object" }, "Exchange": { - "description": "Represents the current exchanges Alpaca supports. List is currently:\n\n- AMEX\n- ARCA\n- BATS\n- NYSE\n- NASDAQ\n- NYSEARCA\n- OTC", + "description": "Represents the current exchanges Alpaca supports. List is currently:\n\n- AMEX\n- ARCA\n- BATS\n- NYSE\n- NASDAQ\n- NYSEARCA\n- OTC\n- CRYPTO", "enum": [ "AMEX", "ARCA", @@ -2264,14 +2217,15 @@ "NYSE", "NASDAQ", "NYSEARCA", - "OTC" + "OTC", + "CRYPTO" ], "example": "NYSE", "title": "Exchange", "type": "string" }, "ExchangeForPosition": { - "description": "Represents the current exchanges Alpaca supports. List is currently:\n\n- AMEX\n- ARCA\n- BATS\n- NYSE\n- NASDAQ\n- NYSEARCA\n- OTC\n\nCan be empty if not applicable (e.g., for options contracts)", + "description": "Represents the current exchanges Alpaca supports. List is currently:\n\n- AMEX\n- ARCA\n- BATS\n- NYSE\n- NASDAQ\n- NYSEARCA\n- OTC\n- CRYPTO\n\nCan be empty if not applicable (e.g., for options contracts)", "enum": [ "AMEX", "ARCA", @@ -2280,6 +2234,7 @@ "NASDAQ", "NYSEARCA", "OTC", + "CRYPTO", "" ], "example": "NYSE", @@ -2615,14 +2570,7 @@ "type": "integer" }, "status": { - "description": "Locate status.", - "enum": [ - "active", - "expired", - "rejected" - ], - "example": "active", - "type": "string" + "$ref": "#/components/schemas/LocateStatus" }, "symbol": { "description": "Stock symbol.", @@ -2752,6 +2700,16 @@ ], "type": "object" }, + "LocateStatus": { + "description": "Locate status.", + "enum": [ + "active", + "expired", + "rejected" + ], + "example": "active", + "type": "string" + }, "MAActivityV2": { "allOf": [ { @@ -3231,13 +3189,7 @@ "type": "string" }, "style": { - "description": "The style of the option contract.", - "enum": [ - "american", - "european" - ], - "example": "american", - "type": "string" + "$ref": "#/components/schemas/OptionContractStyle" }, "symbol": { "description": "The symbol representing the option contract.", @@ -3250,13 +3202,7 @@ "type": "boolean" }, "type": { - "description": "The type of the option contract.", - "enum": [ - "call", - "put" - ], - "example": "call", - "type": "string" + "$ref": "#/components/schemas/OptionContractType" }, "underlying_asset_id": { "description": "The unique identifier of the underlying asset.", @@ -3287,6 +3233,24 @@ ], "type": "object" }, + "OptionContractStyle": { + "description": "The style of the option contract.", + "enum": [ + "american", + "european" + ], + "example": "american", + "type": "string" + }, + "OptionContractType": { + "description": "The type of the option contract.", + "enum": [ + "call", + "put" + ], + "example": "call", + "type": "string" + }, "OptionDeliverable": { "properties": { "allocation_percentage": { @@ -6112,12 +6076,7 @@ "name": "status", "required": false, "schema": { - "enum": [ - "active", - "expired", - "rejected" - ], - "type": "string" + "$ref": "#/components/schemas/LocateStatus" } }, { @@ -6633,15 +6592,12 @@ "accrued_fees": "0", "admin_configurations": {}, "balance_asof": "2023-09-27", - "bod_dtbp": 0, "buying_power": "245432.61", "cash": "122086.5", "created_at": "2023-01-01T18:20:20.272275Z", "crypto_status": "ACTIVE", "crypto_tier": 1, "currency": "USD", - "daytrade_count": 0, - "daytrading_buying_power": "0", "effective_buying_power": "245432.61", "equity": "123346.11", "id": "1d9eed04-be39-4e01-9b84-a48ac5bbafcf", @@ -6655,7 +6611,6 @@ "non_marginable_buying_power": "122086.5", "options_buying_power": "122716.305", "options_trading_level": 2, - "pattern_day_trader": true, "pending_reg_taf_fees": "0", "pending_transfer_in": "0", "portfolio_value": "123346.11", @@ -7098,7 +7053,7 @@ } }, { - "description": "Optional AMEX, ARCA, BATS, NYSE, NASDAQ, NYSEARCA or OTC", + "description": "Optional AMEX, ARCA, BATS, NYSE, NASDAQ, NYSEARCA, OTC or CRYPTO", "in": "query", "name": "exchange", "schema": { @@ -7117,17 +7072,7 @@ "ipo" ], "items": { - "enum": [ - "ptp_no_exception", - "ptp_with_exception", - "ipo", - "has_options", - "options_late_close", - "fractional_eh_enabled", - "overnight_tradable", - "overnight_halted" - ], - "type": "string" + "$ref": "#/components/schemas/AssetAttribute" }, "type": "array" } @@ -7796,11 +7741,7 @@ "in": "query", "name": "type", "schema": { - "enum": [ - "call", - "put" - ], - "type": "string" + "$ref": "#/components/schemas/OptionContractType" } }, { @@ -7809,11 +7750,7 @@ "in": "query", "name": "style", "schema": { - "enum": [ - "american", - "european" - ], - "type": "string" + "$ref": "#/components/schemas/OptionContractStyle" } }, { @@ -9274,29 +9211,20 @@ "parameters": [ { "description": "Tokenization request type to be queried", + "example": "mint", "in": "query", "name": "type", "schema": { - "enum": [ - "mint", - "redeem" - ], - "example": "mint", - "type": "string" + "$ref": "#/components/schemas/TokenizationRequestType" } }, { "description": "Tokenization request status to be queried", + "example": "pending", "in": "query", "name": "status", "schema": { - "enum": [ - "pending", - "rejected", - "completed" - ], - "example": "pending", - "type": "string" + "$ref": "#/components/schemas/TokenizationRequestStatus" } }, { @@ -9446,14 +9374,7 @@ "in": "query", "name": "chain", "schema": { - "enum": [ - "SOL", - "ETH", - "BTC", - "XRP", - "ARB" - ], - "type": "string" + "$ref": "#/components/schemas/CryptoChain" } }, { @@ -9650,15 +9571,7 @@ "type": "string" }, "chain": { - "description": "Optional chain identifier. Use when the underlying asset for the whitelisted address is a multi-chain crypto asset.", - "enum": [ - "SOL", - "ETH", - "BTC", - "XRP", - "ARB" - ], - "type": "string" + "$ref": "#/components/schemas/CryptoChain" } }, "type": "object" From 79116a86e3375d3bef8b1b73392c83776227b11b Mon Sep 17 00:00:00 2001 From: Jacob Peterson <14127217+Petersoj@users.noreply.github.com> Date: Tue, 7 Jul 2026 10:19:37 -0600 Subject: [PATCH 255/271] Add `common` module project dependency in `openapi-generator-adapted` Gradle convention plugin --- buildSrc/src/main/kotlin/openapi-generator-adapted.gradle.kts | 2 ++ module/broker/build.gradle.kts | 4 ---- module/market-data/build.gradle.kts | 4 ---- module/trading/build.gradle.kts | 4 ---- 4 files changed, 2 insertions(+), 12 deletions(-) diff --git a/buildSrc/src/main/kotlin/openapi-generator-adapted.gradle.kts b/buildSrc/src/main/kotlin/openapi-generator-adapted.gradle.kts index ea1c2f77..c31b203b 100644 --- a/buildSrc/src/main/kotlin/openapi-generator-adapted.gradle.kts +++ b/buildSrc/src/main/kotlin/openapi-generator-adapted.gradle.kts @@ -12,6 +12,8 @@ val jacksonVersion = "3.2.0" val jacksonAnnotationsVersion = "2.22" dependencies { + api(project(":module:common")) + api("tools.jackson.core:jackson-core:$jacksonVersion") api("tools.jackson.core:jackson-databind:$jacksonVersion") api("com.fasterxml.jackson.core:jackson-annotations:$jacksonAnnotationsVersion") diff --git a/module/broker/build.gradle.kts b/module/broker/build.gradle.kts index e2d27247..0aafcbe5 100644 --- a/module/broker/build.gradle.kts +++ b/module/broker/build.gradle.kts @@ -5,10 +5,6 @@ plugins { description = "A Java library for the Alpaca brokerage platform Broker API." -dependencies { - api(project(":module:common")) -} - openApiGeneratorAdapted { environmentUrlProduction = "https://broker-api.alpaca.markets" environmentUrlDevelopment = "https://broker-api.sandbox.alpaca.markets" diff --git a/module/market-data/build.gradle.kts b/module/market-data/build.gradle.kts index 8e497a91..6e66e9c5 100644 --- a/module/market-data/build.gradle.kts +++ b/module/market-data/build.gradle.kts @@ -5,10 +5,6 @@ plugins { description = "A Java library for the Alpaca brokerage platform Market Data API." -dependencies { - api(project(":module:common")) -} - openApiGeneratorAdapted { environmentUrlProduction = "https://data.alpaca.markets" environmentUrlDevelopment = "https://data.sandbox.alpaca.markets" diff --git a/module/trading/build.gradle.kts b/module/trading/build.gradle.kts index 1b5103db..3ce72e81 100644 --- a/module/trading/build.gradle.kts +++ b/module/trading/build.gradle.kts @@ -5,10 +5,6 @@ plugins { description = "A Java library for the Alpaca brokerage platform Trading API." -dependencies { - api(project(":module:common")) -} - openApiGeneratorAdapted { environmentUrlProduction = "https://api.alpaca.markets" environmentUrlDevelopment = "https://paper-api.alpaca.markets" From 33b325754f8740d9c3c24b9349a5870526c6dbc1 Mon Sep 17 00:00:00 2001 From: Jacob Peterson <14127217+Petersoj@users.noreply.github.com> Date: Tue, 7 Jul 2026 12:45:57 -0600 Subject: [PATCH 256/271] Change version to pre-release in `ModuleCommon` Gradle build script --- buildSrc/src/main/kotlin/ModuleCommon.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/buildSrc/src/main/kotlin/ModuleCommon.kt b/buildSrc/src/main/kotlin/ModuleCommon.kt index c93f8dc8..5704c22b 100644 --- a/buildSrc/src/main/kotlin/ModuleCommon.kt +++ b/buildSrc/src/main/kotlin/ModuleCommon.kt @@ -1,3 +1,3 @@ const val PROJECT_GROUP = "net.jacobpeterson.alpaca-java" -const val PROJECT_VERSION = "11.0.0" +const val PROJECT_VERSION = "11.0.0-pre.0" const val JRELEASER_MAVEN_REPOSITORY_DIRECTORY = "jreleaser-staging" From 1892f4a804d564aaf5d5614707d217055e2d8fd4 Mon Sep 17 00:00:00 2001 From: Jacob Peterson <14127217+Petersoj@users.noreply.github.com> Date: Tue, 7 Jul 2026 13:22:00 -0600 Subject: [PATCH 257/271] Add and use simpler `ApiClient` constructor in `openapi-generator-adapted` Gradle convention plugin --- .../openapi-generator-adapted.gradle.kts | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/buildSrc/src/main/kotlin/openapi-generator-adapted.gradle.kts b/buildSrc/src/main/kotlin/openapi-generator-adapted.gradle.kts index c31b203b..749ec9ab 100644 --- a/buildSrc/src/main/kotlin/openapi-generator-adapted.gradle.kts +++ b/buildSrc/src/main/kotlin/openapi-generator-adapted.gradle.kts @@ -140,10 +140,17 @@ tasks.openApiGenerate.configure { sourceFile.writeText(source) } val outputPackageDirectory = srcMainJava.resolve(outputPackageName.replace('.', '/')) - outputPackageDirectory.resolve("ApiException.java").apply { writeText(readText() - .replace("extends Exception {", "extends RuntimeException {")) } - outputPackageDirectory.resolve("ApiResponse.java").apply { writeText(readText() - .replace("Map>", "java.net.http.HttpHeaders")) } + outputPackageDirectory.resolve("ApiClient.java").apply { + val apiClientConstructor = "public ApiClient() {" + writeText(readText().replace(apiClientConstructor, + "public ApiClient(ObjectMapper mapper) { this.mapper = mapper; }$apiClientConstructor")) + } + outputPackageDirectory.resolve("ApiException.java").apply { + writeText(readText().replace("extends Exception {", "extends RuntimeException {")) + } + outputPackageDirectory.resolve("ApiResponse.java").apply { + writeText(readText().replace("Map>", "java.net.http.HttpHeaders")) + } val apiClassNamesOfMethodNames = outputPackageDirectory.resolve(apiPackageName).list() .map { it.replace(".java", "") } .associateBy { UPPER_CAMEL.to(LOWER_CAMEL, it.substring(0, it.length - 3)) } @@ -237,8 +244,7 @@ tasks.openApiGenerate.configure { .build(); final var baseUri = apiEnvironment == ApiEnvironment.PRODUCTION ? ENVIRONMENT_URL_PRODUCTION : ENVIRONMENT_URL_DEVELOPMENT; - apiClient = new ApiClient(null, objectMapper != null ? objectMapper : - ApiClient.createDefaultObjectMapper(), null) { + apiClient = new ApiClient(objectMapper != null ? objectMapper : ApiClient.createDefaultObjectMapper()) { @Override public HttpClient getHttpClient() { From 8bbdddbe06b0c07aff2629b9a90305cf5d81b9e0 Mon Sep 17 00:00:00 2001 From: Jacob Peterson <14127217+Petersoj@users.noreply.github.com> Date: Tue, 7 Jul 2026 15:15:47 -0600 Subject: [PATCH 258/271] Fix `@JsonCreator` for enums in `openapi-generator-adapted` Gradle convention plugin --- .../kotlin/openapi-generator-adapted.gradle.kts | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/buildSrc/src/main/kotlin/openapi-generator-adapted.gradle.kts b/buildSrc/src/main/kotlin/openapi-generator-adapted.gradle.kts index 749ec9ab..07eb278c 100644 --- a/buildSrc/src/main/kotlin/openapi-generator-adapted.gradle.kts +++ b/buildSrc/src/main/kotlin/openapi-generator-adapted.gradle.kts @@ -49,6 +49,9 @@ tasks.openApiGenerate.configure { .replaceFirst("\nimport", """ import org.jspecify.annotations.*; import net.jacobpeterson.alpacajava.common.sse.*; + import com.google.common.collect.ImmutableMap; + import java.util.Arrays; + import java.util.Locale; import """.trimIndent()) .replace("@javax.annotation.Nullable", "@Nullable") @@ -60,6 +63,17 @@ tasks.openApiGenerate.configure { "InputStream localVarResponseBody = null;\n try {\n " + "if (memberVarResponseInterceptor != null) {\n " + "memberVarResponseInterceptor.accept(localVarResponse);\n }") + Regex("@JsonCreator\\n {2}public static ([\\w_]*)[\\s\\S]*?\\n {2}}").find(source)?.run { + val type = groupValues[1] + source = source.replaceRange(range.first, range.last + 1, """ + public static final ImmutableMap VALUES_OF_UPPERCASED_STRINGS = Arrays.stream(values()) + .collect(ImmutableMap.toImmutableMap(value -> value.toString().toUpperCase(Locale.ROOT), e -> e)); + @JsonCreator + public static $type fromValue(String value) { + return VALUES_OF_UPPERCASED_STRINGS.get(value.toUpperCase(Locale.ROOT)); + } + """.trimIndent()) + } val headersCall = ".headers()" source = source.replace("$headersCall.map()", headersCall) val httpRequestNewBuilder = "HttpRequest.newBuilder()" From eef01a5ae37ccc2588c216ac2b1934dfadf7ea24 Mon Sep 17 00:00:00 2001 From: Jacob Peterson <14127217+Petersoj@users.noreply.github.com> Date: Tue, 7 Jul 2026 15:29:47 -0600 Subject: [PATCH 259/271] Fix Error Prone warning and add Javadoc to `BrokerApiUtil` --- .../net/jacobpeterson/alpacajava/broker/BrokerApiUtil.java | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/module/broker/src/main/java/net/jacobpeterson/alpacajava/broker/BrokerApiUtil.java b/module/broker/src/main/java/net/jacobpeterson/alpacajava/broker/BrokerApiUtil.java index 29fc9fb7..c6192c46 100644 --- a/module/broker/src/main/java/net/jacobpeterson/alpacajava/broker/BrokerApiUtil.java +++ b/module/broker/src/main/java/net/jacobpeterson/alpacajava/broker/BrokerApiUtil.java @@ -4,6 +4,11 @@ import java.util.Base64; +import static java.nio.charset.StandardCharsets.US_ASCII; + +/** + * {@link BrokerApiUtil} is a utility class for {@link BrokerApi}. + */ @NullMarked public final class BrokerApiUtil { @@ -13,7 +18,7 @@ public final class BrokerApiUtil { * @see docs.alpaca.markets */ public static String getAuthorizationToken(final String apiKey, final String apiSecret) { - return Base64.getEncoder().encodeToString((apiKey + ":" + apiSecret).getBytes()); + return Base64.getEncoder().encodeToString((apiKey + ":" + apiSecret).getBytes(US_ASCII)); } private BrokerApiUtil() {} From 5c64fa1f37035062a051db00b2fc04801c0fb1fa Mon Sep 17 00:00:00 2001 From: Jacob Peterson <14127217+Petersoj@users.noreply.github.com> Date: Tue, 7 Jul 2026 15:31:12 -0600 Subject: [PATCH 260/271] Add config and layout for GitHub Pages to `docs/` --- docs/_config.yml | 5 +++++ docs/_layouts/base.html | 12 ++++++++++++ 2 files changed, 17 insertions(+) create mode 100644 docs/_config.yml create mode 100644 docs/_layouts/base.html diff --git a/docs/_config.yml b/docs/_config.yml new file mode 100644 index 00000000..9258ea5d --- /dev/null +++ b/docs/_config.yml @@ -0,0 +1,5 @@ +plugins: + - jekyll-remote-theme +remote_theme: jekyll/minima@4de3223 +minima: + skin: auto diff --git a/docs/_layouts/base.html b/docs/_layouts/base.html new file mode 100644 index 00000000..5f621aa7 --- /dev/null +++ b/docs/_layouts/base.html @@ -0,0 +1,12 @@ + + + +{%- include head.html -%} + +
+
+ {{ content }} +
+
+ + From 9df8ef4512246ca3dd8567951fcfbb68597520d2 Mon Sep 17 00:00:00 2001 From: Jacob Peterson <14127217+Petersoj@users.noreply.github.com> Date: Tue, 7 Jul 2026 16:11:23 -0600 Subject: [PATCH 261/271] Remove `AutoCloseable` and add `httpClient` getter in `openapi-generator-adapted` Gradle convention plugin --- .../main/kotlin/openapi-generator-adapted.gradle.kts | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/buildSrc/src/main/kotlin/openapi-generator-adapted.gradle.kts b/buildSrc/src/main/kotlin/openapi-generator-adapted.gradle.kts index 07eb278c..989b98dd 100644 --- a/buildSrc/src/main/kotlin/openapi-generator-adapted.gradle.kts +++ b/buildSrc/src/main/kotlin/openapi-generator-adapted.gradle.kts @@ -173,6 +173,7 @@ tasks.openApiGenerate.configure { package ${outputPackageName}; import com.google.errorprone.annotations.concurrent.LazyInit; + import lombok.Getter; import net.jacobpeterson.alpacajava.common.ApiHeader; import net.jacobpeterson.alpacajava.common.ApiEnvironment; import ${outputPackageName}.${apiPackageName}.*; @@ -194,7 +195,7 @@ tasks.openApiGenerate.configure { * {@link ${className}}: $projectDescription */ @NullMarked - public class $className implements AutoCloseable { + public class $className { /** * The URL for {@link ApiEnvironment#PRODUCTION}: "$environmentUrlProduction" @@ -206,7 +207,7 @@ tasks.openApiGenerate.configure { */ public static final String ENVIRONMENT_URL_DEVELOPMENT = "$environmentUrlDevelopment"; - private final HttpClient httpClient; + private final @Getter HttpClient httpClient; private final ApiClient apiClient; ${apiClassNamesOfMethodNames.entries.joinToString(separator = "\n ") { (methodName, apiClassName) -> "private @Nullable @LazyInit $apiClassName $methodName;" }} @@ -289,11 +290,6 @@ tasks.openApiGenerate.configure { apiClient.setResponseInterceptor(responseInterceptor); } - @Override - public void close() { - httpClient.close(); - } - ${apiClassNamesOfMethodNames.entries.joinToString(separator = "\n") { (methodName, apiClassName) -> """ /** * @return {@link ${apiClassName}} internally-cached instance From c4b24971d9646198fe6d6a8d7d11e15d008fad97 Mon Sep 17 00:00:00 2001 From: Jacob Peterson <14127217+Petersoj@users.noreply.github.com> Date: Tue, 7 Jul 2026 16:17:48 -0600 Subject: [PATCH 262/271] Remove old codebase that used the OkHttp client and jsonschema2pojo --- build.gradle | 343 ----------------- settings.gradle | 1 - .../net/jacobpeterson/alpaca/AlpacaAPI.java | 360 ------------------ .../alpaca/rest/broker/AlpacaBrokerAPI.java | 338 ---------------- .../rest/broker/events/EventsApiSSE.java | 190 --------- .../rest/marketdata/AlpacaMarketDataAPI.java | 152 -------- .../alpaca/rest/trader/AlpacaTraderAPI.java | 221 ----------- .../alpaca/util/apikey/APIKeyUtil.java | 23 -- .../apitype/broker_api_endpoint_type.json | 8 - .../market_data_websocket_source_type.json | 7 - .../apitype/trader_api_endpoint_type.json | 8 - .../alpaca/util/sse/SSEListener.java | 38 -- .../alpaca/util/sse/SSEListenerAdapter.java | 33 -- .../alpaca/util/sse/SSERequest.java | 36 -- .../alpaca/websocket/AlpacaWebsocket.java | 268 ------------- .../websocket/AlpacaWebsocketInterface.java | 94 ----- .../AlpacaWebsocketStateListener.java | 33 -- .../marketdata/MarketDataWebsocket.java | 198 ---------- .../MarketDataWebsocketInterface.java | 8 - .../model/control/error_message.json | 17 - .../model/control/success_message.json | 13 - .../model/control/success_message_type.json | 7 - .../marketdata/model/market_data_message.json | 4 - .../crypto/CryptoMarketDataListener.java | 55 --- .../CryptoMarketDataListenerAdapter.java | 30 -- .../crypto/CryptoMarketDataWebsocket.java | 175 --------- .../CryptoMarketDataWebsocketInterface.java | 116 ------ .../crypto/model/bar/crypto_bar_message.json | 53 --- .../control/crypto_subscriptions_message.json | 34 -- .../model/crypto_market_data_message.json | 13 - .../crypto_market_data_message_type.json | 43 --- .../orderbook/crypto_order_book_entry.json | 15 - .../orderbook/crypto_order_book_message.json | 33 -- .../model/quote/crypto_quote_message.json | 38 -- .../model/trade/crypto_trade_message.json | 38 -- .../model/trade/crypto_trade_taker_side.json | 15 - .../streams/news/NewsMarketDataListener.java | 17 - .../news/NewsMarketDataListenerAdapter.java | 12 - .../streams/news/NewsMarketDataWebsocket.java | 85 ----- .../NewsMarketDataWebsocketInterface.java | 36 -- .../control/news_subscriptions_message.json | 12 - .../streams/news/model/news/news_message.json | 38 -- .../news/model/news_market_data_message.json | 13 - .../model/news_market_data_message_type.json | 23 -- .../stock/StockMarketDataListener.java | 78 ---- .../stock/StockMarketDataListenerAdapter.java | 42 -- .../stock/StockMarketDataWebsocket.java | 218 ----------- .../StockMarketDataWebsocketInterface.java | 136 ------- .../stock/model/bar/stock_bar_message.json | 53 --- .../control/stock_subscriptions_message.json | 39 -- ...tock_limit_up_limit_down_band_message.json | 38 -- .../model/quote/stock_quote_message.json | 58 --- .../model/stock_market_data_message.json | 13 - .../model/stock_market_data_message_type.json | 55 --- .../model/trade/stock_trade_message.json | 48 --- .../stock_trade_cancel_error_action.json | 15 - .../stock_trade_cancel_error_message.json | 48 --- .../stock_trade_correction_message.json | 68 ---- .../stock_trading_status_message.json | 43 --- .../websocket/updates/UpdatesListener.java | 17 - .../websocket/updates/UpdatesWebsocket.java | 169 -------- .../updates/UpdatesWebsocketInterface.java | 24 -- .../authorization/authorization_data.json | 13 - .../authorization/authorization_message.json | 12 - .../model/listening/listening_data.json | 9 - .../model/listening/listening_message.json | 12 - .../model/tradeupdate/trade_update.json | 35 -- .../model/tradeupdate/trade_update_event.json | 71 ---- .../tradeupdate/trade_update_message.json | 12 - .../updates/model/updates_message.json | 9 - .../updates/model/updates_message_type.json | 8 - 71 files changed, 4637 deletions(-) delete mode 100644 build.gradle delete mode 100644 settings.gradle delete mode 100644 src/main/java/net/jacobpeterson/alpaca/AlpacaAPI.java delete mode 100644 src/main/java/net/jacobpeterson/alpaca/rest/broker/AlpacaBrokerAPI.java delete mode 100644 src/main/java/net/jacobpeterson/alpaca/rest/broker/events/EventsApiSSE.java delete mode 100644 src/main/java/net/jacobpeterson/alpaca/rest/marketdata/AlpacaMarketDataAPI.java delete mode 100644 src/main/java/net/jacobpeterson/alpaca/rest/trader/AlpacaTraderAPI.java delete mode 100644 src/main/java/net/jacobpeterson/alpaca/util/apikey/APIKeyUtil.java delete mode 100644 src/main/java/net/jacobpeterson/alpaca/util/apitype/broker_api_endpoint_type.json delete mode 100644 src/main/java/net/jacobpeterson/alpaca/util/apitype/market_data_websocket_source_type.json delete mode 100644 src/main/java/net/jacobpeterson/alpaca/util/apitype/trader_api_endpoint_type.json delete mode 100644 src/main/java/net/jacobpeterson/alpaca/util/sse/SSEListener.java delete mode 100644 src/main/java/net/jacobpeterson/alpaca/util/sse/SSEListenerAdapter.java delete mode 100644 src/main/java/net/jacobpeterson/alpaca/util/sse/SSERequest.java delete mode 100644 src/main/java/net/jacobpeterson/alpaca/websocket/AlpacaWebsocket.java delete mode 100644 src/main/java/net/jacobpeterson/alpaca/websocket/AlpacaWebsocketInterface.java delete mode 100644 src/main/java/net/jacobpeterson/alpaca/websocket/AlpacaWebsocketStateListener.java delete mode 100644 src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/MarketDataWebsocket.java delete mode 100644 src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/MarketDataWebsocketInterface.java delete mode 100644 src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/model/control/error_message.json delete mode 100644 src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/model/control/success_message.json delete mode 100644 src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/model/control/success_message_type.json delete mode 100644 src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/model/market_data_message.json delete mode 100644 src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/streams/crypto/CryptoMarketDataListener.java delete mode 100644 src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/streams/crypto/CryptoMarketDataListenerAdapter.java delete mode 100644 src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/streams/crypto/CryptoMarketDataWebsocket.java delete mode 100644 src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/streams/crypto/CryptoMarketDataWebsocketInterface.java delete mode 100644 src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/streams/crypto/model/bar/crypto_bar_message.json delete mode 100644 src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/streams/crypto/model/control/crypto_subscriptions_message.json delete mode 100644 src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/streams/crypto/model/crypto_market_data_message.json delete mode 100644 src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/streams/crypto/model/crypto_market_data_message_type.json delete mode 100644 src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/streams/crypto/model/orderbook/crypto_order_book_entry.json delete mode 100644 src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/streams/crypto/model/orderbook/crypto_order_book_message.json delete mode 100644 src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/streams/crypto/model/quote/crypto_quote_message.json delete mode 100644 src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/streams/crypto/model/trade/crypto_trade_message.json delete mode 100644 src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/streams/crypto/model/trade/crypto_trade_taker_side.json delete mode 100644 src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/streams/news/NewsMarketDataListener.java delete mode 100644 src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/streams/news/NewsMarketDataListenerAdapter.java delete mode 100644 src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/streams/news/NewsMarketDataWebsocket.java delete mode 100644 src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/streams/news/NewsMarketDataWebsocketInterface.java delete mode 100644 src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/streams/news/model/control/news_subscriptions_message.json delete mode 100644 src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/streams/news/model/news/news_message.json delete mode 100644 src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/streams/news/model/news_market_data_message.json delete mode 100644 src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/streams/news/model/news_market_data_message_type.json delete mode 100644 src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/streams/stock/StockMarketDataListener.java delete mode 100644 src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/streams/stock/StockMarketDataListenerAdapter.java delete mode 100644 src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/streams/stock/StockMarketDataWebsocket.java delete mode 100644 src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/streams/stock/StockMarketDataWebsocketInterface.java delete mode 100644 src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/streams/stock/model/bar/stock_bar_message.json delete mode 100644 src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/streams/stock/model/control/stock_subscriptions_message.json delete mode 100644 src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/streams/stock/model/limituplimitdownband/stock_limit_up_limit_down_band_message.json delete mode 100644 src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/streams/stock/model/quote/stock_quote_message.json delete mode 100644 src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/streams/stock/model/stock_market_data_message.json delete mode 100644 src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/streams/stock/model/stock_market_data_message_type.json delete mode 100644 src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/streams/stock/model/trade/stock_trade_message.json delete mode 100644 src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/streams/stock/model/tradecancelerror/stock_trade_cancel_error_action.json delete mode 100644 src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/streams/stock/model/tradecancelerror/stock_trade_cancel_error_message.json delete mode 100644 src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/streams/stock/model/tradecorrection/stock_trade_correction_message.json delete mode 100644 src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/streams/stock/model/tradingstatus/stock_trading_status_message.json delete mode 100644 src/main/java/net/jacobpeterson/alpaca/websocket/updates/UpdatesListener.java delete mode 100644 src/main/java/net/jacobpeterson/alpaca/websocket/updates/UpdatesWebsocket.java delete mode 100644 src/main/java/net/jacobpeterson/alpaca/websocket/updates/UpdatesWebsocketInterface.java delete mode 100644 src/main/java/net/jacobpeterson/alpaca/websocket/updates/model/authorization/authorization_data.json delete mode 100644 src/main/java/net/jacobpeterson/alpaca/websocket/updates/model/authorization/authorization_message.json delete mode 100644 src/main/java/net/jacobpeterson/alpaca/websocket/updates/model/listening/listening_data.json delete mode 100644 src/main/java/net/jacobpeterson/alpaca/websocket/updates/model/listening/listening_message.json delete mode 100644 src/main/java/net/jacobpeterson/alpaca/websocket/updates/model/tradeupdate/trade_update.json delete mode 100644 src/main/java/net/jacobpeterson/alpaca/websocket/updates/model/tradeupdate/trade_update_event.json delete mode 100644 src/main/java/net/jacobpeterson/alpaca/websocket/updates/model/tradeupdate/trade_update_message.json delete mode 100644 src/main/java/net/jacobpeterson/alpaca/websocket/updates/model/updates_message.json delete mode 100644 src/main/java/net/jacobpeterson/alpaca/websocket/updates/model/updates_message_type.json diff --git a/build.gradle b/build.gradle deleted file mode 100644 index c3948da4..00000000 --- a/build.gradle +++ /dev/null @@ -1,343 +0,0 @@ -import org.openapitools.generator.gradle.plugin.tasks.GenerateTask - -import java.nio.file.Paths - -plugins { - id "java" - id "java-library" - - // Adapted JSONSchema2POJO plugin - id "jsonschema2pojo-adapted" - - // OpenAPI Generator - id "org.openapi.generator" version "7.3.0" - - // Gradle versions plugin - id "com.github.ben-manes.versions" version "0.51.0" - - // For Maven Central publishing - id "maven-publish" - id "signing" -} - -final def projectGroup = "net.jacobpeterson.alpaca" -final def projectArtifactID = "alpaca-java" -final def projectVersion = "10.0.1-SNAPSHOT" - -group = projectGroup -version = projectVersion - -repositories { - mavenCentral() -} - -dependencies { - // Logging framework - implementation group: "org.slf4j", name: "slf4j-api", version: "2.1.0-alpha1" - // Require dependencies that use the Apache Jakarta Commons Logging library to use SLF4j instead - implementation group: "org.slf4j", name: "jcl-over-slf4j", version: "2.1.0-alpha1" - - // Google Guava - implementation group: "com.google.guava", name: "guava", version: "33.0.0-jre" - - // GSON - implementation group: "com.google.code.gson", name: "gson", version: "2.10.1" - - // Jetbrains Annotations - implementation group: "org.jetbrains", name: "annotations", version: "24.1.0" - - // OkHttp - implementation group: "com.squareup.okhttp3", name: "okhttp", version: "5.0.0-alpha.12" - implementation group: "com.squareup.okhttp3", name: "okhttp-sse", version: "5.0.0-alpha.12" - implementation group: "com.squareup.okhttp3", name: "logging-interceptor", version: "5.0.0-alpha.12" - - // Libraries used by the OpenAPI generated client libraries - implementation group: "jakarta.annotation", name: "jakarta.annotation-api", version: "1.3.5" - implementation group: "org.openapitools", name: "jackson-databind-nullable", version: "0.2.6" - implementation group: "io.gsonfire", name: "gson-fire", version: "1.9.0" -} - -// Exclude any SLF4j-implementation transitive dependencies so users can use a logging framework of their choice -configurations.implementation { - exclude group: "commons-logging", module: "commons-logging" -} - -java { - sourceCompatibility = JavaVersion.VERSION_17 - targetCompatibility = JavaVersion.VERSION_17 - withJavadocJar() - withSourcesJar() -} -javadocJar.dependsOn generatePOJOs -sourcesJar.dependsOn generatePOJOs -jar.dependsOn compileJava -javadocJar.dependsOn compileJava -sourcesJar.dependsOn compileJava - -[compileJava, compileTestJava]*.options*.encoding = "UTF-8" - -javadoc { - options.addStringOption("source", "17") - options.addStringOption("charset", "UTF-8") - options.addStringOption("Xdoclint:none", "-quiet") // Suppress Javadoc linting warnings - options.addStringOption("link", "https://docs.oracle.com/en/java/javase/17/docs/api/") -} - -// -// BEGIN Alpaca OpenAPI Specification (OAS) client generation -// - -// These IDs come from the URLs on this page: https://docs.alpaca.markets/openapi -final def specIDsOfFileNames = Map.of( - "trader.json", "673e19fc6dc876001ff495b2", - "broker.json", "673e1a03a0b2160060120aa0", - "market-data.json", "673e19f7fd59830026ac651b") -final File specDownloadPath = new File(project.layout.buildDirectory.get().getAsFile(), - "/download/openapi/") -final File generatedClientLibrariesPath = new File(project.layout.buildDirectory.get().getAsFile(), - "/generated/openapi/") - -def downloadOpenAPISpecFilesTask = tasks.register("downloadOpenAPISpecFiles") { - configure { - outputs.dir(specDownloadPath) - outputs.cacheIf { true } - } - - doLast { - // Download spec files - specIDsOfFileNames.forEach { fileName, alpacaSpecID -> - final def outputFile = new File(specDownloadPath, fileName) - final def specURL = "https://docs.alpaca.markets/openapi/${alpacaSpecID}" - outputFile.getParentFile().mkdirs() - try { - logger.info("Downloading OpenAPI spec file from: {}", specURL) - new URL(specURL).withInputStream { inputStream -> outputFile.withOutputStream { it << inputStream } } - logger.info("Successfully downloaded OpenAPI spec file to: {}", outputFile.getPath()) - } catch (IOException ioException) { - throw new RuntimeException("Could not download OpenAPI spec file!", ioException) - } - } - - // Fix an issue in the Broker spec file that uses an empty string as an object key for some reason - // TODO remove this when issue is fixed by Alpaca - final def brokerSpecFile = new File(specDownloadPath, "broker.json") - brokerSpecFile.text = brokerSpecFile.text - .replace(",\"\":{\"type\":\"string\",\"x-stoplight\":{\"id\":\"1zg5jnb6rzdoo\"}}", "") - } -} -compileJava.dependsOn downloadOpenAPISpecFilesTask - -final def generateTasks = [] -for (def specFileName : specIDsOfFileNames.keySet()) { - final def specName = specFileName.replace(".json", "") - final def inputSpecFile = new File(specDownloadPath, specFileName) - final def outputDirectory = new File(generatedClientLibrariesPath, specName) - - // Add source set of OpenAPI generated client library - sourceSets { - main { - java { - srcDir new File(outputDirectory, "src/main/java/").getPath() - } - } - } - - // Register and configure OpenAPI generate task - final def generateTask = tasks.register("generateOpenAPIClientFor-" + specName, GenerateTask) { - dependsOn { - downloadOpenAPISpecFilesTask - } - - configure { - inputs.file(inputSpecFile) - outputs.dir(outputDirectory) - outputs.cacheIf { true } - } - - generatorName.set("java") - inputSpec.set(inputSpecFile.getPath()) - outputDir.set(outputDirectory.getPath()) - final def destinationPackage = projectGroup + ".openapi." + specName.replace("-", "") - configOptions.set(Map.of( - "artifactId", specName, - "artifactVersion", "1.0.0-SNAPSHOT", - "groupId", destinationPackage, - "invokerPackage", destinationPackage, - "apiPackage", destinationPackage + ".api", - "modelPackage", destinationPackage + ".model", - "library", "okhttp-gson", - "documentationProvider", "none", - "useBeanValidation", "false", - "performBeanValidation", "false")) - cleanupOutput.set(true) - validateSpec.set(false) - skipValidateSpec.set(true) - generateModelTests.set(false) - generateModelDocumentation.set(false) - generateApiTests.set(false) - generateApiDocumentation.set(false) - } - compileJava.dependsOn generateTask - generateTasks.add(generateTask) -} - -final def fixOpenAPIGeneratedClientIssuesTask = tasks.register("fixOpenAPIGeneratedClientIssues") { - configure { - mustRunAfter(generateTasks) - } - - it.onlyIf { - generateTasks.stream().allMatch { generateTask -> - !generateTask.get().state.upToDate && - generateTask.get().state.failure == null && - !generateTask.get().state.skipped && - generateTask.get().state.executed - } - } - - doLast { - // TODO remove these manual and hacky fixes once OpenAPI fixes these issues - - final def sourceFilesPath = "src/main/java/net/jacobpeterson/alpaca/openapi" - - // Fix broker spec generated client issues with discriminators - // on some enums: https://github.com/OpenAPITools/openapi-generator/issues/806 - final def brokerTransferDataJavaFile = new File(generatedClientLibrariesPath, - "broker/${sourceFilesPath}/broker/model/TransferData.java") - brokerTransferDataJavaFile.text = brokerTransferDataJavaFile.text - .replace("this.transferType = this.getClass().getSimpleName();", "") - final def brokerTransferResourceJavaFile = new File(generatedClientLibrariesPath, - "broker/${sourceFilesPath}/broker/model/TransferResource.java") - brokerTransferResourceJavaFile.text = brokerTransferResourceJavaFile.text - .replace("this.type = this.getClass().getSimpleName();", "") - - // Remove the JSON validation that the generated client models always run (there isn't an option to - // disable it for some reason). Also, remove throwing an IllegalArgumentException for a null/empty string - // given to enum 'fromValue' methods. - specIDsOfFileNames.keySet().stream().map { it.replace(".json", "") }.forEach { - for (File modelFile : Paths.get(generatedClientLibrariesPath.getPath(), - it, sourceFilesPath, it.replace("-", ""), "model").toFile().listFiles()) { - modelFile.text = modelFile.text - .replaceAll("/\\*\\*\\n {2}\\* Validates the JSON Element and throws an exception if issues " + - "found[\\s\\S]*?\\*/", "") - .replace("import java.io.IOException;", - "import java.io.IOException;\nimport java.util.function.Predicate;") - .replace("public static void validateJsonElement(JsonElement jsonElement) throws IOException {", - "public static boolean validate = false;\n" + - " public static Predicate isValid = jsonElement -> true;\n" + - " public static void validateJsonElement(JsonElement jsonElement) " + - "throws IOException {\n" + - " if (!isValid.test(jsonElement)) throw new RuntimeException();\n" + - " if (!validate) return;\n") - .replace("throw new IllegalArgumentException(\"Unexpected value '\" + value + \"'\");", - "return null;") - } - } - - // Make certain method signatures in the generated client API classes 'protected' instead of 'public' - specIDsOfFileNames.keySet().stream().map { it.replace(".json", "") }.forEach { - for (File apiFile : Paths.get(generatedClientLibrariesPath.getPath(), - it, sourceFilesPath, it.replace("-", ""), "api").toFile().listFiles()) { - // Don't make replacements in the broker 'events' API as that class is wrapped - if (apiFile.name == "EventsApi.java") { - continue - } - // Make replacements - apiFile.text = apiFile.text - .replace("public okhttp3.Call", "protected okhttp3.Call") - .replace("public ApiResponse", "protected ApiResponse") - .replaceAll("\\n.*public ApiClient[\\s\\S]*?customBaseUrl;\\n {4}}", "") - } - } - - // Remove the necessary authentication header logic from the generate client's 'ApiClient' - specIDsOfFileNames.keySet().stream().map { it.replace(".json", "") }.forEach { - final def apiClientFile = Paths.get(generatedClientLibrariesPath.getPath(), it, sourceFilesPath, - it.replace("-", ""), "ApiClient.java").toFile() - apiClientFile.text = apiClientFile.text - .replaceAll("// Setup authentications \\(key: authentication name, value: authentication\\)" + - "[\\s\\S]*?authentications\\);", "") - .replaceAll("for \\(String authName : authNames\\)[\\s\\S]*?uri\\);\\n. {7}}", "") - } - - // Make the 'JSON' classes of the generate clients have a static Gson initializer. Also fix the OffsetDateTime - // Gson type adapter to accommodate ISO local dates. - specIDsOfFileNames.keySet().stream().map { it.replace(".json", "") }.forEach { - final def jsonClientFile = Paths.get(generatedClientLibrariesPath.getPath(), it, sourceFilesPath, - it.replace("-", ""), "JSON.java").toFile() - jsonClientFile.text = jsonClientFile.text - .replaceAll("\\{\\n {8}GsonBuilder", "static {\n GsonBuilder") - .replaceAll("}[\\s\\S].*return OffsetDateTime\\.parse\\(date, formatter\\);", - "} else if (!date.contains(\"T\")) {\n" + - "return LocalDate.parse(date, DateTimeFormatter.ISO_LOCAL_DATE)" + - ".atTime(java.time.LocalTime.MIN.atOffset(java.time.ZoneOffset.UTC));\n" + - "}\n" + - "return OffsetDateTime.parse(date, formatter);") - } - } -} -compileJava.dependsOn fixOpenAPIGeneratedClientIssuesTask - -// -// END Alpaca OpenAPI Specification (OAS) client generation -// - -// -// BEGIN Publishing -// - -publishing { - publications { - mavenJava(MavenPublication) { - groupId = projectGroup - artifactId = projectArtifactID - version = projectVersion - from(components["java"]) - pom { - name = projectArtifactID - description = "A Java API for Alpaca, the commission free, algo friendly, stock trading broker." - url = "https://github.com/Petersoj/alpaca-java" - inceptionYear = "2018" - licenses { - license { - name = "MIT License" - url = "https://opensource.org/licenses/MIT" - } - } - developers { - developer { - id = "Petersoj" - name = "Jacob Peterson" - } - developer { - id = "mainstringargs" - name = "main(String[] args)" - } - } - scm { - url = "https://github.com/Petersoj/alpaca-java.git" - connection = "scm:git:https://github.com/Petersoj/alpaca-java.git" - developerConnection = "scm:git:git@github.com/Petersoj/alpaca-java.git" - } - } - } - } - repositories { - maven { - name = "OSSRH" - url = projectVersion.contains("SNAPSHOT") ? "https://oss.sonatype.org/content/repositories/snapshots/" : - "https://oss.sonatype.org/service/local/staging/deploy/maven2/" - credentials { - username = project.property("nexus.username") - password = project.property("nexus.password") - } - } - } -} -signing { - sign publishing.publications.mavenJava -} - -// -// END Publishing -// diff --git a/settings.gradle b/settings.gradle deleted file mode 100644 index 6088a942..00000000 --- a/settings.gradle +++ /dev/null @@ -1 +0,0 @@ -rootProject.name = 'alpaca-java' diff --git a/src/main/java/net/jacobpeterson/alpaca/AlpacaAPI.java b/src/main/java/net/jacobpeterson/alpaca/AlpacaAPI.java deleted file mode 100644 index a4da83cf..00000000 --- a/src/main/java/net/jacobpeterson/alpaca/AlpacaAPI.java +++ /dev/null @@ -1,360 +0,0 @@ -package net.jacobpeterson.alpaca; - -import net.jacobpeterson.alpaca.model.util.apitype.BrokerAPIEndpointType; -import net.jacobpeterson.alpaca.model.util.apitype.MarketDataWebsocketSourceType; -import net.jacobpeterson.alpaca.model.util.apitype.TraderAPIEndpointType; -import net.jacobpeterson.alpaca.rest.broker.AlpacaBrokerAPI; -import net.jacobpeterson.alpaca.rest.marketdata.AlpacaMarketDataAPI; -import net.jacobpeterson.alpaca.rest.trader.AlpacaTraderAPI; -import net.jacobpeterson.alpaca.websocket.marketdata.streams.crypto.CryptoMarketDataWebsocket; -import net.jacobpeterson.alpaca.websocket.marketdata.streams.crypto.CryptoMarketDataWebsocketInterface; -import net.jacobpeterson.alpaca.websocket.marketdata.streams.news.NewsMarketDataWebsocket; -import net.jacobpeterson.alpaca.websocket.marketdata.streams.news.NewsMarketDataWebsocketInterface; -import net.jacobpeterson.alpaca.websocket.marketdata.streams.stock.StockMarketDataWebsocket; -import net.jacobpeterson.alpaca.websocket.marketdata.streams.stock.StockMarketDataWebsocketInterface; -import net.jacobpeterson.alpaca.websocket.updates.UpdatesWebsocket; -import net.jacobpeterson.alpaca.websocket.updates.UpdatesWebsocketInterface; -import okhttp3.OkHttpClient; -import okhttp3.logging.HttpLoggingInterceptor; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import static net.jacobpeterson.alpaca.model.util.apitype.BrokerAPIEndpointType.SANDBOX; -import static net.jacobpeterson.alpaca.model.util.apitype.MarketDataWebsocketSourceType.IEX; -import static net.jacobpeterson.alpaca.model.util.apitype.TraderAPIEndpointType.PAPER; -import static okhttp3.logging.HttpLoggingInterceptor.Level.BODY; - -/** - * {@link AlpacaAPI} is the main class used to interface with the various Alpaca API endpoints. If you are using the - * Trading or Market Data APIs for a single Alpaca account or if you are using the Broker API, you will generally only - * need one instance of this class. However, if you are using the Trading API with OAuth to act on behalf of an Alpaca - * account, this class is optimized so that it can be instantiated quickly, especially when an existing - * {@link OkHttpClient} is given in the constructor. Additionally, all API endpoint instances are instantiated lazily. - * This class is thread-safe. - * - * @see Alpaca Docs - */ -public class AlpacaAPI { - - private static final Logger LOGGER = LoggerFactory.getLogger(AlpacaAPI.class); - - private final String traderKeyID; - private final String traderSecretKey; - private final String traderOAuthToken; - private final TraderAPIEndpointType traderAPIEndpointType; - private final MarketDataWebsocketSourceType marketDataWebsocketSourceType; - private final String brokerAPIKey; - private final String brokerAPISecret; - private final BrokerAPIEndpointType brokerAPIEndpointType; - private final OkHttpClient okHttpClient; - - private AlpacaTraderAPI trader; - private AlpacaMarketDataAPI marketData; - private AlpacaBrokerAPI broker; - private UpdatesWebsocket updatesWebsocket; - private StockMarketDataWebsocket stockMarketDataWebsocket; - private CryptoMarketDataWebsocket cryptoMarketDataWebsocket; - private NewsMarketDataWebsocket newsMarketDataWebsocket; - - /** - * Instantiates a new {@link AlpacaAPI}. Use this constructor if you are using the Trading or Market Data APIs for a - * single Alpaca account. - * - * @param traderKeyID the Trader key ID - * @param traderSecretKey the Trader secret key - * @param traderAPIEndpointType the {@link TraderAPIEndpointType} - * @param marketDataWebsocketSourceType the {@link MarketDataWebsocketSourceType} - */ - public AlpacaAPI(String traderKeyID, String traderSecretKey, TraderAPIEndpointType traderAPIEndpointType, - MarketDataWebsocketSourceType marketDataWebsocketSourceType) { - this(traderKeyID, traderSecretKey, null, traderAPIEndpointType, marketDataWebsocketSourceType, null, null, null, - null); - } - - /** - * Instantiates a new {@link AlpacaAPI}. Use this constructor if you are using the Trading or Market Data APIs for a - * single Alpaca account and a custom {@link OkHttpClient} instance. - * - * @param traderKeyID the Trader key ID - * @param traderSecretKey the Trader secret key - * @param traderAPIEndpointType the {@link TraderAPIEndpointType} - * @param marketDataWebsocketSourceType the {@link MarketDataWebsocketSourceType} - * @param okHttpClient an existing {@link OkHttpClient} or null to create a new - * default instance - */ - public AlpacaAPI(String traderKeyID, String traderSecretKey, TraderAPIEndpointType traderAPIEndpointType, - MarketDataWebsocketSourceType marketDataWebsocketSourceType, OkHttpClient okHttpClient) { - this(traderKeyID, traderSecretKey, null, traderAPIEndpointType, marketDataWebsocketSourceType, null, null, null, - okHttpClient); - } - - /** - * Instantiates a new {@link AlpacaAPI}. Use this constructor if you are using the Trading API with OAuth to act on - * behalf of an Alpaca account. - * - * @param traderOAuthToken the Trader OAuth token - * @param traderAPIEndpointType the {@link TraderAPIEndpointType} - */ - public AlpacaAPI(String traderOAuthToken, TraderAPIEndpointType traderAPIEndpointType) { - this(null, null, traderOAuthToken, traderAPIEndpointType, null, null, null, null, null); - } - - /** - * Instantiates a new {@link AlpacaAPI}. Use this constructor if you are using the Trading API with OAuth to act on - * behalf of an Alpaca account and a custom {@link OkHttpClient} instance. - * - * @param traderOAuthToken the Trader OAuth token - * @param traderAPIEndpointType the {@link TraderAPIEndpointType} - * @param okHttpClient an existing {@link OkHttpClient} or null to create a new default - * instance - */ - public AlpacaAPI(String traderOAuthToken, TraderAPIEndpointType traderAPIEndpointType, OkHttpClient okHttpClient) { - this(null, null, traderOAuthToken, traderAPIEndpointType, null, null, null, null, okHttpClient); - } - - /** - * Instantiates a new {@link AlpacaAPI}. Use this constructor if you are using the Broker API. - * - * @param brokerAPIKey the Broker API key - * @param brokerAPISecret the Broker API secret - * @param brokerAPIEndpointType the {@link BrokerAPIEndpointType} - */ - public AlpacaAPI(String brokerAPIKey, String brokerAPISecret, BrokerAPIEndpointType brokerAPIEndpointType) { - this(null, null, null, null, null, brokerAPIKey, brokerAPISecret, brokerAPIEndpointType, null); - } - - /** - * Instantiates a new {@link AlpacaAPI}. Use this constructor if you are using the Broker API and a custom - * {@link OkHttpClient} instance. - * - * @param brokerAPIKey the Broker API key - * @param brokerAPISecret the Broker API secret - * @param brokerAPIEndpointType the {@link BrokerAPIEndpointType} - * @param okHttpClient an existing {@link OkHttpClient} or null to create a new default - * instance - */ - public AlpacaAPI(String brokerAPIKey, String brokerAPISecret, BrokerAPIEndpointType brokerAPIEndpointType, - OkHttpClient okHttpClient) { - this(null, null, null, null, null, brokerAPIKey, brokerAPISecret, brokerAPIEndpointType, okHttpClient); - } - - /** - * Instantiates a new {@link AlpacaAPI}. - * - * @param traderKeyID the Trader key ID - * @param traderSecretKey the Trader secret key - * @param traderOAuthToken the Trader OAuth token - * @param traderAPIEndpointType the {@link TraderAPIEndpointType} - * @param marketDataWebsocketSourceType the {@link MarketDataWebsocketSourceType} - * @param brokerAPIKey the Broker API key - * @param brokerAPISecret the Broker API secret - * @param brokerAPIEndpointType the {@link BrokerAPIEndpointType} - * @param okHttpClient an existing {@link OkHttpClient} or null to create a new - * default instance - */ - public AlpacaAPI(String traderKeyID, String traderSecretKey, - String traderOAuthToken, TraderAPIEndpointType traderAPIEndpointType, - MarketDataWebsocketSourceType marketDataWebsocketSourceType, - String brokerAPIKey, String brokerAPISecret, BrokerAPIEndpointType brokerAPIEndpointType, - OkHttpClient okHttpClient) { - this.traderKeyID = traderKeyID; - this.traderSecretKey = traderSecretKey; - this.traderOAuthToken = traderOAuthToken; - this.traderAPIEndpointType = traderAPIEndpointType != null ? - traderAPIEndpointType : PAPER; - this.marketDataWebsocketSourceType = marketDataWebsocketSourceType != null ? - marketDataWebsocketSourceType : IEX; - this.brokerAPIKey = brokerAPIKey; - this.brokerAPISecret = brokerAPISecret; - this.brokerAPIEndpointType = brokerAPIEndpointType != null ? - brokerAPIEndpointType : SANDBOX; - - // Create default OkHttpClient instance - if (okHttpClient == null) { - final OkHttpClient.Builder clientBuilder = new OkHttpClient.Builder(); - if (LOGGER.isDebugEnabled()) { - final HttpLoggingInterceptor loggingInterceptor = new HttpLoggingInterceptor(LOGGER::debug); - loggingInterceptor.setLevel(BODY); - clientBuilder.addInterceptor(loggingInterceptor); - } - okHttpClient = clientBuilder.build(); - } - this.okHttpClient = okHttpClient; - } - - /** - * Gets the {@link OkHttpClient}. - * - * @return the {@link OkHttpClient} - */ - public OkHttpClient getOkHttpClient() { - return okHttpClient; - } - - /** - * Gets the {@link AlpacaTraderAPI}. Lazily instantiated. - * - * @return the {@link AlpacaTraderAPI} - */ - public synchronized AlpacaTraderAPI trader() { - if (trader == null) { - trader = new AlpacaTraderAPI(traderKeyID, traderSecretKey, traderOAuthToken, traderAPIEndpointType, - okHttpClient); - } - return trader; - } - - /** - * Gets the {@link AlpacaMarketDataAPI}. Lazily instantiated. - * - * @return the {@link AlpacaMarketDataAPI} - */ - public synchronized AlpacaMarketDataAPI marketData() { - if (marketData == null) { - marketData = new AlpacaMarketDataAPI(traderKeyID, traderSecretKey, brokerAPIKey, brokerAPISecret, - okHttpClient); - } - return marketData; - } - - /** - * Gets the {@link AlpacaBrokerAPI}. Lazily instantiated. - * - * @return the {@link AlpacaBrokerAPI} - */ - public synchronized AlpacaBrokerAPI broker() { - if (broker == null) { - broker = new AlpacaBrokerAPI(brokerAPIKey, brokerAPISecret, brokerAPIEndpointType, okHttpClient); - } - return broker; - } - - /** - * Gets the {@link UpdatesWebsocketInterface}. Lazily instantiated. - * - * @return the {@link UpdatesWebsocketInterface} - */ - public synchronized UpdatesWebsocketInterface updatesStream() { - if (updatesWebsocket == null) { - updatesWebsocket = new UpdatesWebsocket(okHttpClient, traderAPIEndpointType, - traderKeyID, traderSecretKey, traderOAuthToken); - } - return updatesWebsocket; - } - - /** - * Gets the {@link StockMarketDataWebsocketInterface}. Lazily instantiated. - * - * @return the {@link StockMarketDataWebsocketInterface} - */ - public synchronized StockMarketDataWebsocketInterface stockMarketDataStream() { - if (stockMarketDataWebsocket == null) { - stockMarketDataWebsocket = new StockMarketDataWebsocket(okHttpClient, - traderKeyID, traderSecretKey, brokerAPIKey, brokerAPISecret, marketDataWebsocketSourceType); - } - return stockMarketDataWebsocket; - } - - /** - * Gets the {@link CryptoMarketDataWebsocketInterface}. Lazily instantiated. - * - * @return the {@link CryptoMarketDataWebsocketInterface} - */ - public synchronized CryptoMarketDataWebsocketInterface cryptoMarketDataStream() { - if (cryptoMarketDataWebsocket == null) { - cryptoMarketDataWebsocket = new CryptoMarketDataWebsocket(okHttpClient, - traderKeyID, traderSecretKey, brokerAPIKey, brokerAPISecret); - } - return cryptoMarketDataWebsocket; - } - - /** - * Gets the {@link NewsMarketDataWebsocketInterface}. Lazily instantiated. - * - * @return the {@link NewsMarketDataWebsocketInterface} - */ - public synchronized NewsMarketDataWebsocketInterface newsMarketDataStream() { - if (newsMarketDataWebsocket == null) { - newsMarketDataWebsocket = new NewsMarketDataWebsocket(okHttpClient, - traderKeyID, traderSecretKey, brokerAPIKey, brokerAPISecret); - } - return newsMarketDataWebsocket; - } - - /** - * Creates a {@link Builder} for {@link AlpacaAPI}. - * - * @return the {@link Builder} - */ - public static Builder builder() { - return new Builder(); - } - - /** - * A builder for {@link AlpacaAPI} - */ - public static final class Builder { - - private String traderKeyID; - private String traderSecretKey; - private String traderOAuthToken; - private TraderAPIEndpointType traderAPIEndpointType; - private MarketDataWebsocketSourceType marketDataWebsocketSourceType; - private String brokerAPIKey; - private String brokerAPISecret; - private BrokerAPIEndpointType brokerAPIEndpointType; - private OkHttpClient okHttpClient; - - private Builder() {} - - public Builder withTraderKeyID(String traderKeyID) { - this.traderKeyID = traderKeyID; - return this; - } - - public Builder withTraderSecretKey(String traderSecretKey) { - this.traderSecretKey = traderSecretKey; - return this; - } - - public Builder withTraderOAuthToken(String traderOAuthToken) { - this.traderOAuthToken = traderOAuthToken; - return this; - } - - public Builder withTraderAPIEndpointType(TraderAPIEndpointType traderAPIEndpointType) { - this.traderAPIEndpointType = traderAPIEndpointType; - return this; - } - - public Builder withMarketDataWebsocketSourceType(MarketDataWebsocketSourceType marketDataWebsocketSourceType) { - this.marketDataWebsocketSourceType = marketDataWebsocketSourceType; - return this; - } - - public Builder withBrokerAPIKey(String brokerAPIKey) { - this.brokerAPIKey = brokerAPIKey; - return this; - } - - public Builder withBrokerAPISecret(String brokerAPISecret) { - this.brokerAPISecret = brokerAPISecret; - return this; - } - - public Builder withBrokerAPIEndpointType(BrokerAPIEndpointType brokerAPIEndpointType) { - this.brokerAPIEndpointType = brokerAPIEndpointType; - return this; - } - - public Builder withOkHttpClient(OkHttpClient okHttpClient) { - this.okHttpClient = okHttpClient; - return this; - } - - public AlpacaAPI build() { - return new AlpacaAPI(traderKeyID, traderSecretKey, traderOAuthToken, traderAPIEndpointType, - marketDataWebsocketSourceType, brokerAPIKey, brokerAPISecret, brokerAPIEndpointType, okHttpClient); - } - } -} diff --git a/src/main/java/net/jacobpeterson/alpaca/rest/broker/AlpacaBrokerAPI.java b/src/main/java/net/jacobpeterson/alpaca/rest/broker/AlpacaBrokerAPI.java deleted file mode 100644 index cfbd3e75..00000000 --- a/src/main/java/net/jacobpeterson/alpaca/rest/broker/AlpacaBrokerAPI.java +++ /dev/null @@ -1,338 +0,0 @@ -package net.jacobpeterson.alpaca.rest.broker; - -import com.google.gson.JsonElement; -import net.jacobpeterson.alpaca.model.util.apitype.BrokerAPIEndpointType; -import net.jacobpeterson.alpaca.openapi.broker.ApiClient; -import net.jacobpeterson.alpaca.openapi.broker.api.AccountsApi; -import net.jacobpeterson.alpaca.openapi.broker.api.AssetsApi; -import net.jacobpeterson.alpaca.openapi.broker.api.CalendarApi; -import net.jacobpeterson.alpaca.openapi.broker.api.ClockApi; -import net.jacobpeterson.alpaca.openapi.broker.api.CorporateActionsApi; -import net.jacobpeterson.alpaca.openapi.broker.api.CountryInfoApi; -import net.jacobpeterson.alpaca.openapi.broker.api.DocumentsApi; -import net.jacobpeterson.alpaca.openapi.broker.api.FundingApi; -import net.jacobpeterson.alpaca.openapi.broker.api.FundingWalletsApi; -import net.jacobpeterson.alpaca.openapi.broker.api.JournalsApi; -import net.jacobpeterson.alpaca.openapi.broker.api.KycApi; -import net.jacobpeterson.alpaca.openapi.broker.api.LogosApi; -import net.jacobpeterson.alpaca.openapi.broker.api.OAuthApi; -import net.jacobpeterson.alpaca.openapi.broker.api.RebalancingApi; -import net.jacobpeterson.alpaca.openapi.broker.api.ReportingApi; -import net.jacobpeterson.alpaca.openapi.broker.api.TradingApi; -import net.jacobpeterson.alpaca.openapi.broker.api.WatchlistApi; -import net.jacobpeterson.alpaca.openapi.broker.model.AdminActionLegacyNote; -import net.jacobpeterson.alpaca.openapi.broker.model.AdminActionLiquidation; -import net.jacobpeterson.alpaca.openapi.broker.model.AdminActionTransactionCancel; -import net.jacobpeterson.alpaca.openapi.broker.model.JNLC; -import net.jacobpeterson.alpaca.openapi.broker.model.JNLS; -import net.jacobpeterson.alpaca.openapi.broker.model.NonTradeActivity; -import net.jacobpeterson.alpaca.openapi.broker.model.TradeActivity; -import net.jacobpeterson.alpaca.rest.broker.events.EventsApiSSE; -import okhttp3.OkHttpClient; - -import java.util.function.BiFunction; - -import static com.google.common.base.Preconditions.checkNotNull; -import static net.jacobpeterson.alpaca.util.apikey.APIKeyUtil.createBrokerAPIAuthKey; - -/** - * {@link AlpacaBrokerAPI} is the class used to interface with the Alpaca Broker API endpoints. This class is - * thread-safe. - */ -public class AlpacaBrokerAPI { - - // Set validation predicates for 'anyOf' and 'oneOf' models - static { - // 'AccountsApi' models - TradeActivity.isValid = jsonElement -> jsonElement.getAsJsonObject().has("type"); - NonTradeActivity.isValid = jsonElement -> !jsonElement.getAsJsonObject().has("type"); - - // 'JournalsApi' models - final BiFunction transferPredicate = (jnlc, jsonElement) -> { - final String entryType = jsonElement.getAsJsonObject().get("entry_type").getAsString(); - return jnlc ? entryType.equals("JNLC") : entryType.equals("JNLS"); - }; - JNLS.isValid = jsonElement -> transferPredicate.apply(true, jsonElement); - JNLC.isValid = jsonElement -> transferPredicate.apply(false, jsonElement); - - // 'EventsApi' models - AdminActionLegacyNote.isValid = jsonElement -> jsonElement.getAsJsonObject() - .get("type").getAsString().contains("note_admin_event"); - AdminActionLiquidation.isValid = jsonElement -> jsonElement.getAsJsonObject() - .get("type").getAsString().equals("liquidation_admin_event"); - AdminActionTransactionCancel.isValid = jsonElement -> jsonElement.getAsJsonObject() - .get("type").getAsString().equals("transaction_cancel_admin_event"); - } - - private final ApiClient apiClient; - private AccountsApi accounts; - private AssetsApi assets; - private CalendarApi calendar; - private ClockApi clock; - private CorporateActionsApi corporateActions; - private CountryInfoApi countryInfo; - private DocumentsApi documents; - private EventsApiSSE events; - private FundingApi funding; - private FundingWalletsApi fundingWallets; - private JournalsApi journals; - private KycApi kyc; - private LogosApi logos; - private OAuthApi oAuth; - private RebalancingApi rebalancing; - private ReportingApi reporting; - private TradingApi trading; - private WatchlistApi watchlist; - - /** - * Instantiates a new {@link AlpacaBrokerAPI}. - * - * @param brokerAPIKey the Broker API key - * @param brokerAPISecret the Broker API secret - * @param brokerAPIEndpointType the {@link BrokerAPIEndpointType} - * @param okHttpClient an existing {@link OkHttpClient} or null to create a new default - * instance - */ - @SuppressWarnings("UnnecessaryDefault") - public AlpacaBrokerAPI(String brokerAPIKey, String brokerAPISecret, BrokerAPIEndpointType brokerAPIEndpointType, - OkHttpClient okHttpClient) { - checkNotNull(brokerAPIKey); - checkNotNull(brokerAPISecret); - checkNotNull(brokerAPIEndpointType); - checkNotNull(okHttpClient); - - apiClient = new ApiClient(okHttpClient); - apiClient.setServerIndex(switch (brokerAPIEndpointType) { - case SANDBOX -> 0; - case PRODUCTION -> 1; - default -> throw new UnsupportedOperationException(); - }); - apiClient.addDefaultHeader("Authorization", "Basic " + - createBrokerAPIAuthKey(brokerAPIKey, brokerAPISecret)); - } - - /** - * Gets the internal {@link ApiClient}. - * - * @return the {@link ApiClient} - */ - public ApiClient getInternalAPIClient() { - return apiClient; - } - - /** - * Gets the {@link AccountsApi}. Lazily instantiated. - * - * @return the {@link AccountsApi} - */ - public synchronized AccountsApi accounts() { - if (accounts == null) { - accounts = new AccountsApi(apiClient); - } - return accounts; - } - - /** - * Gets the {@link AssetsApi}. Lazily instantiated. - * - * @return the {@link AssetsApi} - */ - public synchronized AssetsApi assets() { - if (assets == null) { - assets = new AssetsApi(apiClient); - } - return assets; - } - - /** - * Gets the {@link CalendarApi}. Lazily instantiated. - * - * @return the {@link CalendarApi} - */ - public synchronized CalendarApi calendar() { - if (calendar == null) { - calendar = new CalendarApi(apiClient); - } - return calendar; - } - - /** - * Gets the {@link ClockApi}. Lazily instantiated. - * - * @return the {@link ClockApi} - */ - public synchronized ClockApi clock() { - if (clock == null) { - clock = new ClockApi(apiClient); - } - return clock; - } - - /** - * Gets the {@link CorporateActionsApi}. Lazily instantiated. - * - * @return the {@link CorporateActionsApi} - */ - public synchronized CorporateActionsApi corporateActions() { - if (corporateActions == null) { - corporateActions = new CorporateActionsApi(apiClient); - } - return corporateActions; - } - - /** - * Gets the {@link CountryInfoApi}. Lazily instantiated. - * - * @return the {@link CountryInfoApi} - */ - public synchronized CountryInfoApi countryInfo() { - if (countryInfo == null) { - countryInfo = new CountryInfoApi(apiClient); - } - return countryInfo; - } - - /** - * Gets the {@link DocumentsApi}. Lazily instantiated. - * - * @return the {@link DocumentsApi} - */ - public synchronized DocumentsApi documents() { - if (documents == null) { - documents = new DocumentsApi(apiClient); - } - return documents; - } - - /** - * Gets the {@link EventsApiSSE}. Lazily instantiated. - * - * @return the {@link EventsApiSSE} - */ - public synchronized EventsApiSSE events() { - if (events == null) { - events = new EventsApiSSE(apiClient); - } - return events; - } - - /** - * Gets the {@link FundingApi}. Lazily instantiated. - * - * @return the {@link FundingApi} - */ - public synchronized FundingApi funding() { - if (funding == null) { - funding = new FundingApi(apiClient); - } - return funding; - } - - /** - * Gets the {@link FundingWalletsApi}. Lazily instantiated. - * - * @return the {@link FundingWalletsApi} - */ - public synchronized FundingWalletsApi fundingWallets() { - if (fundingWallets == null) { - fundingWallets = new FundingWalletsApi(apiClient); - } - return fundingWallets; - } - - /** - * Gets the {@link JournalsApi}. Lazily instantiated. - * - * @return the {@link JournalsApi} - */ - public synchronized JournalsApi journals() { - if (journals == null) { - journals = new JournalsApi(apiClient); - } - return journals; - } - - /** - * Gets the {@link KycApi}. Lazily instantiated. - * - * @return the {@link KycApi} - */ - public synchronized KycApi kyc() { - if (kyc == null) { - kyc = new KycApi(apiClient); - } - return kyc; - } - - /** - * Gets the {@link LogosApi}. Lazily instantiated. - * - * @return the {@link LogosApi} - */ - public synchronized LogosApi logos() { - if (logos == null) { - logos = new LogosApi(apiClient); - } - return logos; - } - - /** - * Gets the {@link OAuthApi}. Lazily instantiated. - * - * @return the {@link OAuthApi} - */ - public synchronized OAuthApi oAuth() { - if (oAuth == null) { - oAuth = new OAuthApi(apiClient); - } - return oAuth; - } - - /** - * Gets the {@link RebalancingApi}. Lazily instantiated. - * - * @return the {@link RebalancingApi} - */ - public synchronized RebalancingApi rebalancing() { - if (rebalancing == null) { - rebalancing = new RebalancingApi(apiClient); - } - return rebalancing; - } - - /** - * Gets the {@link ReportingApi}. Lazily instantiated. - * - * @return the {@link ReportingApi} - */ - public synchronized ReportingApi reporting() { - if (reporting == null) { - reporting = new ReportingApi(apiClient); - } - return reporting; - } - - /** - * Gets the {@link TradingApi}. Lazily instantiated. - * - * @return the {@link TradingApi} - */ - public synchronized TradingApi trading() { - if (trading == null) { - trading = new TradingApi(apiClient); - } - return trading; - } - - /** - * Gets the {@link WatchlistApi}. Lazily instantiated. - * - * @return the {@link WatchlistApi} - */ - public synchronized WatchlistApi watchlist() { - if (watchlist == null) { - watchlist = new WatchlistApi(apiClient); - } - return watchlist; - } -} diff --git a/src/main/java/net/jacobpeterson/alpaca/rest/broker/events/EventsApiSSE.java b/src/main/java/net/jacobpeterson/alpaca/rest/broker/events/EventsApiSSE.java deleted file mode 100644 index 2c18b5ff..00000000 --- a/src/main/java/net/jacobpeterson/alpaca/rest/broker/events/EventsApiSSE.java +++ /dev/null @@ -1,190 +0,0 @@ -package net.jacobpeterson.alpaca.rest.broker.events; - -import com.google.gson.reflect.TypeToken; -import net.jacobpeterson.alpaca.openapi.broker.ApiClient; -import net.jacobpeterson.alpaca.openapi.broker.ApiException; -import net.jacobpeterson.alpaca.openapi.broker.api.EventsApi; -import net.jacobpeterson.alpaca.openapi.broker.model.AccountStatusEvent; -import net.jacobpeterson.alpaca.openapi.broker.model.JournalStatusEvent; -import net.jacobpeterson.alpaca.openapi.broker.model.NonTradeActivityEvent; -import net.jacobpeterson.alpaca.openapi.broker.model.SubscribeToAdminActionSSE200ResponseInner; -import net.jacobpeterson.alpaca.openapi.broker.model.TradeUpdateEvent; -import net.jacobpeterson.alpaca.openapi.broker.model.TradeUpdateEventV2; -import net.jacobpeterson.alpaca.openapi.broker.model.TransferStatusEvent; -import net.jacobpeterson.alpaca.util.sse.SSEListener; -import net.jacobpeterson.alpaca.util.sse.SSERequest; -import okhttp3.Request; -import okhttp3.Response; -import okhttp3.sse.EventSource; -import okhttp3.sse.EventSourceListener; -import okhttp3.sse.EventSources; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.lang.reflect.Type; -import java.time.LocalDate; -import java.time.OffsetDateTime; -import java.util.UUID; - -import static java.util.concurrent.TimeUnit.SECONDS; -import static net.jacobpeterson.alpaca.openapi.broker.JSON.getGson; - -/** - * {@link EventsApiSSE} add SSE support to {@link EventsApi}. - */ -public class EventsApiSSE { - - private static final Logger LOGGER = LoggerFactory.getLogger(EventsApiSSE.class); - - private final EventsApi eventsAPI; - private final EventSource.Factory eventSourceFactory; - - /** - * Instantiates a new {@link EventsApiSSE}. - * - * @param apiClient the api client - */ - public EventsApiSSE(ApiClient apiClient) { - eventsAPI = new EventsApi(apiClient); - eventSourceFactory = EventSources.createFactory(apiClient.getHttpClient().newBuilder() // Shallow clone - .readTimeout(0, SECONDS) - .writeTimeout(0, SECONDS) - .build()); - } - - /** - * See {@link EventsApi#getV1EventsNta(String, String, String, Integer, Integer, String, String, Boolean, UUID)}. - * - * @return a {@link SSERequest} - */ - public SSERequest subscribeToNonTradingActivitiesEvents(String id, String since, String until, Integer sinceId, - Integer untilId, String sinceUlid, String untilUlid, Boolean includePreprocessing, UUID groupId, - SSEListener sseListener) throws ApiException { - final Request request = eventsAPI.getV1EventsNtaCall(id, since, until, sinceId, untilId, sinceUlid, untilUlid, - includePreprocessing, groupId, null).request(); - return new SSERequest(eventSourceFactory.newEventSource(request, createEventSourceListener(sseListener, - new TypeToken() {}.getType()))); - } - - /** - * See {@link EventsApi#subscribeToAdminActionSSE(OffsetDateTime, OffsetDateTime, String, String)}. - * - * @return a {@link SSERequest} - */ - public SSERequest subscribeToAdminAction(OffsetDateTime since, OffsetDateTime until, String sinceId, - String untilId, SSEListener sseListener) throws ApiException { - final Request request = eventsAPI.subscribeToAdminActionSSECall(since, until, sinceId, untilId, null).request(); - return new SSERequest(eventSourceFactory.newEventSource(request, createEventSourceListener(sseListener, - new TypeToken() {}.getType()))); - } - - /** - * See - * {@link EventsApi#subscribeToJournalStatusSSE(OffsetDateTime, OffsetDateTime, Integer, Integer, String, String, - * String)}. - * - * @return a {@link SSERequest} - */ - public SSERequest subscribeToJournalStatus(OffsetDateTime since, OffsetDateTime until, Integer sinceId, - Integer untilId, String sinceUlid, String untilUlid, String id, - SSEListener sseListener) throws ApiException { - final Request request = eventsAPI.subscribeToJournalStatusSSECall(since, until, sinceId, untilId, sinceUlid, - untilUlid, id, null).request(); - return new SSERequest(eventSourceFactory.newEventSource(request, createEventSourceListener(sseListener, - new TypeToken() {}.getType()))); - } - - /** - * See {@link EventsApi#subscribeToTradeSSE(OffsetDateTime, OffsetDateTime, Integer, Integer, String, String)}. - * - * @return a {@link SSERequest} - */ - public SSERequest subscribeToTrade(OffsetDateTime since, OffsetDateTime until, Integer sinceId, - Integer untilId, String sinceUlid, String untilUlid, SSEListener sseListener) - throws ApiException { - final Request request = eventsAPI.subscribeToTradeSSECall(since, until, sinceId, untilId, sinceUlid, untilUlid, - null).request(); - return new SSERequest(eventSourceFactory.newEventSource(request, createEventSourceListener(sseListener, - new TypeToken() {}.getType()))); - } - - /** - * See {@link EventsApi#subscribeToTradeV2SSE(OffsetDateTime, OffsetDateTime, String, String)}. - * - * @return a {@link SSERequest} - */ - public SSERequest subscribeToTradeV2(OffsetDateTime since, OffsetDateTime until, String sinceId, - String untilId, SSEListener sseListener) throws ApiException { - final Request request = eventsAPI.subscribeToTradeV2SSECall(since, until, sinceId, untilId, null).request(); - return new SSERequest(eventSourceFactory.newEventSource(request, createEventSourceListener(sseListener, - new TypeToken() {}.getType()))); - } - - /** - * See - * {@link EventsApi#subscribeToTransferStatusSSE(OffsetDateTime, OffsetDateTime, Integer, Integer, String, - * String)}. - * - * @return a {@link SSERequest} - */ - public SSERequest subscribeToTransferStatus(OffsetDateTime since, OffsetDateTime until, Integer sinceId, - Integer untilId, String sinceUlid, String untilUlid, SSEListener sseListener) - throws ApiException { - final Request request = eventsAPI.subscribeToTransferStatusSSECall(since, until, sinceId, untilId, sinceUlid, - untilUlid, null).request(); - return new SSERequest(eventSourceFactory.newEventSource(request, createEventSourceListener(sseListener, - new TypeToken() {}.getType()))); - } - - /** - * See - * {@link EventsApi#suscribeToAccountStatusSSE(LocalDate, LocalDate, Integer, Integer, String, String, String)}. - * - * @return a {@link SSERequest} - */ - public SSERequest subscribeToAccountStatus(LocalDate since, LocalDate until, Integer sinceId, Integer untilId, - String sinceUlid, String untilUlid, String id, SSEListener sseListener) - throws ApiException { - final Request request = eventsAPI.suscribeToAccountStatusSSECall(since, until, sinceId, untilId, sinceUlid, - untilUlid, id, null).request(); - return new SSERequest(eventSourceFactory.newEventSource(request, createEventSourceListener(sseListener, - new TypeToken() {}.getType()))); - } - - private EventSourceListener createEventSourceListener(SSEListener sseListener, Type responseTypeToken) { - return new EventSourceListener() { - @Override - public void onClosed(@NotNull EventSource eventSource) { - LOGGER.info("Event source closed: eventSource={}", eventSource); - sseListener.onClose(); - } - - @Override - public void onEvent(@NotNull EventSource eventSource, @Nullable String id, @Nullable String type, - @NotNull String data) { - LOGGER.trace("Event source event: eventSource={} id={}, type={}, data={}", eventSource, id, type, data); - sseListener.onMessage(getGson().fromJson(data, responseTypeToken)); - } - - @Override - public void onFailure(@NotNull EventSource eventSource, @Nullable Throwable throwable, - @Nullable Response response) { - if (throwable != null && throwable.getMessage().equals("canceled")) { - sseListener.onClose(); - return; - } - LOGGER.error("Event source failure: eventSource={} throwable={}, response={}", - eventSource, throwable, response); - sseListener.onError(throwable, response); - } - - @Override - public void onOpen(@NotNull EventSource eventSource, @NotNull Response response) { - LOGGER.info("Event source opened: {}", eventSource); - sseListener.onOpen(); - } - }; - } -} diff --git a/src/main/java/net/jacobpeterson/alpaca/rest/marketdata/AlpacaMarketDataAPI.java b/src/main/java/net/jacobpeterson/alpaca/rest/marketdata/AlpacaMarketDataAPI.java deleted file mode 100644 index 4206b485..00000000 --- a/src/main/java/net/jacobpeterson/alpaca/rest/marketdata/AlpacaMarketDataAPI.java +++ /dev/null @@ -1,152 +0,0 @@ -package net.jacobpeterson.alpaca.rest.marketdata; - -import net.jacobpeterson.alpaca.openapi.marketdata.ApiClient; -import net.jacobpeterson.alpaca.openapi.marketdata.api.CorporateActionsApi; -import net.jacobpeterson.alpaca.openapi.marketdata.api.CryptoApi; -import net.jacobpeterson.alpaca.openapi.marketdata.api.ForexApi; -import net.jacobpeterson.alpaca.openapi.marketdata.api.LogosApi; -import net.jacobpeterson.alpaca.openapi.marketdata.api.NewsApi; -import net.jacobpeterson.alpaca.openapi.marketdata.api.OptionApi; -import net.jacobpeterson.alpaca.openapi.marketdata.api.StockApi; -import okhttp3.OkHttpClient; - -import static com.google.common.base.Preconditions.checkArgument; -import static com.google.common.base.Preconditions.checkNotNull; -import static net.jacobpeterson.alpaca.util.apikey.APIKeyUtil.createBrokerAPIAuthKey; - -/** - * {@link AlpacaMarketDataAPI} is the class used to interface with the Alpaca Market Data API endpoints. This class is - * thread-safe. - */ -public class AlpacaMarketDataAPI { - - private final ApiClient apiClient; - private CorporateActionsApi corporateActions; - private CryptoApi crypto; - private ForexApi forex; - private LogosApi logos; - private NewsApi news; - private OptionApi option; - private StockApi stock; - - /** - * Instantiates a new {@link AlpacaMarketDataAPI}. - * - * @param traderKeyID the Trader key ID - * @param traderSecretKey the Trader secret key - * @param brokerAPIKey the Broker API key - * @param brokerAPISecret the Broker API secret - * @param okHttpClient an existing {@link OkHttpClient} or null to create a new default instance - */ - public AlpacaMarketDataAPI(String traderKeyID, String traderSecretKey, - String brokerAPIKey, String brokerAPISecret, OkHttpClient okHttpClient) { - checkArgument((traderKeyID != null && traderSecretKey != null) ^ - (brokerAPIKey != null && brokerAPISecret != null), - "You must specify a (trader key ID and secret key) or an (broker API key and secret)!"); - checkNotNull(okHttpClient); - - final boolean traderKeysGiven = traderKeyID != null && traderSecretKey != null; - apiClient = new ApiClient(okHttpClient); - apiClient.setServerIndex(traderKeysGiven ? 0 : 1); - if (traderKeysGiven) { - apiClient.addDefaultHeader("APCA-API-KEY-ID", traderKeyID); - apiClient.addDefaultHeader("APCA-API-SECRET-KEY", traderSecretKey); - } else { - apiClient.addDefaultHeader("Authorization", "Basic " + - createBrokerAPIAuthKey(brokerAPIKey, brokerAPISecret)); - } - } - - /** - * Gets the internal {@link ApiClient}. - * - * @return the {@link ApiClient} - */ - public ApiClient getInternalAPIClient() { - return apiClient; - } - - /** - * Gets the {@link CorporateActionsApi}. Lazily instantiated. - * - * @return the {@link CorporateActionsApi} - */ - public synchronized CorporateActionsApi corporateActions() { - if (corporateActions == null) { - corporateActions = new CorporateActionsApi(apiClient); - } - return corporateActions; - } - - /** - * Gets the {@link CryptoApi}. Lazily instantiated. - * - * @return the {@link CryptoApi} - */ - public synchronized CryptoApi crypto() { - if (crypto == null) { - crypto = new CryptoApi(apiClient); - } - return crypto; - } - - /** - * Gets the {@link ForexApi}. Lazily instantiated. - * - * @return the {@link ForexApi} - */ - public synchronized ForexApi forex() { - if (forex == null) { - forex = new ForexApi(apiClient); - } - return forex; - } - - /** - * Gets the {@link LogosApi}. Lazily instantiated. - * - * @return the {@link LogosApi} - */ - public synchronized LogosApi logos() { - if (logos == null) { - logos = new LogosApi(apiClient); - } - return logos; - } - - /** - * Gets the {@link NewsApi}. Lazily instantiated. - * - * @return the {@link NewsApi} - */ - public synchronized NewsApi news() { - if (news == null) { - news = new NewsApi(apiClient); - } - return news; - } - - /** - * Gets the {@link OptionApi}. Lazily instantiated. - * - * @return the {@link OptionApi} - */ - public synchronized OptionApi option() { - if (option == null) { - option = new OptionApi(apiClient); - } - return option; - } - - /** - * Gets the {@link StockApi}. Lazily instantiated. - * - * @return the {@link StockApi} - */ - public synchronized StockApi stock() { - if (stock == null) { - stock = new StockApi(apiClient); - } - return stock; - } -} diff --git a/src/main/java/net/jacobpeterson/alpaca/rest/trader/AlpacaTraderAPI.java b/src/main/java/net/jacobpeterson/alpaca/rest/trader/AlpacaTraderAPI.java deleted file mode 100644 index 7b560a26..00000000 --- a/src/main/java/net/jacobpeterson/alpaca/rest/trader/AlpacaTraderAPI.java +++ /dev/null @@ -1,221 +0,0 @@ -package net.jacobpeterson.alpaca.rest.trader; - -import net.jacobpeterson.alpaca.model.util.apitype.TraderAPIEndpointType; -import net.jacobpeterson.alpaca.openapi.trader.ApiClient; -import net.jacobpeterson.alpaca.openapi.trader.api.AccountActivitiesApi; -import net.jacobpeterson.alpaca.openapi.trader.api.AccountConfigurationsApi; -import net.jacobpeterson.alpaca.openapi.trader.api.AccountsApi; -import net.jacobpeterson.alpaca.openapi.trader.api.AssetsApi; -import net.jacobpeterson.alpaca.openapi.trader.api.CalendarApi; -import net.jacobpeterson.alpaca.openapi.trader.api.ClockApi; -import net.jacobpeterson.alpaca.openapi.trader.api.CorporateActionsApi; -import net.jacobpeterson.alpaca.openapi.trader.api.OrdersApi; -import net.jacobpeterson.alpaca.openapi.trader.api.PortfolioHistoryApi; -import net.jacobpeterson.alpaca.openapi.trader.api.PositionsApi; -import net.jacobpeterson.alpaca.openapi.trader.api.WatchlistsApi; -import net.jacobpeterson.alpaca.openapi.trader.model.NonTradeActivities; -import net.jacobpeterson.alpaca.openapi.trader.model.TradingActivities; -import okhttp3.OkHttpClient; - -import static com.google.common.base.Preconditions.checkArgument; -import static com.google.common.base.Preconditions.checkNotNull; - -/** - * {@link AlpacaTraderAPI} is the class used to interface with the Alpaca Trader API endpoints. This class is - * thread-safe. - */ -public class AlpacaTraderAPI { - - // Set validation predicates for 'anyOf' and 'oneOf' models - static { - // 'AccountConfigurationsApi' models - TradingActivities.isValid = jsonElement -> jsonElement.getAsJsonObject().has("type"); - NonTradeActivities.isValid = jsonElement -> !jsonElement.getAsJsonObject().has("type"); - } - - private final ApiClient apiClient; - private AccountActivitiesApi accountActivities; - private AccountConfigurationsApi accountConfigurations; - private AccountsApi accounts; - private AssetsApi assets; - private CalendarApi calendar; - private ClockApi clock; - private CorporateActionsApi traderCorporateActions; - private OrdersApi orders; - private PortfolioHistoryApi portfolioHistory; - private PositionsApi positions; - private WatchlistsApi watchlists; - - /** - * Instantiates a new {@link AlpacaTraderAPI}. - * - * @param traderKeyID the Trader key ID - * @param traderSecretKey the Trader secret key - * @param traderOAuthToken the Trader OAuth token - * @param traderAPIEndpointType the {@link TraderAPIEndpointType} - * @param okHttpClient an existing {@link OkHttpClient} or null to create a new default - * instance - */ - @SuppressWarnings("UnnecessaryDefault") - public AlpacaTraderAPI(String traderKeyID, String traderSecretKey, String traderOAuthToken, - TraderAPIEndpointType traderAPIEndpointType, OkHttpClient okHttpClient) { - checkArgument((traderKeyID != null && traderSecretKey != null) ^ (traderOAuthToken != null), - "You must specify a (trader key ID and secret key) or an (OAuth token)!"); - checkNotNull(traderAPIEndpointType); - checkNotNull(okHttpClient); - - apiClient = new ApiClient(okHttpClient); - apiClient.setServerIndex(switch (traderAPIEndpointType) { - case PAPER -> 0; - case LIVE -> 1; - default -> throw new UnsupportedOperationException(); - }); - if (traderKeyID != null && traderSecretKey != null) { - apiClient.addDefaultHeader("APCA-API-KEY-ID", traderKeyID); - apiClient.addDefaultHeader("APCA-API-SECRET-KEY", traderSecretKey); - } else { - apiClient.addDefaultHeader("Authorization", "Bearer " + traderOAuthToken); - } - } - - /** - * Gets the internal {@link ApiClient}. - * - * @return the {@link ApiClient} - */ - public ApiClient getInternalAPIClient() { - return apiClient; - } - - /** - * Gets the {@link AccountActivitiesApi}. Lazily instantiated. - * - * @return the {@link AccountActivitiesApi} - */ - public synchronized AccountActivitiesApi accountActivities() { - if (accountActivities == null) { - accountActivities = new AccountActivitiesApi(apiClient); - } - return accountActivities; - } - - /** - * Gets the {@link AccountConfigurationsApi}. Lazily instantiated. - * - * @return the {@link AccountConfigurationsApi} - */ - public synchronized AccountConfigurationsApi accountConfigurations() { - if (accountConfigurations == null) { - accountConfigurations = new AccountConfigurationsApi(apiClient); - } - return accountConfigurations; - } - - /** - * Gets the {@link AccountsApi}. Lazily instantiated. - * - * @return {@link AccountsApi} - */ - public synchronized AccountsApi accounts() { - if (accounts == null) { - accounts = new AccountsApi(apiClient); - } - return accounts; - } - - /** - * Gets the {@link AssetsApi}. Lazily instantiated. - * - * @return {@link AssetsApi} - */ - public synchronized AssetsApi assets() { - if (assets == null) { - assets = new AssetsApi(apiClient); - } - return assets; - } - - /** - * Gets the {@link CalendarApi}. Lazily instantiated. - * - * @return {@link CalendarApi} - */ - public synchronized CalendarApi calendar() { - if (calendar == null) { - calendar = new CalendarApi(apiClient); - } - return calendar; - } - - /** - * Gets the {@link ClockApi}. Lazily instantiated. - * - * @return {@link ClockApi} - */ - public synchronized ClockApi clock() { - if (clock == null) { - clock = new ClockApi(apiClient); - } - return clock; - } - - /** - * Gets the {@link CorporateActionsApi}. Lazily instantiated. - * - * @return the {@link CorporateActionsApi} - */ - public synchronized CorporateActionsApi traderCorporateActions() { - if (traderCorporateActions == null) { - traderCorporateActions = new CorporateActionsApi(apiClient); - } - return traderCorporateActions; - } - - /** - * Gets the {@link OrdersApi}. Lazily instantiated. - * - * @return {@link OrdersApi} - */ - public synchronized OrdersApi orders() { - if (orders == null) { - orders = new OrdersApi(apiClient); - } - return orders; - } - - /** - * Gets the {@link PortfolioHistoryApi}. Lazily instantiated. - * - * @return {@link PortfolioHistoryApi} - */ - public synchronized PortfolioHistoryApi portfolioHistory() { - if (portfolioHistory == null) { - portfolioHistory = new PortfolioHistoryApi(apiClient); - } - return portfolioHistory; - } - - /** - * Gets the {@link PositionsApi}. Lazily instantiated. - * - * @return {@link PositionsApi} - */ - public synchronized PositionsApi positions() { - if (positions == null) { - positions = new PositionsApi(apiClient); - } - return positions; - } - - /** - * Gets the {@link WatchlistsApi}. Lazily instantiated. - * - * @return {@link WatchlistsApi} - */ - public synchronized WatchlistsApi watchlists() { - if (watchlists == null) { - watchlists = new WatchlistsApi(apiClient); - } - return watchlists; - } -} diff --git a/src/main/java/net/jacobpeterson/alpaca/util/apikey/APIKeyUtil.java b/src/main/java/net/jacobpeterson/alpaca/util/apikey/APIKeyUtil.java deleted file mode 100644 index c9d0ce07..00000000 --- a/src/main/java/net/jacobpeterson/alpaca/util/apikey/APIKeyUtil.java +++ /dev/null @@ -1,23 +0,0 @@ -package net.jacobpeterson.alpaca.util.apikey; - -import java.util.Base64; - -/** - * {@link APIKeyUtil} is a utility class for Alpaca API keys. - */ -public class APIKeyUtil { - - /** - * Creates a broker API auth key. - * - * @param brokerAPIKey the broker API key - * @param brokerAPISecret the broker API secret - * - * @return the key {@link String} - * - * @see Alpaca Docs - */ - public static String createBrokerAPIAuthKey(String brokerAPIKey, String brokerAPISecret) { - return Base64.getEncoder().encodeToString((brokerAPIKey + ":" + brokerAPISecret).getBytes()); - } -} diff --git a/src/main/java/net/jacobpeterson/alpaca/util/apitype/broker_api_endpoint_type.json b/src/main/java/net/jacobpeterson/alpaca/util/apitype/broker_api_endpoint_type.json deleted file mode 100644 index c4dd1c66..00000000 --- a/src/main/java/net/jacobpeterson/alpaca/util/apitype/broker_api_endpoint_type.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "type": "string", - "javaName": "BrokerAPIEndpointType", - "enum": [ - "sandbox", - "production" - ] -} diff --git a/src/main/java/net/jacobpeterson/alpaca/util/apitype/market_data_websocket_source_type.json b/src/main/java/net/jacobpeterson/alpaca/util/apitype/market_data_websocket_source_type.json deleted file mode 100644 index 5a27c8fb..00000000 --- a/src/main/java/net/jacobpeterson/alpaca/util/apitype/market_data_websocket_source_type.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "type": "string", - "enum": [ - "iex", - "sip" - ] -} diff --git a/src/main/java/net/jacobpeterson/alpaca/util/apitype/trader_api_endpoint_type.json b/src/main/java/net/jacobpeterson/alpaca/util/apitype/trader_api_endpoint_type.json deleted file mode 100644 index 574d4109..00000000 --- a/src/main/java/net/jacobpeterson/alpaca/util/apitype/trader_api_endpoint_type.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "type": "string", - "javaName": "TraderAPIEndpointType", - "enum": [ - "paper", - "live" - ] -} diff --git a/src/main/java/net/jacobpeterson/alpaca/util/sse/SSEListener.java b/src/main/java/net/jacobpeterson/alpaca/util/sse/SSEListener.java deleted file mode 100644 index 5b28942b..00000000 --- a/src/main/java/net/jacobpeterson/alpaca/util/sse/SSEListener.java +++ /dev/null @@ -1,38 +0,0 @@ -package net.jacobpeterson.alpaca.util.sse; - -import okhttp3.Response; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -/** - * {@link SSEListener} is a listener interface for server-sent events (SSE). - * - * @param the data type - */ -public interface SSEListener { - - /** - * Called on SSE open. - */ - void onOpen(); - - /** - * Called on SSE close. - */ - void onClose(); - - /** - * Called on SSE error. - * - * @param throwable the {@link Throwable} - * @param response the {@link Response} - */ - void onError(@Nullable Throwable throwable, @Nullable Response response); - - /** - * Called on SSE message received. - * - * @param message the {@link T} message - */ - void onMessage(@NotNull T message); -} diff --git a/src/main/java/net/jacobpeterson/alpaca/util/sse/SSEListenerAdapter.java b/src/main/java/net/jacobpeterson/alpaca/util/sse/SSEListenerAdapter.java deleted file mode 100644 index 0bd194d1..00000000 --- a/src/main/java/net/jacobpeterson/alpaca/util/sse/SSEListenerAdapter.java +++ /dev/null @@ -1,33 +0,0 @@ -package net.jacobpeterson.alpaca.util.sse; - -import okhttp3.Response; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -/** - * {@link SSEListenerAdapter} and adapter for {@link SSEListener}. - * - * @param the data type - */ -public class SSEListenerAdapter implements SSEListener { - - private static final Logger LOGGER = LoggerFactory.getLogger(SSEListenerAdapter.class); - - public void onOpen() { - LOGGER.info("SSE connection opened."); - } - - public void onClose() { - LOGGER.info("SSE connection closed."); - } - - public void onError(@Nullable Throwable throwable, @Nullable Response response) { - LOGGER.error("SSE connection error! response={}", response, throwable); - } - - public void onMessage(@NotNull T message) { - LOGGER.info("SSE message received: message={}", message); - } -} diff --git a/src/main/java/net/jacobpeterson/alpaca/util/sse/SSERequest.java b/src/main/java/net/jacobpeterson/alpaca/util/sse/SSERequest.java deleted file mode 100644 index 65e5b96e..00000000 --- a/src/main/java/net/jacobpeterson/alpaca/util/sse/SSERequest.java +++ /dev/null @@ -1,36 +0,0 @@ -package net.jacobpeterson.alpaca.util.sse; - -import okhttp3.sse.EventSource; - -/** - * {@link SSERequest} is a {@link EventSource} wrapper class. - */ -public class SSERequest { - - private final EventSource eventSource; - - /** - * Instantiates a new {@link SSERequest}. - * - * @param eventSource the {@link EventSource} - */ - public SSERequest(EventSource eventSource) { - this.eventSource = eventSource; - } - - /** - * Closes the internal {@link EventSource}. - */ - public void close() { - eventSource.cancel(); - } - - /** - * Gets the internal {@link EventSource}. - * - * @return the {@link EventSource} - */ - public EventSource getInternalEventSource() { - return eventSource; - } -} diff --git a/src/main/java/net/jacobpeterson/alpaca/websocket/AlpacaWebsocket.java b/src/main/java/net/jacobpeterson/alpaca/websocket/AlpacaWebsocket.java deleted file mode 100644 index d6f920c2..00000000 --- a/src/main/java/net/jacobpeterson/alpaca/websocket/AlpacaWebsocket.java +++ /dev/null @@ -1,268 +0,0 @@ -package net.jacobpeterson.alpaca.websocket; - -import okhttp3.HttpUrl; -import okhttp3.OkHttpClient; -import okhttp3.Request; -import okhttp3.Response; -import okhttp3.WebSocket; -import okhttp3.WebSocketListener; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.time.Duration; -import java.util.concurrent.CompletableFuture; -import java.util.concurrent.Future; - -import static com.google.common.base.Preconditions.checkNotNull; -import static java.util.concurrent.ForkJoinPool.commonPool; - -/** - * {@link AlpacaWebsocket} represents an abstract websocket for Alpaca. - */ -public abstract class AlpacaWebsocket extends WebSocketListener implements AlpacaWebsocketInterface { - - /** - * Defines a websocket normal closure code. - * - * @see WebSocket#close(int, String) - */ - public static final int WEBSOCKET_NORMAL_CLOSURE_CODE = 1000; - - /** - * Defines a websocket normal closure message. - * - * @see WebSocket#close(int, String) - */ - public static final String WEBSOCKET_NORMAL_CLOSURE_MESSAGE = "Normal closure"; - - /** - * Defines the maximum number of reconnection attempts to be made by an {@link AlpacaWebsocket}. - */ - public static int MAX_RECONNECT_ATTEMPTS = 5; - - /** - * Defines the sleep interval {@link Duration} between reconnection attempts made by an {@link AlpacaWebsocket}. - */ - public static Duration RECONNECT_SLEEP_INTERVAL = Duration.ofSeconds(1); - - private static final Logger LOGGER = LoggerFactory.getLogger(AlpacaWebsocket.class); - - protected final OkHttpClient okHttpClient; - protected final HttpUrl websocketURL; - protected final String websocketName; - protected AlpacaWebsocketStateListener alpacaWebsocketStateListener; - private WebSocket websocket; - protected boolean connected; - protected boolean authenticated; - protected CompletableFuture authenticationMessageFuture; - protected boolean intentionalClose; - protected int reconnectAttempts; - - protected boolean automaticallyReconnect; - - /** - * Instantiates a {@link AlpacaWebsocket}. - * - * @param okHttpClient the {@link OkHttpClient} - * @param websocketURL the websocket {@link HttpUrl} - * @param websocketName the websocket name - */ - protected AlpacaWebsocket(OkHttpClient okHttpClient, HttpUrl websocketURL, String websocketName) { - checkNotNull(okHttpClient); - checkNotNull(websocketURL); - checkNotNull(websocketName); - - this.okHttpClient = okHttpClient; - this.websocketURL = websocketURL; - this.websocketName = websocketName; - - automaticallyReconnect = true; - } - - @Override - public void connect() { - if (!isConnected()) { - final Request websocketRequest = new Request.Builder() - .url(websocketURL) - .get() - .build(); - websocket = okHttpClient.newWebSocket(websocketRequest, this); - } - } - - @Override - public void disconnect() { - if (websocket != null && isConnected()) { - intentionalClose = true; - websocket.close(WEBSOCKET_NORMAL_CLOSURE_CODE, WEBSOCKET_NORMAL_CLOSURE_MESSAGE); - } else { - cleanupState(); - } - } - - @Override - public boolean isConnected() { - return connected; - } - - @Override - public boolean isAuthenticated() { - return authenticated; - } - - @Override - public void onOpen(@NotNull WebSocket webSocket, @NotNull Response response) { - connected = true; - LOGGER.info("{} websocket response: response={}", websocketName, response); - // Call 'onConnection' or 'onReconnection' async to avoid any potential deadlocking since this is called - // in sync with 'onMessage' in OkHttp's 'WebSocketListener' - commonPool().execute(() -> { - if (reconnectAttempts > 0) { - onReconnection(); - } else { - onConnection(); - } - }); - if (alpacaWebsocketStateListener != null) { - alpacaWebsocketStateListener.onOpen(response); - } - } - - @Override - public void onClosed(@NotNull WebSocket webSocket, int code, @NotNull String reason) { - connected = false; - if (intentionalClose) { - LOGGER.info("{} websocket closed. code={}, reason={}", websocketName, code, reason); - cleanupState(); - } else { - LOGGER.error("{} websocket closed unintentionally! code={}, reason={}", websocketName, code, reason); - handleReconnectionAttempt(); - } - if (alpacaWebsocketStateListener != null) { - alpacaWebsocketStateListener.onClosed(code, reason); - } - } - - @Override - public void onFailure(@NotNull WebSocket webSocket, @NotNull Throwable cause, @Nullable Response response) { - if (intentionalClose) { - onClosed(webSocket, WEBSOCKET_NORMAL_CLOSURE_CODE, WEBSOCKET_NORMAL_CLOSURE_MESSAGE); - return; - } - - LOGGER.error("{} websocket failure!", websocketName, cause); - // A websocket failure occurs when either there is a connection failure or when the client throws - // an exception when receiving a message. In either case, OkHttp will close the websocket connection, - // so try to reopen it. - connected = false; - handleReconnectionAttempt(); - if (alpacaWebsocketStateListener != null) { - alpacaWebsocketStateListener.onFailure(cause); - } - } - - /** - * Attempts to reconnect the disconnected {@link #websocket} asynchronously. - */ - private void handleReconnectionAttempt() { - if (!automaticallyReconnect) { - return; - } - if (reconnectAttempts < MAX_RECONNECT_ATTEMPTS) { - LOGGER.info("Attempting to reconnect {} websocket in {} seconds... (attempt {} of {})", - websocketName, RECONNECT_SLEEP_INTERVAL.toSeconds(), reconnectAttempts + 1, MAX_RECONNECT_ATTEMPTS); - reconnectAttempts++; - commonPool().execute(() -> { - try { - Thread.sleep(RECONNECT_SLEEP_INTERVAL.toMillis()); - } catch (InterruptedException interruptedException) { - return; - } - connect(); - }); - } else { - LOGGER.error("Exhausted {} reconnection attempts. Not attempting to reconnect.", MAX_RECONNECT_ATTEMPTS); - cleanupState(); - } - } - - /** - * Cleans up this instance's state variables. - */ - protected void cleanupState() { - websocket = null; - connected = false; - authenticated = false; - if (authenticationMessageFuture != null && !authenticationMessageFuture.isDone()) { - authenticationMessageFuture.complete(false); - } - authenticationMessageFuture = null; - intentionalClose = false; - reconnectAttempts = 0; - } - - /** - * Sends a message to the underlying {@link #websocket}. - * - * @param message the message - */ - protected void sendWebsocketMessage(String message) { - if (!isConnected()) { - throw new IllegalStateException("This websocket must be connected before send a message!"); - } - LOGGER.trace("Websocket message sent: {}", message); - websocket.send(message); - } - - /** - * Called asynchronously when a websocket connection is made. - */ - protected abstract void onConnection(); - - /** - * Called asynchronously when a websocket reconnection is made after unintentional disconnection. - */ - protected abstract void onReconnection(); - - /** - * Sends an authentication message to authenticate this websocket stream. - */ - protected abstract void sendAuthenticationMessage(); - - @Override - public Future getAuthorizationFuture() { - if (authenticationMessageFuture == null) { - authenticationMessageFuture = new CompletableFuture<>(); - } - return authenticationMessageFuture; - } - - @Override - public void setAlpacaWebsocketStateListener(AlpacaWebsocketStateListener alpacaWebsocketStateListener) { - this.alpacaWebsocketStateListener = alpacaWebsocketStateListener; - } - - @Override - public boolean doesAutomaticallyReconnect() { - return automaticallyReconnect; - } - - @Override - public void setAutomaticallyReconnect(boolean automaticallyReconnect) { - this.automaticallyReconnect = automaticallyReconnect; - } - - public OkHttpClient getOkHttpClient() { - return okHttpClient; - } - - public HttpUrl getWebsocketURL() { - return websocketURL; - } - - public WebSocket getWebsocket() { - return websocket; - } -} diff --git a/src/main/java/net/jacobpeterson/alpaca/websocket/AlpacaWebsocketInterface.java b/src/main/java/net/jacobpeterson/alpaca/websocket/AlpacaWebsocketInterface.java deleted file mode 100644 index 48adfb4d..00000000 --- a/src/main/java/net/jacobpeterson/alpaca/websocket/AlpacaWebsocketInterface.java +++ /dev/null @@ -1,94 +0,0 @@ -package net.jacobpeterson.alpaca.websocket; - -import java.util.concurrent.ExecutionException; -import java.util.concurrent.Future; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.TimeoutException; - -/** - * {@link AlpacaWebsocketInterface} defines an interface for Alpaca websockets. - */ -public interface AlpacaWebsocketInterface { - - /** - * Connects this Websocket. - */ - void connect(); - - /** - * Disconnects this Websocket. - */ - void disconnect(); - - /** - * Returns true if this websocket is connected, false otherwise. - * - * @return a boolean - */ - boolean isConnected(); - - /** - * Returns true if this websocket is authenticated, false otherwise. - * - * @return a boolean - */ - boolean isAuthenticated(); - - /** - * Returns true if {@link #isConnected()} and {@link #isAuthenticated()}, false - * otherwise. - * - * @return a boolean - */ - default boolean isValid() { - return isConnected() && isAuthenticated(); - } - - /** - * Gets a {@link Boolean} {@link Future} that completes when an authentication message that is received after a new - * websocket connection indicates successful authentication. - *
- * Note that if this {@link AlpacaWebsocketInterface} is already authorized, the returned {@link Future} will likely - * never complete. - * - * @return a {@link Boolean} {@link Future} - */ - Future getAuthorizationFuture(); - - /** - * Waits for {@link #getAuthorizationFuture()} to complete and returns its value, except when timeout - * time has elapsed, then this will return false. - * - * @param timeout the timeout time - * @param unit the timeout {@link TimeUnit} - * - * @return a boolean - */ - default boolean waitForAuthorization(long timeout, TimeUnit unit) { - try { - return getAuthorizationFuture().get(timeout, unit); - } catch (InterruptedException | ExecutionException | TimeoutException ignored) {} - return false; - } - - /** - * Sets the {@link AlpacaWebsocketStateListener}. - * - * @param alpacaWebsocketStateListener the {@link AlpacaWebsocketStateListener} - */ - void setAlpacaWebsocketStateListener(AlpacaWebsocketStateListener alpacaWebsocketStateListener); - - /** - * Returns true if this websocket automatically reconnects, false otherwise. - * - * @return a boolean - */ - boolean doesAutomaticallyReconnect(); - - /** - * Sets whether to automatically reconnect and reauthenticate on a websocket failure. true by default. - * - * @param automaticallyReconnect true to automatically reconnect, false otherwise - */ - void setAutomaticallyReconnect(boolean automaticallyReconnect); -} diff --git a/src/main/java/net/jacobpeterson/alpaca/websocket/AlpacaWebsocketStateListener.java b/src/main/java/net/jacobpeterson/alpaca/websocket/AlpacaWebsocketStateListener.java deleted file mode 100644 index 2667c2ca..00000000 --- a/src/main/java/net/jacobpeterson/alpaca/websocket/AlpacaWebsocketStateListener.java +++ /dev/null @@ -1,33 +0,0 @@ -package net.jacobpeterson.alpaca.websocket; - -import okhttp3.Response; -import okhttp3.WebSocket; - -/** - * {@link AlpacaWebsocketStateListener} is an interface to listen for various state changes in an {@link WebSocket} - * instance. - */ -public interface AlpacaWebsocketStateListener { - - /** - * Called when the {@link WebSocket} is connected. - * - * @param response the HTTP websocket upgrade {@link Response} - */ - void onOpen(Response response); - - /** - * Called when the {@link WebSocket} is disconnected. - * - * @param code the code - * @param reason the reason - */ - void onClosed(int code, String reason); - - /** - * Called when a {@link WebSocket} error has occurred. - * - * @param cause the cause {@link Throwable} - */ - void onFailure(Throwable cause); -} diff --git a/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/MarketDataWebsocket.java b/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/MarketDataWebsocket.java deleted file mode 100644 index fbb7dca9..00000000 --- a/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/MarketDataWebsocket.java +++ /dev/null @@ -1,198 +0,0 @@ -package net.jacobpeterson.alpaca.websocket.marketdata; - -import com.google.gson.JsonElement; -import com.google.gson.JsonObject; -import net.jacobpeterson.alpaca.model.websocket.marketdata.model.MarketDataMessage; -import net.jacobpeterson.alpaca.model.websocket.marketdata.model.control.ErrorMessage; -import net.jacobpeterson.alpaca.model.websocket.marketdata.model.control.SuccessMessage; -import net.jacobpeterson.alpaca.websocket.AlpacaWebsocket; -import okhttp3.HttpUrl; -import okhttp3.OkHttpClient; -import okhttp3.WebSocket; -import org.jetbrains.annotations.NotNull; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.util.Set; -import java.util.function.Function; - -import static com.google.common.collect.Sets.difference; -import static com.google.gson.JsonParser.parseString; -import static java.util.concurrent.TimeUnit.SECONDS; -import static net.jacobpeterson.alpaca.model.websocket.marketdata.model.control.SuccessMessageType.AUTHENTICATED; -import static net.jacobpeterson.alpaca.openapi.marketdata.JSON.getGson; - -/** - * {@link MarketDataWebsocket} is an abstract {@link AlpacaWebsocket} implementation for - * Market Data Streaming. - * - * @param the 'message type' type - * @param the 'subscription message' type - * @param the 'listener' type - */ -public abstract class MarketDataWebsocket extends AlpacaWebsocket - implements MarketDataWebsocketInterface { - - private static final Logger LOGGER = LoggerFactory.getLogger(MarketDataWebsocket.class); - private static final Set AUTH_FAILURE_MESSAGES = Set.of("auth failed", "auth timeout", "not authenticated"); - - protected final String authKey; - protected final String authSecret; - protected final Class messageTypeClass; - protected final Class subscriptionsMessageClass; - protected S subscriptionsMessage; - protected L listener; - - /** - * Instantiates a new {@link MarketDataWebsocket}. - * - * @param okHttpClient the {@link OkHttpClient} - * @param websocketURL the websocket {@link HttpUrl} - * @param websocketMarketDataTypeName the websocket market data type name {@link String} - * @param traderKeyID the trader key ID - * @param traderSecretKey the trader secret key - * @param brokerAPIKey the broker API key - * @param brokerAPISecret the broker API secret - * @param messageTypeClass the {@link T} message type {@link Class} - * @param subscriptionsMessageClass the {@link S} subscription message {@link Class} - */ - protected MarketDataWebsocket(OkHttpClient okHttpClient, HttpUrl websocketURL, String websocketMarketDataTypeName, - String traderKeyID, String traderSecretKey, String brokerAPIKey, String brokerAPISecret, - Class messageTypeClass, Class subscriptionsMessageClass) { - super(okHttpClient, websocketURL, websocketMarketDataTypeName + " Market Data"); - final boolean traderKeysGiven = traderKeyID != null && traderSecretKey != null; - this.authKey = traderKeysGiven ? traderKeyID : brokerAPIKey; - this.authSecret = traderKeysGiven ? traderSecretKey : brokerAPISecret; - this.messageTypeClass = messageTypeClass; - this.subscriptionsMessageClass = subscriptionsMessageClass; - } - - @Override - protected void cleanupState() { - super.cleanupState(); - subscriptionsMessage = null; - } - - @Override - protected void onConnection() { - sendAuthenticationMessage(); - } - - @Override - protected void onReconnection() { - sendAuthenticationMessage(); - if (waitForAuthorization(5, SECONDS) && subscriptionsMessage != null) { - sendSubscriptionMessage(subscriptionsMessage, true); - } - } - - @Override - protected void sendAuthenticationMessage() { - // Ensure that the authorization Future exists - getAuthorizationFuture(); - - final JsonObject authObject = new JsonObject(); - authObject.addProperty("action", "auth"); - authObject.addProperty("key", authKey); - authObject.addProperty("secret", authSecret); - - LOGGER.info("{} websocket sending authentication message...", websocketName); - sendWebsocketMessage(authObject.toString()); - } - - @Override - public void onMessage(@NotNull WebSocket webSocket, @NotNull String message) { // Text framing - LOGGER.trace("Websocket message received: {}", message); - - // Loop through message array and handle each message according to its type - for (JsonElement arrayElement : parseString(message).getAsJsonArray()) { - final JsonObject messageObject = arrayElement.getAsJsonObject(); - final T messageType = getGson().fromJson(messageObject.get("T"), messageTypeClass); - if (isSuccessMessageType(messageType)) { - final SuccessMessage successMessage = getGson().fromJson(messageObject, SuccessMessage.class); - if (successMessage.getMessageType() == AUTHENTICATED) { - LOGGER.info("{} websocket authenticated.", websocketName); - authenticated = true; - if (authenticationMessageFuture != null) { - authenticationMessageFuture.complete(true); - } - } - } else if (isErrorMessageType(messageType)) { - final ErrorMessage errorMessage = getGson().fromJson(messageObject, ErrorMessage.class); - if (AUTH_FAILURE_MESSAGES.contains(errorMessage.getMessage()) && authenticationMessageFuture != null) { - authenticated = false; - authenticationMessageFuture.complete(false); - throw new RuntimeException(websocketName + " websocket authentication failed!"); - } else { - throw new RuntimeException(websocketName + " websocket error! Message: " + errorMessage); - } - } else if (isSubscriptionMessageType(messageType)) { - subscriptionsMessage = getGson().fromJson(messageObject, subscriptionsMessageClass); - } else if (listener != null) { - callListenerWithMessage(messageType, messageObject); - } - } - } - - /** - * Sets the websocket stream's subscriptions for a specific message type. - * - * @param previousSubscriptions the previous subscriptions symbol {@link Set} - * @param newSubscriptions the new subscriptions symbol {@link Set} - * @param subscriptionUpdateObjectCreator the subscription update object creator {@link Function} - */ - protected void setSubscriptions(@NotNull Set previousSubscriptions, - @NotNull Set newSubscriptions, @NotNull Function, S> subscriptionUpdateObjectCreator) { - // Unsubscribe from previous subscriptions - final Set unsubscribeSet = difference(previousSubscriptions, newSubscriptions); - if (!unsubscribeSet.isEmpty()) { - sendSubscriptionMessage(subscriptionUpdateObjectCreator.apply(unsubscribeSet), false); - } - // Subscribe to new subscriptions - final Set subscribeSet = difference(newSubscriptions, previousSubscriptions); - if (!subscribeSet.isEmpty()) { - sendSubscriptionMessage(subscriptionUpdateObjectCreator.apply(subscribeSet), true); - } - } - - private void sendSubscriptionMessage(S subscriptionsMessage, boolean subscribe) { - final JsonObject subscribeObject = getGson().toJsonTree(subscriptionsMessage).getAsJsonObject(); - subscribeObject.addProperty("action", subscribe ? "subscribe" : "unsubscribe"); - sendWebsocketMessage(getGson().toJson(subscribeObject)); - } - - /** - * Whether the given messageType is "success". - * - * @param messageType the message type - * - * @return a boolean - */ - protected abstract boolean isSuccessMessageType(T messageType); - - /** - * Whether the given messageType is "error". - * - * @param messageType the message type - * - * @return a boolean - */ - protected abstract boolean isErrorMessageType(T messageType); - - /** - * Whether the given messageType is "subscription". - * - * @param messageType the message type - * - * @return a boolean - */ - protected abstract boolean isSubscriptionMessageType(T messageType); - - /** - * Calls the {@link #listener} with a {@link MarketDataMessage} from a {@link JsonObject}. - * - * @param messageType the message type - * @param messageObject the message {@link JsonObject} - */ - protected abstract void callListenerWithMessage(T messageType, JsonObject messageObject); -} diff --git a/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/MarketDataWebsocketInterface.java b/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/MarketDataWebsocketInterface.java deleted file mode 100644 index 989389eb..00000000 --- a/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/MarketDataWebsocketInterface.java +++ /dev/null @@ -1,8 +0,0 @@ -package net.jacobpeterson.alpaca.websocket.marketdata; - -import net.jacobpeterson.alpaca.websocket.AlpacaWebsocketInterface; - -/** - * {@link MarketDataWebsocketInterface} is an {@link AlpacaWebsocketInterface} for {@link MarketDataWebsocket}. - */ -public interface MarketDataWebsocketInterface extends AlpacaWebsocketInterface {} diff --git a/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/model/control/error_message.json b/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/model/control/error_message.json deleted file mode 100644 index 655d6a03..00000000 --- a/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/model/control/error_message.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "type": "object", - "extends": { - "existingJavaType": "net.jacobpeterson.alpaca.model.websocket.marketdata.model.MarketDataMessage" - }, - "properties": { - "code": { - "existingJavaType": "java.lang.Integer", - "title": "The error code." - }, - "msg": { - "existingJavaType": "java.lang.String", - "javaName": "message", - "title": "The error message." - } - } -} diff --git a/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/model/control/success_message.json b/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/model/control/success_message.json deleted file mode 100644 index e471b400..00000000 --- a/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/model/control/success_message.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "type": "object", - "extends": { - "existingJavaType": "net.jacobpeterson.alpaca.model.websocket.marketdata.model.MarketDataMessage" - }, - "properties": { - "msg": { - "existingJavaType": "net.jacobpeterson.alpaca.model.websocket.marketdata.model.control.SuccessMessageType", - "javaName": "messageType", - "title": "The success message type." - } - } -} diff --git a/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/model/control/success_message_type.json b/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/model/control/success_message_type.json deleted file mode 100644 index 61c6c983..00000000 --- a/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/model/control/success_message_type.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "type": "string", - "enum": [ - "success", - "authenticated" - ] -} diff --git a/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/model/market_data_message.json b/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/model/market_data_message.json deleted file mode 100644 index e1a8346a..00000000 --- a/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/model/market_data_message.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "type": "object", - "properties": {} -} diff --git a/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/streams/crypto/CryptoMarketDataListener.java b/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/streams/crypto/CryptoMarketDataListener.java deleted file mode 100644 index ae8efe52..00000000 --- a/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/streams/crypto/CryptoMarketDataListener.java +++ /dev/null @@ -1,55 +0,0 @@ -package net.jacobpeterson.alpaca.websocket.marketdata.streams.crypto; - -import net.jacobpeterson.alpaca.model.websocket.marketdata.streams.crypto.model.bar.CryptoBarMessage; -import net.jacobpeterson.alpaca.model.websocket.marketdata.streams.crypto.model.orderbook.CryptoOrderBookMessage; -import net.jacobpeterson.alpaca.model.websocket.marketdata.streams.crypto.model.quote.CryptoQuoteMessage; -import net.jacobpeterson.alpaca.model.websocket.marketdata.streams.crypto.model.trade.CryptoTradeMessage; - -/** - * {@link CryptoMarketDataListener} defines a listener interface for {@link CryptoMarketDataWebsocketInterface} - * messages. - */ -public interface CryptoMarketDataListener { - - /** - * Called when a {@link CryptoTradeMessage} is received. - * - * @param trade the {@link CryptoTradeMessage} - */ - void onTrade(CryptoTradeMessage trade); - - /** - * Called when a {@link CryptoQuoteMessage} is received. - * - * @param quote the {@link CryptoQuoteMessage} - */ - void onQuote(CryptoQuoteMessage quote); - - /** - * Called when a {@link CryptoBarMessage} is received. - * - * @param bar the {@link CryptoBarMessage} - */ - void onMinuteBar(CryptoBarMessage bar); - - /** - * Called when a {@link CryptoBarMessage} is received. - * - * @param bar the {@link CryptoBarMessage} - */ - void onDailyBar(CryptoBarMessage bar); - - /** - * Called when a {@link CryptoBarMessage} is received. - * - * @param bar the {@link CryptoBarMessage} - */ - void onUpdatedBar(CryptoBarMessage bar); - - /** - * Called when a {@link CryptoOrderBookMessage} is received. - * - * @param orderBook the {@link CryptoOrderBookMessage} - */ - void onOrderBook(CryptoOrderBookMessage orderBook); -} diff --git a/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/streams/crypto/CryptoMarketDataListenerAdapter.java b/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/streams/crypto/CryptoMarketDataListenerAdapter.java deleted file mode 100644 index cf1cc348..00000000 --- a/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/streams/crypto/CryptoMarketDataListenerAdapter.java +++ /dev/null @@ -1,30 +0,0 @@ -package net.jacobpeterson.alpaca.websocket.marketdata.streams.crypto; - -import net.jacobpeterson.alpaca.model.websocket.marketdata.streams.crypto.model.bar.CryptoBarMessage; -import net.jacobpeterson.alpaca.model.websocket.marketdata.streams.crypto.model.orderbook.CryptoOrderBookMessage; -import net.jacobpeterson.alpaca.model.websocket.marketdata.streams.crypto.model.quote.CryptoQuoteMessage; -import net.jacobpeterson.alpaca.model.websocket.marketdata.streams.crypto.model.trade.CryptoTradeMessage; - -/** - * {@link CryptoMarketDataListenerAdapter} is an adapter for {@link CryptoMarketDataListener}. - */ -public class CryptoMarketDataListenerAdapter implements CryptoMarketDataListener { - - @Override - public void onTrade(CryptoTradeMessage trade) {} - - @Override - public void onQuote(CryptoQuoteMessage quote) {} - - @Override - public void onMinuteBar(CryptoBarMessage bar) {} - - @Override - public void onDailyBar(CryptoBarMessage bar) {} - - @Override - public void onUpdatedBar(CryptoBarMessage bar) {} - - @Override - public void onOrderBook(CryptoOrderBookMessage orderBook) {} -} diff --git a/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/streams/crypto/CryptoMarketDataWebsocket.java b/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/streams/crypto/CryptoMarketDataWebsocket.java deleted file mode 100644 index ed3a88ad..00000000 --- a/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/streams/crypto/CryptoMarketDataWebsocket.java +++ /dev/null @@ -1,175 +0,0 @@ -package net.jacobpeterson.alpaca.websocket.marketdata.streams.crypto; - -import com.google.gson.JsonObject; -import net.jacobpeterson.alpaca.model.websocket.marketdata.streams.crypto.model.CryptoMarketDataMessageType; -import net.jacobpeterson.alpaca.model.websocket.marketdata.streams.crypto.model.bar.CryptoBarMessage; -import net.jacobpeterson.alpaca.model.websocket.marketdata.streams.crypto.model.control.CryptoSubscriptionsMessage; -import net.jacobpeterson.alpaca.model.websocket.marketdata.streams.crypto.model.orderbook.CryptoOrderBookMessage; -import net.jacobpeterson.alpaca.model.websocket.marketdata.streams.crypto.model.quote.CryptoQuoteMessage; -import net.jacobpeterson.alpaca.model.websocket.marketdata.streams.crypto.model.trade.CryptoTradeMessage; -import net.jacobpeterson.alpaca.websocket.marketdata.MarketDataWebsocket; -import okhttp3.HttpUrl; -import okhttp3.OkHttpClient; - -import java.util.Set; - -import static net.jacobpeterson.alpaca.model.websocket.marketdata.streams.crypto.model.CryptoMarketDataMessageType.ERROR; -import static net.jacobpeterson.alpaca.model.websocket.marketdata.streams.crypto.model.CryptoMarketDataMessageType.SUBSCRIPTION; -import static net.jacobpeterson.alpaca.model.websocket.marketdata.streams.crypto.model.CryptoMarketDataMessageType.SUCCESS; -import static net.jacobpeterson.alpaca.openapi.marketdata.JSON.getGson; - -/** - * {@link CryptoMarketDataWebsocket} is an implementation for {@link CryptoMarketDataWebsocketInterface}. - */ -public class CryptoMarketDataWebsocket - extends MarketDataWebsocket - implements CryptoMarketDataWebsocketInterface { - - /** - * Instantiates a new {@link CryptoMarketDataWebsocket}. - * - * @param okHttpClient the {@link OkHttpClient} - * @param traderKeyID the trader key ID - * @param traderSecretKey the trader secret key - * @param brokerAPIKey the broker API key - * @param brokerAPISecret the broker API secret - */ - public CryptoMarketDataWebsocket(OkHttpClient okHttpClient, String traderKeyID, String traderSecretKey, - String brokerAPIKey, String brokerAPISecret) { - super(okHttpClient, new HttpUrl.Builder() - .scheme("https") - .host("stream.data.alpaca.markets") - .addPathSegments("v1beta3/crypto/us") - .build(), - "Crypto", traderKeyID, traderSecretKey, brokerAPIKey, brokerAPISecret, - CryptoMarketDataMessageType.class, CryptoSubscriptionsMessage.class); - } - - @Override - protected boolean isSuccessMessageType(CryptoMarketDataMessageType messageType) { - return messageType == SUCCESS; - } - - @Override - protected boolean isErrorMessageType(CryptoMarketDataMessageType messageType) { - return messageType == ERROR; - } - - @Override - protected boolean isSubscriptionMessageType(CryptoMarketDataMessageType messageType) { - return messageType == SUBSCRIPTION; - } - - @Override - protected void callListenerWithMessage(CryptoMarketDataMessageType messageType, JsonObject messageObject) { - switch (messageType) { - case TRADES: - listener.onTrade(getGson().fromJson(messageObject, CryptoTradeMessage.class)); - break; - case QUOTES: - listener.onQuote(getGson().fromJson(messageObject, CryptoQuoteMessage.class)); - break; - case MINUTE_BARS: - listener.onMinuteBar(getGson().fromJson(messageObject, CryptoBarMessage.class)); - break; - case DAILY_BARS: - listener.onDailyBar(getGson().fromJson(messageObject, CryptoBarMessage.class)); - break; - case UPDATED_BARS: - listener.onUpdatedBar(getGson().fromJson(messageObject, CryptoBarMessage.class)); - break; - case ORDER_BOOKS: - listener.onOrderBook(getGson().fromJson(messageObject, CryptoOrderBookMessage.class)); - break; - default: - throw new UnsupportedOperationException(); - } - } - - @Override - public void setListener(CryptoMarketDataListener listener) { - this.listener = listener; - } - - @Override - public void setTradeSubscriptions(Set symbols) { - symbols = symbols == null ? Set.of() : symbols; - setSubscriptions(getTradeSubscriptions(), symbols, - set -> newEmptyCryptoSubscriptionsMessage().withTrades(set)); - } - - @Override - public Set getTradeSubscriptions() { - return subscriptionsMessage == null ? Set.of() : subscriptionsMessage.getTrades(); - } - - @Override - public void setQuoteSubscriptions(Set symbols) { - symbols = symbols == null ? Set.of() : symbols; - setSubscriptions(getQuoteSubscriptions(), symbols, - set -> newEmptyCryptoSubscriptionsMessage().withQuotes(set)); - } - - @Override - public Set getQuoteSubscriptions() { - return subscriptionsMessage == null ? Set.of() : subscriptionsMessage.getQuotes(); - } - - @Override - public void setMinuteBarSubscriptions(Set symbols) { - symbols = symbols == null ? Set.of() : symbols; - setSubscriptions(getMinuteBarSubscriptions(), symbols, - set -> newEmptyCryptoSubscriptionsMessage().withMinuteBars(set)); - } - - @Override - public Set getMinuteBarSubscriptions() { - return subscriptionsMessage == null ? Set.of() : subscriptionsMessage.getMinuteBars(); - } - - @Override - public void setDailyBarSubscriptions(Set symbols) { - symbols = symbols == null ? Set.of() : symbols; - setSubscriptions(getDailyBarSubscriptions(), symbols, - set -> newEmptyCryptoSubscriptionsMessage().withDailyBars(set)); - } - - @Override - public Set getDailyBarSubscriptions() { - return subscriptionsMessage == null ? Set.of() : subscriptionsMessage.getDailyBars(); - } - - @Override - public void setUpdatedBarSubscriptions(Set symbols) { - symbols = symbols == null ? Set.of() : symbols; - setSubscriptions(getUpdatedBarSubscriptions(), symbols, - set -> newEmptyCryptoSubscriptionsMessage().withUpdatedBars(set)); - } - - @Override - public Set getUpdatedBarSubscriptions() { - return subscriptionsMessage == null ? Set.of() : subscriptionsMessage.getUpdatedBars(); - } - - @Override - public void setOrderBookSubscriptions(Set symbols) { - symbols = symbols == null ? Set.of() : symbols; - setSubscriptions(getOrderBookSubscriptions(), symbols, - set -> newEmptyCryptoSubscriptionsMessage().withOrderBooks(set)); - } - - @Override - public Set getOrderBookSubscriptions() { - return subscriptionsMessage == null ? Set.of() : subscriptionsMessage.getOrderBooks(); - } - - private CryptoSubscriptionsMessage newEmptyCryptoSubscriptionsMessage() { - return new CryptoSubscriptionsMessage() - .withTrades(null) - .withQuotes(null) - .withMinuteBars(null) - .withDailyBars(null) - .withUpdatedBars(null) - .withOrderBooks(null); - } -} diff --git a/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/streams/crypto/CryptoMarketDataWebsocketInterface.java b/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/streams/crypto/CryptoMarketDataWebsocketInterface.java deleted file mode 100644 index a24e250f..00000000 --- a/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/streams/crypto/CryptoMarketDataWebsocketInterface.java +++ /dev/null @@ -1,116 +0,0 @@ -package net.jacobpeterson.alpaca.websocket.marketdata.streams.crypto; - -import net.jacobpeterson.alpaca.model.websocket.marketdata.streams.crypto.model.CryptoMarketDataMessageType; -import net.jacobpeterson.alpaca.websocket.marketdata.MarketDataWebsocketInterface; - -import java.util.Set; - -/** - * {@link CryptoMarketDataWebsocketInterface} is a {@link MarketDataWebsocketInterface} for - * {@link CryptoMarketDataWebsocket}. - */ -public interface CryptoMarketDataWebsocketInterface extends MarketDataWebsocketInterface { - - /** - * Sets the {@link CryptoMarketDataListener}. - * - * @param listener the {@link CryptoMarketDataListener} - */ - void setListener(CryptoMarketDataListener listener); - - /** - * Subscribes the given symbols to {@link CryptoMarketDataMessageType#TRADES}. This will remove all - * previous {@link CryptoMarketDataMessageType#QUOTES} subscriptions. - * - * @param symbols the {@link Set} of symbols (use an asterisk ("*") as a wildcard to subscribe to all available - * symbols) or null to unsubscribe from all symbols - */ - void setTradeSubscriptions(Set symbols); - - /** - * Gets the current symbols subscribed to {@link CryptoMarketDataMessageType#TRADES}. - * - * @return a {@link Set} of {@link String} symbols - */ - Set getTradeSubscriptions(); - - /** - * Subscribes the given symbols to {@link CryptoMarketDataMessageType#QUOTES}. This will remove all - * previous {@link CryptoMarketDataMessageType#QUOTES} subscriptions. - * - * @param symbols the {@link Set} of symbols (use an asterisk ("*") as a wildcard to subscribe to all available - * symbols) or null to unsubscribe from all symbols - */ - void setQuoteSubscriptions(Set symbols); - - /** - * Gets the current symbols subscribed to {@link CryptoMarketDataMessageType#QUOTES}. - * - * @return a {@link Set} of {@link String} symbols - */ - Set getQuoteSubscriptions(); - - /** - * Subscribes the given symbols to {@link CryptoMarketDataMessageType#MINUTE_BARS}. This will remove - * all previous {@link CryptoMarketDataMessageType#QUOTES} subscriptions. - * - * @param symbols the {@link Set} of symbols (use an asterisk ("*") as a wildcard to subscribe to all available - * symbols) or null to unsubscribe from all symbols - */ - void setMinuteBarSubscriptions(Set symbols); - - /** - * Gets the current symbols subscribed to {@link CryptoMarketDataMessageType#MINUTE_BARS}. - * - * @return a {@link Set} of {@link String} symbols - */ - Set getMinuteBarSubscriptions(); - - /** - * Subscribes the given symbols to {@link CryptoMarketDataMessageType#DAILY_BARS}. This will remove all - * previous {@link CryptoMarketDataMessageType#QUOTES} subscriptions. - * - * @param symbols the {@link Set} of symbols (use an asterisk ("*") as a wildcard to subscribe to all available - * symbols) or null to unsubscribe from all symbols - */ - void setDailyBarSubscriptions(Set symbols); - - /** - * Gets the current symbols subscribed to {@link CryptoMarketDataMessageType#DAILY_BARS}. - * - * @return a {@link Set} of {@link String} symbols - */ - Set getDailyBarSubscriptions(); - - /** - * Subscribes the given symbols to {@link CryptoMarketDataMessageType#UPDATED_BARS}. This will remove - * all previous {@link CryptoMarketDataMessageType#QUOTES} subscriptions. - * - * @param symbols the {@link Set} of symbols (use an asterisk ("*") as a wildcard to subscribe to all available - * symbols) or null to unsubscribe from all symbols - */ - void setUpdatedBarSubscriptions(Set symbols); - - /** - * Gets the current symbols subscribed to {@link CryptoMarketDataMessageType#UPDATED_BARS}. - * - * @return a {@link Set} of {@link String} symbols - */ - Set getUpdatedBarSubscriptions(); - - /** - * Subscribes the given symbols to {@link CryptoMarketDataMessageType#ORDER_BOOKS}. This will remove - * all previous {@link CryptoMarketDataMessageType#QUOTES} subscriptions. - * - * @param symbols the {@link Set} of symbols (use an asterisk ("*") as a wildcard to subscribe to all available - * symbols) or null to unsubscribe from all symbols - */ - void setOrderBookSubscriptions(Set symbols); - - /** - * Gets the current symbols subscribed to {@link CryptoMarketDataMessageType#ORDER_BOOKS}. - * - * @return a {@link Set} of {@link String} symbols - */ - Set getOrderBookSubscriptions(); -} diff --git a/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/streams/crypto/model/bar/crypto_bar_message.json b/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/streams/crypto/model/bar/crypto_bar_message.json deleted file mode 100644 index f24e3e99..00000000 --- a/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/streams/crypto/model/bar/crypto_bar_message.json +++ /dev/null @@ -1,53 +0,0 @@ -{ - "type": "object", - "extends": { - "existingJavaType": "net.jacobpeterson.alpaca.model.websocket.marketdata.streams.crypto.model.CryptoMarketDataMessage" - }, - "properties": { - "S": { - "existingJavaType": "java.lang.String", - "javaName": "symbol", - "title": "The symbol." - }, - "o": { - "existingJavaType": "java.lang.Double", - "javaName": "open", - "title": "The open price." - }, - "h": { - "existingJavaType": "java.lang.Double", - "javaName": "high", - "title": "The high price." - }, - "l": { - "existingJavaType": "java.lang.Double", - "javaName": "low", - "title": "The low price." - }, - "c": { - "existingJavaType": "java.lang.Double", - "javaName": "close", - "title": "The close price." - }, - "t": { - "existingJavaType": "java.time.OffsetDateTime", - "javaName": "timestamp", - "title": "The timestamp." - }, - "v": { - "existingJavaType": "java.lang.Long", - "javaName": "volume", - "title": "The volume." - }, - "n": { - "existingJavaType": "java.lang.Long", - "javaName": "tradeCount", - "title": "The trade count." - }, - "vw": { - "existingJavaType": "java.lang.Double", - "javaName": "vwap", - "title": "The VWAP (Volume Weighted Average Price)." - } - } -} diff --git a/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/streams/crypto/model/control/crypto_subscriptions_message.json b/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/streams/crypto/model/control/crypto_subscriptions_message.json deleted file mode 100644 index 6d8438a8..00000000 --- a/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/streams/crypto/model/control/crypto_subscriptions_message.json +++ /dev/null @@ -1,34 +0,0 @@ -{ - "type": "object", - "extends": { - "existingJavaType": "net.jacobpeterson.alpaca.model.websocket.marketdata.streams.crypto.model.CryptoMarketDataMessage" - }, - "properties": { - "trades": { - "existingJavaType": "java.util.Set", - "title": "The {@link java.util.Set} of symbols subscribed to trades." - }, - "quotes": { - "existingJavaType": "java.util.Set", - "title": "The {@link java.util.Set} of symbols subscribed to quotes." - }, - "bars": { - "existingJavaType": "java.util.Set", - "javaName": "minuteBars", - "title": "The {@link java.util.Set} of symbols subscribed to minute bars." - }, - "dailyBars": { - "existingJavaType": "java.util.Set", - "title": "The {@link java.util.Set} of symbols subscribed to daily bars." - }, - "updatedBars": { - "existingJavaType": "java.util.Set", - "title": "The {@link java.util.Set} of symbols subscribed to updated bars." - }, - "orderbooks": { - "existingJavaType": "java.util.Set", - "javaName": "orderBooks", - "title": "The {@link java.util.Set} of symbols subscribed to order books." - } - } -} diff --git a/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/streams/crypto/model/crypto_market_data_message.json b/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/streams/crypto/model/crypto_market_data_message.json deleted file mode 100644 index 0ced4e8f..00000000 --- a/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/streams/crypto/model/crypto_market_data_message.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "type": "object", - "extends": { - "existingJavaType": "net.jacobpeterson.alpaca.model.websocket.marketdata.model.MarketDataMessage" - }, - "properties": { - "T": { - "existingJavaType": "net.jacobpeterson.alpaca.model.websocket.marketdata.streams.crypto.model.CryptoMarketDataMessageType", - "javaName": "messageType", - "title": "The {@link net.jacobpeterson.alpaca.model.websocket.marketdata.streams.crypto.model.CryptoMarketDataMessageType}." - } - } -} diff --git a/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/streams/crypto/model/crypto_market_data_message_type.json b/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/streams/crypto/model/crypto_market_data_message_type.json deleted file mode 100644 index a84d1728..00000000 --- a/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/streams/crypto/model/crypto_market_data_message_type.json +++ /dev/null @@ -1,43 +0,0 @@ -{ - "type": "string", - "enum": [ - "success", - "error", - "subscription", - "t", - "q", - "b", - "d", - "u", - "o" - ], - "javaEnums": [ - { - "name": "SUCCESS" - }, - { - "name": "ERROR" - }, - { - "name": "SUBSCRIPTION" - }, - { - "name": "TRADES" - }, - { - "name": "QUOTES" - }, - { - "name": "MINUTE_BARS" - }, - { - "name": "DAILY_BARS" - }, - { - "name": "UPDATED_BARS" - }, - { - "name": "ORDER_BOOKS" - } - ] -} diff --git a/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/streams/crypto/model/orderbook/crypto_order_book_entry.json b/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/streams/crypto/model/orderbook/crypto_order_book_entry.json deleted file mode 100644 index 51ce64b5..00000000 --- a/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/streams/crypto/model/orderbook/crypto_order_book_entry.json +++ /dev/null @@ -1,15 +0,0 @@ -{ - "type": "object", - "properties": { - "p": { - "existingJavaType": "java.lang.Double", - "javaName": "price", - "title": "The price." - }, - "s": { - "existingJavaType": "java.lang.Double", - "javaName": "size", - "title": "The size." - } - } -} diff --git a/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/streams/crypto/model/orderbook/crypto_order_book_message.json b/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/streams/crypto/model/orderbook/crypto_order_book_message.json deleted file mode 100644 index 79814a8a..00000000 --- a/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/streams/crypto/model/orderbook/crypto_order_book_message.json +++ /dev/null @@ -1,33 +0,0 @@ -{ - "type": "object", - "extends": { - "existingJavaType": "net.jacobpeterson.alpaca.model.websocket.marketdata.streams.crypto.model.CryptoMarketDataMessage" - }, - "properties": { - "S": { - "existingJavaType": "java.lang.String", - "javaName": "symbol", - "title": "The symbol." - }, - "a": { - "existingJavaType": "java.util.Set", - "javaName": "asks", - "title": "The {@link java.util.Set} of {@link net.jacobpeterson.alpaca.model.websocket.marketdata.streams.crypto.model.orderbook.CryptoOrderBookEntry} asks." - }, - "b": { - "existingJavaType": "java.util.Set", - "javaName": "bids", - "title": "The {@link java.util.Set} of {@link net.jacobpeterson.alpaca.model.websocket.marketdata.streams.crypto.model.orderbook.CryptoOrderBookEntry} bids." - }, - "t": { - "existingJavaType": "java.time.OffsetDateTime", - "javaName": "timestamp", - "title": "The timestamp with nanosecond precision." - }, - "r": { - "existingJavaType": "java.lang.Boolean", - "javaName": "reset", - "title": "Whether this particular {@link net.jacobpeterson.alpaca.model.websocket.marketdata.streams.crypto.model.orderbook.CryptoOrderBookMessage} contains the whole order book and not just an update. Typically true for the first message and false for subsequent messages." - } - } -} diff --git a/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/streams/crypto/model/quote/crypto_quote_message.json b/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/streams/crypto/model/quote/crypto_quote_message.json deleted file mode 100644 index d314de7a..00000000 --- a/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/streams/crypto/model/quote/crypto_quote_message.json +++ /dev/null @@ -1,38 +0,0 @@ -{ - "type": "object", - "extends": { - "existingJavaType": "net.jacobpeterson.alpaca.model.websocket.marketdata.streams.crypto.model.CryptoMarketDataMessage" - }, - "properties": { - "S": { - "existingJavaType": "java.lang.String", - "javaName": "symbol", - "title": "The symbol." - }, - "ap": { - "existingJavaType": "java.lang.Double", - "javaName": "askPrice", - "title": "The ask price." - }, - "as": { - "existingJavaType": "java.lang.Double", - "javaName": "askSize", - "title": "The ask size." - }, - "bp": { - "existingJavaType": "java.lang.Double", - "javaName": "bidPrice", - "title": "The bid price." - }, - "bs": { - "existingJavaType": "java.lang.Double", - "javaName": "bidSize", - "title": "The bid size." - }, - "t": { - "existingJavaType": "java.time.OffsetDateTime", - "javaName": "timestamp", - "title": "The timestamp with nanosecond precision." - } - } -} diff --git a/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/streams/crypto/model/trade/crypto_trade_message.json b/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/streams/crypto/model/trade/crypto_trade_message.json deleted file mode 100644 index 13118dd7..00000000 --- a/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/streams/crypto/model/trade/crypto_trade_message.json +++ /dev/null @@ -1,38 +0,0 @@ -{ - "type": "object", - "extends": { - "existingJavaType": "net.jacobpeterson.alpaca.model.websocket.marketdata.streams.crypto.model.CryptoMarketDataMessage" - }, - "properties": { - "S": { - "existingJavaType": "java.lang.String", - "javaName": "symbol", - "title": "The symbol." - }, - "i": { - "existingJavaType": "java.lang.Long", - "javaName": "tradeID", - "title": "The trade ID." - }, - "p": { - "existingJavaType": "java.lang.Double", - "javaName": "price", - "title": "The trade price." - }, - "s": { - "existingJavaType": "java.lang.Double", - "javaName": "size", - "title": "The trade size." - }, - "t": { - "existingJavaType": "java.time.OffsetDateTime", - "javaName": "timestamp", - "title": "The timestamp with nanosecond precision." - }, - "tks": { - "existingJavaType": "net.jacobpeterson.alpaca.model.websocket.marketdata.streams.crypto.model.trade.CryptoTradeTakerSide", - "javaName": "takerSide", - "title": "The taker side." - } - } -} diff --git a/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/streams/crypto/model/trade/crypto_trade_taker_side.json b/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/streams/crypto/model/trade/crypto_trade_taker_side.json deleted file mode 100644 index d793fbfb..00000000 --- a/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/streams/crypto/model/trade/crypto_trade_taker_side.json +++ /dev/null @@ -1,15 +0,0 @@ -{ - "type": "string", - "enum": [ - "B", - "S" - ], - "javaEnums": [ - { - "name": "BUY" - }, - { - "name": "SELL" - } - ] -} diff --git a/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/streams/news/NewsMarketDataListener.java b/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/streams/news/NewsMarketDataListener.java deleted file mode 100644 index c47cb6dc..00000000 --- a/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/streams/news/NewsMarketDataListener.java +++ /dev/null @@ -1,17 +0,0 @@ -package net.jacobpeterson.alpaca.websocket.marketdata.streams.news; - -import net.jacobpeterson.alpaca.model.websocket.marketdata.streams.news.model.news.NewsMessage; - -/** - * {@link NewsMarketDataListener} defines a listener interface for {@link NewsMarketDataWebsocketInterface} messages. - */ -@FunctionalInterface -public interface NewsMarketDataListener { - - /** - * Called when a {@link NewsMessage} is received. - * - * @param news the {@link NewsMessage} - */ - void onNews(NewsMessage news); -} diff --git a/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/streams/news/NewsMarketDataListenerAdapter.java b/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/streams/news/NewsMarketDataListenerAdapter.java deleted file mode 100644 index 2278df82..00000000 --- a/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/streams/news/NewsMarketDataListenerAdapter.java +++ /dev/null @@ -1,12 +0,0 @@ -package net.jacobpeterson.alpaca.websocket.marketdata.streams.news; - -import net.jacobpeterson.alpaca.model.websocket.marketdata.streams.news.model.news.NewsMessage; - -/** - * {@link NewsMarketDataListenerAdapter} is an adapter for {@link NewsMarketDataListener}. - */ -public class NewsMarketDataListenerAdapter implements NewsMarketDataListener { - - @Override - public void onNews(NewsMessage news) {} -} diff --git a/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/streams/news/NewsMarketDataWebsocket.java b/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/streams/news/NewsMarketDataWebsocket.java deleted file mode 100644 index 495f7932..00000000 --- a/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/streams/news/NewsMarketDataWebsocket.java +++ /dev/null @@ -1,85 +0,0 @@ -package net.jacobpeterson.alpaca.websocket.marketdata.streams.news; - -import com.google.gson.JsonObject; -import net.jacobpeterson.alpaca.model.websocket.marketdata.streams.news.model.NewsMarketDataMessageType; -import net.jacobpeterson.alpaca.model.websocket.marketdata.streams.news.model.control.NewsSubscriptionsMessage; -import net.jacobpeterson.alpaca.model.websocket.marketdata.streams.news.model.news.NewsMessage; -import net.jacobpeterson.alpaca.websocket.marketdata.MarketDataWebsocket; -import okhttp3.HttpUrl; -import okhttp3.OkHttpClient; - -import java.util.Set; - -import static net.jacobpeterson.alpaca.model.websocket.marketdata.streams.news.model.NewsMarketDataMessageType.ERROR; -import static net.jacobpeterson.alpaca.model.websocket.marketdata.streams.news.model.NewsMarketDataMessageType.SUBSCRIPTION; -import static net.jacobpeterson.alpaca.model.websocket.marketdata.streams.news.model.NewsMarketDataMessageType.SUCCESS; -import static net.jacobpeterson.alpaca.openapi.marketdata.JSON.getGson; - -/** - * {@link NewsMarketDataWebsocket} is an implementation for {@link NewsMarketDataWebsocketInterface}. - */ -public class NewsMarketDataWebsocket - extends MarketDataWebsocket - implements NewsMarketDataWebsocketInterface { - - /** - * Instantiates a new {@link NewsMarketDataWebsocket}. - * - * @param okHttpClient the {@link OkHttpClient} - * @param traderKeyID the trader key ID - * @param traderSecretKey the trader secret key - * @param brokerAPIKey the broker API key - * @param brokerAPISecret the broker API secret - */ - public NewsMarketDataWebsocket(OkHttpClient okHttpClient, String traderKeyID, String traderSecretKey, - String brokerAPIKey, String brokerAPISecret) { - super(okHttpClient, new HttpUrl.Builder() - .scheme("https") - .host("stream.data.alpaca.markets") - .addPathSegments("v1beta1/news") - .build(), - "News", traderKeyID, traderSecretKey, brokerAPIKey, brokerAPISecret, - NewsMarketDataMessageType.class, NewsSubscriptionsMessage.class); - } - - @Override - protected boolean isSuccessMessageType(NewsMarketDataMessageType messageType) { - return messageType == SUCCESS; - } - - @Override - protected boolean isErrorMessageType(NewsMarketDataMessageType messageType) { - return messageType == ERROR; - } - - @Override - protected boolean isSubscriptionMessageType(NewsMarketDataMessageType messageType) { - return messageType == SUBSCRIPTION; - } - - @Override - protected void callListenerWithMessage(NewsMarketDataMessageType messageType, JsonObject messageObject) { - if (messageType == NewsMarketDataMessageType.NEWS) { - listener.onNews(getGson().fromJson(messageObject, NewsMessage.class)); - } else { - throw new UnsupportedOperationException(); - } - } - - @Override - public void setListener(NewsMarketDataListener listener) { - this.listener = listener; - } - - @Override - public void setNewsSubscriptions(Set symbols) { - symbols = symbols == null ? Set.of() : symbols; - setSubscriptions(getNewsSubscriptions(), symbols, - set -> new NewsSubscriptionsMessage().withNews(set)); - } - - @Override - public Set getNewsSubscriptions() { - return subscriptionsMessage == null ? Set.of() : subscriptionsMessage.getNews(); - } -} diff --git a/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/streams/news/NewsMarketDataWebsocketInterface.java b/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/streams/news/NewsMarketDataWebsocketInterface.java deleted file mode 100644 index 8a89857d..00000000 --- a/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/streams/news/NewsMarketDataWebsocketInterface.java +++ /dev/null @@ -1,36 +0,0 @@ -package net.jacobpeterson.alpaca.websocket.marketdata.streams.news; - -import net.jacobpeterson.alpaca.model.websocket.marketdata.streams.news.model.NewsMarketDataMessageType; -import net.jacobpeterson.alpaca.websocket.marketdata.MarketDataWebsocketInterface; - -import java.util.Set; - -/** - * {@link NewsMarketDataWebsocketInterface} is a {@link MarketDataWebsocketInterface} for - * {@link NewsMarketDataWebsocket}. - */ -public interface NewsMarketDataWebsocketInterface extends MarketDataWebsocketInterface { - - /** - * Sets the {@link NewsMarketDataListener}. - * - * @param listener the {@link NewsMarketDataListener} - */ - void setListener(NewsMarketDataListener listener); - - /** - * Subscribes the given symbols to {@link NewsMarketDataMessageType#NEWS}. This will remove all - * previous {@link NewsMarketDataMessageType#NEWS} subscriptions. - * - * @param symbols the {@link Set} of symbols (use an asterisk ("*") as a wildcard to subscribe to all available - * symbols) or null to unsubscribe from all symbols - */ - void setNewsSubscriptions(Set symbols); - - /** - * Gets the current symbols subscribed to {@link NewsMarketDataMessageType#NEWS}. - * - * @return a {@link Set} of {@link String} symbols - */ - Set getNewsSubscriptions(); -} diff --git a/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/streams/news/model/control/news_subscriptions_message.json b/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/streams/news/model/control/news_subscriptions_message.json deleted file mode 100644 index dbd84da7..00000000 --- a/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/streams/news/model/control/news_subscriptions_message.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "type": "object", - "extends": { - "existingJavaType": "net.jacobpeterson.alpaca.model.websocket.marketdata.streams.news.model.NewsMarketDataMessage" - }, - "properties": { - "news": { - "existingJavaType": "java.util.Set", - "title": "The {@link java.util.Set} of symbols subscribed to news." - } - } -} diff --git a/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/streams/news/model/news/news_message.json b/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/streams/news/model/news/news_message.json deleted file mode 100644 index 638a3a67..00000000 --- a/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/streams/news/model/news/news_message.json +++ /dev/null @@ -1,38 +0,0 @@ -{ - "type": "object", - "extends": { - "existingJavaType": "net.jacobpeterson.alpaca.model.websocket.marketdata.streams.news.model.NewsMarketDataMessage" - }, - "properties": { - "id": { - "existingJavaType": "java.lang.Long" - }, - "source": { - "existingJavaType": "java.lang.String" - }, - "headline": { - "existingJavaType": "java.lang.String" - }, - "author": { - "existingJavaType": "java.lang.String" - }, - "summary": { - "existingJavaType": "java.lang.String" - }, - "content": { - "existingJavaType": "java.lang.String" - }, - "url": { - "existingJavaType": "java.lang.String" - }, - "symbols": { - "existingJavaType": "java.util.Set" - }, - "created_at": { - "existingJavaType": "java.time.OffsetDateTime" - }, - "updated_at": { - "existingJavaType": "java.time.OffsetDateTime" - } - } -} diff --git a/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/streams/news/model/news_market_data_message.json b/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/streams/news/model/news_market_data_message.json deleted file mode 100644 index 1f967c5b..00000000 --- a/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/streams/news/model/news_market_data_message.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "type": "object", - "extends": { - "existingJavaType": "net.jacobpeterson.alpaca.model.websocket.marketdata.model.MarketDataMessage" - }, - "properties": { - "T": { - "existingJavaType": "net.jacobpeterson.alpaca.model.websocket.marketdata.streams.news.model.NewsMarketDataMessageType", - "javaName": "messageType", - "title": "The {@link net.jacobpeterson.alpaca.model.websocket.marketdata.streams.news.model.NewsMarketDataMessageType}." - } - } -} diff --git a/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/streams/news/model/news_market_data_message_type.json b/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/streams/news/model/news_market_data_message_type.json deleted file mode 100644 index 4ddd6017..00000000 --- a/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/streams/news/model/news_market_data_message_type.json +++ /dev/null @@ -1,23 +0,0 @@ -{ - "type": "string", - "enum": [ - "success", - "error", - "subscription", - "n" - ], - "javaEnums": [ - { - "name": "SUCCESS" - }, - { - "name": "ERROR" - }, - { - "name": "SUBSCRIPTION" - }, - { - "name": "NEWS" - } - ] -} diff --git a/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/streams/stock/StockMarketDataListener.java b/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/streams/stock/StockMarketDataListener.java deleted file mode 100644 index 396e6af2..00000000 --- a/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/streams/stock/StockMarketDataListener.java +++ /dev/null @@ -1,78 +0,0 @@ -package net.jacobpeterson.alpaca.websocket.marketdata.streams.stock; - -import net.jacobpeterson.alpaca.model.websocket.marketdata.streams.stock.model.bar.StockBarMessage; -import net.jacobpeterson.alpaca.model.websocket.marketdata.streams.stock.model.limituplimitdownband.StockLimitUpLimitDownBandMessage; -import net.jacobpeterson.alpaca.model.websocket.marketdata.streams.stock.model.quote.StockQuoteMessage; -import net.jacobpeterson.alpaca.model.websocket.marketdata.streams.stock.model.trade.StockTradeMessage; -import net.jacobpeterson.alpaca.model.websocket.marketdata.streams.stock.model.tradecancelerror.StockTradeCancelErrorMessage; -import net.jacobpeterson.alpaca.model.websocket.marketdata.streams.stock.model.tradecorrection.StockTradeCorrectionMessage; -import net.jacobpeterson.alpaca.model.websocket.marketdata.streams.stock.model.tradingstatus.StockTradingStatusMessage; - -/** - * {@link StockMarketDataListener} defines a listener interface for {@link StockMarketDataWebsocketInterface} messages. - */ -public interface StockMarketDataListener { - - /** - * Called when a {@link StockTradeMessage} is received. - * - * @param trade the {@link StockTradeMessage} - */ - void onTrade(StockTradeMessage trade); - - /** - * Called when a {@link StockQuoteMessage} is received. - * - * @param quote the {@link StockQuoteMessage} - */ - void onQuote(StockQuoteMessage quote); - - /** - * Called when a {@link StockBarMessage} is received. - * - * @param bar the {@link StockBarMessage} - */ - void onMinuteBar(StockBarMessage bar); - - /** - * Called when a {@link StockBarMessage} is received. - * - * @param bar the {@link StockBarMessage} - */ - void onDailyBar(StockBarMessage bar); - - /** - * Called when a {@link StockBarMessage} is received. - * - * @param bar the {@link StockBarMessage} - */ - void onUpdatedBar(StockBarMessage bar); - - /** - * Called when a {@link StockTradeCorrectionMessage} is received. - * - * @param tradeCorrection the {@link StockTradeCorrectionMessage} - */ - void onTradeCorrection(StockTradeCorrectionMessage tradeCorrection); - - /** - * Called when a {@link StockTradeCancelErrorMessage} is received. - * - * @param tradeCancelError the {@link StockTradeCancelErrorMessage} - */ - void onTradeCancelError(StockTradeCancelErrorMessage tradeCancelError); - - /** - * Called when a {@link StockLimitUpLimitDownBandMessage} is received. - * - * @param limitUpLimitDownBand the {@link StockLimitUpLimitDownBandMessage} - */ - void onLimitUpLimitDownBand(StockLimitUpLimitDownBandMessage limitUpLimitDownBand); - - /** - * Called when a {link StockTradingStatusMessage} is received. - * - * @param tradingStatus the {@link StockTradingStatusMessage} - */ - void onTradingStatus(StockTradingStatusMessage tradingStatus); -} diff --git a/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/streams/stock/StockMarketDataListenerAdapter.java b/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/streams/stock/StockMarketDataListenerAdapter.java deleted file mode 100644 index df99badf..00000000 --- a/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/streams/stock/StockMarketDataListenerAdapter.java +++ /dev/null @@ -1,42 +0,0 @@ -package net.jacobpeterson.alpaca.websocket.marketdata.streams.stock; - -import net.jacobpeterson.alpaca.model.websocket.marketdata.streams.stock.model.bar.StockBarMessage; -import net.jacobpeterson.alpaca.model.websocket.marketdata.streams.stock.model.limituplimitdownband.StockLimitUpLimitDownBandMessage; -import net.jacobpeterson.alpaca.model.websocket.marketdata.streams.stock.model.quote.StockQuoteMessage; -import net.jacobpeterson.alpaca.model.websocket.marketdata.streams.stock.model.trade.StockTradeMessage; -import net.jacobpeterson.alpaca.model.websocket.marketdata.streams.stock.model.tradecancelerror.StockTradeCancelErrorMessage; -import net.jacobpeterson.alpaca.model.websocket.marketdata.streams.stock.model.tradecorrection.StockTradeCorrectionMessage; -import net.jacobpeterson.alpaca.model.websocket.marketdata.streams.stock.model.tradingstatus.StockTradingStatusMessage; - -/** - * {@link StockMarketDataListenerAdapter} is an adapter for {@link StockMarketDataListener}. - */ -public class StockMarketDataListenerAdapter implements StockMarketDataListener { - - @Override - public void onTrade(StockTradeMessage trade) {} - - @Override - public void onQuote(StockQuoteMessage quote) {} - - @Override - public void onMinuteBar(StockBarMessage bar) {} - - @Override - public void onDailyBar(StockBarMessage bar) {} - - @Override - public void onUpdatedBar(StockBarMessage bar) {} - - @Override - public void onTradeCorrection(StockTradeCorrectionMessage tradeCorrection) {} - - @Override - public void onTradeCancelError(StockTradeCancelErrorMessage tradeCancelError) {} - - @Override - public void onLimitUpLimitDownBand(StockLimitUpLimitDownBandMessage limitUpLimitDownBand) {} - - @Override - public void onTradingStatus(StockTradingStatusMessage tradingStatus) {} -} diff --git a/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/streams/stock/StockMarketDataWebsocket.java b/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/streams/stock/StockMarketDataWebsocket.java deleted file mode 100644 index 753d0246..00000000 --- a/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/streams/stock/StockMarketDataWebsocket.java +++ /dev/null @@ -1,218 +0,0 @@ -package net.jacobpeterson.alpaca.websocket.marketdata.streams.stock; - -import com.google.gson.JsonObject; -import net.jacobpeterson.alpaca.model.util.apitype.MarketDataWebsocketSourceType; -import net.jacobpeterson.alpaca.model.websocket.marketdata.streams.stock.model.StockMarketDataMessageType; -import net.jacobpeterson.alpaca.model.websocket.marketdata.streams.stock.model.bar.StockBarMessage; -import net.jacobpeterson.alpaca.model.websocket.marketdata.streams.stock.model.control.StockSubscriptionsMessage; -import net.jacobpeterson.alpaca.model.websocket.marketdata.streams.stock.model.limituplimitdownband.StockLimitUpLimitDownBandMessage; -import net.jacobpeterson.alpaca.model.websocket.marketdata.streams.stock.model.quote.StockQuoteMessage; -import net.jacobpeterson.alpaca.model.websocket.marketdata.streams.stock.model.trade.StockTradeMessage; -import net.jacobpeterson.alpaca.model.websocket.marketdata.streams.stock.model.tradecancelerror.StockTradeCancelErrorMessage; -import net.jacobpeterson.alpaca.model.websocket.marketdata.streams.stock.model.tradecorrection.StockTradeCorrectionMessage; -import net.jacobpeterson.alpaca.model.websocket.marketdata.streams.stock.model.tradingstatus.StockTradingStatusMessage; -import net.jacobpeterson.alpaca.websocket.marketdata.MarketDataWebsocket; -import okhttp3.HttpUrl; -import okhttp3.OkHttpClient; - -import java.util.Set; - -import static net.jacobpeterson.alpaca.model.websocket.marketdata.streams.stock.model.StockMarketDataMessageType.ERROR; -import static net.jacobpeterson.alpaca.model.websocket.marketdata.streams.stock.model.StockMarketDataMessageType.SUBSCRIPTION; -import static net.jacobpeterson.alpaca.model.websocket.marketdata.streams.stock.model.StockMarketDataMessageType.SUCCESS; -import static net.jacobpeterson.alpaca.openapi.marketdata.JSON.getGson; - -/** - * {@link StockMarketDataWebsocket} is an implementation for {@link StockMarketDataWebsocketInterface}. - */ -public class StockMarketDataWebsocket - extends MarketDataWebsocket - implements StockMarketDataWebsocketInterface { - - private static HttpUrl createWebsocketURL(boolean isSandbox, - MarketDataWebsocketSourceType marketDataWebsocketSourceType) { - return new HttpUrl.Builder() - .scheme("https") - .host(isSandbox ? "stream.data.sandbox.alpaca.markets" : "stream.data.alpaca.markets") - .addPathSegment("v2") - .addPathSegment(marketDataWebsocketSourceType.toString()) - .build(); - } - - /** - * Instantiates a new {@link StockMarketDataWebsocket}. - * - * @param okHttpClient the {@link OkHttpClient} - * @param traderKeyID the trader key ID - * @param traderSecretKey the trader secret key - * @param brokerAPIKey the broker API key - * @param brokerAPISecret the broker API secret - * @param marketDataWebsocketSourceType the {@link MarketDataWebsocketSourceType} - */ - public StockMarketDataWebsocket(OkHttpClient okHttpClient, String traderKeyID, String traderSecretKey, - String brokerAPIKey, String brokerAPISecret, MarketDataWebsocketSourceType marketDataWebsocketSourceType) { - super(okHttpClient, - createWebsocketURL(brokerAPIKey != null && brokerAPISecret != null, marketDataWebsocketSourceType), - "Stock", traderKeyID, traderSecretKey, brokerAPIKey, brokerAPISecret, - StockMarketDataMessageType.class, StockSubscriptionsMessage.class); - } - - @Override - protected boolean isSuccessMessageType(StockMarketDataMessageType messageType) { - return messageType == SUCCESS; - } - - @Override - protected boolean isErrorMessageType(StockMarketDataMessageType messageType) { - return messageType == ERROR; - } - - @Override - protected boolean isSubscriptionMessageType(StockMarketDataMessageType messageType) { - return messageType == SUBSCRIPTION; - } - - @Override - protected void callListenerWithMessage(StockMarketDataMessageType messageType, JsonObject messageObject) { - switch (messageType) { - case TRADES: - listener.onTrade(getGson().fromJson(messageObject, - StockTradeMessage.class)); - break; - case QUOTES: - listener.onQuote(getGson().fromJson(messageObject, - StockQuoteMessage.class)); - break; - case MINUTE_BARS: - listener.onMinuteBar(getGson().fromJson(messageObject, - StockBarMessage.class)); - break; - case DAILY_BARS: - listener.onDailyBar(getGson().fromJson(messageObject, - StockBarMessage.class)); - break; - case UPDATED_BARS: - listener.onUpdatedBar(getGson().fromJson(messageObject, - StockBarMessage.class)); - break; - case TRADE_CORRECTIONS: - listener.onTradeCorrection(getGson().fromJson(messageObject, - StockTradeCorrectionMessage.class)); - break; - case TRADE_CANCEL_ERRORS: - listener.onTradeCancelError(getGson().fromJson(messageObject, - StockTradeCancelErrorMessage.class)); - break; - case LIMIT_UP_LIMIT_DOWN_BANDS: - listener.onLimitUpLimitDownBand(getGson().fromJson(messageObject, - StockLimitUpLimitDownBandMessage.class)); - break; - case TRADING_STATUSES: - listener.onTradingStatus(getGson().fromJson(messageObject, - StockTradingStatusMessage.class)); - break; - default: - throw new UnsupportedOperationException(); - } - } - - @Override - public void setListener(StockMarketDataListener listener) { - this.listener = listener; - } - - @Override - public void setTradeSubscriptions(Set symbols) { - symbols = symbols == null ? Set.of() : symbols; - setSubscriptions(getTradeSubscriptions(), symbols, - set -> newEmptyStockSubscriptionsMessage().withTrades(set)); - } - - @Override - public Set getTradeSubscriptions() { - return subscriptionsMessage == null ? Set.of() : subscriptionsMessage.getTrades(); - } - - @Override - public void setQuoteSubscriptions(Set symbols) { - symbols = symbols == null ? Set.of() : symbols; - setSubscriptions(getQuoteSubscriptions(), symbols, - set -> newEmptyStockSubscriptionsMessage().withQuotes(set)); - } - - @Override - public Set getQuoteSubscriptions() { - return subscriptionsMessage == null ? Set.of() : subscriptionsMessage.getQuotes(); - } - - @Override - public void setMinuteBarSubscriptions(Set symbols) { - symbols = symbols == null ? Set.of() : symbols; - setSubscriptions(getMinuteBarSubscriptions(), symbols, - set -> newEmptyStockSubscriptionsMessage().withMinuteBars(set)); - } - - @Override - public Set getMinuteBarSubscriptions() { - return subscriptionsMessage == null ? Set.of() : subscriptionsMessage.getMinuteBars(); - } - - @Override - public void setDailyBarSubscriptions(Set symbols) { - symbols = symbols == null ? Set.of() : symbols; - setSubscriptions(getDailyBarSubscriptions(), symbols, - set -> newEmptyStockSubscriptionsMessage().withDailyBars(set)); - } - - @Override - public Set getDailyBarSubscriptions() { - return subscriptionsMessage == null ? Set.of() : subscriptionsMessage.getDailyBars(); - } - - @Override - public void setUpdatedBarSubscriptions(Set symbols) { - symbols = symbols == null ? Set.of() : symbols; - setSubscriptions(getUpdatedBarSubscriptions(), symbols, - set -> newEmptyStockSubscriptionsMessage().withUpdatedBars(set)); - } - - @Override - public Set getUpdatedBarSubscriptions() { - return subscriptionsMessage == null ? Set.of() : subscriptionsMessage.getUpdatedBars(); - } - - @Override - public void setLimitUpLimitDownBandSubscriptions(Set symbols) { - symbols = symbols == null ? Set.of() : symbols; - setSubscriptions(getLimitUpLimitDownBandSubscriptions(), symbols, - set -> newEmptyStockSubscriptionsMessage().withLimitUpLimitDownBands(set)); - } - - @Override - public Set getLimitUpLimitDownBandSubscriptions() { - return subscriptionsMessage == null ? Set.of() : subscriptionsMessage.getLimitUpLimitDownBands(); - } - - @Override - public void setTradingStatuseSubscriptions(Set symbols) { - symbols = symbols == null ? Set.of() : symbols; - setSubscriptions(getTradingStatuseSubscriptions(), symbols, - set -> newEmptyStockSubscriptionsMessage().withTradingStatuses(set)); - } - - @Override - public Set getTradingStatuseSubscriptions() { - return subscriptionsMessage == null ? Set.of() : subscriptionsMessage.getTradingStatuses(); - } - - private StockSubscriptionsMessage newEmptyStockSubscriptionsMessage() { - return new StockSubscriptionsMessage() - .withTrades(null) - .withQuotes(null) - .withMinuteBars(null) - .withDailyBars(null) - .withUpdatedBars(null) - .withLimitUpLimitDownBands(null) - .withTradingStatuses(null); - } -} diff --git a/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/streams/stock/StockMarketDataWebsocketInterface.java b/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/streams/stock/StockMarketDataWebsocketInterface.java deleted file mode 100644 index 32fc3b8d..00000000 --- a/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/streams/stock/StockMarketDataWebsocketInterface.java +++ /dev/null @@ -1,136 +0,0 @@ -package net.jacobpeterson.alpaca.websocket.marketdata.streams.stock; - -import net.jacobpeterson.alpaca.model.websocket.marketdata.streams.stock.model.StockMarketDataMessageType; -import net.jacobpeterson.alpaca.websocket.marketdata.MarketDataWebsocketInterface; - -import java.util.Set; - -/** - * {@link StockMarketDataWebsocketInterface} is a {@link MarketDataWebsocketInterface} for - * {@link StockMarketDataWebsocket}. - */ -public interface StockMarketDataWebsocketInterface extends MarketDataWebsocketInterface { - - /** - * Sets the {@link StockMarketDataListener}. - * - * @param listener the {@link StockMarketDataListener} - */ - void setListener(StockMarketDataListener listener); - - /** - * Subscribes the given symbols to {@link StockMarketDataMessageType#TRADES}. This will remove all - * previous {@link StockMarketDataMessageType#TRADES} subscriptions. - *
- * Note that this will automatically apply the same symbols subscription list to the - * {@link StockMarketDataMessageType#TRADE_CORRECTIONS} and {@link StockMarketDataMessageType#TRADE_CANCEL_ERRORS} - * types. - * - * @param symbols the {@link Set} of symbols (use an asterisk ("*") as a wildcard to subscribe to all available - * symbols) or null to unsubscribe from all symbols - */ - void setTradeSubscriptions(Set symbols); - - /** - * Gets the current symbols subscribed to {@link StockMarketDataMessageType#TRADES}. - * - * @return a {@link Set} of {@link String} symbols - */ - Set getTradeSubscriptions(); - - /** - * Subscribes the given symbols to {@link StockMarketDataMessageType#QUOTES}. This will remove all - * previous {@link StockMarketDataMessageType#QUOTES} subscriptions. - * - * @param symbols the {@link Set} of symbols (use an asterisk ("*") as a wildcard to subscribe to all available - * symbols) or null to unsubscribe from all symbols - */ - void setQuoteSubscriptions(Set symbols); - - /** - * Gets the current symbols subscribed to {@link StockMarketDataMessageType#QUOTES}. - * - * @return a {@link Set} of {@link String} symbols - */ - Set getQuoteSubscriptions(); - - /** - * Subscribes the given symbols to {@link StockMarketDataMessageType#MINUTE_BARS}. This will remove all - * previous {@link StockMarketDataMessageType#MINUTE_BARS} subscriptions. - * - * @param symbols the {@link Set} of symbols (use an asterisk ("*") as a wildcard to subscribe to all available - * symbols) or null to unsubscribe from all symbols - */ - void setMinuteBarSubscriptions(Set symbols); - - /** - * Gets the current symbols subscribed to {@link StockMarketDataMessageType#MINUTE_BARS}. - * - * @return a {@link Set} of {@link String} symbols - */ - Set getMinuteBarSubscriptions(); - - /** - * Subscribes the given symbols to {@link StockMarketDataMessageType#DAILY_BARS}. This will remove all - * previous {@link StockMarketDataMessageType#DAILY_BARS} subscriptions. - * - * @param symbols the {@link Set} of symbols (use an asterisk ("*") as a wildcard to subscribe to all available - * symbols) or null to unsubscribe from all symbols - */ - void setDailyBarSubscriptions(Set symbols); - - /** - * Gets the current symbols subscribed to {@link StockMarketDataMessageType#DAILY_BARS}. - * - * @return a {@link Set} of {@link String} symbols - */ - Set getDailyBarSubscriptions(); - - /** - * Subscribes the given symbols to {@link StockMarketDataMessageType#UPDATED_BARS}. This will remove - * all previous {@link StockMarketDataMessageType#UPDATED_BARS} subscriptions. - * - * @param symbols the {@link Set} of symbols (use an asterisk ("*") as a wildcard to subscribe to all available - * symbols) or null to unsubscribe from all symbols - */ - void setUpdatedBarSubscriptions(Set symbols); - - /** - * Gets the current symbols subscribed to {@link StockMarketDataMessageType#UPDATED_BARS}. - * - * @return a {@link Set} of {@link String} symbols - */ - Set getUpdatedBarSubscriptions(); - - /** - * Subscribes the given symbols to {@link StockMarketDataMessageType#LIMIT_UP_LIMIT_DOWN_BANDS}. This - * will remove all previous {@link StockMarketDataMessageType#LIMIT_UP_LIMIT_DOWN_BANDS} subscriptions. - * - * @param symbols the {@link Set} of symbols (use an asterisk ("*") as a wildcard to subscribe to all available - * symbols) or null to unsubscribe from all symbols - */ - void setLimitUpLimitDownBandSubscriptions(Set symbols); - - /** - * Gets the current symbols subscribed to {@link StockMarketDataMessageType#LIMIT_UP_LIMIT_DOWN_BANDS}. - * - * @return a {@link Set} of {@link String} symbols - */ - Set getLimitUpLimitDownBandSubscriptions(); - - /** - * Subscribes the given symbols to {@link StockMarketDataMessageType#TRADING_STATUSES}. This will - * remove all previous {@link StockMarketDataMessageType#TRADING_STATUSES} subscriptions. - * - * @param symbols the {@link Set} of symbols (use an asterisk ("*") as a wildcard to subscribe to all available - * symbols) or null to unsubscribe from all symbols - */ - void setTradingStatuseSubscriptions(Set symbols); - - /** - * Gets the current symbols subscribed to {@link StockMarketDataMessageType#TRADING_STATUSES}. - * - * @return a {@link Set} of {@link String} symbols - */ - Set getTradingStatuseSubscriptions(); -} diff --git a/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/streams/stock/model/bar/stock_bar_message.json b/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/streams/stock/model/bar/stock_bar_message.json deleted file mode 100644 index 95c6c0d3..00000000 --- a/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/streams/stock/model/bar/stock_bar_message.json +++ /dev/null @@ -1,53 +0,0 @@ -{ - "type": "object", - "extends": { - "existingJavaType": "net.jacobpeterson.alpaca.model.websocket.marketdata.streams.stock.model.StockMarketDataMessage" - }, - "properties": { - "S": { - "existingJavaType": "java.lang.String", - "javaName": "symbol", - "title": "The symbol." - }, - "o": { - "existingJavaType": "java.lang.Double", - "javaName": "open", - "title": "The open price." - }, - "h": { - "existingJavaType": "java.lang.Double", - "javaName": "high", - "title": "The high price." - }, - "l": { - "existingJavaType": "java.lang.Double", - "javaName": "low", - "title": "The low price." - }, - "c": { - "existingJavaType": "java.lang.Double", - "javaName": "close", - "title": "The close price." - }, - "t": { - "existingJavaType": "java.time.OffsetDateTime", - "javaName": "timestamp", - "title": "The timestamp." - }, - "v": { - "existingJavaType": "java.lang.Long", - "javaName": "volume", - "title": "The volume." - }, - "n": { - "existingJavaType": "java.lang.Long", - "javaName": "tradeCount", - "title": "The trade count." - }, - "vw": { - "existingJavaType": "java.lang.Double", - "javaName": "vwap", - "title": "The VWAP (Volume Weighted Average Price)." - } - } -} diff --git a/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/streams/stock/model/control/stock_subscriptions_message.json b/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/streams/stock/model/control/stock_subscriptions_message.json deleted file mode 100644 index 05a1d449..00000000 --- a/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/streams/stock/model/control/stock_subscriptions_message.json +++ /dev/null @@ -1,39 +0,0 @@ -{ - "type": "object", - "extends": { - "existingJavaType": "net.jacobpeterson.alpaca.model.websocket.marketdata.streams.stock.model.StockMarketDataMessage" - }, - "properties": { - "trades": { - "existingJavaType": "java.util.Set", - "title": "The {@link java.util.Set} of symbols subscribed to trades." - }, - "quotes": { - "existingJavaType": "java.util.Set", - "title": "The {@link java.util.Set} of symbols subscribed to quotes." - }, - "bars": { - "existingJavaType": "java.util.Set", - "javaName": "minuteBars", - "title": "The {@link java.util.Set} of symbols subscribed to minute bars." - }, - "dailyBars": { - "existingJavaType": "java.util.Set", - "title": "The {@link java.util.Set} of symbols subscribed to daily bars." - }, - "updatedBars": { - "existingJavaType": "java.util.Set", - "title": "The {@link java.util.Set} of symbols subscribed to updated bars." - }, - "lulds": { - "existingJavaType": "java.util.Set", - "javaName": "limitUpLimitDownBands", - "title": "The {@link java.util.Set} of symbols subscribed to limit up - limit down bands." - }, - "statuses": { - "existingJavaType": "java.util.Set", - "javaName": "tradingStatuses", - "title": "The {@link java.util.Set} of symbols subscribed to trading statuses." - } - } -} diff --git a/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/streams/stock/model/limituplimitdownband/stock_limit_up_limit_down_band_message.json b/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/streams/stock/model/limituplimitdownband/stock_limit_up_limit_down_band_message.json deleted file mode 100644 index db9238cf..00000000 --- a/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/streams/stock/model/limituplimitdownband/stock_limit_up_limit_down_band_message.json +++ /dev/null @@ -1,38 +0,0 @@ -{ - "type": "object", - "extends": { - "existingJavaType": "net.jacobpeterson.alpaca.model.websocket.marketdata.streams.stock.model.StockMarketDataMessage" - }, - "properties": { - "S": { - "existingJavaType": "java.lang.String", - "javaName": "symbol", - "title": "The symbol." - }, - "u": { - "existingJavaType": "java.lang.Double", - "javaName": "limitUpPrice", - "title": "The upper limit price band of a security." - }, - "d": { - "existingJavaType": "java.lang.Double", - "javaName": "limitDownPrice", - "title": "The lower limit price band of a security." - }, - "i": { - "existingJavaType": "java.lang.String", - "javaName": "indicator", - "title": "The indicator." - }, - "t": { - "existingJavaType": "java.time.OffsetDateTime", - "javaName": "timestamp", - "title": "The timestamp with nanosecond precision." - }, - "z": { - "existingJavaType": "java.lang.String", - "javaName": "tape", - "title": "The tape." - } - } -} diff --git a/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/streams/stock/model/quote/stock_quote_message.json b/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/streams/stock/model/quote/stock_quote_message.json deleted file mode 100644 index b0e2d72f..00000000 --- a/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/streams/stock/model/quote/stock_quote_message.json +++ /dev/null @@ -1,58 +0,0 @@ -{ - "type": "object", - "extends": { - "existingJavaType": "net.jacobpeterson.alpaca.model.websocket.marketdata.streams.stock.model.StockMarketDataMessage" - }, - "properties": { - "S": { - "existingJavaType": "java.lang.String", - "javaName": "symbol", - "title": "The symbol." - }, - "ax": { - "existingJavaType": "java.lang.String", - "javaName": "askExchange", - "title": "The ask exchange code." - }, - "ap": { - "existingJavaType": "java.lang.Double", - "javaName": "askPrice", - "title": "The ask price." - }, - "as": { - "existingJavaType": "java.lang.Integer", - "javaName": "askSize", - "title": "The ask size." - }, - "bx": { - "existingJavaType": "java.lang.String", - "javaName": "bidExchange", - "title": "The bid exchange code." - }, - "bp": { - "existingJavaType": "java.lang.Double", - "javaName": "bidPrice", - "title": "The bid price." - }, - "bs": { - "existingJavaType": "java.lang.Integer", - "javaName": "bidSize", - "title": "The bid size." - }, - "c": { - "existingJavaType": "java.util.Set", - "javaName": "conditions", - "title": "The {@link java.util.Set} of quote conditions." - }, - "t": { - "existingJavaType": "java.time.OffsetDateTime", - "javaName": "timestamp", - "title": "The timestamp with nanosecond precision." - }, - "z": { - "existingJavaType": "java.lang.String", - "javaName": "tape", - "title": "The tape." - } - } -} diff --git a/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/streams/stock/model/stock_market_data_message.json b/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/streams/stock/model/stock_market_data_message.json deleted file mode 100644 index 8fbfaf6b..00000000 --- a/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/streams/stock/model/stock_market_data_message.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "type": "object", - "extends": { - "existingJavaType": "net.jacobpeterson.alpaca.model.websocket.marketdata.model.MarketDataMessage" - }, - "properties": { - "T": { - "existingJavaType": "net.jacobpeterson.alpaca.model.websocket.marketdata.streams.stock.model.StockMarketDataMessageType", - "javaName": "messageType", - "title": "The {@link net.jacobpeterson.alpaca.model.websocket.marketdata.streams.stock.model.StockMarketDataMessageType}." - } - } -} diff --git a/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/streams/stock/model/stock_market_data_message_type.json b/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/streams/stock/model/stock_market_data_message_type.json deleted file mode 100644 index 17495738..00000000 --- a/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/streams/stock/model/stock_market_data_message_type.json +++ /dev/null @@ -1,55 +0,0 @@ -{ - "type": "string", - "enum": [ - "success", - "error", - "subscription", - "t", - "q", - "b", - "d", - "u", - "c", - "x", - "l", - "s" - ], - "javaEnums": [ - { - "name": "SUCCESS" - }, - { - "name": "ERROR" - }, - { - "name": "SUBSCRIPTION" - }, - { - "name": "TRADES" - }, - { - "name": "QUOTES" - }, - { - "name": "MINUTE_BARS" - }, - { - "name": "DAILY_BARS" - }, - { - "name": "UPDATED_BARS" - }, - { - "name": "TRADE_CORRECTIONS" - }, - { - "name": "TRADE_CANCEL_ERRORS" - }, - { - "name": "LIMIT_UP_LIMIT_DOWN_BANDS" - }, - { - "name": "TRADING_STATUSES" - } - ] -} diff --git a/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/streams/stock/model/trade/stock_trade_message.json b/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/streams/stock/model/trade/stock_trade_message.json deleted file mode 100644 index 239a8164..00000000 --- a/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/streams/stock/model/trade/stock_trade_message.json +++ /dev/null @@ -1,48 +0,0 @@ -{ - "type": "object", - "extends": { - "existingJavaType": "net.jacobpeterson.alpaca.model.websocket.marketdata.streams.stock.model.StockMarketDataMessage" - }, - "properties": { - "S": { - "existingJavaType": "java.lang.String", - "javaName": "symbol", - "title": "The symbol." - }, - "i": { - "existingJavaType": "java.lang.Long", - "javaName": "tradeID", - "title": "The trade ID." - }, - "x": { - "existingJavaType": "java.lang.String", - "javaName": "exchange", - "title": "The exchange code where the trade occurred." - }, - "p": { - "existingJavaType": "java.lang.Double", - "javaName": "price", - "title": "The trade price." - }, - "s": { - "existingJavaType": "java.lang.Integer", - "javaName": "size", - "title": "The trade size." - }, - "c": { - "existingJavaType": "java.util.Set", - "javaName": "conditions", - "title": "The {@link java.util.Set} of trade conditions." - }, - "t": { - "existingJavaType": "java.time.OffsetDateTime", - "javaName": "timestamp", - "title": "The timestamp with nanosecond precision." - }, - "z": { - "existingJavaType": "java.lang.String", - "javaName": "tape", - "title": "The tape." - } - } -} diff --git a/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/streams/stock/model/tradecancelerror/stock_trade_cancel_error_action.json b/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/streams/stock/model/tradecancelerror/stock_trade_cancel_error_action.json deleted file mode 100644 index c13371a9..00000000 --- a/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/streams/stock/model/tradecancelerror/stock_trade_cancel_error_action.json +++ /dev/null @@ -1,15 +0,0 @@ -{ - "type": "string", - "enum": [ - "C", - "E" - ], - "javaEnums": [ - { - "name": "CANCEL" - }, - { - "name": "ERROR" - } - ] -} diff --git a/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/streams/stock/model/tradecancelerror/stock_trade_cancel_error_message.json b/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/streams/stock/model/tradecancelerror/stock_trade_cancel_error_message.json deleted file mode 100644 index 577586c8..00000000 --- a/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/streams/stock/model/tradecancelerror/stock_trade_cancel_error_message.json +++ /dev/null @@ -1,48 +0,0 @@ -{ - "type": "object", - "extends": { - "existingJavaType": "net.jacobpeterson.alpaca.model.websocket.marketdata.streams.stock.model.StockMarketDataMessage" - }, - "properties": { - "S": { - "existingJavaType": "java.lang.String", - "javaName": "symbol", - "title": "The symbol." - }, - "i": { - "existingJavaType": "java.lang.Long", - "javaName": "tradeID", - "title": "The trade ID." - }, - "x": { - "existingJavaType": "java.lang.String", - "javaName": "exchange", - "title": "The exchange code where the trade cancel/error occurred." - }, - "p": { - "existingJavaType": "java.lang.Double", - "javaName": "price", - "title": "The trade price." - }, - "s": { - "existingJavaType": "java.lang.Integer", - "javaName": "size", - "title": "The trade size." - }, - "a": { - "existingJavaType": "net.jacobpeterson.alpaca.model.websocket.marketdata.streams.stock.model.tradecancelerror.StockTradeCancelErrorAction", - "javaName": "action", - "title": "The trade cancel/error action." - }, - "t": { - "existingJavaType": "java.time.OffsetDateTime", - "javaName": "timestamp", - "title": "The timestamp with nanosecond precision." - }, - "z": { - "existingJavaType": "java.lang.String", - "javaName": "tape", - "title": "The tape." - } - } -} diff --git a/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/streams/stock/model/tradecorrection/stock_trade_correction_message.json b/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/streams/stock/model/tradecorrection/stock_trade_correction_message.json deleted file mode 100644 index acb6a6ab..00000000 --- a/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/streams/stock/model/tradecorrection/stock_trade_correction_message.json +++ /dev/null @@ -1,68 +0,0 @@ -{ - "type": "object", - "extends": { - "existingJavaType": "net.jacobpeterson.alpaca.model.websocket.marketdata.streams.stock.model.StockMarketDataMessage" - }, - "properties": { - "S": { - "existingJavaType": "java.lang.String", - "javaName": "symbol", - "title": "The symbol." - }, - "x": { - "existingJavaType": "java.lang.String", - "javaName": "exchange", - "title": "The exchange code where the trade correction occurred." - }, - "oi": { - "existingJavaType": "java.lang.Integer", - "javaName": "originalTradeID", - "title": "The original trade ID." - }, - "op": { - "existingJavaType": "java.lang.Double", - "javaName": "originalPrice", - "title": "The original trade price." - }, - "os": { - "existingJavaType": "java.lang.Integer", - "javaName": "originalSize", - "title": "The original trade size." - }, - "oc": { - "existingJavaType": "java.util.Set", - "javaName": "originalConditions", - "title": "The {@link java.util.Set} of original trade conditions." - }, - "ci": { - "existingJavaType": "java.lang.Integer", - "javaName": "correctedTradeID", - "title": "The corrected trade ID." - }, - "cp": { - "existingJavaType": "java.lang.Double", - "javaName": "correctedPrice", - "title": "The corrected trade price." - }, - "cs": { - "existingJavaType": "java.lang.Integer", - "javaName": "correctedSize", - "title": "The corrected trade size." - }, - "cc": { - "existingJavaType": "java.util.Set", - "javaName": "correctedConditions", - "title": "The {@link java.util.Set} of original trade conditions." - }, - "t": { - "existingJavaType": "java.time.OffsetDateTime", - "javaName": "timestamp", - "title": "The timestamp with nanosecond precision." - }, - "z": { - "existingJavaType": "java.lang.String", - "javaName": "tape", - "title": "The tape." - } - } -} diff --git a/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/streams/stock/model/tradingstatus/stock_trading_status_message.json b/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/streams/stock/model/tradingstatus/stock_trading_status_message.json deleted file mode 100644 index d1946769..00000000 --- a/src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/streams/stock/model/tradingstatus/stock_trading_status_message.json +++ /dev/null @@ -1,43 +0,0 @@ -{ - "type": "object", - "extends": { - "existingJavaType": "net.jacobpeterson.alpaca.model.websocket.marketdata.streams.stock.model.StockMarketDataMessage" - }, - "properties": { - "S": { - "existingJavaType": "java.lang.String", - "javaName": "symbol", - "title": "The symbol." - }, - "sc": { - "existingJavaType": "java.lang.String", - "javaName": "statusCode", - "title": "The status code." - }, - "sm": { - "existingJavaType": "java.lang.String", - "javaName": "statusMessage", - "title": "The status message." - }, - "rc": { - "existingJavaType": "java.lang.String", - "javaName": "reasonCode", - "title": "The reason code." - }, - "rm": { - "existingJavaType": "java.lang.String", - "javaName": "reasonMessage", - "title": "The reason message." - }, - "t": { - "existingJavaType": "java.time.OffsetDateTime", - "javaName": "timestamp", - "title": "The timestamp with nanosecond precision." - }, - "z": { - "existingJavaType": "java.lang.String", - "javaName": "tape", - "title": "The tape." - } - } -} diff --git a/src/main/java/net/jacobpeterson/alpaca/websocket/updates/UpdatesListener.java b/src/main/java/net/jacobpeterson/alpaca/websocket/updates/UpdatesListener.java deleted file mode 100644 index 3a752308..00000000 --- a/src/main/java/net/jacobpeterson/alpaca/websocket/updates/UpdatesListener.java +++ /dev/null @@ -1,17 +0,0 @@ -package net.jacobpeterson.alpaca.websocket.updates; - -import net.jacobpeterson.alpaca.model.websocket.updates.model.tradeupdate.TradeUpdateMessage; - -/** - * {@link UpdatesListener} defines a listener interface for {@link UpdatesWebsocketInterface} messages. - */ -@FunctionalInterface -public interface UpdatesListener { - - /** - * Called when a {@link TradeUpdateMessage} is received. - * - * @param tradeUpdate the {@link TradeUpdateMessage} - */ - void onTradeUpdate(TradeUpdateMessage tradeUpdate); -} diff --git a/src/main/java/net/jacobpeterson/alpaca/websocket/updates/UpdatesWebsocket.java b/src/main/java/net/jacobpeterson/alpaca/websocket/updates/UpdatesWebsocket.java deleted file mode 100644 index ae31a623..00000000 --- a/src/main/java/net/jacobpeterson/alpaca/websocket/updates/UpdatesWebsocket.java +++ /dev/null @@ -1,169 +0,0 @@ -package net.jacobpeterson.alpaca.websocket.updates; - -import com.google.gson.JsonArray; -import com.google.gson.JsonObject; -import net.jacobpeterson.alpaca.model.util.apitype.TraderAPIEndpointType; -import net.jacobpeterson.alpaca.model.websocket.updates.model.UpdatesMessageType; -import net.jacobpeterson.alpaca.model.websocket.updates.model.authorization.AuthorizationMessage; -import net.jacobpeterson.alpaca.model.websocket.updates.model.tradeupdate.TradeUpdateMessage; -import net.jacobpeterson.alpaca.websocket.AlpacaWebsocket; -import okhttp3.HttpUrl; -import okhttp3.OkHttpClient; -import okhttp3.WebSocket; -import okio.ByteString; -import org.jetbrains.annotations.NotNull; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import static com.google.gson.JsonParser.parseString; -import static java.util.concurrent.TimeUnit.SECONDS; -import static net.jacobpeterson.alpaca.model.websocket.updates.model.UpdatesMessageType.TRADE_UPDATES; -import static net.jacobpeterson.alpaca.openapi.trader.JSON.getGson; - -/** - * {@link UpdatesWebsocket} is an {@link AlpacaWebsocket} implementation and provides the - * {@link UpdatesWebsocketInterface} interface for - * Updates Streaming. - */ -public class UpdatesWebsocket extends AlpacaWebsocket implements UpdatesWebsocketInterface { - - private static final Logger LOGGER = LoggerFactory.getLogger(UpdatesWebsocket.class); - - @SuppressWarnings("UnnecessaryDefault") - private static HttpUrl createWebsocketURL(TraderAPIEndpointType traderAPIEndpointType) { - return new HttpUrl.Builder() - .scheme("https") - .host((switch (traderAPIEndpointType) { - case LIVE -> "api"; - case PAPER -> "paper-api"; - default -> throw new UnsupportedOperationException(); - }) + ".alpaca.markets") - .addPathSegment("stream") - .build(); - } - - protected final String keyID; - protected final String secretKey; - protected final String oAuthToken; - protected UpdatesListener listener; - protected boolean listenToTradeUpdates; - - /** - * Instantiates a new {@link UpdatesWebsocket}. - * - * @param okHttpClient the {@link OkHttpClient} - * @param traderAPIEndpointType the {@link TraderAPIEndpointType} - * @param keyID the key ID - * @param secretKey the secret key - * @param oAuthToken the OAuth token - */ - public UpdatesWebsocket(OkHttpClient okHttpClient, TraderAPIEndpointType traderAPIEndpointType, - String keyID, String secretKey, String oAuthToken) { - super(okHttpClient, createWebsocketURL(traderAPIEndpointType), "Trades Stream"); - this.keyID = keyID; - this.secretKey = secretKey; - this.oAuthToken = oAuthToken; - } - - @Override - protected void cleanupState() { - super.cleanupState(); - } - - @Override - protected void onConnection() { - sendAuthenticationMessage(); - } - - @Override - protected void onReconnection() { - sendAuthenticationMessage(); - if (waitForAuthorization(5, SECONDS) && listenToTradeUpdates) { - sendTradeUpdatesListenMessage(); - } - } - - @Override - protected void sendAuthenticationMessage() { - // Ensure that the authorization Future exists - getAuthorizationFuture(); - - final JsonObject authObject = new JsonObject(); - authObject.addProperty("action", "authenticate"); - final JsonObject authData = new JsonObject(); - if (oAuthToken != null) { - authData.addProperty("oauth_token", oAuthToken); - } else { - authData.addProperty("key_id", keyID); - authData.addProperty("secret_key", secretKey); - } - authObject.add("data", authData); - - LOGGER.info("{} websocket sending authentication message...", websocketName); - sendWebsocketMessage(authObject.toString()); - } - - @Override - public void onMessage(@NotNull WebSocket webSocket, @NotNull ByteString byteString) { // Binary framing - final String messageString = byteString.utf8(); - LOGGER.trace("Websocket message received: message={}", messageString); - - // Parse JSON string and identify 'messageType' - final JsonObject messageObject = parseString(messageString).getAsJsonObject(); - final UpdatesMessageType messageType = - getGson().fromJson(messageObject.get("stream"), UpdatesMessageType.class); - - // Deserialize message based on 'messageType' and call listener - switch (messageType) { - case AUTHORIZATION: - final AuthorizationMessage authorizationMessage = - getGson().fromJson(messageObject, AuthorizationMessage.class); - authenticated = authorizationMessage.getData().getAction().equalsIgnoreCase("authenticate") && - authorizationMessage.getData().getStatus().equalsIgnoreCase("authorized"); - if (authenticationMessageFuture != null) { - authenticationMessageFuture.complete(authenticated); - } - if (!authenticated) { - throw new RuntimeException(websocketName + " websocket authentication failed!"); - } else { - LOGGER.info("{} websocket authenticated.", websocketName); - } - break; - case LISTENING: - break; - case TRADE_UPDATES: - if (listener != null) { - listener.onTradeUpdate(getGson().fromJson(messageObject, TradeUpdateMessage.class)); - } - break; - default: - throw new UnsupportedOperationException(); - } - } - - @Override - public void setListener(UpdatesListener listener) { - this.listener = listener; - } - - @Override - public void subscribeToTradeUpdates(boolean subscribe) { - listenToTradeUpdates = subscribe; - sendTradeUpdatesListenMessage(); - } - - private void sendTradeUpdatesListenMessage() { - final JsonObject requestObject = new JsonObject(); - requestObject.addProperty("action", "listen"); - final JsonArray streamsArray = new JsonArray(); - if (listenToTradeUpdates) { - streamsArray.add(TRADE_UPDATES.toString()); - } - final JsonObject dataObject = new JsonObject(); - dataObject.add("streams", streamsArray); - requestObject.add("data", dataObject); - - sendWebsocketMessage(requestObject.toString()); - LOGGER.info("Requested streams: streams={}.", streamsArray); - } -} diff --git a/src/main/java/net/jacobpeterson/alpaca/websocket/updates/UpdatesWebsocketInterface.java b/src/main/java/net/jacobpeterson/alpaca/websocket/updates/UpdatesWebsocketInterface.java deleted file mode 100644 index 0c86554c..00000000 --- a/src/main/java/net/jacobpeterson/alpaca/websocket/updates/UpdatesWebsocketInterface.java +++ /dev/null @@ -1,24 +0,0 @@ -package net.jacobpeterson.alpaca.websocket.updates; - -import net.jacobpeterson.alpaca.model.websocket.updates.model.UpdatesMessageType; -import net.jacobpeterson.alpaca.websocket.AlpacaWebsocketInterface; - -/** - * {@link UpdatesWebsocketInterface} is an {@link AlpacaWebsocketInterface} for {@link UpdatesWebsocket}. - */ -public interface UpdatesWebsocketInterface extends AlpacaWebsocketInterface { - - /** - * Sets the {@link UpdatesListener}. - * - * @param listener the {@link UpdatesListener} - */ - void setListener(UpdatesListener listener); - - /** - * Subscribes to {@link UpdatesMessageType#TRADE_UPDATES}. - * - * @param subscribe true to subscribe, false otherwise - */ - void subscribeToTradeUpdates(boolean subscribe); -} diff --git a/src/main/java/net/jacobpeterson/alpaca/websocket/updates/model/authorization/authorization_data.json b/src/main/java/net/jacobpeterson/alpaca/websocket/updates/model/authorization/authorization_data.json deleted file mode 100644 index f05ae9bd..00000000 --- a/src/main/java/net/jacobpeterson/alpaca/websocket/updates/model/authorization/authorization_data.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "type": "object", - "properties": { - "status": { - "existingJavaType": "java.lang.String", - "title": "The status." - }, - "action": { - "existingJavaType": "java.lang.String", - "title": "The desired action." - } - } -} diff --git a/src/main/java/net/jacobpeterson/alpaca/websocket/updates/model/authorization/authorization_message.json b/src/main/java/net/jacobpeterson/alpaca/websocket/updates/model/authorization/authorization_message.json deleted file mode 100644 index 4c991d10..00000000 --- a/src/main/java/net/jacobpeterson/alpaca/websocket/updates/model/authorization/authorization_message.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "type": "object", - "extends": { - "existingJavaType": "net.jacobpeterson.alpaca.model.websocket.updates.model.UpdatesMessage" - }, - "properties": { - "data": { - "existingJavaType": "net.jacobpeterson.alpaca.model.websocket.updates.model.authorization.AuthorizationData", - "title": "The {@link net.jacobpeterson.alpaca.model.websocket.updates.model.authorization.AuthorizationData}." - } - } -} diff --git a/src/main/java/net/jacobpeterson/alpaca/websocket/updates/model/listening/listening_data.json b/src/main/java/net/jacobpeterson/alpaca/websocket/updates/model/listening/listening_data.json deleted file mode 100644 index b2798264..00000000 --- a/src/main/java/net/jacobpeterson/alpaca/websocket/updates/model/listening/listening_data.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "type": "object", - "properties": { - "streams": { - "existingJavaType": "java.util.Set", - "title": "The {@link java.util.Set} of {@link net.jacobpeterson.alpaca.model.websocket.updates.model.UpdatesMessageType}s." - } - } -} diff --git a/src/main/java/net/jacobpeterson/alpaca/websocket/updates/model/listening/listening_message.json b/src/main/java/net/jacobpeterson/alpaca/websocket/updates/model/listening/listening_message.json deleted file mode 100644 index 0590794f..00000000 --- a/src/main/java/net/jacobpeterson/alpaca/websocket/updates/model/listening/listening_message.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "type": "object", - "extends": { - "existingJavaType": "net.jacobpeterson.alpaca.model.websocket.updates.model.UpdatesMessage" - }, - "properties": { - "data": { - "existingJavaType": "net.jacobpeterson.alpaca.model.websocket.updates.model.listening.ListeningData", - "title": "The {@link net.jacobpeterson.alpaca.model.websocket.updates.model.listening.ListeningData}." - } - } -} diff --git a/src/main/java/net/jacobpeterson/alpaca/websocket/updates/model/tradeupdate/trade_update.json b/src/main/java/net/jacobpeterson/alpaca/websocket/updates/model/tradeupdate/trade_update.json deleted file mode 100644 index 27f86dc2..00000000 --- a/src/main/java/net/jacobpeterson/alpaca/websocket/updates/model/tradeupdate/trade_update.json +++ /dev/null @@ -1,35 +0,0 @@ -{ - "type": "object", - "properties": { - "event": { - "existingJavaType": "net.jacobpeterson.alpaca.model.websocket.updates.model.tradeupdate.TradeUpdateEvent", - "title": "The {@link net.jacobpeterson.alpaca.model.websocket.updates.model.tradeupdate.TradeUpdateEvent}." - }, - "execution_id": { - "existingJavaType": "java.lang.String", - "title": "The execution ID." - }, - "price": { - "existingJavaType": "java.lang.String", - "title": "The price." - }, - "position_qty": { - "existingJavaType": "java.lang.String", - "javaName": "positionQuantity", - "title": "The position quantity." - }, - "qty": { - "existingJavaType": "java.lang.String", - "javaName": "quantity", - "title": "The quantity." - }, - "timestamp": { - "existingJavaType": "java.time.OffsetDateTime", - "title": "The timestamp." - }, - "order": { - "existingJavaType": "net.jacobpeterson.alpaca.openapi.trader.model.Order", - "title": "The {@link net.jacobpeterson.alpaca.openapi.trader.model.Order}." - } - } -} diff --git a/src/main/java/net/jacobpeterson/alpaca/websocket/updates/model/tradeupdate/trade_update_event.json b/src/main/java/net/jacobpeterson/alpaca/websocket/updates/model/tradeupdate/trade_update_event.json deleted file mode 100644 index 1277728e..00000000 --- a/src/main/java/net/jacobpeterson/alpaca/websocket/updates/model/tradeupdate/trade_update_event.json +++ /dev/null @@ -1,71 +0,0 @@ -{ - "type": "string", - "enum": [ - "new", - "fill", - "partial_fill", - "canceled", - "expired", - "done_for_day", - "replaced", - "rejected", - "pending_new", - "stopped", - "pending_cancel", - "pending_replace", - "calculated", - "suspended", - "order_replace_rejected", - "order_cancel_rejected" - ], - "javaEnums": [ - { - "description": "Sent when an order has been routed to exchanges for execution." - }, - { - "description": "Sent when an order has been completely filled." - }, - { - "description": "Sent when a number of shares less than the total remaining quantity on your order has been filled." - }, - { - "description": "Sent when your requested cancellation of an order is processed." - }, - { - "description": "Sent when an order has reached the end of its lifespan, as determined by the order’s time in force value." - }, - { - "description": "Sent when the order is done executing for the day, and will not receive further updates until the next trading day." - }, - { - "description": "Sent when your requested replacement of an order is processed." - }, - { - "description": "Sent when your order has been rejected." - }, - { - "description": "Sent when the order has been received by Alpaca and routed to the exchanges, but has not yet been accepted for execution." - }, - { - "description": "Sent when your order has been stopped, and a trade is guaranteed for the order, usually at a stated price or better, but has not yet occurred." - }, - { - "description": "Sent when the order is awaiting cancellation. Most cancellations will occur without the order entering this state." - }, - { - "description": "Sent when the order is awaiting replacement." - }, - { - "description": "Sent when the order has been completed for the day - it is either \"filled\" or Z\"done_for_day\" - but remaining settlement calculations are still pending." - }, - { - "description": "Sent when the order has been suspended and is not eligible for trading." - }, - { - "description": "Sent when the order replace has been rejected." - }, - { - "description": "Sent when the order cancel has been rejected." - } - ] -} diff --git a/src/main/java/net/jacobpeterson/alpaca/websocket/updates/model/tradeupdate/trade_update_message.json b/src/main/java/net/jacobpeterson/alpaca/websocket/updates/model/tradeupdate/trade_update_message.json deleted file mode 100644 index af22fa58..00000000 --- a/src/main/java/net/jacobpeterson/alpaca/websocket/updates/model/tradeupdate/trade_update_message.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "type": "object", - "extends": { - "existingJavaType": "net.jacobpeterson.alpaca.model.websocket.updates.model.UpdatesMessage" - }, - "properties": { - "data": { - "existingJavaType": "net.jacobpeterson.alpaca.model.websocket.updates.model.tradeupdate.TradeUpdate", - "title": "The {@link net.jacobpeterson.alpaca.model.websocket.updates.model.tradeupdate.TradeUpdate}." - } - } -} diff --git a/src/main/java/net/jacobpeterson/alpaca/websocket/updates/model/updates_message.json b/src/main/java/net/jacobpeterson/alpaca/websocket/updates/model/updates_message.json deleted file mode 100644 index ad625676..00000000 --- a/src/main/java/net/jacobpeterson/alpaca/websocket/updates/model/updates_message.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "type": "object", - "properties": { - "stream": { - "existingJavaType": "net.jacobpeterson.alpaca.model.websocket.updates.model.UpdatesMessageType", - "title": "The {@link net.jacobpeterson.alpaca.model.websocket.updates.model.UpdatesMessageType}." - } - } -} diff --git a/src/main/java/net/jacobpeterson/alpaca/websocket/updates/model/updates_message_type.json b/src/main/java/net/jacobpeterson/alpaca/websocket/updates/model/updates_message_type.json deleted file mode 100644 index 98ab6ea6..00000000 --- a/src/main/java/net/jacobpeterson/alpaca/websocket/updates/model/updates_message_type.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "type": "string", - "enum": [ - "listening", - "authorization", - "trade_updates" - ] -} From 4b8e2dccc336759ec2e75b42c2e568777a929052 Mon Sep 17 00:00:00 2001 From: Jacob Peterson <14127217+Petersoj@users.noreply.github.com> Date: Tue, 7 Jul 2026 16:19:30 -0600 Subject: [PATCH 263/271] Remove old README and logo --- .github/image/logo.png | Bin 52737 -> 0 bytes README.md | 266 ----------------------------------------- _config.yml | 1 - 3 files changed, 267 deletions(-) delete mode 100644 .github/image/logo.png delete mode 100644 README.md delete mode 100644 _config.yml diff --git a/.github/image/logo.png b/.github/image/logo.png deleted file mode 100644 index a9cdaa37b4314105f5b4663bc274384a7cba552d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 52737 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 GGywn&neP7p diff --git a/README.md b/README.md deleted file mode 100644 index a7a4efb9..00000000 --- a/README.md +++ /dev/null @@ -1,266 +0,0 @@ -

Logo

-

- GitHub Repository - Maven Central - Javadocs - GitHub -

- -# Overview -This library is a Java client implementation of the [Alpaca](https://alpaca.markets) API. Alpaca lets you trade with algorithms, connect with apps, and build services all with a commission-free trading API for stocks, crypto, and options. This library uses the [Alpaca OpenAPI Specification](https://docs.alpaca.markets/openapi) to generate clients for the REST API with the [OkHttp](https://square.github.io/okhttp/) library, but implements the websocket and SSE streaming interface using a custom implementation with the [OkHttp](https://square.github.io/okhttp/) library. This library is community developed and if you have any questions, please ask them on [Github Discussions](https://github.com/Petersoj/alpaca-java/discussions), the [Alpaca Slack #dev-alpaca-java channel](https://alpaca.markets/slack), or on the [Alpaca Forums](https://forum.alpaca.markets/). This library strives to provide the complete Alpaca API as a Java client implementation, so open a [new issue](https://github.com/Petersoj/alpaca-java/issues) or [new pull request](https://github.com/Petersoj/alpaca-java/pulls) if you find something missing. - -Give this repository a star ⭐ if it helped you build a trading algorithm in Java! - -# Gradle and Maven Integration -If you are using Gradle as your build tool, add the following dependency to your `build.gradle` file: - -``` -implementation group: "net.jacobpeterson.alpaca", name: "alpaca-java", version: "10.0.1" -``` - -If you are using Maven as your build tool, add the following dependency to your `pom.xml` file: - -``` - - net.jacobpeterson.alpaca - alpaca-java - 10.0.1 - -``` - -Note that you don't have to use the Maven Central artifacts. Instead, you can clone this repository, build this project, and install the artifacts to your local Maven repository as shown in the [Building](#building) section. - -# Logger -For logging, this library uses [SLF4j](http://www.slf4j.org/) which serves as an interface for various logging frameworks. This enables you to use whatever logging framework you would like. However, if you do not add a logging framework as a dependency in your project, the console will output a message stating that SLF4j is defaulting to a no-operation (NOP) logger implementation. To enable logging, add a logging framework of your choice as a dependency to your project such as [Logback](http://logback.qos.ch/), [Log4j 2](http://logging.apache.org/log4j/2.x/index.html), [SLF4j-simple](http://www.slf4j.org/manual.html), or [Apache Commons Logging](https://commons.apache.org/proper/commons-logging/). - -# `BigDecimal` vs. `Double` -It is generally considered bad practice to represent monetary values using floating-point number data types such as `float` or `double` because it can lead to [rounding errors](https://ta4j.github.io/ta4j-wiki/Num.html) and [precision loss](https://stackoverflow.com/a/3730040/4352701). However, using floating-point number data types can have significant performance benefits compared to using fixed-point number data types (via `BigDecimal`), especially in a quantitative finance and algorithmic trading environment. Because of this, `alpaca-java` uses the `Double` data type (the `double` boxed type) when using the Market Data APIs and the `BigDecimal` data type when using the Trading or Broker APIs. The rationale behind this is that exact decimal quantities are important when placing real trades with real money, but less important when performing calculations through technical indicators. The best solution would be to use the [TA4j](https://github.com/ta4j/ta4j) `Num` interface so that you can decide what data type to use based on your use case, but that is on the [TODO list](#todo) for now. By the way, using `BigDecimal` wouldn't matter if Alpaca used floating-point data types in their internal systems, but the fact that they use `String` data types in some of the REST API JSON responses and their Trading and Broker OpenAPI specifications don't use the `double` data type, this leads me to believe that using `BigDecimal` does actually matter. - -# Examples -Note that the examples below are not exhaustive. Refer to the [Javadoc](https://javadoc.io/doc/net.jacobpeterson.alpaca/alpaca-java) for all classes and method signatures. - -## [`AlpacaAPI`](src/main/java/net/jacobpeterson/alpaca/AlpacaAPI.java) -[`AlpacaAPI`](src/main/java/net/jacobpeterson/alpaca/AlpacaAPI.java) is the main class used to interface with the various Alpaca API endpoints. If you are using the Trading or Market Data APIs for a single Alpaca account or if you are using the Broker API, you will generally only need one instance of this class. However, if you are using the Trading API with OAuth to act on behalf of an Alpaca account, this class is optimized so that it can be instantiated quickly, especially when an existing `OkHttpClient` is given in the constructor. Additionally, all API endpoint instances are instantiated lazily. This class is thread-safe. - -The Alpaca API documentation is located [here](https://docs.alpaca.markets/) and the [`AlpacaAPI`](src/main/java/net/jacobpeterson/alpaca/AlpacaAPI.java) Javadoc is located [here](https://javadoc.io/doc/net.jacobpeterson.alpaca/alpaca-java/latest/net/jacobpeterson/alpaca/AlpacaAPI.html). - -Example usage: - -Use this code if you are using the Trading or Market Data APIs for a single Alpaca account: -```java -final String keyID = ""; -final String secretKey = ""; -final TraderAPIEndpointType endpointType = TraderAPIEndpointType.PAPER; // or 'LIVE' -final MarketDataWebsocketSourceType sourceType = MarketDataWebsocketSourceType.IEX; // or 'SIP' -final AlpacaAPI alpacaAPI = new AlpacaAPI(keyID, secretKey, endpointType, sourceType); -``` - -Use this code if you are using the Trading API with OAuth to act on behalf of an Alpaca account: -```java -final String oAuthToken = ""; -final TraderAPIEndpointType endpointType = TraderAPIEndpointType.PAPER; // or 'LIVE' -final AlpacaAPI alpacaAPI = new AlpacaAPI(oAuthToken, endpointType); -``` - -Use this code if you are using the Broker API: -```java -final String brokerAPIKey = ""; -final String brokerAPISecret = ""; -final BrokerAPIEndpointType endpointType = BrokerAPIEndpointType.SANDBOX; // or 'PRODUCTION' -final AlpacaAPI alpacaAPI = new AlpacaAPI(brokerAPIKey, brokerAPISecret, endpointType); -``` - -## [`Trader API`](src/main/java/net/jacobpeterson/alpaca/rest/trader/AlpacaTraderAPI.java) -The [`Trader API`](src/main/java/net/jacobpeterson/alpaca/rest/trader/AlpacaTraderAPI.java) is used for placing trades, updating account details, getting open positions, and more. Refer to the [Javadoc](https://javadoc.io/doc/net.jacobpeterson.alpaca/alpaca-java/latest/net/jacobpeterson/alpaca/rest/trader/AlpacaTraderAPI.html) for a list of all available method signatures. - -Example usage: -```java -// Place a market order to buy one share of Apple -final Order openingOrder = alpacaAPI.trader().orders() - .postOrder(new PostOrderRequest() - .symbol("AAPL") - .qty("1") - .side(OrderSide.BUY) - .type(OrderType.MARKET) - .timeInForce(TimeInForce.GTC)); -System.out.println("Opening Apple order: " + openingOrder); - -// Wait for massive gains -Thread.sleep(10_000); - -// Close the Apple position -final Order closingOrder = alpacaAPI.trader().positions() - .deleteOpenPosition("AAPL", null, new BigDecimal("100")); -System.out.println("Closing Apple order: " + openingOrder); - -// Wait for closing trade to fill -Thread.sleep(10_000); - -// Print out PnL -final String openFillPrice = alpacaAPI.trader().orders() - .getOrderByOrderID(UUID.fromString(openingOrder.getId()), false) - .getFilledAvgPrice(); -final String closeFillPrice = alpacaAPI.trader().orders() - .getOrderByOrderID(UUID.fromString(closingOrder.getId()), false) - .getFilledAvgPrice(); -System.out.println("PnL from Apple trade: " + - new BigDecimal(closeFillPrice).subtract(new BigDecimal(openFillPrice))); -``` - -## [`Market Data API`](src/main/java/net/jacobpeterson/alpaca/rest/marketdata/AlpacaMarketDataAPI.java) -The [`Market Data API`](src/main/java/net/jacobpeterson/alpaca/rest/marketdata/AlpacaMarketDataAPI.java) is used for getting market data for stocks, cryptocurrencies, options, and more. Refer to the [Javadoc](https://javadoc.io/doc/net.jacobpeterson.alpaca/alpaca-java/latest/net/jacobpeterson/alpaca/rest/marketdata/AlpacaMarketDataAPI.html) for a list of all available method signatures. - -Example usage: -```java -// Print out the latest Tesla trade -final StockTrade latestTSLATrade = alpacaAPI.marketData().stock() - .stockLatestTradeSingle("TSLA", StockFeed.IEX, null).getTrade(); -System.out.printf("Latest TSLA trade: price=%s, size=%s\n", - latestTSLATrade.getP(), latestTSLATrade.getS()); - -// Print out the highest Bitcoin ask price on the order book -final CryptoOrderbook latestBitcoinOrderBooks = alpacaAPI.marketData().crypto() - .cryptoLatestOrderbooks(CryptoLoc.US, "BTC/USD").getOrderbooks().get("BTC/USD"); -final Double highestBitcoinAskPrice = latestBitcoinOrderBooks.getA().stream() - .map(CryptoOrderbookEntry::getP) - .max(Double::compare) - .orElse(null); -System.out.println("Bitcoin highest ask price: " + highestBitcoinAskPrice); - -// Print out the latest Microsoft option trade -final String latestMSFTOptionTrade = alpacaAPI.marketData().option() - .optionChain("MSFT").getSnapshots().entrySet().stream() - .filter(entry -> entry.getValue().getLatestTrade() != null) - .map(entry -> Map.entry(entry.getKey(), entry.getValue().getLatestTrade())) - .max(Comparator.comparing(entry -> entry.getValue().getT())) - .map(entry -> String.format("ticker=%s, time=%s, price=%s", - entry.getKey(), entry.getValue().getT(), entry.getValue().getP())) - .orElse(null); -System.out.println("Latest Microsoft option trade: " + latestMSFTOptionTrade); -``` - -## [`Broker API`](src/main/java/net/jacobpeterson/alpaca/rest/broker/AlpacaBrokerAPI.java) -The [`Broker API`](src/main/java/net/jacobpeterson/alpaca/rest/broker/AlpacaBrokerAPI.java) is used for creating new Alpaca accounts for your end users, funding their accounts, placing trades, and more. Refer to the [Javadoc](https://javadoc.io/doc/net.jacobpeterson.alpaca/alpaca-java/latest/net/jacobpeterson/alpaca/rest/broker/AlpacaBrokerAPI.html) for a list of all available method signatures. - -Example usage: -```java -// Listen to the trade events (SSE) and print them out -final SSERequest sseRequest = alpacaAPI.broker().events() - .subscribeToTradeV2(null, null, null, null, new SSEListenerAdapter<>()); - -// Wait for SSE channel to be ready -Thread.sleep(2000); - -// Buy one share of GME for an account -alpacaAPI.broker().trading() - .createOrderForAccount(UUID.fromString(""), - new CreateOrderRequest() - .symbol("GME") - .qty(BigDecimal.ONE) - .side(OrderSide.SELL) - .timeInForce(TimeInForce.GTC) - .type(OrderType.MARKET)); - -// Wait to be filled -Thread.sleep(2000); - -// Close the SSE stream and the OkHttpClient to exit cleanly -sseRequest.close(); -``` - -## [`Updates Stream`](src/main/java/net/jacobpeterson/alpaca/websocket/updates/UpdatesWebsocket.java) -The [`Updates Stream`](src/main/java/net/jacobpeterson/alpaca/websocket/updates/UpdatesWebsocket.java) is used for listening to trade updates in realtime. Refer to the [Javadoc](https://javadoc.io/doc/net.jacobpeterson.alpaca/alpaca-java/latest/net/jacobpeterson/alpaca/websocket/updates/UpdatesWebsocketInterface.html) for a list of all available method signatures. - -Example usage: -```java -// Connect to the 'updates' stream and wait until it's authorized -alpacaAPI.updatesStream().connect(); -if (!alpacaAPI.updatesStream().waitForAuthorization(5, TimeUnit.SECONDS)) { - throw new RuntimeException(); -} - -// Print out trade updates -alpacaAPI.updatesStream().setListener(System.out::println); -alpacaAPI.updatesStream().subscribeToTradeUpdates(true); - -// Place a trade -alpacaAPI.trader().orders().postOrder(new PostOrderRequest() - .symbol("AAPL") - .qty("1") - .side(OrderSide.BUY) - .type(OrderType.MARKET) - .timeInForce(TimeInForce.GTC)); - -// Wait a few seconds -Thread.sleep(5000); - -// Close the trade -alpacaAPI.trader().positions().deleteAllOpenPositions(true); - -// Wait a few seconds -Thread.sleep(5000); - -// Disconnect the 'updates' stream and exit cleanly -alpacaAPI.updatesStream().disconnect(); -``` - -## [`Market Data Stream`](src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/MarketDataWebsocket.java) -The [`Market Data Stream`](src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/MarketDataWebsocket.java) is used for listening to stock market data, crypto market data, and news data in realtime. Refer to the Javadocs ([stock](https://javadoc.io/doc/net.jacobpeterson.alpaca/alpaca-java/latest/net/jacobpeterson/alpaca/websocket/marketdata/streams/stock/StockMarketDataWebsocketInterface.html), [crypto](https://javadoc.io/doc/net.jacobpeterson.alpaca/alpaca-java/latest/net/jacobpeterson/alpaca/websocket/marketdata/streams/crypto/CryptoMarketDataWebsocketInterface.html), [news](https://javadoc.io/doc/net.jacobpeterson.alpaca/alpaca-java/latest/net/jacobpeterson/alpaca/websocket/marketdata/streams/news/NewsMarketDataWebsocketInterface.html)) for a list of all available method signatures. - -Example usage: -```java -// Connect to the 'stock market data' stream and wait until it's authorized -alpacaAPI.stockMarketDataStream().connect(); -if (!alpacaAPI.stockMarketDataStream().waitForAuthorization(5, TimeUnit.SECONDS)) { - throw new RuntimeException(); -} - -// Print out trade messages -alpacaAPI.stockMarketDataStream().setListener(new StockMarketDataListenerAdapter() { - @Override - public void onTrade(StockTradeMessage trade) { - System.out.println("Received trade: " + trade); - } -}); - -// Subscribe to AAPL trades -alpacaAPI.stockMarketDataStream().setTradeSubscriptions(Set.of("AAPL")); -System.out.println("Subscribed to Apple trades."); - -// Wait a few seconds -Thread.sleep(5000); - -// Unsubscribe from AAPL and subscribe to TSLA and MSFT -alpacaAPI.stockMarketDataStream().setTradeSubscriptions(Set.of("TSLA", "MSFT")); -System.out.println("Subscribed to Tesla and Microsoft trades."); - -// Wait a few seconds -Thread.sleep(5000); - -// Disconnect the 'stock market data' stream and exit cleanly -alpacaAPI.stockMarketDataStream().disconnect(); -``` - -# Building -To build this project yourself, clone this repository and run: -``` -./gradlew build -``` - -To install the built artifacts to your local Maven repository on your machine (the `~/.m2/` directory), run: -``` -./gradlew publishToMavenLocal -``` - -# TODO -- Implement Options market data. -- Implement better reconnect logic for Websockets and SSE streaming. -- Implement Unit Testing for REST API and Websocket streaming (both live and mocked). -- Use [TA4j](https://github.com/ta4j/ta4j) `Num` interface instead of `Double` or `BigDecimal` for number data types so that users can use either `Double` or `BigDecimal` for performance or precision in price data. - -# Contributing -Contributions are welcome! - -Do the following before starting your pull request: -1. Create a new branch in your forked repository for your feature or bug fix instead of committing directly to the `master` branch in your fork. -2. Use the `dev` branch as the base branch in your pull request. diff --git a/_config.yml b/_config.yml deleted file mode 100644 index 72d781d7..00000000 --- a/_config.yml +++ /dev/null @@ -1 +0,0 @@ -theme: jekyll-theme-tactile From 803b0269190e27d3f7315e8049ee3625b53dbf38 Mon Sep 17 00:00:00 2001 From: Jacob Peterson <14127217+Petersoj@users.noreply.github.com> Date: Tue, 7 Jul 2026 16:19:39 -0600 Subject: [PATCH 264/271] Add new README to `docs/` --- docs/README.md | 328 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 328 insertions(+) create mode 100644 docs/README.md diff --git a/docs/README.md b/docs/README.md new file mode 100644 index 00000000..f463f21f --- /dev/null +++ b/docs/README.md @@ -0,0 +1,328 @@ +

+
+ + Logo +



+ + GitHub Repository + + GitHub Pages + + GitHub Discussions +
+ + Maven Central + + javadoc +
+ + Java Version + + License +
+
+ +# Table of Contents + +- [Introduction](#introduction) +- [Logging](#logging) +- [Number Representation for Currency Values](#number-representation-for-currency-values) +- [⚠️ WebSocket Support is Coming Soon ⚠️](#-websocket-support-is-coming-soon-) +- [Modules](#modules) + - [Trading](#trading) + - [Installation](#installation) + - [Example](#example) + - [Market Data](#market-data) + - [Installation](#installation-1) + - [Example](#example-1) + - [Broker](#broker) + - [Installation](#installation-2) + - [Example](#example-2) + +# Introduction + +This project is a Java client library for the [Alpaca](https://alpaca.markets) API. Alpaca is a brokerage platform that +lets you trade with algorithms, connect with apps, and build services all with a commission-free trading API for stocks, +crypto, options, and more. This library uses the [Alpaca OpenAPI Specifications](https://docs.alpaca.markets/us/openapi) +to generate clients for the REST API and provides a custom implementation for WebSocket and SSE support. This library is +community developed and if you have any questions, please ask them on +[GitHub Discussions](https://github.com/Petersoj/alpaca-java/discussions), the +[Alpaca Slack #dev-alpaca-java channel](https://alpaca.markets/slack), or on the +[Alpaca Forums](https://forum.alpaca.markets/). + +Alpaca Java offers three modules: [Trading](#trading), [Market Data](#market-data), and [Broker](#broker). + +Some awesome things about Alpaca Java: + +- Exhaustive Javadoc documentation +- Uses Java's native `HttpClient` (which also enables you to provide a different HTTP client implementation) +- Lightweight with minimal dependencies (only dependencies are: SLF4j, Guava, Jackson, and MessagePack) +- Releases are built directly from this source repository using GitHub Actions CI +- Kotlin friendly +- MIT licensed + +Give this repository a star ⭐ and consider [sponsoring](https://github.com/sponsors/Petersoj) ❤️ + +# Logging + +For logging, this library uses [SLF4j](http://www.slf4j.org/) which serves as an interface for various logging +frameworks. This enables you to use whatever logging framework you would like. However, if you do not add a logging +framework as a dependency in your project, the console will output a message stating that SLF4j is defaulting to a +no-operation (NOP) logger implementation. To enable logging, add a logging framework of your choice as a runtime +dependency to your project such as [Logback](http://logback.qos.ch/), +[Log4j 2](http://logging.apache.org/log4j/2.x/index.html), [SLF4j-simple](http://www.slf4j.org/manual.html), or +[Apache Commons Logging](https://commons.apache.org/proper/commons-logging/). + +# Number Representation for Currency Values + +When working with currency values in computer memory, you may find yourself going back-and-forth between the `Double` +and `BigDecimal` number representations. This is a common issue. Consider using the +[Num](https://github.com/invision-trading/num) library to abstract this problem away as you develop your algorithmic +trading application. + +# ⚠️ WebSocket Support is Coming Soon ⚠️ + +Version 11 is a pre-release that provides the REST API and SSE support for the latest Alpaca OpenAPI specifications. +WebSocket support for streaming market data and trade updates is coming soon. Previous version had WebSocket support via +OkHttp, but this library has switched to using Java's native `HttpClient` and WebSocket support is still a WIP. + +# Modules + +## Trading + +This module is for the Alpaca brokerage platform [Trading API](https://docs.alpaca.markets/us/docs/trading-api). + +### Installation + +For `build.gradle.kts`: + +```kotlin +dependencies { + implementation("net.jacobpeterson.alpaca-java:trading:11.0.0-pre.0") +} +``` + +For `build.gradle`: + +```groovy +dependencies { + implementation 'net.jacobpeterson.alpaca-java:trading:11.0.0-pre.0' +} +``` + +For `pom.xml`: + +```xml + + net.jacobpeterson.alpaca-java + trading + 11.0.0-pre.0 + +``` + +### Example + +```java +import net.jacobpeterson.alpacajava.common.ApiEnvironment; +import net.jacobpeterson.alpacajava.trading.TradingApi; +import net.jacobpeterson.alpacajava.trading.model.OrderSide; +import net.jacobpeterson.alpacajava.trading.model.OrderType; +import net.jacobpeterson.alpacajava.trading.model.PostOrderRequest; +import net.jacobpeterson.alpacajava.trading.model.TimeInForce; + +import java.math.BigDecimal; + +import static java.lang.Thread.sleep; +import static java.time.Duration.ofSeconds; +import static java.util.Objects.requireNonNull; + +public class Launcher { + + static void main() throws Exception { + final var keyID = ""; + final var secretKey = ""; + final var apiEnvironment = ApiEnvironment.DEVELOPMENT; // or `PRODUCTION` + final var tradingApi = new TradingApi(keyID, secretKey, apiEnvironment); + + // Print out account info + System.out.println(tradingApi.accounts().getAccount()); + + // Place a market order to buy one share of Apple + final var openingOrder = tradingApi.orders() + .postOrder(new PostOrderRequest() + .symbol("AAPL") + .qty("1") + .side(OrderSide.BUY) + .type(OrderType.MARKET) + .timeInForce(TimeInForce.GTC)); + System.out.println("Opening Apple order: " + openingOrder); + + // Wait for massive gains + sleep(ofSeconds(10)); + + // Close the Apple position + final var closingOrder = tradingApi.positions() + .deleteOpenPosition("AAPL", null, new BigDecimal("100")); + System.out.println("Closing Apple order: " + closingOrder); + + // Wait for closing trade to fill. For this example, we're sleeping, but in production, + // this should be done using polling or listening to the trade updates stream. + sleep(ofSeconds(10)); + + // Print out the PnL of our Apple trade + final var openFillPrice = tradingApi.orders() + .getOrderByOrderID(requireNonNull(openingOrder.getId()), false) + .getFilledAvgPrice(); + final var closeFillPrice = tradingApi.orders() + .getOrderByOrderID(requireNonNull(closingOrder.getId()), false) + .getFilledAvgPrice(); + System.out.println("PnL from Apple trade: " + new BigDecimal(requireNonNull(closeFillPrice)) + .subtract(new BigDecimal(requireNonNull(openFillPrice)))); + } +} +``` + +## Market Data + +This module is for the Alpaca brokerage platform +[Market Data API](https://docs.alpaca.markets/us/docs/about-market-data-api). + +### Installation + +For `build.gradle.kts`: + +```kotlin +dependencies { + implementation("net.jacobpeterson.alpaca-java:market-data:11.0.0-pre.0") +} +``` + +For `build.gradle`: + +```groovy +dependencies { + implementation 'net.jacobpeterson.alpaca-java:market-data:11.0.0-pre.0' +} +``` + +For `pom.xml`: + +```xml + + net.jacobpeterson.alpaca-java + market-data + 11.0.0-pre.0 + +``` + +### Example + +```java +import net.jacobpeterson.alpacajava.common.ApiEnvironment; +import net.jacobpeterson.alpacajava.marketdata.MarketDataApi; +import net.jacobpeterson.alpacajava.marketdata.model.CryptoLatestLoc; +import net.jacobpeterson.alpacajava.marketdata.model.CryptoOrderbookEntry; +import net.jacobpeterson.alpacajava.marketdata.model.StockLatestFeed; + +public class Launcher { + + static void main() throws Exception { + final var keyID = ""; + final var secretKey = ""; + final var apiEnvironment = ApiEnvironment.PRODUCTION; // or `DEVELOPMENT` for Broker Sandbox + final var marketDataApi = new MarketDataApi(keyID, secretKey, apiEnvironment); + + // Print out the latest Tesla trade + System.out.println(marketDataApi.stock() + .stockLatestTradeSingle("TSLA", StockLatestFeed.IEX, null)); + + // Print out the lowest Bitcoin ask price on the order book + System.out.println("Bitcoin lowest ask price: " + marketDataApi.crypto() + .cryptoLatestOrderbooks(CryptoLatestLoc.US, "BTC/USD").getOrderbooks().get("BTC/USD") + .getA().stream() + .map(CryptoOrderbookEntry::getP) + .min(Double::compare) + .orElseThrow()); + } +} +``` + +## Broker + +This module is for the Alpaca brokerage platform [Broker API](https://docs.alpaca.markets/us/docs/about-broker-api). + +### Installation + +For `build.gradle.kts`: + +```kotlin +dependencies { + implementation("net.jacobpeterson.alpaca-java:broker:11.0.0-pre.0") +} +``` + +For `build.gradle`: + +```groovy +dependencies { + implementation 'net.jacobpeterson.alpaca-java:broker:11.0.0-pre.0' +} +``` + +For `pom.xml`: + +```xml + + net.jacobpeterson.alpaca-java + broker + 11.0.0-pre.0 + +``` + +### Example + +```java +import net.jacobpeterson.alpacajava.broker.BrokerApi; +import net.jacobpeterson.alpacajava.broker.BrokerApiUtil; +import net.jacobpeterson.alpacajava.broker.model.CreateOrderRequest; +import net.jacobpeterson.alpacajava.broker.model.OrderSide; +import net.jacobpeterson.alpacajava.broker.model.OrderType; +import net.jacobpeterson.alpacajava.broker.model.TimeInForce; +import net.jacobpeterson.alpacajava.common.ApiEnvironment; +import net.jacobpeterson.alpacajava.common.ApiHeader; +import net.jacobpeterson.alpacajava.common.sse.SseListenerAdapter; + +import java.math.BigDecimal; +import java.util.UUID; + +import static java.lang.Thread.sleep; +import static java.time.Duration.ofSeconds; + +public class Launcher { + + static void main() throws Exception { + final var authorizationToken = BrokerApiUtil.getAuthorizationToken("", ""); + final var apiEnvironment = ApiEnvironment.DEVELOPMENT; // or `PRODUCTION` + final var brokerApi = new BrokerApi(ApiHeader.AUTHORIZATION_BASIC_PREFIX + authorizationToken, apiEnvironment); + + // Listen to the trade events via SSE and print them out + final var tradesStream = brokerApi.events() + .subscribeToTradeV2SSE(null, null, null, null, new SseListenerAdapter<>()); + + // Buy one share of GME for an account + System.out.println(brokerApi.trading() + .createOrderForAccount(UUID.fromString(""), new CreateOrderRequest() + .symbol("GME") + .qty(BigDecimal.ONE) + .side(OrderSide.BUY) + .timeInForce(TimeInForce.GTC) + .type(OrderType.MARKET))); + + // Wait a little bit + sleep(ofSeconds(10)); + + // Close the SSE stream + tradesStream.close(); + } +} +``` From 9c6ae12d08550965445c725f6800f191ce38bab0 Mon Sep 17 00:00:00 2001 From: Jacob Peterson <14127217+Petersoj@users.noreply.github.com> Date: Tue, 7 Jul 2026 16:22:50 -0600 Subject: [PATCH 265/271] Fix `img.shields.io` image links in README --- docs/README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/README.md b/docs/README.md index f463f21f..c441fdee 100644 --- a/docs/README.md +++ b/docs/README.md @@ -11,9 +11,9 @@ GitHub Discussions
- Maven Central + Maven Central - javadoc + javadoc
Java Version From 56fd5d2e108afc5235c15f78da3fdc4ab8deb2a3 Mon Sep 17 00:00:00 2001 From: Jacob Peterson <14127217+Petersoj@users.noreply.github.com> Date: Tue, 1 Sep 2026 11:52:58 -0600 Subject: [PATCH 266/271] Minor README improvements --- docs/README.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/README.md b/docs/README.md index c441fdee..61ec84e3 100644 --- a/docs/README.md +++ b/docs/README.md @@ -56,7 +56,7 @@ Some awesome things about Alpaca Java: - Exhaustive Javadoc documentation - Uses Java's native `HttpClient` (which also enables you to provide a different HTTP client implementation) -- Lightweight with minimal dependencies (only dependencies are: SLF4j, Guava, Jackson, and MessagePack) +- Lightweight with minimal dependencies (only dependencies are: SLF4j, Guava, Jackson, MessagePack) - Releases are built directly from this source repository using GitHub Actions CI - Kotlin friendly - MIT licensed @@ -83,8 +83,8 @@ trading application. # ⚠️ WebSocket Support is Coming Soon ⚠️ Version 11 is a pre-release that provides the REST API and SSE support for the latest Alpaca OpenAPI specifications. -WebSocket support for streaming market data and trade updates is coming soon. Previous version had WebSocket support via -OkHttp, but this library has switched to using Java's native `HttpClient` and WebSocket support is still a WIP. +WebSocket support for streaming market data and trade updates is coming soon. Previous versions had WebSocket support +via OkHttp, but this library has switched to using Java's native `HttpClient` and WebSocket support is still a WIP. # Modules @@ -166,7 +166,7 @@ public class Launcher { System.out.println("Closing Apple order: " + closingOrder); // Wait for closing trade to fill. For this example, we're sleeping, but in production, - // this should be done using polling or listening to the trade updates stream. + // this should be done using polling or listening to the trade updates stream sleep(ofSeconds(10)); // Print out the PnL of our Apple trade From 674e39642ec363a2d39b83843fb449f4b7dfac24 Mon Sep 17 00:00:00 2001 From: Jacob Peterson <14127217+Petersoj@users.noreply.github.com> Date: Sat, 26 Sep 2026 11:45:18 -0600 Subject: [PATCH 267/271] Update Gradle wrapper --- gradle/wrapper/gradle-wrapper.jar | Bin 48966 -> 48462 bytes gradle/wrapper/gradle-wrapper.properties | 4 ++- gradlew | 2 +- gradlew.bat | 31 ++++++++--------------- 4 files changed, 14 insertions(+), 23 deletions(-) diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index d997cfc60f4cff0e7451d19d49a82fa986695d07..b1b8ef56b44f16b14dc800fa8103a6d89abb526f 100644 GIT binary patch delta 39760 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 e(*aw}>J`&Ep$5$o2mVP)i30-M3~u@DBh0 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 delta 40115 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 e{Sg5G5zENPNVU~b5#<2@M#Os+0czL&{q{eZe&2 echo Please set the JAVA_HOME variable in your environment to match the 1>&2 echo location of your Java installation. 1>&2 -goto fail +"%COMSPEC%" /c exit 1 :findJavaFromJavaHome set JAVA_HOME=%JAVA_HOME:"=% @@ -65,7 +65,7 @@ echo. 1>&2 echo Please set the JAVA_HOME variable in your environment to match the 1>&2 echo location of your Java installation. 1>&2 -goto fail +"%COMSPEC%" /c exit 1 :execute @rem Setup the command line @@ -73,21 +73,10 @@ goto fail @rem Execute Gradle -"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %* +@rem endlocal doesn't take effect until after the line is parsed and variables are expanded +@rem which allows us to clear the local environment before executing the java command +endlocal & "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %* & call :exitWithErrorLevel -:end -@rem End local scope for the variables with windows NT shell -if %ERRORLEVEL% equ 0 goto mainEnd - -:fail -rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of -rem the _cmd.exe /c_ return code! -set EXIT_CODE=%ERRORLEVEL% -if %EXIT_CODE% equ 0 set EXIT_CODE=1 -if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% -exit /b %EXIT_CODE% - -:mainEnd -if "%OS%"=="Windows_NT" endlocal - -:omega +:exitWithErrorLevel +@rem Use "%COMSPEC%" /c exit to allow operators to work properly in scripts +"%COMSPEC%" /c exit %ERRORLEVEL% From 70df017c749116c28435540c941533a006a4e46d Mon Sep 17 00:00:00 2001 From: Jacob Peterson <14127217+Petersoj@users.noreply.github.com> Date: Sat, 26 Sep 2026 12:42:46 -0600 Subject: [PATCH 268/271] Update dependencies --- build.gradle.kts | 2 +- buildSrc/build.gradle.kts | 10 +++++----- buildSrc/src/main/kotlin/module-common.gradle.kts | 8 ++++---- .../main/kotlin/openapi-generator-adapted.gradle.kts | 7 +++---- 4 files changed, 13 insertions(+), 14 deletions(-) diff --git a/build.gradle.kts b/build.gradle.kts index 8f9b37a4..5faf0426 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -3,7 +3,7 @@ import org.jreleaser.model.Active.NEVER plugins { java - id("org.jreleaser") version "1.24.0" + id("org.jreleaser") version "1.26.0" } group = PROJECT_GROUP diff --git a/buildSrc/build.gradle.kts b/buildSrc/build.gradle.kts index 70810aad..b807c7c4 100644 --- a/buildSrc/build.gradle.kts +++ b/buildSrc/build.gradle.kts @@ -8,13 +8,13 @@ repositories { } dependencies { - implementation("com.google.guava:guava:33.6.0-jre") + implementation("com.google.guava:guava:33.7.1-jre") // Define versions for `module-common.gradle.kts` plugins: - implementation("io.freefair.lombok:io.freefair.lombok.gradle.plugin:9.5.0") - implementation("net.ltgt.errorprone:net.ltgt.errorprone.gradle.plugin:5.1.0") - implementation("com.github.ben-manes:gradle-versions-plugin:0.54.0") + implementation("io.freefair.lombok:io.freefair.lombok.gradle.plugin:9.7.0") + implementation("net.ltgt.errorprone:net.ltgt.errorprone.gradle.plugin:5.1.1") + implementation("io.github.ben-manes.versions:io.github.ben-manes.versions.gradle.plugin:0.61.0") // Define versions for `openapi-generator-adapted.gradle.kts` plugins: - implementation("org.openapi.generator:org.openapi.generator.gradle.plugin:7.23.0") + implementation("org.openapi.generator:org.openapi.generator.gradle.plugin:7.25.0") } diff --git a/buildSrc/src/main/kotlin/module-common.gradle.kts b/buildSrc/src/main/kotlin/module-common.gradle.kts index 7c88e93e..3dbb9a84 100644 --- a/buildSrc/src/main/kotlin/module-common.gradle.kts +++ b/buildSrc/src/main/kotlin/module-common.gradle.kts @@ -24,16 +24,16 @@ repositories { mavenCentral() } -val guavaVersion = "33.6.0-jre" +val guavaVersion = "33.7.1-jre" dependencies { api("org.jspecify:jspecify:1.0.0") api("com.google.guava:guava:$guavaVersion") - implementation("org.slf4j:slf4j-api:2.0.17") + implementation("org.slf4j:slf4j-api:2.0.20") - errorprone("com.google.errorprone:error_prone_core:2.49.0") - errorprone("com.uber.nullaway:nullaway:0.13.4") + errorprone("com.google.errorprone:error_prone_core:2.50.0") + errorprone("com.uber.nullaway:nullaway:0.14.2") errorprone("net.jacobpeterson:final-coat:1.2.3") } diff --git a/buildSrc/src/main/kotlin/openapi-generator-adapted.gradle.kts b/buildSrc/src/main/kotlin/openapi-generator-adapted.gradle.kts index 989b98dd..773e0a59 100644 --- a/buildSrc/src/main/kotlin/openapi-generator-adapted.gradle.kts +++ b/buildSrc/src/main/kotlin/openapi-generator-adapted.gradle.kts @@ -8,7 +8,7 @@ plugins { id("org.openapi.generator") } -val jacksonVersion = "3.2.0" +val jacksonVersion = "3.2.3" val jacksonAnnotationsVersion = "2.22" dependencies { @@ -165,7 +165,7 @@ tasks.openApiGenerate.configure { outputPackageDirectory.resolve("ApiResponse.java").apply { writeText(readText().replace("Map>", "java.net.http.HttpHeaders")) } - val apiClassNamesOfMethodNames = outputPackageDirectory.resolve(apiPackageName).list() + val apiClassNamesOfMethodNames = outputPackageDirectory.resolve(apiPackageName).list()!! .map { it.replace(".java", "") } .associateBy { UPPER_CAMEL.to(LOWER_CAMEL, it.substring(0, it.length - 3)) } val className = "${LOWER_HYPHEN.to(UPPER_CAMEL, projectName)}Api" @@ -322,8 +322,7 @@ sourceSets { tasks.withType(Javadoc::class).configureEach { options { (this as StandardJavadocDocletOptions).links( - // TODO uncomment once Jackson Core published Javadoc artifact is fixed - // "https://javadoc.io/doc/tools.jackson.core/jackson-core/$jacksonVersion", + "https://javadoc.io/doc/tools.jackson.core/jackson-core/$jacksonVersion", "https://javadoc.io/doc/tools.jackson.core/jackson-databind/$jacksonVersion", "https://javadoc.io/doc/com.fasterxml.jackson.core/jackson-annotations/$jacksonAnnotationsVersion") } From 524da99ee2e6e7b015af31dcce12c95e7a886807 Mon Sep 17 00:00:00 2001 From: Jacob Peterson <14127217+Petersoj@users.noreply.github.com> Date: Sat, 26 Sep 2026 12:47:35 -0600 Subject: [PATCH 269/271] Fix cyclical dependency in Javadoc task project dependency configuration in `module-common` --- .../src/main/kotlin/module-common.gradle.kts | 22 +++++++++++-------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/buildSrc/src/main/kotlin/module-common.gradle.kts b/buildSrc/src/main/kotlin/module-common.gradle.kts index 3dbb9a84..5a76e412 100644 --- a/buildSrc/src/main/kotlin/module-common.gradle.kts +++ b/buildSrc/src/main/kotlin/module-common.gradle.kts @@ -74,16 +74,20 @@ tasks.withType(Javadoc::class).configureEach { "https://guava.dev/releases/$guavaVersion/api/docs/", "https://errorprone.info/api/latest/") } - project.configurations.flatMap { it.dependencies.withType(ProjectDependency::class) }.forEach { - project(it.path).tasks.withType(Javadoc::class).forEach { dependencyJavadocTask -> - this@configureEach.dependsOn(dependencyJavadocTask) - this@configureEach.options { - (this as StandardJavadocDocletOptions).linksOffline( - "https://javadoc.io/doc/${it.group}/${it.name}/${it.version}/", - dependencyJavadocTask.destinationDir!!.path) + project.configurations + .flatMap { it.dependencies.withType(ProjectDependency::class) } + .map { project(it.path) } + .filter { it != this@configureEach.project } + .forEach { + it.tasks.withType(Javadoc::class).forEach { dependencyJavadocTask -> + this@configureEach.dependsOn(dependencyJavadocTask) + this@configureEach.options { + (this as StandardJavadocDocletOptions).linksOffline( + "https://javadoc.io/doc/${it.group}/${it.name}/${it.version}/", + dependencyJavadocTask.destinationDir!!.path) + } + } } - } - } } publishing { From 6d1e7e01b1790cef5cf85a7a236803c25297507f Mon Sep 17 00:00:00 2001 From: Jacob Peterson <14127217+Petersoj@users.noreply.github.com> Date: Sat, 26 Sep 2026 12:50:37 -0600 Subject: [PATCH 270/271] Update OpenAPI specification JSON files --- module/broker/openapi.json | 24461 +++++++++++++++++------------- module/market-data/openapi.json | 4700 ++++-- module/trading/openapi.json | 7869 +++++----- 3 files changed, 21597 insertions(+), 15433 deletions(-) diff --git a/module/broker/openapi.json b/module/broker/openapi.json index 3db61c8e..3e61e7cd 100644 --- a/module/broker/openapi.json +++ b/module/broker/openapi.json @@ -424,15 +424,6 @@ "updated_at": "2023-12-12T21:35:49.102504673Z" } }, - "SystemEventV2": { - "value": { - "at": "2026-01-05T10:28:23.163857Z", - "description": "End-of-day balances are now available.", - "event_id": "01F535Y1FVY8WZHE763HCNS8SZ", - "system_date": "2026-01-05", - "type": "eod_balances_ready" - } - }, "TradeUpdateEventV2MultilegOptionsFill": { "value": { "account_id": "529248ad-c4cc-4a50-bea4-6bfd2953f83a", @@ -627,6 +618,45 @@ "previous_execution_id": "aeb60660-412f-4537-8d1f-1101b3fc8f64", "timestamp": "2022-04-19T14:12:30.602193534Z" } + }, + "TravelRuleValidationError": { + "description": "The request requires the beneficiary's name before the transfer can proceed.", + "summary": "Missing beneficiary name", + "value": { + "error_code": "BENEFICIARY_NAME_REQUIRED", + "message": "Please provide the beneficiary's name. Select one of the following options.", + "next_actions": [ + { + "description": "Provide the recipient's first and last name.", + "name": "supply_beneficiaryName_naturalPerson", + "required_fields": [ + { + "description": "The recipient's first name.", + "field": "beneficiary_given_name", + "value_type": "string" + }, + { + "description": "The recipient's last name.", + "field": "beneficiary_family_name", + "value_type": "string" + } + ], + "type": "ONE_OF" + }, + { + "description": "Provide the entity's legal name.", + "name": "supply_beneficiaryName_legalEntity", + "required_fields": [ + { + "description": "The legal name of the receiving entity.", + "field": "beneficiary_entity_name", + "value_type": "string" + } + ], + "type": "ONE_OF" + } + ] + } } }, "headers": { @@ -715,10 +745,12 @@ "OPEXP", "OPTRD", "PTC", + "REO", "REORG", "SPIN", "SPLIT", - "FOPT" + "FOPT", + "OCT" ], "type": "string" } @@ -773,12 +805,23 @@ "type": "string" } }, + "LegacyIdempotencyKey": { + "description": "Optional client-generated key for safe retries and duplicate request detection.\nThis endpoint currently accepts keys up to 128 characters. Alpaca is moving toward\na 36-character maximum; new implementations should generate a unique UUIDv7 or\nUUIDv4 value (36 characters including hyphens) for each logical operation. Do not\nreuse a key across operations.\n", + "in": "header", + "name": "Idempotency-Key", + "required": false, + "schema": { + "maxLength": 128, + "type": "string" + } + }, "OrderID": { "description": "Order identifier.", "in": "path", "name": "order_id", "required": true, "schema": { + "format": "uuid", "type": "string" } }, @@ -853,8 +896,7 @@ "name": "end", "schema": { "format": "date-time", - "type": "string", - "x-go-type": "legacyTime" + "type": "string" } }, "legacy_start": { @@ -863,8 +905,7 @@ "name": "start", "schema": { "format": "date-time", - "type": "string", - "x-go-type": "legacyTime" + "type": "string" } }, "market": { @@ -977,6 +1018,16 @@ }, "description": "Request is forbidden" }, + "IdempotencyKeyConflict": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + }, + "description": "The idempotency key was already used with a different request payload." + }, "NotAuthorized": { "content": { "application/json": { @@ -1156,6 +1207,43 @@ "description": "Automated customer account transfer service (cash)", "type": "object" }, + "AcatsAccountType": { + "description": "Account type, as represented by DTCC", + "enum": [ + "AGENCY", + "BANK_CUSTODY", + "BENEFICIARY", + "BENEFICIARY_ROTH_IRA", + "CORPORATE", + "CO_TRUSTEE", + "COVERDELL_IRA", + "CUSTODIAN_UGMA", + "DIRECT_ROLLOVER", + "ESTATE", + "HSA", + "IRA", + "JOINT", + "MEDICAL_SAVINGS_ACCOUNT", + "OTHER", + "QUALIFIED_OR_PROFIT_SHARING_OR_PENSION", + "ROTH_401K", + "ROTH_IRA", + "ROTH_SEP", + "ROTH_SIMPLE", + "ROTH_SOLO_401K", + "SARSEP", + "SEP_IRA", + "SIMPLE_IRA", + "SINGLE", + "SOLO_401K", + "TRUST", + "TYPE_401K", + "TYPE_403B", + "TYPE_457_PLAN", + "TYPE_529_PLAN" + ], + "type": "string" + }, "AcatsActivityV2": { "allOf": [ { @@ -1180,429 +1268,877 @@ "description": "Automated customer account transfer service (stock)", "type": "object" }, - "Account": { - "description": "Represents high level account info. Used when returning entire account information would not be useful like the getAllAccounts operation", + "AcatsAsset": { + "description": "An asset included in an ACATS transfer.", + "example": { + "amount": "24223.62", + "asset_class": "EQUITY", + "asset_description": "APPLE INC", + "currency_code": "USD", + "cusip": "037833100", + "position": "LONG", + "qty": "123", + "status": "SETTLED", + "symbol": "AAPL" + }, "properties": { - "account_number": { - "description": "A human-readable account number that can be shown to the end user", - "nullable": true, + "amount": { + "description": "Positive number indicating the market value of the assets being transferred", + "format": "decimal", "type": "string" }, - "account_type": { - "$ref": "#/components/schemas/AccountType" - }, - "agreements": { - "items": { - "$ref": "#/components/schemas/Agreement" - }, - "type": "array" - }, - "cash_interest": { - "$ref": "#/components/schemas/AccountCashInterestResponse" - }, - "contact": { - "$ref": "#/components/schemas/Contact" - }, - "created_at": { - "description": "Timestamp (RFC3339) of account creation.", - "format": "date-time", + "asset_class": { + "description": "Asset type", + "enum": [ + "EQUITY", + "CASH", + "OPTION", + "OTHER" + ], "type": "string" }, - "crypto_status": { - "$ref": "#/components/schemas/AccountStatus" - }, - "currency": { - "$ref": "#/components/schemas/Currency" - }, - "disclosures": { - "$ref": "#/components/schemas/Disclosures" - }, - "documents": { - "description": "The documents associated with the primary owner of the account", - "items": { - "$ref": "#/components/schemas/OwnerDocument" - }, - "type": "array" - }, - "enabled_assets": { - "description": "Assets the user has enabled and is able to trade once status and/or crypto_status are ACTIVE", - "items": { - "$ref": "#/components/schemas/AssetClass" - }, - "type": "array" + "asset_description": { + "description": "Description, usually from DTCC's Security Master File", + "type": "string" }, - "fpsl": { - "$ref": "#/components/schemas/AccountFPSLResponse" + "currency_code": { + "description": "ISO currency code in which amount is denominated", + "type": "string" }, - "id": { - "description": "UUID that identifies the account for later reference", - "format": "uuid", + "cusip": { + "description": "CUSIP security identifier for the asset. Only returned when a CUSIP was provided for this asset at the time the transfer was initiated, in which case it is returned alongside `symbol`.", "type": "string" }, - "identity": { - "$ref": "#/components/schemas/Identity" + "isin": { + "deprecated": true, + "description": "ISIN for the asset. DEPRECATED -- will be removed in a future version; use `symbol` or `cusip` instead. Temporarily returned with all assets for backward compatibility.", + "type": "string", + "x-deprecation": { + "reason": "Use `symbol` or `cusip` instead. Returned with all assets only for backward compatibility while consumers migrate.", + "since": "2026-08-04", + "sunset": "2027-02-04" + } }, - "last_equity": { - "description": "EOD equity calculation (cash + long market value + short market value)", + "position": { + "$ref": "#/components/schemas/AcatsPosition" + }, + "qty": { + "description": "Positive number indicating how many shares or units are being transferred", "format": "decimal", "type": "string" }, - "primary_account_holder_id": { - "description": "UUID that identifies the primary account holder (party) for accounts opened under the Multi-Live Accounts (MLA) flow.\nOnly populated when the account was created by referencing an existing account holder via `primary_account_holder_id`\non `POST /v1/accounts`. Otherwise `null`. Currently only supported for `account_type` of `trading` and `ira`.\n", - "format": "uuid", - "nullable": true, + "rejection_message": { + "description": "Human readable reason for why an asset is considered ineligible for transfer", "type": "string" }, "status": { - "$ref": "#/components/schemas/AccountStatus" + "description": "Status", + "enum": [ + "NOT_VALIDATED", + "REQUESTED", + "DELETED", + "SETTLED", + "REVERSED", + "INTERFACE_REJECTED" + ], + "type": "string" }, - "trusted_contact": { - "$ref": "#/components/schemas/TrustedContact" + "symbol": { + "description": "Symbol identifier for the asset. Always returned when a symbol is known for the asset, including when the transfer was initiated with a CUSIP.", + "type": "string" } }, "required": [ - "id", - "account_number", + "asset_class", + "asset_description", "status", - "currency", - "created_at", - "last_equity" + "position" ], - "type": "object", - "x-examples": { - "example-1": { - "account_number": "9034005019", - "created_at": "2019-09-30T23:55:31.185998Z", - "currency": "USD", - "id": "0d18ae51-3c94-4511-b209-101e1666416b", - "last_equity": "1500.65", - "primary_account_holder_id": null, - "status": "APPROVED" + "type": "object" + }, + "AcatsCashBalance": { + "description": "Cash balance", + "example": { + "amount": "10000", + "currency": "USD" + }, + "properties": { + "amount": { + "description": "Amount of cash to be transferred. Can be positive or negative. Use `transfer_all` to transfer the entire balance instead of specifying an amount -- `amount` and `transfer_all: true` are mutually exclusive, and sending both is rejected with HTTP 400.", + "format": "decimal", + "type": "string" + }, + "currency": { + "description": "Currency of the cash balance. Only `USD` is currently supported; any other value is rejected with HTTP 400. Each currency may appear at most once in the `cash` array.", + "enum": [ + "USD" + ], + "type": "string" + }, + "transfer_all": { + "description": "Set to true to transfer the entire balance of this currency. Must not be combined with `amount`. Setting it to `false` without an `amount` is rejected with HTTP 400.", + "type": "boolean" } - } + }, + "required": [ + "currency" + ], + "type": "object" }, - "AccountCashInterestEvent": { - "description": "This property is included when the account's cash interest program had changed due to an enrollment, APR tier change, or unenrollment.\nThe from and to status or APR tier name will be included in the event, depending on the change.\n", + "AcatsContraAccountDetails": { + "description": "Contra account details", + "example": { + "account_number": "12345678A", + "broker_number": "1234", + "customer_name": "John Smith", + "customer_tax_id": "911923333" + }, "properties": { - "apr_tier_name_from": { - "description": "The APR tier name before the change", + "account_number": { + "description": "Account number at the contra broker", "type": "string" }, - "apr_tier_name_to": { - "description": "The APR tier name after the change", + "broker_name": { + "description": "Name of the contra broker (non-functional, used for auditability and display purposes)", + "maxLength": 100, + "minLength": 1, "type": "string" }, - "currency": { - "description": "The currency of the cash interest program that changed.", - "example": "USD", + "broker_number": { + "description": "Clearing number of the contra broker", "type": "string" }, - "status_from": { - "description": "The cash_interest program status of the account before the change", + "customer_name": { + "description": "Name on the account at the contra broker", "type": "string" }, - "status_to": { - "description": "The cash_interest program status of the account after the change", + "customer_tax_id": { + "description": "Tax ID on the account at the contra broker", "type": "string" } }, + "required": [ + "account_number", + "customer_name", + "customer_tax_id", + "broker_number" + ], "type": "object" }, - "AccountCashInterestPatch": { - "description": "Use this property to change the account's configuration for the USD cash interest program.\nTo enroll the account, specify the apr_tier_name. The status should not be specified on enrollment.\nTo change the APR tier, specify the new apr_tier_name. The status should not be specified on tier changes.\nThe unenroll, set the status to INACTIVE.\nAfter any change, the response will contain a status of PENDING_CHANGE.\nAn event showing the status change to ACTIVE (for enrollment or tier changes) or INACTIVE (for unenrollment) will be generated when the change is complete.\n", + "AcatsContraBroker": { + "description": "A contra broker or bank participating in ACATS, identified by its DTCC account number.", "properties": { - "USD": { - "$ref": "#/components/schemas/AccountCashInterestProgram" + "name": { + "description": "Participant name", + "type": "string" + }, + "number": { + "description": "DTCC account number", + "type": "string" + }, + "type": { + "$ref": "#/components/schemas/AcatsContraBrokerType" } }, + "required": [ + "number", + "type", + "name" + ], "type": "object" }, - "AccountCashInterestPost": { - "description": "The configuration of the account's USD cash interest program when creating an account.\nIf cash_interest is not provided and there is a default APR tier defined, that tier will be used.\nTo enroll the account in a non-default APR tier, provide the cash_interest object with the desired apr_tier_name. The status should not be specified on enrollment.\nThe response will contain a status of PENDING_CHANGE. An event showing the status change to ACTIVE will be generated when the enrollment is complete.\n", - "properties": { - "USD": { - "$ref": "#/components/schemas/AccountCashInterestProgram" - } - }, - "type": "object" + "AcatsContraBrokerType": { + "description": "ACATS participant type", + "enum": [ + "BANK", + "BROKER" + ], + "type": "string" }, - "AccountCashInterestProgram": { + "AcatsDtccStatus": { + "description": "Transfer status as tracked by DTCC", + "enum": [ + "REQUEST", + "REQUEST_ADJUST", + "REQUEST_ADJUST_PAST", + "REQUEST_PAST", + "REQUEST_REJECT", + "REVIEW", + "REVIEW_ADJUST_DELIVERER", + "REVIEW_ERROR", + "REVIEW_ACCELERATE", + "REVIEW_ADJUST_RECEIVER_ACCELERATE", + "SETTLE_PREP", + "SETTLE_CLOSE", + "CLOSE_PURGE", + "REQUEST_PTR", + "MEMO_PURGE_PARTIAL_TRANSFER_REQUEST_RECEIVER", + "REJECT", + "SYSTEM_REJECTED" + ], + "type": "string" + }, + "AcatsDtccTransferType": { + "description": "ACATS transfer type", + "enum": [ + "FUL", + "FRV", + "MFC", + "PTD", + "PTR", + "RCL", + "RCR", + "PTF" + ], + "type": "string" + }, + "AcatsEquity": { + "description": "Equity asset requested in a partial transfer. Identify the security with either `cusip` or `symbol` -- provide one, not both. Prefer `cusip` when you have it: it is a stable, unambiguous identifier. Use `symbol` only when a CUSIP is not available, as ticker symbols can change or be reused over time.", + "example": { + "position": "LONG", + "qty": "2", + "symbol": "AAPL" + }, + "oneOf": [ + { + "required": [ + "cusip", + "position" + ] + }, + { + "required": [ + "symbol", + "position" + ] + } + ], "properties": { - "apr_tier_name": { - "description": "The unique name of the APR tier for a specific program", - "example": "gold", + "cusip": { + "description": "CUSIP security identifier for the asset -- 8 uppercase alphanumeric characters followed by a check character, which may be a digit or one of `*`, `@`, `#`. Recommended over `symbol` for unambiguous identification. Provide either `cusip` or `symbol`, not both.", + "pattern": "^[0-9A-Z]{8}[0-9*@#]$", "type": "string" }, - "status": { - "description": "The status of the account within a cash interest program. One of:\n- **ACTIVE**\nThe account is enrolled and eligible for idle cash to be swept at the end of day (EOD).\n- **INACTIVE**\nThe account is not enrolled due to it either not being eligible (e.g. the updated Alpaca Customer Agreement has not been signed), an APR tier needs to be assigned, or they have been unenrolled.\n- **PENDING_CHANGE**\nAn enrollment, APR Tier change, or unenrollment is in progress\n", - "example": "ACTIVE", + "position": { + "description": "Position type. Only `LONG` and `SHORT` are accepted for equities -- `CREDIT` and `DEBIT` apply to cash positions and are rejected with HTTP 400 here.", + "enum": [ + "LONG", + "SHORT" + ], + "type": "string" + }, + "qty": { + "description": "Quantity of assets to transfer. Use `transfer_all` to transfer the entire position instead of specifying a quantity -- `qty` and `transfer_all: true` are mutually exclusive, and sending both is rejected with HTTP 400. Note that the ACATS system will only transfer whole shares.", + "format": "decimal", "type": "string" + }, + "symbol": { + "description": "Stock ticker symbol (e.g. `AAPL`, `BRK.B`, `GOOGL`). Up to 6 alphanumeric characters, optionally followed by a single `.` or `-` separator and a 1-5 letter suffix. Values are trimmed and upper-cased before validation. Use only when a CUSIP is not available, since symbols can change or be reused over time. Provide either `cusip` or `symbol`, not both.", + "pattern": "^[A-Z0-9]{1,6}([-.][A-Z]{1,5})?$", + "type": "string" + }, + "transfer_all": { + "description": "Set to true to transfer the entire balance of this asset. Must not be combined with `qty`. Setting it to `false` without a `qty` is rejected with HTTP 400.", + "type": "boolean" } }, "type": "object" }, - "AccountCashInterestResponse": { - "description": "The configuration and status of the account's USD cash interest program\n", + "AcatsError": { + "description": "Body for responses with HTTP status codes indicating an error", + "example": { + "message": "contra broker number is required" + }, "properties": { - "USD": { - "$ref": "#/components/schemas/AccountCashInterestProgram" + "message": { + "type": "string" } }, + "required": [ + "message" + ], "type": "object" }, - "AccountConfigurations": { - "description": "Represents additional configuration settings for an account", - "properties": { - "disable_overnight_trading": { - "description": "If true, overnight trading is disabled.", - "type": "boolean" - }, - "fractional_trading": { - "description": "If true, account is able to participate in fractional trading", - "type": "boolean" - }, - "max_margin_multiplier": { - "description": "Can be \"1\" or \"2\"", - "type": "string" - }, - "max_options_trading_level": { - "description": "The desired maximum options trading level. 0=disabled, 1=Covered Call/Cash-Secured Put, 2=Long Call/Put, 3=Spreads/Straddles.", - "enum": [ - 0, - 1, - 2, - 3 - ], - "type": "integer" - }, - "no_shorting": { - "description": "If true, account becomes long-only mode.", - "type": "boolean" - }, - "ptp_no_exception_entry": { - "description": "If set to true then Alpaca will accept orders for PTP symbols with no exception. Default is false.", - "type": "string" - }, - "suspend_trade": { - "description": "If true, new orders are blocked.", - "type": "boolean" + "AcatsInitiateTransferDetails": { + "description": "Transfer details. The two transfer types are mutually exclusive in what they accept:\n- `FULL`: do not include the `cash` or `securities` objects (the entire account will be transferred). Including either is rejected with HTTP 400.\n- `PARTIAL`: specify which assets to transfer using the `cash` and/or `securities` objects. At least one of the two must be present and non-empty, otherwise the request is rejected with HTTP 400.\n", + "example": { + "cash": [ + { + "amount": "10000", + "currency": "USD" + } + ], + "securities": { + "equities": [ + { + "cusip": "037833100", + "position": "LONG", + "qty": "2" + }, + { + "cusip": "78462F103", + "position": "SHORT" + } + ] }, - "trade_confirm_email": { - "description": "all or none. If none, emails for order fills are not sent.", - "enum": [ - "all", - "none" - ], - "type": "string" - } + "type": "PARTIAL" }, - "title": "AccountConfigurations", - "type": "object" - }, - "AccountCreationRequest": { - "description": "Represents the fields required to create a new account", - "properties": { - "account_sub_type": { - "$ref": "#/components/schemas/AccountSubType" - }, - "account_type": { - "$ref": "#/components/schemas/AccountType" - }, - "agreements": { - "description": "The client must present the Alpaca Account and Margin Agreements to the end user, and confirm they have read and agreed to the agreement.", - "items": { - "$ref": "#/components/schemas/Agreement" + "oneOf": [ + { + "description": "Transfers the entire account. Must not carry `cash` or `securities`.", + "not": { + "anyOf": [ + { + "required": [ + "cash" + ] + }, + { + "required": [ + "securities" + ] + } + ] }, - "type": "array" - }, - "beneficiaries": { - "description": "IRA Account only. A user can submit max 6 beneficiaries.", - "items": { - "$ref": "#/components/schemas/Beneficiary" + "properties": { + "type": { + "enum": [ + "FULL" + ], + "type": "string" + } }, - "type": "array" - }, - "cash_interest": { - "$ref": "#/components/schemas/AccountCashInterestPost" - }, - "contact": { - "$ref": "#/components/schemas/Contact" - }, - "disclosures": { - "$ref": "#/components/schemas/Disclosures" + "title": "Full transfer" }, - "documents": { - "items": { - "$ref": "#/components/schemas/OwnerDocumentUploadRequest" + { + "anyOf": [ + { + "properties": { + "cash": { + "minItems": 1, + "type": "array" + } + }, + "required": [ + "cash" + ] + }, + { + "properties": { + "securities": { + "properties": { + "equities": { + "minItems": 1, + "type": "array" + } + }, + "required": [ + "equities" + ], + "type": "object" + } + }, + "required": [ + "securities" + ] + } + ], + "description": "Transfers selected assets. Must carry a non-empty `cash` array, a non-empty `securities.equities` array, or both.", + "properties": { + "type": { + "enum": [ + "PARTIAL" + ], + "type": "string" + } }, - "type": "array" - }, - "enabled_assets": { - "description": "Will default to `us_equity`. Alpaca has the ability to update the default value upon request.", + "title": "Partial transfer" + } + ], + "properties": { + "cash": { + "description": "Cash amounts to transfer. Only used for PARTIAL transfers. Must not be included for FULL transfers.", "items": { - "$ref": "#/components/schemas/AssetClass" + "$ref": "#/components/schemas/AcatsCashBalance" }, "type": "array" }, - "fpsl": { - "$ref": "#/components/schemas/AccountFPSLPost" - }, - "identity": { - "$ref": "#/components/schemas/Identity" - }, - "investment_objective": { - "description": "The user's investment objective. This field should be used instead of the deprecated `investment_objective` under identity.\n", - "enum": [ - "generate_income", - "preserve_wealth", - "market_speculation", - "growth", - "balance_preserve_wealth_with_growth" - ], - "type": "string" - }, - "investment_time_horizon": { - "description": "The expected period of time the user plan to invest to achieve his/her financial goal(s). This field should be used instead of the deprecated `investment_time_horizon` under identity.\n", - "enum": [ - "less_than_1_year", - "1_to_2_years", - "3_to_5_years", - "6_to_10_years", - "more_than_10_years" - ], - "type": "string" - }, - "liquidity_needs": { - "description": "The user's ability to quickly and easily convert to cash all or a portion of the investments in this account without experiencing significant loss in value. This field should be used instead of the deprecated `liquidity_needs` under identity.\n", - "enum": [ - "very_important", - "important", - "somewhat_important", - "does_not_matter" - ], - "type": "string" - }, - "primary_account_holder_id": { - "description": "UUID of an existing account holder (party) to use as the primary account holder for the new account.\nUsed to open additional accounts under the Multi-Live Accounts (MLA) flow.\n\nWhen present:\n- `contact` and `identity` must be omitted (returns HTTP 400 otherwise).\n- `agreements` must still be supplied (e.g. `customer_agreement` for trading; `customer_agreement` and `etc_agreement` for IRA).\n- Only `account_type` values of `trading` and `ira` are supported.\n", - "format": "uuid", - "type": "string" - }, - "risk_tolerance": { - "description": "The user's investment risk tolerance. This field should be used instead of the deprecated `risk_tolerance` under identity.\n", - "enum": [ - "conservative", - "moderate", - "significant_risk" - ], - "type": "string" - }, - "trading_configurations": { - "$ref": "#/components/schemas/AccountConfigurations" + "securities": { + "allOf": [ + { + "description": "Securities to transfer. Only used for PARTIAL transfers. Must not be included for FULL transfers." + }, + { + "$ref": "#/components/schemas/AcatsSecurities" + } + ] }, - "trusted_contact": { - "$ref": "#/components/schemas/TrustedContact" + "type": { + "$ref": "#/components/schemas/AcatsInitiateTransferType" } }, "required": [ - "contact", - "identity", - "disclosures", - "agreements" + "type" ], - "title": "AccountCreationRequest", - "type": "object", - "x-examples": { - "multi-live-accounts": { - "summary": "Opening an additional account for an existing account holder (MLA)", - "value": { - "account_sub_type": "traditional", - "account_type": "ira", - "agreements": [ + "type": "object" + }, + "AcatsInitiateTransferRequest": { + "description": "Request body for initiating an incoming ACATS transfer.", + "example": { + "contra_account_details": { + "account_number": "A00000123", + "broker_number": "0158", + "customer_name": "Jane Doe", + "customer_tax_id": "123456789" + }, + "ip_address": "203.0.113.42", + "signed_at": "2026-04-29T13:00:00Z", + "transfer_details": { + "cash": [ + { + "amount": "10000", + "currency": "USD" + } + ], + "securities": { + "equities": [ + { + "position": "LONG", + "qty": "2", + "symbol": "AAPL" + }, { - "agreement": "customer_agreement", - "ip_address": "185.13.21.99", - "signed_at": "2020-09-11T18:13:44Z" + "cusip": "123456789", + "position": "LONG", + "qty": "5" }, { - "agreement": "etc_agreement", - "ip_address": "185.13.21.99", - "signed_at": "2020-09-11T18:13:44Z" + "position": "SHORT", + "symbol": "TSLA" } - ], - "enabled_assets": [ - "us_equity" - ], - "primary_account_holder_id": "8b5b7e4c-9f2d-4f4a-9a7b-1b2c3d4e5f60", - "trusted_contact": { - "email_address": "jane.doe@example.com", - "family_name": "Doe", - "given_name": "Jane" - } - } + ] + }, + "type": "PARTIAL" } - } - }, - "AccountDocument": { - "description": "A document associated with an account", - "example": { - "date": "2019-09-30", - "id": "0d18ae51-3c94-4511-b209-101e1666416b", - "sub_type": "1099-Comp", - "type": "account_statement" }, "properties": { - "date": { - "format": "date", - "type": "string" + "contra_account_details": { + "$ref": "#/components/schemas/AcatsContraAccountDetails" }, - "id": { - "format": "uuid", + "ip_address": { + "description": "IP address from which the account holder initiated the transfer (IPv4 or IPv6)", + "example": "203.0.113.42", "type": "string" }, - "name": { + "signed_at": { + "description": "Timestamp at which the account holder signed/authorized the transfer. Must be within the last week and must not be in the future (a 5 minute grace period absorbs clock skew); values outside that window are rejected with HTTP 400.", + "example": "2026-04-29T13:00:00Z", + "format": "date-time", "type": "string" }, - "sub_type": { - "description": "The sub-type of the document. This is a free-form property.", - "type": "string" + "transfer_details": { + "$ref": "#/components/schemas/AcatsInitiateTransferDetails" }, - "type": { - "$ref": "#/components/schemas/AccountDocumentType" + "validate_account_number": { + "default": false, + "description": "Whether to validate the contra account number against the contra broker's configured format rules. Defaults to false (no per-broker format check). Set to true to opt in, in which case a number that matches none of the broker's enabled rules is rejected with HTTP 422.", + "type": "boolean" } }, "required": [ - "id", - "type", - "date" + "contra_account_details", + "transfer_details", + "ip_address", + "signed_at" ], "type": "object" }, - "AccountDocumentType": { - "description": "The type of the account document\n", + "AcatsInitiateTransferResponse": { + "description": "Response returned when an ACATS transfer has been accepted for processing.", + "properties": { + "acats_id": { + "description": "The ID of the ACATS transfer that was just initiated", + "example": "2c827687-ddd3-4ad9-bee8-0a3cdad35301", + "type": "string" + } + }, + "required": [ + "acats_id" + ], + "type": "object" + }, + "AcatsInitiateTransferType": { + "description": "Transfer type", "enum": [ - "account_application", - "account_statement", - "crypto_account_statement", - "options_application", - "tax_statement", - "trade_confirmation_json", - "trade_confirmation" + "FULL", + "PARTIAL" ], - "example": "account_application", "type": "string" }, - "AccountExtended": { - "description": "Represents an account with all data available. If your api response is missing some of these fields, there is a good chance you are using a route that returns `Account` instances instead of these.", - "properties": { - "account_name": { - "type": "string" + "AcatsListAssetsResponse": { + "description": "The list of assets associated with an ACATS transfer.", + "example": [ + { + "asset_class": "EQUITY", + "asset_description": "APPLE INC", + "cusip": "037833100", + "position": "LONG", + "qty": "10", + "status": "SETTLED", + "symbol": "AAPL" + }, + { + "amount": "1250.75", + "asset_class": "CASH", + "asset_description": "US DOLLAR", + "currency_code": "USD", + "position": "CREDIT", + "status": "SETTLED" + } + ], + "items": { + "$ref": "#/components/schemas/AcatsAsset" + }, + "type": "array" + }, + "AcatsListContraBrokersResponse": { + "description": "Response containing the list of valid ACATS contra brokers.", + "example": { + "brokers": [ + { + "name": "Example Brokerage Inc.", + "number": "0001", + "type": "BROKER" + }, + { + "name": "Example Bank, N.A.", + "number": "0002", + "type": "BANK" + } + ] + }, + "properties": { + "brokers": { + "items": { + "$ref": "#/components/schemas/AcatsContraBroker" + }, + "type": "array" + } + }, + "type": "object" + }, + "AcatsListTransfersResponse": { + "description": "A page of ACATS transfers, with optional pagination tokens.", + "example": { + "page_token_next": "eyJjcmVhdGVkX2F0IjoiMjAyNi0wNC0yOFQxNDozMDowMFoiLCJpZCI6IjAxQUJDIn0=", + "pending_transfers": 37, + "transfers": [ + { + "acats_id": "2c827687-ddd3-4ad9-bee8-0a3cdad35301", + "account_id": "9ac16d7d-1f6e-445b-8e51-807e77ba2902", + "account_name": "John Doe", + "account_number": "12345678", + "account_type": "SINGLE", + "contra_account_number": "A00000123", + "contra_broker_name": "Alpaca Clearing", + "contra_broker_number": "00001234", + "contra_broker_type": "BANK", + "created_at": "2026-04-21T12:41:03.505862Z", + "direction": "INCOMING", + "ip_address": "203.0.113.42", + "rejection_details": { + "rejection_message": "SS# Tax ID Mismatch", + "rejection_reason": "SS_TAX_ID_MISMATCH" + }, + "status": { + "dtcc_status": "REJECT", + "transfer_status": "REJECTED" + }, + "total_transfer_value": "24223.62", + "transfer_identifier": "03251237654321", + "transfer_type": "FUL" + } + ] + }, + "properties": { + "page_token_next": { + "allOf": [ + { + "description": "Page token for the next page of transfers. Pass it back in the `page_token` query parameter to fetch that page. Not returned when the caller is on the last page." + }, + { + "$ref": "#/components/schemas/AcatsPageToken" + } + ] + }, + "page_token_previous": { + "allOf": [ + { + "description": "Page token for the previous page of transfers. Pass it back in the `page_token` query parameter to fetch that page. Not returned when the caller is on the first page." + }, + { + "$ref": "#/components/schemas/AcatsPageToken" + } + ] + }, + "pending_transfers": { + "description": "The count of ACATS transfers pending review", + "example": 37, + "format": "int32", + "type": "integer" + }, + "transfers": { + "items": { + "$ref": "#/components/schemas/AcatsTransfer" + }, + "type": "array" + } + }, + "required": [ + "transfers" + ], + "type": "object" + }, + "AcatsPageToken": { + "description": "Page token, pass it back unchanged in the `page_token` query parameter to fetch the next or previous page.", + "example": "eyJjcmVhdGVkX2F0IjoiMjAyNi0wNC0yOFQxNDozMDowMFoiLCJpZCI6IjAxQUJDIn0=", + "type": "string" + }, + "AcatsPosition": { + "description": "Position type. `LONG` and `SHORT` apply to security positions; `CREDIT` and `DEBIT` apply to cash positions.", + "enum": [ + "LONG", + "SHORT", + "CREDIT", + "DEBIT" + ], + "type": "string" + }, + "AcatsRejectionDetails": { + "description": "Why the transfer was rejected, only present on transfers in rejected status", + "properties": { + "rejection_message": { + "description": "Human readable message regarding the reason for the rejection", + "type": "string" + }, + "rejection_reason": { + "description": "Rejection reason, as returned by DTCC", + "enum": [ + "SS_TAX_ID_MISMATCH", + "ACCOUNT_TITLE_MISMATCH", + "DOCUMENTATION_NEEDED", + "ACCOUNT_FLAT", + "INVALID_ACCOUNT_NUMBER", + "DUPLICATE", + "ACCOUNT_IN_DISTRIBUTION_OR_TRANSFER", + "CLIENT_RESCINDED", + "MISSING_AUTHORIZATION_SIGNATURE", + "ACCOUNT_VIOLATES_CREDIT_POLICY_OF_RECEIVING_FIRM", + "UNRECOGNIZED_FOR_RESIDUAL_CREDIT_BALANCE", + "PARTIAL_TRANSFER_DELIVERER_INITIATED_REJECT", + "FAIL_REVERSAL_REJECT", + "RECLAIM_REJECT", + "MUTUAL_FUND_CLEANUP", + "SYSTEM_REJECTED", + "INVALID_PARTICIPANT", + "ASSOCIATED_RECORD_NOT_PRESENT_OR_INVALID", + "MISSED_CUTOFF" + ], + "type": "string" + } + }, + "type": "object" + }, + "AcatsSecurities": { + "description": "List of securities to transfer", + "properties": { + "equities": { + "items": { + "$ref": "#/components/schemas/AcatsEquity" + }, + "type": "array" + } + }, + "type": "object" + }, + "AcatsSortDirection": { + "default": "DESC", + "description": "Sort direction for list results.", + "enum": [ + "ASC", + "DESC" + ], + "type": "string" + }, + "AcatsStatusDetails": { + "description": "Status details", + "properties": { + "dtcc_status": { + "$ref": "#/components/schemas/AcatsDtccStatus" + }, + "transfer_status": { + "$ref": "#/components/schemas/AcatsTransferStatus" + } + }, + "type": "object" + }, + "AcatsTimestamp": { + "description": "Timestamp in RFC-3339 format with microsecond precision", + "example": "2026-01-01T00:00:00Z", + "format": "date-time", + "type": "string" + }, + "AcatsTransfer": { + "description": "An ACATS transfer and its current status.", + "example": { + "acats_id": "2c827687-ddd3-4ad9-bee8-0a3cdad35301", + "account_id": "9ac16d7d-1f6e-445b-8e51-807e77ba2902", + "account_name": "John Doe", + "account_number": "12345678", + "account_type": "SINGLE", + "contra_account_number": "A00000123", + "contra_broker_name": "Alpaca Clearing", + "contra_broker_number": "00001234", + "contra_broker_type": "BANK", + "created_at": "2026-04-21T12:41:03.505862Z", + "direction": "INCOMING", + "ip_address": "203.0.113.42", + "rejection_details": { + "rejection_message": "SS# Tax ID Mismatch", + "rejection_reason": "SS_TAX_ID_MISMATCH" + }, + "status": { + "dtcc_status": "REJECT", + "transfer_status": "REJECTED" + }, + "total_transfer_value": "24223.62", + "transfer_identifier": "03251237654321", + "transfer_type": "FUL" + }, + "properties": { + "acats_id": { + "description": "The ID of the ACATS transfer", + "format": "uuid", + "type": "string" + }, + "account_id": { + "description": "Account ID associated with the ACATS transfer", + "format": "uuid", + "type": "string" + }, + "account_name": { + "description": "Name on the Alpaca account associated with the transfer", + "type": "string" }, "account_number": { - "nullable": true, + "description": "Account number at Alpaca", "type": "string" }, - "account_sub_type": { - "$ref": "#/components/schemas/AccountSubType" + "account_type": { + "$ref": "#/components/schemas/AcatsAccountType" + }, + "contra_account_number": { + "description": "Account number at the contra broker", + "type": "string" + }, + "contra_broker_name": { + "description": "Name of the contra broker", + "type": "string" + }, + "contra_broker_number": { + "description": "DTCC account number for the contra broker", + "type": "string" + }, + "contra_broker_type": { + "$ref": "#/components/schemas/AcatsContraBrokerType" + }, + "created_at": { + "$ref": "#/components/schemas/AcatsTimestamp" + }, + "direction": { + "$ref": "#/components/schemas/AcatsTransferDirection" + }, + "ip_address": { + "description": "User's IP address (IPv4 or IPv6) at time of submission", + "type": "string" + }, + "original_transfer_identifier": { + "description": "DTCC transfer identifier for a previous, related transfer", + "type": "string" + }, + "rejection_details": { + "$ref": "#/components/schemas/AcatsRejectionDetails" + }, + "settlement_date": { + "$ref": "#/components/schemas/AcatsTimestamp" + }, + "status": { + "$ref": "#/components/schemas/AcatsStatusDetails" + }, + "total_transfer_value": { + "description": "Total transfer value is the sum of the market value of all assets and securities in an ACAT at the time of settlement, plus any cash balance.", + "format": "decimal", + "type": "string" + }, + "transfer_identifier": { + "description": "DTCC transfer identifier, also known as the control number", + "type": "string" + }, + "transfer_type": { + "$ref": "#/components/schemas/AcatsDtccTransferType" + } + }, + "required": [ + "acats_id", + "account_id", + "account_number", + "created_at", + "contra_account_number", + "contra_broker_number", + "transfer_type", + "direction", + "status" + ], + "type": "object" + }, + "AcatsTransferDirection": { + "description": "Transfer direction", + "enum": [ + "INCOMING", + "OUTGOING" + ], + "type": "string" + }, + "AcatsTransferStatus": { + "description": "Transfer status as tracked by Alpaca", + "enum": [ + "PENDING", + "IN_PROGRESS", + "REJECTED", + "SETTLED" + ], + "type": "string" + }, + "Account": { + "description": "Represents high level account info. Used when returning entire account information would not be useful like the getAllAccounts operation", + "examples": [ + { + "account_number": "9034005019", + "created_at": "2019-09-30T23:55:31.185998Z", + "currency": "USD", + "id": "0d18ae51-3c94-4511-b209-101e1666416b", + "last_equity": "1500.65", + "primary_account_holder_id": null, + "status": "APPROVED" + } + ], + "properties": { + "account_number": { + "description": "A human-readable account number that can be shown to the end user", + "type": [ + "string", + "null" + ] }, "account_type": { "$ref": "#/components/schemas/AccountType" @@ -1613,11 +2149,9 @@ }, "type": "array" }, - "beneficiaries": { - "items": { - "$ref": "#/components/schemas/Beneficiary" - }, - "type": "array" + "allow_instant_ach": { + "description": "Determines whether the account is enabled for Instant ACH by the partner.", + "type": "boolean" }, "cash_interest": { "$ref": "#/components/schemas/AccountCashInterestResponse" @@ -1626,6 +2160,7 @@ "$ref": "#/components/schemas/Contact" }, "created_at": { + "description": "Timestamp (RFC3339) of account creation.", "format": "date-time", "type": "string" }, @@ -1635,9 +2170,6 @@ "currency": { "$ref": "#/components/schemas/Currency" }, - "custodial_account_type": { - "$ref": "#/components/schemas/CustodialAccountType" - }, "disclosures": { "$ref": "#/components/schemas/Disclosures" }, @@ -1649,8 +2181,9 @@ "type": "array" }, "enabled_assets": { + "description": "Assets the user has enabled and is able to trade once status and/or crypto_status are ACTIVE", "items": { - "$ref": "#/components/schemas/AssetClass" + "$ref": "#/components/schemas/EnabledAssetClass" }, "type": "array" }, @@ -1658,88 +2191,35 @@ "$ref": "#/components/schemas/AccountFPSLResponse" }, "id": { + "description": "UUID that identifies the account for later reference", "format": "uuid", "type": "string" }, "identity": { "$ref": "#/components/schemas/Identity" }, - "investment_objective": { - "description": "The user's investment objective. This field should be used instead of the deprecated `investment_objective` under identity.\n", - "enum": [ - "generate_income", - "preserve_wealth", - "market_speculation", - "growth", - "balance_preserve_wealth_with_growth" - ], - "type": "string" - }, - "investment_time_horizon": { - "description": "The expected period of time the user plan to invest to achieve his/her financial goal(s). This field should be used instead of the deprecated `investment_time_horizon` under identity.\n", - "enum": [ - "less_than_1_year", - "1_to_2_years", - "3_to_5_years", - "6_to_10_years", - "more_than_10_years" - ], - "type": "string" - }, - "kyc_results": { - "$ref": "#/components/schemas/KYCResults" + "instant_ach_blocked": { + "description": "Indicates whether the account is blocked for Instant ACH by Alpaca. Defaults to false when the partner creates the account. If the partner has set allow_instant_ach to true but instant_ach_blocked is true, then the account is blocked for Instant ACH irrespective of the fact that the partner has it enabled.", + "type": "boolean" }, "last_equity": { + "description": "EOD equity calculation (cash + long market value + short market value)", "format": "decimal", "type": "string" }, - "liquidity_needs": { - "description": "The user's ability to quickly and easily convert to cash all or a portion of the investments in this account without experiencing significant loss in value. This field should be used instead of the deprecated `liquidity_needs` under identity.\n", - "enum": [ - "very_important", - "important", - "somewhat_important", - "does_not_matter" - ], - "type": "string" - }, - "minor_identity": { - "$ref": "#/components/schemas/CustodialAccountMinorIdentity" - }, "primary_account_holder_id": { - "description": "UUID that identifies the primary account holder (party) for accounts opened under the Multi-Live Accounts (MLA) flow.\nOnly populated when the account was created by referencing an existing account holder via `primary_account_holder_id`\non `POST /v1/accounts`. Otherwise `null`. Currently only supported for `account_type` of `trading` and `ira`.\n", + "description": "UUID of the account's primary holder when available; otherwise `null`.", "format": "uuid", - "nullable": true, - "type": "string" - }, - "risk_tolerance": { - "description": "The user's investment risk tolerance. This field should be used instead of the deprecated `risk_tolerance` under identity.\n", - "enum": [ - "conservative", - "moderate", - "significant_risk" - ], - "type": "string" + "type": [ + "string", + "null" + ] }, "status": { "$ref": "#/components/schemas/AccountStatus" }, - "trading_configurations": { - "$ref": "#/components/schemas/AccountConfigurations" - }, "trusted_contact": { "$ref": "#/components/schemas/TrustedContact" - }, - "usd": { - "description": "values in USD. This is returned for LCT (non-USD) accounts only.", - "properties": { - "last_equity": { - "example": "123.45", - "format": "decimal", - "type": "string" - } - }, - "type": "object" } }, "required": [ @@ -1747,368 +2227,267 @@ "account_number", "status", "currency", - "last_equity", "created_at", - "account_type" + "last_equity" ], - "type": "object", - "x-examples": { - "example-1": { - "account_number": "601842165", - "account_sub_type": null, - "account_type": "trading", - "agreements": [ - { - "agreement": "margin_agreement", - "ip_address": "127.0.0.1", - "revision": "16.2021.05", - "signed_at": "2022-01-21T21:25:26.579487214Z" - }, - { - "agreement": "customer_agreement", - "ip_address": "127.0.0.1", - "revision": "22.2024.08", - "signed_at": "2022-01-21T21:25:26.579487214Z" - } - ], - "beneficiaries": [ - { - "date_of_birth": "1970-01-01", - "family_name": "Smith", - "given_name": "John", - "middle_name": "P", - "relationship": "spouse", - "share_pct": "100", - "tax_id": "xxx-xx-xxxx", - "tax_id_type": "USA_SSN", - "type": "primary" - } - ], - "contact": { - "city": "San Mateo", - "country": "USA", - "email_address": "strange_elbakyan_97324509@example.com", - "phone_number": "614-555-0697", - "postal_code": "94401", - "state": "CA", - "street_address": [ - "20 N San Mateo Dr" - ] - }, - "created_at": "2022-01-21T21:25:26.583576Z", - "crypto_status": "INACTIVE", - "currency": "USD", - "disclosures": { - "immediate_family_exposed": false, - "is_affiliated_exchange_or_finra": false, - "is_control_person": false, - "is_discretionary": false, - "is_politically_exposed": false - }, - "documents": [ - { - "content": "https://s3.amazonaws.com/stg.alpaca.markets/documents/accounts/3dcb795c-3ccc-402a-abb9-07e26a1b1326/2490f5d9-8988-4405-a3e0-d76e65de13c2.jpg?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=AKIAJLDF4SCWSL6HUQKA%2F20220210%2Fus-east-1%2Fs3%2Faws4_request&X-Amz-Date=20220210T162205Z&X-Amz-Expires=900&X-Amz-SignedHeaders=host&X-Amz-Signature=bc6cc0f541d569623f3973fb83734d7960a69886e8b175a9d472f666b87419f1", - "created_at": "2022-01-21T21:25:28.184231Z", - "document_sub_type": "passport", - "document_type": "identity_verification", - "id": "d5af1585-6c60-494d-9ea5-c5df62704229" - } - ], - "id": "3dcb795c-3ccc-402a-abb9-07e26a1b1326", - "identity": { - "country_of_birth": "USA", - "country_of_citizenship": "USA", - "country_of_tax_residence": "USA", - "date_of_birth": "1970-01-01", - "date_of_departure_from_usa": null, - "family_name": "Elbakyan", - "funding_source": [ - "employment_income" - ], - "given_name": "Strange", - "permanent_resident": null, - "tax_id_type": "USA_SNN", - "visa_expiration_date": null, - "visa_type": null - }, - "last_equity": "40645.13", - "primary_account_holder_id": null, - "status": "ACTIVE", - "trading_configurations": null, - "trusted_contact": { - "email_address": "strange_elbakyan_97324509@example.com", - "family_name": "Doe", - "given_name": "Jane" - } - } - } - }, - "AccountFPSLEvent": { - "description": "This property is included when the account's FPSL information had changed due to an enrollment, tier change, or unenrollment.\nThe from and to status or tier id will be included in the event, depending on the change.\n", - "properties": { - "US": { - "properties": { - "status_from": { - "description": "The FPSL program status of the account before the change", - "type": "string" - }, - "status_to": { - "description": "The FPSL program status of the account after the change", - "type": "string" - }, - "tier_from": { - "description": "The tier id before the change", - "type": "string" - }, - "tier_to": { - "description": "The tier id after the change", - "type": "string" - } - }, - "type": "object" - } - }, "type": "object" }, - "AccountFPSLItem": { + "AccountCashInterestEvent": { + "description": "This property is included when the account's cash interest program had changed due to an enrollment, APR tier change, or unenrollment.\nThe from and to status or APR tier name will be included in the event, depending on the change.\n", "properties": { - "status": { - "description": "The status of the account for this FPSL market. One of:\n- **ACTIVE**\nThe account is successfully enrolled for FPSL for this market.\n- **INACTIVE**\nThe account is not enrolled for FPSL for this market due to it either not being eligible, an FPSL tier has not been assigned, or it has been unenrolled.\n", - "example": "ACTIVE", + "apr_tier_name_from": { + "description": "The APR tier name before the change", "type": "string" }, - "tier_id": { - "description": "The id of the FPSL tier for this market", - "example": "61e69015-8549-4bfd-b9c3-01e75843f47d", - "format": "uuid", + "apr_tier_name_to": { + "description": "The APR tier name after the change", + "type": "string" + }, + "currency": { + "description": "The currency of the cash interest program that changed.", + "example": "USD", + "type": "string" + }, + "status_from": { + "description": "The cash_interest program status of the account before the change", + "type": "string" + }, + "status_to": { + "description": "The cash_interest program status of the account after the change", "type": "string" } }, "type": "object" }, - "AccountFPSLItemPost": { + "AccountCashInterestPatch": { + "description": "Use this property to change the account's configuration for the USD cash interest program.\nTo enroll the account, specify the apr_tier_name. The status should not be specified on enrollment.\nTo change the APR tier, specify the new apr_tier_name. The status should not be specified on tier changes.\nThe unenroll, set the status to INACTIVE.\nAfter any change, the response will contain a status of PENDING_CHANGE.\nAn event showing the status change to ACTIVE (for enrollment or tier changes) or INACTIVE (for unenrollment) will be generated when the change is complete.\n", "properties": { - "tier_id": { - "description": "The id of the FPSL tier for this market", - "example": "61e69015-8549-4bfd-b9c3-01e75843f47d", - "format": "uuid", - "type": "string" + "USD": { + "$ref": "#/components/schemas/AccountCashInterestProgram" } }, "type": "object" }, - "AccountFPSLPatch": { - "description": "The account's Fully Paid Securities Lending (FPSL) configuration.\nUse this property to change the account's configuration for the FPSL program.\nTo enroll the account for a market, specify the tier_id. The status should not be specified on enrollment.\nTo change the tier, specify the new tier_id. The status should not be specified on tier changes.\nTo unenroll, set the status to INACTIVE.\nTo re-enroll the account, set the status to ACTIVE. You can also specify the tier_id in case you want to change it.\nCurrently only US market is supported.\n", + "AccountCashInterestPost": { + "description": "The configuration of the account's USD cash interest program when creating an account.\nIf cash_interest is not provided and there is a default APR tier defined, that tier will be used.\nTo enroll the account in a non-default APR tier, provide the cash_interest object with the desired apr_tier_name. The status should not be specified on enrollment.\nThe response will contain a status of PENDING_CHANGE. An event showing the status change to ACTIVE will be generated when the enrollment is complete.\n", "properties": { - "US": { - "$ref": "#/components/schemas/AccountFPSLItem" + "USD": { + "$ref": "#/components/schemas/AccountCashInterestProgram" } }, "type": "object" }, - "AccountFPSLPost": { - "description": "The account's Fully Paid Securities Lending (FPSL) configuration.\nTo enroll the account for a market, specify the tier_id. The status should not be specified on enrollment.\nCurrently only the US market is supported.\n", + "AccountCashInterestProgram": { "properties": { - "US": { - "$ref": "#/components/schemas/AccountFPSLItemPost" + "apr_tier_name": { + "description": "The unique name of the APR tier for a specific program", + "example": "gold", + "type": "string" + }, + "status": { + "description": "The status of the account within a cash interest program. One of:\n- **ACTIVE**\nThe account is enrolled and eligible for idle cash to be swept at the end of day (EOD).\n- **INACTIVE**\nThe account is not enrolled due to it either not being eligible (e.g. the updated Alpaca Customer Agreement has not been signed), an APR tier needs to be assigned, or they have been unenrolled.\n- **PENDING_CHANGE**\nAn enrollment, APR Tier change, or unenrollment is in progress\n", + "example": "ACTIVE", + "type": "string" } }, "type": "object" }, - "AccountFPSLResponse": { - "description": "The account's Fully Paid Securities Lending (FPSL) configuration.\nThis is only returned for accounts that have FPSL enabled.\n", + "AccountCashInterestResponse": { + "description": "The configuration and status of the account's USD cash interest program\n", "properties": { - "US": { - "$ref": "#/components/schemas/AccountFPSLItem" + "USD": { + "$ref": "#/components/schemas/AccountCashInterestProgram" } }, "type": "object" }, - "AccountLimit": { + "AccountConfigurations": { + "description": "Represents additional configuration settings for an account", "properties": { - "account_no": { - "type": "string" + "disable_overnight_trading": { + "description": "If true, overnight trading is disabled.", + "type": "boolean" }, - "amount_available": { - "format": "decimal", + "fractional_trading": { + "description": "If true, account is able to participate in fractional trading", + "type": "boolean" + }, + "max_margin_multiplier": { + "description": "Can be \"1\" or \"2\"", "type": "string" }, - "amount_in_use": { - "format": "decimal", + "max_options_trading_level": { + "description": "The desired maximum options trading level. 0=disabled, 1=Covered Call/Cash-Secured Put, 2=Long Call/Put, 3=Spreads/Straddles.", + "enum": [ + 0, + 1, + 2, + 3 + ], + "type": "integer" + }, + "no_shorting": { + "description": "If true, account becomes long-only mode.", + "type": "boolean" + }, + "ptp_no_exception_entry": { + "description": "If set to true then Alpaca will accept orders for PTP symbols with no exception. Default is false.", "type": "string" }, - "amount_limit": { - "format": "decimal", + "suspend_trade": { + "description": "If true, new orders are blocked.", + "type": "boolean" + }, + "trade_confirm_email": { + "description": "all or none. If none, emails for order fills are not sent.", + "enum": [ + "all", + "none" + ], "type": "string" } }, - "required": [ - "account_no", - "amount_in_use", - "amount_available", - "amount_limit" - ], + "title": "AccountConfigurations", "type": "object" }, - "AccountStatus": { - "description": "Designates the current status of this account\n\nPossible Values:\n- **INACTIVE**\nAccount not set to trade given asset.\n- **ONBOARDING**\nAn application is expected for this user, but has not been submitted yet.\n- **SUBMITTED**\nThe application has been submitted and is being processed.\n- **SUBMISSION_FAILED**\nUsed to display if failure on submission\n- **ACTION_REQUIRED**\nThe application requires manual action.\n- **ACCOUNT_UPDATED**\nUsed to display when Account has been modified by user\n- **APPROVAL_PENDING**\nInitial value. The application approval process is in progress.\n- **APPROVED**\nThe account application has been approved, and waiting to be ACTIVE\n- **REJECTED**\nThe account application is rejected for some reason\n- **ACTIVE**\nThe account is fully active. Trading and funding are processed under this status.\n- **ACCOUNT_CLOSED**\nThe account is closed.\n", - "enum": [ - "INACTIVE", - "ONBOARDING", - "SUBMITTED", - "SUBMISSION_FAILED", - "ACTION_REQUIRED", - "ACCOUNT_UPDATED", - "APPROVAL_PENDING", - "APPROVED", - "REJECTED", - "ACTIVE", - "ACCOUNT_CLOSED" - ], - "example": "ACTIVE", - "type": "string" - }, - "AccountStatusEvent": { - "description": "Represents a change to certain account properties, sent over the events streaming API.\n", + "AccountCreationRequest": { + "description": "Fields accepted when creating an account. Requirements vary by account type, enabled assets, existing holder agreements, and correspondent configuration. The server enforces conditional requirements that are not represented statically in this schema.\n\nAccount-specific requirements:\n- Trading accounts require inline `identity` plus `contact`, or an existing holder through `primary_account_holder_id`. Individual US equity accounts created with inline identity also require `disclosures`.\n- Custodial accounts require `identity` and `minor_identity`. US equity accounts also require `disclosures`.\n- IRA accounts require `account_sub_type`, US equity, and either inline `identity` or an existing holder through `primary_account_holder_id`; they do not support crypto. IRA accounts created with inline identity also require `disclosures`.\n- Donor-advised accounts require `entity_id`.\n", "properties": { - "account_blocked": { - "description": "If true the account was blocked, if false, the account got unblocked", - "type": "boolean" + "account_sub_type": { + "allOf": [ + { + "$ref": "#/components/schemas/AccountSubType" + } + ], + "description": "The account subtype. Required for IRA accounts." }, - "account_id": { - "description": "The unique identifier of the account that was changed", - "minLength": 1, - "type": "string" + "account_type": { + "allOf": [ + { + "$ref": "#/components/schemas/AccountCreationType" + } + ], + "default": "trading", + "description": "The account type. Defaults to `trading` when omitted." }, - "account_number": { - "description": "The account number of the account that was changed", - "minLength": 1, - "type": "string" + "agreements": { + "description": "Agreements submitted for the account or holder. Required agreement types depend on account type, enabled assets, existing holder agreements, and correspondent configuration. The server validates required coverage.", + "items": { + "$ref": "#/components/schemas/Agreement" + }, + "type": "array" }, - "admin_configurations": { - "$ref": "#/components/schemas/AdminConfigurationsEvent" + "allow_instant_ach": { + "default": false, + "description": "Determines whether the account will be enabled for Instant ACH by the partner. Defaults to false if not provided.", + "type": "boolean" }, - "at": { - "description": "timestamp of event", - "minLength": 1, - "type": "string" + "beneficiaries": { + "description": "IRA Account only. A user can submit max 6 beneficiaries.", + "items": { + "$ref": "#/components/schemas/Beneficiary" + }, + "type": "array" }, "cash_interest": { - "$ref": "#/components/schemas/AccountCashInterestEvent" + "$ref": "#/components/schemas/AccountCashInterestPost" }, - "crypto_status_from": { - "description": "account crypto_status changed from", - "type": "string" + "contact": { + "$ref": "#/components/schemas/Contact" }, - "crypto_status_to": { - "description": "account crypto_status changed to", - "type": "string" + "disclosures": { + "$ref": "#/components/schemas/Disclosures" }, - "event_id": { - "description": "monotonically increasing 64bit integer", - "type": "integer" + "documents": { + "items": { + "$ref": "#/components/schemas/OwnerDocumentUploadRequest" + }, + "type": "array" }, - "event_ulid": { - "description": "lexically sortable, monotonically increasing character array", - "format": "ulid", + "enabled_assets": { + "description": "Will default to `us_equity`. Alpaca has the ability to update the default value upon request.", + "items": { + "$ref": "#/components/schemas/EnabledAssetClass" + }, + "type": "array" + }, + "entity_id": { + "description": "UUID of an existing legal entity used as the holder of a donor-advised account. Required for donor-advised accounts.", + "format": "uuid", "type": "string" }, "fpsl": { - "$ref": "#/components/schemas/AccountFPSLEvent" - }, - "kyc_results": { - "$ref": "#/components/schemas/KYCResults" - }, - "options": { - "$ref": "#/components/schemas/OptionsApprovalEvent" + "$ref": "#/components/schemas/AccountFPSLPost" }, - "reason": { - "deprecated": true, - "minLength": 1, - "type": "string" + "identity": { + "$ref": "#/components/schemas/Identity" }, - "status_from": { - "description": "The account status before the change", - "example": "APPROVED", + "investment_objective": { + "description": "The user's investment objective. This field should be used instead of the deprecated `investment_objective` under identity.\n", + "enum": [ + "generate_income", + "preserve_wealth", + "market_speculation", + "growth", + "balance_preserve_wealth_with_growth" + ], "type": "string" }, - "status_to": { - "description": "The account status after the change", - "example": "ACTIVE", + "investment_time_horizon": { + "description": "The expected period of time the user plan to invest to achieve his/her financial goal(s). This field should be used instead of the deprecated `investment_time_horizon` under identity.\n", + "enum": [ + "less_than_1_year", + "1_to_2_years", + "3_to_5_years", + "6_to_10_years", + "more_than_10_years" + ], "type": "string" }, - "trading_blocked": { - "description": "If true the account cannot trade going forward, if false, the ban has been lifed", - "type": "boolean" - } - }, - "required": [ - "account_id", - "at", - "event_id", - "event_ulid" - ], - "title": "AccountStatusEvent", - "type": "object", - "x-examples": { - "example-1": { - "account_id": "4db36989-6565-4011-9126-39fe6b3d9bf6", - "account_number": "", - "at": "2021-06-14T09:59:15.232782Z", - "event_id": 122039, - "event_ulid": "01F84ZC0H0Q1QN7XPNWX44HF5J", - "kyc_results": null, - "status_from": "", - "status_to": "APPROVED" - } - } - }, - "AccountSubType": { - "description": "IRA Account only\n\nPossible values are:\n\n- traditional\n- roth", - "enum": [ - "traditional", - "roth" - ], - "example": "traditional", - "title": "AccountSubType", - "type": "string" - }, - "AccountTradingLimit": { - "properties": { - "available": { - "description": "The remaining net buying limit that can be used for trading.", - "format": "decimal", + "liquidity_needs": { + "description": "The user's ability to quickly and easily convert to cash all or a portion of the investments in this account without experiencing significant loss in value. This field should be used instead of the deprecated `liquidity_needs` under identity.\n", + "enum": [ + "very_important", + "important", + "somewhat_important", + "does_not_matter" + ], "type": "string" }, - "daily_net_limit": { - "description": "The net buying limit that can be reached before further trading activity is restricted. Please reach out to learn more about how this limit is determined.", - "format": "decimal", - "type": "string" + "minor_identity": { + "allOf": [ + { + "$ref": "#/components/schemas/CustodialAccountMinorIdentity" + } + ], + "description": "Identity information for the minor. Required for custodial accounts." }, - "held": { - "description": "The limit that is currently being held for open orders", - "format": "decimal", + "primary_account_holder_id": { + "description": "UUID of an existing party to use as the primary account holder. Among the documented account types, existing holders are supported for trading and IRA accounts.\n\nWhen provided, omit inline holder fields such as `contact`, `identity`, `disclosures`, and `minor_identity`. The server validates the referenced party, account-type compatibility, eligibility, and required agreements.\n", + "format": "uuid", "type": "string" }, - "swap_rate": { - "description": "The swap rate is applicable for Local Currency Trading (LCT) accounts", - "format": "decimal", + "risk_tolerance": { + "description": "The user's investment risk tolerance. This field should be used instead of the deprecated `risk_tolerance` under identity.\n", + "enum": [ + "conservative", + "moderate", + "significant_risk" + ], "type": "string" }, - "usd": { - "$ref": "#/components/schemas/USDAccountTradingLimit" + "trading_configurations": { + "$ref": "#/components/schemas/AccountConfigurations" }, - "used": { - "description": "The real time net value of cash inflows (buy trades, etc.) with cash outflows (sell trades, etc). This will be dynamic throughout the day based on user activity, with executed orders being reset at the start of the next trading day.", - "format": "decimal", - "type": "string" + "trusted_contact": { + "$ref": "#/components/schemas/TrustedContact" } }, - "title": "AccountTradingLimit", + "title": "AccountCreationRequest", "type": "object" }, - "AccountType": { - "description": "Possible values are:\n\n- trading\n- custodial\n- donor_advised\n- ira", + "AccountCreationType": { + "description": "The account type to create.", "enum": [ "trading", "custodial", @@ -2116,10197 +2495,10029 @@ "ira" ], "example": "trading", - "title": "AccountType", + "title": "AccountCreationType", "type": "string" }, - "AccountUpdateRequest": { + "AccountDocument": { + "description": "A document associated with an account", + "example": { + "date": "2019-09-30", + "id": "0d18ae51-3c94-4511-b209-101e1666416b", + "sub_type": "1099-Comp", + "type": "account_statement" + }, "properties": { - "agreements": { - "description": "Additional agreements, or new revisions of existing agreements, read and signed by the account holder.", - "items": { - "$ref": "#/components/schemas/Agreement" - }, - "type": "array" - }, - "beneficiaries": { - "items": { - "$ref": "#/components/schemas/Beneficiary" - }, - "type": "array" - }, - "cash_interest": { - "$ref": "#/components/schemas/AccountCashInterestPatch" - }, - "contact": { - "$ref": "#/components/schemas/Contact" - }, - "disclosures": { - "$ref": "#/components/schemas/Disclosures" + "date": { + "format": "date", + "type": "string" }, - "fpsl": { - "$ref": "#/components/schemas/AccountFPSLPatch" + "id": { + "format": "uuid", + "type": "string" }, - "identity": { - "$ref": "#/components/schemas/Identity" + "name": { + "type": "string" }, - "primary_account_holder_id": { - "description": "The UUID of the primary account holder. This field is immutable after the account is created.\n\n- Omitting the field, or supplying the value already associated with the account, is a no-op.\n- Supplying a value different from the current primary account holder returns HTTP 400.\n", - "format": "uuid", + "sub_type": { + "description": "The sub-type of the document. This is a free-form property.", "type": "string" }, - "trusted_contact": { - "$ref": "#/components/schemas/TrustedContact" + "type": { + "$ref": "#/components/schemas/AccountDocumentType" } }, - "type": "object", - "x-examples": { - "example-1": { - "beneficiaries": [ - { - "date_of_birth": "1970-01-01", - "family_name": "Smith", - "given_name": "John", - "middle_name": "P", - "relationship": "spouse", - "share_pct": "100", - "tax_id": "xxx-xx-xxxx", - "tax_id_type": "USA_SSN", - "type": "primary" - } - ], - "contact": { - "city": "San Mateo", - "country": "USA", - "email_address": "john.doe@example.com", - "phone_number": "+15556667788", + "required": [ + "id", + "type", + "date" + ], + "type": "object" + }, + "AccountDocumentType": { + "description": "The type of the account document\n", + "enum": [ + "account_application", + "account_statement", + "crypto_account_statement", + "options_application", + "tax_statement", + "trade_confirmation_json", + "trade_confirmation" + ], + "example": "account_application", + "type": "string" + }, + "AccountExtended": { + "description": "Represents an account with all data available. If your api response is missing some of these fields, there is a good chance you are using a route that returns `Account` instances instead of these.", + "examples": [ + { + "account_number": "601842165", + "account_type": "trading", + "agreements": [ + { + "agreement": "margin_agreement", + "ip_address": "127.0.0.1", + "revision": "16.2021.05", + "signed_at": "2022-01-21T21:25:26.579487214Z" + }, + { + "agreement": "customer_agreement", + "ip_address": "127.0.0.1", + "revision": "22.2024.08", + "signed_at": "2022-01-21T21:25:26.579487214Z" + } + ], + "beneficiaries": [ + { + "date_of_birth": "1970-01-01", + "family_name": "Smith", + "given_name": "John", + "middle_name": "P", + "relationship": "spouse", + "share_pct": "100", + "tax_id": "xxx-xx-xxxx", + "tax_id_type": "USA_SSN", + "type": "primary" + } + ], + "contact": { + "city": "San Mateo", + "country": "USA", + "email_address": "strange_elbakyan_97324509@example.com", + "phone_number": "614-555-0697", "postal_code": "94401", "state": "CA", "street_address": [ "20 N San Mateo Dr" ] }, + "created_at": "2022-01-21T21:25:26.583576Z", + "crypto_status": "INACTIVE", + "currency": "USD", "disclosures": { "immediate_family_exposed": false, "is_affiliated_exchange_or_finra": false, "is_control_person": false, + "is_discretionary": false, "is_politically_exposed": false }, + "documents": [ + { + "content": "https://example.com/documents/identity-verification.jpg", + "created_at": "2022-01-21T21:25:28.184231Z", + "document_sub_type": "passport", + "document_type": "identity_verification", + "id": "d5af1585-6c60-494d-9ea5-c5df62704229" + } + ], + "id": "3dcb795c-3ccc-402a-abb9-07e26a1b1326", "identity": { - "country_of_birth": "AUS", - "country_of_citizenship": "AUS", + "country_of_birth": "USA", + "country_of_citizenship": "USA", "country_of_tax_residence": "USA", - "date_of_birth": "1990-01-01", - "family_name": "Doe", + "date_of_birth": "1970-01-01", + "family_name": "Elbakyan", "funding_source": [ "employment_income" ], - "given_name": "John", - "tax_id": "666-55-4321", + "given_name": "Strange", "tax_id_type": "USA_SSN" }, + "last_equity": "40645.13", + "primary_account_holder_id": null, + "status": "ACTIVE", "trusted_contact": { - "email_address": "jane.doe@example.com", + "email_address": "strange_elbakyan_97324509@example.com", "family_name": "Doe", "given_name": "Jane" } } - } - }, - "Activity": { - "allOf": [ - { - "properties": { - "account_id": { - "example": "c8f1ef5d-edc0-4f23-9ee4-378f19cb92a4", - "format": "uuid", - "type": "string" - }, - "activity_type": { - "$ref": "#/components/schemas/ActivityType" - }, - "id": { - "example": "20220208125959696::88b5f678-fef5-447b-af15-f21e367e6d8c", - "type": "string" - } - }, - "type": "object" + ], + "properties": { + "account_name": { + "type": "string" }, - { - "oneOf": [ - { - "$ref": "#/components/schemas/TradeActivity" - }, - { - "$ref": "#/components/schemas/NonTradeActivity" - } + "account_number": { + "type": [ + "string", + "null" ] - } - ], - "description": "Base for activity types", - "required": [ - "id", - "activity_type" - ], - "title": "Activity" - }, - "ActivityEventV2": { - "allOf": [ - { - "$ref": "#/components/schemas/ActivityEventV2CommonFields" }, - { - "properties": { - "details": { - "oneOf": [ - { - "$ref": "#/components/schemas/ActivityV2DetailTRD" - }, - { - "$ref": "#/components/schemas/ActivityV2DetailNTA" - } - ] - } + "account_sub_type": { + "$ref": "#/components/schemas/AccountSubType" + }, + "account_type": { + "$ref": "#/components/schemas/AccountType" + }, + "agreements": { + "items": { + "$ref": "#/components/schemas/Agreement" }, - "required": [ - "details" - ], - "type": "object" - } - ], - "description": "Represents an account activity, sent over the Event Streaming API.", - "title": "ActivityEventV2", - "type": "object" - }, - "ActivityEventV2CommonFields": { - "description": "Represents the common fields for all Activity V2 Events", - "properties": { - "account_id": { - "description": "Account UUID", - "format": "uuid", - "type": "string" + "type": "array" }, - "activity_subtype": { - "description": "Sub category for activity type, if any", - "type": "string" + "allow_instant_ach": { + "description": "Determines whether the account is enabled for Instant ACH by the partner.", + "type": "boolean" }, - "activity_type": { - "description": "The type of the activity, which can be trade or any of the non trade activities", - "type": "string" + "beneficiaries": { + "items": { + "$ref": "#/components/schemas/Beneficiary" + }, + "type": "array" }, - "at": { - "description": "Timestamp of event", + "cash_interest": { + "$ref": "#/components/schemas/AccountCashInterestResponse" + }, + "contact": { + "$ref": "#/components/schemas/Contact" + }, + "created_at": { "format": "date-time", - "minLength": 1, "type": "string" }, - "currency": { - "description": "Currency code in ISO format", - "type": "string" + "crypto_status": { + "$ref": "#/components/schemas/AccountStatus" }, - "event_id": { - "description": "Lexically sortable, monotonically increasing character string", - "format": "ulid", - "type": "string" + "currency": { + "$ref": "#/components/schemas/Currency" }, - "executed_at": { - "description": "Execution time for the activity event", - "format": "date-time", - "minLength": 1, - "type": "string" + "custodial_account_type": { + "$ref": "#/components/schemas/CustodialAccountType" }, - "net_amount": { - "description": "The net amount of money (positive or negative) associated with the activity", - "format": "decimal", - "type": "string" + "disclosures": { + "$ref": "#/components/schemas/Disclosures" }, - "previous_id": { - "description": "Previous ID is presented if this activity corrects or cancels a previous trade or non trade activity. It contains execution_id or trns_id respectively", - "format": "uuid", - "type": "string" + "documents": { + "description": "The documents associated with the primary owner of the account", + "items": { + "$ref": "#/components/schemas/OwnerDocument" + }, + "type": "array" }, - "price": { - "description": "The price of the security involved with the activity", - "format": "decimal", - "type": "string" + "enabled_assets": { + "items": { + "$ref": "#/components/schemas/EnabledAssetClass" + }, + "type": "array" }, - "qty": { - "description": "The quantity of the security involved with the activity", - "format": "decimal", - "type": "string" + "fpsl": { + "$ref": "#/components/schemas/AccountFPSLResponse" }, - "ref_id": { - "description": "The unique identifier for the activity. For trades, the execution_id is used, for other activities the trns_id is used.", + "id": { "format": "uuid", "type": "string" }, - "settle_date": { - "description": "Date when the activity settled", - "format": "date", - "minLength": 1, - "type": "string" + "identity": { + "$ref": "#/components/schemas/Identity" }, - "status": { - "description": "Status of the activity", - "type": "string" - }, - "swap_fee_bps": { - "description": "Currency conversion fee rate base-point in case of local currency activity", - "format": "decimal", - "type": "string" + "instant_ach_blocked": { + "description": "Indicates whether the account is blocked for Instant ACH by Alpaca. Defaults to false when the partner creates the account. If the partner has set allow_instant_ach to true but instant_ach_blocked is true, then the account is blocked for Instant ACH irrespective of the fact that the partner has it enabled.", + "type": "boolean" }, - "swap_rate": { - "description": "Conversion rate for local currency activities", - "format": "decimal", - "type": "string" - } - }, - "required": [ - "account_id", - "at", - "event_id", - "activity_type", - "executed_at", - "status", - "settle_date", - "currency", - "ref_id" - ], - "type": "object" - }, - "ActivitySubType": { - "description": "Represents a more specific classification to the `activity_type`.\nThis field is optional and may not always be populated, depending on the activity type and the available data.\nEach `activity_type` has a set of valid `activity_sub_type` values.\n\nFull mapping of `activity_type` to `activity_sub_type`:\n\n- **DIV**: Dividend activity sub-types:\n - **CDIV**: Cash Dividend\n - **SDIV**: Stock Dividend\n - **SPD**: Substitute Payment In Lieu Of Dividend\n\n- **FEE**: Fee-related activity sub-types:\n - **REG**: Regulatory Fee\n - **TAF**: Trading Activity Fee\n - **LCT**: Local Currency Trading Fee\n - **ORF**: Options Regulatory Fee\n - **OCC**: Options Clearing Corporation Fee\n - **NRC**: Non-Retail Commission Fee\n - **NRV**: Non-Retail Venue Fee\n - **COM**: Commission\n - **CAT**: Consolidated Audit Trail Fee\n\n- **INT**: Interest-related activity sub-types:\n - **MGN**: Margin Interest\n - **CDT**: Credit Interest\n - **SWP**: Sweep Interest\n - **QII**: Qualified Interest\n\n- **MA**: Merger and Acquisition activity sub-types:\n - **CMA**: Cash Merger\n - **SMA**: Stock Merger\n - **SCMA**: Stock & Cash Merger\n\n- **NC**: Name Change activity sub types\n - **SNC**: Symbol Name Change\n - **CNC**: CUSIP Name Change\n - **SCNC**: Symbol & CUSIP Name Change\n\n- **OPCA**: Option Corporate Action activity sub-types:\n - **DIV.CDIV**: Cash Dividend\n - **DIV.SDIV**: Stock Dividend\n - **MA.CMA**: Cash Merger\n - **MA.SMA**: Stock Merger\n - **MA.SCMA**: Stock & Cash Merger\n - **NC.CNC**: CUSIP Name Change\n - **NC.SNC**: Symbol Name Change\n - **NC.SCNC**: Symbol & CUSIP Name Change\n - **SPIN**: Spin-off\n - **SPLIT.FSPLIT**: Forward Stock Split\n - **SPLIT.RSPLIT**: Reverse Stock Split\n - **SPLIT.USPLIT**: Unit Split\n\n- **REORG**: Reorganization activity sub-types:\n - **WRM**: Worthless Removal\n\n- **SPLIT**: Stock Split activity sub-types:\n - **FSPLIT**: Forward Stock Split\n - **RSPLIT**: Reverse Stock Split\n - **USPLIT**: Unit Split\n\n- **VOF**: Voluntary Offering activity sub-types:\n - **VTND**: Tender Offer\n - **VWRT**: Warrant Exercise\n - **VRGT**: Rights Offer\n - **VEXH**: Exchange Offer\n\n- **WH**: Withholding activity sub-types:\n - **SWH**: State Withholding\n - **FWH**: Federal Withholding\n - **SLWH**: Sales Withholding", - "title": "ActivitySubType", - "type": "string" - }, - "ActivityType": { - "description": "Represents the various kinds of activity.\n\nTradeActivity's will always have the type `FILL`\n\n- **FILL**\n Order Fills (Partial/Full)\n- **ACATC**\n ACATS IN/OUT (Cash)\n- **ACATS**\n ACATS IN/OUT (Securities)\n- **CGD**\n Capital Gain Distribution\n- **CIL**\n Cash in Lieu of Stock\n- **CSD**\n Cash Disbursement (+)\n- **CSW**\n Cash Withdrawable\n- **DIV**\n Dividend\n- **DIVCGL**\n Dividend (Capital Gain Long Term)\n- **DIVCGS**\n Dividend (Capital Gain Short Term)\n- **DIVNRA**\n Dividend Adjusted (NRA Withheld)\n- **DIVROC**\n Dividend Return of Capital\n- **DIVTXEX**\n Dividend (Tax Exempt)\n- **FEE**\n REG and TAF Fees\n- **INT**\n Interest (Credit/Margin)\n- **JNLC**\n Journal Entry (Cash)\n- **JNLS**\n Journal Entry (Stock)\n- **OPASN**\n Option Assignment\n- **OPCA**\n Option Corporate Action\n- **OPCSH**\n Option cash deliverable for non-standard contracts\n- **OPEXC**\n Option Exercise\n- **OPEXP**\n Option Expiration\n- **OPTRD**\n Option Trade\n- **MA**\n Merger/Acquisition\n- **PTC**\n Pass Thru Change\n- **REORG**\n Reorg CA\n- **SPIN**\n Stock Spinoff\n- **SPLIT**\n Stock Split\n- **FOPT**\n Free of Payment Transfers", - "enum": [ - "FILL", - "ACATC", - "ACATS", - "CGD", - "CIL", - "CSD", - "CSW", - "DIV", - "DIVCGL", - "DIVCGS", - "DIVNRA", - "DIVROC", - "DIVTXEX", - "FEE", - "INT", - "JNLC", - "JNLS", - "MA", - "OPASN", - "OPCA", - "OPCSH", - "OPEXC", - "OPEXP", - "OPTRD", - "PTC", - "REORG", - "SPIN", - "SPLIT", - "FOPT" - ], - "title": "ActivityType", - "type": "string" - }, - "ActivityV2DetailNTA": { - "allOf": [ - { - "oneOf": [ - { - "$ref": "#/components/schemas/DIVSPDActivityV2" - }, - { - "$ref": "#/components/schemas/CDIVActivityV2" - }, - { - "$ref": "#/components/schemas/SDIVActivityV2" - }, - { - "$ref": "#/components/schemas/ForwardSplitActivityV2" - }, - { - "$ref": "#/components/schemas/ReverseSplitActivityV2" - }, - { - "$ref": "#/components/schemas/UnitSplitActivityV2" - }, - { - "$ref": "#/components/schemas/SpinoffActivityV2" - }, - { - "$ref": "#/components/schemas/MAActivityV2" - }, - { - "$ref": "#/components/schemas/NCActivityV2" - }, - { - "$ref": "#/components/schemas/FixedIncomeRedemptionActivityV2" - }, - { - "$ref": "#/components/schemas/RightsDistributionActivityV2" - }, - { - "$ref": "#/components/schemas/RightsSubscriptionElectionActivityV2" - }, - { - "$ref": "#/components/schemas/TenderOfferActivityV2" - }, - { - "$ref": "#/components/schemas/ExchangeOfferActivityV2" - }, - { - "$ref": "#/components/schemas/WarrantExerciseElectionActivityV2" - }, - { - "$ref": "#/components/schemas/WRMActivityV2" - }, - { - "$ref": "#/components/schemas/OpcaCDIVActivityV2" - }, - { - "$ref": "#/components/schemas/OpcaSDIVActivityV2" - }, - { - "$ref": "#/components/schemas/OpcaMAActivityV2" - }, - { - "$ref": "#/components/schemas/OpcaNCActivityV2" - }, - { - "$ref": "#/components/schemas/OpcaSPINActivityV2" - }, - { - "$ref": "#/components/schemas/OpcaFSPLITActivityV2" - }, - { - "$ref": "#/components/schemas/OpcaRSPLITActivityV2" - }, - { - "$ref": "#/components/schemas/OpcaUSPLITActivityV2" - }, - { - "$ref": "#/components/schemas/OPASNActivityV2" - }, - { - "$ref": "#/components/schemas/OPEXCActivityV2" - }, - { - "$ref": "#/components/schemas/OPEXPActivityV2" - }, - { - "$ref": "#/components/schemas/OPTRDActivityV2" - }, - { - "$ref": "#/components/schemas/AcatsActivityV2" - }, - { - "$ref": "#/components/schemas/AcatcActivityV2" - }, - { - "$ref": "#/components/schemas/FOPTActivityV2" - }, - { - "$ref": "#/components/schemas/DIVNRAActivityV2" - }, - { - "$ref": "#/components/schemas/JNLSActivityV2" - }, - { - "$ref": "#/components/schemas/JNLCActivityV2" - }, - { - "$ref": "#/components/schemas/CSWActivityV2" - }, - { - "$ref": "#/components/schemas/FEEActivityV2" - } - ] - } - ], - "type": "object" - }, - "ActivityV2DetailTRD": { - "description": "Activity details for a fill or partial_fill event", - "properties": { - "asset_id": { - "description": "Asset ID (For options this represents the option contract ID)", - "format": "uuid", + "investment_objective": { + "description": "The user's investment objective. This field should be used instead of the deprecated `investment_objective` under identity.\n", + "enum": [ + "generate_income", + "preserve_wealth", + "market_speculation", + "growth", + "balance_preserve_wealth_with_growth" + ], "type": "string" }, - "client_order_id": { - "description": "Order ID provided by the customer", + "investment_time_horizon": { + "description": "The expected period of time the user plan to invest to achieve his/her financial goal(s). This field should be used instead of the deprecated `investment_time_horizon` under identity.\n", + "enum": [ + "less_than_1_year", + "1_to_2_years", + "3_to_5_years", + "6_to_10_years", + "more_than_10_years" + ], "type": "string" }, - "commission": { - "description": "Commission to collect from the account holder", - "format": "decimal", - "type": "string" + "kyc_results": { + "$ref": "#/components/schemas/KYCResults" }, - "cum_qty": { - "description": "Total filled quantity on the order", + "last_equity": { "format": "decimal", "type": "string" }, - "cusip": { - "description": "CUSIP of the security involved with the activity", - "type": "string" - }, - "execution_type": { - "description": "The execution type", + "liquidity_needs": { + "description": "The user's ability to quickly and easily convert to cash all or a portion of the investments in this account without experiencing significant loss in value. This field should be used instead of the deprecated `liquidity_needs` under identity.\n", "enum": [ - "fill", - "trade_correct", - "trade_bust" + "very_important", + "important", + "somewhat_important", + "does_not_matter" ], "type": "string" }, - "leaves_qty": { - "description": "Unfilled quantity on the order, when order is filled value could be 0", - "format": "decimal", - "type": "string" + "minor_identity": { + "$ref": "#/components/schemas/CustodialAccountMinorIdentity" }, - "order_id": { - "description": "Order ID generated by Alpaca", + "primary_account_holder_id": { + "description": "UUID of the account's primary holder when available; otherwise `null`.", "format": "uuid", - "type": "string" - }, - "order_status": { - "description": "Identifies the current status of the order", - "type": "string" + "type": [ + "string", + "null" + ] }, - "side": { - "description": "Represents what side of the transaction an order was on", + "risk_tolerance": { + "description": "The user's investment risk tolerance. This field should be used instead of the deprecated `risk_tolerance` under identity.\n", "enum": [ - "buy", - "sell" + "conservative", + "moderate", + "significant_risk" ], "type": "string" }, - "symbol": { - "description": "The symbol of the security involved with the activity", - "type": "string" + "status": { + "$ref": "#/components/schemas/AccountStatus" + }, + "trading_configurations": { + "$ref": "#/components/schemas/AccountConfigurations" + }, + "trusted_contact": { + "$ref": "#/components/schemas/TrustedContact" + }, + "usd": { + "description": "values in USD. This is returned for LCT (non-USD) accounts only.", + "properties": { + "last_equity": { + "example": "123.45", + "format": "decimal", + "type": "string" + } + }, + "type": "object" } }, "required": [ - "order_id", - "side", - "symbol", - "asset_id", - "leaves_qty", - "cum_qty", - "order_status", - "execution_type" + "id", + "account_number", + "status", + "currency", + "last_equity", + "created_at", + "account_type" ], "type": "object" }, - "AdminActionBelongsTo": { - "description": "Represents structure of an Identifier for all AdminAction type", + "AccountFPSLEvent": { + "description": "This property is included when the account's FPSL information had changed due to an enrollment, tier change, or unenrollment.\nThe from and to status or tier id will be included in the event, depending on the change.\n", "properties": { - "id_reference": { + "US": { + "properties": { + "status_from": { + "description": "The FPSL program status of the account before the change", + "type": "string" + }, + "status_to": { + "description": "The FPSL program status of the account after the change", + "type": "string" + }, + "tier_from": { + "description": "The tier id before the change", + "type": "string" + }, + "tier_to": { + "description": "The tier id after the change", + "type": "string" + } + }, + "type": "object" + } + }, + "type": "object" + }, + "AccountFPSLItem": { + "properties": { + "status": { + "description": "The status of the account for this FPSL market. One of:\n- **ACTIVE**\nThe account is successfully enrolled for FPSL for this market.\n- **INACTIVE**\nThe account is not enrolled for FPSL for this market due to it either not being eligible, an FPSL tier has not been assigned, or it has been unenrolled.\n", + "example": "ACTIVE", "type": "string" }, - "kind": { - "$ref": "#/components/schemas/AdminActionBelongsToKind" + "tier_id": { + "description": "The id of the FPSL tier for this market", + "example": "61e69015-8549-4bfd-b9c3-01e75843f47d", + "format": "uuid", + "type": "string" } }, - "title": "AdminActionBelongsTo", "type": "object" }, - "AdminActionBelongsToKind": { - "enum": [ - "account", - "owner", - "correspondent" - ] + "AccountFPSLItemPost": { + "properties": { + "tier_id": { + "description": "The id of the FPSL tier for this market", + "example": "61e69015-8549-4bfd-b9c3-01e75843f47d", + "format": "uuid", + "type": "string" + } + }, + "type": "object" }, - "AdminActionCategory": { - "description": "Category of the Admin Action", - "enum": [ - "order", - "other" - ] + "AccountFPSLPatch": { + "description": "The account's Fully Paid Securities Lending (FPSL) configuration.\nUse this property to change the account's configuration for the FPSL program.\nTo enroll the account for a market, specify the tier_id. The status should not be specified on enrollment.\nTo change the tier, specify the new tier_id. The status should not be specified on tier changes.\nTo unenroll, set the status to INACTIVE.\nTo re-enroll the account, set the status to ACTIVE. You can also specify the tier_id in case you want to change it.\nCurrently only US market is supported.\n", + "properties": { + "US": { + "$ref": "#/components/schemas/AccountFPSLItem" + } + }, + "type": "object" }, - "AdminActionCreatedBy": { - "description": "Represents structure of an Creator's Identifier for all AdminAction type\n", + "AccountFPSLPost": { + "description": "The account's Fully Paid Securities Lending (FPSL) configuration.\nTo enroll the account for a market, specify the tier_id. The status should not be specified on enrollment.\nCurrently only the US market is supported.\n", "properties": { - "id_reference": { + "US": { + "$ref": "#/components/schemas/AccountFPSLItemPost" + } + }, + "type": "object" + }, + "AccountFPSLResponse": { + "description": "The account's Fully Paid Securities Lending (FPSL) configuration.\nThis is only returned for accounts that have FPSL enabled.\n", + "properties": { + "US": { + "$ref": "#/components/schemas/AccountFPSLItem" + } + }, + "type": "object" + }, + "AccountLimit": { + "properties": { + "account_no": { "type": "string" }, - "kind": { - "$ref": "#/components/schemas/AdminActionCreatedByKind" + "amount_available": { + "format": "decimal", + "type": "string" + }, + "amount_in_use": { + "format": "decimal", + "type": "string" + }, + "amount_limit": { + "format": "decimal", + "type": "string" } }, - "title": "AdminActionCreatedBy", + "required": [ + "account_no", + "amount_in_use", + "amount_available", + "amount_limit" + ], "type": "object" }, - "AdminActionCreatedByKind": { + "AccountStatus": { + "description": "Designates the current status of this account\n\nPossible Values:\n- **INACTIVE**\nAccount not set to trade given asset.\n- **PAPER_ONLY**\nThe account is limited to paper trading.\n- **ONBOARDING**\nAn application is expected for this user, but has not been submitted yet.\n- **SUBMITTED**\nThe application has been submitted and is being processed.\n- **SUBMISSION_FAILED**\nUsed to display if failure on submission\n- **ACTION_REQUIRED**\nThe application requires manual action.\n- **ACCOUNT_UPDATED**\nUsed to display when Account has been modified by user\n- **APPROVAL_PENDING**\nInitial value. The application approval process is in progress.\n- **APPROVED**\nThe account application has been approved, and waiting to be ACTIVE\n- **REJECTED**\nThe account application is rejected for some reason\n- **ACTIVE**\nThe account is fully active. Trading and funding are processed under this status.\n- **ACCOUNT_CLOSED**\nThe account is closed.\n", "enum": [ - "admin" - ] + "INACTIVE", + "PAPER_ONLY", + "ONBOARDING", + "SUBMITTED", + "SUBMISSION_FAILED", + "ACTION_REQUIRED", + "ACCOUNT_UPDATED", + "APPROVAL_PENDING", + "APPROVED", + "REJECTED", + "ACTIVE", + "ACCOUNT_CLOSED" + ], + "example": "ACTIVE", + "type": "string" }, - "AdminActionEventGeneral": { - "description": "Represents general fields for all AdminAction type", + "AccountStatusEvent": { + "description": "Represents a change to certain account properties, sent over the events streaming API.\n", + "examples": [ + { + "account_id": "4db36989-6565-4011-9126-39fe6b3d9bf6", + "at": "2021-06-14T09:59:15.232782Z", + "event_id": 122039, + "event_ulid": "01F84ZC0H0Q1QN7XPNWX44HF5J", + "status_from": "", + "status_to": "APPROVED" + } + ], "properties": { - "at": { - "description": "Timestamp of event", - "format": "date-time", + "account_blocked": { + "description": "If true the account was blocked, if false, the account got unblocked", + "type": "boolean" + }, + "account_id": { + "description": "The unique identifier of the account that was changed", + "minLength": 1, "type": "string" }, - "belongs_to": { - "$ref": "#/components/schemas/AdminActionBelongsTo" + "account_number": { + "description": "The account number of the account that was changed", + "minLength": 1, + "type": "string" }, - "category": { - "$ref": "#/components/schemas/AdminActionCategory" + "admin_configurations": { + "$ref": "#/components/schemas/AdminConfigurationsEvent" }, - "correspondent": { - "description": "Related correspondent", + "at": { + "description": "timestamp of event", + "minLength": 1, "type": "string" }, - "created_by": { - "$ref": "#/components/schemas/AdminActionCreatedBy" + "cash_interest": { + "$ref": "#/components/schemas/AccountCashInterestEvent" + }, + "crypto_status_from": { + "description": "account crypto_status changed from", + "type": "string" + }, + "crypto_status_to": { + "description": "account crypto_status changed to", + "type": "string" }, "event_id": { - "description": "Lexically sortable, monotonically increasing character array", + "description": "Monotonically increasing 64-bit integer not available to new partners, and for backward compatibility purposes only; use `event_ulid` as the stable identifier where possible\n", + "type": "integer" + }, + "event_ulid": { + "description": "lexically sortable, monotonically increasing character array", "format": "ulid", "type": "string" }, - "note": { - "description": "Free text form description of the admin action", + "fpsl": { + "$ref": "#/components/schemas/AccountFPSLEvent" + }, + "kyc_results": { + "$ref": "#/components/schemas/KYCResults" + }, + "options": { + "$ref": "#/components/schemas/OptionsApprovalEvent" + }, + "reason": { + "deprecated": true, + "minLength": 1, "type": "string" }, - "replaces_event_id": { - "description": "Id of the replaced event (optional)", - "format": "ulid", + "status_from": { + "description": "The account status before the change", + "example": "APPROVED", "type": "string" }, - "type": { - "$ref": "#/components/schemas/AdminActionType" + "status_to": { + "description": "The account status after the change", + "example": "ACTIVE", + "type": "string" }, - "visibility": { - "$ref": "#/components/schemas/AdminActionVisibility" + "trading_blocked": { + "description": "If true the account cannot trade going forward, if false, the ban has been lifed", + "type": "boolean" } }, "required": [ - "event_id", + "account_id", "at", - "belongs_to", - "created_by", - "type", - "category", - "visibility", - "note", - "correspondent" + "event_ulid" ], - "title": "AdminActionEvent", + "title": "AccountStatusEvent", "type": "object" }, - "AdminActionLegacyNote": { - "allOf": [ - { - "$ref": "#/components/schemas/AdminActionEventGeneral" - }, - { - "properties": { - "context": { - "description": "Variable schema type which depends on the type", - "type": "object" - } - }, - "required": [ - "context" - ], - "title": "AdminActionContextLegacyNote", - "type": "object" - } + "AccountSubType": { + "description": "IRA Account only\n\nPossible values are:\n\n- traditional\n- roth", + "enum": [ + "traditional", + "roth" ], - "description": "Represents structure of a LegacyNote type AdminAction", - "x-examples": { - "example-1": { - "at": "2023-03-06T16:38:01Z", - "belongs_to": { - "kind": "account", - "value": "0bbf1dd7-4235-4eca-8b1a-0db63572c735" - }, - "category": "other", - "context": null, - "correspondent": "LPCA", - "created_by": { - "kind": "admin", - "value": "19455a3c-595f-457f-97b3-64a2b5aeae96" - }, - "event_id": "01GTVS4FVS2KJDTPYH2WM6NAXF", - "note": "Performed action: Positions split AMZN (long): 1.011266402\n", - "type": "legacy_note_admin_event", - "visibility": "internal" - } - } + "example": "traditional", + "title": "AccountSubType", + "type": "string" }, - "AdminActionLiquidation": { - "allOf": [ - { - "$ref": "#/components/schemas/AdminActionEventGeneral" + "AccountTradingLimit": { + "properties": { + "available": { + "description": "The remaining net buying limit that can be used for trading.", + "format": "decimal", + "type": "string" + }, + "daily_net_limit": { + "description": "The net buying limit that can be reached before further trading activity is restricted. Please reach out to learn more about how this limit is determined.", + "format": "decimal", + "type": "string" + }, + "held": { + "description": "The limit that is currently being held for open orders", + "format": "decimal", + "type": "string" + }, + "swap_rate": { + "description": "The swap rate is applicable for Local Currency Trading (LCT) accounts", + "format": "decimal", + "type": "string" }, + "usd": { + "$ref": "#/components/schemas/USDAccountTradingLimit" + }, + "used": { + "description": "The real time net value of cash inflows (buy trades, etc.) with cash outflows (sell trades, etc). This will be dynamic throughout the day based on user activity, with executed orders being reset at the start of the next trading day.", + "format": "decimal", + "type": "string" + } + }, + "title": "AccountTradingLimit", + "type": "object" + }, + "AccountType": { + "description": "The account type returned for the account.", + "enum": [ + "trading", + "custodial", + "donor_advised", + "ira", + "trust", + "omnibus_non_disclosed", + "omnibus_sub", + "hsa", + "joint" + ], + "example": "trading", + "title": "AccountType", + "type": "string" + }, + "AccountUpdateRequest": { + "examples": [ { - "properties": { - "context": { - "description": "Variable schema type which depends on the type", - "properties": { - "available_qty": { - "format": "decimal", - "type": "string" - }, - "error": { - "type": "string" - }, - "reason": { - "type": "string" - }, - "requested_qty": { - "format": "decimal", - "type": "string" - }, - "symbol": { - "type": "string" - } - }, - "type": "object" + "beneficiaries": [ + { + "date_of_birth": "1970-01-01", + "family_name": "Smith", + "given_name": "John", + "middle_name": "P", + "relationship": "spouse", + "share_pct": "100", + "tax_id": "xxx-xx-xxxx", + "tax_id_type": "USA_SSN", + "type": "primary" } + ], + "contact": { + "city": "San Mateo", + "country": "USA", + "email_address": "john.doe@example.com", + "phone_number": "+15556667788", + "postal_code": "94401", + "state": "CA", + "street_address": [ + "20 N San Mateo Dr" + ] }, - "required": [ - "context" + "disclosures": { + "immediate_family_exposed": false, + "is_affiliated_exchange_or_finra": false, + "is_control_person": false, + "is_politically_exposed": false + }, + "enabled_assets": [ + "us_equity", + "crypto" ], - "title": "AdminActionContextLiquidation", - "type": "object" + "identity": { + "country_of_birth": "AUS", + "country_of_citizenship": "AUS", + "country_of_tax_residence": "USA", + "date_of_birth": "1990-01-01", + "family_name": "Doe", + "funding_source": [ + "employment_income" + ], + "given_name": "John", + "tax_id": "666-55-4321", + "tax_id_type": "USA_SSN" + }, + "trusted_contact": { + "email_address": "jane.doe@example.com", + "family_name": "Doe", + "given_name": "Jane" + } } ], - "description": "Represents structure of a Liquidation type AdminAction", - "x-examples": { - "example-1": { - "at": "2023-03-06T16:38:01Z", - "belongs_to": { - "kind": "account", - "value": "0bbf1dd7-4235-4eca-8b1a-0db63572c735" + "properties": { + "agreements": { + "description": "Additional agreements, or new revisions of existing agreements, read and signed by the account holder.", + "items": { + "$ref": "#/components/schemas/Agreement" }, - "category": "accounts", - "context": { - "available_qty": "0.0001", - "error": "", - "reason": "Liquidation due to real time risk", - "requested_qty": "0.0001", - "symbol": "TSLA" + "type": "array" + }, + "allow_instant_ach": { + "description": "If provided, updates whether the account is enabled for Instant ACH by the partner. Omitting the field leaves the current setting unchanged.", + "type": "boolean" + }, + "beneficiaries": { + "items": { + "$ref": "#/components/schemas/Beneficiary" }, - "correspondent": "LPCA", - "created_by": { - "kind": "admin", - "value": "19455a3c-595f-457f-97b3-64a2b5aeae96" + "type": "array" + }, + "cash_interest": { + "$ref": "#/components/schemas/AccountCashInterestPatch" + }, + "contact": { + "$ref": "#/components/schemas/Contact" + }, + "disclosures": { + "$ref": "#/components/schemas/Disclosures" + }, + "enabled_assets": { + "description": "The asset classes enabled on the account. Omit this field to leave the\naccount's enabled assets unchanged. Updates are additive only: the\nsubmitted list must contain every asset class already enabled on the account,\nbecause asset removal is not supported. For example, to enable\ncrypto for an account that currently trades only equities, submit\n`[\"us_equity\", \"crypto\"]` together with the signed crypto agreement.\n`us_option` may appear in this list for options-enabled accounts and\nmust be re-listed on additive updates, but options cannot be enabled\nthrough this endpoint.\n", + "items": { + "$ref": "#/components/schemas/EnabledAssetClass" }, - "event_id": "01GTVS4FVS2KJDTPYH2WM6NAXF", - "note": "Performed action: Position liquidated: Asset: TSLA, Quantity: 0.0001, Reason: Liquidation due to real time risk\n", - "type": "liquidation_admin_event", - "visibility": "external" + "type": "array" + }, + "fpsl": { + "$ref": "#/components/schemas/AccountFPSLPatch" + }, + "identity": { + "$ref": "#/components/schemas/Identity" + }, + "primary_account_holder_id": { + "description": "The UUID of the primary account holder. This field is immutable after the account is created.\n\n- Omitting the field, or supplying the value already associated with the account, is a no-op.\n- Supplying a value different from the current primary account holder returns HTTP 403.\n", + "format": "uuid", + "type": "string" + }, + "trusted_contact": { + "$ref": "#/components/schemas/TrustedContact" } - } + }, + "type": "object" }, - "AdminActionTransactionCancel": { + "Activity": { "allOf": [ - { - "$ref": "#/components/schemas/AdminActionEventGeneral" - }, { "properties": { - "context": { - "description": "Variable schema type which depends on the type", - "properties": { - "entry_type": { - "type": "string" - }, - "external_id": { - "type": "string" - }, - "transaction_id": { - "format": "uuid", - "type": "string" - } - }, - "type": "object" + "account_id": { + "example": "c8f1ef5d-edc0-4f23-9ee4-378f19cb92a4", + "format": "uuid", + "type": "string" + }, + "activity_type": { + "$ref": "#/components/schemas/ActivityType" + }, + "id": { + "example": "20220208125959696::88b5f678-fef5-447b-af15-f21e367e6d8c", + "type": "string" } }, - "required": [ - "context" - ], - "title": "AdminActionContextTransactionCancel", "type": "object" + }, + { + "oneOf": [ + { + "$ref": "#/components/schemas/TradeActivity" + }, + { + "$ref": "#/components/schemas/NonTradeActivity" + } + ] } ], - "description": "Represents structure of a TransactionCancel type AdminAction", - "x-examples": { - "example-1": { - "at": "2023-03-06T16:38:01Z", - "belongs_to": { - "kind": "account", - "value": "0bbf1dd7-4235-4eca-8b1a-0db63572c735" - }, - "category": "accounts", - "context": { - "entry_type": "JNLC", - "external_id": "d5eede2c-1c08-45df-9800-87ad5eefc11f", - "transaction_id": "fdf18af2-142b-409e-8aad-d1731e276af0" - }, - "correspondent": "LPCA", - "created_by": { - "kind": "admin", - "value": "19455a3c-595f-457f-97b3-64a2b5aeae96" + "description": "Base for activity types", + "required": [ + "id", + "activity_type" + ], + "title": "Activity" + }, + "ActivityEventV2": { + "allOf": [ + { + "$ref": "#/components/schemas/ActivityEventV2CommonFields" + }, + { + "properties": { + "details": { + "oneOf": [ + { + "$ref": "#/components/schemas/ActivityV2DetailTRD" + }, + { + "$ref": "#/components/schemas/ActivityV2DetailNTA" + } + ] + } }, - "event_id": "01GTVS4FVS2KJDTPYH2WM6NAXF", - "note": "\"Transaction fdf18af2-142b-409e-8aad-d1731e276af0 cancelled for LPCA-12345678\n", - "type": "transaction_cancel_admin_event", - "visibility": "external" + "required": [ + "details" + ], + "type": "object" } - } - }, - "AdminActionType": { - "description": "Type of the Admin Action", - "enum": [ - "liquidation_admin_event", - "legacy_note_admin_event", - "transaction_cancel_admin_event" - ] - }, - "AdminActionVisibility": { - "description": "Visibility of the Admin Action", - "enum": [ - "internal", - "external", - "correspondent_only" - ] + ], + "description": "Represents an account activity, sent over the Event Streaming API.", + "title": "ActivityEventV2", + "type": "object" }, - "AdminConfigurations": { - "description": "These configurations show account properties that are overriden either by Alpaca Broker Operations or an automated process.\n\nThese values cannot be modified by the Broker Partners.\n\nThis schema represents the current, effective value of each configuration (e.g. as returned by the get trading account endpoint). For the shape of the payloads broadcast by the **events** streaming API when these values change, see [AdminConfigurationsEvent](#/components/schemas/AdminConfigurationsEvent).", + "ActivityEventV2CommonFields": { + "description": "Represents the common fields for all Activity V2 Events", "properties": { - "acct_daily_transfer_limit": { - "description": "Override the correspondent level daily transfer limits", - "format": "decimal", + "account_id": { + "description": "Account UUID", + "format": "uuid", "type": "string" }, - "allow_instant_ach": { - "description": "If true, the account is allowed to perform instant ACH", - "type": "boolean" + "activity_subtype": { + "description": "Sub category for activity type, if any", + "type": "string" }, - "disable_algodash_access": { - "description": "If true, the account is allowed to access algo dash", - "type": "boolean" + "activity_type": { + "description": "The type of the activity, which can be trade or any of the non trade activities", + "type": "string" }, - "disable_api_key": { - "description": "If true, the account's API key will be disabled", - "type": "boolean" + "at": { + "description": "Timestamp of event", + "format": "date-time", + "minLength": 1, + "type": "string" }, - "disable_crypto": { - "description": "If true, the account is not allowed to trade cryptos", - "type": "boolean" + "currency": { + "description": "Currency code in ISO format", + "type": "string" }, - "disable_day_trading": { - "description": "If true, the account is not allowed to day trade (e.g. buy and sell the same security on the same day)", - "type": "boolean" + "event_id": { + "description": "Lexically sortable, monotonically increasing character string", + "format": "ulid", + "type": "string" }, - "disable_fractional": { - "description": "If true, the account cannot create orders for fractional share positions", - "type": "boolean" + "executed_at": { + "description": "Execution time for the activity event", + "format": "date-time", + "minLength": 1, + "type": "string" }, - "disable_shorting": { - "description": "If true the account is not allowed to create short position orders", - "type": "boolean" + "net_amount": { + "description": "The net amount of money (positive or negative) associated with the activity", + "format": "decimal", + "type": "string" }, - "incoming_transfers_blocked": { - "description": "If true, incoming transfers to this account are rejected", - "type": "boolean" + "previous_id": { + "description": "Previous ID is presented if this activity corrects or cancels a previous trade or non trade activity. It contains execution_id or trns_id respectively", + "format": "uuid", + "type": "string" }, - "max_margin_multiplier": { - "description": "The max margin multiplier set by admin for this account. Can be \"1\", \"2\", or \"4\".", + "price": { + "description": "The price of the security involved with the activity", + "format": "decimal", "type": "string" }, - "max_options_trading_level": { - "description": "The max options trading level set by admin for this account.\n0=disabled, 1=Covered Call/Cash-Secured Put, 2=Long Call/Put, 3=Spreads/Straddles.\n", - "enum": [ - 0, - 1, - 2, - 3 - ], - "type": "integer" + "qty": { + "description": "The quantity of the security involved with the activity", + "format": "decimal", + "type": "string" }, - "outgoing_transfers_blocked": { - "description": "If true, outgoing transfers from this account are rejected", - "type": "boolean" + "ref_id": { + "description": "The unique identifier for the activity. For trades, the execution_id is used, for other activities the trns_id is used.", + "format": "uuid", + "type": "string" }, - "restrict_to_liquidation_reasons": { - "$ref": "#/components/schemas/RestrictToLiquidationReasons" + "settle_date": { + "description": "Date when the activity settled", + "format": "date", + "minLength": 1, + "type": "string" + }, + "status": { + "description": "Status of the activity", + "type": "string" + }, + "swap_fee_bps": { + "description": "Currency conversion fee rate base-point in case of local currency activity", + "format": "decimal", + "type": "string" + }, + "swap_rate": { + "description": "Conversion rate for local currency activities", + "format": "decimal", + "type": "string" } }, - "title": "AdminConfigurations", + "required": [ + "account_id", + "at", + "event_id", + "activity_type", + "executed_at", + "status", + "settle_date", + "currency", + "ref_id" + ], "type": "object" }, - "AdminConfigurationsEvent": { - "description": "Represents a change to admin configurations, as broadcast over the **events** streaming API. Only fields whose value changed are included; unrelated update events will not include a flag that did not change.\n\nDepending on the type of the Admin Configuration, the sent event will behave differently. For bool flags we are only sending the new value.\n\nFor example, the following payload means that the disable_shorting flag was set to true:\n\n```\n{\n \"disable_shorting\": true\n}\n```\n\nFor other data types, we are embedding the old and new values into the payload. For example changing the max_margin_multiplier from 4 to 1 will yield this payload:\n\n```\n{\n \"max_margin_multiplier\": {\n \"from\": 4,\n \"to\": 1\n }\n}\n```\n\nIntroducing an override value from the default will yield a null value as `from`. For example restricting the max_margin_multiplier to 1 from default will yield the following payload:\n\n```\n{\n \"max_margin_multiplier\": {\n \"from\": null,\n \"to\": 1\n }\n}\n```", - "properties": { - "acct_daily_transfer_limit": { - "description": "The correspondent level daily transfer limit override was changed", - "properties": { - "from": { - "description": "Old value of the daily transfer limit", - "type": "string" - }, - "to": { - "description": "New value of the daily transfer limit", - "type": "string" - } - }, - "type": "object" + "ActivitySubType": { + "description": "Represents a more specific classification to the `activity_type`.\nThis field is optional and may not always be populated, depending on the activity type and the available data.\nEach `activity_type` has a set of valid `activity_sub_type` values.\n\nFull mapping of `activity_type` to `activity_sub_type`:\n\n- **CGD**: Capital Gains Distribution activity sub-types:\n - **LTCG**: Long-Term Capital Gain\n - **STCG**: Short-Term Capital Gain\n\n- **DIV**: Dividend activity sub-types:\n - **CDIV**: Cash Dividend\n - **ROC**: Return of Capital\n - **SDIV**: Stock Dividend\n - **SPD**: Substitute Payment In Lieu Of Dividend\n\n- **FEE**: Fee-related activity sub-types:\n - **REG**: Regulatory Fee\n - **TAF**: Trading Activity Fee\n - **LCT**: Local Currency Trading Fee\n - **ORF**: Options Regulatory Fee\n - **OCC**: Options Clearing Corporation Fee\n - **NRC**: Non-Retail Commission Fee\n - **NRV**: Non-Retail Venue Fee\n - **COM**: Commission\n - **CAT**: Consolidated Audit Trail Fee\n\n- **INT**: Interest-related activity sub-types:\n - **MGN**: Margin Interest\n - **CDT**: Credit Interest\n - **SWP**: Sweep Interest\n - **QII**: Qualified Interest\n - **FI**: Fixed Income Interest (coupon or accrued)\n\n- **MA**: Merger and Acquisition activity sub-types:\n - **CMA**: Cash Merger\n - **SMA**: Stock Merger\n - **SCMA**: Stock & Cash Merger\n\n- **NC**: Name Change activity sub types\n - **SNC**: Symbol Name Change\n - **CNC**: CUSIP Name Change\n - **SCNC**: Symbol & CUSIP Name Change\n\n- **OPCA**: Option Corporate Action activity sub-types:\n - **DIV.CDIV**: Cash Dividend\n - **DIV.ROC**: Return of Capital\n - **DIV.SDIV**: Stock Dividend\n - **MA.CMA**: Cash Merger\n - **MA.SMA**: Stock Merger\n - **MA.SCMA**: Stock & Cash Merger\n - **NC.CNC**: CUSIP Name Change\n - **NC.SNC**: Symbol Name Change\n - **NC.SCNC**: Symbol & CUSIP Name Change\n - **SPIN**: Spin-off\n - **SPLIT.FSPLIT**: Forward Stock Split\n - **SPLIT.RSPLIT**: Reverse Stock Split\n - **SPLIT.USPLIT**: Unit Split\n\n- **REO**: Reorganization activity sub-types:\n - **REOS**: Stock only (1 or more stock legs)\n - **REOC**: Cash only (one cash leg)\n - **REOSC**: Stock and cash\n\n- **REORG**: Activity sub-types:\n - **WRM**: Worthless Removal\n\n- **SPLIT**: Stock Split activity sub-types:\n - **FSPLIT**: Forward Stock Split\n - **RSPLIT**: Reverse Stock Split\n - **USPLIT**: Unit Split\n\n- **VOF**: Voluntary Offering activity sub-types:\n - **VTND**: Tender Offer\n - **VWRT**: Warrant Exercise\n - **VRGT**: Rights Offer\n - **VEXH**: Exchange Offer\n\n- **WH**: Withholding activity sub-types:\n - **SWH**: State Withholding\n - **FWH**: Federal Withholding\n - **SLWH**: Sales Withholding", + "title": "ActivitySubType", + "type": "string" + }, + "ActivityType": { + "description": "Represents the various kinds of activity.\n\nTradeActivity's will always have the type `FILL`\n\n- **FILL**\n Order Fills (Partial/Full)\n- **ACATC**\n ACATS IN/OUT (Cash)\n- **ACATS**\n ACATS IN/OUT (Securities)\n- **CGD**\n Capital Gains Distribution\n- **CIL**\n Cash in Lieu of Stock\n- **CSD**\n Cash Disbursement (+)\n- **CSW**\n Cash Withdrawable\n- **DIV**\n Dividend\n- **DIVCGL**\n Dividend (Capital Gains Long Term)\n- **DIVCGS**\n Dividend (Capital Gains Short Term)\n- **DIVNRA**\n Dividend Adjusted (NRA Withheld)\n- **DIVROC**\n Dividend Return of Capital\n- **DIVTXEX**\n Dividend (Tax Exempt)\n- **FEE**\n REG and TAF Fees\n- **INT**\n Interest (Credit/Margin)\n- **JNLC**\n Journal Entry (Cash)\n- **JNLS**\n Journal Entry (Stock)\n- **OPASN**\n Option Assignment\n- **OPCA**\n Option Corporate Action\n- **OPCSH**\n Option cash deliverable for non-standard contracts\n- **OPEXC**\n Option Exercise\n- **OPEXP**\n Option Expiration\n- **OPTRD**\n Option Trade\n- **MA**\n Merger/Acquisition\n- **PTC**\n Pass Thru Change\n- **REO**\n Reorganization\n- **REORG**\n Worthless removal CA\n- **SPIN**\n Stock Spinoff\n- **SPLIT**\n Stock Split\n- **FOPT**\n Free of Payment Transfers\n- **OCT**\n On Chain Transactions (blockchain deposits/withdrawals)", + "enum": [ + "FILL", + "ACATC", + "ACATS", + "CGD", + "CIL", + "CSD", + "CSW", + "DIV", + "DIVCGL", + "DIVCGS", + "DIVNRA", + "DIVROC", + "DIVTXEX", + "FEE", + "INT", + "JNLC", + "JNLS", + "MA", + "OPASN", + "OPCA", + "OPCSH", + "OPEXC", + "OPEXP", + "OPTRD", + "PTC", + "REO", + "REORG", + "SPIN", + "SPLIT", + "FOPT", + "OCT" + ], + "title": "ActivityType", + "type": "string" + }, + "ActivityV2DetailNTA": { + "oneOf": [ + { + "$ref": "#/components/schemas/DIVSPDActivityV2" }, - "allow_instant_ach": { - "description": "If true, the account is allowed to perform instant ACH", - "type": "boolean" + { + "$ref": "#/components/schemas/CDIVActivityV2" }, - "disable_algodash_access": { - "description": "If true, the account is allowed to access algo dash", - "type": "boolean" + { + "$ref": "#/components/schemas/SDIVActivityV2" }, - "disable_api_key": { - "description": "If true, the account's API key will be disabled", - "type": "boolean" + { + "$ref": "#/components/schemas/CGDActivityV2" }, - "disable_crypto": { - "description": "If true, the account is not allowed to trade cryptos", - "type": "boolean" + { + "$ref": "#/components/schemas/ForwardSplitActivityV2" }, - "disable_day_trading": { - "description": "If true, the account is not allowed to day trade (e.g. buy and sell the same security on the same day)", - "type": "boolean" + { + "$ref": "#/components/schemas/ReverseSplitActivityV2" }, - "disable_fractional": { - "description": "If true, the account cannot create orders for fractional share positions", - "type": "boolean" + { + "$ref": "#/components/schemas/UnitSplitActivityV2" }, - "disable_shorting": { - "description": "If true the account is not allowed to create short position orders", - "type": "boolean" + { + "$ref": "#/components/schemas/SpinoffActivityV2" }, - "incoming_transfers_blocked": { - "description": "If true, incoming transfers to this account are rejected", - "type": "boolean" + { + "$ref": "#/components/schemas/MAActivityV2" }, - "max_margin_multiplier": { - "description": "Max margin multiplier was changed by admin to this value", - "properties": { - "from": { - "description": "Old value of margin multiplier", - "type": "string" - }, - "to": { - "description": "New value of margin multiplier", - "type": "string" - } - }, - "type": "object" + { + "$ref": "#/components/schemas/NCActivityV2" }, - "max_options_trading_level": { - "description": "Max options trading level was changed by admin to this value. It can be 0, 1, 2, or 3.", - "properties": { - "from": { - "description": "Old value of max options trading level", - "type": "string" - }, - "to": { - "description": "New value of max options trading level", - "type": "string" - } - }, - "type": "object" + { + "$ref": "#/components/schemas/FixedIncomeRedemptionActivityV2" }, - "outgoing_transfers_blocked": { - "description": "If true, outgoing transfers from this account are rejected", - "type": "boolean" + { + "$ref": "#/components/schemas/FixedIncomeInterestActivityV2" }, - "restrict_to_liquidation_reasons": { - "$ref": "#/components/schemas/RestrictToLiquidationReasons" + { + "$ref": "#/components/schemas/REOActivityV2" + }, + { + "$ref": "#/components/schemas/RightsDistributionActivityV2" + }, + { + "$ref": "#/components/schemas/RightsSubscriptionElectionActivityV2" + }, + { + "$ref": "#/components/schemas/TenderOfferActivityV2" + }, + { + "$ref": "#/components/schemas/ExchangeOfferActivityV2" + }, + { + "$ref": "#/components/schemas/WarrantExerciseElectionActivityV2" + }, + { + "$ref": "#/components/schemas/WRMActivityV2" + }, + { + "$ref": "#/components/schemas/OpcaCDIVActivityV2" + }, + { + "$ref": "#/components/schemas/OpcaSDIVActivityV2" + }, + { + "$ref": "#/components/schemas/OpcaMAActivityV2" + }, + { + "$ref": "#/components/schemas/OpcaNCActivityV2" + }, + { + "$ref": "#/components/schemas/OpcaSPINActivityV2" + }, + { + "$ref": "#/components/schemas/OpcaFSPLITActivityV2" + }, + { + "$ref": "#/components/schemas/OpcaRSPLITActivityV2" + }, + { + "$ref": "#/components/schemas/OpcaUSPLITActivityV2" + }, + { + "$ref": "#/components/schemas/OPASNActivityV2" + }, + { + "$ref": "#/components/schemas/OPEXCActivityV2" + }, + { + "$ref": "#/components/schemas/OPEXPActivityV2" + }, + { + "$ref": "#/components/schemas/OPTRDActivityV2" + }, + { + "$ref": "#/components/schemas/OPCSHActivityV2" + }, + { + "$ref": "#/components/schemas/AcatsActivityV2" + }, + { + "$ref": "#/components/schemas/AcatcActivityV2" + }, + { + "$ref": "#/components/schemas/FOPTActivityV2" + }, + { + "$ref": "#/components/schemas/DIVNRAActivityV2" + }, + { + "$ref": "#/components/schemas/DIVWHActivityV2" + }, + { + "$ref": "#/components/schemas/JNLSActivityV2" + }, + { + "$ref": "#/components/schemas/JNLCActivityV2" + }, + { + "$ref": "#/components/schemas/CSWActivityV2" + }, + { + "$ref": "#/components/schemas/FEEActivityV2" + }, + { + "$ref": "#/components/schemas/MEMActivityV2" + }, + { + "$ref": "#/components/schemas/OCTActivityV2" } - }, - "title": "AdminConfigurationsEvent", + ], "type": "object" }, - "AggregatePositionResponse": { + "ActivityV2DetailTRD": { + "description": "Activity details for a fill or partial_fill event", "properties": { - "asset_type": { - "$ref": "#/components/schemas/AssetClass" - }, - "closing_price": { - "description": "EOD asset price per share at session close", - "format": "decimal", + "asset_id": { + "description": "Asset ID (For options this represents the option contract ID)", + "format": "uuid", "type": "string" }, - "cusip": { - "description": "Cusip (9 digits, can start with 0's)", - "format": "decimal", + "client_order_id": { + "description": "Order ID provided by the customer", "type": "string" }, - "long_market_value": { - "description": "Aggregate notional dollar amount of the partner's long positions", + "commission": { + "description": "Commission to collect from the account holder", "format": "decimal", "type": "string" }, - "long_qty": { - "description": "Aggregate number of shares that the partner is long", + "cum_qty": { + "description": "Total filled quantity on the order", "format": "decimal", "type": "string" }, - "num_accounts": { - "description": "Number of accounts that have a position in this asset (either long or short)", - "format": "decimal", - "type": "integer" + "cusip": { + "description": "CUSIP of the security involved with the activity", + "type": "string" }, - "short_market_value": { - "description": "Aggregate notional dollar amount of the partner's short positions", - "format": "decimal", + "execution_type": { + "description": "The execution type", + "enum": [ + "fill", + "trade_correct", + "trade_bust" + ], "type": "string" }, - "short_qty": { - "description": "Aggregate number of shares that the partner is short", + "leaves_qty": { + "description": "Unfilled quantity on the order, when order is filled value could be 0", "format": "decimal", "type": "string" }, - "symbol": { - "description": "Symbol of asset", + "order_id": { + "description": "Order ID generated by Alpaca", + "format": "uuid", "type": "string" - } - }, - "title": "", - "type": "object", - "x-examples": { - "Example 1": { - "asset_type": "us_equity", - "closing_price": "148.4700", - "cusip": "037833100", - "long_market_value": "148.4700", - "long_qty": "1", - "num_accounts": "1", - "short_market_value": "0", - "short_qty": "0", - "symbol": "AAPL" - } - } - }, - "Agreement": { - "properties": { - "agreement": { - "$ref": "#/components/schemas/AgreementType" }, - "ip_address": { - "description": "The ip_address the signed agreements were sent from by the user.", - "example": "185.13.21.99", - "format": "ipv4", + "order_status": { + "description": "Identifies the current status of the order", "type": "string" }, - "revision": { - "description": "The agreement revision.\nThe format is XX.YYYY.MM where XX is an incrementing revision number, YYYY is the year and MM is the month.\nIf the revision is not specified in a POST or PATCH request, the active revision will be used, which will align with the [Alpaca Documents Library](https://alpaca.markets/disclosures).\n", + "side": { + "description": "Represents what side of the transaction an order was on", + "enum": [ + "buy", + "sell" + ], "type": "string" }, - "signed_at": { - "description": "The timestamp the agreement was signed.", - "example": "2019-09-11T18:09:33Z", - "format": "date-time", + "symbol": { + "description": "The symbol of the security involved with the activity", "type": "string" } }, "required": [ - "agreement", - "signed_at", - "ip_address" + "order_id", + "side", + "symbol", + "asset_id", + "leaves_qty", + "cum_qty", + "order_status", + "execution_type" ], "type": "object" }, - "AgreementType": { - "description": "- margin_agreement: Alpaca Margin Agreement\n- account_agreement: Alpaca Account Agreement\n- customer_agreement: Alpaca Customer Agreement\n- crypto_agreement: Alpaca Crypto agreement\n- options_agreement: Alpaca Option agreement\n- custodial_customer_agreement: Alpaca Custodial Customer agreement\n", - "enum": [ - "margin_agreement", - "account_agreement", - "customer_agreement", - "crypto_agreement", - "options_agreement" - ], - "example": "customer_agreement", - "title": "AgreementType", - "type": "string" - }, - "Announcement": { - "description": "The announcements endpoint contains public information on previous and upcoming dividends, mergers, spinoffs, and stock splits.\n\nAnnouncement data is made available through the API as soon as it is ingested by Alpaca, which is typically the following trading day after the declaration date. This provides insight into future account stock position and cash balance changes that will take effect on an announcement's payable date. Additionally, viewing previous announcement details can improve bookkeeping and reconciling previous account cash and position changes.", + "AdminActionBelongsTo": { + "description": "Represents structure of an Identifier for all AdminAction type", "properties": { - "ca_sub_type": { - "$ref": "#/components/schemas/AnnouncementCASubType" - }, - "ca_type": { - "$ref": "#/components/schemas/AnnouncementCAType" - }, - "cash": { - "description": "The amount of cash to be paid per share held by an account on the record date.", - "minLength": 1, - "nullable": true, - "type": "string" - }, - "corporate_action_id": { - "description": "ID that remains consistent across all announcements for the same corporate action. Unlike 'id', this can be used to connect multiple announcements to see how the terms have changed throughout the lifecycle of the corporate action event.", - "minLength": 1, - "type": "string" - }, - "declaration_date": { - "description": "Date the corporate action or subsequent terms update was announced.", - "minLength": 1, + "id_reference": { "type": "string" }, - "ex_date": { - "description": "The first date that purchasing a security will not result in a corporate action entitlement.", - "minLength": 1, - "nullable": true, - "type": "string" - }, - "id": { - "description": "ID that is specific to a single announcement.", - "minLength": 1, - "type": "string" - }, - "initiating_original_cusip": { - "description": "CUSIP of the company initiating the announcement.", - "minLength": 1, - "type": "string" - }, - "initiating_symbol": { - "description": "Symbol of the company initiating the announcement.", - "minLength": 1, - "type": "string" - }, - "new_rate": { - "description": "The numerator to determine any quantity change ratios in positions.", - "minLength": 1, - "nullable": true, - "type": "string" - }, - "old_rate": { - "description": "The denominator to determine any quantity change ratios in positions.", - "minLength": 1, - "nullable": true, - "type": "string" - }, - "payable_date": { - "description": "The date the announcement will take effect. On this date, account stock and cash balances are expected to be processed accordingly.", - "minLength": 1, - "type": "string" - }, - "record_date": { - "description": "The date an account must hold a settled position in the security in order to receive the corporate action entitlement.", - "minLength": 1, - "nullable": true, - "type": "string" - }, - "target_original_cusip": { - "description": "CUSIP of the child company involved in the announcement.", - "minLength": 1, - "nullable": true, - "type": "string" - }, - "target_symbol": { - "description": "Symbol of the child company involved in the announcement.", - "minLength": 1, - "nullable": true, - "type": "string" + "kind": { + "$ref": "#/components/schemas/AdminActionBelongsToKind" } }, - "required": [ - "id", - "corporate_action_id", - "ca_type", - "ca_sub_type", - "initiating_symbol", - "initiating_original_cusip", - "target_symbol", - "target_original_cusip", - "declaration_date", - "ex_date", - "record_date", - "payable_date", - "cash", - "old_rate", - "new_rate" - ], - "title": "Announcement", - "type": "object", - "x-examples": { - "example-1": { - "ca_sub_type": "cash", - "ca_type": "dividend", - "cash": "0", - "corporate_action_id": "78467X109_AA22", - "declaration_date": "2021-12-19", - "ex_date": "2022-01-21", - "id": "bebc5ece-34be-47e9-b944-687e69a102be", - "initiating_original_cusip": "252787106", - "initiating_symbol": "DIA", - "new_rate": "1", - "old_rate": "1", - "payable_date": "2022-02-14", - "record_date": "2022-01-24", - "target_original_cusip": "252787106", - "target_symbol": "DIA" - } - } + "title": "AdminActionBelongsTo", + "type": "object" }, - "AnnouncementCASubType": { - "description": "Announcements have both a type and a subtype to categorize them. This model represents the lowever level abstract \"sub types\" of Announcement. Please see the AnnouncementCAType model for higher level descriptions of the possible types\n\nPossible values are:\n\n- from the `dividend` type:\n - **cash**\n\n A cash payment based on the number of shares the account holds on the record date.\n - **stock**\n\n A stock payment based on the number of shares the account holds on the record date.\n\n- from the `merger` type:\n - **merger_update**\n\n An update to the terms of an upcoming merger. This can happen any number of times before the merger is completed and can be tracked by using the id parameter.\n\n - **merger_completion**\n\n A final update in the terms of the merger in which the intiating_symbol will acquire the target_symbol. Any previous terms updates for this announcement will have the same id value.\n\n- from the `split` type:\n - **stock_split**\n\n An increase in the number of shares outstanding with a decrease in the dollar value of each share. The new_rate and old_rate parameters will be returned in order to derive the ratio of the split\n - **until_split**\n\n An increase in the number of shares outstanding with a decrease in the dollar value of each share. The new_rate and old_rate parameters will be returned in order to derive the ratio of the split.\n - **reverse_split**\n\n A decrease in the number of shares outstanding with an increase in the dollar value of each share. The new_rate and old_rate parameters will be returned in order to derive the ratio of the spli\n - **recapitalization**\n\n A stock recapitalization, typically used by a company to adjust debt and equity ratios.\n\n- from the `spinoff` type:\n - **spinoff**\n\n A disbursement of a newly tradable security when the intiating_symbol creates the target_symbol.", + "AdminActionBelongsToKind": { "enum": [ - "cash", - "stock", - "merger_update", - "merger_completion", - "stock_split", - "until_split", - "reverse_split", - "recapitalization", - "spinoff" - ], - "title": "", - "type": "string" + "account", + "owner", + "correspondent" + ] }, - "AnnouncementCAType": { - "description": "Announcements have both a type and a subtype to categorize them. This model represents the higher level abstract \"types\" of Announcement. Please see the AnnouncementCASubType model for finer grain descriptions of the subtypes\n\nPossible values are:\n- dividend\n can have `cash` and `stock` subtypes\n- merger\n has `merger_update` and `merger_completion` sub types\n- split\n has `stock_split`, `until_split`, `reverse_split`, and `recapitalization` sub types\n- spinoff\n currently has only the `spinoff` subtype and thus is just this higher level category for now. A disbursement of a newly tradable security when the intiating_symbol creates the target_symbol.", + "AdminActionCategory": { + "description": "Category of the Admin Action", "enum": [ - "dividend", - "merger", - "split", - "spinoff" - ], - "example": "dividend", - "title": "", - "type": "string" + "accounts", + "order", + "other" + ] }, - "Asset": { - "description": "Assets are sorted by asset class, exchange and symbol. Some assets are not tradable with Alpaca. These assets will be marked with the flag tradable=false", + "AdminActionCreatedBy": { + "description": "Represents structure of an Creator's Identifier for all AdminAction type\n", "properties": { - "attributes": { - "description": "Unique characteristics of the asset. Supported values:\n- `ptp_no_exception`: Asset is a Publicly Traded Partnership (PTP) without a qualified notice; non-U.S. customers may incur 10% withholding on gross proceeds as per IRS guidance, and are blocked from being purchased by default.\n- `ptp_with_exception`: Users can open positions in these PTPs without general restrictions.\n- `ipo`: Accepting limit orders only before the stock begins trading on the secondary market.\n- `has_options`: The underlying equity has listed options available on the platform. Note: if the equity had inactive/expired contracts in the past, this will still show up.\n- `options_late_close`: Indicates the underlying asset's options contracts close at 4:15pm ET instead of the standard 4:00pm ET.\n- `fractional_eh_enabled`: Indicates the asset accepts fractional orders during extended hours sessions (pre-market, post-market, and overnight if enabled).\n- `overnight_tradable`: Asset is eligible for overnight (24x5) trading in supported venues on the platform.\n- `overnight_halted`: Asset is eligible for overnight trading but is currently halted/blocked for overnight sessions due to risk, corporate action, compliance, or venue constraints.", - "example": [ - "ptp_no_exception", - "ipo" - ], - "items": { - "enum": [ - "ptp_no_exception", - "ptp_with_exception", - "ipo", - "has_options", - "options_late_close", - "fractional_eh_enabled", - "overnight_tradable", - "overnight_halted" - ], - "type": "string" - }, - "type": "array" - }, - "borrow_status": { - "description": "Borrow status for US equity assets. This field is omitted for non-US-equity assets.", - "enum": [ - "easy_to_borrow", - "hard_to_borrow" - ], - "example": "easy_to_borrow", - "type": "string" - }, - "class": { - "$ref": "#/components/schemas/AssetClass" - }, - "easy_to_borrow": { - "deprecated": true, - "description": "**deprecated**: Please use borrow_status instead.\nAsset is easy-to-borrow or not (filtering for easy_to_borrow = True is the best way to check whether the name is currently available to short at Alpaca).\n", - "example": true, - "type": "boolean", - "x-deprecation": { - "reason": "Use borrow_status instead.", - "since": "2026-06-22", - "sunset": "2026-09-22" - } - }, - "exchange": { - "$ref": "#/components/schemas/Exchange" - }, - "fractionable": { - "description": "Asset is fractionable or not", - "example": true, - "type": "boolean" - }, - "id": { - "description": "Asset ID", - "example": "904837e3-3b76-47ec-b432-046db621571b", - "format": "uuid", + "id_reference": { "type": "string" }, - "maintenance_margin_requirement": { - "deprecated": true, - "description": "**deprecated**: Please use margin_requirement_long or margin_requirement_short instead. Note that these fields are of type string.\nShows the margin requirement percentage for the asset (equities only).\n", - "type": "number" - }, - "margin_requirement_long": { - "description": "The margin requirement percentage for the asset's long positions (equities only).", + "kind": { + "$ref": "#/components/schemas/AdminActionCreatedByKind" + } + }, + "title": "AdminActionCreatedBy", + "type": "object" + }, + "AdminActionCreatedByKind": { + "enum": [ + "admin" + ] + }, + "AdminActionEventGeneral": { + "description": "Represents general fields for all AdminAction type", + "properties": { + "at": { + "description": "Timestamp of event", + "format": "date-time", "type": "string" }, - "margin_requirement_short": { - "description": "The margin requirement percentage for the asset's short positions (equities only).", - "type": "string" + "belongs_to": { + "$ref": "#/components/schemas/AdminActionBelongsTo" }, - "marginable": { - "description": "Asset is marginable or not", - "example": true, - "type": "boolean" + "category": { + "$ref": "#/components/schemas/AdminActionCategory" }, - "min_order_size": { - "description": "Minimum order size. Field available for crypto only.", + "correspondent": { + "description": "Related correspondent", "type": "string" }, - "min_trade_increment": { - "description": "Amount a trade quantity can be incremented by. Field available for crypto only.", - "type": "string" + "created_by": { + "$ref": "#/components/schemas/AdminActionCreatedBy" }, - "name": { - "description": "The official name of the asset", - "example": "Apple Inc. Common Stock", + "event_id": { + "description": "Lexically sortable, monotonically increasing character array", + "format": "ulid", "type": "string" }, - "price_increment": { - "description": "Amount the price can be incremented by. Field available for crypto only.", + "note": { + "description": "Free text form description of the admin action", "type": "string" }, - "shortable": { - "description": "Asset is shortable or not", - "example": true, - "type": "boolean" - }, - "status": { - "description": "active or inactive", - "enum": [ - "active", - "inactive" - ], - "example": "active", + "replaces_event_id": { + "description": "Id of the replaced event (optional)", + "format": "ulid", "type": "string" }, - "symbol": { - "description": "The symbol of the asset", - "example": "AAPL", - "type": "string" + "type": { + "$ref": "#/components/schemas/AdminActionType" }, - "tradable": { - "description": "Asset is tradable on Alpaca or not", - "example": true, - "type": "boolean" + "visibility": { + "$ref": "#/components/schemas/AdminActionVisibility" } }, "required": [ - "id", - "class", - "exchange", - "symbol", - "name", - "status", - "tradable", - "marginable", - "shortable", - "easy_to_borrow", - "fractionable", - "maintenance_margin_requirement", - "margin_requirement_long", - "margin_requirement_short" + "event_id", + "at", + "belongs_to", + "created_by", + "type", + "category", + "visibility", + "note", + "correspondent" ], - "title": "Asset", + "title": "AdminActionEvent", "type": "object" }, - "AssetClass": { - "description": "This represents the category to which the asset belongs to. It serves to identify the nature of the financial instrument, with options including \"us_equity\" for U.S. equities, \"us_option\" for U.S. options, \"crypto\" for cryptocurrencies, and \"ipo\" for IPO indications of interest. This `asset_class: ipo` value is distinct from the assets API `attributes: [\"ipo\"]` flag.", - "enum": [ - "us_equity", - "us_option", - "crypto", - "ipo" + "AdminActionLegacyNote": { + "allOf": [ + { + "$ref": "#/components/schemas/AdminActionEventGeneral" + }, + { + "properties": { + "context": { + "description": "Variable schema type which depends on the type", + "type": "object" + } + }, + "required": [ + "context" + ], + "title": "AdminActionContextLegacyNote", + "type": "object" + } ], - "type": "string" + "description": "Represents structure of a LegacyNote type AdminAction", + "examples": [ + { + "at": "2023-03-06T16:38:01Z", + "belongs_to": { + "kind": "account", + "value": "0bbf1dd7-4235-4eca-8b1a-0db63572c735" + }, + "category": "other", + "context": {}, + "correspondent": "LPCA", + "created_by": { + "kind": "admin", + "value": "19455a3c-595f-457f-97b3-64a2b5aeae96" + }, + "event_id": "01GTVS4FVS2KJDTPYH2WM6NAXF", + "note": "Performed action: Positions split AMZN (long): 1.011266402\n", + "type": "legacy_note_admin_event", + "visibility": "internal" + } + ] }, - "AssetEntryRequirements": { - "description": "Defines the necessary conditions that must be met to initiate a position for a specific asset", - "properties": { - "regt_long": { - "description": "The percentage of the asset's market value required as Reg T (2x) buying power to open a long position", - "example": "0.5", - "format": "decimal", - "type": "string" - }, - "regt_short": { - "description": "The percentage of the asset's market value required as Reg T (2x) buying power to open a short position", - "example": "0.5", - "format": "decimal", - "type": "string" + "AdminActionLiquidation": { + "allOf": [ + { + "$ref": "#/components/schemas/AdminActionEventGeneral" }, - "symbol": { - "description": "The symbol (or asset id) of the requested asset", - "example": "AAPL", - "type": "string" + { + "properties": { + "context": { + "description": "Variable schema type which depends on the type", + "properties": { + "available_qty": { + "format": "decimal", + "type": "string" + }, + "error": { + "type": "string" + }, + "reason": { + "type": "string" + }, + "requested_qty": { + "format": "decimal", + "type": "string" + }, + "symbol": { + "type": "string" + } + }, + "type": "object" + } + }, + "required": [ + "context" + ], + "title": "AdminActionContextLiquidation", + "type": "object" } - }, - "required": [ - "symbol" ], - "title": "AssetEntryRequirements", - "type": "object" + "description": "Represents structure of a Liquidation type AdminAction", + "examples": [ + { + "at": "2023-03-06T16:38:01Z", + "belongs_to": { + "kind": "account", + "value": "0bbf1dd7-4235-4eca-8b1a-0db63572c735" + }, + "category": "accounts", + "context": { + "available_qty": "0.0001", + "error": "", + "reason": "Liquidation due to real time risk", + "requested_qty": "0.0001", + "symbol": "TSLA" + }, + "correspondent": "LPCA", + "created_by": { + "kind": "admin", + "value": "19455a3c-595f-457f-97b3-64a2b5aeae96" + }, + "event_id": "01GTVS4FVS2KJDTPYH2WM6NAXF", + "note": "Performed action: Position liquidated: Asset: TSLA, Quantity: 0.0001, Reason: Liquidation due to real time risk\n", + "type": "liquidation_admin_event", + "visibility": "external" + } + ] }, - "AuthorizeOAuthTokenResponse": { - "description": "", - "properties": { - "client_id": { - "description": "OAuth `client_id`", - "minLength": 1, - "type": "string" - }, - "code": { - "description": "OAuth code to exchange with token", - "minLength": 1, - "type": "string" - }, - "redirect_uri": { - "description": "Redirect URI of OAuth flow", - "minLength": 1, - "type": "string" + "AdminActionTransactionCancel": { + "allOf": [ + { + "$ref": "#/components/schemas/AdminActionEventGeneral" }, - "scope": { - "description": "Granted scopes", - "type": "string" + { + "properties": { + "context": { + "description": "Variable schema type which depends on the type", + "properties": { + "entry_type": { + "type": "string" + }, + "external_id": { + "type": "string" + }, + "transaction_id": { + "format": "uuid", + "type": "string" + } + }, + "type": "object" + } + }, + "required": [ + "context" + ], + "title": "AdminActionContextTransactionCancel", + "type": "object" } - }, - "required": [ - "code", - "client_id", - "redirect_uri", - "scope" ], - "type": "object", - "x-examples": { - "example-1": { - "client_id": "7a3c52a910e1dc2abbb14da2b6b8e711", - "code": "912b5502-c983-40f7-a01d-6a66f13a754d", - "redirect_uri": "http://localhost", - "scope": "" + "description": "Represents structure of a TransactionCancel type AdminAction", + "examples": [ + { + "at": "2023-03-06T16:38:01Z", + "belongs_to": { + "kind": "account", + "value": "0bbf1dd7-4235-4eca-8b1a-0db63572c735" + }, + "category": "accounts", + "context": { + "entry_type": "JNLC", + "external_id": "d5eede2c-1c08-45df-9800-87ad5eefc11f", + "transaction_id": "fdf18af2-142b-409e-8aad-d1731e276af0" + }, + "correspondent": "LPCA", + "created_by": { + "kind": "admin", + "value": "19455a3c-595f-457f-97b3-64a2b5aeae96" + }, + "event_id": "01GTVS4FVS2KJDTPYH2WM6NAXF", + "note": "\"Transaction fdf18af2-142b-409e-8aad-d1731e276af0 cancelled for LPCA-12345678\n", + "type": "transaction_cancel_admin_event", + "visibility": "external" } - } + ] }, - "Bank": { + "AdminActionType": { + "description": "Type of the Admin Action", + "enum": [ + "liquidation_admin_event", + "legacy_note_admin_event", + "transaction_cancel_admin_event" + ] + }, + "AdminActionVisibility": { + "description": "Visibility of the Admin Action", + "enum": [ + "internal", + "external", + "correspondent_only" + ] + }, + "AdminConfigurations": { + "description": "These configurations show account properties that are overriden either by Alpaca Broker Operations or an automated process.\n\nThese values cannot be modified by the Broker Partners.\n\nThis schema represents the current, effective value of each configuration (e.g. as returned by the get trading account endpoint). For the shape of the payloads broadcast by the **events** streaming API when these values change, see [AdminConfigurationsEvent](#/components/schemas/AdminConfigurationsEvent).", "properties": { - "account_id": { - "format": "uuid", + "acct_daily_transfer_limit": { + "description": "Override the correspondent level daily transfer limits", + "format": "decimal", "type": "string" }, - "account_number": { + "allow_instant_ach": { + "description": "If true, the account is allowed to perform instant ACH", + "type": "boolean" + }, + "disable_algodash_access": { + "description": "If true, the account is allowed to access algo dash", + "type": "boolean" + }, + "disable_api_key": { + "description": "If true, the account's API key will be disabled", + "type": "boolean" + }, + "disable_crypto": { + "description": "If true, the account is not allowed to trade cryptos", + "type": "boolean" + }, + "disable_day_trading": { + "description": "If true, the account is not allowed to day trade (e.g. buy and sell the same security on the same day)", + "type": "boolean" + }, + "disable_fractional": { + "description": "If true, the account cannot create orders for fractional share positions", + "type": "boolean" + }, + "disable_shorting": { + "description": "If true the account is not allowed to create short position orders", + "type": "boolean" + }, + "incoming_transfers_blocked": { + "description": "If true, incoming transfers to this account are rejected", + "type": "boolean" + }, + "max_margin_multiplier": { + "description": "The max margin multiplier set by admin for this account. Can be \"1\", \"2\", or \"4\".", "type": "string" }, - "bank_code": { - "description": "9-Digit ABA RTN (Routing Number) or BIC", + "max_options_trading_level": { + "description": "The max options trading level set by admin for this account.\n0=disabled, 1=Covered Call/Cash-Secured Put, 2=Long Call/Put, 3=Spreads/Straddles.\n", + "enum": [ + 0, + 1, + 2, + 3 + ], + "type": "integer" + }, + "outgoing_transfers_blocked": { + "description": "If true, outgoing transfers from this account are rejected", + "type": "boolean" + }, + "restrict_to_liquidation_reasons": { + "$ref": "#/components/schemas/RestrictToLiquidationReasons" + } + }, + "title": "AdminConfigurations", + "type": "object" + }, + "AdminConfigurationsEvent": { + "description": "Represents a change to admin configurations, as broadcast over the **events** streaming API. Only fields whose value changed are included; unrelated update events will not include a flag that did not change.\n\nDepending on the type of the Admin Configuration, the sent event will behave differently. For bool flags we are only sending the new value.\n\nFor example, the following payload means that the disable_shorting flag was set to true:\n\n```\n{\n \"disable_shorting\": true\n}\n```\n\nFor other data types, we are embedding the old and new values into the payload. For example changing the max_margin_multiplier from 4 to 1 will yield this payload:\n\n```\n{\n \"max_margin_multiplier\": {\n \"from\": 4,\n \"to\": 1\n }\n}\n```\n\nIntroducing an override value from the default will yield a null value as `from`. For example restricting the max_margin_multiplier to 1 from default will yield the following payload:\n\n```\n{\n \"max_margin_multiplier\": {\n \"from\": null,\n \"to\": 1\n }\n}\n```", + "properties": { + "acct_daily_transfer_limit": { + "description": "The correspondent level daily transfer limit override was changed", + "properties": { + "from": { + "description": "Old value of the daily transfer limit", + "type": "string" + }, + "to": { + "description": "New value of the daily transfer limit", + "type": "string" + } + }, + "type": "object" + }, + "allow_instant_ach": { + "description": "If true, the account is allowed to perform instant ACH", + "type": "boolean" + }, + "disable_algodash_access": { + "description": "If true, the account is allowed to access algo dash", + "type": "boolean" + }, + "disable_api_key": { + "description": "If true, the account's API key will be disabled", + "type": "boolean" + }, + "disable_crypto": { + "description": "If true, the account is not allowed to trade cryptos", + "type": "boolean" + }, + "disable_day_trading": { + "description": "If true, the account is not allowed to day trade (e.g. buy and sell the same security on the same day)", + "type": "boolean" + }, + "disable_fractional": { + "description": "If true, the account cannot create orders for fractional share positions", + "type": "boolean" + }, + "disable_shorting": { + "description": "If true the account is not allowed to create short position orders", + "type": "boolean" + }, + "incoming_transfers_blocked": { + "description": "If true, incoming transfers to this account are rejected", + "type": "boolean" + }, + "max_margin_multiplier": { + "description": "Max margin multiplier was changed by admin to this value", + "properties": { + "from": { + "description": "Old value of margin multiplier", + "type": "string" + }, + "to": { + "description": "New value of margin multiplier", + "type": "string" + } + }, + "type": "object" + }, + "max_options_trading_level": { + "description": "Max options trading level was changed by admin to this value. It can be 0, 1, 2, or 3.", + "properties": { + "from": { + "description": "Old value of max options trading level", + "type": "string" + }, + "to": { + "description": "New value of max options trading level", + "type": "string" + } + }, + "type": "object" + }, + "outgoing_transfers_blocked": { + "description": "If true, outgoing transfers from this account are rejected", + "type": "boolean" + }, + "restrict_to_liquidation_reasons": { + "$ref": "#/components/schemas/RestrictToLiquidationReasons" + } + }, + "title": "AdminConfigurationsEvent", + "type": "object" + }, + "AdvancedInstructions": { + "description": "Advanced instructions for Direct Market Access (DMA) routing. When `algorithm` is `DMA`,\nthe order is routed directly to the exchange specified in `destination`. Note that this feature is only\navailable to partners/correspondents who have been enabled for DMA.", + "examples": [ + { + "algorithm": "DMA", + "destination": "IEX", + "display_qty": "100" + } + ], + "properties": { + "algorithm": { + "description": "The advanced routing algorithm to use for the order. Only `DMA` (Direct Market Access) is supported.", + "enum": [ + "DMA" + ], + "example": "DMA", "type": "string" }, - "bank_code_type": { - "description": "ABA (Domestic) or BIC (International)", + "destination": { + "description": "Target exchange for order execution. Required when `algorithm` is `DMA`.", "enum": [ - "ABA", - "BIC" + "IEX", + "MEMX" ], + "example": "IEX", "type": "string" }, - "city": { - "description": "Only for international banks", + "display_qty": { + "description": "Maximum shares displayed on the exchange at any time. Must be in round lot increments.", + "example": "100", + "format": "decimal", "type": "string" + } + }, + "title": "AdvancedInstructions", + "type": "object" + }, + "AggregatePositionResponse": { + "examples": [ + { + "asset_type": "us_equity", + "closing_price": "148.4700", + "cusip": "037833100", + "long_market_value": "148.4700", + "long_qty": "1", + "num_accounts": 1, + "short_market_value": "0", + "short_qty": "0", + "symbol": "AAPL" + } + ], + "properties": { + "asset_type": { + "$ref": "#/components/schemas/AssetClass" }, - "country": { - "description": "Only for international banks", + "closing_price": { + "description": "EOD asset price per share at session close", + "format": "decimal", "type": "string" }, - "created_at": { - "description": "Format: 2020-01-01T01:01:01Z", - "example": "2021-03-16T18:38:01.942282Z", - "format": "date-time", + "cusip": { + "description": "Cusip (9 digits, can start with 0's)", + "format": "decimal", "type": "string" }, - "extra_fields": { - "$ref": "#/components/schemas/BankAdditionalFields" + "long_market_value": { + "description": "Aggregate notional dollar amount of the partner's long positions", + "format": "decimal", + "type": "string" }, - "id": { - "example": "61e69015-8549-4bfd-b9c3-01e75843f47d", - "format": "uuid", + "long_qty": { + "description": "Aggregate number of shares that the partner is long", + "format": "decimal", "type": "string" }, - "name": { - "description": "Name of recipient bank", + "num_accounts": { + "description": "Number of accounts that have a position in this asset (either long or short)", + "format": "decimal", + "type": "integer" + }, + "short_market_value": { + "description": "Aggregate notional dollar amount of the partner's short positions", + "format": "decimal", "type": "string" }, - "postal_code": { - "description": "Only for international banks", + "short_qty": { + "description": "Aggregate number of shares that the partner is short", + "format": "decimal", "type": "string" }, - "state_province": { - "description": "Only for international banks", + "symbol": { + "description": "Symbol of asset", "type": "string" + } + }, + "title": "", + "type": "object" + }, + "Agreement": { + "properties": { + "agreement": { + "$ref": "#/components/schemas/AgreementType" }, - "status": { - "description": "QUEUED, SENT_TO_CLEARING, APPROVED, REJECTED, CANCELED", - "enum": [ - "QUEUED", - "SENT_TO_CLEARING", - "APPROVED", - "REJECTED", - "CANCELED" - ], + "ip_address": { + "description": "The ip_address the signed agreements were sent from by the user.", + "example": "185.13.21.99", + "format": "ipv4", "type": "string" }, - "street_address": { - "description": "Only for international banks", + "revision": { + "description": "The agreement revision.\nThe format is XX.YYYY.MM where XX is an incrementing revision number, YYYY is the year and MM is the month.\nIf the revision is not specified in a POST or PATCH request, the active revision will be used, which will align with the [Alpaca Documents Library](https://alpaca.markets/disclosures).\n", "type": "string" }, - "updated_at": { - "description": "Format: 2020-01-01T01:01:01Z", - "example": "2021-03-16T18:38:01.942282Z", + "signed_at": { + "description": "The timestamp the agreement was signed.", + "example": "2019-09-11T18:09:33Z", "format": "date-time", "type": "string" } }, "required": [ - "id", - "created_at", - "updated_at", - "name", - "bank_code", - "bank_code_type", - "account_number" + "agreement", + "signed_at", + "ip_address" ], - "type": "object", - "x-examples": { - "example-1": { - "account_id": "56712986-9ff7-4d8f-8e52-077e099e533e", - "account_number": "123456789abc", - "bank_code": "123456789", - "bank_code_type": "ABA", - "city": "", - "country": "", - "created_at": "2022-02-11T21:35:19.268681613Z", - "id": "8475c676-68e3-4cfc-a683-9ca2f47a6172", - "name": "Bank XYZ", - "postal_code": "", - "state_province": "", - "status": "QUEUED", - "street_address": "", - "updated_at": "2022-02-11T21:35:19.268681613Z" - } - } - }, - "BankAdditionalFields": { - "description": "Additional wire instructions used to explicitly specify intermediary (correspondent) banks for international wire transfers. If omitted, intermediary banks may be automatically selected by downstream institutions, which can result in delays or additional charges.\n", - "properties": { - "intermediary_bank1_bic": { - "description": "The primary intermediary (correspondent) bank to be used when routing the international wire transfer to the beneficiary bank.", - "type": "string" - }, - "intermediary_bank2_bic": { - "description": "An additional intermediary bank to be used if the wire transfer requires multiple correspondent banks before reaching the beneficiary bank.", - "type": "string" - }, - "intermediary_bank3_bic": { - "description": "A tertiary intermediary bank used in complex international wire routes that require three correspondent banks prior to reaching the beneficiary bank.", - "type": "string" - } - }, - "type": "object" - }, - "BatchCreateFundingWalletRequest": { - "properties": { - "account_ids": { - "description": "list of UUID account ids which will have funding wallets created", - "items": { - "type": "string" - }, - "type": "array" - } - }, - "type": "object" - }, - "BatchCreateFundingWalletResponse": { - "properties": { - "funding_wallets": { - "items": { - "$ref": "#/components/schemas/FundingWallet" - }, - "type": "array" - } - }, "type": "object" }, - "BatchJournalRequest": { - "description": "Journals API allows you to move cash or securities from one account to another.\n\nThis model represents the fields you can specify when creating a request of many Journals out of one account to many others at once.", - "properties": { - "description": { - "description": "Journal description, gets returned in the response", - "type": "string" - }, - "entries": { - "description": "An array of objects describing which accounts you want to move funds into and how much to move into each account", - "items": { - "properties": { - "amount": { - "description": "Journal amount in USD", - "type": "string" - }, - "currency": { - "description": "Currency", - "type": "string" - }, - "description": { - "description": "Journal entry description, gets returned in the response", - "type": "string" - }, - "to_account": { - "description": "The ID of the account that you want to journal funds into", - "format": "uuid", - "type": "string" - }, - "transmitter_account_number": { - "description": "Only valid for JNLC journals. Null for JNLS.max 255 characters", - "type": "string" - }, - "transmitter_address": { - "description": "Only valid for JNLC journals. Null for JNLS.max 255 characters", - "type": "string" - }, - "transmitter_financial_institution": { - "description": "Only valid for JNLC journals. Null for JNLS.max 255 characters", - "type": "string" - }, - "transmitter_name": { - "description": "Only valid for JNLC journals. Null for JNLS. Max 255 characters.", - "type": "string" - }, - "transmitter_timestamp": { - "format": "date-time", - "type": "string" - } - }, - "required": [ - "to_account", - "amount" - ], - "type": "object" - }, - "minItems": 1, - "type": "array" - }, - "entry_type": { - "description": "Only supports `JNLC` for now", - "enum": [ - "JNLC" - ], - "type": "string" - }, - "from_account": { - "description": "The account id that is the originator of the funds being moved. Most likely is your Sweep Firm Account", - "format": "uuid", - "type": "string" - } - }, - "required": [ - "entry_type", - "from_account", - "entries" + "AgreementType": { + "description": "- margin_agreement: Alpaca Margin Agreement\n- account_agreement: Alpaca Account Agreement\n- customer_agreement: Alpaca Customer Agreement\n- crypto_agreement: Alpaca Crypto agreement\n- options_agreement: Alpaca Option agreement\n- custodial_customer_agreement: Alpaca Custodial Customer agreement\n", + "enum": [ + "margin_agreement", + "account_agreement", + "customer_agreement", + "crypto_agreement", + "options_agreement" ], - "title": "BatchJournalRequest", - "type": "object" + "example": "customer_agreement", + "title": "AgreementType", + "type": "string" }, - "BatchJournalResponse": { - "allOf": [ - { - "$ref": "#/components/schemas/Journal" - }, + "Announcement": { + "description": "The announcements endpoint contains public information on previous and upcoming dividends, mergers, spinoffs, and stock splits.\n\nAnnouncement data is made available through the API as soon as it is ingested by Alpaca, which is typically the following trading day after the declaration date. This provides insight into future account stock position and cash balance changes that will take effect on an announcement's payable date. Additionally, viewing previous announcement details can improve bookkeeping and reconciling previous account cash and position changes.", + "examples": [ { - "properties": { - "error_message": { - "description": "Description of why this journal transaction failed", - "type": "string" - } - }, - "required": [ - "error_message" - ], - "type": "object" + "ca_sub_type": "cash", + "ca_type": "dividend", + "cash": "0", + "corporate_action_id": "78467X109_AA22", + "declaration_date": "2021-12-19", + "ex_date": "2022-01-21", + "id": "bebc5ece-34be-47e9-b944-687e69a102be", + "initiating_original_cusip": "252787106", + "initiating_symbol": "DIA", + "new_rate": "1", + "old_rate": "1", + "payable_date": "2022-02-14", + "record_date": "2022-01-24", + "target_original_cusip": "252787106", + "target_symbol": "DIA" } ], - "description": "A Journal object with an extra attribute error_message in the case when a specific account fails to receive a journal.", - "title": "BatchJournalResponse", - "x-examples": { - "example-1": { - "description": "", - "entry_type": "JNLC", - "error_message": "", - "from_account": "8f8c8cee-2591-4f83-be12-82c659b5e748", - "id": "56f106e5-25a4-4eee-96fa-25bb05dc86bc", - "net_amount": "1000", - "price": null, - "qty": null, - "settle_date": null, - "status": "pending", - "symbol": "", - "system_date": null, - "to_account": "399f85f1-cbbd-4eaa-a934-70027fb5c1de" - } - } - }, - "Beneficiary": { - "description": "Beneficiary of an account", - "example": { - "date_of_birth": "1970-01-01", - "family_name": "Doe", - "given_name": "Jane", - "middle_name": "P", - "relationship": "spouse", - "share_pct": "100", - "tax_id": "xxx-xx-xxxx", - "tax_id_type": "USA_SSN", - "type": "primary" - }, "properties": { - "date_of_birth": { - "example": "1970-01-01", - "type": "string" + "ca_sub_type": { + "$ref": "#/components/schemas/AnnouncementCASubType" }, - "family_name": { - "example": "Doe", - "type": "string" + "ca_type": { + "$ref": "#/components/schemas/AnnouncementCAType" }, - "given_name": { - "example": "Jane", - "type": "string" + "cash": { + "description": "The amount of cash to be paid per share held by an account on the record date.", + "minLength": 1, + "type": [ + "string", + "null" + ] }, - "middle_name": { - "example": "P", + "corporate_action_id": { + "description": "ID that remains consistent across all announcements for the same corporate action. Unlike 'id', this can be used to connect multiple announcements to see how the terms have changed throughout the lifecycle of the corporate action event.", + "minLength": 1, "type": "string" }, - "relationship": { - "example": "spouse", + "declaration_date": { + "description": "Date the corporate action or subsequent terms update was announced.", + "minLength": 1, "type": "string" }, - "share_pct": { - "example": "100", - "type": "string" + "ex_date": { + "description": "The first date that purchasing a security will not result in a corporate action entitlement.", + "minLength": 1, + "type": [ + "string", + "null" + ] }, - "tax_id": { - "example": "xxx-xx-xxxx", + "id": { + "description": "ID that is specific to a single announcement.", + "minLength": 1, "type": "string" }, - "tax_id_type": { - "example": "USA_SSN", + "initiating_original_cusip": { + "description": "CUSIP of the company initiating the announcement.", + "minLength": 1, "type": "string" }, - "type": { - "example": "primary", + "initiating_symbol": { + "description": "Symbol of the company initiating the announcement.", + "minLength": 1, "type": "string" - } + }, + "new_rate": { + "description": "The numerator to determine any quantity change ratios in positions.", + "minLength": 1, + "type": [ + "string", + "null" + ] + }, + "old_rate": { + "description": "The denominator to determine any quantity change ratios in positions.", + "minLength": 1, + "type": [ + "string", + "null" + ] + }, + "payable_date": { + "description": "The date the announcement will take effect. On this date, account stock and cash balances are expected to be processed accordingly.", + "minLength": 1, + "type": "string" + }, + "record_date": { + "description": "The date an account must hold a settled position in the security in order to receive the corporate action entitlement.", + "minLength": 1, + "type": [ + "string", + "null" + ] + }, + "target_original_cusip": { + "description": "CUSIP of the child company involved in the announcement.", + "minLength": 1, + "type": [ + "string", + "null" + ] + }, + "target_symbol": { + "description": "Symbol of the child company involved in the announcement.", + "minLength": 1, + "type": [ + "string", + "null" + ] + } }, "required": [ - "given_name", - "middle_name", - "family_name", - "date_of_birth", - "tax_id", - "tax_id_type", - "relationship", - "type", - "share_pct" + "id", + "corporate_action_id", + "ca_type", + "ca_sub_type", + "initiating_symbol", + "initiating_original_cusip", + "target_symbol", + "target_original_cusip", + "declaration_date", + "ex_date", + "record_date", + "payable_date", + "cash", + "old_rate", + "new_rate" ], + "title": "Announcement", "type": "object" }, - "CDIVActivityV2": { - "allOf": [ - { - "$ref": "#/components/schemas/CommonCaActivityV2" - }, - { - "$ref": "#/components/schemas/CommonCDIVActivityV2" - }, - { - "properties": { - "cash_payout": { - "description": "Total cash amount paid", - "type": "string" - }, - "entitled_qty": { - "description": "Quantity of shares entitled to receive the dividend", - "type": "string" - } - }, - "required": [ - "entitled_qty", - "cash_payout" - ], - "type": "object" - } + "AnnouncementCASubType": { + "description": "Announcements have both a type and a subtype to categorize them. This model represents the lowever level abstract \"sub types\" of Announcement. Please see the AnnouncementCAType model for higher level descriptions of the possible types\n\nPossible values are:\n\n- from the `dividend` type:\n - **cash**\n\n A cash payment based on the number of shares the account holds on the record date.\n - **stock**\n\n A stock payment based on the number of shares the account holds on the record date.\n\n- from the `merger` type:\n - **merger_update**\n\n An update to the terms of an upcoming merger. This can happen any number of times before the merger is completed and can be tracked by using the id parameter.\n\n - **merger_completion**\n\n A final update in the terms of the merger in which the intiating_symbol will acquire the target_symbol. Any previous terms updates for this announcement will have the same id value.\n\n- from the `split` type:\n - **stock_split**\n\n An increase in the number of shares outstanding with a decrease in the dollar value of each share. The new_rate and old_rate parameters will be returned in order to derive the ratio of the split\n - **until_split**\n\n An increase in the number of shares outstanding with a decrease in the dollar value of each share. The new_rate and old_rate parameters will be returned in order to derive the ratio of the split.\n - **reverse_split**\n\n A decrease in the number of shares outstanding with an increase in the dollar value of each share. The new_rate and old_rate parameters will be returned in order to derive the ratio of the spli\n - **recapitalization**\n\n A stock recapitalization, typically used by a company to adjust debt and equity ratios.\n\n- from the `spinoff` type:\n - **spinoff**\n\n A disbursement of a newly tradable security when the intiating_symbol creates the target_symbol.", + "enum": [ + "cash", + "stock", + "merger_update", + "merger_completion", + "stock_split", + "until_split", + "reverse_split", + "recapitalization", + "spinoff" ], - "description": "Cash dividend", - "type": "object" + "title": "", + "type": "string" }, - "CIPDocument": { - "description": "Represents results of checking a document for CIPInfo\n", + "AnnouncementCAType": { + "description": "Announcements have both a type and a subtype to categorize them. This model represents the higher level abstract \"types\" of Announcement. Please see the AnnouncementCASubType model for finer grain descriptions of the subtypes\n\nPossible values are:\n- dividend\n can have `cash` and `stock` subtypes\n- merger\n has `merger_update` and `merger_completion` sub types\n- split\n has `stock_split`, `until_split`, `reverse_split`, and `recapitalization` sub types\n- spinoff\n currently has only the `spinoff` subtype and thus is just this higher level category for now. A disbursement of a newly tradable security when the intiating_symbol creates the target_symbol.", + "enum": [ + "dividend", + "merger", + "split", + "spinoff" + ], + "example": "dividend", + "title": "", + "type": "string" + }, + "Asset": { + "description": "Assets are sorted by asset class, exchange and symbol. Some assets are not tradable with Alpaca. These assets will be marked with the flag tradable=false", "properties": { - "age_validation": { - "$ref": "#/components/schemas/CIPResult" - }, - "comprised_document": { - "$ref": "#/components/schemas/CIPResult" - }, - "created_at": { - "description": "Datetime for when this check was done", - "format": "date-time", - "type": "string" - }, - "data_comparison": { - "$ref": "#/components/schemas/CIPResult" - }, - "data_comparison_breakdown": { - "description": "json object representing the results of the various sub-checks\n done when calculating the result on `data_comparison`. Example: {\"date_of_birth\": \"clear\",\n \"date_of_expiry\": \"clear\" \"document_numbers\": \"clear\", \"document_type\": \"clear\", \"first_name\": \"clear\",\n \"gender\": \"clear\", \"issuing_country\": \"clear\", \"last_name\": \"clear\"}", - "type": "string" - }, - "date_of_birth": { - "description": "Datetime for when this check was done", - "format": "date", - "type": "string" - }, - "date_of_expiry": { - "description": "Datetime for when this check was done", - "format": "date", - "type": "string" - }, - "document_numbers": { - "description": "Number of the document that was checked", + "attributes": { + "description": "Unique characteristics of the asset. Supported values:\n- `ptp_no_exception`: Asset is a Publicly Traded Partnership (PTP) without a qualified notice; non-U.S. customers may incur 10% withholding on gross proceeds as per IRS guidance, and are blocked from being purchased by default.\n- `ptp_with_exception`: Users can open positions in these PTPs without general restrictions.\n- `ipo`: Accepting limit orders only before the stock begins trading on the secondary market.\n- `has_options`: The underlying equity has listed options available on the platform. Note: if the equity had inactive/expired contracts in the past, this will still show up.\n- `options_late_close`: Indicates the underlying asset's options contracts close at 4:15pm ET instead of the standard 4:00pm ET.\n- `fractional_eh_enabled`: Indicates the asset accepts fractional orders during extended hours sessions (pre-market, post-market, and overnight if enabled).\n- `overnight_tradable`: Asset is eligible for overnight (24x5) trading in supported venues on the platform.\n- `overnight_halted`: Asset is eligible for overnight trading but is currently halted/blocked for overnight sessions due to risk, corporate action, compliance, or venue constraints.", + "example": [ + "ptp_no_exception", + "ipo" + ], "items": { + "enum": [ + "ptp_no_exception", + "ptp_with_exception", + "ipo", + "has_options", + "options_late_close", + "fractional_eh_enabled", + "overnight_tradable", + "overnight_halted" + ], "type": "string" }, "type": "array" }, - "document_type": { - "description": "Type of the document that was checked", + "borrow_status": { + "description": "Borrow status for US equity assets. This field is omitted for non-US-equity assets.", + "enum": [ + "easy_to_borrow", + "hard_to_borrow" + ], + "example": "easy_to_borrow", "type": "string" }, - "first_name": { - "description": "First name extracted from the document", - "type": "string" + "class": { + "$ref": "#/components/schemas/AssetClass" }, - "gender": { - "description": "Gender info extracted from the document", - "type": "string" + "exchange": { + "$ref": "#/components/schemas/Exchange" + }, + "fractionable": { + "description": "Asset is fractionable or not", + "example": true, + "type": "boolean" }, "id": { - "description": "Your internal ID of check", + "description": "Asset ID", + "example": "904837e3-3b76-47ec-b432-046db621571b", + "format": "uuid", "type": "string" }, - "image_integrity": { - "$ref": "#/components/schemas/CIPResult" + "maintenance_margin_requirement": { + "deprecated": true, + "description": "**deprecated**: Please use margin_requirement_long or margin_requirement_short instead. Note that these fields are of type string.\nShows the margin requirement percentage for the asset (equities only).\n", + "type": "number" }, - "image_integrity_breakdown": { - "description": "json object representing the results of the various sub-checks done\n when calculating the result on `image_integrity`. Example: example: {\"colour_picture\": \"clear\",\n \"conclusive_document_quality\": \"clear\", \"image_quality\": \"clear\", \"supported_document\": \"clear\"}", + "margin_requirement_long": { + "description": "The margin requirement percentage for the asset's long positions (equities only), encoded as a decimal string.", + "example": "100", + "format": "decimal", "type": "string" }, - "issuing_country": { - "description": "Country for which issued the document", + "margin_requirement_short": { + "description": "The margin requirement percentage for the asset's short positions (equities only), encoded as a decimal string.", + "example": "30", + "format": "decimal", "type": "string" }, - "last_name": { - "description": "Last name extracted from the document", + "marginable": { + "description": "Asset is marginable or not", + "example": true, + "type": "boolean" + }, + "min_order_size": { + "description": "Minimum order size. Field available for crypto only.", "type": "string" }, - "nationality": { - "description": "Nationality extracted from the document", + "min_trade_increment": { + "description": "Amount a trade quantity can be incremented by. Field available for crypto only.", "type": "string" }, - "police_record": { - "$ref": "#/components/schemas/CIPStatus" + "name": { + "description": "The official name of the asset", + "example": "Apple Inc. Common Stock", + "type": "string" }, - "result": { - "$ref": "#/components/schemas/CIPResult" + "price_increment": { + "description": "Amount the price can be incremented by. Field available for crypto only.", + "type": "string" + }, + "shortable": { + "description": "Asset is shortable or not", + "example": true, + "type": "boolean" }, "status": { - "$ref": "#/components/schemas/CIPStatus" + "description": "active or inactive", + "enum": [ + "active", + "inactive" + ], + "example": "active", + "type": "string" }, - "visual_authenticity": { - "description": "json object representing the various sub-checks done when determining\n whether visual (non-textual) elements are correct given the document type. Example: {\n \"digital_tampering\": \"clear\", \"face_detection\": \"clear\", \"fonts\": \"clear\", \"original_document_present\":\n \"clear\", \"picture_face_integrity\": \"clear\", \"security_features\": \"clear\", \"template\": \"clear\"}", + "symbol": { + "description": "The symbol of the asset", + "example": "AAPL", "type": "string" + }, + "tradable": { + "description": "Asset is tradable on Alpaca or not", + "example": true, + "type": "boolean" } }, "required": [ - "id" - ], - "type": "object", - "x-examples": { - "Example 1": { - "created_at": "2021-06-10T15:37:03Z", - "id": "55B9931A-3BE6-4BC0-9BDD-0B954E4A4632", - "image_integrity": "clear", - "result": "clear", - "status": "complete" - } - } + "id", + "class", + "exchange", + "symbol", + "name", + "status", + "tradable", + "marginable", + "shortable", + "fractionable", + "margin_requirement_long", + "margin_requirement_short" + ], + "title": "Asset", + "type": "object" }, - "CIPIdentity": { + "AssetClass": { + "description": "This represents the category to which the asset belongs to. It serves to identify the nature of the financial instrument, with options including \"us_equity\" for U.S. equities, \"us_option\" for U.S. options, \"crypto\" for cryptocurrencies, and \"ipo\" for IPO indications of interest. This `asset_class: ipo` value is distinct from the assets API `attributes: [\"ipo\"]` flag.", + "enum": [ + "us_equity", + "us_option", + "crypto", + "ipo" + ], + "type": "string" + }, + "AssetEntryRequirements": { + "description": "Defines the necessary conditions that must be met to initiate a position for a specific asset", "properties": { - "address": { - "$ref": "#/components/schemas/CIPResult" - }, - "address_breakdown": { - "description": "a json object representing the breakdown of the `address` field. For example:\n {\"credit_agencies\": {\"result\": \"clear\",\"properties\":{\"number_of_matches\":\"1\"}}", - "type": "string" - }, - "created_at": { - "description": "datetime when identity check happened", - "type": "string" - }, - "date_of_birth": { - "$ref": "#/components/schemas/CIPResult" - }, - "date_of_birth_breakdown": { - "description": "a json object representing the breakdown of the `date_of_birth` field.\n For example: example: {\"credit_agencies\":{\"result\": \"clear\",\"properties\": {\"number_of_matches\": \"1\"}}", - "type": "string" - }, - "id": { - "description": "Your internal ID of check", - "type": "string" - }, - "matched_address": { - "$ref": "#/components/schemas/CIPResult" - }, - "matched_addresses": { - "description": "datetime when identity check happened", + "regt_long": { + "description": "The percentage of the asset's market value required as Reg T (2x) buying power to open a long position", + "example": "0.5", + "format": "decimal", "type": "string" }, - "result": { - "$ref": "#/components/schemas/CIPResult" - }, - "sources": { - "$ref": "#/components/schemas/CIPResult" - }, - "sources_breakdown": { - "description": "a json object representing the breakdown of `sources` field. For example:\n {\"total_sources\": {\"result\": \"clear\",\"properties\": {\"total_number_of_sources\": \"3\"}}}", + "regt_short": { + "description": "The percentage of the asset's market value required as Reg T (2x) buying power to open a short position", + "example": "0.5", + "format": "decimal", "type": "string" }, - "status": { - "$ref": "#/components/schemas/CIPStatus" - }, - "tax_id": { - "$ref": "#/components/schemas/CIPResult" - }, - "tax_id_breakdown": { - "description": "a json object representing the breakdown of the `tax_id` field", + "symbol": { + "description": "The symbol (or asset id) of the requested asset", + "example": "AAPL", "type": "string" } }, "required": [ - "id" + "symbol" ], - "type": "object", - "x-examples": { - "Example 1": { - "address": "clear", - "created_at": "2021-06-10T15:37:03Z", - "date_of_birth": "clear", - "id": "28E1CCE8-1B1A-4472-9AD4-C6C5B7C3A6AF", - "result": "clear", - "sources": "clear", - "status": "complete" - } - } + "title": "AssetEntryRequirements", + "type": "object" }, - "CIPInfo": { - "description": "Customer Identification Program (CIP) information for an account applicant.", + "AuthorizeOAuthTokenResponse": { + "description": "", + "examples": [ + { + "client_id": "7a3c52a910e1dc2abbb14da2b6b8e711", + "code": "912b5502-c983-40f7-a01d-6a66f13a754d", + "redirect_uri": "http://localhost", + "scope": "" + } + ], "properties": { - "account_id": { - "description": "UUID of the Account instance this CIPInfo is for", - "format": "uuid", + "client_id": { + "description": "OAuth `client_id`", + "minLength": 1, "type": "string" }, - "created_at": { - "format": "date-time", + "code": { + "description": "OAuth code to exchange with token", + "minLength": 1, "type": "string" }, - "document": { - "$ref": "#/components/schemas/CIPDocument" - }, - "id": { - "description": "ID of this CIPInfo", - "format": "uuid", + "redirect_uri": { + "description": "Redirect URI of OAuth flow", + "minLength": 1, "type": "string" }, - "identity": { - "$ref": "#/components/schemas/CIPIdentity" - }, - "kyc": { - "$ref": "#/components/schemas/CIPKYC" - }, - "photo": { - "$ref": "#/components/schemas/CIPPhoto" - }, - "provider_name": { - "description": "List of KYC providers this information came from", - "items": { - "$ref": "#/components/schemas/CIPProvider" - }, - "type": "array" - }, - "updated_at": { - "format": "date-time", + "scope": { + "description": "Granted scopes", "type": "string" - }, - "watchlist": { - "$ref": "#/components/schemas/CIPWatchlist" } }, - "title": "CIPInfo", + "required": [ + "code", + "client_id", + "redirect_uri", + "scope" + ], "type": "object" }, - "CIPKYC": { - "description": "Represents Know Your Customer (KYC) info for a CIPInfo", + "Bank": { + "examples": [ + { + "account_id": "56712986-9ff7-4d8f-8e52-077e099e533e", + "account_number": "123456789abc", + "bank_code": "123456789", + "bank_code_type": "ABA", + "city": "", + "country": "", + "created_at": "2022-02-11T21:35:19.268681613Z", + "id": "8475c676-68e3-4cfc-a683-9ca2f47a6172", + "name": "Bank XYZ", + "postal_code": "", + "state_province": "", + "status": "QUEUED", + "street_address": "", + "updated_at": "2022-02-11T21:35:19.268681613Z" + } + ], "properties": { - "address": { - "description": "Concatenated street address, city, state and country of applicant", + "account_id": { + "format": "uuid", "type": "string" }, - "applicant_name": { - "description": "Given and family name of applicant", + "account_number": { "type": "string" }, - "approval_status": { - "description": "Approval status of KYC check", - "enum": [ - "approved", - "rejected" - ], - "example": "approved", + "bank_code": { + "description": "9-Digit ABA RTN (Routing Number) or BIC", "type": "string" }, - "approved_at": { - "description": "Reason for approving this KYC check", - "format": "date-time", + "bank_code_type": { + "description": "ABA (Domestic) or BIC (International)", + "enum": [ + "ABA", + "BIC" + ], "type": "string" }, - "approved_by": { - "description": "Identifier of who approved KYC check", + "city": { + "description": "Only for international banks", "type": "string" }, - "approved_reason": { - "description": "Datetime that this KYC check was approved", + "country": { + "description": "Only for international banks", "type": "string" }, - "check_completed_at": { - "description": "completion datetime of KYC check", + "created_at": { + "description": "Format: 2020-01-01T01:01:01Z", + "example": "2021-03-16T18:38:01.942282Z", "format": "date-time", "type": "string" }, - "check_initiated_at": { - "description": "start datetime of KYC check", - "format": "date-time", - "type": "string" + "extra_fields": { + "$ref": "#/components/schemas/BankAdditionalFields" }, - "country_of_residency": { - "description": "country for `address` field", + "id": { + "example": "61e69015-8549-4bfd-b9c3-01e75843f47d", + "format": "uuid", "type": "string" }, - "date_of_birth": { - "description": "DOB of applicant", - "format": "date", + "name": { + "description": "Name of recipient bank", "type": "string" }, - "email_address": { - "description": "email address of applicant", + "postal_code": { + "description": "Only for international banks", "type": "string" }, - "id": { - "description": "Your internal ID of check", - "format": "uuid", + "state_province": { + "description": "Only for international banks", "type": "string" }, - "ip_address": { - "description": "IP address of applicant at time of KYC check", + "status": { + "description": "QUEUED, SENT_TO_CLEARING, APPROVED, REJECTED, CANCELED", + "enum": [ + "QUEUED", + "SENT_TO_CLEARING", + "APPROVED", + "REJECTED", + "CANCELED" + ], "type": "string" }, - "kyc_completed_at": { - "description": "Datetime that KYC check was completed at", - "format": "date-time", + "street_address": { + "description": "Only for international banks", "type": "string" }, - "nationality": { - "description": "nationality of applicant", - "type": "string" - }, - "postal_code": { - "description": "postal code for `address` field", - "type": "string" - }, - "risk_categories": { - "description": "The list of risk categories returned by the KYC provider or assessed", - "items": { - "type": "string" - }, - "type": "array" - }, - "risk_level": { - "description": "Overall risk level returned by KYC provider or assessed", + "updated_at": { + "description": "Format: 2020-01-01T01:01:01Z", + "example": "2021-03-16T18:38:01.942282Z", + "format": "date-time", "type": "string" - }, - "risk_score": { - "description": "Overall risk score returned by KYC provider or assessed", - "type": "integer" } }, "required": [ - "id" + "id", + "created_at", + "updated_at", + "name", + "bank_code", + "bank_code_type", + "account_number" ], - "title": "CIPKYCInfo", - "type": "object", - "x-examples": { - "Example 1": { - "address": "42 Faux St", - "applicant_name": "John Doe", - "approval_status": "approved", - "approved_at": "2021-06-10T15:38:03Z", - "approved_by": "Jane Doe", - "check_completed_at": "2021-06-10T15:37:03Z", - "check_initiated_at": "2021-06-10T15:37:03Z", - "country_of_residency": "USA", - "date_of_birth": "1970-12-01", - "email_address": "johndoe@example.com", - "id": "CBDAD1C4-1047-450E-BAE5-B6C406F509B4", - "id_number": "jd0000123456789", - "ip_address": "127.0.0.1", - "kyc_completed_at": "2021-06-10T15:37:03Z", - "nationality": "American", - "postal_code": "94401", - "risk_level": "LOW" - } - } + "type": "object" }, - "CIPPhoto": { - "description": "Represents the results of checking a Photo for CIPInfo", + "BankAdditionalFields": { + "description": "Additional wire instructions used to explicitly specify intermediary (correspondent) banks for international wire transfers. If omitted, intermediary banks may be automatically selected by downstream institutions, which can result in delays or additional charges.\n", "properties": { - "created_at": { - "description": "datetime of when the check happened", - "type": "string" - }, - "face_comparison": { - "$ref": "#/components/schemas/CIPResult" - }, - "face_comparison_breakdown": { - "description": "a json object representing the breakdown of sub-checks done in\n `face_comparison`. Example: {\"face_match\":{\"result\": \"clear\",\"properties\":{\"score\": \"80\"}}}", - "type": "string" - }, - "id": { - "description": "Your internal ID of the check", - "type": "string" - }, - "image_integrity": { - "$ref": "#/components/schemas/CIPResult" - }, - "image_integrity_breakdown": { - "description": "a json object representing the breakdown of sub-checks done in\n `image_integrity`. Example {\"face_detected\":{\"result\": \"clear\"},\"source_integrity\": {\"result\": \"clear\"}}", + "intermediary_bank1_bic": { + "description": "The primary intermediary (correspondent) bank to be used when routing the international wire transfer to the beneficiary bank.", "type": "string" }, - "result": { - "$ref": "#/components/schemas/CIPResult" - }, - "status": { - "description": "Overall status of the check. Either `complete` or `withdrawn`.", - "enum": [ - "complete", - "withdrawn" - ], + "intermediary_bank2_bic": { + "description": "An additional intermediary bank to be used if the wire transfer requires multiple correspondent banks before reaching the beneficiary bank.", "type": "string" }, - "visual_authenticity": { - "$ref": "#/components/schemas/CIPResult" - }, - "visual_authenticity_breakdown": { - "description": "a json object representing the breakdown of sub-checks don in\n `visual_authenticity`. Example {\"spoofing_detection\": {\"result\": \"clear\",\"properties\": {\"score\": \"26\"}}}}", + "intermediary_bank3_bic": { + "description": "A tertiary intermediary bank used in complex international wire routes that require three correspondent banks prior to reaching the beneficiary bank.", "type": "string" } }, - "required": [ - "id" - ], - "type": "object", - "x-examples": { - "Example 1": { - "created_at": "2021-06-10T15:37:03Z", - "face_comparison": "clear", - "id": "0DD13020-F0FD-4B5A-B58F-BC1885E90A6D", - "result": "clear", - "status": "complete" - } - } - }, - "CIPProvider": { - "description": "\"alloy\" - ALLOY\n\"trulioo\" - TRULIOO\n\"onfido\" - ONFIDO\n\"veriff\" - VERIFF\n\"jumio\" - JUMIO\n\"getmati\" - GETMATI", - "title": "CIPProvider", - "type": "string" + "type": "object" }, - "CIPResult": { - "description": "The result of the check. Either `clear` or `consider`.", - "title": "CIPResult", - "type": "string" + "BatchCreateFundingWalletRequest": { + "properties": { + "account_ids": { + "description": "list of UUID account ids which will have funding wallets created", + "items": { + "type": "string" + }, + "type": "array" + } + }, + "type": "object" }, - "CIPStatus": { - "description": "An enum representing the status of the CIPInfo\n\n\"complete\"\n\n\"withdrawn\"", - "title": "CIPStatus", - "type": "string" + "BatchCreateFundingWalletResponse": { + "properties": { + "funding_wallets": { + "items": { + "$ref": "#/components/schemas/FundingWallet" + }, + "type": "array" + } + }, + "type": "object" }, - "CIPWatchlist": { - "description": "Represents the result of checking to see if the applicant is in any watchlists for a CIPInfo", + "BatchJournalRequest": { + "description": "Journals API allows you to move cash or securities from one account to another.\n\nThis model represents the fields you can specify when creating a request of many Journals out of one account to many others at once.", "properties": { - "adverse_media": { - "$ref": "#/components/schemas/CIPResult" - }, - "created_at": { - "description": "datetime when check happened", - "type": "string" - }, - "id": { - "description": "Your internal ID of check", + "correspondent": { + "description": "The 4-character correspondent code", + "example": "LPCA", "type": "string" }, - "monitored_lists": { - "$ref": "#/components/schemas/CIPResult" - }, - "politically_exposed_person": { - "$ref": "#/components/schemas/CIPResult" + "entries": { + "description": "An array of objects describing which accounts you want to move funds into and how much to move into each account", + "items": { + "properties": { + "amount": { + "description": "Journal amount in USD", + "type": [ + "string", + "null" + ] + }, + "currency": { + "description": "Currency code in ISO format", + "type": [ + "string", + "null" + ] + }, + "description": { + "description": "Journal entry description, gets returned in the response", + "type": "string" + }, + "qty": { + "type": [ + "string", + "null" + ] + }, + "symbol": { + "type": "string" + }, + "to_account": { + "description": "The ID of the account that you want to journal funds into", + "format": "uuid", + "type": "string" + }, + "transmitter_account_number": { + "description": "Only valid for JNLC journals. Null for JNLS.max 255 characters", + "type": "string" + }, + "transmitter_address": { + "description": "Only valid for JNLC journals. Null for JNLS.max 255 characters", + "type": "string" + }, + "transmitter_financial_institution": { + "description": "Only valid for JNLC journals. Null for JNLS.max 255 characters", + "type": "string" + }, + "transmitter_name": { + "description": "Only valid for JNLC journals. Null for JNLS. Max 255 characters.", + "type": "string" + }, + "transmitter_timestamp": { + "format": "date-time", + "type": "string" + } + }, + "required": [ + "to_account", + "amount" + ], + "type": "object" + }, + "minItems": 1, + "type": "array" }, - "records": { - "description": "a json object. Example [{\"text\": \"Record info\"}]", + "entry_type": { + "description": "Only supports `JNLC` for now", + "enum": [ + "JNLC" + ], "type": "string" }, - "result": { - "$ref": "#/components/schemas/CIPResult" - }, - "sanction": { - "$ref": "#/components/schemas/CIPResult" - }, - "status": { - "$ref": "#/components/schemas/CIPStatus" + "from_account": { + "description": "The account id that is the originator of the funds being moved. Most likely is your Sweep Firm Account", + "format": "uuid", + "type": "string" } }, "required": [ - "id" + "entry_type", + "from_account", + "entries" ], - "type": "object", - "x-examples": { - "Example 1": { - "adverse_media": "clear", - "created_at": "2021-06-10T15:37:03Z", - "id": "7572B870-EB4C-46A2-8B88-509194CCEE7E", - "monitored_lists": "clear", - "politically_exposed_person": "clear", - "result": "clear", - "sanction": "clear", - "status": "complete" - } - } + "title": "BatchJournalRequest", + "type": "object" }, - "CSWActivityV2": { - "allOf": [ - { - "$ref": "#/components/schemas/CommonNTAActivityV2" + "BatchJournalResponse": { + "description": "A forward batch journal result selected by `entry_type`, with an optional non-empty failure message.", + "discriminator": { + "mapping": { + "JNLC": "#/components/schemas/BatchJournalResponseJNLC", + "JNLS": "#/components/schemas/BatchJournalResponseJNLS" + }, + "propertyName": "entry_type" + }, + "examples": [ + { + "currency": "USD", + "description": "", + "entry_type": "JNLC", + "from_account": "8f8c8cee-2591-4f83-be12-82c659b5e748", + "id": "56f106e5-25a4-4eee-96fa-25bb05dc86bc", + "net_amount": "1000", + "price": null, + "qty": null, + "settle_date": null, + "status": "pending", + "symbol": null, + "system_date": null, + "to_account": "399f85f1-cbbd-4eaa-a934-70027fb5c1de" + }, + { + "currency": "USD", + "description": "this is a test journal", + "entry_type": "JNLS", + "from_account": "8f4f3d11-4483-4199-840f-6c5fe0b7ca24", + "id": "f45f67e8-d1fc-4136-aa4f-cf4460aecdfc", + "net_amount": null, + "price": "128.23", + "qty": "0.5", + "settle_date": "2020-12-24", + "status": "executed", + "symbol": "AAPL", + "system_date": "2020-12-24", + "to_account": "a7a7e666-6f2a-433c-8c33-17b66b8941fa" + }, + { + "currency": "USD", + "description": "", + "entry_type": "JNLC", + "error_message": "The destination account is not eligible to receive this journal.", + "from_account": "8f8c8cee-2591-4f83-be12-82c659b5e748", + "id": "c79bcd51-4bea-4d1d-9b3c-f37f5035b8f7", + "net_amount": "250", + "price": null, + "qty": null, + "settle_date": null, + "status": "refused", + "symbol": null, + "system_date": null, + "to_account": "d5f7dbea-cb40-4ea6-a56d-e30d572ee84f" + } + ], + "oneOf": [ + { + "$ref": "#/components/schemas/BatchJournalResponseJNLC" + }, + { + "$ref": "#/components/schemas/BatchJournalResponseJNLS" + } + ], + "title": "BatchJournalResponse" + }, + "BatchJournalResponseJNLC": { + "allOf": [ + { + "$ref": "#/components/schemas/JNLC" }, { "properties": { - "bank_transaction_id": { - "description": "The bank transaction's ID", - "format": "uuid", + "error_message": { + "description": "Description of why this journal transaction failed. Omitted for successful results.", + "minLength": 1, "type": "string" } }, "type": "object" } ], - "description": "Cash withdrawal", - "type": "object" - }, - "CommissionType": { - "default": "notional", - "description": "An enum to select how to interpret the value provided in the commission field.\n\n- notional:\nCharge commission on a per order basis. (When the `commission_type` field is omitted from the order request, this is used as the default).\n\n- qty:\nCharge commission on a per qty/contract basis, pro rated.\n\n- bps:\nThe percent commission you want to charge the end user on the order (expressed in bps). Alpaca will convert the order to a notional amount for purposes of calculating commission.\nCommission value in bps can have up to two decimal places.", - "enum": [ - "notional", - "qty", - "bps" + "description": "A cash journal result returned by the forward batch endpoint.", + "examples": [ + { + "currency": "USD", + "description": "", + "entry_type": "JNLC", + "from_account": "8f8c8cee-2591-4f83-be12-82c659b5e748", + "id": "56f106e5-25a4-4eee-96fa-25bb05dc86bc", + "net_amount": "1000", + "price": null, + "qty": null, + "settle_date": null, + "status": "pending", + "symbol": null, + "system_date": null, + "to_account": "399f85f1-cbbd-4eaa-a934-70027fb5c1de" + }, + { + "currency": "USD", + "description": "", + "entry_type": "JNLC", + "error_message": "The destination account is not eligible to receive this journal.", + "from_account": "8f8c8cee-2591-4f83-be12-82c659b5e748", + "id": "c79bcd51-4bea-4d1d-9b3c-f37f5035b8f7", + "net_amount": "250", + "price": null, + "qty": null, + "settle_date": null, + "status": "refused", + "symbol": null, + "system_date": null, + "to_account": "d5f7dbea-cb40-4ea6-a56d-e30d572ee84f" + } ], - "example": "qty", - "title": "CommissionType", - "type": "string" + "title": "BatchJournalResponseJNLC" }, - "CommonAcatActivityV2": { - "properties": { - "external_id": { - "description": "The ID that DTCC assigned to this transfer", - "type": "string" - }, - "hold_date": { - "description": "Hold date when the transfers settle", - "format": "date", - "minLength": 1, - "type": "string" + "BatchJournalResponseJNLS": { + "allOf": [ + { + "$ref": "#/components/schemas/JNLS" }, - "request_id": { - "description": "The ID for original ACATS request", - "format": "uuid", - "type": "string" + { + "properties": { + "error_message": { + "description": "Description of why this journal transaction failed. Omitted for successful results.", + "minLength": 1, + "type": "string" + } + }, + "type": "object" } - }, - "required": [ - "external_id", - "request_id" ], - "type": "object" + "description": "A securities journal result that preserves discriminator-complete batch response modeling, although current public batch requests support JNLC only.", + "examples": [ + { + "currency": "USD", + "description": "this is a test journal", + "entry_type": "JNLS", + "from_account": "8f4f3d11-4483-4199-840f-6c5fe0b7ca24", + "id": "f45f67e8-d1fc-4136-aa4f-cf4460aecdfc", + "net_amount": null, + "price": "128.23", + "qty": "0.5", + "settle_date": "2020-12-24", + "status": "executed", + "symbol": "AAPL", + "system_date": "2020-12-24", + "to_account": "a7a7e666-6f2a-433c-8c33-17b66b8941fa" + } + ], + "title": "BatchJournalResponseJNLS" }, - "CommonCDIVActivityV2": { + "Beneficiary": { + "description": "Beneficiary of an account", + "example": { + "date_of_birth": "1970-01-01", + "family_name": "Doe", + "given_name": "Jane", + "middle_name": "P", + "relationship": "spouse", + "share_pct": "100", + "tax_id": "xxx-xx-xxxx", + "tax_id_type": "USA_SSN", + "type": "primary" + }, "properties": { - "cusip": { - "description": "The CUSIP of the security involved with the activity", + "date_of_birth": { + "example": "1970-01-01", "type": "string" }, - "due_bill_off_date": { - "description": "When due bills stop applying for this event", - "format": "date", - "minLength": 1, + "family_name": { + "example": "Doe", "type": "string" }, - "due_bill_on_date": { - "description": "When due bills begin to apply for this event", - "format": "date", - "minLength": 1, + "given_name": { + "example": "Jane", "type": "string" }, - "ex_date": { - "description": "The ex_date for this corporate action", - "format": "date", - "minLength": 1, + "middle_name": { + "example": "P", "type": "string" }, - "foreign": { - "description": "Indicates if related to a non-US security", - "type": "boolean" - }, - "payable_date": { - "description": "The payable_date for this corporate action", - "format": "date", - "minLength": 1, + "relationship": { + "example": "spouse", "type": "string" }, - "rate": { - "description": "Dividend rate per share", + "share_pct": { + "example": "100", "type": "string" }, - "record_date": { - "description": "The record_date for this corporate action", - "format": "date", - "minLength": 1, + "tax_id": { + "example": "xxx-xx-xxxx", "type": "string" }, - "special": { - "description": "Indicates if this is a special dividend", - "type": "boolean" + "tax_id_type": { + "example": "USA_SSN", + "type": "string" }, - "symbol": { - "description": "The symbol of the security involved with the activity", + "type": { + "example": "primary", "type": "string" } }, "required": [ - "symbol", - "cusip", - "rate", - "foreign", - "special" + "given_name", + "middle_name", + "family_name", + "date_of_birth", + "tax_id", + "tax_id_type", + "relationship", + "type", + "share_pct" ], "type": "object" }, - "CommonCaActivityV2": { + "CDIVActivityV2": { "allOf": [ { - "$ref": "#/components/schemas/CommonNTAActivityV2" + "$ref": "#/components/schemas/CommonCaActivityV2" + }, + { + "$ref": "#/components/schemas/CommonCDIVActivityV2" }, { "properties": { - "ca_id": { - "description": "The unique identifier for this corporate action", - "format": "uuid", - "type": "string" - }, - "position_date": { - "description": "The position_date for this corporate action", - "format": "date", - "minLength": 1, + "cash_payout": { + "description": "Total cash amount paid", "type": "string" }, - "reorg_id": { - "description": "The reorg identifier, if present in the source corporate action definition", + "entitled_qty": { + "description": "Quantity of shares entitled to receive the dividend or return of capital", "type": "string" } }, "required": [ - "position_date" + "entitled_qty", + "cash_payout" ], "type": "object" } ], + "description": "Cash-dividend related details. Used for both Cash Dividend and Return of Capital.", "type": "object" }, - "CommonJournalActivityV2": { + "CGDActivityV2": { "allOf": [ { - "$ref": "#/components/schemas/CommonNTAActivityV2" + "$ref": "#/components/schemas/CommonCaActivityV2" }, { "properties": { - "journal_id": { - "description": "The journal's ID", - "format": "uuid", + "cash_payout": { + "description": "Cash amount paid for this leg", "type": "string" - } - }, - "type": "object" - } - ], - "type": "object" - }, - "CommonMAActivityV2": { - "properties": { - "acquiree_cusip": { - "description": "CUSIP of the acquiree", - "type": "string" - }, - "acquiree_rate": { - "description": "Rate of the acquiree", - "type": "string" - }, - "acquiree_symbol": { - "description": "Symbol of the acquiree", - "type": "string" - }, - "acquirer_cusip": { - "description": "CUSIP of the acquirer", - "type": "string" - }, - "acquirer_rate": { - "description": "Rate of the acquirer", - "type": "string" - }, - "acquirer_symbol": { - "description": "Symbol of the acquirer", - "type": "string" - }, - "effective_date": { - "description": "When the merger/acquisition becomes effective", - "format": "date", - "minLength": 1, - "type": "string" - }, - "payable_date": { - "description": "The payable date", - "format": "date", - "minLength": 1, - "type": "string" - } - }, - "required": [ - "acquiree_cusip", - "acquiree_symbol", - "effective_date", - "payable_date" - ], - "type": "object" - }, - "CommonNCActivityV2": { - "properties": { - "new_cusip": { - "description": "New CUSIP for the name change", - "type": "string" - }, - "new_symbol": { - "description": "New symbol for the name change", - "type": "string" - }, - "old_cusip": { - "description": "Old CUSIP for the name change", - "type": "string" - }, - "old_symbol": { - "description": "Old symbol for the name change", - "type": "string" - } - }, - "required": [ - "old_cusip", - "old_symbol", - "new_cusip", - "new_symbol" - ], - "type": "object" - }, - "CommonNTAActivityV2": { - "properties": { - "group_id": { - "description": "Optional group ID which can help grouping together related activities", - "format": "uuid", - "type": "string" - }, - "system_date": { - "description": "The date when the activity was booked", - "format": "date", - "minLength": 1, - "type": "string" - } - }, - "required": [ - "system_date" - ], - "type": "object" - }, - "CommonOPCAActivityV2": { - "allOf": [ - { - "$ref": "#/components/schemas/CommonCaActivityV2" - }, - { - "properties": { - "new_contract_symbol": { - "description": "The new contract symbol", + }, + "cusip": { + "description": "The CUSIP of the security involved with the activity", "type": "string" }, - "new_qty": { - "description": "used when the old contract's quantity is not equal to the new contract's quantity. Mutually exclusive with 'qty'.", + "entitled_qty": { + "description": "Quantity of shares entitled to receive the capital gains distribution", "type": "string" }, - "old_contract_symbol": { - "description": "The old contract symbol", + "ex_date": { + "description": "The ex_date for this corporate action", + "format": "date", + "minLength": 1, "type": "string" }, - "old_qty": { - "description": "used when the old contract's quantity is not equal to the new contract's quantity. Mutually exclusive with 'qty'.", + "isin": { + "description": "The ISIN of the security involved with the activity", "type": "string" }, - "qty": { - "description": "used when the old contract's quantity is equal to the new contract's quantity. Mutually exclusive with 'old_qty' and 'new_qty'", + "long_term_rate": { + "description": "Long-term capital gains distribution rate per share, when applicable", + "type": "string" + }, + "payable_date": { + "description": "The payable_date for this corporate action", + "format": "date", + "minLength": 1, + "type": "string" + }, + "rate": { + "description": "Distribution rate per share for this leg (LTCG or STCG)", + "type": "string" + }, + "record_date": { + "description": "The record_date for this corporate action", + "format": "date", + "minLength": 1, + "type": "string" + }, + "short_term_rate": { + "description": "Short-term capital gains distribution rate per share, when applicable", + "type": "string" + }, + "symbol": { + "description": "The symbol of the security involved with the activity", "type": "string" } }, "required": [ - "old_contract_symbol", - "new_contract_symbol" + "entitled_qty", + "cash_payout", + "symbol", + "cusip", + "rate" ], "type": "object" } ], + "description": "Capital gains distribution. Used for both Long-Term (LTCG) and Short-Term (STCG) sub-types. Each activity represents a single leg; `long_term_rate` and/or `short_term_rate` are populated based on the source event, and `rate` matches the leg's own rate.", "type": "object" }, - "CommonOptionsActivityV2": { - "allOf": [ + "CIPDocument": { + "description": "Represents results of checking a document for CIPInfo\n", + "examples": [ { - "$ref": "#/components/schemas/CommonNTAActivityV2" + "created_at": "2021-06-10T15:37:03Z", + "id": "55B9931A-3BE6-4BC0-9BDD-0B954E4A4632", + "image_integrity": "clear", + "result": "clear", + "status": "complete" } ], - "required": [ - "group_id" - ], - "type": "object" - }, - "CommonSDIVActivityV2": { "properties": { - "cusip": { - "description": "The CUSIP of the security involved with the activity", - "type": "string" + "age_validation": { + "$ref": "#/components/schemas/CIPResult" }, - "ex_date": { - "description": "The ex_date for this corporate action", - "format": "date", - "minLength": 1, - "type": "string" + "comprised_document": { + "$ref": "#/components/schemas/CIPResult" }, - "payable_date": { - "description": "The payable_date for this corporate action", - "format": "date", - "minLength": 1, + "created_at": { + "description": "Datetime for when this check was done", + "format": "date-time", "type": "string" }, - "rate": { - "description": "Dividend rate per share", - "type": "string" + "data_comparison": { + "$ref": "#/components/schemas/CIPResult" }, - "record_date": { - "description": "The record_date for this corporate action", - "format": "date", - "minLength": 1, + "data_comparison_breakdown": { + "description": "json object representing the results of the various sub-checks\n done when calculating the result on `data_comparison`. Example: {\"date_of_birth\": \"clear\",\n \"date_of_expiry\": \"clear\" \"document_numbers\": \"clear\", \"document_type\": \"clear\", \"first_name\": \"clear\",\n \"gender\": \"clear\", \"issuing_country\": \"clear\", \"last_name\": \"clear\"}", "type": "string" }, - "symbol": { - "description": "The symbol of the security involved with the activity", - "type": "string" - } - }, - "required": [ - "symbol", - "cusip", - "rate" - ], - "type": "object" - }, - "CommonSpinoffActivityV2": { - "properties": { - "due_bill_redemption_date": { - "description": "When due bills related to the spinoff are redeemed", + "date_of_birth": { + "description": "Datetime for when this check was done", "format": "date", - "minLength": 1, "type": "string" }, - "ex_date": { - "description": "The ex_date for this corporate action", + "date_of_expiry": { + "description": "Datetime for when this check was done", "format": "date", - "minLength": 1, "type": "string" }, - "new_cusip": { - "description": "CUSIP of the new security", - "type": "string" + "document_numbers": { + "description": "Number of the document that was checked", + "items": { + "type": "string" + }, + "type": "array" }, - "new_price": { - "description": "Market price of new shares after the spinoff", + "document_type": { + "description": "Type of the document that was checked", "type": "string" }, - "new_rate": { - "description": "Ratio of new shares received", + "first_name": { + "description": "First name extracted from the document", "type": "string" }, - "new_symbol": { - "description": "Symbol of the new security", + "gender": { + "description": "Gender info extracted from the document", "type": "string" }, - "payable_date": { - "description": "The payable_date for this corporate action", - "format": "date", - "minLength": 1, + "id": { + "description": "Your internal ID of check", "type": "string" }, - "record_date": { - "description": "The record_date for this corporate action", - "format": "date", - "minLength": 1, - "type": "string" + "image_integrity": { + "$ref": "#/components/schemas/CIPResult" }, - "source_cusip": { - "description": "CUSIP of the parent security", + "image_integrity_breakdown": { + "description": "json object representing the results of the various sub-checks done\n when calculating the result on `image_integrity`. Example: example: {\"colour_picture\": \"clear\",\n \"conclusive_document_quality\": \"clear\", \"image_quality\": \"clear\", \"supported_document\": \"clear\"}", "type": "string" }, - "source_price": { - "description": "Market price of parent shares before the spinoff", + "issuing_country": { + "description": "Country for which issued the document", "type": "string" }, - "source_rate": { - "description": "Ratio of parent shares", + "last_name": { + "description": "Last name extracted from the document", "type": "string" }, - "source_symbol": { - "description": "Symbol of the parent security", + "nationality": { + "description": "Nationality extracted from the document", + "type": "string" + }, + "police_record": { + "$ref": "#/components/schemas/CIPStatus" + }, + "result": { + "$ref": "#/components/schemas/CIPResult" + }, + "status": { + "$ref": "#/components/schemas/CIPStatus" + }, + "visual_authenticity": { + "description": "json object representing the various sub-checks done when determining\n whether visual (non-textual) elements are correct given the document type. Example: {\n \"digital_tampering\": \"clear\", \"face_detection\": \"clear\", \"fonts\": \"clear\", \"original_document_present\":\n \"clear\", \"picture_face_integrity\": \"clear\", \"security_features\": \"clear\", \"template\": \"clear\"}", "type": "string" } }, "required": [ - "source_cusip", - "source_symbol", - "source_rate", - "source_price", - "new_cusip", - "new_symbol", - "new_rate", - "new_price" + "id" ], "type": "object" }, - "CommonSplitActivityV2": { + "CIPIdentity": { + "examples": [ + { + "address": "clear", + "created_at": "2021-06-10T15:37:03Z", + "date_of_birth": "clear", + "id": "28E1CCE8-1B1A-4472-9AD4-C6C5B7C3A6AF", + "result": "clear", + "sources": "clear", + "status": "complete" + } + ], "properties": { - "new_cusip": { - "description": "CUSIP of the new security after the split", - "type": "string" + "address": { + "$ref": "#/components/schemas/CIPResult" }, - "new_rate": { - "description": "Ratio of new shares received", + "address_breakdown": { + "description": "a json object representing the breakdown of the `address` field. For example:\n {\"credit_agencies\": {\"result\": \"clear\",\"properties\":{\"number_of_matches\":\"1\"}}", "type": "string" }, - "old_cusip": { - "description": "CUSIP of the old security before the split", + "created_at": { + "description": "datetime when identity check happened", "type": "string" }, - "old_rate": { - "description": "Ratio of old shares exchanged", + "date_of_birth": { + "$ref": "#/components/schemas/CIPResult" + }, + "date_of_birth_breakdown": { + "description": "a json object representing the breakdown of the `date_of_birth` field.\n For example: example: {\"credit_agencies\":{\"result\": \"clear\",\"properties\": {\"number_of_matches\": \"1\"}}", "type": "string" }, - "payable_date": { - "description": "The payable_date for this corporate action", - "format": "date", - "minLength": 1, + "id": { + "description": "Your internal ID of check", "type": "string" - } - }, - "required": [ - "old_cusip", - "new_cusip", - "old_rate", - "new_rate" - ], - "type": "object" - }, - "CommonSplitStockActivityV2": { - "allOf": [ - { - "$ref": "#/components/schemas/CommonCaActivityV2" }, - { - "$ref": "#/components/schemas/CommonSplitActivityV2" + "matched_address": { + "$ref": "#/components/schemas/CIPResult" }, - { - "properties": { - "new_qty": { - "description": "The new quantity after the split", - "type": "string" - }, - "old_qty": { - "description": "The old quantity before the split", - "type": "string" - } - }, - "required": [ - "old_qty", - "new_qty" - ], - "type": "object" - } - ], - "type": "object" - }, - "CommonVOFSubtypeActivityV2": { - "properties": { - "new_cusip": { - "description": "CUSIP of the new security", + "matched_addresses": { + "description": "datetime when identity check happened", "type": "string" }, - "new_symbol": { - "description": "Symbol of the new security", - "type": "string" + "result": { + "$ref": "#/components/schemas/CIPResult" }, - "source_cusip": { - "description": "CUSIP of the parent security", + "sources": { + "$ref": "#/components/schemas/CIPResult" + }, + "sources_breakdown": { + "description": "a json object representing the breakdown of `sources` field. For example:\n {\"total_sources\": {\"result\": \"clear\",\"properties\": {\"total_number_of_sources\": \"3\"}}}", "type": "string" }, - "source_symbol": { - "description": "Symbol of the parent security", + "status": { + "$ref": "#/components/schemas/CIPStatus" + }, + "tax_id": { + "$ref": "#/components/schemas/CIPResult" + }, + "tax_id_breakdown": { + "description": "a json object representing the breakdown of the `tax_id` field", "type": "string" } }, "required": [ - "source_cusip", - "source_symbol" + "id" ], "type": "object" }, - "Contact": { - "description": "Contact is the model for the account owner contact information.\n", + "CIPInfo": { + "description": "Customer Identification Program (CIP) information for an account applicant.", "properties": { - "city": { - "example": "San Mateo", + "account_id": { + "description": "UUID of the Account instance this CIPInfo is for", + "format": "uuid", "type": "string" }, - "country": { - "description": "country code in ISO 3166-1 alpha-3 format, representing the country the person/entity resides in.", - "example": "USA", + "created_at": { + "format": "date-time", "type": "string" }, - "email_address": { - "example": "john.doe@example.com", - "format": "email", - "type": "string" + "document": { + "$ref": "#/components/schemas/CIPDocument" }, - "phone_number": { - "description": "Phone number should include the country code, format: \"+15555555555\"", - "example": "+15556667788", + "id": { + "description": "ID of this CIPInfo", + "format": "uuid", "type": "string" }, - "postal_code": { - "example": "94401", - "type": "string" + "identity": { + "$ref": "#/components/schemas/CIPIdentity" }, - "state": { - "description": "Required if the country or country_of_tax_residence (in the identity model below) is 'USA'.", - "example": "CA", - "type": "string" + "kyc": { + "$ref": "#/components/schemas/CIPKYC" }, - "street_address": { - "description": "The user's street address. If multiple lines in address, pass in as additional array elements. Maximum of 3 objects in array", + "photo": { + "$ref": "#/components/schemas/CIPPhoto" + }, + "provider_name": { + "description": "List of KYC providers this information came from", "items": { - "$ref": "#/components/schemas/StreetAddress" + "$ref": "#/components/schemas/CIPProvider" }, "type": "array" }, - "unit": { - "description": "The specific apartment number if applicable", + "updated_at": { + "format": "date-time", "type": "string" + }, + "watchlist": { + "$ref": "#/components/schemas/CIPWatchlist" } }, - "required": [ - "email_address", - "phone_number", - "street_address", - "city" - ], + "title": "CIPInfo", "type": "object" }, - "CorrespondentLimit": { + "CIPKYC": { + "description": "Represents Know Your Customer (KYC) info for a CIPInfo", + "examples": [ + { + "address": "42 Faux St", + "applicant_name": "John Doe", + "approval_status": "approved", + "approved_at": "2021-06-10T15:38:03Z", + "approved_by": "Jane Doe", + "check_completed_at": "2021-06-10T15:37:03Z", + "check_initiated_at": "2021-06-10T15:37:03Z", + "country_of_residency": "USA", + "date_of_birth": "1970-12-01", + "email_address": "johndoe@example.com", + "id": "CBDAD1C4-1047-450E-BAE5-B6C406F509B4", + "id_number": "jd0000123456789", + "ip_address": "127.0.0.1", + "kyc_completed_at": "2021-06-10T15:37:03Z", + "nationality": "American", + "postal_code": "94401", + "risk_level": "LOW" + } + ], "properties": { - "amount_available": { - "format": "decimal", + "address": { + "description": "Concatenated street address, city, state and country of applicant", "type": "string" }, - "amount_in_use": { - "format": "decimal", + "applicant_name": { + "description": "Given and family name of applicant", "type": "string" }, - "amount_limit": { - "format": "decimal", - "type": "string" - } - }, - "type": "object" - }, - "CountryInfo": { - "description": "Represents the info for a country.", - "properties": { - "crypto_risk_rating": { - "description": "crypto risk rating of the country", + "approval_status": { + "description": "Approval status of KYC check", "enum": [ - "low", - "medium", - "high", - "prohibited" + "approved", + "rejected" ], + "example": "approved", "type": "string" }, - "crypto_supported_states": { - "description": "states where Alpaca supports crypto trading", - "items": { - "description": "the state code defined by ISO 3166-2", - "type": "string" - }, - "type": "array" + "approved_at": { + "description": "Reason for approving this KYC check", + "format": "date-time", + "type": "string" }, - "full_name": { - "description": "The full name of the country definded by ISO 3166-1", - "example": "Åland Islands", - "minLength": 1, - "type": "string" - }, - "securities_risk_rating": { - "description": "the securities risk rating of the country", - "enum": [ - "low", - "medium", - "high", - "prohibited" - ], - "type": "string" - } - }, - "required": [ - "full_name", - "securities_risk_rating", - "crypto_risk_rating" - ], - "title": "CountryInfo", - "type": "object", - "x-examples": { - "example": "\"ZAF\": {\n \"full_name\": \"South Africa\",\n \"crypto_risk_rating\": \"high\",\n \"securities_risk_rating\": \"high\"\n}\n", - "example-1": "\"USA\": {\n \"full_name\": \"United States of America\",\n \"crypto_risk_rating\": \"medium\",\n \"securities_risk_rating\": \"medium\",\n \"crypto_supported_states\": [\n \"CA\",\n \"CT\",\n \"GA\",\n \"IA\",\n \"ID\",\n \"IL\",\n \"IN\",\n \"KS\",\n \"KY\",\n \"MA\",\n \"MD\",\n \"ME\",\n \"MI\",\n \"MS\",\n \"MO\",\n \"MT\",\n \"NC\",\n \"ND\",\n \"NE\",\n \"OH\",\n \"RI\",\n \"SD\",\n \"UT\",\n \"VT\",\n \"WA\"\n ]\n}\n" - } - }, - "CountryInfos": { - "additionalProperties": { - "$ref": "#/components/schemas/CountryInfo" - }, - "description": "Represents the info for countries as a map where the keys are the ISO 3166 country codes.", - "title": "CountryInfos", - "type": "object" - }, - "CreateACHRelationshipRequest": { - "description": "Represents the fields used in creation of a new ACHRelationship.\n\nYou can create an ACHRelationship by passing the required fields here or if you have an account with Plaid you can use our integration with Plaid to create a relationship.\n\nPlease see the documentation [here](https://alpaca.markets/docs/api-references/broker-api/funding/ach/#plaid-integration-for-bank-transfers) for more info on using Plaid with Alpaca", - "properties": { - "account_owner_name": { - "minLength": 1, + "approved_by": { + "description": "Identifier of who approved KYC check", "type": "string" }, - "bank_account_number": { - "description": "In sandbox, this still must be a valid format", - "minLength": 1, + "approved_reason": { + "description": "Datetime that this KYC check was approved", "type": "string" }, - "bank_account_type": { - "description": "Must be `CHECKING` or `SAVINGS`", - "enum": [ - "CHECKING", - "SAVINGS" - ], - "minLength": 1, + "check_completed_at": { + "description": "completion datetime of KYC check", + "format": "date-time", "type": "string" }, - "bank_routing_number": { - "description": "In sandbox, this still must be a valid format", - "minLength": 1, + "check_initiated_at": { + "description": "start datetime of KYC check", + "format": "date-time", "type": "string" }, - "instant": { - "type": "boolean" - }, - "nickname": { - "minLength": 1, + "country_of_residency": { + "description": "country for `address` field", "type": "string" }, - "processor_token": { - "description": "If using Plaid, you can specify a Plaid processor token here ", - "type": "string" - } - }, - "required": [ - "account_owner_name", - "bank_account_type", - "bank_account_number", - "bank_routing_number" - ], - "title": "CreateACHRelationshipRequest", - "type": "object" - }, - "CreateBankRequest": { - "description": "Represents the possible fields to send when creating a new associated Bank resource for an account", - "properties": { - "account_number": { + "date_of_birth": { + "description": "DOB of applicant", + "format": "date", "type": "string" }, - "bank_code": { - "description": "9-Digit ABA RTN (Routing Number) or BIC", + "email_address": { + "description": "email address of applicant", "type": "string" }, - "bank_code_type": { - "description": "ABA (Domestic) or BIC (International)", - "enum": [ - "ABA", - "BIC" - ], + "id": { + "description": "Your internal ID of check", + "format": "uuid", "type": "string" }, - "city": { - "description": "Only for international banks, ie if bank_code_type = BIC", + "ip_address": { + "description": "IP address of applicant at time of KYC check", "type": "string" }, - "country": { - "description": "Only for international banks, ie if bank_code_type = BIC", + "kyc_completed_at": { + "description": "Datetime that KYC check was completed at", + "format": "date-time", "type": "string" }, - "extra_fields": { - "$ref": "#/components/schemas/BankAdditionalFields" - }, - "name": { - "description": "Name of recipient bank", + "nationality": { + "description": "nationality of applicant", "type": "string" }, "postal_code": { - "description": "Only for international banks, ie if bank_code_type = BIC. Minimum of 3 characters", + "description": "postal code for `address` field", "type": "string" }, - "state_province": { - "description": "Only for international banks, ie if bank_code_type = BIC", - "type": "string" + "risk_categories": { + "description": "The list of risk categories returned by the KYC provider or assessed", + "items": { + "type": "string" + }, + "type": "array" }, - "street_address": { - "description": "Only for international banks, ie if bank_code_type = BIC", + "risk_level": { + "description": "Overall risk level returned by KYC provider or assessed", "type": "string" + }, + "risk_score": { + "description": "Overall risk score returned by KYC provider or assessed", + "type": "integer" } }, "required": [ - "name", - "bank_code", - "bank_code_type", - "account_number" + "id" ], - "title": "CreateBankRequest", + "title": "CIPKYCInfo", "type": "object" }, - "CreateCryptoTransferRequest": { - "properties": { - "address": { - "description": "The destination wallet address", - "type": "string" - }, - "amount": { - "description": "The amount, denoted in the specified asset, to be withdrawn from the user's wallet", - "type": "string" - }, - "asset": { - "type": "string" - }, - "chain": { - "description": "Optional chain identifier. The blockchain network used for the withdrawal.", - "enum": [ - "SOL", - "ETH", - "BTC", - "XRP", - "ARB" - ], - "type": "string" + "CIPPhoto": { + "description": "Represents the results of checking a Photo for CIPInfo", + "examples": [ + { + "created_at": "2021-06-10T15:37:03Z", + "face_comparison": "clear", + "id": "0DD13020-F0FD-4B5A-B58F-BC1885E90A6D", + "result": "clear", + "status": "complete" } - }, - "required": [ - "amount", - "address", - "asset" ], - "title": "CreateCryptoTransferRequest", - "type": "object" - }, - "CreateFundingWalletWithdrawalRequest": { "properties": { - "desired_currency": { + "created_at": { + "description": "datetime of when the check happened", "type": "string" }, - "payment_type": { - "$ref": "#/components/schemas/FundingDetailPaymentType" + "face_comparison": { + "$ref": "#/components/schemas/CIPResult" }, - "usd_amount": { - "format": "decimal", - "type": "string" - } - }, - "type": "object" - }, - "CreateIFTransferRequest": { - "description": "Request to create a new instant funding transfer\n", - "properties": { - "account_no": { + "face_comparison_breakdown": { + "description": "a json object representing the breakdown of sub-checks done in\n `face_comparison`. Example: {\"face_match\":{\"result\": \"clear\",\"properties\":{\"score\": \"80\"}}}", "type": "string" }, - "amount": { - "format": "decimal", + "id": { + "description": "Your internal ID of the check", "type": "string" }, - "source_account_no": { + "image_integrity": { + "$ref": "#/components/schemas/CIPResult" + }, + "image_integrity_breakdown": { + "description": "a json object representing the breakdown of sub-checks done in\n `image_integrity`. Example {\"face_detected\":{\"result\": \"clear\"},\"source_integrity\": {\"result\": \"clear\"}}", "type": "string" - } - }, - "required": [ - "account_no", - "source_account_no", - "amount" - ], - "type": "object" - }, - "CreateJITSettlementRequest": { - "description": "Request to create a new settlement. All transfers included must use the same source account, and the total amount received must be in the same currency as the base currency of the source account.\n", - "properties": { - "accounts": { - "items": { - "$ref": "#/components/schemas/SettlementAccount" - }, - "type": "array" }, - "additional_info": { - "description": "Additional remittance information. Include if the funding source may require additional information to associate to the source account or settlement.\n", + "result": { + "$ref": "#/components/schemas/CIPResult" + }, + "status": { + "description": "Overall status of the check. Either `complete` or `withdrawn`.", + "enum": [ + "complete", + "withdrawn" + ], "type": "string" }, - "asset_class": { - "$ref": "#/components/schemas/JITAssetClass" + "visual_authenticity": { + "$ref": "#/components/schemas/CIPResult" }, - "currency": { - "description": "The currency of the settlement. All accounts identified should have this base currency.\n", + "visual_authenticity_breakdown": { + "description": "a json object representing the breakdown of sub-checks don in\n `visual_authenticity`. Example {\"spoofing_detection\": {\"result\": \"clear\",\"properties\": {\"score\": \"26\"}}}}", "type": "string" } }, "required": [ - "currency", - "asset_class", - "accounts" + "id" ], "type": "object" }, - "CreateJournalRequest": { - "description": "Journals API allows you to move cash or securities from one account to another.\n\nThis model represents the fields you can specify when creating a Journal\n\nFixture Rules\n\n- No Fixtures\n - anything below limit is executed immediately\n - anything above limit is pending until executed at EOD,\n- With Fixtures\n - any status = rejected will be rejected EOD\n - any status = pending will be pending forever", - "properties": { - "amount": { - "description": "Required if `entry_type` = `JNLC`", + "CIPProvider": { + "description": "\"alloy\" - ALLOY\n\"trulioo\" - TRULIOO\n\"onfido\" - ONFIDO\n\"veriff\" - VERIFF\n\"jumio\" - JUMIO\n\"getmati\" - GETMATI", + "title": "CIPProvider", + "type": "string" + }, + "CIPResult": { + "description": "The result of the check. Either `clear` or `consider`.", + "title": "CIPResult", + "type": "string" + }, + "CIPStatus": { + "description": "An enum representing the status of the CIPInfo\n\n\"complete\"\n\n\"withdrawn\"", + "title": "CIPStatus", + "type": "string" + }, + "CIPWatchlist": { + "description": "Represents the result of checking to see if the applicant is in any watchlists for a CIPInfo", + "examples": [ + { + "adverse_media": "clear", + "created_at": "2021-06-10T15:37:03Z", + "id": "7572B870-EB4C-46A2-8B88-509194CCEE7E", + "monitored_lists": "clear", + "politically_exposed_person": "clear", + "result": "clear", + "sanction": "clear", + "status": "complete" + } + ], + "properties": { + "adverse_media": { + "$ref": "#/components/schemas/CIPResult" + }, + "created_at": { + "description": "datetime when check happened", "type": "string" }, - "currency": { + "id": { + "description": "Your internal ID of check", "type": "string" }, - "description": { - "description": "Max 1024 characters. Can include fixtures for amounts that are above the transaction limit", - "maxLength": 1024, + "monitored_lists": { + "$ref": "#/components/schemas/CIPResult" + }, + "politically_exposed_person": { + "$ref": "#/components/schemas/CIPResult" + }, + "records": { + "description": "a json object. Example [{\"text\": \"Record info\"}]", "type": "string" }, - "entry_type": { - "$ref": "#/components/schemas/JournalEntryType" + "result": { + "$ref": "#/components/schemas/CIPResult" }, - "from_account": { - "description": "The account_id you wish to journal from", - "format": "uuid", + "sanction": { + "$ref": "#/components/schemas/CIPResult" + }, + "status": { + "$ref": "#/components/schemas/CIPStatus" + } + }, + "required": [ + "id" + ], + "type": "object" + }, + "CSWActivityV2": { + "allOf": [ + { + "$ref": "#/components/schemas/CommonNTAActivityV2" + }, + { + "properties": { + "bank_transaction_id": { + "description": "The bank transaction's ID", + "format": "uuid", + "type": "string" + } + }, + "type": "object" + } + ], + "description": "Cash withdrawal", + "type": "object" + }, + "CommissionType": { + "default": "notional", + "description": "An enum to select how to interpret the value provided in the commission field.\n\n- notional:\nCharge commission on a per order basis. (When the `commission_type` field is omitted from the order request, this is used as the default).\n\n- qty:\nCharge commission on a per qty/contract basis, pro rated.\n\n- bps:\nThe percent commission you want to charge the end user on the order (expressed in bps). Alpaca will convert the order to a notional amount for purposes of calculating commission.\nCommission value in bps can have up to two decimal places.", + "enum": [ + "notional", + "qty", + "bps" + ], + "example": "qty", + "title": "CommissionType", + "type": "string" + }, + "CommonAcatActivityV2": { + "properties": { + "external_id": { + "description": "The ID that DTCC assigned to this transfer", + "type": "string" + }, + "hold_date": { + "description": "Hold date when the transfers settle", + "format": "date", "minLength": 1, "type": "string" }, - "qty": { - "description": "Required if `entry_type` = `JNLS`", + "request_id": { + "description": "The ID for original ACATS request", + "format": "uuid", + "type": "string" + } + }, + "required": [ + "external_id", + "request_id" + ], + "type": "object" + }, + "CommonCDIVActivityV2": { + "properties": { + "cusip": { + "description": "The CUSIP of the security involved with the activity", "type": "string" }, - "symbol": { - "description": "Required if `entry_type` = `JNLS`", + "due_bill_off_date": { + "description": "When due bills stop applying for this event", + "format": "date", + "minLength": 1, "type": "string" }, - "to_account": { - "description": "The account_id you wish to journal to", - "format": "uuid", + "due_bill_on_date": { + "description": "When due bills begin to apply for this event", + "format": "date", "minLength": 1, "type": "string" }, - "transmitter_account_number": { - "description": "Max 255 characters. See more details about [Travel Rule](https://alpaca.markets/docs/broker/integration/funding/#travel-rule) in our main documentation.", - "maxLength": 255, + "ex_date": { + "description": "The ex_date for this corporate action", + "format": "date", + "minLength": 1, "type": "string" }, - "transmitter_address": { - "description": "Max 255 characters. See more details about [Travel Rule](https://alpaca.markets/docs/broker/integration/funding/#travel-rule) in our main documentation.", - "maxLength": 255, + "foreign": { + "description": "Indicates if related to a non-US security. Serialized as the JSON strings `\"true\"` or `\"false\"`, not a JSON boolean.", + "enum": [ + "true", + "false" + ], + "example": "false", "type": "string" }, - "transmitter_financial_institution": { - "description": "Max 255 characters. See more details about [Travel Rule](https://alpaca.markets/docs/broker/integration/funding/#travel-rule) in our main documentation.", - "maxLength": 255, + "isin": { + "description": "The ISIN of the security involved with the activity", "type": "string" }, - "transmitter_name": { - "description": "Max 255 characters. See more details about [Travel Rule](https://alpaca.markets/docs/broker/integration/funding/#travel-rule) in our main documentation.", - "maxLength": 255, + "payable_date": { + "description": "The payable_date for this corporate action", + "format": "date", + "minLength": 1, "type": "string" }, - "transmitter_timestamp": { - "description": "RFC 3339 format. See more details about [Travel Rule](https://alpaca.markets/docs/broker/integration/funding/#travel-rule) in our main documentation.", - "format": "date-time", + "rate": { + "description": "Dividend rate per share", + "type": "string" + }, + "record_date": { + "description": "The record_date for this corporate action", + "format": "date", + "minLength": 1, + "type": "string" + }, + "special": { + "description": "Indicates if this is a special dividend. Serialized as the JSON strings `\"true\"` or `\"false\"`, not a JSON boolean.", + "enum": [ + "true", + "false" + ], + "example": "false", + "type": "string" + }, + "symbol": { + "description": "The symbol of the security involved with the activity", "type": "string" } }, "required": [ - "to_account", - "from_account", - "entry_type" + "symbol", + "cusip", + "rate", + "foreign", + "special" ], - "type": "object", - "x-examples": { - "example-1": { - "amount": "51", - "description": "test text /fixtures/status=rejected/fixtures/", - "entry_type": "JNLC", - "from_account": "c94bu7rn-4483-4199-840f-6c5fe0b7ca24", - "to_account": "fn68sbrk-6f2a-433c-8c33-17b66b8941fa" + "type": "object" + }, + "CommonCaActivityV2": { + "allOf": [ + { + "$ref": "#/components/schemas/CommonNTAActivityV2" + }, + { + "properties": { + "ca_id": { + "description": "The unique identifier for this corporate action", + "format": "uuid", + "type": "string" + }, + "position_date": { + "description": "The position_date for this corporate action", + "format": "date", + "minLength": 1, + "type": "string" + }, + "reorg_id": { + "description": "The reorg identifier, if present in the source corporate action definition", + "type": "string" + } + }, + "required": [ + "position_date" + ], + "type": "object" } - } + ], + "type": "object" }, - "CreateOrderRequest": { + "CommonFixedIncomeInterestActivityV2": { + "description": "Fixed income interest fields (INT/FI). Variants from the same activity type:\n\n- Coupon: `interest_type` is `coupon`. Includes `rate` (no `price` / `accrued_interest_rate`). No `order_id`.\n- Accrued interest on call: `interest_type` is `accrued`. Includes `price` and `accrued_interest_rate` (no `rate`). No `order_id`.\n- Trade accrued: `interest_type` is `accrued`. Includes `order_id` so the interest NTA can be linked to the fill. `parent_id` is set when a parent trade `ref_id` is available.", "properties": { - "client_order_id": { - "description": "A unique identifier for the order. Automatically generated if not sent. (<= 128 characters)", - "example": "eb9e2aaa-f71a-4f51-b5b4-52a6c565dad4", - "maxLength": 128, + "accrued_interest_rate": { + "description": "Accrued interest rate (present for accrued-interest-on-call variants)", + "format": "decimal", "type": "string" }, - "commission": { - "description": "The commission you want to collect from the user.", - "example": "1.0", + "cash_payout": { + "description": "The cash payout for this interest activity", "format": "decimal", "type": "string" }, - "commission_bps": { - "deprecated": true, - "description": "**deprecated**: Please use the commission_type = bps instead and set the desired bps value in the `commission` field.\nThe percent commission you want to charge the end user on the order (expressed in bps). Alpaca will convert the order to a notional amount for purposes of calculating commission.\n", - "example": "10", - "format": "decimal", + "cusip": { + "description": "The CUSIP of the security involved with the activity", "type": "string" }, - "commission_type": { - "$ref": "#/components/schemas/CommissionType" + "entitled_qty": { + "description": "The entitled quantity (principal / face amount)", + "example": "1000", + "type": "string" }, - "extended_hours": { - "description": "Defaults to false. If true, order will be eligible for execution in the pre-market, after-hours, and overnight sessions. Only works with type `limit` and time_in_force set to either `day` or `gtc`.", - "example": false, - "type": "boolean" + "interest_type": { + "$ref": "#/components/schemas/FixedIncomeInterestType" }, - "instructions": { + "isin": { + "description": "The ISIN of the security involved with the activity", "type": "string" }, - "legs": { - "description": "list of order legs (<= 4)", - "items": { - "$ref": "#/components/schemas/MLegOrderLeg" - }, - "maxItems": 4, - "type": "array" - }, - "limit_price": { - "description": "Required if type is `limit` or `stop_limit`.\n- In case of `mleg`, the limit_price parameter is expressed with the following notation:\n - A positive value indicates a debit, representing a cost or payment to be made.\n - A negative value signifies a credit, reflecting an amount to be received.\n- In case of Fied Income, the price is expressed in percentage of par value (face value).\nPrice is always clean price, meaning it does not include accrued interest.", - "example": "3.14", - "format": "decimal", + "order_id": { + "description": "The order that generated this accrued interest. Present for trade-accrued INT/FI.", + "example": "257bf308-a6b7-4777-8cf6-00dc441dc4d3", + "format": "uuid", "type": "string" }, - "notional": { - "description": "Dollar amount to trade. Cannot work with qty. Only market and limit orders supported with time_in_force = day; Only limit orders for extended hours.", - "example": "3", - "format": "decimal", + "parent_id": { + "description": "The ref_id of the parent trade this interest is attached to. Present for trade-accrued INT/FI when a parent trade `ref_id` is available.", + "example": "46aecae1-4629-4bcf-b441-0d8c847a1787", + "format": "uuid", "type": "string" }, - "order_class": { - "$ref": "#/components/schemas/OrderClass" - }, - "position_intent": { - "$ref": "#/components/schemas/PositionIntent" + "payment_date": { + "description": "The payment date", + "format": "date", + "type": "string" }, - "qty": { - "description": "- For equities, the number of shares to trade. Can be fractionable for only market and day order types.\n- Required for `mleg` order class, represents the number of units to trade of this strategy.\n- For Fixed Income securities, qty represents the order size in par value (face value).\nFor example, to place an order for 1 bond with a face value of $1,000, provide a qty of 1000.\n", - "example": "4.124", + "price": { + "description": "Call price used for accrued-interest-on-call variants", "format": "decimal", "type": "string" }, - "side": { - "$ref": "#/components/schemas/OrderSide" + "rate": { + "description": "Coupon rate amount (present for regular coupon payments)", + "format": "decimal", + "type": "string" }, - "source": { + "record_date": { + "description": "The record date", + "format": "date", "type": "string" + } + }, + "required": [ + "entitled_qty", + "cash_payout", + "cusip", + "interest_type" + ], + "type": "object" + }, + "CommonJournalActivityV2": { + "allOf": [ + { + "$ref": "#/components/schemas/CommonNTAActivityV2" }, - "stop_loss": { - "description": "Takes in a string/number values for stop_price and limit_price", + { "properties": { - "limit_price": { - "example": "3.14", - "format": "decimal", - "type": "string" - }, - "stop_price": { - "example": "3.14", - "format": "decimal", + "journal_id": { + "description": "The journal's ID", + "format": "uuid", "type": "string" } }, "type": "object" + } + ], + "description": "Shared fields for journal non-trade activity records.", + "type": "object" + }, + "CommonMAActivityV2": { + "properties": { + "acquiree_cusip": { + "description": "CUSIP of the acquiree", + "type": "string" }, - "stop_price": { - "description": "Required if type is stop or stop_limit", - "example": "3.14", - "format": "decimal", + "acquiree_isin": { + "description": "ISIN of the acquiree", "type": "string" }, - "subtag": { + "acquiree_rate": { + "description": "Rate of the acquiree", "type": "string" }, - "swap_fee_bps": { - "format": "decimal", + "acquiree_symbol": { + "description": "Symbol of the acquiree", "type": "string" }, - "symbol": { - "description": "Symbol or asset ID to identify the asset to trade. Required for all order classes except for `mleg`.", - "example": "AAPL", + "acquirer_cusip": { + "description": "CUSIP of the acquirer", "type": "string" }, - "take_profit": { - "description": "Takes in a string/number value for limit_price", - "properties": { - "limit_price": { - "example": "3.14", - "format": "decimal", - "type": "string" - } - }, - "type": "object" + "acquirer_isin": { + "description": "ISIN of the acquirer", + "type": "string" }, - "time_in_force": { - "$ref": "#/components/schemas/TimeInForce" + "acquirer_rate": { + "description": "Rate of the acquirer", + "type": "string" }, - "trail_percent": { - "description": "If type is trailing_stop, then one of trail_price or trail_percent is required", - "example": "5.0", - "format": "decimal", + "acquirer_symbol": { + "description": "Symbol of the acquirer", "type": "string" }, - "trail_price": { - "description": "If type is trailing_stop, then one of trail_price or trail_percent is required", - "example": "3.14", - "format": "decimal", + "effective_date": { + "description": "When the merger/acquisition becomes effective", + "format": "date", + "minLength": 1, "type": "string" }, - "type": { - "$ref": "#/components/schemas/OrderType" + "payable_date": { + "description": "The payable date", + "format": "date", + "minLength": 1, + "type": "string" } }, "required": [ - "type", - "time_in_force" + "acquiree_cusip", + "acquiree_symbol", + "effective_date", + "payable_date" ], "type": "object" }, - "CreateSettlementRequest": { - "description": "Request to create a new settlement. All transfers included must use the same source account, and the total amount received must be in the same currency as the base currency of the source account as well as the transmitter information.\n", + "CommonNCActivityV2": { "properties": { - "additional_info": { - "description": "Additional remittance information. Include if the funding source may require additional information to associate to the source account or settlement.\n", + "new_cusip": { + "description": "New CUSIP for the name change", "type": "string" }, - "transfers": { - "items": { - "$ref": "#/components/schemas/SettlementTransfer" - }, - "type": "array" + "new_symbol": { + "description": "New symbol for the name change", + "type": "string" + }, + "old_cusip": { + "description": "Old CUSIP for the name change", + "type": "string" + }, + "old_symbol": { + "description": "Old symbol for the name change", + "type": "string" } }, "required": [ - "transfers" + "old_cusip", + "old_symbol", + "new_cusip", + "new_symbol" ], "type": "object" }, - "CreateTransferRequest": { - "description": "[See main docs here](https://alpaca.markets/docs/api-references/broker-api/funding/transfers/#creating-a-transfer-entity)", + "CommonNTAActivityV2": { + "description": "Shared fields for non-trade (NTA) activity records.", "properties": { - "additional_information": { - "description": "Additional details for when type = `wire`", - "nullable": true, - "type": "string" - }, - "amount": { - "description": "Must be > 0.00", - "format": "decimal", - "type": "string" - }, - "bank_id": { - "description": "Required if type = `wire`\n\nThe bank_relationship created for the account_id [here](https://alpaca.markets/docs/api-references/broker-api/funding/bank/#creating-a-new-bank-relationship)", + "group_id": { + "description": "Optional group ID which can help grouping together related activities", "format": "uuid", "type": "string" }, - "direction": { - "$ref": "#/components/schemas/TransferDirection" - }, - "fee_payment_method": { - "$ref": "#/components/schemas/FeePaymentMethod" - }, - "ira": { - "$ref": "#/components/schemas/TransferIRA" - }, - "relationship_id": { - "description": "Required if type = `ach`\n\nThe ach_relationship created for the account_id [here](https://alpaca.markets/docs/api-references/broker-api/funding/ach/#creating-an-ach-relationship)", - "format": "uuid", + "system_date": { + "description": "The date when the activity was booked", + "format": "date", + "minLength": 1, "type": "string" - }, - "timing": { - "deprecated": true, - "description": "Deprecated. Not honored by the API; values sent are silently ignored and this field will be removed.", - "enum": [ - "immediate" - ], - "example": "immediate", - "type": "string", - "x-deprecation": { - "reason": "Not honored by the API since January 2021; any value sent is silently ignored. Stop including this field in requests.", - "since": "2026-05-26T00:00:00Z", - "sunset": "2026-08-26T00:00:00Z" - } - }, - "transfer_type": { - "$ref": "#/components/schemas/TransferType" } }, "required": [ - "transfer_type", - "amount", - "direction" + "system_date" ], - "title": "CreateTransferRequest", "type": "object" }, - "CreateWatchlistRequest": { - "description": "This model represents the fields you can specify when Creating or Updating/Replacing a Watchlist", - "properties": { - "name": { - "description": "The watchlist name", - "type": "string" + "CommonOPCAActivityV2": { + "allOf": [ + { + "$ref": "#/components/schemas/CommonCaActivityV2" }, - "symbols": { - "description": "The new list of symbol names to watch", - "items": { - "example": "[\"AAPL\", \"TSLA\"]", - "type": "string" + { + "properties": { + "new_contract_symbol": { + "description": "The new contract symbol", + "type": "string" + }, + "new_qty": { + "description": "used when the old contract's quantity is not equal to the new contract's quantity. Mutually exclusive with 'qty'.", + "type": "string" + }, + "old_contract_symbol": { + "description": "The old contract symbol", + "type": "string" + }, + "old_qty": { + "description": "used when the old contract's quantity is not equal to the new contract's quantity. Mutually exclusive with 'qty'.", + "type": "string" + }, + "qty": { + "description": "used when the old contract's quantity is equal to the new contract's quantity. Mutually exclusive with 'old_qty' and 'new_qty'", + "type": "string" + } }, - "type": "array" + "required": [ + "old_contract_symbol", + "new_contract_symbol" + ], + "type": "object" } - }, - "required": [ - "name", - "symbols" ], - "title": "CreateWatchlistRequest", "type": "object" }, - "CryptoTransfer": { - "description": "Transfers allow you to transfer assets into your end customer's account (deposits) or out (withdrawal).", - "properties": { - "amount": { - "description": "Amount of transfer denominated in the underlying crypto asset", - "type": "string" + "CommonOptionsActivityV2": { + "allOf": [ + { + "description": "Shared fields for option-related non-trade activity records.", + "properties": { + "contract_symbol": { + "description": "The contract symbol of the security involved with the activity", + "type": "string" + }, + "cusip": { + "description": "The CUSIP of the security involved with the activity", + "type": "string" + }, + "symbol": { + "description": "The symbol of the security involved with the activity", + "type": "string" + } + }, + "required": [ + "group_id" + ], + "type": "object" }, - "asset": { - "description": "Symbol of crypto asset for given transfer (e.g. BTC)", + { + "$ref": "#/components/schemas/CommonNTAActivityV2" + } + ] + }, + "CommonREOActivityV2": { + "description": "Reorganization (REO) activity sub-types:\n- **REOS**: Stock only (1 or more stock legs)\n- **REOC**: Cash only (one cash leg)\n- **REOSC**: Stock and cash", + "properties": { + "cash_payout": { + "description": "Cash payout amount", + "format": "decimal", "type": "string" }, - "chain": { - "description": "Underlying network for given transfer", + "cash_rate": { + "description": "Cash rate per share", + "format": "decimal", "type": "string" }, - "created_at": { - "description": "Timestamp when transfer was created", - "format": "date-time", + "cusip": { + "description": "CUSIP received on this stock leg", "type": "string" }, - "direction": { - "$ref": "#/components/schemas/TransferDirection" + "effective_date": { + "description": "Effective date of the reorganization", + "format": "date", + "type": "string" }, - "fees": { + "isin": { + "description": "ISIN received on this stock leg", "type": "string" }, - "from_address": { - "description": "Originating address of the transfer", + "new_rate": { + "description": "New ratio for this stock leg", + "format": "decimal", "type": "string" }, - "id": { - "description": "The crypto transfer ID", - "format": "uuid", + "payable_date": { + "description": "Payable date of the reorganization", + "format": "date", "type": "string" }, - "network_fee": { + "qty": { + "description": "Quantity received on this stock leg", + "format": "decimal", "type": "string" }, - "status": { - "$ref": "#/components/schemas/CryptoTransferStatus" + "removed_qty": { + "description": "Positive quantity removed from the source position", + "format": "decimal", + "type": "string" }, - "to_address": { - "description": "Destination address of the transfer", + "source_cusip": { + "description": "CUSIP of the source security", "type": "string" }, - "tx_hash": { - "description": "On-chain transaction hash (e.g. 0xabc...xyz)", + "source_isin": { + "description": "ISIN of the source security", "type": "string" }, - "usd_value": { - "description": "Equivalent USD value at time of transfer", + "source_qty": { + "description": "Source position quantity", + "format": "decimal", + "type": "string" + }, + "source_rate": { + "description": "Source ratio for this stock leg", + "format": "decimal", + "type": "string" + }, + "source_symbol": { + "description": "Symbol of the source security", + "example": "CEPT", + "type": "string" + }, + "symbol": { + "description": "Symbol received on this stock leg", + "example": "SECZ", "type": "string" } }, - "type": "object" - }, - "CryptoTransferStatus": { - "enum": [ - "PROCESSING", - "FAILED", - "COMPLETE" + "required": [ + "source_symbol", + "source_cusip", + "payable_date" ], - "example": "PROCESSING", - "type": "string" + "type": "object" }, - "CryptoWallet": { + "CommonSDIVActivityV2": { "properties": { - "address": { + "cusip": { + "description": "The CUSIP of the security involved with the activity", "type": "string" }, - "chain": { + "ex_date": { + "description": "The ex_date for this corporate action", + "format": "date", + "minLength": 1, "type": "string" }, - "created_at": { - "description": "Timestamp (RFC3339) of account creation.", - "format": "date-time", + "isin": { + "description": "The ISIN of the security involved with the activity", + "type": "string" + }, + "payable_date": { + "description": "The payable_date for this corporate action", + "format": "date", + "minLength": 1, + "type": "string" + }, + "rate": { + "description": "Dividend rate per share", + "type": "string" + }, + "record_date": { + "description": "The record_date for this corporate action", + "format": "date", + "minLength": 1, + "type": "string" + }, + "symbol": { + "description": "The symbol of the security involved with the activity", "type": "string" } }, + "required": [ + "symbol", + "cusip", + "rate" + ], "type": "object" }, - "Currency": { - "description": "\"USD\" // US Dollar\n\"JPY\" // Japanese Yen\n\"EUR\" // Euro\n\"CAD\" // Canadian Dollar\n\"GBP\" // British Pound Sterling\n\"CHF\" // Swiss Franc\n\"TRY\" // Turkish Lira\n\"AUD\" // Australian Dollar\n\"CZK\" // Czech Koruna\n\"SEK\" // Swedish Krona\n\"DKK\" // Danish Krone\n\"SGD\" // Singapore Dollar\n\"HKD\" // Hong Kong Dollar\n\"HUF\" // Hungarian Forint\n\"NZD\" // New Zealand Dollar\n\"NOK\" // Norwegian Krone\n\"PLN\" // Poland Złoty", - "title": "Currency", - "type": "string" - }, - "CustodialAccountMinorIdentity": { - "description": "Represents Identity information for a minor that an account of type \"custodial\" is for", + "CommonSpinoffActivityV2": { "properties": { - "country_of_birth": { + "due_bill_redemption_date": { + "description": "When due bills related to the spinoff are redeemed", + "format": "date", + "minLength": 1, "type": "string" }, - "country_of_citizenship": { + "ex_date": { + "description": "The ex_date for this corporate action", + "format": "date", + "minLength": 1, "type": "string" }, - "country_of_tax_residence": { + "new_cusip": { + "description": "CUSIP of the new security", "type": "string" }, - "date_of_birth": { + "new_isin": { + "description": "ISIN of the new security", + "type": "string" + }, + "new_price": { + "description": "Market price of new shares after the spinoff", + "type": "string" + }, + "new_rate": { + "description": "Ratio of new shares received", + "type": "string" + }, + "new_symbol": { + "description": "Symbol of the new security", + "type": "string" + }, + "payable_date": { + "description": "The payable_date for this corporate action", "format": "date", + "minLength": 1, "type": "string" }, - "email": { - "format": "email", + "record_date": { + "description": "The record_date for this corporate action", + "format": "date", + "minLength": 1, "type": "string" }, - "family_name": { + "source_cusip": { + "description": "CUSIP of the parent security", "type": "string" }, - "given_name": { + "source_isin": { + "description": "ISIN of the parent security", "type": "string" }, - "state": { + "source_price": { + "description": "Market price of parent shares before the spinoff", "type": "string" }, - "tax_id": { + "source_rate": { + "description": "Ratio of parent shares", "type": "string" }, - "tax_id_type": { - "$ref": "#/components/schemas/TaxIdType" + "source_symbol": { + "description": "Symbol of the parent security", + "type": "string" } }, "required": [ - "given_name", - "family_name", - "date_of_birth", - "country_of_tax_residence", - "state", - "email" + "source_cusip", + "source_symbol", + "source_rate", + "source_price", + "new_cusip", + "new_symbol", + "new_rate", + "new_price" ], - "type": "object", - "x-examples": {} + "type": "object" }, - "CustodialAccountType": { - "description": "Represents the type of custodial account based on the state where the beneficiary resides.\nThis value is returned only when the `country_of_tax_residence` is `USA`. For other countries, this property is not included in the response.\n\n**Possible Return Values**\n\n`CustodianTypeUTMA` - Indicates that the custodial account is governed by the Uniform Transfers to Minors Act.\n`CustodianTypeUGMA` - Indicates that the custodial account is governed by the Uniform Gifts to Minors Act.", - "title": "CustodialAccountType", - "type": "string" + "CommonSplitActivityV2": { + "properties": { + "new_cusip": { + "description": "CUSIP of the new security after the split", + "type": "string" + }, + "new_isin": { + "description": "ISIN of the new security after the split", + "type": "string" + }, + "new_rate": { + "description": "Ratio of new shares received", + "type": "string" + }, + "old_cusip": { + "description": "CUSIP of the old security before the split", + "type": "string" + }, + "old_isin": { + "description": "ISIN of the old security before the split", + "type": "string" + }, + "old_rate": { + "description": "Ratio of old shares exchanged", + "type": "string" + }, + "payable_date": { + "description": "The payable_date for this corporate action", + "format": "date", + "minLength": 1, + "type": "string" + } + }, + "required": [ + "old_cusip", + "new_cusip", + "old_rate", + "new_rate" + ], + "type": "object" }, - "DIVNRAActivityV2": { + "CommonSplitStockActivityV2": { "allOf": [ { - "$ref": "#/components/schemas/CommonNTAActivityV2" + "$ref": "#/components/schemas/CommonCaActivityV2" + }, + { + "$ref": "#/components/schemas/CommonSplitActivityV2" }, { "properties": { - "cusip": { - "description": "The CUSIP of the security involved with the activity", + "new_qty": { + "description": "The new quantity after the split", "type": "string" }, - "parent_id": { - "description": "The ref_id of the parent dividend", - "type": "string" - }, - "symbol": { - "description": "The symbol of the security involved with the activity", + "old_qty": { + "description": "The old quantity before the split", "type": "string" } }, "required": [ - "cusip", - "symbol", - "parent_id" + "old_qty", + "new_qty" ], "type": "object" } ], - "description": "Dividend withholding for non resident aliens", "type": "object" }, - "DIVSPDActivityV2": { - "allOf": [ - { - "$ref": "#/components/schemas/CommonCaActivityV2" + "CommonVOFSubtypeActivityV2": { + "properties": { + "new_cusip": { + "description": "CUSIP of the new security", + "type": "string" }, - { - "properties": { - "cash_payout": { - "description": "Total cash amount paid", - "type": "string" - }, - "cusip": { - "description": "The CUSIP of the security involved with the activity", - "type": "string" - }, - "due_bill_off_date": { - "description": "When due bills stop applying for this event", - "format": "date", - "minLength": 1, - "type": "string" - }, - "due_bill_on_date": { - "description": "When due bills begin to apply for this event", - "format": "date", - "minLength": 1, - "type": "string" - }, - "entitled_qty": { - "description": "Quantity of shares entitled to receive cash in lieu", - "type": "string" - }, - "ex_date": { - "description": "The ex_date for this corporate action", - "format": "date", - "minLength": 1, - "type": "string" - }, - "foreign": { - "description": "Indicates if related to a non-US security", - "type": "boolean" - }, - "payable_date": { - "description": "The payable_date for this corporate action", - "format": "date", - "minLength": 1, - "type": "string" - }, - "rate": { - "description": "Cash payout per share", - "type": "string" - }, - "record_date": { - "description": "The record_date for this corporate action", - "format": "date", - "minLength": 1, - "type": "string" - }, - "special": { - "description": "Indicates if this is a special dividend", - "type": "boolean" - }, - "symbol": { - "description": "The symbol of the security involved with the activity", - "type": "string" - } - }, - "required": [ - "entitled_qty", - "cash_payout", - "symbol", - "cusip", - "rate", - "foreign", - "special" - ], - "type": "object" + "new_symbol": { + "description": "Symbol of the new security", + "type": "string" + }, + "source_cusip": { + "description": "CUSIP of the parent security", + "type": "string" + }, + "source_symbol": { + "description": "Symbol of the parent security", + "type": "string" } + }, + "required": [ + "source_cusip", + "source_symbol" ], - "description": "Substitute payment in lieu of dividend", "type": "object" }, - "DailyCashInterest": { + "Contact": { + "description": "Contact is the model for the account owner contact information.\n", "properties": { - "account_accrued_interest": { - "example": "1.2152", - "format": "decimal", + "city": { + "example": "San Mateo", "type": "string" }, - "account_id": { - "example": "02e5c5ba-36d2-467a-8699-81dc24271291", - "format": "uuid", + "country": { + "description": "country code in ISO 3166-1 alpha-3 format, representing the country the person/entity resides in.", + "example": "USA", "type": "string" }, - "account_rate_bps": { - "example": 423, - "type": "integer" - }, - "apr_tier_id": { - "example": "13d149c8-d620-43e0-978b-88af8abb2ef1", - "format": "uuid", + "email_address": { + "example": "john.doe@example.com", + "format": "email", "type": "string" }, - "apr_tier_name": { - "example": "gold", - "type": "string" + "phone_number": { + "description": "Phone number should include the country code, format: \"+15555555555\"", + "example": "+15556667788", + "type": [ + "string", + "null" + ] }, - "cash_balance": { - "example": "10485.76", - "format": "decimal", + "postal_code": { + "example": "94401", "type": "string" }, - "correspondent_fee": { - "example": "0.0718", - "format": "decimal", + "state": { + "description": "Required if the country or country_of_tax_residence (in the identity model below) is 'USA'.", + "example": "CA", "type": "string" }, - "correspondent_rate_bps": { - "example": 25, - "type": "integer" - }, - "currency": { - "example": "USD", - "type": "string" + "street_address": { + "description": "The user's street address. If multiple lines in address, pass in as additional array elements. Maximum of 3 objects in array", + "items": { + "$ref": "#/components/schemas/StreetAddress" + }, + "type": "array" }, - "date": { - "example": "2024-09-27", - "format": "date", + "unit": { + "description": "The specific apartment number if applicable", "type": "string" } }, + "required": [ + "email_address", + "street_address", + "city" + ], "type": "object" }, - "DailyTradingLimit": { + "CorrespondentLimit": { "properties": { - "daily_net_limit": { - "description": "The net buying limit that can be reached before further cash outflow trading activity is restricted. Please reach out to learn more about how this limit is determined.", + "amount_available": { + "format": "decimal", + "type": "string" + }, + "amount_in_use": { "format": "decimal", "type": "string" }, - "daily_net_limit_in_use": { - "description": "The real time net value of cash inflows (buy trades, etc.) with cash outflows (sell trades, dividends, etc). This will be dynamic throughout the day based on user activity, with executed orders being reset at the start of the next trading day.", + "amount_limit": { "format": "decimal", "type": "string" } }, - "title": "DailyTradingLimit", "type": "object" }, - "DemoFundingTransfer": { + "CountryInfo": { + "description": "Represents the info for a country.", "properties": { - "amount": { - "format": "decimal", + "crypto_risk_rating": { + "description": "crypto risk rating of the country", + "enum": [ + "low", + "medium", + "high", + "restricted", + "prohibited" + ], "type": "string" }, - "currency": { - "type": "string" + "crypto_supported_states": { + "description": "states where Alpaca supports crypto trading", + "items": { + "description": "the state code defined by ISO 3166-2", + "type": "string" + }, + "type": "array" }, - "receiver_account_number": { + "full_name": { + "description": "The full name of the country definded by ISO 3166-1", + "example": "Åland Islands", + "minLength": 1, "type": "string" }, - "receiver_routing_code": { + "securities_risk_rating": { + "description": "the securities risk rating of the country", + "enum": [ + "low", + "medium", + "high", + "restricted", + "prohibited" + ], "type": "string" } }, + "required": [ + "full_name", + "securities_risk_rating", + "crypto_risk_rating" + ], + "title": "CountryInfo", "type": "object" }, - "DisclosureContextAnnotation": { + "CountryInfos": { + "additionalProperties": { + "$ref": "#/components/schemas/CountryInfo" + }, + "description": "Represents the info for countries as a map where the keys are the ISO 3166 country codes.", + "title": "CountryInfos", + "type": "object" + }, + "CreateACHRelationshipRequest": { + "description": "Represents the fields used in creation of a new ACHRelationship.\n\nYou can create an ACHRelationship by passing the required fields here or if you have an account with Plaid you can use our integration with Plaid to create a relationship.\n\nPlease see the documentation [here](https://alpaca.markets/docs/api-references/broker-api/funding/ach/#plaid-integration-for-bank-transfers) for more info on using Plaid with Alpaca", "properties": { - "company_city": { - "description": "Required for FINRA affiliations and controlled firms.", + "account_owner_name": { + "minLength": 1, "type": "string" }, - "company_compliance_email": { - "description": "Required for FINRA affiliations and controlled firms.", + "bank_account_number": { + "description": "In sandbox, this still must be a valid format", + "minLength": 1, "type": "string" }, - "company_country": { - "description": "Required for FINRA affiliations and controlled firms.", + "bank_account_type": { + "description": "Must be `CHECKING` or `SAVINGS`", + "enum": [ + "CHECKING", + "SAVINGS" + ], + "minLength": 1, "type": "string" }, - "company_name": { - "description": "Required for FINRA affiliations and controlled firms.", + "bank_routing_number": { + "description": "In sandbox, this still must be a valid format", + "minLength": 1, "type": "string" }, - "company_state": { - "description": "Required if and only if `company_country` is `USA`.", - "type": "string" - }, - "company_street_address": { - "description": "Required for FINRA affiliations and controlled firms.", - "type": "string" - }, - "context_type": { - "description": "Specifies the type of disclosure annotation. Valid types are FINRA affiliations, for users affiliated with or employed by a FINRA member firm, a Stock Exchange Member, FINRA, Registered Investment Advisor, or a Municipal Securities Broker/Dealer; Company control relationships, for senior executives, and 10% or greater shareholders, of a publicly traded company; and immediate family members of politically exposed individuals.", - "enum": [ - "CONTROLLED_FIRM", - "IMMEDIATE_FAMILY_EXPOSED", - "AFFILIATE_FIRM" - ], - "type": "string" + "instant": { + "type": "boolean" }, - "family_name": { - "description": "Required for immediate family members of politically exposed persons.", + "nickname": { + "minLength": 1, "type": "string" }, - "given_name": { - "description": "Required for immediate family members of politically exposed persons.", + "processor_token": { + "description": "If using Plaid, you can specify a Plaid processor token here ", "type": "string" } }, "required": [ - "context_type" + "account_owner_name", + "bank_account_type", + "bank_account_number", + "bank_routing_number" ], - "title": "DisclosureContextAnnotation", + "title": "CreateACHRelationshipRequest", "type": "object" }, - "Disclosures": { - "description": "Disclosures fields denote if the account owner falls under\neach category defined by FINRA rule. The client has to ask\nquestions for the end user and the values should reflect\ntheir answers.\nIf one of the answers is true (yes), the account goes into\nACTION_REQUIRED status.\n", - "example": { - "immediate_family_exposed": false, - "is_affiliated_exchange_or_finra": false, - "is_control_person": false, - "is_politically_exposed": false - }, + "CreateBankRequest": { + "description": "Represents the possible fields to send when creating a new associated Bank resource for an account", "properties": { - "context": { - "description": "Array of annotations describing the rationale for marking `is_control_person`, `is_affiliated_exchange_or_finra`, and/or `immediate_family_exposed` as true", - "items": { - "$ref": "#/components/schemas/DisclosureContextAnnotation" - }, - "nullable": true, - "type": "array" - }, - "employer_address": { - "description": "The employer's address if the user is employed.", - "type": "string" - }, - "employer_name": { - "description": "The name of the employer if the user is employed.", + "account_number": { "type": "string" }, - "employment_position": { - "description": "The user's position if they are employed.", + "bank_code": { + "description": "9-Digit ABA RTN (Routing Number) or BIC", "type": "string" }, - "employment_sector": { - "description": "The industry sector of employment.\nIf the `employment_status` is `unemployed` or `student`, set this property to `not_employed`.\nIf the `employment_status` is `retired`, set this to `self_employed`.\n", + "bank_code_type": { + "description": "ABA (Domestic) or BIC (International)", "enum": [ - "agriculture", - "business_management", - "computers_and_it", - "construction", - "education", - "finance", - "government", - "healthcare", - "hospitality", - "manufacturing", - "marketing", - "media", - "other", - "science", - "self_employed", - "transportation", - "not_employed" + "ABA", + "BIC" ], "type": "string" }, - "employment_status": { - "description": "One of the following: `employed`, `unemployed`, `retired`, or `student`.", - "enum": [ - "unemployed", - "employed", - "student", - "retired" - ], + "city": { + "description": "Only for international banks, ie if bank_code_type = BIC", "type": "string" }, - "immediate_family_exposed": { - "description": "If your user's immediate family member (sibling, husband/wife, child, parent) is either politically exposed or holds a control position.", - "type": "boolean" + "country": { + "description": "Only for international banks, ie if bank_code_type = BIC", + "type": "string" }, - "is_affiliated_exchange_or_finra": { - "description": "Whether user is affiliated with any exchanges or FINRA.", - "type": "boolean" + "extra_fields": { + "$ref": "#/components/schemas/BankAdditionalFields" }, - "is_control_person": { - "description": "Whether user holds a controlling position in a publicly traded company, member of the board of directors or has policy making abilities in a publicly traded company.", - "type": "boolean" + "name": { + "description": "Name of recipient bank", + "type": "string" }, - "is_politically_exposed": { - "description": "Whether the user is politically exposed.", - "type": "boolean" + "postal_code": { + "description": "Only for international banks, ie if bank_code_type = BIC. Minimum of 3 characters", + "type": "string" + }, + "state_province": { + "description": "Only for international banks, ie if bank_code_type = BIC", + "type": "string" + }, + "street_address": { + "description": "Only for international banks, ie if bank_code_type = BIC", + "type": "string" } }, "required": [ - "is_control_person", - "is_affiliated_exchange_or_finra", - "is_politically_exposed", - "immediate_family_exposed" + "name", + "bank_code", + "bank_code_type", + "account_number" ], + "title": "CreateBankRequest", "type": "object" }, - "DoNotExerciseRequest": { - "description": "Request to submit a do-not-exercise (DNE) instruction for an option contract for an account.\nThe request body is empty. A successful request will mark the entire position as do not exercise.", - "title": "DoNotExerciseRequest", - "type": "object" - }, - "DoNotExerciseResponse": { - "description": "Response to a do-not-exercise request.\nA 200 response indicates that the entire position is set as do not exercise.\nThe response body is empty.", - "title": "DoNotExerciseResponse", - "type": "object" - }, - "EoDCashInterestReportResponse": { + "CreateCryptoTransferRequest": { "properties": { - "interest": { - "items": { - "$ref": "#/components/schemas/DailyCashInterest" - }, - "type": "array" + "address": { + "description": "The destination wallet address", + "type": "string" }, - "next_page_token": { - "example": "293639be-8807-423f-b00d-68c781e8bb56", - "nullable": true, + "amount": { + "description": "The amount, denoted in the specified asset, to be withdrawn from the user's wallet", "type": "string" - } - }, - "type": "object" - }, - "Error": { - "properties": { - "code": { - "type": "number" }, - "message": { + "asset": { + "type": "string" + }, + "chain": { + "description": "Optional chain identifier. The blockchain network used for the withdrawal.", + "enum": [ + "SOL", + "ETH", + "BTC", + "XRP", + "ARB" + ], "type": "string" } }, "required": [ - "code", - "message" - ], - "title": "Error", - "type": "object" - }, - "Exchange": { - "description": "Represents the exchange where an asset is traded.\n\nFor Stocks:\n- AMEX\n- ARCA\n- BATS\n- NYSE\n- NASDAQ\n- NYSEARCA\n- OTC\n\nFor Crypto:\n- CRYPTO", - "enum": [ - "AMEX", - "ARCA", - "BATS", - "NYSE", - "NASDAQ", - "NYSEARCA", - "OTC" - ], - "example": "NASDAQ", - "title": "Exchange", - "type": "string" - }, - "ExchangeOfferActivityV2": { - "allOf": [ - { - "$ref": "#/components/schemas/CommonNTAActivityV2" - }, - { - "$ref": "#/components/schemas/CommonVOFSubtypeActivityV2" - } + "amount", + "address", + "asset" ], + "title": "CreateCryptoTransferRequest", "type": "object" }, - "ExerciseRequest": { - "description": "Request to exercise an option contract for an account.\nThis can be omitted or left empty, but it also allows for optionally charging a commission for exercising or\nspecifying a specific quantity of contracts to exercise (rather than the full quantity of contracts held).", + "CreateFundingWalletWithdrawalRequest": { "properties": { - "commission": { - "description": "The commission you want to collect from the user. (notional amount)", - "example": "0.25", - "format": "decimal", + "desired_currency": { "type": "string" }, - "qty": { - "description": "The number of contracts to exercise. If not provided, all contracts will be exercised.", - "example": "1", + "payment_type": { + "$ref": "#/components/schemas/FundingDetailPaymentType" + }, + "usd_amount": { "format": "decimal", "type": "string" } }, - "title": "ExerciseRequest", "type": "object" }, - "ExerciseResponse": { - "description": "Response to an exercise request.\nThis will return the total quantity of contracts exercised as well as the quantity of remaining contracts.", + "CreateIFTransferRequest": { + "description": "Request to create a new instant funding transfer\n", "properties": { - "qty_exercised": { - "description": "The total quantity of contracts exercised.", - "format": "decimal", + "account_no": { "type": "string" }, - "qty_remaining": { - "description": "The total quantity of contracts remaining after the exercise.", + "amount": { "format": "decimal", "type": "string" + }, + "source_account_no": { + "type": "string" } }, - "title": "ExerciseResponse", + "required": [ + "account_no", + "source_account_no", + "amount" + ], "type": "object" }, - "FEEActivityV2": { - "allOf": [ - { - "$ref": "#/components/schemas/CommonNTAActivityV2" - }, - { - "properties": { - "parent_id": { - "description": "The parent transaction's ID", - "format": "uuid", - "type": "string" - } + "CreateJITSettlementRequest": { + "description": "Request to create a new settlement. All transfers included must use the same source account, and the total amount received must be in the same currency as the base currency of the source account.\n", + "properties": { + "accounts": { + "items": { + "$ref": "#/components/schemas/SettlementAccount" }, - "required": [ - "parent_id" - ], - "type": "object" - } - ], - "description": "Fee", - "type": "object" - }, - "FOPTActivityV2": { - "allOf": [ - { - "$ref": "#/components/schemas/CommonNTAActivityV2" + "type": "array" }, - { - "properties": { - "contra": { - "description": "Contra for the transfer", - "type": "string" - }, - "external_id": { - "description": "External ID of the transfer", - "type": "string" - }, - "symbol": { - "description": "The symbol of the security involved with the activity", - "type": "string" - } - }, - "required": [ - "external_id", - "contra", - "symbol" - ], - "type": "object" - } - ], - "description": "Free-of-payment (FOP) transfers", - "type": "object" - }, - "FPSLAnalyticsLoansResponse": { - "description": "Aggregated FPSL interest and loan-activity counts for one account over a date range.\n", - "properties": { - "account_number": { - "description": "Account's number at Alpaca", - "example": "286873545", + "additional_info": { + "description": "Additional remittance information. Include if the funding source may require additional information to associate to the source account or settlement.\n", "type": "string" }, - "in_progress_lending_activities": { - "description": "The number of FPSL lending activities currently in progress for the account.", - "example": 2, - "format": "int64", - "type": "integer" - }, - "interest": { - "$ref": "#/components/schemas/FPSLInterest" + "asset_class": { + "$ref": "#/components/schemas/JITAssetClass" }, - "total_lending_activities": { - "description": "The total number of FPSL lending activities for the account within the specified date range.", - "example": 7, - "format": "int64", - "type": "integer" + "currency": { + "description": "The currency of the settlement. All accounts identified should have this base currency.\n", + "type": "string" } }, "required": [ - "account_number", - "total_lending_activities", - "in_progress_lending_activities" + "currency", + "asset_class", + "accounts" ], "type": "object" }, - "FPSLError": { - "description": "FPSL API error response", - "properties": { - "message": { - "example": "Internal Server Error", - "type": "string" - } - }, - "type": "object" - }, - "FPSLInterest": { - "description": "The interest's details.\nPlease note that the object might not be set if interest has not yet been calculated for the loan.\nAdditionally, interest details may be adjusted retroactively at any time until interests are finalized for the month.\nThe loan's changed `updated_at` field can indicate such an adjustment.\n", - "properties": { - "customer": { - "description": "The interest accrued by the customer for this loan.", - "example": 1.23, - "format": "double", - "type": "number" - }, - "partner": { - "description": "The interest accrued by the partner for this loan.", - "example": 1.65, - "format": "double", - "type": "number" + "CreateJournalRequest": { + "description": "Journals API allows you to move cash or securities from one account to another.\n\nThis model represents the fields you can specify when creating a Journal\n\nFixture Rules\n\n- No Fixtures\n - anything below limit is executed immediately\n - anything above limit is pending until executed at EOD,\n- With Fixtures\n - any status = rejected will be rejected EOD\n - any status = pending will be pending forever", + "examples": [ + { + "amount": "51", + "description": "test text /fixtures/status=rejected/fixtures/", + "entry_type": "JNLC", + "from_account": "c94bu7rn-4483-4199-840f-6c5fe0b7ca24", + "to_account": "fn68sbrk-6f2a-433c-8c33-17b66b8941fa" } - }, - "required": [ - "customer", - "partner" ], - "type": "object" - }, - "FPSLLoan": { - "description": "A loan of a security by an account on a date.", "properties": { - "account_id": { - "description": "Account's ID at Alpaca", - "example": "06d83e79-5bea-4ce9-a362-a25251a700ec", + "amount": { + "description": "Required if `entry_type` = `JNLC`", "type": "string" }, - "account_number": { - "description": "Account's number at Alpaca", - "example": "286873545", + "currency": { "type": "string" }, - "collateral": { - "description": "The collateral posted for the loan.", - "example": 1161.015, - "format": "double", - "type": "number" + "description": { + "description": "Max 1024 characters. Can include fixtures for amounts that are above the transaction limit", + "maxLength": 1024, + "type": "string" }, - "correspondent": { - "description": "Account's correspondent", - "example": "LPCA", + "entry_type": { + "$ref": "#/components/schemas/JournalEntryType" + }, + "from_account": { + "description": "The account_id you wish to journal from", + "format": "uuid", + "minLength": 1, "type": "string" }, - "date": { - "example": "2006-01-02", - "format": "date", + "qty": { + "description": "Required if `entry_type` = `JNLS`", "type": "string" }, - "interest": { - "$ref": "#/components/schemas/FPSLInterest" + "symbol": { + "description": "Required if `entry_type` = `JNLS`", + "type": "string" }, - "market_value": { - "description": "The total market value of the shares on loan.", - "example": 1138.25, - "format": "double", - "type": "number" + "to_account": { + "description": "The account_id you wish to journal to", + "format": "uuid", + "minLength": 1, + "type": "string" }, - "quantity": { - "description": "The number of shares in the loan. This may be less than the total number of eligible shares held by the account.", - "example": 5, - "format": "int64", - "type": "integer" + "transmitter_account_number": { + "description": "Max 255 characters. See more details about [Travel Rule](https://alpaca.markets/docs/broker/integration/funding/#travel-rule) in our main documentation.", + "maxLength": 255, + "type": "string" }, - "symbol": { - "description": "Stock/Ticker symbol of a stock or security", - "example": "AAPL", + "transmitter_address": { + "description": "Max 255 characters. See more details about [Travel Rule](https://alpaca.markets/docs/broker/integration/funding/#travel-rule) in our main documentation.", + "maxLength": 255, "type": "string" }, - "updated_at": { - "description": "Timestamp of the last update to this loan in RFC-3339 format with microsecond precision with timezone.\nAn updated value may indicate adjusted interest values.\n", - "example": "2025-03-21T15:54:19.857034+01:00", + "transmitter_financial_institution": { + "description": "Max 255 characters. See more details about [Travel Rule](https://alpaca.markets/docs/broker/integration/funding/#travel-rule) in our main documentation.", + "maxLength": 255, + "type": "string" + }, + "transmitter_name": { + "description": "Max 255 characters. See more details about [Travel Rule](https://alpaca.markets/docs/broker/integration/funding/#travel-rule) in our main documentation.", + "maxLength": 255, + "type": "string" + }, + "transmitter_timestamp": { + "description": "RFC 3339 format. See more details about [Travel Rule](https://alpaca.markets/docs/broker/integration/funding/#travel-rule) in our main documentation.", "format": "date-time", "type": "string" } }, "required": [ - "date", - "account_id", - "account_number", - "correspondent", - "symbol", - "quantity", - "market_value", - "collateral" + "to_account", + "from_account", + "entry_type" ], "type": "object" }, - "FPSLTier": { + "CreateOrderRequest": { "properties": { - "created_at": { - "description": "The timestamp the FPSL tier was created, in RFC 3339 format.", - "example": "2024-09-16T20:06:32.207729Z", - "format": "date-time", - "type": "string" + "advanced_instructions": { + "$ref": "#/components/schemas/AdvancedInstructions" }, - "customer_split": { - "description": "Percentage of the customer split represented as a value between 0 and 1.00. Max 2 decimal places are allowed.", - "example": 0.25, - "type": "number" + "client_order_id": { + "description": "A unique identifier for the order. Automatically generated if not sent. (<= 128 characters)", + "example": "eb9e2aaa-f71a-4f51-b5b4-52a6c565dad4", + "maxLength": 128, + "type": "string" }, - "id": { - "description": "The unique identifier of the FPSL tier", - "example": "150df7eb-31e7-46b3-8848-a54ca21ff3bc", - "format": "uuid", + "commission": { + "description": "The commission you want to collect from the user.", + "example": "1.0", + "format": "decimal", "type": "string" }, - "market": { - "description": "The market of the FPSL tier", - "example": "US", + "commission_bps": { + "deprecated": true, + "description": "**deprecated**: Please use the commission_type = bps instead and set the desired bps value in the `commission` field.\nThe percent commission you want to charge the end user on the order (expressed in bps). Alpaca will convert the order to a notional amount for purposes of calculating commission.\n", + "example": "10", + "format": "decimal", "type": "string" }, - "partner_split": { - "description": "Percentage of the partner split represented as a value between 0 and 1.00. Max 2 decimal places are allowed.", - "example": 0.3, - "type": "number" + "commission_type": { + "$ref": "#/components/schemas/CommissionType" }, - "tier_name": { - "description": "The name of the FPSL tier", - "example": "gold", - "type": "string" + "extended_hours": { + "description": "Defaults to false. If true, order will be eligible for execution in the pre-market, after-hours, and overnight sessions. Only works with type `limit` and time_in_force set to either `day` or `gtc`.", + "example": false, + "type": "boolean" }, - "updated_at": { - "description": "The timestamp of the last update to the FPSL tier, in RFC 3339 format.", - "example": "2024-09-16T20:06:32.207729Z", - "format": "date-time", + "instructions": { "type": "string" - } - }, - "type": "object" - }, - "FeePaymentMethod": { - "description": "Only outgoing wire fees are currently supported for automated processing.\n\n\n**user**\tThe end user will pay any applicable fees\n**invoice**\tAny applicable fees will be billed to the client in the following monthly invoice", - "title": "FeePaymentMethod", - "type": "string" - }, - "FeePaymentType": { - "description": "Status:\n * `invoice`\n", - "enum": [ - "invoice" - ], - "type": "string" - }, - "FeeType": { - "description": "Status:\n * `withdrawal_fee`: Withdrawal fee\n * `fx_fee`: FX Fee\n * `network_fee`\n * `deposit_fee`\n * `ach_return_fee`\n * `parnter_fee`\n * `alpaca_fee`\n", - "enum": [ - "withdrawal_fee", - "fx_fee", - "network_fee", - "deposit_fee", - "ach_return_fee", - "parnter_fee", - "alpaca_fee" - ], - "type": "string" - }, - "FixedIncomeRedemptionActivityV2": { - "allOf": [ - { - "$ref": "#/components/schemas/CommonNTAActivityV2" }, - { - "properties": { - "ca_id": { - "description": "The unique identifier for this corporate action", - "type": "string" - }, - "cash_payout": { - "description": "The cash payout", - "type": "string" - }, - "cusip": { - "description": "The CUSIP of the security involved with the activity", - "type": "string" - }, - "payment_date": { - "description": "The payment date", - "format": "date", - "minLength": 1, - "type": "string" - }, - "qty": { - "description": "Quantity for the redemption", - "type": "string" - } + "legs": { + "description": "list of order legs (<= 4)", + "items": { + "$ref": "#/components/schemas/MLegOrderLeg" }, - "required": [ - "ca_id", - "payment_date", - "cusip", - "qty", - "cash_payout" - ], - "type": "object" - } - ], - "description": "Redemption", - "type": "object" - }, - "ForwardSplitActivityV2": { - "allOf": [ - { - "$ref": "#/components/schemas/CommonSplitStockActivityV2" + "maxItems": 4, + "type": "array" }, - { + "limit_price": { + "description": "Required if type is `limit` or `stop_limit`.\n- In case of `mleg`, the limit_price parameter is expressed with the following notation:\n - A positive value indicates a debit, representing a cost or payment to be made.\n - A negative value signifies a credit, reflecting an amount to be received.\n- In case of Fied Income, the price is expressed in percentage of par value (face value).\nPrice is always clean price, meaning it does not include accrued interest.", + "example": "3.14", + "format": "decimal", + "type": "string" + }, + "notional": { + "description": "Dollar amount to trade. Cannot work with qty. Only market and limit orders supported with time_in_force = day; Only limit orders for extended hours.", + "example": "3", + "format": "decimal", + "type": "string" + }, + "order_class": { + "$ref": "#/components/schemas/OrderClass" + }, + "position_intent": { + "$ref": "#/components/schemas/PositionIntent" + }, + "qty": { + "description": "- For equities, the number of shares to trade. Can be fractionable for only market and day order types.\n- Required for `mleg` order class, represents the number of units to trade of this strategy.\n- For Fixed Income securities, qty represents the order size in par value (face value).\nFor example, to place an order for 1 bond with a face value of $1,000, provide a qty of 1000.\n", + "example": "4.124", + "format": "decimal", + "type": "string" + }, + "side": { + "$ref": "#/components/schemas/OrderSide" + }, + "source": { + "type": "string" + }, + "stop_loss": { + "description": "Takes in a string/number values for stop_price and limit_price", "properties": { - "due_bill_redemption_date": { - "description": "When due bills related to the split are redeemed", - "format": "date", - "minLength": 1, - "type": "string" - }, - "ex_date": { - "description": "The ex_date for this corporate action", - "format": "date", - "minLength": 1, - "type": "string" - }, - "record_date": { - "description": "The record_date for this corporate action", - "format": "date", - "minLength": 1, + "limit_price": { + "example": "3.14", + "format": "decimal", "type": "string" }, - "symbol": { - "description": "The symbol of the security being split", + "stop_price": { + "example": "3.14", + "format": "decimal", "type": "string" } }, - "required": [ - "symbol" - ], "type": "object" - } - ], - "description": "Forward stock split", - "type": "object" - }, - "FundingDetail": { - "description": "Gets funding details that can be used to settle and collect funds in each available currency.\n", - "properties": { - "account_holder_name": { - "type": "string" }, - "account_number": { + "stop_price": { + "description": "Required if type is stop or stop_limit", + "example": "3.14", + "format": "decimal", "type": "string" }, - "account_number_type": { + "subtag": { "type": "string" }, - "bank_address": { + "swap_fee_bps": { + "format": "decimal", "type": "string" }, - "bank_country": { + "symbol": { + "description": "Symbol or asset ID to identify the asset to trade. Required for all order classes except for `mleg`.", + "example": "AAPL", "type": "string" }, - "bank_name": { - "type": "string" + "take_profit": { + "description": "Takes in a string/number value for limit_price", + "properties": { + "limit_price": { + "example": "3.14", + "format": "decimal", + "type": "string" + } + }, + "type": "object" }, - "currency": { - "type": "string" + "time_in_force": { + "$ref": "#/components/schemas/TimeInForce" }, - "payment_type": { - "$ref": "#/components/schemas/FundingDetailPaymentType" + "trail_percent": { + "description": "If type is trailing_stop, then one of trail_price or trail_percent is required", + "example": "5.0", + "format": "decimal", + "type": "string" }, - "routing_code": { + "trail_price": { + "description": "If type is trailing_stop, then one of trail_price or trail_percent is required", + "example": "3.14", + "format": "decimal", "type": "string" }, - "routing_code_type": { - "$ref": "#/components/schemas/FundingDetailRoutingCodeType" + "type": { + "$ref": "#/components/schemas/OrderType" } }, - "type": "object" - }, - "FundingDetailPaymentType": { - "description": "Status:\n * `swift_wire`: SWIFT wire\n * `local_rails`: Local scheme\n", - "enum": [ - "swift_wire", - "local_rails" - ], - "type": "string" - }, - "FundingDetailRoutingCodeType": { - "description": "Status:\n * `BIC`: bic_swift\n * `ACH`: ach_routing_code\n * `ABA`: aba\n * `ROUTING`: routing_code\n * `SORT_CODE`: sort_code\n", - "enum": [ - "BIC", - "ACH_ROUTING", - "ABA", - "ROUTING", - "SORT_CODE" + "required": [ + "type", + "time_in_force" ], - "type": "string" + "type": "object" }, - "FundingWallet": { + "CreateSettlementRequest": { + "description": "Request to create a new settlement. All transfers included must use the same source account, and the total amount received must be in the same currency as the base currency of the source account as well as the transmitter information.\n", "properties": { - "account_id": { - "format": "uuid", - "type": "string" - }, - "created_at": { - "format": "date-time", + "additional_info": { + "description": "Additional remittance information. Include if the funding source may require additional information to associate to the source account or settlement.\n", "type": "string" }, - "status": { - "$ref": "#/components/schemas/FundingWalletStatus" + "transfers": { + "items": { + "$ref": "#/components/schemas/SettlementTransfer" + }, + "type": "array" } }, "required": [ - "account_id", - "status", - "created_at" + "transfers" ], "type": "object" }, - "FundingWalletRecipientBank": { + "CreateTransferRequest": { + "description": "[See main docs here](https://alpaca.markets/docs/api-references/broker-api/funding/transfers/#creating-a-transfer-entity)", "properties": { - "account_number": { - "description": "Bank account number.", - "type": "string" + "additional_information": { + "description": "Additional details for when type = `wire`", + "type": [ + "string", + "null" + ] }, - "bic_swift": { - "description": "BIC/SWIFT code", - "type": "string" - }, - "city": { - "description": "City", - "type": "string" - }, - "company_name": { - "type": "string" - }, - "country": { - "description": "Two-letter ISO country code.", + "amount": { + "description": "Must be > 0.00", + "format": "decimal", "type": "string" }, - "created_at": { - "description": "Date the beneficiary record was created.", - "format": "date-time", + "bank_id": { + "description": "Required if type = `wire`\n\nThe bank_relationship created for the account_id [here](https://alpaca.markets/docs/api-references/broker-api/funding/bank/#creating-a-new-bank-relationship)", + "format": "uuid", "type": "string" }, - "currency": { - "description": "Currency in which money is held in the beneficiary's bank account. Three-digit currency code.", - "type": "string" + "direction": { + "$ref": "#/components/schemas/TransferDirection" }, - "first_name": { - "type": "string" + "fee_payment_method": { + "$ref": "#/components/schemas/FeePaymentMethod" }, - "iban": { - "description": "IBAN code", - "type": "string" + "ira": { + "$ref": "#/components/schemas/TransferIRA" }, - "id": { + "relationship_id": { + "description": "Required if type = `ach`\n\nThe ach_relationship created for the account_id [here](https://alpaca.markets/docs/api-references/broker-api/funding/ach/#creating-an-ach-relationship)", "format": "uuid", "type": "string" }, - "last_name": { + "transfer_type": { + "$ref": "#/components/schemas/TransferType" + } + }, + "required": [ + "transfer_type", + "amount", + "direction" + ], + "title": "CreateTransferRequest", + "type": "object" + }, + "CreateWatchlistRequest": { + "description": "This model represents the fields you can specify when Creating or Updating/Replacing a Watchlist", + "properties": { + "name": { + "description": "The watchlist name", "type": "string" }, - "payment_types": { + "symbols": { + "description": "The new list of symbol names to watch", "items": { - "$ref": "#/components/schemas/FundingDetailPaymentType" + "example": "[\"AAPL\", \"TSLA\"]", + "type": "string" }, "type": "array" - }, - "postal_code": { - "description": "Postal code", - "type": "string" - }, - "routing_code": { - "description": "Value for \"routing_code_type\".", - "type": "string" - }, - "routing_code_type": { - "description": "Local payment routing system.", - "type": "string" - }, - "state_or_province": { - "description": "State or province.", - "type": "string" - }, - "street_address": { - "type": "string" - }, - "updated_at": { - "description": "Date the beneficiary record was last updated.", - "format": "date-time", - "type": "string" } }, - "type": "object" - }, - "FundingWalletStatus": { - "description": "Status:\n * `active`: The funding wallet is ready\n * `pending`: The funding wallet is being processed\n * `disabled`: The funding wallet is disabled\n", - "enum": [ - "active", - "pending", - "disabled" + "required": [ + "name", + "symbols" ], - "type": "string" + "title": "CreateWatchlistRequest", + "type": "object" }, - "FundingWalletTransfer": { + "CryptoTransfer": { + "description": "Transfers allow you to transfer assets into your end customer's account (deposits) or out (withdrawal).", "properties": { - "account_id": { - "format": "uuid", + "amount": { + "description": "Amount of transfer denominated in the underlying crypto asset", + "type": "string" + }, + "asset": { + "description": "Symbol of crypto asset for given transfer (e.g. BTC)", + "type": "string" + }, + "chain": { + "description": "Underlying network for given transfer", "type": "string" }, "created_at": { + "description": "Timestamp when transfer was created", "format": "date-time", "type": "string" }, "direction": { - "$ref": "#/components/schemas/FundingWalletTransferDirection" + "$ref": "#/components/schemas/TransferDirection" }, "fees": { - "items": { - "$ref": "#/components/schemas/TransferFee" - }, - "type": "array" - }, - "id": { - "format": "uuid", "type": "string" }, - "original_amount": { - "description": "The amount you should expect to receive, calculated as requested amount - fees", - "format": "decimal", + "from_address": { + "description": "Originating address of the transfer", "type": "string" }, - "original_currency": { - "description": "The currency of the withdrawn amount, 3-letter ISO code", + "id": { + "description": "The crypto transfer ID", + "format": "uuid", "type": "string" }, - "payment_type": { - "$ref": "#/components/schemas/FundingDetailPaymentType" - }, - "requested_amount": { - "description": "The amount sent as part of the withdrawal creation", - "format": "decimal", + "network_fee": { "type": "string" }, "status": { - "$ref": "#/components/schemas/FundingWalletTransferStatus" + "$ref": "#/components/schemas/CryptoTransferStatus" }, - "updated_at": { - "format": "date-time", + "to_address": { + "description": "Destination address of the transfer", "type": "string" }, - "usd": { - "$ref": "#/components/schemas/Usd" + "tx_hash": { + "description": "On-chain transaction hash (e.g. 0xabc...xyz)", + "type": "string" + }, + "usd_value": { + "description": "Equivalent USD value at time of transfer", + "type": "string" } }, "type": "object" }, - "FundingWalletTransferDirection": { - "description": "Status:\n * `incoming`: incoming amount\n * `outgoing`: outgoing amount\n", - "enum": [ - "incoming", - "outgoing" - ], - "type": "string" - }, - "FundingWalletTransferStatus": { - "description": "Status:\n * `PENDING`: Created and waiting to be processed\n * `CANCELED`: Canceled\n * `FAILED`: Failed mostly due to technical reasons\n * `COMPLETE`: Transfer has settled\n", + "CryptoTransferStatus": { "enum": [ - "PENDING", - "CANCELED", - "EXECUTED", + "PROCESSING", "FAILED", "COMPLETE" ], + "example": "PROCESSING", "type": "string" }, - "IFFee": { + "CryptoWallet": { "properties": { - "amount": { - "format": "decimal", + "address": { "type": "string" }, - "id": { - "format": "uuid", + "chain": { "type": "string" }, - "type": { - "$ref": "#/components/schemas/IFFeeType" + "created_at": { + "description": "Timestamp (RFC3339) of account creation.", + "format": "date-time", + "type": "string" } }, - "required": [ - "id", - "amount", - "type" - ], "type": "object" }, - "IFFeeType": { - "description": "Status:\n * `partner`: The fee on a transfer that was allocated to the partner\n * `alpaca`: The fee on a transfer that was allocated to Alpaca\n", - "enum": [ - "partner", - "alpaca" - ], + "Currency": { + "description": "\"USD\" // US Dollar\n\"JPY\" // Japanese Yen\n\"EUR\" // Euro\n\"CAD\" // Canadian Dollar\n\"GBP\" // British Pound Sterling\n\"CHF\" // Swiss Franc\n\"TRY\" // Turkish Lira\n\"AUD\" // Australian Dollar\n\"CZK\" // Czech Koruna\n\"SEK\" // Swedish Krona\n\"DKK\" // Danish Krone\n\"SGD\" // Singapore Dollar\n\"HKD\" // Hong Kong Dollar\n\"HUF\" // Hungarian Forint\n\"NZD\" // New Zealand Dollar\n\"NOK\" // Norwegian Krone\n\"PLN\" // Poland Złoty", + "title": "Currency", "type": "string" }, - "IPOEvent": { - "description": "Represents an IPO offering lifecycle event delivered over the IPO events streaming API (`/v2/events/ipos`).\n\nIPO events come in six `verb` types:\n- **`Offering`** - initial publication of an offering. The `payload` carries `name`, `available_to_order`, `ticker_symbol`, `min_price`, `max_price` (and optional `source`).\n- **`OfferingUpdate`** - update to a previously published offering. The `payload` shape is identical to `Offering`.\n- **`Prospectus`** - prospectus document is now available. The `payload` carries `prospectus_url` and `subject`.\n- **`SixtyMinMail`** - 60-minute pricing window has opened, no new orders accepted. The `payload` carries `sixty_minute_expiration_time` and `subject`.\n- **`Allocation`** - final allocation result for a specific account. The `payload` carries `cusip_id`, `final_price`, `allocated_shares`, `allocated_amount`, `subject`. **This is the only verb that is account-scoped**: `account_id` and `correspondent` are populated.\n- **`OfferingCancellation`** - offering was cancelled. **No `payload` is present.**\n\nSee the example payloads below for a concrete example per verb.\n", + "CustodialAccountMinorIdentity": { + "description": "Represents Identity information for a minor that an account of type \"custodial\" is for", "properties": { - "account_id": { - "description": "The account this event applies to. Only populated for `Allocation` events; omitted for system-wide verbs (`Offering`, `OfferingUpdate`, `Prospectus`, `SixtyMinMail`, `OfferingCancellation`).\n", - "format": "uuid", + "country_of_birth": { "type": "string" }, - "at": { - "description": "Timestamp the event was emitted by the streaming service.", - "format": "date-time", + "country_of_citizenship": { "type": "string" }, - "correspondent": { - "description": "The correspondent that owns the account. Only populated for `Allocation` events.\n", - "example": "COR1", + "country_of_tax_residence": { "type": "string" }, - "event_id": { - "description": "Lexically sortable, monotonically increasing identifier for this event. Use this value with `since_id`/`until_id` to resume the stream from a known point.", - "format": "ulid", + "date_of_birth": { + "format": "date", "type": "string" }, - "offering_reference": { - "description": "The IPO offering this event refers to. Use this value with `GET /v1/ipos/{offering_reference}` to fetch the full offering metadata.\n", - "example": "FI111225", + "email": { + "format": "email", "type": "string" }, - "payload": { - "description": "Verb-specific payload. The shape varies by `verb` - see schema description and `components/examples` for the structure of each. **Omitted entirely for `OfferingCancellation`.**\n", - "type": "object" + "family_name": { + "type": "string" }, - "received_at": { - "description": "Timestamp the upstream event was first received by Alpaca's IPO ingestion pipeline (before fanout).", - "format": "date-time", + "given_name": { "type": "string" }, - "verb": { - "description": "The IPO event type. Determines the shape of `payload`.", - "enum": [ - "Allocation", - "Offering", - "OfferingUpdate", - "Prospectus", - "SixtyMinMail", - "OfferingCancellation" - ], + "state": { + "type": "string" + }, + "tax_id": { "type": "string" + }, + "tax_id_type": { + "$ref": "#/components/schemas/TaxIdType" } }, "required": [ - "event_id", - "at", - "verb", - "offering_reference", - "received_at" + "given_name", + "family_name", + "date_of_birth", + "tax_id_type", + "country_of_tax_residence", + "state", + "email" ], - "title": "IPOEvent", - "type": "object", - "x-examples": { - "Allocation": { - "account_id": "6221407b-32b2-3e6b-a7fe-cd79e1b68ba3", - "at": "2026-03-20T00:15:42.318901Z", - "correspondent": "COR1", - "event_id": "01J9RTPZGHKVB8M3X4F5J7LCQ9", - "offering_reference": "FI111225", - "payload": { - "allocated_amount": "1764", - "allocated_shares": "42", - "cusip_id": "123456789", - "final_price": "42", - "subject": "IPO Allocation: Example Corp" - }, - "received_at": "2026-03-20T00:15:42.103572Z", - "verb": "Allocation" - }, - "Offering": { - "at": "2026-03-20T12:24:58.807230Z", - "event_id": "01J9RPMV5TKB8WX3M4F1KZ7QH2", - "offering_reference": "FI111225", - "payload": { - "available_to_order": "Available", - "max_price": "42", - "min_price": "42", - "name": "Example Corp", - "source": "example source", - "ticker_symbol": "EXMP" - }, - "received_at": "2026-03-20T12:24:58.500000Z", - "verb": "Offering" - }, - "OfferingCancellation": { - "at": "2026-03-20T12:24:52.325158Z", - "event_id": "01J9RVB6Y4ZK8M3N7QD2WX1RFP", - "offering_reference": "FI111225", - "received_at": "2026-03-20T12:24:52.110430Z", - "verb": "OfferingCancellation" + "type": "object" + }, + "CustodialAccountType": { + "description": "Represents the type of custodial account based on the state where the beneficiary resides.\nThis value is returned only when the `country_of_tax_residence` is `USA`. For other countries, this property is not included in the response.\n\n**Possible Return Values**\n\n`CustodianTypeUTMA` - Indicates that the custodial account is governed by the Uniform Transfers to Minors Act.\n`CustodianTypeUGMA` - Indicates that the custodial account is governed by the Uniform Gifts to Minors Act.", + "title": "CustodialAccountType", + "type": "string" + }, + "DIVNRAActivityV2": { + "allOf": [ + { + "$ref": "#/components/schemas/CommonNTAActivityV2" }, - "OfferingUpdate": { - "at": "2026-03-20T15:42:11.118274Z", - "event_id": "01J9RQ5HNZQK7M3RDVJ8XBPCT1", - "offering_reference": "FI111225", - "payload": { - "available_to_order": "Available", - "max_price": "44", - "min_price": "40", - "name": "Example Corp", - "source": "example source", - "ticker_symbol": "EXMP" + { + "properties": { + "cusip": { + "description": "The CUSIP of the security involved with the activity", + "type": "string" + }, + "parent_id": { + "description": "The ref_id of the parent dividend", + "format": "uuid", + "type": "string" + }, + "symbol": { + "description": "The symbol of the security involved with the activity", + "type": "string" + }, + "tax_country": { + "description": "Tax country used as the primary jurisdiction when determining withholding treatment for the dividend. Other factors may affect the final withholding rate.\nThe value is an [ISO 3166-1 alpha-3](https://www.iso.org/iso-3166-country-codes.html) country code.\n", + "example": "THA", + "type": "string" + }, + "tax_rate": { + "description": "The tax withholding rate applied to the dividend, represented as a value between 0 and 1", + "example": "0.15", + "format": "decimal", + "type": "string" + } }, - "received_at": "2026-03-20T15:42:10.892140Z", - "verb": "OfferingUpdate" + "required": [ + "cusip", + "symbol", + "parent_id" + ], + "type": "object" + } + ], + "description": "Dividend withholding for non resident aliens", + "type": "object" + }, + "DIVSPDActivityV2": { + "allOf": [ + { + "$ref": "#/components/schemas/CommonCaActivityV2" }, - "Prospectus": { - "at": "2026-03-19T23:26:46.853262Z", - "event_id": "01J9RSGD8JR2A4K7MZNTPVQ5XW", - "offering_reference": "FI111225", - "payload": { - "prospectus_url": "https://example.com/prospectus/example-corp.pdf", - "subject": "IPO Prospectus: Example Corp" + { + "properties": { + "cash_payout": { + "description": "Total cash amount paid", + "type": "string" + }, + "cusip": { + "description": "The CUSIP of the security involved with the activity", + "type": "string" + }, + "due_bill_off_date": { + "description": "When due bills stop applying for this event", + "format": "date", + "minLength": 1, + "type": "string" + }, + "due_bill_on_date": { + "description": "When due bills begin to apply for this event", + "format": "date", + "minLength": 1, + "type": "string" + }, + "entitled_qty": { + "description": "Quantity of shares entitled to receive cash in lieu", + "type": "string" + }, + "ex_date": { + "description": "The ex_date for this corporate action", + "format": "date", + "minLength": 1, + "type": "string" + }, + "foreign": { + "description": "Indicates if related to a non-US security. Serialized as the JSON strings `\"true\"` or `\"false\"`, not a JSON boolean.", + "enum": [ + "true", + "false" + ], + "example": "false", + "type": "string" + }, + "isin": { + "description": "The ISIN of the security involved with the activity", + "type": "string" + }, + "payable_date": { + "description": "The payable_date for this corporate action", + "format": "date", + "minLength": 1, + "type": "string" + }, + "rate": { + "description": "Cash payout per share", + "type": "string" + }, + "record_date": { + "description": "The record_date for this corporate action", + "format": "date", + "minLength": 1, + "type": "string" + }, + "special": { + "description": "Indicates if this is a special dividend. Serialized as the JSON strings `\"true\"` or `\"false\"`, not a JSON boolean.", + "enum": [ + "true", + "false" + ], + "example": "false", + "type": "string" + }, + "symbol": { + "description": "The symbol of the security involved with the activity", + "type": "string" + } }, - "received_at": "2026-03-19T23:26:46.612880Z", - "verb": "Prospectus" + "required": [ + "entitled_qty", + "cash_payout", + "symbol", + "cusip", + "rate", + "foreign", + "special" + ], + "type": "object" + } + ], + "description": "Substitute payment in lieu of dividend", + "type": "object" + }, + "DIVWHActivityV2": { + "allOf": [ + { + "$ref": "#/components/schemas/CommonNTAActivityV2" }, - "SixtyMinMail": { - "at": "2026-03-19T22:50:11.729329Z", - "event_id": "01J9RT9P3WYH8KCJM2LFQX5ZRD", - "offering_reference": "FI111225", - "payload": { - "sixty_minute_expiration_time": "2026-03-19T23:50:11.729329Z", - "subject": "IPO 60 Minute Mail: Example Corp" + { + "properties": { + "cusip": { + "description": "The CUSIP of the security involved with the activity", + "type": "string" + }, + "isin": { + "description": "The ISIN of the security involved with the activity", + "type": "string" + }, + "parent_id": { + "description": "The ref_id of the parent dividend", + "format": "uuid", + "type": "string" + }, + "symbol": { + "description": "The symbol of the security involved with the activity", + "type": "string" + } }, - "received_at": "2026-03-19T22:50:11.512640Z", - "verb": "SixtyMinMail" + "required": [ + "cusip", + "symbol", + "parent_id" + ], + "type": "object" } - } + ], + "description": "Dividend tax withholding (e.g. foreign withholding on global securities)", + "type": "object" }, - "IPOOffering": { - "description": "An IPO (Initial Public Offering) offering exposed via the IPO discovery REST API. Returned by `GET /v1/ipos` and `GET /v1/ipos/{offering_reference}`.\n\nMost IPO lifecycle changes (new offerings, prospectus availability, 60-minute mail, allocations, cancellations) are pushed in real time over the [IPO Events Stream](#operation/subscribeToIPOEventsSSE).\n", + "DailyCashInterest": { "properties": { - "anticipated_shares": { - "description": "Anticipated total share count for the offering, when known.", - "format": "int64", - "type": "integer" - }, - "availability": { - "description": "Whether the offering is currently accepting orders.\n- `available` - accepting new orders.\n- `not_available` - not yet open for orders.\n- `closed` - no longer accepting orders.\n\nNote: the IPO Events Stream uses a different casing for the analogous `payload.available_to_order` field on `Offering`/`OfferingUpdate` events (`Available`, `NotAvailable`, `Closed`).\n", - "enum": [ - "available", - "not_available", - "closed" - ], - "example": "available", + "account_accrued_interest": { + "example": "1.2152", + "format": "decimal", "type": "string" }, - "cusip_id": { - "description": "CUSIP identifier of the offering.", - "example": "123456789", + "account_id": { + "example": "02e5c5ba-36d2-467a-8699-81dc24271291", + "format": "uuid", "type": "string" }, - "description": { - "description": "A longer human-readable description of the offering, when available.", - "type": "string" + "account_rate_bps": { + "example": 423, + "type": "integer" }, - "ipo_reference": { - "description": "The unique offering identifier used across the IPO Events Stream and `/v1/ipos/{offering_reference}`. Note that this value is also used as the path parameter, where it is named `offering_reference`.\n", - "example": "FI111225", + "apr_tier_id": { + "example": "13d149c8-d620-43e0-978b-88af8abb2ef1", + "format": "uuid", "type": "string" }, - "logo_small": { - "description": "URL to a small logo asset for the issuer.", + "apr_tier_name": { + "example": "gold", "type": "string" }, - "max_price": { - "description": "Upper bound of the indicated price range.", - "example": "42", + "cash_balance": { + "example": "10485.76", "format": "decimal", "type": "string" }, - "max_ticket_size": { - "description": "Maximum allowed order amount.", - "type": "string" - }, - "min_price": { - "description": "Lower bound of the indicated price range.", - "example": "42", + "correspondent_fee": { + "example": "0.0718", "format": "decimal", "type": "string" }, - "min_ticket_size": { - "description": "Minimum allowed order amount.", - "type": "string" + "correspondent_rate_bps": { + "example": 25, + "type": "integer" }, - "name": { - "description": "The official name of the offering.", - "example": "Example Corp", + "currency": { + "example": "USD", "type": "string" }, - "no_new_orders": { - "description": "When `true`, the offering is in its 60-minute pricing window and is not accepting new orders. This mirrors the `SixtyMinMail` event on the IPO Events Stream.\n", - "example": false, - "type": "boolean" + "date": { + "example": "2024-09-27", + "format": "date", + "type": "string" + } + }, + "type": "object" + }, + "DailyTradingLimit": { + "description": "Correspondent-level daily net buying limit and the real-time amount currently consuming it.", + "properties": { + "cash_held": { + "description": "Cash reserved for open cash-secured put positions. Only present in the response when the correspondent has at least one open cash-secured put; the field is omitted from the response otherwise (including for options-enabled correspondents with no open cash-secured puts).", + "example": "20000", + "format": "decimal", + "type": "string" }, - "offering_type": { - "description": "The type of offering. Currently always `IPO`.", - "example": "IPO", + "correspondent": { + "description": "The correspondent code the limits apply to.", + "example": "EXMP", "type": "string" }, - "prospectus_url": { - "description": "URL to the prospectus document. Mirrors the `prospectus_url` payload of the corresponding `Prospectus` event on the IPO Events Stream.\n", + "daily_net_limit": { + "description": "The daily net buying limit for the correspondent; further buying is halted once the in_use_limit limit reaches this value.", + "example": "1000000", + "format": "decimal", "type": "string" }, - "settlement_date": { - "description": "Anticipated settlement date.", - "format": "date", + "executed_buys": { + "description": "Cumulative cash value of executed buy activity contributing to the in-use limit since the last reset.", + "example": "55", + "format": "decimal", "type": "string" }, - "ticker_symbol": { - "description": "The ticker symbol that will be used once the security begins trading on the secondary market.", - "example": "EXMP", + "executed_sells": { + "description": "Cumulative cash value of executed sell activity offsetting the in-use limit since the last reset.", + "example": "0", + "format": "decimal", "type": "string" }, - "trade_date": { - "description": "Anticipated first trading date on the secondary market.", - "format": "date", + "in_use_limit": { + "description": "Real-time net amount currently consuming the daily limit. Equal to `executed_buys` − `executed_sells` + `open_buys` − `open_sells` (+ `cash_held` when a cash-secured put is open). Resets at the start of the next trading day at 8.00 PM ET.", + "example": "55", + "format": "decimal", "type": "string" }, - "underwriters": { - "description": "List of underwriter names participating in the offering.", - "items": { - "type": "string" - }, - "type": "array" + "open_buys": { + "description": "Reserved cash value of currently open buy orders (limit price for limit orders, notional or estimated mid for market orders) contributing to the in-use limit.", + "example": "0", + "format": "decimal", + "type": "string" }, - "unit_step_size": { - "description": "The minimum increment in which order quantities can be specified.", + "open_sells": { + "description": "Reserved cash value of currently open sell orders offsetting the in-use limit.", + "example": "0", + "format": "decimal", "type": "string" } }, - "required": [ - "name", - "ipo_reference", - "offering_type", - "availability", - "max_price", - "min_price", - "no_new_orders" - ], - "title": "IPOOffering", + "title": "DailyTradingLimit", "type": "object" }, - "IPOOfferingListResponse": { - "description": "Wrapper response returned by `GET /v1/ipos`.", + "DemoFundingTransfer": { "properties": { - "data": { - "description": "The page of IPO offerings matching the query.", - "items": { - "$ref": "#/components/schemas/IPOOffering" - }, - "type": "array" + "amount": { + "format": "decimal", + "type": "string" }, - "next_page_token": { - "description": "Opaque cursor for the next page of results. When `null`, there are no further pages. Pass this value back as the `page_token` query parameter to fetch the next page.\n", - "nullable": true, + "currency": { + "type": "string" + }, + "receiver_account_number": { + "type": "string" + }, + "receiver_routing_code": { "type": "string" } }, - "required": [ - "data", - "next_page_token" - ], - "title": "IPOOfferingListResponse", - "type": "object" - }, - "IPOOfferingResponse": { - "description": "Wrapper response returned by `GET /v1/ipos/{offering_reference}`.", - "properties": { - "data": { - "$ref": "#/components/schemas/IPOOffering" - } - }, - "required": [ - "data" - ], - "title": "IPOOfferingResponse", "type": "object" }, - "Identity": { - "description": "Identity is the model to provide account owner's identity information.\n", - "example": { - "country_of_birth": "AUS", - "country_of_citizenship": "AUS", - "country_of_tax_residence": "USA", - "date_of_birth": "1990-01-01", - "family_name": "Doe", - "funding_source": [ - "employment_income" - ], - "given_name": "John", - "tax_id": "666-55-4321", - "tax_id_type": "USA_SSN" - }, + "DisclosureContextAnnotation": { "properties": { - "annual_income_max": { - "description": "The upper bound of the user's annual income.", - "type": "number" + "company_city": { + "description": "Required for FINRA affiliations and controlled firms.", + "type": "string" }, - "annual_income_min": { - "description": "The lower bound of the user's annual income.", - "type": "number" + "company_compliance_email": { + "description": "Required for FINRA affiliations and controlled firms.", + "type": "string" }, - "country_of_birth": { - "description": "[ISO 3166-1 alpha-3](https://www.iso.org/iso-3166-country-codes.html).\n", - "example": "USA", + "company_country": { + "description": "Required for FINRA affiliations and controlled firms.", "type": "string" }, - "country_of_citizenship": { - "description": "[ISO 3166-1 alpha-3](https://www.iso.org/iso-3166-country-codes.html).\n", - "example": "USA", + "company_name": { + "description": "Required for FINRA affiliations and controlled firms.", "type": "string" }, - "country_of_tax_residence": { - "description": "[ISO 3166-1 alpha-3](https://www.iso.org/iso-3166-country-codes.html).\n", - "example": "USA", + "company_state": { + "description": "Required if and only if `company_country` is `USA`.", "type": "string" }, - "date_of_birth": { - "description": "The date of birth in \"YYYY-MM-DD\" format.", - "example": "1990-01-01", - "format": "date", + "company_street_address": { + "description": "Required for FINRA affiliations and controlled firms.", "type": "string" }, - "date_of_departure_from_usa": { - "description": "Required if `visa_type` = B1 or B2", - "format": "date", + "context_type": { + "description": "Specifies the type of disclosure annotation. Valid types are FINRA affiliations, for users affiliated with or employed by a FINRA member firm, a Stock Exchange Member, FINRA, Registered Investment Advisor, or a Municipal Securities Broker/Dealer; Company control relationships, for senior executives, and 10% or greater shareholders, of a publicly traded company; and immediate family members of politically exposed individuals.", + "enum": [ + "CONTROLLED_FIRM", + "IMMEDIATE_FAMILY_EXPOSED", + "AFFILIATE_FIRM" + ], "type": "string" }, "family_name": { - "description": "The last name (surname) of the user.", - "example": "Doe", + "description": "Required for immediate family members of politically exposed persons.", "type": "string" }, - "funding_source": { - "description": "Can be one or more of the following: `employment_income`, `investments`, `inheritance`, `business_income`, `savings`, `family`.", + "given_name": { + "description": "Required for immediate family members of politically exposed persons.", + "type": "string" + } + }, + "required": [ + "context_type" + ], + "title": "DisclosureContextAnnotation", + "type": "object" + }, + "Disclosures": { + "description": "Disclosures fields denote if the account owner falls under\neach category defined by FINRA rule. The client has to ask\nquestions for the end user and the values should reflect\ntheir answers.\nIf one of the answers is true (yes), the account goes into\nACTION_REQUIRED status.\n", + "example": { + "immediate_family_exposed": false, + "is_affiliated_exchange_or_finra": false, + "is_control_person": false, + "is_politically_exposed": false + }, + "properties": { + "context": { + "description": "Array of annotations describing the rationale for marking `is_control_person`, `is_affiliated_exchange_or_finra`, and/or `immediate_family_exposed` as true", "items": { - "enum": [ - "employment_income", - "investments", - "inheritance", - "business_income", - "savings", - "family" - ], - "type": "string" + "$ref": "#/components/schemas/DisclosureContextAnnotation" }, - "type": "array" + "type": [ + "array", + "null" + ] }, - "given_name": { - "description": "The first/given name of the user.", - "example": "John", + "employer_address": { + "description": "The employer's address if the user is employed.", "type": "string" }, - "investment_experience_with_options": { - "description": "The user's level of expertise and familiarity with investing in Options.\n", - "enum": [ - "none", - "1_to_5_years", - "over_5_years" - ], + "employer_name": { + "description": "The name of the employer if the user is employed.", "type": "string" }, - "investment_experience_with_stocks": { - "description": "The user's level of expertise and familiarity with investing in US Equities.\n", - "enum": [ - "none", - "1_to_5_years", - "over_5_years" - ], + "employment_position": { + "description": "The user's position if they are employed.", "type": "string" }, - "investment_objective": { - "deprecated": true, - "description": "The user's investment objective. This field is deprecated. Please use the top level `investment_objective` field.\n", - "enum": [ - "generate_income", - "preserve_wealth", - "market_speculation", - "growth", - "balance_preserve_wealth_with_growth" - ], - "type": "string" - }, - "investment_time_horizon": { - "deprecated": true, - "description": "The expected period of time the user plan to invest to achieve his/her financial goal(s). This field is deprecated. Please use the top level `investment_time_horizon` field.\n", - "enum": [ - "less_than_1_year", - "1_to_2_years", - "3_to_5_years", - "6_to_10_years", - "more_than_10_years" - ], - "type": "string" - }, - "liquid_net_worth_max": { - "description": "The upper bound of the user's liquid net worth.", - "type": "number" - }, - "liquid_net_worth_min": { - "description": "The lower bound of the user's liquid net worth.", - "type": "number" - }, - "liquidity_needs": { - "deprecated": true, - "description": "The user's ability to quickly and easily convert all or part of their investments in this account to cash without significant loss in value. This field is deprecated. Please use the top level `liquidity_needs` field.\n", + "employment_sector": { + "description": "The industry sector of employment.\nIf the `employment_status` is `unemployed` or `student`, set this property to `not_employed`.\nIf the `employment_status` is `retired`, set this to `self_employed`.\n", "enum": [ - "very_important", - "important", - "somewhat_important", - "does_not_matter" + "agriculture", + "business_management", + "computers_and_it", + "construction", + "education", + "finance", + "government", + "healthcare", + "hospitality", + "manufacturing", + "marketing", + "media", + "other", + "science", + "self_employed", + "transportation", + "not_employed" ], "type": "string" }, - "marital_status": { - "description": "The marital status of the user.\n", + "employment_status": { + "description": "One of the following: `employed`, `unemployed`, `retired`, or `student`.", "enum": [ - "SINGLE", - "MARRIED", - "DIVORCED", - "WIDOWED" + "unemployed", + "employed", + "student", + "retired" ], "type": "string" }, - "middle_name": { - "description": "The middle name of the user.", - "type": "string" - }, - "number_of_dependents": { - "description": "The number of dependents the user has.\n", - "type": "integer" - }, - "permanent_resident": { - "description": "Only used to collect permanent residence status in the USA.", + "immediate_family_exposed": { + "description": "If your user's immediate family member (sibling, husband/wife, child, parent) is either politically exposed or holds a control position.", "type": "boolean" }, - "risk_tolerance": { - "deprecated": true, - "description": "The user's investment risk tolerance. This field is deprecated. Please use the top level `risk_tolerance` field.\n", - "enum": [ - "conservative", - "moderate", - "significant_risk" - ], - "type": "string" - }, - "tax_id": { - "description": "Required if tax_id_type is set.", - "example": "666-55-4321", - "type": "string" - }, - "tax_id_type": { - "$ref": "#/components/schemas/TaxIdType" - }, - "total_net_worth_max": { - "description": "The upper bound of the user's total net worth.", - "type": "number" - }, - "total_net_worth_min": { - "description": "The lower bound of the user's total net worth.", - "type": "number" + "is_affiliated_exchange_or_finra": { + "description": "Whether user is affiliated with any exchanges or FINRA.", + "type": "boolean" }, - "visa_expiration_date": { - "description": "Required if `visa_type` is set.", - "format": "date", - "type": "string" + "is_control_person": { + "description": "Whether user holds a controlling position in a publicly traded company, member of the board of directors or has policy making abilities in a publicly traded company.", + "type": "boolean" }, - "visa_type": { - "description": "Only used to collect visa types for users residing in the USA.", - "type": "string" + "is_politically_exposed": { + "description": "Whether the user is politically exposed.", + "type": "boolean" } }, "required": [ - "given_name", - "family_name", - "date_of_birth", - "tax_id_type", - "country_of_tax_residence", - "funding_source" + "is_control_person", + "is_affiliated_exchange_or_finra", + "is_politically_exposed", + "immediate_family_exposed" ], "type": "object" }, - "InstantFunding": { + "DoNotExerciseRequest": { + "description": "Request to submit a do-not-exercise (DNE) instruction for an option contract for an account.\nThe request body is empty. A successful request will mark the entire position as do not exercise.", + "title": "DoNotExerciseRequest", + "type": "object" + }, + "DoNotExerciseResponse": { + "description": "Response to a do-not-exercise request.\nA 200 response indicates that the entire position is set as do not exercise.\nThe response body is empty.", + "title": "DoNotExerciseResponse", + "type": "object" + }, + "EnabledAssetClass": { + "description": "An asset class that can be enabled on a brokerage account, i.e. a value that may appear in an account's `enabled_assets`. This differs from `AssetClass` (which also covers order and asset metadata) by excluding `ipo`, which is not an enable-able asset.", + "enum": [ + "us_equity", + "us_option", + "crypto" + ], + "type": "string" + }, + "EoDCashInterestReportResponse": { "properties": { - "account_no": { - "type": "string" - }, - "amount": { - "format": "decimal", - "type": "string" - }, - "created_at": { - "format": "date-time", - "type": "string" - }, - "deadline": { - "format": "date", - "type": "string" - }, - "fees": { - "items": { - "$ref": "#/components/schemas/IFFee" - }, - "type": "array" - }, - "id": { - "format": "uuid", - "type": "string" - }, - "interests": { + "interest": { "items": { - "$ref": "#/components/schemas/Interest" + "$ref": "#/components/schemas/DailyCashInterest" }, "type": "array" }, - "remaining_payable": { - "format": "decimal", - "type": "string" - }, - "source_account_no": { - "type": "string" - }, - "status": { - "$ref": "#/components/schemas/InstantFundingStatus" - }, - "system_date": { - "format": "date", - "type": "string" + "next_page_token": { + "example": "293639be-8807-423f-b00d-68c781e8bb56", + "type": [ + "string", + "null" + ] + } + }, + "type": "object" + }, + "Error": { + "properties": { + "code": { + "type": "number" }, - "total_interest": { - "format": "decimal", + "message": { "type": "string" } }, "required": [ - "id", - "amount", - "account_no", - "source_account_no", - "status", - "system_date", - "deadline", - "created_at", - "total_interest", - "remaining_payable", - "interests", - "fees" + "code", + "message" ], + "title": "Error", "type": "object" }, - "InstantFundingStatus": { - "description": "Status:\n * `PENDING`: Created and waiting to be processed\n * `CANCELED`: Canceled\n * `EXECUTED`: All fees (including Partner fee) are transacted\n * `FAILED`: Failed mostly due to technical reasons\n * `COMPLETED`: All transactions are settled\n", + "Exchange": { + "description": "Represents the exchange where an asset is traded.\n\nFor Stocks:\n- AMEX\n- ARCA\n- BATS\n- NYSE\n- NASDAQ\n- NYSEARCA\n- OTC\n\nFor Crypto:\n- CRYPTO", "enum": [ - "PENDING", - "CANCELED", - "EXECUTED", - "FAILED", - "COMPLETED" + "AMEX", + "ARCA", + "BATS", + "NYSE", + "NASDAQ", + "NYSEARCA", + "OTC" ], + "example": "NASDAQ", + "title": "Exchange", "type": "string" }, - "Interest": { - "properties": { - "amount": { - "format": "decimal", - "type": "string" - }, - "created_at": { - "format": "date-time", - "type": "string" - }, - "date": { - "format": "date", - "type": "string" + "ExchangeOfferActivityV2": { + "allOf": [ + { + "$ref": "#/components/schemas/CommonNTAActivityV2" }, - "id": { - "format": "uuid", + { + "$ref": "#/components/schemas/CommonVOFSubtypeActivityV2" + } + ], + "type": "object" + }, + "ExerciseRequest": { + "description": "Request to exercise an option contract for an account.\nThis can be omitted or left empty, but it also allows for optionally charging a commission for exercising or\nspecifying a specific quantity of contracts to exercise (rather than the full quantity of contracts held).", + "properties": { + "commission": { + "description": "The commission you want to collect from the user. (notional amount)", + "example": "0.25", + "format": "decimal", "type": "string" }, - "reconciled_at": { - "format": "date-time", + "qty": { + "description": "The number of contracts to exercise. If not provided, all contracts will be exercised.", + "example": "1", + "format": "decimal", "type": "string" - }, - "status": { - "$ref": "#/components/schemas/InstantFundingStatus" } }, - "required": [ - "id", - "date", - "amount", - "status", - "created_at", - "reconciled_at" - ], + "title": "ExerciseRequest", "type": "object" }, - "IssueOAuthTokenResponse": { - "description": "", + "ExerciseResponse": { + "description": "Response to an exercise request.\nThis will return the total quantity of contracts exercised as well as the quantity of remaining contracts.", "properties": { - "access_token": { - "description": "OAuth token", + "qty_exercised": { + "description": "The total quantity of contracts exercised.", + "format": "decimal", "type": "string" }, - "scope": { - "description": "Token's scope", + "qty_remaining": { + "description": "The total quantity of contracts remaining after the exercise.", + "format": "decimal", "type": "string" + } + }, + "title": "ExerciseResponse", + "type": "object" + }, + "FEEActivityV2": { + "allOf": [ + { + "$ref": "#/components/schemas/CommonNTAActivityV2" }, - "token_type": { - "description": "Always `Bearer`", - "enum": [ - "Bearer" + { + "properties": { + "parent_id": { + "description": "The parent transaction's ID", + "format": "uuid", + "type": "string" + } + }, + "required": [ + "parent_id" ], - "example": "Bearer", - "type": "string" + "type": "object" } - }, - "required": [ - "access_token", - "token_type", - "scope" ], - "type": "object", - "x-examples": { - "example-1": { - "access_token": "87586f14-c3f4-4912-b107-f75bc17ff87a", - "scope": "general", - "token_type": "Bearer" - } - } + "description": "Fee", + "type": "object" }, - "JITAssetClass": { - "description": "Values:\n * `crypto`: Used to identify a crypto only account\n * `us_equity`: Used to identify an account that trades US equities, or both crypto and\n US equities.\n", - "enum": [ - "crypto", - "us_equity" + "FOPTActivityV2": { + "allOf": [ + { + "$ref": "#/components/schemas/CommonNTAActivityV2" + }, + { + "properties": { + "contra": { + "description": "Contra for the transfer", + "type": "string" + }, + "external_id": { + "description": "External ID of the transfer", + "type": "string" + }, + "symbol": { + "description": "The symbol of the security involved with the activity", + "type": "string" + } + }, + "required": [ + "external_id", + "contra", + "symbol" + ], + "type": "object" + } ], - "type": "string" + "description": "Free-of-payment (FOP) transfers", + "type": "object" }, - "JITLedgerAccount": { + "FPSLAnalyticsLoansResponse": { + "description": "Aggregated FPSL interest and loan-activity counts for one account over a date range.\n", "properties": { - "created_at": { - "description": "Creation time in UNIX format", + "account_number": { + "description": "Account's number at Alpaca", + "example": "286873545", "type": "string" }, - "id": { - "description": "The ledger ID", - "type": "string" + "in_progress_lending_activities": { + "description": "The number of FPSL lending activities currently in progress for the account.", + "example": 2, + "format": "int64", + "type": "integer" }, - "ledger_name": { - "description": "The ledger name", - "type": "string" + "interest": { + "$ref": "#/components/schemas/FPSLInterest" }, - "status": { - "type": "string" + "total_lending_activities": { + "description": "The total number of FPSL lending activities for the account within the specified date range.", + "example": 7, + "format": "int64", + "type": "integer" } }, - "title": "JITLedgerAccount", - "type": "object", - "x-examples": { - "Example 1": { - "created_at": "1653532627", - "id": "2896b9e2-3198-44cd-a08e-4cc4079aee33", - "ledger_name": "Securities JIT IN", - "status": "active" - } - } + "required": [ + "account_number", + "total_lending_activities", + "in_progress_lending_activities" + ], + "type": "object" }, - "JITReport": { - "description": "JIT Securities reports are made available through the API and can be accessed within the hour after 11:30 PM EST on the trade date (T+0). The reports communicate transaction-level details as well as overall settlement amounts, transfer direction, and payment timing.", + "FPSLError": { + "description": "FPSL API error response", "properties": { - "detail": { - "description": "Contains all activities that impact cash throughout the trading session including executed trades, trading fees, and corporate actions that involve cash allocations.\n\ncontent-type = application/csv", - "type": "string" - }, - "net_payment": { - "description": "Highlights the net amount due to Alpaca by settlement or to the partner on the date of settlement in a formalized invoice format.\n\ncontent-type = application/pdf", - "type": "string" - }, - "net_payment_final": { - "description": "Includes additional information to account for T+0 and T+1 settling activity to clarify settlement journaling reconciliation. This report is generated after trading session close on T+1.\n\ncontent-type = application/pdf", - "type": "string" - }, - "net_summary": { - "description": "Consists of three columns and a single row, which lists the net money movement to or from Alpaca for T0, T1, and T2.\n\ncontent-type = application/csv", - "type": "string" - }, - "obligation": { - "description": "Lists of all open obligations towards the partner that are to be settled.\n\ncontent-type = application/csv", + "message": { + "example": "Internal Server Error", "type": "string" } }, - "title": "JITReport", "type": "object" }, - "JITReportDownloadURL": { + "FPSLInterest": { + "description": "The interest's details.\nPlease note that the object might not be set if interest has not yet been calculated for the loan.\nAdditionally, interest details may be adjusted retroactively at any time until interests are finalized for the month.\nThe loan's changed `updated_at` field can indicate such an adjustment.\n", "properties": { - "expires_at": { - "description": "Timestamp when the signed URL expires.", - "format": "date-time", - "type": "string" - }, - "filename": { - "description": "Name of the generated report file.", - "type": "string" + "customer": { + "description": "The interest accrued by the customer for this loan.", + "example": 1.23, + "format": "double", + "type": "number" }, - "url": { - "description": "Temporary signed URL used to download the report.", - "format": "uri", - "type": "string" + "partner": { + "description": "The interest accrued by the partner for this loan.", + "example": 1.65, + "format": "double", + "type": "number" } }, "required": [ - "url", - "filename", - "expires_at" + "customer", + "partner" ], "type": "object" }, - "JNLC": { - "description": "Journal information specific to cash transfers. This field is required for `Journal`s with an `entry_type` of `jnlc` (cash transfers), but will be null for those with `jnls` (securities transfers).", - "example": { - "entry_type": "JNLC", - "from_account": "8fjkjn-4483-4199-840f-6c5fe0b7ca24", - "id": "f45g67h8-d1fc-4136-aa4f-cf4460aecdfc", - "net_amount": "115.5", - "status": "pending", - "to_account": "3gtt65jd-6f2a-433c-8c33-17b66b8941fa" - }, + "FPSLLoan": { + "description": "A loan of a security by an account on a date.", "properties": { - "currency": { - "description": "Currency denomination of the journal. USD by default.", + "account_id": { + "description": "Account's ID at Alpaca", + "example": "06d83e79-5bea-4ce9-a362-a25251a700ec", "type": "string" }, - "description": { - "description": "ID the amount goes to. Only valid for JNLC journals. Null for JNLS.", + "account_number": { + "description": "Account's number at Alpaca", + "example": "286873545", "type": "string" }, - "entry_type": { - "description": "JNLC", - "type": "string" + "collateral": { + "description": "The collateral posted for the loan.", + "example": 1161.015, + "format": "double", + "type": "number" }, - "net_amount": { - "description": "Only valid for JNLC journals. Null for JNLS.", - "format": "decimal", + "correspondent": { + "description": "Account's correspondent", + "example": "LPCA", "type": "string" }, - "transmitter_account_number": { - "description": "Only valid for JNLC journals. Null for JNLS.max 255 characters", + "date": { + "example": "2006-01-02", + "format": "date", "type": "string" }, - "transmitter_address": { - "description": "Only valid for JNLC journals. Null for JNLS.max 255 characters", - "type": "string" + "interest": { + "$ref": "#/components/schemas/FPSLInterest" }, - "transmitter_financial_institution": { - "description": "Only valid for JNLC journals. Null for JNLS.max 255 characters", - "type": "string" + "market_value": { + "description": "The total market value of the shares on loan.", + "example": 1138.25, + "format": "double", + "type": "number" }, - "transmitter_name": { - "description": "Only valid for JNLC journals. Null for JNLS. Max 255 characters.", + "quantity": { + "description": "The number of shares in the loan. This may be less than the total number of eligible shares held by the account.", + "example": 5, + "format": "int64", + "type": "integer" + }, + "symbol": { + "description": "Stock/Ticker symbol of a stock or security", + "example": "AAPL", "type": "string" }, - "transmitter_timestamp": { - "description": "Only valid for JNLC journals. Null for JNLS.", + "updated_at": { + "description": "Timestamp of the last update to this loan in RFC-3339 format with microsecond precision with timezone.\nAn updated value may indicate adjusted interest values.\n", + "example": "2025-03-21T15:54:19.857034+01:00", "format": "date-time", "type": "string" } }, "required": [ - "entry_type", - "net_amount" - ], - "title": "JNLC", - "type": "object", - "x-examples": {} - }, - "JNLCActivityV2": { - "allOf": [ - { - "$ref": "#/components/schemas/CommonJournalActivityV2" - } + "date", + "account_id", + "account_number", + "correspondent", + "symbol", + "quantity", + "market_value", + "collateral" ], - "description": "Journal entry (cash)", "type": "object" }, - "JNLS": { + "FPSLTier": { "properties": { - "currency": { - "description": "Currency denomination of the journal. USD by default.", - "type": "string" - }, - "description": { - "type": "string" - }, - "entry_type": { - "description": "JNLS", + "created_at": { + "description": "The timestamp the FPSL tier was created, in RFC 3339 format.", + "example": "2024-09-16T20:06:32.207729Z", + "format": "date-time", "type": "string" }, - "from_account": { - "description": "The account ID that initiates the journal - account_status must equal to ACTIVE or CLOSE", - "type": "string" + "customer_split": { + "description": "Percentage of the customer split represented as a value between 0 and 1.00. Max 2 decimal places are allowed.", + "example": 0.25, + "type": "number" }, "id": { - "description": "The journal ID", - "type": "string" - }, - "price": { - "description": "The price of the security journaled", - "type": "string" - }, - "qty": { - "description": "The quantity of the securities journaled", + "description": "The unique identifier of the FPSL tier", + "example": "150df7eb-31e7-46b3-8848-a54ca21ff3bc", + "format": "uuid", "type": "string" }, - "settle_date": { - "description": "Date string in \"%Y-%m-%d\" format", + "market": { + "description": "The market of the FPSL tier", + "example": "US", "type": "string" }, - "status": { - "$ref": "#/components/schemas/JournalStatus" - }, - "symbol": { - "description": "The symbol of the security journaled", - "type": "string" + "partner_split": { + "description": "Percentage of the partner split represented as a value between 0 and 1.00. Max 2 decimal places are allowed.", + "example": 0.3, + "type": "number" }, - "system_date": { - "description": "Date string in \"%Y-%m-%d\" format", + "tier_name": { + "description": "The name of the FPSL tier", + "example": "gold", "type": "string" }, - "to_account": { - "description": "The account ID that received the journal - account_status must equal to ACTIVE", + "updated_at": { + "description": "The timestamp of the last update to the FPSL tier, in RFC 3339 format.", + "example": "2024-09-16T20:06:32.207729Z", + "format": "date-time", "type": "string" } }, - "required": [ - "entry_type" + "type": "object" + }, + "FeePaymentMethod": { + "description": "Only outgoing wire fees are currently supported for automated processing.\n\n\n**user**\tThe end user will pay any applicable fees\n**invoice**\tAny applicable fees will be billed to the client in the following monthly invoice", + "title": "FeePaymentMethod", + "type": "string" + }, + "FeePaymentType": { + "description": "Status:\n * `invoice`\n", + "enum": [ + "invoice" ], - "type": "object", - "x-examples": { - "Example 1": { - "description": "this is a test journal", - "entry_type": "JNLS", - "from_account": "8k4f3d-4483-4199-840f-6c5fe0b7ca24", - "id": "f45g67h8-d1fc-4136-aa4f-cf4460aecdfc", - "price": "128.23", - "qty": "0.5", - "settle_date": "2020-12-24", - "status": "executed", - "symbol": "AAPL", - "system_date": "2020-12-24", - "to_account": "g7h7rg66-6f2a-433c-8c33-17b66b8941fa" + "type": "string" + }, + "FeeType": { + "description": "Status:\n * `withdrawal_fee`: Withdrawal fee\n * `fx_fee`: FX Fee\n * `network_fee`\n * `deposit_fee`\n * `ach_return_fee`\n * `parnter_fee`\n * `alpaca_fee`\n", + "enum": [ + "withdrawal_fee", + "fx_fee", + "network_fee", + "deposit_fee", + "ach_return_fee", + "parnter_fee", + "alpaca_fee" + ], + "type": "string" + }, + "FixedIncomeInterestActivityV2": { + "allOf": [ + { + "$ref": "#/components/schemas/CommonCaActivityV2" + }, + { + "$ref": "#/components/schemas/CommonFixedIncomeInterestActivityV2" } - } + ], + "description": "Fixed income interest (INT/FI)", + "type": "object" }, - "JNLSActivityV2": { + "FixedIncomeInterestType": { + "description": "Distinguishes coupon payments from accrued interest on INT/FI activities.", + "enum": [ + "coupon", + "accrued" + ], + "example": "accrued", + "title": "FixedIncomeInterestType", + "type": "string" + }, + "FixedIncomeRedemptionActivityV2": { "allOf": [ { - "$ref": "#/components/schemas/CommonJournalActivityV2" + "$ref": "#/components/schemas/CommonNTAActivityV2" }, { "properties": { - "symbol": { - "description": "The symbol of the security involved with the activity", + "ca_id": { + "description": "The unique identifier for this corporate action", + "type": "string" + }, + "cash_payout": { + "description": "The cash payout", + "type": "string" + }, + "cusip": { + "description": "The CUSIP of the security involved with the activity", + "type": "string" + }, + "payment_date": { + "description": "The payment date", + "format": "date", + "minLength": 1, + "type": "string" + }, + "qty": { + "description": "Quantity for the redemption", "type": "string" } }, "required": [ - "symbol" + "ca_id", + "payment_date", + "cusip", + "qty", + "cash_payout" ], "type": "object" } ], - "description": "Journal entry (stock)", + "description": "Redemption", "type": "object" }, - "Journal": { + "ForwardSplitActivityV2": { "allOf": [ + { + "$ref": "#/components/schemas/CommonSplitStockActivityV2" + }, { "properties": { - "created_at": { - "format": "date-time", - "type": "string" - }, - "entry_type": { - "$ref": "#/components/schemas/JournalEntryType" - }, - "from_account": { - "description": "account ID the shares go from", - "format": "uuid", + "due_bill_redemption_date": { + "description": "When due bills related to the split are redeemed", + "format": "date", + "minLength": 1, "type": "string" }, - "id": { - "description": "journal ID", - "format": "uuid", + "ex_date": { + "description": "The ex_date for this corporate action", + "format": "date", + "minLength": 1, "type": "string" }, - "settle_date": { + "record_date": { + "description": "The record_date for this corporate action", "format": "date", + "minLength": 1, "type": "string" }, - "status": { - "$ref": "#/components/schemas/JournalStatus" - }, - "to_account": { - "description": "account ID the shares go to", - "format": "uuid", + "symbol": { + "description": "The symbol of the security being split", "type": "string" - }, - "transmitter_info": { - "$ref": "#/components/schemas/TransmitterInfo" } }, "required": [ - "id", - "entry_type", - "from_account", - "to_account", - "settle_date" + "symbol" ], "type": "object" - }, - { - "oneOf": [ - { - "$ref": "#/components/schemas/JNLS" - }, - { - "$ref": "#/components/schemas/JNLC" - } - ] } ], - "description": "Represents a cash or security transfer between accounts, as specified by the `entry_type` parameter.", - "example": { - "entry_type": "JNLS", - "from_account": "8fjkjn-4483-4199-840f-6c5fe0b7ca24", - "id": "h7h5g33f-ef01-4458-9a4b-9598727a406f", - "price": "128.23", - "qty": "2", - "settle_date": "2020-12-24", - "status": "executed", - "symbol": "AAPL", - "to_account": "3gtt65jd-6f2a-433c-8c33-17b66b8941fa" - }, - "title": "Journal", - "x-examples": { - "example": { - "entry_type": "JNLS", - "from_account": "8fjkjn-4483-4199-840f-6c5fe0b7ca24", - "id": "h7h5g33f-ef01-4458-9a4b-9598727a406f", - "price": "128.23", - "qty": "2", - "settle_date": "2020-12-24", - "status": "executed", - "symbol": "AAPL", - "to_account": "3gtt65jd-6f2a-433c-8c33-17b66b8941fa" + "description": "Forward stock split", + "type": "object" + }, + "FundingDetail": { + "description": "Gets funding details that can be used to settle and collect funds in each available currency.\n", + "properties": { + "account_holder_name": { + "type": "string" }, - "example-pending": { - "description": "", - "entry_type": "JNLC", - "from_account": "3dcb795c-3ccc-402a-abb9-07e26a1b1326", - "id": "6d2cba43-cb57-4534-9603-a6e159167c0a", - "net_amount": "645", - "price": "0", - "qty": null, - "settle_date": "2022-02-17", - "status": "pending", - "symbol": null, - "system_date": "2022-02-17", - "to_account": "2a87c088-ffb6-472b-a4a3-cd9305c8605c" + "account_number": { + "type": "string" }, - "example-queued": { - "description": "", - "entry_type": "JNLC", - "from_account": "3dcb795c-3ccc-402a-abb9-07e26a1b1326", - "id": "6d2cba43-cb57-4534-9603-a6e159167c0a", - "net_amount": "645", - "price": "0", - "qty": null, - "settle_date": null, - "status": "queued", - "symbol": "", - "system_date": null, - "to_account": "2a87c088-ffb6-472b-a4a3-cd9305c8605c" + "account_number_type": { + "type": "string" + }, + "bank_address": { + "type": "string" + }, + "bank_country": { + "type": "string" + }, + "bank_name": { + "type": "string" + }, + "currency": { + "type": "string" + }, + "payment_type": { + "$ref": "#/components/schemas/FundingDetailPaymentType" + }, + "routing_code": { + "type": "string" + }, + "routing_code_type": { + "$ref": "#/components/schemas/FundingDetailRoutingCodeType" } - } + }, + "type": "object" }, - "JournalEntryType": { - "description": "This enum represents the various kinds of Journal alpaca supports.\n\nCurrent values are:\n\n- **JNLC**\n\n Journal Cash between accounts\n\n- **JNLS**\n\n Journal Securities between accounts", + "FundingDetailPaymentType": { + "description": "Status:\n * `swift_wire`: SWIFT wire\n * `local_rails`: Local scheme\n", "enum": [ - "JNLC", - "JNLS" + "swift_wire", + "local_rails" ], - "title": "", "type": "string" }, - "JournalStatus": { - "description": "Represents the status that a Journal instance can be in.\n\n**Current Values**\n\nqueued\tJournal in queue to be processed. Journal is not processed yet.\n\nsent_to_clearing\tJournal sent to be processed by Alpaca's booking system. The journal is not processed yet.\n\npending\t Journal pending to be processed as it requires manual approval from Alpaca operations (for example due to hitting JNLC daily limits).\n\nexecuted\tJournal executed and balances updated for both sides of \nthe journal transaction. This is not a final status, journals can be reversed if there is an error.\n\nactivity_created\tNon-trade activity has been created for journal (JNLC v2-only).\n\nrejected\tJournal rejected. Please try again.\n\ncanceled\tJournal canceled. This is a **FINAL** status.\n\nrefused\tJournal refused. Please try again.\n\ndeleted\tJournal deleted. This is a **FINAL** status.\n\ncorrect\tJournal is corrected. Previously executed journal is cancelled and a new journal is corrected amount is created. This is a **FINAL** status.", + "FundingDetailRoutingCodeType": { + "description": "Status:\n * `BIC`: bic_swift\n * `ACH`: ach_routing_code\n * `ABA`: aba\n * `ROUTING`: routing_code\n * `SORT_CODE`: sort_code\n", "enum": [ - "pending", - "canceled", - "executed", - "activity_created", - "queued", - "rejected", - "deleted", - "refused", - "sent_to_clearing", - "correct" + "BIC", + "ACH_ROUTING", + "ABA", + "ROUTING", + "SORT_CODE" ], "type": "string" }, - "JournalStatusEvent": { - "description": "Represents a change in a Journal's status, sent over the events streaming api.\n", + "FundingWallet": { "properties": { - "at": { - "description": "Timestamp of event", - "format": "date-time", - "minLength": 1, - "type": "string" - }, - "entry_type": { - "$ref": "#/components/schemas/JournalEntryType" - }, - "event_id": { - "description": "Monotonically increasing 64bit integer", - "type": "integer" - }, - "event_ulid": { - "description": "lexically sortable, monotonically increasing character array", - "format": "ulid", - "type": "string" - }, - "journal_id": { - "description": "The UUID of the related Journal", + "account_id": { "format": "uuid", "type": "string" }, - "status_from": { - "$ref": "#/components/schemas/JournalStatus" + "created_at": { + "format": "date-time", + "type": "string" }, - "status_to": { - "$ref": "#/components/schemas/JournalStatus" + "status": { + "$ref": "#/components/schemas/FundingWalletStatus" } }, "required": [ - "at", - "entry_type", - "event_id", - "event_ulid", - "journal_id", - "status_from", - "status_to" + "account_id", + "status", + "created_at" ], - "title": "JournalStatusEvent", - "type": "object", - "x-examples": { - "example-1": { - "at": "2021-05-07T10:28:23.163857Z", - "entry_type": "JNLC", - "event_id": 1406, - "event_ulid": "01F535Y1FVY8WZHE763HCNS8SZ", - "journal_id": "2f144d2a-91e6-46ff-8e37-959a701cc58d", - "status_from": "", - "status_to": "queued" - } - } + "type": "object" }, - "JournalStatusEventV2": { - "description": "Represents a change in a Journal's status, sent over the events streaming api.\n", + "FundingWalletRecipientBank": { "properties": { - "at": { - "description": "Timestamp of event", - "format": "date-time", - "minLength": 1, + "account_number": { + "description": "Bank account number.", "type": "string" }, - "batch_error_message": { - "description": "If journal submitted in batch (that is, idempotency_key_type is batch), this is the error message of the batch journal execution.", + "bic_swift": { + "description": "BIC/SWIFT code", "type": "string" }, - "description": { - "description": "The description of the journal event when submitted", - "example": "Journal of cash between accounts", + "city": { + "description": "City", "type": "string" }, - "entry_type": { - "$ref": "#/components/schemas/JournalEntryType" + "company_name": { + "type": "string" }, - "event_id": { - "description": "lexically sortable, monotonically increasing character array", - "format": "ulid", + "country": { + "description": "Two-letter ISO country code.", "type": "string" }, - "idempotency_key": { - "description": "The idempotency key of the journal event", - "format": "uuid", + "created_at": { + "description": "Date the beneficiary record was created.", + "format": "date-time", "type": "string" }, - "idempotency_key_type": { - "description": "The type of idempotency key", - "enum": [ - "single", - "batch" - ], + "currency": { + "description": "Currency in which money is held in the beneficiary's bank account. Three-digit currency code.", "type": "string" }, - "journal_id": { - "description": "The UUID of the related Journal", + "first_name": { + "type": "string" + }, + "iban": { + "description": "IBAN code", + "type": "string" + }, + "id": { "format": "uuid", "type": "string" }, - "status_from": { - "$ref": "#/components/schemas/JournalStatus" + "last_name": { + "type": "string" }, - "status_to": { - "$ref": "#/components/schemas/JournalStatus" - } - }, - "required": [ - "at", - "entry_type", - "event_id", - "journal_id", - "status_from", - "status_to" - ], - "title": "JournalStatusEvent", - "type": "object", - "x-examples": { - "example-1": { - "at": "2021-05-07T10:28:23.163857Z", - "description": "Journal of cash between accounts", - "entry_type": "JNLC", - "event_id": "01F535Y1FVY8WZHE763HCNS8SZ", - "journal_id": "2f144d2a-91e6-46ff-8e37-959a701cc58d", - "status_from": "", - "status_to": "queued" + "payment_types": { + "items": { + "$ref": "#/components/schemas/FundingDetailPaymentType" + }, + "type": "array" }, - "example-idempotency-key": { - "at": "2021-05-07T10:28:23.163857Z", - "description": "Journal of cash between accounts", - "entry_type": "JNLC", - "event_id": "01F535Y1FVY8WZHE763HCNS8SZ", - "idempotency_key": "72bdf18f-7410-43ec-b4e8-b6e41c5c0160", - "idempotency_key_type": "single", - "journal_id": "2f144d2a-91e6-46ff-8e37-959a701cc58d", - "status_from": "queued", - "status_to": "executed" - } - } - }, - "KYCResultType": { - "description": "IDENTITY_VERIFICATION\tIdentity needs to be verified\n\nTAX_IDENTIFICATION\tTax ID number needs to be verified\n\nADDRESS_VERIFICATION\tAddress needs to be verified\n\nDATE_OF_BIRTH\tDate of birth needs to be verified\n\nINVALID_IDENTITY_PASSPORT\tIdentity needs to be verified via a \ngovernment issued ID. This is commonly used in conjuction with OTHER to describe the exact document needed.\n\nSELFIE_VERIFICATION\tIdentity needs to be verified via a live selfie of the account owner\n\nPEP\tFurther information needs to be submitted if account owner is politically exposed person\n\nFAMILY_MEMBER_PEP\tFurther information needs to be submitted if family member is a politically exposed person\n\nCONTROL_PERSON\tFurther information needs to be submitted if account owner is a control person\n\nAFFILIATED\tFurther information needs to be submitted if account owner is affiliated to finra or an exchange\n\nVISA_TYPE_OTHER\tFurther information needs to be submitted about account owner's visa\n\nW8BEN_CORRECTION\tIdenfitying information submitted by the user was incorrect so a new, corrected, W8BEN needs to be submitted\nCOUNTRY_NOT_SUPPORTED\tThe account owner's country of tax residence is not supported by our KYC providers. In this case, we'll manully perform KYC on the user\n\nWATCHLIST_HIT\tResults from the watchlist screening need further investigation before account opening. No action is needed from the user\n\nOTHER\tA custom message will be sent to describe exactly what is needed from the account owner. The message will be displayed in the additional_information attribute.\n\nOTHER_PARTNER\tA custom message will be sent to relay information relevant only to the partner. The message will be displayed in the additional_information attribute.", - "title": "KYCResultType", - "type": "string" - }, - "KYCResults": { - "description": "Hold information about the result of KYC. Please see the documentation [here](https://alpaca.markets/docs/api-references/broker-api/events/#kyc-results) for more indepth details", - "properties": { - "accept": { - "$ref": "#/components/schemas/KYCResultType" + "postal_code": { + "description": "Postal code", + "type": "string" }, - "additional_information": { - "description": "Used to display a custom message.", + "routing_code": { + "description": "Value for \"routing_code_type\".", "type": "string" }, - "indeterminate": { - "$ref": "#/components/schemas/KYCResultType" + "routing_code_type": { + "description": "Local payment routing system.", + "type": "string" }, - "reject": { - "$ref": "#/components/schemas/KYCResultType" + "state_or_province": { + "description": "State or province.", + "type": "string" }, - "summary": { - "description": "Either `pass` or `fail`. Used to indicate if KYC has completed and passed or not. This field is used for internal purposes only.", + "street_address": { + "type": "string" + }, + "updated_at": { + "description": "Date the beneficiary record was last updated.", + "format": "date-time", "type": "string" } }, "type": "object" }, - "ListAPRTiersResponse": { - "description": "Response to a successful request for a list of APR tiers.\n", - "properties": { - "apr_tiers": { - "description": "All configured APR tiers available for assignment to accounts", - "items": { - "$ref": "#/components/schemas/APRTier" - }, - "type": "array" - } - }, - "type": "object" + "FundingWalletStatus": { + "description": "Status:\n * `active`: The funding wallet is ready\n * `pending`: The funding wallet is being processed\n * `disabled`: The funding wallet is disabled\n", + "enum": [ + "active", + "pending", + "disabled" + ], + "type": "string" }, - "ListFPSLLoansResponse": { - "description": "Response to a successful request for a list of FPSL loans.\n", + "FundingWalletTransfer": { "properties": { - "loans": { - "description": "All FPSL loans matching the filter criteria", + "account_id": { + "format": "uuid", + "type": "string" + }, + "created_at": { + "format": "date-time", + "type": "string" + }, + "direction": { + "$ref": "#/components/schemas/FundingWalletTransferDirection" + }, + "fees": { "items": { - "$ref": "#/components/schemas/FPSLLoan" + "$ref": "#/components/schemas/TransferFee" }, "type": "array" }, - "next_page_token": { - "description": "The token to use to retrieve the next page of results. If `null`, there are no more pages.\n", - "nullable": true, + "id": { + "format": "uuid", "type": "string" - } - }, - "required": [ - "loans", - "next_page_token" - ], - "type": "object" - }, - "ListFundingDetails": { - "properties": { - "funding_details": { - "items": { - "$ref": "#/components/schemas/FundingDetail" - }, - "type": "array" + }, + "original_amount": { + "deprecated": true, + "description": "Deprecated. Prefer `total_amount` for the actual amount debited from the account. The amount you should expect to receive, calculated as requested amount - fees.", + "format": "decimal", + "type": "string", + "x-deprecation": { + "reason": "Use total_amount for the actual amount debited from the account.", + "since": "2026-09-22", + "sunset": "2026-12-21" + } + }, + "original_currency": { + "description": "The currency of the withdrawn amount, 3-letter ISO code", + "type": "string" + }, + "payment_type": { + "$ref": "#/components/schemas/FundingDetailPaymentType" + }, + "requested_amount": { + "description": "The amount requested when the transfer was created.", + "format": "decimal", + "type": "string" + }, + "status": { + "$ref": "#/components/schemas/FundingWalletTransferStatus" + }, + "total_amount": { + "description": "The total amount moved for the transfer. For outgoing transfers with fees added on top, this is the requested amount plus fees (the total debited from the account). For fee-inclusive outgoing withdrawals, fees are taken out of the requested amount, so this equals requested_amount. For incoming transfers, this is the requested amount credited to the account.", + "format": "decimal", + "type": "string" + }, + "updated_at": { + "format": "date-time", + "type": "string" + }, + "usd": { + "$ref": "#/components/schemas/Usd" } }, "type": "object" }, - "ListSortBy": { + "FundingWalletTransferDirection": { + "description": "Status:\n * `incoming`: incoming amount\n * `outgoing`: outgoing amount\n", "enum": [ - "created_at", - "amount", - "deadline" + "incoming", + "outgoing" ], "type": "string" }, - "ListTransfersResponse": { - "properties": { - "transfers": { - "items": { - "$ref": "#/components/schemas/FundingWalletTransfer" - }, - "type": "array" - } - }, - "type": "object" - }, - "MAActivityV2": { - "allOf": [ - { - "$ref": "#/components/schemas/CommonCaActivityV2" - }, - { - "$ref": "#/components/schemas/CommonMAActivityV2" - }, - { - "properties": { - "acquiree_qty": { - "description": "Quantity of the acquiree", - "type": "string" - }, - "acquirer_qty": { - "description": "Quantity of the acquirer", - "type": "string" - }, - "cash_payout": { - "description": "The cash payout", - "type": "string" - }, - "cash_rate": { - "description": "The cash rate", - "type": "string" - } - }, - "required": [ - "acquiree_qty" - ], - "type": "object" - } + "FundingWalletTransferStatus": { + "description": "Status:\n * `PENDING`: Created and waiting to be processed\n * `CANCELED`: Canceled\n * `FAILED`: Failed mostly due to technical reasons\n * `COMPLETE`: Transfer has settled\n", + "enum": [ + "PENDING", + "CANCELED", + "EXECUTED", + "FAILED", + "COMPLETE" ], - "description": "Merger and acquisition", - "type": "object" + "type": "string" }, - "MLegOrderLeg": { - "description": "Represents an individual leg of a multileg options order.", + "IFFee": { "properties": { - "position_intent": { - "$ref": "#/components/schemas/PositionIntent" - }, - "ratio_qty": { - "description": "proportional quantity of this leg in relation to the overall multileg order qty", - "example": "1", + "amount": { + "format": "decimal", "type": "string" }, - "side": { - "$ref": "#/components/schemas/OrderSide" - }, - "symbol": { - "description": "symbol or asset ID to identify the asset to trade", + "id": { + "format": "uuid", "type": "string" + }, + "type": { + "$ref": "#/components/schemas/IFFeeType" } }, "required": [ - "symbol", - "ratio_qty" + "id", + "amount", + "type" ], - "title": "MLegOrderLeg", "type": "object" }, - "NCActivityV2": { - "allOf": [ + "IFFeeType": { + "description": "Status:\n * `partner`: The fee on a transfer that was allocated to the partner\n * `alpaca`: The fee on a transfer that was allocated to Alpaca\n", + "enum": [ + "partner", + "alpaca" + ], + "type": "string" + }, + "IPOEvent": { + "description": "Represents an IPO offering lifecycle event delivered over the IPO events streaming API (`/v2/events/ipos`).\n\nIPO events come in six `verb` types:\n- **`Offering`** - initial publication of an offering. The `payload` carries `name`, `available_to_order`, `ticker_symbol`, `min_price`, `max_price` (and optional `source`).\n- **`OfferingUpdate`** - update to a previously published offering. The `payload` shape is identical to `Offering`.\n- **`Prospectus`** - prospectus document is now available. The `payload` carries `prospectus_url` and `subject`.\n- **`SixtyMinMail`** - 60-minute pricing window has opened, no new orders accepted. The `payload` carries `sixty_minute_expiration_time` and `subject`.\n- **`Allocation`** - final allocation result for a specific account. The `payload` carries `cusip_id`, `final_price`, `allocated_shares`, `allocated_amount`, `subject`. **This is the only verb that is account-scoped**: `account_id` and `correspondent` are populated.\n- **`OfferingCancellation`** - offering was cancelled. **No `payload` is present.**\n\nSee the example payloads below for a concrete example per verb.\n", + "examples": [ { - "$ref": "#/components/schemas/CommonCaActivityV2" + "at": "2026-03-20T12:24:58.807230Z", + "event_id": "01J9RPMV5TKB8WX3M4F1KZ7QH2", + "offering_reference": "FI111225", + "payload": { + "available_to_order": "Available", + "max_price": "42", + "min_price": "42", + "name": "Example Corp", + "source": "example source", + "ticker_symbol": "EXMP" + }, + "received_at": "2026-03-20T12:24:58.500000Z", + "verb": "Offering" }, { - "$ref": "#/components/schemas/CommonNCActivityV2" + "at": "2026-03-20T15:42:11.118274Z", + "event_id": "01J9RQ5HNZQK7M3RDVJ8XBPCT1", + "offering_reference": "FI111225", + "payload": { + "available_to_order": "Available", + "max_price": "44", + "min_price": "40", + "name": "Example Corp", + "source": "example source", + "ticker_symbol": "EXMP" + }, + "received_at": "2026-03-20T15:42:10.892140Z", + "verb": "OfferingUpdate" }, { - "properties": { - "position_qty": { - "description": "The position quantity", - "type": "string" - } + "at": "2026-03-19T23:26:46.853262Z", + "event_id": "01J9RSGD8JR2A4K7MZNTPVQ5XW", + "offering_reference": "FI111225", + "payload": { + "prospectus_url": "https://example.com/prospectus/example-corp.pdf", + "subject": "IPO Prospectus: Example Corp" }, - "required": [ - "position_qty" - ], - "type": "object" + "received_at": "2026-03-19T23:26:46.612880Z", + "verb": "Prospectus" + }, + { + "at": "2026-03-19T22:50:11.729329Z", + "event_id": "01J9RT9P3WYH8KCJM2LFQX5ZRD", + "offering_reference": "FI111225", + "payload": { + "sixty_minute_expiration_time": "2026-03-19T23:50:11.729329Z", + "subject": "IPO 60 Minute Mail: Example Corp" + }, + "received_at": "2026-03-19T22:50:11.512640Z", + "verb": "SixtyMinMail" + }, + { + "account_id": "6221407b-32b2-3e6b-a7fe-cd79e1b68ba3", + "at": "2026-03-20T00:15:42.318901Z", + "correspondent": "COR1", + "event_id": "01J9RTPZGHKVB8M3X4F5J7LCQ9", + "offering_reference": "FI111225", + "payload": { + "allocated_amount": "1764", + "allocated_shares": "42", + "cusip_id": "123456789", + "final_price": "42", + "subject": "IPO Allocation: Example Corp" + }, + "received_at": "2026-03-20T00:15:42.103572Z", + "verb": "Allocation" + }, + { + "at": "2026-03-20T12:24:52.325158Z", + "event_id": "01J9RVB6Y4ZK8M3N7QD2WX1RFP", + "offering_reference": "FI111225", + "received_at": "2026-03-20T12:24:52.110430Z", + "verb": "OfferingCancellation" } ], - "description": "Name change", - "type": "object" - }, - "NextPageToken": { - "description": "Use this token in your next API call to paginate through the dataset and retrieve the next page of results. A null token indicates there are no more data to fetch.\n", - "example": "MTAwMA==", - "nullable": true, - "type": "string" - }, - "NonTradeActivity": { "properties": { - "activity_sub_type": { - "$ref": "#/components/schemas/ActivitySubType" + "account_id": { + "description": "The account this event applies to. Only populated for `Allocation` events; omitted for system-wide verbs (`Offering`, `OfferingUpdate`, `Prospectus`, `SixtyMinMail`, `OfferingCancellation`).\n", + "format": "uuid", + "type": "string" }, - "created_at": { - "description": "Valid only for non-trading activity types. Null for trading activites.", - "example": "2021-05-10T14:01:04.650275Z", + "at": { + "description": "Timestamp the event was emitted by the streaming service.", "format": "date-time", "type": "string" }, - "currency": { - "description": "Currency denomination of the activity. USD by default.", + "correspondent": { + "description": "The correspondent that owns the account. Only populated for `Allocation` events.\n", + "example": "COR1", "type": "string" }, - "cusip": { - "description": "Valid only for non-trading activity types. Null for trading activites.", - "example": "037833100", + "event_id": { + "description": "Lexically sortable, monotonically increasing identifier for this event. Use this value with `since_id`/`until_id` to resume the stream from a known point.", + "format": "ulid", "type": "string" }, - "date": { - "description": "Valid only for non-trading activity types. Null for trading activites.", - "example": "2021-05-21", - "format": "date", - "type": "string" - }, - "description": { - "description": "Valid only for non-trading activity types. Null for trading activites.", - "example": "Example description", - "type": "string" - }, - "group_id": { - "description": "ID used to link activities who share a sibling relationship", - "example": "13d96cf3-1cbe-4632-b2f2-86a9df5a3b9d", - "format": "uuid", - "type": "string" - }, - "net_amount": { - "description": "Valid only for non-trading activity types. Null for trading activites.", - "example": "1234", - "format": "decimal", + "offering_reference": { + "description": "The IPO offering this event refers to. Use this value with `GET /v1/ipos/{offering_reference}` to fetch the full offering metadata.\n", + "example": "FI111225", "type": "string" }, - "per_share_amount": { - "description": "Valid only for non-trading activity types. Null for trading activites.", - "example": "0.38921", - "format": "decimal", - "type": "string" + "payload": { + "description": "Verb-specific payload. The shape varies by `verb` - see schema description and `components/examples` for the structure of each. **Omitted entirely for `OfferingCancellation`.**\n", + "type": "object" }, - "qty": { - "description": "Valid only for non-trading activity types. Null for trading activites.", - "example": "0.38921", - "format": "decimal", + "received_at": { + "description": "Timestamp the upstream event was first received by Alpaca's IPO ingestion pipeline (before fanout).", + "format": "date-time", "type": "string" }, - "status": { - "description": "Valid only for non-trading activity types. Null for trading activites.", + "verb": { + "description": "The IPO event type. Determines the shape of `payload`.", "enum": [ - "executed", - "correct", - "canceled" + "Allocation", + "Offering", + "OfferingUpdate", + "Prospectus", + "SixtyMinMail", + "OfferingCancellation" ], - "example": "executed", - "type": "string" - }, - "symbol": { - "description": "Valid only for non-trading activity types. Null for trading activites.", - "example": "AAPL", "type": "string" } }, - "title": "NonTradeActivity", + "required": [ + "event_id", + "at", + "verb", + "offering_reference", + "received_at" + ], + "title": "IPOEvent", "type": "object" }, - "NonTradeActivityEvent": { - "description": "Represents a non-trade activity SSE event", + "IPOOffering": { + "description": "An IPO (Initial Public Offering) offering exposed via the IPO discovery REST API. Returned by `GET /v1/ipos` and `GET /v1/ipos/{offering_reference}`.\n\nMost IPO lifecycle changes (new offerings, prospectus availability, 60-minute mail, allocations, cancellations) are pushed in real time over the [IPO Events Stream](#operation/subscribeToIPOEventsSSE).\n", "properties": { - "account_id": { - "description": "Account UUID", - "format": "uuid", - "type": "string" + "anticipated_shares": { + "description": "Anticipated total share count for the offering, when known.", + "format": "int64", + "type": "integer" }, - "at": { - "description": "Timestamp of when the event was emitted", - "format": "date-time", + "availability": { + "description": "Whether the offering is currently accepting orders.\n- `available` - accepting new orders.\n- `not_available` - not yet open for orders.\n- `closed` - no longer accepting orders.\n\nNote: the IPO Events Stream uses a different casing for the analogous `payload.available_to_order` field on `Offering`/`OfferingUpdate` events (`Available`, `NotAvailable`, `Closed`).\n", + "enum": [ + "available", + "not_available", + "closed" + ], + "example": "available", "type": "string" }, - "cusip": { - "description": "CUSIP the event is associated with, not present when no CUSIP is applicable", - "example": "037833100", + "cusip_id": { + "description": "CUSIP identifier of the offering.", + "example": "123456789", "type": "string" }, "description": { - "description": "Additional information about the event, empty string if not applicable", - "example": "Example description", - "type": "string" - }, - "entry_type": { - "description": "Type of entry for e.g JNLC, FEE, INT, DIVNRA etc", - "example": "JNLC", + "description": "A longer human-readable description of the offering, when available.", "type": "string" }, - "event_id": { - "description": "Monotonically increasing 64bit integer", - "type": "integer" - }, - "event_ulid": { - "description": "lexically sortable, monotonically increasing character array", - "format": "ulid", + "ipo_reference": { + "description": "The unique offering identifier used across the IPO Events Stream and `/v1/ipos/{offering_reference}`. Note that this value is also used as the path parameter, where it is named `offering_reference`.\n", + "example": "FI111225", "type": "string" }, - "id": { - "description": "Record UUID", - "format": "uuid", + "logo_small": { + "description": "URL to a small logo asset for the issuer.", "type": "string" }, - "net_amount": { - "description": "Net amount if applicable, 0 otherwise", - "example": 1, - "format": "decimal", - "type": "number" - }, - "per_share_amount": { - "description": "Per share amount if applicable", - "example": 0.3, + "max_price": { + "description": "Upper bound of the indicated price range.", + "example": "42", "format": "decimal", - "type": "number" + "type": "string" }, - "price": { - "description": "Price if applicable.", - "example": "0.38921", - "format": "decimal", + "max_ticket_size": { + "description": "Maximum allowed order amount.", "type": "string" }, - "qty": { - "description": "Quantity of the stock affected. 0 for cash events", + "min_price": { + "description": "Lower bound of the indicated price range.", + "example": "42", "format": "decimal", - "type": "number" - }, - "settle_date": { - "description": "Date of settlement if applicable", - "format": "date", "type": "string" }, - "status": { - "description": "Status of the event", - "enum": [ - "executed", - "correct", - "canceled" - ], - "example": "executed", + "min_ticket_size": { + "description": "Minimum allowed order amount.", "type": "string" }, - "symbol": { - "description": "Symbol the event is associated with, empty string when no symbol is applicable", - "example": "AAPL", + "name": { + "description": "The official name of the offering.", + "example": "Example Corp", "type": "string" }, - "system_date": { - "description": "Date of the event recorded in the system", - "format": "date", + "no_new_orders": { + "description": "When `true`, the offering is in its 60-minute pricing window and is not accepting new orders. This mirrors the `SixtyMinMail` event on the IPO Events Stream.\n", + "example": false, + "type": "boolean" + }, + "offering_type": { + "description": "The type of offering. Currently always `IPO`.", + "example": "IPO", "type": "string" - } - }, - "required": [ - "account_id", - "at", - "id", - "event_ulid", - "system_date", - "settle_date", - "net_amount", - "entry_type", - "description" - ], - "title": "NonTradeActivityEvent", - "type": "object", - "x-examples": { - "example-1": { - "account_id": "8e00606a-c9ac-409a-ba45-f55e8f77984a", - "at": "2024-11-26T15:25:17.803914Z", - "description": null, - "entry_type": "JNLC", - "event_ulid": "01JDMH7BKCCB4XY5F11HN63NZX", - "id": "afb78fa2-7e4c-4e0a-bca2-2e0d26a87f88", - "net_amount": 10, - "qty": 0, - "settle_date": "2024-11-26", - "status": "executed", - "symbol": null, - "system_date": "2024-11-26" }, - "example-2": { - "account_id": "11629972-5fd6-4e14-ad9e-0f0cabd2777f", - "at": "2024-11-12T01:15:53.132425Z", - "description": "TAF fee for proceed of 0.010052895 shares (1 trades) on 2024-11-11 by 613651765", - "entry_sub_type": "TAF", - "entry_type": "FEE", - "event_ulid": "01JCEZ1ZDCE44GFX6WZ75N4199", - "id": "d96cea8a-6a77-46cd-bb7b-df6d74442653", - "net_amount": -0.01, - "qty": 0, - "settle_date": "2024-11-12", - "status": "executed", - "symbol": null, - "system_date": "2024-11-11" - } - } - }, - "OAuthTokenRequest": { - "description": "This model is used for both the Issue and Authorize OAuth token routes", - "example": { - "account_id": "0d18ae51-3c94-4511-b209-101e1666416b", - "client_id": "7a3c52a910e1dc2abbb14da2b6b8e711", - "client_secret": "bbb14da2b6b8e7117a3c52a910e1dc2a", - "redirect_uri": "http://localhost", - "scope": "general" - }, - "properties": { - "account_id": { - "description": "end-user account ID", - "format": "uuid", + "prospectus_url": { + "description": "URL to the prospectus document. Mirrors the `prospectus_url` payload of the corresponding `Prospectus` event on the IPO Events Stream.\n", "type": "string" }, - "client_id": { - "description": "OAuth client ID", + "settlement_date": { + "description": "Anticipated settlement date.", + "format": "date", "type": "string" }, - "client_secret": { - "description": "OAuth client secret", + "ticker_symbol": { + "description": "The ticker symbol that will be used once the security begins trading on the secondary market.", + "example": "EXMP", "type": "string" }, - "redirect_uri": { - "description": "redirect URI for the OAuth flow", + "trade_date": { + "description": "Anticipated first trading date on the secondary market.", + "format": "date", "type": "string" }, - "scope": { - "description": "scopes requested by the OAuth flow", + "underwriters": { + "description": "List of underwriter names participating in the offering.", + "items": { + "type": "string" + }, + "type": "array" + }, + "unit_step_size": { + "description": "The minimum increment in which order quantities can be specified.", "type": "string" } }, "required": [ - "client_id", - "client_secret", - "redirect_uri", - "scope", - "account_id" - ], - "title": "OAuthTokenRequest", - "type": "object" - }, - "OPASNActivityV2": { - "allOf": [ - { - "$ref": "#/components/schemas/CommonOptionsActivityV2" - } - ], - "description": "Option assignment", - "type": "object" - }, - "OPEXCActivityV2": { - "allOf": [ - { - "$ref": "#/components/schemas/CommonOptionsActivityV2" - } + "name", + "ipo_reference", + "offering_type", + "availability", + "max_price", + "min_price", + "no_new_orders" ], - "description": "Option exercise", + "title": "IPOOffering", "type": "object" }, - "OPEXPActivityV2": { - "allOf": [ - { - "$ref": "#/components/schemas/CommonOptionsActivityV2" + "IPOOfferingListResponse": { + "description": "Wrapper response returned by `GET /v1/ipos`.", + "properties": { + "data": { + "description": "The page of IPO offerings matching the query.", + "items": { + "$ref": "#/components/schemas/IPOOffering" + }, + "type": "array" + }, + "next_page_token": { + "description": "Opaque cursor for the next page of results. When `null`, there are no further pages. Pass this value back as the `page_token` query parameter to fetch the next page.\n", + "type": [ + "string", + "null" + ] } + }, + "required": [ + "data", + "next_page_token" ], - "description": "Option expiry", + "title": "IPOOfferingListResponse", "type": "object" }, - "OPTRDActivityV2": { - "allOf": [ - { - "$ref": "#/components/schemas/CommonOptionsActivityV2" + "IPOOfferingResponse": { + "description": "Wrapper response returned by `GET /v1/ipos/{offering_reference}`.", + "properties": { + "data": { + "$ref": "#/components/schemas/IPOOffering" } + }, + "required": [ + "data" ], - "description": "Trading activity that is paired with the assignment/exercise", + "title": "IPOOfferingResponse", "type": "object" }, - "OathClientResponse": { + "Identity": { + "description": "Identity is the model to provide account owner's identity information.\n", "example": { - "client_id": "7a3c52a910e1dc2abbb14da2b6b8e711", - "description": "Sample description", - "live_trading_approved": true, - "name": "TradingApp", - "privacy_policy": "", - "redirect_uri": [ - "http://localhost" + "country_of_birth": "AUS", + "country_of_citizenship": "AUS", + "country_of_tax_residence": "USA", + "date_of_birth": "1990-01-01", + "family_name": "Doe", + "funding_source": [ + "employment_income" ], - "status": "ACTIVE", - "terms_of_use": "", - "url": "http://test.com" + "given_name": "John", + "tax_id": "666-55-4321", + "tax_id_type": "USA_SSN" }, "properties": { - "client_id": { - "description": "OAuth client id", + "annual_income_max": { + "description": "The upper bound of the user's annual income.", + "example": "100000", + "format": "decimal", "type": "string" }, - "description": { + "annual_income_min": { + "description": "The lower bound of the user's annual income.", + "example": "50000", + "format": "decimal", "type": "string" }, - "live_trading_approved": { - "example": true, - "type": "boolean" + "country_of_birth": { + "description": "[ISO 3166-1 alpha-3](https://www.iso.org/iso-3166-country-codes.html).\n", + "example": "USA", + "type": "string" }, - "name": { - "description": "Broker name (your name)", + "country_of_citizenship": { + "description": "[ISO 3166-1 alpha-3](https://www.iso.org/iso-3166-country-codes.html).\n", + "example": "USA", "type": "string" }, - "privacy_policy": { - "description": "URL of Privacy Policy", + "country_of_tax_residence": { + "description": "[ISO 3166-1 alpha-3](https://www.iso.org/iso-3166-country-codes.html).\n", + "example": "USA", "type": "string" }, - "redirect_uri": { + "date_of_birth": { + "description": "The date of birth in \"YYYY-MM-DD\" format.", + "example": "1990-01-01", + "format": "date", + "type": "string" + }, + "date_of_departure_from_usa": { + "description": "Required if `visa_type` = B1 or B2", + "format": "date", + "type": "string" + }, + "family_name": { + "description": "The last name (surname) of the user.", + "example": "Doe", + "type": "string" + }, + "funding_source": { + "description": "Can be one or more of the following: `employment_income`, `investments`, `inheritance`, `business_income`, `savings`, `family`.", "items": { + "enum": [ + "employment_income", + "investments", + "inheritance", + "business_income", + "savings", + "family" + ], "type": "string" }, "type": "array" }, - "status": { - "description": "ACTIVE or DISABLED", + "given_name": { + "description": "The first/given name of the user.", + "example": "John", + "type": "string" + }, + "investment_experience_with_options": { + "description": "The user's level of expertise and familiarity with investing in Options.\n", "enum": [ - "ACTIVE", - "DISABLED" + "none", + "1_to_5_years", + "over_5_years" ], - "example": "ACTIVE", "type": "string" }, - "terms_of_use": { - "description": "URL of Terms of Use", + "investment_experience_with_stocks": { + "description": "The user's level of expertise and familiarity with investing in US Equities.\n", + "enum": [ + "none", + "1_to_5_years", + "over_5_years" + ], "type": "string" }, - "url": { + "investment_objective": { + "deprecated": true, + "description": "The user's investment objective. This field is deprecated. Please use the top level `investment_objective` field.\n", + "enum": [ + "generate_income", + "preserve_wealth", + "market_speculation", + "growth", + "balance_preserve_wealth_with_growth" + ], "type": "string" - } - }, - "title": "OathClientResponse", - "type": "object", - "x-examples": { - "example-1": { - "client_id": "7a3c52a910e1dc2abbb14da2b6b8e711", - "description": "Sample description", - "live_trading_approved": true, - "name": "TradingApp", - "privacy_policy": "", - "redirect_uri": [ - "http://localhost" + }, + "investment_time_horizon": { + "deprecated": true, + "description": "The expected period of time the user plan to invest to achieve his/her financial goal(s). This field is deprecated. Please use the top level `investment_time_horizon` field.\n", + "enum": [ + "less_than_1_year", + "1_to_2_years", + "3_to_5_years", + "6_to_10_years", + "more_than_10_years" ], - "status": "ACTIVE", - "terms_of_use": "", - "url": "http://test.com" - } - } - }, - "OnfidoSDKOutcome": { - "description": "\"NOT_STARTED\"\tThe user has not started the SDK flow yet. outcome is set to this default value upon token generation\n\"USER_EXITED\"\tThe user exited the SDK flow\n\"SDK_ERROR\"\tAn error occurred in the SDK flow\n\"USER_COMPLETED\"\tThe user completed the SDK flow", - "title": "OnfidoSDKOutcome", - "type": "string" - }, - "OpcaCDIVActivityV2": { - "allOf": [ - { - "$ref": "#/components/schemas/CommonOPCAActivityV2" + "type": "string" }, - { - "$ref": "#/components/schemas/CommonCDIVActivityV2" - } - ], - "description": "Options corporate action of Cash dividend", - "type": "object" - }, - "OpcaFSPLITActivityV2": { - "allOf": [ - { - "$ref": "#/components/schemas/CommonOPCAActivityV2" + "liquid_net_worth_max": { + "description": "The upper bound of the user's liquid net worth.", + "example": "500000", + "format": "decimal", + "type": "string" }, - { - "$ref": "#/components/schemas/CommonSplitActivityV2" + "liquid_net_worth_min": { + "description": "The lower bound of the user's liquid net worth.", + "example": "100000", + "format": "decimal", + "type": "string" }, - { - "properties": { - "due_bill_redemption_date": { - "description": "When due bills related to the split are redeemed", - "format": "date", - "minLength": 1, - "type": "string" - }, - "ex_date": { - "description": "The ex_date for this corporate action", - "format": "date", - "minLength": 1, - "type": "string" - }, - "record_date": { - "description": "The record_date for this corporate action", - "format": "date", - "minLength": 1, - "type": "string" - }, - "symbol": { - "description": "The symbol of the security involved with the activity", - "type": "string" - } - }, - "required": [ - "symbol" + "liquidity_needs": { + "deprecated": true, + "description": "The user's ability to quickly and easily convert all or part of their investments in this account to cash without significant loss in value. This field is deprecated. Please use the top level `liquidity_needs` field.\n", + "enum": [ + "very_important", + "important", + "somewhat_important", + "does_not_matter" ], - "type": "object" - } - ], - "description": "Options corporate action of forward-splits", - "type": "object" - }, - "OpcaMAActivityV2": { - "allOf": [ - { - "$ref": "#/components/schemas/CommonOPCAActivityV2" + "type": "string" }, - { - "$ref": "#/components/schemas/CommonMAActivityV2" - } - ], - "description": "Options corporate action of Mergers & Acquisitions", - "type": "object" - }, - "OpcaNCActivityV2": { - "allOf": [ - { - "$ref": "#/components/schemas/CommonOPCAActivityV2" + "marital_status": { + "description": "The marital status of the user.\n", + "enum": [ + "SINGLE", + "MARRIED", + "DIVORCED", + "WIDOWED" + ], + "type": "string" }, - { - "$ref": "#/components/schemas/CommonNCActivityV2" - } - ], - "description": "Options corporate action of name changes", - "type": "object" - }, - "OpcaRSPLITActivityV2": { - "allOf": [ - { - "$ref": "#/components/schemas/CommonOPCAActivityV2" + "middle_name": { + "description": "The middle name of the user.", + "type": "string" }, - { - "$ref": "#/components/schemas/CommonSplitActivityV2" + "number_of_dependents": { + "description": "The number of dependents the user has.\n", + "type": "integer" }, - { - "properties": { - "ex_date": { - "description": "The ex_date for this corporate action", - "format": "date", - "minLength": 1, - "type": "string" - }, - "new_symbol": { - "description": "Symbol of the new security after the split", - "type": "string" - }, - "record_date": { - "description": "The record_date for this corporate action", - "format": "date", - "minLength": 1, - "type": "string" + "permanent_resident": { + "description": "Only used to collect permanent residence status in the USA.", + "type": "boolean" + }, + "risk_tolerance": { + "deprecated": true, + "description": "The user's investment risk tolerance. This field is deprecated. Please use the top level `risk_tolerance` field.\n", + "enum": [ + "conservative", + "moderate", + "significant_risk" + ], + "type": "string" + }, + "tax_id": { + "description": "If this is provided, `tax_id_type` is required.", + "example": "666-55-4321", + "type": "string" + }, + "tax_id_type": { + "description": "Required on write when `tax_id` is set. May be `null` on read when unset or for sparse identity on OmniSub / omnibus-non-disclosed accounts.", + "oneOf": [ + { + "$ref": "#/components/schemas/TaxIdType" }, - "symbol": { - "description": "The symbol of the security involved with the activity", - "type": "string" + { + "type": "null" } - }, - "required": [ - "symbol" - ], - "type": "object" - } - ], - "description": "Options corporate action of reverse-splits", - "type": "object" - }, - "OpcaSDIVActivityV2": { - "allOf": [ - { - "$ref": "#/components/schemas/CommonOPCAActivityV2" + ] }, - { - "$ref": "#/components/schemas/CommonSDIVActivityV2" - } - ], - "description": "Options corporate action of stock dividend", - "type": "object" - }, - "OpcaSPINActivityV2": { - "allOf": [ - { - "$ref": "#/components/schemas/CommonOPCAActivityV2" + "total_net_worth_max": { + "description": "The upper bound of the user's total net worth.", + "example": "1000000", + "format": "decimal", + "type": "string" }, - { - "$ref": "#/components/schemas/CommonSpinoffActivityV2" - } - ], - "description": "Options corporate action of spin-offs", - "type": "object" - }, - "OpcaUSPLITActivityV2": { - "allOf": [ - { - "$ref": "#/components/schemas/CommonOPCAActivityV2" + "total_net_worth_min": { + "description": "The lower bound of the user's total net worth.", + "example": "100000", + "format": "decimal", + "type": "string" }, - { - "$ref": "#/components/schemas/CommonSplitActivityV2" + "visa_expiration_date": { + "description": "Required if `visa_type` is set.", + "format": "date", + "type": "string" }, - { - "properties": { - "alternate_cusip": { - "description": "CUSIP for the alternate security after the split", - "type": "string" - }, - "alternate_rate": { - "description": "Ratio of alternate shares received", - "type": "string" - }, - "alternate_symbol": { - "description": "Symbol for the alternate security after the split", - "type": "string" - }, - "effective_date": { - "description": "When the unit split becomes effective", - "format": "date", - "minLength": 1, - "type": "string" - }, - "new_symbol": { - "description": "Symbol of the new security after the unit split", - "type": "string" - }, - "old_symbol": { - "description": "The old symbol of the security involved with the activity", - "type": "string" - } - }, - "required": [ - "old_symbol", - "new_symbol", - "alternate_cusip", - "alternate_symbol", - "alternate_rate", - "effective_date" - ], - "type": "object" + "visa_type": { + "description": "Only used to collect visa types for users residing in the USA.", + "type": "string" } + }, + "required": [ + "given_name", + "family_name", + "date_of_birth", + "country_of_tax_residence", + "funding_source" ], - "description": "Options corporate action of unit-splits", "type": "object" }, - "OptionContract": { + "InstantFunding": { "properties": { - "close_price": { - "description": "The close price of the option contract.", - "example": "148.38", + "account_no": { "type": "string" }, - "close_price_date": { - "description": "The date of the close price data.", - "example": "2023-12-11", + "amount": { + "format": "decimal", + "type": "string" + }, + "created_at": { + "format": "date-time", + "type": "string" + }, + "deadline": { "format": "date", "type": "string" }, - "deliverables": { - "description": "Represents the deliverables tied to the option contract. While standard contracts entail a single deliverable, non-standard ones can encompass multiple deliverables, each potentially customized with distinct parameters.\nThis array is included in the list contracts response only if the query parameter show_deliverables=true is provided.\n", + "fees": { "items": { - "$ref": "#/components/schemas/OptionDeliverable" + "$ref": "#/components/schemas/IFFee" }, "type": "array" }, - "expiration_date": { - "description": "The expiration date of the option contract.", - "example": "2025-06-20", - "format": "date", - "type": "string" - }, "id": { - "description": "The unique identifier of the option contract.", - "example": "98359ef7-5124-49f3-85ea-5cf02df6defa", + "format": "uuid", "type": "string" }, - "multiplier": { - "description": "The multiplier of the option contract is crucial for calculating both the trade premium and the extended strike price. In standard contracts, the multiplier is always set to 100.\nFor instance, if a contract is traded at $1.50 and the multiplier is 100, the total amount debited when buying the contract would be $150.00.\nSimilarly, when exercising a call contract, the total cost will be equal to the strike price times the multiplier.", - "example": "100", - "type": "string" + "interests": { + "items": { + "$ref": "#/components/schemas/Interest" + }, + "type": "array" }, - "name": { - "description": "The name of the option contract.", - "example": "AAPL Jun 20 2025 100 Call", + "remaining_payable": { + "format": "decimal", "type": "string" }, - "open_interest": { - "description": "The open interest of the option contract.", - "example": "237", - "type": "string" - }, - "open_interest_date": { - "description": "The date of the open interest data.", - "example": "2023-12-11", - "format": "date", - "type": "string" - }, - "root_symbol": { - "description": "The root symbol of the option contract.", - "example": "AAPL", - "type": "string" - }, - "size": { - "description": "Represents the number of underlying shares to be delivered in case the contract is exercised/assigned. For standard contracts, this is always 100.\nThis field should **not** be used as a multiplier, specially for non-standard contracts.", - "example": "100", + "source_account_no": { "type": "string" }, "status": { - "description": "The status of the option contract.", - "enum": [ - "active", - "inactive" - ], - "example": "active", - "type": "string" - }, - "strike_price": { - "description": "The strike price of the option contract.", - "example": "100", - "type": "string" - }, - "style": { - "description": "The style of the option contract.", - "enum": [ - "american", - "european" - ], - "example": "american", - "type": "string" - }, - "symbol": { - "description": "The symbol representing the option contract.", - "example": "AAPL250620C00100000", - "type": "string" - }, - "tradable": { - "description": "Indicates whether the option contract is tradable.", - "example": true, - "type": "boolean" - }, - "type": { - "description": "The type of the option contract.", - "enum": [ - "call", - "put" - ], - "example": "call", - "type": "string" + "$ref": "#/components/schemas/InstantFundingStatus" }, - "underlying_asset_id": { - "description": "The unique identifier of the underlying asset.", - "example": "b0b6dd9d-8b9b-48a9-ba46-b9d54906e415", + "system_date": { + "format": "date", "type": "string" }, - "underlying_symbol": { - "description": "The underlying symbol of the option contract.", - "example": "AAPL", + "total_interest": { + "format": "decimal", "type": "string" } }, "required": [ "id", - "symbol", - "name", + "amount", + "account_no", + "source_account_no", "status", - "tradable", - "expiration_date", - "underlying_symbol", - "underlying_asset_id", - "type", - "style", - "strike_price", - "multiplier", - "size" + "system_date", + "deadline", + "created_at", + "total_interest", + "remaining_payable", + "interests", + "fees" ], "type": "object" }, - "OptionDeliverable": { + "InstantFundingStatus": { + "description": "Status:\n * `PENDING`: Created and waiting to be processed\n * `CANCELED`: Canceled\n * `EXECUTED`: All fees (including Partner fee) are transacted\n * `FAILED`: Failed mostly due to technical reasons\n * `COMPLETED`: All transactions are settled\n", + "enum": [ + "PENDING", + "CANCELED", + "EXECUTED", + "FAILED", + "COMPLETED" + ], + "type": "string" + }, + "Interest": { "properties": { - "allocation_percentage": { - "description": "Cost allocation percentage of the deliverable.\nThis is used to determine the cost basis of the equity shares received from the exercise, specially for non-standard contracts with multiple deliverables.\n", - "example": "100", - "type": "string" - }, "amount": { - "description": "The deliverable amount. For cash deliverables, this is the cash amount.\nFor standard contract, this is always 100.\nThis field can be null in case the deliverable settlement is delayed and the amount is yet to be determined.\n", - "example": "100", + "format": "decimal", "type": "string" }, - "asset_id": { - "description": "Unique identifier of the deliverable asset. For standard contracts, this is equivalent to underlying_asset_id of the contracts.\nThis field is not returned for cash deliverables.\n", - "example": "b0b6dd9d-8b9b-48a9-ba46-b9d54906e415", + "created_at": { + "format": "date-time", "type": "string" }, - "delayed_settlement": { - "description": "If true, the settlement of the deliverable will be delayed.\nFor instance, in the event of a contract with a delayed deliverable being exercised, both the availability of the deliverable and its settlement may be postponed beyond the typical timeframe.\n", - "example": false, - "type": "boolean" - }, - "settlement_method": { - "description": "Indicates the settlement method that will be used:\n- **BTOB**: Broker to Broker\n- **CADF**: Cash Difference\n- **CAFX**: Cash Fixed\n- **CCC**: Correspondent Clearing Corp\n", - "enum": [ - "BTOB", - "CADF", - "CAFX", - "CCC" - ], - "example": "CCC", + "date": { + "format": "date", "type": "string" }, - "settlement_type": { - "description": "Indicates when the deliverable will be settled if the contract is exercised/assigned.\n", - "enum": [ - "T+0", - "T+1", - "T+2", - "T+3", - "T+4", - "T+5" - ], - "example": "T+2", + "id": { + "format": "uuid", "type": "string" }, - "symbol": { - "description": "Symbol of the deliverable. For standard contracts, this is equivalent to the underlying symbol of the contract.\n", - "example": "AAPL", + "reconciled_at": { + "format": "date-time", "type": "string" }, - "type": { - "description": "Type of deliverable, indicating whether it's cash or equity. For standard contracts, it is always \"equity\".\n", - "enum": [ - "cash", - "equity" - ], - "example": "equity", - "type": "string" + "status": { + "$ref": "#/components/schemas/InstantFundingStatus" } }, "required": [ - "type", - "symbol", + "id", + "date", "amount", - "allocation_percentage", - "settlement_type", - "settlement_method", - "delayed_settlement" + "status", + "created_at", + "reconciled_at" ], "type": "object" }, - "OptionsApprovalEvent": { - "description": "This property is included when the account's approved options level changes.\n", - "properties": { - "approved_level_from": { - "description": "The approved options level before the change", - "type": "integer" - }, - "approved_level_to": { - "description": "The approved options level after the change", - "type": "integer" + "IssueOAuthTokenResponse": { + "description": "", + "examples": [ + { + "access_token": "87586f14-c3f4-4912-b107-f75bc17ff87a", + "scope": "general", + "token_type": "Bearer" } - }, - "type": "object" - }, - "OptionsApprovalRequest": { + ], "properties": { - "level": { - "description": "The desired option trading level. 1=Covered Call/Cash-Secured Put, 2=Long Call/Put, 3=Spreads/Straddles.", + "access_token": { + "description": "OAuth token", + "type": "string" + }, + "scope": { + "description": "Token's scope", + "type": "string" + }, + "token_type": { + "description": "Always `Bearer`", "enum": [ - 1, - 2, - 3 + "Bearer" ], - "example": 3, - "type": "integer" + "example": "Bearer", + "type": "string" } }, + "required": [ + "access_token", + "token_type", + "scope" + ], "type": "object" }, - "OptionsApprovalResponse": { + "JITAssetClass": { + "description": "Values:\n * `crypto`: Used to identify a crypto only account\n * `us_equity`: Used to identify an account that trades US equities, or both crypto and\n US equities.\n", + "enum": [ + "crypto", + "us_equity" + ], + "type": "string" + }, + "JITLedgerAccount": { + "examples": [ + { + "created_at": "1653532627", + "id": "2896b9e2-3198-44cd-a08e-4cc4079aee33", + "ledger_name": "Securities JIT IN", + "status": "active" + } + ], "properties": { - "account_id": { - "description": "The account ID.", - "example": "c8f1ef5d-edc0-4f23-9ee4-378f19cb92a4", - "format": "uuid", - "type": "string" - }, - "approved_level": { - "description": "The option trading level approved for this request. Only present once the request has completed processiing.\nNote that a subsequent request may be approved for a different level.\n0=Disabled, 1=Covered Call/Cash-Secured Put, 2=Long Call/Put, 3=Spreads/Straddles.\"\n", - "enum": [ - 0, - 1, - 2, - 3 - ], - "example": 3, - "type": "integer" - }, "created_at": { - "description": "The time when the request was submitted.", - "example": "2021-03-16T18:38:01.942282Z", - "format": "date-time", + "description": "Creation time in UNIX format", "type": "string" }, "id": { - "description": "The request ID.", - "example": "88b5f678-fef5-447b-af15-f21e367e6d8c", - "format": "uuid", + "description": "The ledger ID", "type": "string" }, - "requested_level": { - "description": "The request option trading level. 0=Disabled, 1=Covered Call/Cash-Secured Put, 2=Long Call/Put, 3=Spreads/Straddles.", - "enum": [ - 0, - 1, - 2, - 3 - ], - "example": 3, - "type": "integer" - }, - "requester": { - "description": "The requester of the options approval request.", - "enum": [ - "CORRESPONDENT", - "ALPACA_ADMIN" - ], - "example": "CORRESPONDENT", + "ledger_name": { + "description": "The ledger name", "type": "string" }, "status": { - "$ref": "#/components/schemas/OptionsApprovalStatus" - }, - "updated_at": { - "description": "The time when the request was last updated.", - "example": "2021-03-16T18:38:01.942282Z", - "format": "date-time", "type": "string" } }, + "title": "JITLedgerAccount", "type": "object" }, - "OptionsApprovalStatus": { - "description": "The request status.\n- PENDING: The request is under review.\n- APPROVED: The request has been successfully approved, the account is now able to trade options.\n- LOWER_LEVEL_APPROVED: The request has been approved for a level lower than the requested one.\n- REJECTED: The request has been rejected.\n", - "enum": [ - "PENDING", - "APPROVED", - "LOWER_LEVEL_APPROVED", - "REJECTED" - ], - "example": "PENDING", - "type": "string" - }, - "OptionsApprovalsList": { - "description": "A list of options approval requests.", - "properties": { - "next_page_token": { - "$ref": "#/components/schemas/NextPageToken" - }, - "options_approvals": { - "description": "An array of options approval requests.", - "items": { - "$ref": "#/components/schemas/OptionsApprovalResponse" - }, - "type": "array" - } - }, - "type": "object" - }, - "Order": { + "JITReport": { + "description": "JIT Securities reports are made available through the API and can be accessed within the hour after 11:30 PM EST on the trade date (T+0). The reports communicate transaction-level details as well as overall settlement amounts, transfer direction, and payment timing.", "properties": { - "asset_class": { - "$ref": "#/components/schemas/AssetClass" - }, - "asset_id": { - "description": "The asset ID (For options this represents the option contract ID)", - "example": "61e69015-8549-4bfd-b9c3-01e75843f47d", - "format": "uuid", - "type": "string" - }, - "cancel_requested_at": { - "description": "Time when cancellation or bust was requested (if applicable)", - "example": "2021-03-16T18:38:01.942282Z", - "format": "date-time", - "nullable": true, - "type": "string" - }, - "canceled_at": { - "description": "Can be null", - "example": "2021-03-16T18:38:01.942282Z", - "format": "date-time", - "nullable": true, - "type": "string" - }, - "client_order_id": { - "description": "Client unique order ID", - "example": "61e69015-8549-4bfd-b9c3-01e75843f47d", - "maxLength": 128, - "type": "string" - }, - "commission": { - "description": "The dollar value commission for this order.", - "example": "3.14", - "format": "decimal", + "detail": { + "description": "Contains all activities that impact cash throughout the trading session including executed trades, trading fees, and corporate actions that involve cash allocations.\n\ncontent-type = application/csv", "type": "string" }, - "commission_bps": { - "deprecated": true, - "description": "**deprecated**: Please use the commission_type = bps instead and set the desired bps value in the `commission` field.\nThe percent commission you want to charge the end user on the order (expressed in bps). Alpaca will convert the order to a notional amount for purposes of calculating commission.\n", - "example": "10", - "format": "decimal", + "net_payment": { + "description": "Highlights the net amount due to Alpaca by settlement or to the partner on the date of settlement in a formalized invoice format.\n\ncontent-type = application/pdf", "type": "string" }, - "commission_type": { - "$ref": "#/components/schemas/CommissionType" - }, - "created_at": { - "description": "Time when order was entered", - "example": "2021-03-16T18:38:01.942282Z", - "format": "date-time", + "net_payment_final": { + "description": "Includes additional information to account for T+0 and T+1 settling activity to clarify settlement journaling reconciliation. This report is generated after trading session close on T+1.\n\ncontent-type = application/pdf", "type": "string" }, - "expired_at": { - "description": "Can be null", - "example": "2021-03-16T18:38:01.942282Z", - "format": "date-time", - "nullable": true, + "net_summary": { + "description": "Consists of three columns and a single row, which lists the net money movement to or from Alpaca for T0, T1, and T2.\n\ncontent-type = application/csv", "type": "string" }, - "extended_hours": { - "example": true, - "type": "boolean" - }, - "failed_at": { - "description": "Can be null", - "example": "2021-03-16T18:38:01.942282Z", - "format": "date-time", - "nullable": true, + "obligation": { + "description": "Lists of all open obligations towards the partner that are to be settled.\n\ncontent-type = application/csv", "type": "string" - }, - "filled_at": { - "description": "Time the order was filled. Can be null if not filled", - "example": "2021-03-16T18:38:01.942282Z", + } + }, + "title": "JITReport", + "type": "object" + }, + "JITReportDownloadURL": { + "properties": { + "expires_at": { + "description": "Timestamp when the signed URL expires.", "format": "date-time", - "nullable": true, - "type": "string" - }, - "filled_avg_price": { - "description": "Filled average price. Can be 0 until order is processed in case order is passed outside of market hours", - "example": "4.2", - "format": "decimal", - "nullable": true, - "type": "string" - }, - "filled_qty": { - "description": "Filled quantity", - "example": "4.2", - "format": "decimal", "type": "string" }, - "hwm": { - "description": "The highest (lowest) market price seen since the trailing stop order was submitted.", - "example": "3.14", - "format": "decimal", - "nullable": true, + "filename": { + "description": "Name of the generated report file.", "type": "string" }, - "id": { - "description": "Order ID generated by Alpaca", - "example": "61e69015-8549-4bfd-b9c3-01e75843f47d", - "format": "uuid", + "url": { + "description": "Temporary signed URL used to download the report.", + "format": "uri", "type": "string" + } + }, + "required": [ + "url", + "filename", + "expires_at" + ], + "type": "object" + }, + "JNLC": { + "allOf": [ + { + "$ref": "#/components/schemas/JournalBase" }, - "legs": { - "description": "When querying non-simple order_class orders in a nested style, an array of Order entities associated with this order. Otherwise, null.", - "items": { - "$ref": "#/components/schemas/OrderLeg" + { + "properties": { + "entry_type": { + "description": "Cash journal discriminator value.", + "enum": [ + "JNLC" + ], + "type": "string" + }, + "transmitter_account_number": { + "description": "Optional for JNLC journals. Maximum 255 characters. See more details about [Travel Rule](https://alpaca.markets/docs/broker/integration/funding/#travel-rule) in our main documentation.", + "maxLength": 255, + "type": "string" + }, + "transmitter_address": { + "description": "Optional for JNLC journals. Maximum 255 characters. See more details about [Travel Rule](https://alpaca.markets/docs/broker/integration/funding/#travel-rule) in our main documentation.", + "maxLength": 255, + "type": "string" + }, + "transmitter_financial_institution": { + "description": "Optional for JNLC journals. Maximum 255 characters. See more details about [Travel Rule](https://alpaca.markets/docs/broker/integration/funding/#travel-rule) in our main documentation.", + "maxLength": 255, + "type": "string" + }, + "transmitter_name": { + "description": "Optional for JNLC journals. Maximum 255 characters. See more details about [Travel Rule](https://alpaca.markets/docs/broker/integration/funding/#travel-rule) in our main documentation.", + "maxLength": 255, + "type": "string" + }, + "transmitter_timestamp": { + "description": "Optional for JNLC journals. RFC 3339 format. See more details about [Travel Rule](https://alpaca.markets/docs/broker/integration/funding/#travel-rule) in our main documentation.", + "format": "date-time", + "type": "string" + } }, - "nullable": true, - "type": "array" - }, - "limit_price": { - "description": "Limit price", - "example": "3.14", - "format": "decimal", - "nullable": true, - "type": "string" - }, - "notional": { - "description": "Ordered notional amount. If entered, qty will be null. Can take up to 2 decimal points.", - "example": "4.2", - "format": "decimal", - "nullable": true, - "type": "string" - }, - "order_class": { - "$ref": "#/components/schemas/OrderClass" - }, - "order_type": { - "$ref": "#/components/schemas/OrderType" - }, - "position_intent": { - "$ref": "#/components/schemas/PositionIntent" + "required": [ + "entry_type" + ], + "type": "object" + } + ], + "description": "A cash journal response with all shared Journal fields.", + "examples": [ + { + "currency": "USD", + "description": "", + "entry_type": "JNLC", + "from_account": "8f8c8cee-2591-4f83-be12-82c659b5e748", + "id": "56f106e5-25a4-4eee-96fa-25bb05dc86bc", + "net_amount": "1000", + "price": null, + "qty": null, + "settle_date": null, + "status": "pending", + "symbol": null, + "system_date": null, + "to_account": "399f85f1-cbbd-4eaa-a934-70027fb5c1de" + } + ], + "title": "JNLC" + }, + "JNLCActivityV2": { + "allOf": [ + { + "$ref": "#/components/schemas/CommonJournalActivityV2" }, - "qty": { - "description": "Ordered quantity. If entered, notional will be null. Can take up to 2 decimal points.", - "example": "4.2", - "format": "decimal", - "nullable": true, - "type": "string" + { + "type": "object" + } + ], + "description": "Journal entry (cash)", + "type": "object" + }, + "JNLS": { + "allOf": [ + { + "$ref": "#/components/schemas/JournalBase" }, - "replaced_at": { - "description": "Can be null", - "example": "2021-03-16T18:38:01.942282Z", - "format": "date-time", - "nullable": true, - "type": "string" - }, - "replaced_by": { - "description": "The order ID that this order was replaced by. (Can be null)", - "example": "61e69015-8549-4bfd-b9c3-01e75843f47d", - "format": "uuid", - "nullable": true, - "type": "string" + { + "properties": { + "entry_type": { + "description": "Securities journal discriminator value.", + "enum": [ + "JNLS" + ], + "type": "string" + } + }, + "required": [ + "entry_type" + ], + "type": "object" + } + ], + "description": "A securities journal response with all shared Journal fields.", + "examples": [ + { + "currency": "USD", + "description": "this is a test journal", + "entry_type": "JNLS", + "from_account": "8f4f3d11-4483-4199-840f-6c5fe0b7ca24", + "id": "f45f67e8-d1fc-4136-aa4f-cf4460aecdfc", + "net_amount": null, + "price": "128.23", + "qty": "0.5", + "settle_date": "2020-12-24", + "status": "executed", + "symbol": "AAPL", + "system_date": "2020-12-24", + "to_account": "a7a7e666-6f2a-433c-8c33-17b66b8941fa" + } + ], + "title": "JNLS" + }, + "JNLSActivityV2": { + "allOf": [ + { + "$ref": "#/components/schemas/CommonJournalActivityV2" }, - "replaces": { - "description": "The order ID that this order replaces. (Can be null)", - "example": "61e69015-8549-4bfd-b9c3-01e75843f47d", - "format": "uuid", - "nullable": true, - "type": "string" + { + "properties": { + "symbol": { + "description": "The symbol of the security involved with the activity", + "type": "string" + } + }, + "required": [ + "symbol" + ], + "type": "object" + } + ], + "description": "Journal entry (stock)", + "type": "object" + }, + "Journal": { + "description": "Represents a cash or securities transfer between accounts, selected by `entry_type`.", + "discriminator": { + "mapping": { + "JNLC": "#/components/schemas/JNLC", + "JNLS": "#/components/schemas/JNLS" }, - "side": { - "$ref": "#/components/schemas/OrderSide" + "propertyName": "entry_type" + }, + "examples": [ + { + "currency": "USD", + "description": "", + "entry_type": "JNLC", + "from_account": "8f8c8cee-2591-4f83-be12-82c659b5e748", + "id": "56f106e5-25a4-4eee-96fa-25bb05dc86bc", + "net_amount": "1000", + "price": null, + "qty": null, + "settle_date": null, + "status": "pending", + "symbol": null, + "system_date": null, + "to_account": "399f85f1-cbbd-4eaa-a934-70027fb5c1de" }, - "status": { - "$ref": "#/components/schemas/OrderStatus" + { + "currency": "USD", + "description": "this is a test journal", + "entry_type": "JNLS", + "from_account": "8f4f3d11-4483-4199-840f-6c5fe0b7ca24", + "id": "f45f67e8-d1fc-4136-aa4f-cf4460aecdfc", + "net_amount": null, + "price": "128.23", + "qty": "0.5", + "settle_date": "2020-12-24", + "status": "executed", + "symbol": "AAPL", + "system_date": "2020-12-24", + "to_account": "a7a7e666-6f2a-433c-8c33-17b66b8941fa" + } + ], + "oneOf": [ + { + "$ref": "#/components/schemas/JNLC" }, - "stop_price": { - "description": "Stop price", - "example": "3.14", - "format": "decimal", - "nullable": true, + { + "$ref": "#/components/schemas/JNLS" + } + ], + "title": "Journal" + }, + "JournalBase": { + "description": "Shared response fields returned for cash and securities journals.", + "properties": { + "created_at": { + "description": "The creation time when supplied by the endpoint.", + "format": "date-time", "type": "string" }, - "submitted_at": { - "description": "Time the order was submitted for execution or, if not yet submitted the created_at time. Because orders are submitted for execution asynchronous to database updates, at times this may be before the created_at time.", - "example": "2021-03-16T18:38:01.942282Z", - "format": "date-time", + "currency": { + "description": "Currency denomination of the journal.", "type": "string" }, - "swap_fee_bps": { - "description": "Fee in basis points on top swap rate charged by the correspondent on every order", + "description": { + "description": "The journal description.", "type": "string" }, - "swap_rate": { - "description": "Swap rate is the exchange rate (without mark-up) used to convert the price into local currency or crypto asset", + "from_account": { + "description": "The account ID that initiated the journal.", + "format": "uuid", "type": "string" }, - "symbol": { - "description": "The asset symbol", - "example": "AALP", + "id": { + "description": "The journal ID.", + "format": "uuid", "type": "string" }, - "time_in_force": { - "$ref": "#/components/schemas/TimeInForce" + "net_amount": { + "description": "The cash amount, or null when not applicable.", + "format": "decimal", + "type": [ + "string", + "null" + ] }, - "trail_percent": { - "description": "The percent value away from the high water mark for trailing stop orders.", - "example": "5.0", + "price": { + "description": "The journaled security price, or null when not applicable.", "format": "decimal", - "nullable": true, - "type": "string" + "type": [ + "string", + "null" + ] }, - "trail_price": { - "description": "The dollar value away from the high water mark for trailing stop orders.", - "example": "3.14", + "qty": { + "description": "The journaled security quantity, or null when not applicable.", "format": "decimal", - "nullable": true, - "type": "string" + "type": [ + "string", + "null" + ] }, - "type": { - "$ref": "#/components/schemas/OrderType" + "settle_date": { + "description": "The settlement date, or null until one is assigned.", + "format": "date", + "type": [ + "string", + "null" + ] }, - "updated_at": { - "description": "Time of most recent change to the order", - "example": "2021-03-16T18:38:01.942282Z", - "format": "date-time", + "status": { + "$ref": "#/components/schemas/JournalStatus" + }, + "symbol": { + "description": "The journaled security symbol, or null for a cash journal.", + "type": [ + "string", + "null" + ] + }, + "system_date": { + "description": "The booking-system date, or null until one is assigned.", + "format": "date", + "type": [ + "string", + "null" + ] + }, + "to_account": { + "description": "The account ID that received the journal.", + "format": "uuid", "type": "string" }, - "usd": { - "description": "Nested object to encompass the USD equivalent fields for the local currency fields", - "type": "object" + "transmitter_info": { + "$ref": "#/components/schemas/TransmitterInfo" } }, "required": [ "id", - "symbol" + "from_account", + "to_account", + "symbol", + "qty", + "price", + "status", + "settle_date", + "system_date", + "net_amount", + "description", + "currency" ], + "title": "JournalBase", "type": "object" }, - "OrderClass": { - "description": "The order classes supported by Alpaca vary based on the order's security type. The following provides a comprehensive breakdown of the supported order classes for each category:\n - Equity trading: simple (or \"\"), oco, oto, bracket.\n - Options trading:\n - simple (or \"\")\n - mleg (required for multi-leg complex option strategies)\n - Crypto trading: simple (or \"\").", + "JournalEntryType": { + "description": "This enum represents the various kinds of Journal alpaca supports.\n\nCurrent values are:\n\n- **JNLC**\n\n Journal Cash between accounts\n\n- **JNLS**\n\n Journal Securities between accounts", "enum": [ - "simple", - "bracket", - "oco", - "oto", - "mleg" + "JNLC", + "JNLS" ], - "example": "bracket", + "title": "", "type": "string" }, - "OrderClosedResponse": { - "description": "Represents the result of asking the api to cancel an Order.\n", + "JournalStatus": { + "description": "Represents the status that a Journal instance can be in.\n\n**Current Values**\n\nqueued\tJournal in queue to be processed. Journal is not processed yet.\n\nsent_to_clearing\tJournal sent to be processed by Alpaca's booking system. The journal is not processed yet.\n\npending\t Journal pending to be processed as it requires manual approval from Alpaca operations (for example due to hitting JNLC daily limits).\n\nexecuted\tJournal executed and balances updated for both sides of \nthe journal transaction. This is not a final status, journals can be reversed if there is an error.\n\nactivity_created\tNon-trade activity has been created for journal (JNLC v2-only).\n\nrejected\tJournal rejected. Please try again.\n\ncanceled\tJournal canceled. This is a **FINAL** status.\n\nrefused\tJournal refused. Please try again.\n\ndeleted\tJournal deleted. This is a **FINAL** status.\n\ncorrect\tJournal is corrected. Previously executed journal is cancelled and a new journal is corrected amount is created. This is a **FINAL** status.", + "enum": [ + "pending", + "canceled", + "executed", + "activity_created", + "queued", + "rejected", + "deleted", + "refused", + "sent_to_clearing", + "correct" + ], + "type": "string" + }, + "JournalStatusEvent": { + "description": "Represents a change in a Journal's status, sent over the events streaming api.\n", + "examples": [ + { + "at": "2021-05-07T10:28:23.163857Z", + "entry_type": "JNLC", + "event_id": 1406, + "event_ulid": "01F535Y1FVY8WZHE763HCNS8SZ", + "journal_id": "2f144d2a-91e6-46ff-8e37-959a701cc58d", + "status_from": "", + "status_to": "queued" + } + ], "properties": { - "body": { - "$ref": "#/components/schemas/Order" + "at": { + "description": "Timestamp of event", + "format": "date-time", + "minLength": 1, + "type": "string" }, - "id": { - "description": "UUID of the order that was canceled", + "entry_type": { + "$ref": "#/components/schemas/JournalEntryType" + }, + "event_id": { + "description": "Monotonically increasing 64-bit integer not available to new partners, and for backward compatibility purposes only; use `event_ulid` as the stable identifier where possible\n", + "type": "integer" + }, + "event_ulid": { + "description": "lexically sortable, monotonically increasing character array", + "format": "ulid", + "type": "string" + }, + "journal_id": { + "description": "The UUID of the related Journal", "format": "uuid", "type": "string" }, - "status": { - "description": "Http status code for the attempt to close this Order", - "type": "integer" + "status_from": { + "$ref": "#/components/schemas/JournalStatusFrom" + }, + "status_to": { + "$ref": "#/components/schemas/JournalStatus" } }, "required": [ - "id", - "status" + "at", + "entry_type", + "event_ulid", + "journal_id", + "status_from", + "status_to" ], - "title": "OrderClosedResponse", - "type": "object", - "x-examples": {} + "title": "JournalStatusEvent", + "type": "object" }, - "OrderLeg": { - "properties": { - "asset_class": { - "$ref": "#/components/schemas/AssetClass" - }, - "asset_id": { - "description": "The asset ID (For options this represents the option contract ID)", - "example": "61e69015-8549-4bfd-b9c3-01e75843f47d", - "format": "uuid", - "type": "string" + "JournalStatusEventV2": { + "description": "Represents a change in a Journal's status, sent over the events streaming api.\n", + "examples": [ + { + "at": "2021-05-07T10:28:23.163857Z", + "description": "Journal of cash between accounts", + "entry_type": "JNLC", + "event_id": "01F535Y1FVY8WZHE763HCNS8SZ", + "journal_id": "2f144d2a-91e6-46ff-8e37-959a701cc58d", + "status_from": "", + "status_to": "queued" }, - "canceled_at": { - "description": "Can be null", - "example": "2021-03-16T18:38:01.942282Z", + { + "at": "2021-05-07T10:28:23.163857Z", + "description": "Journal of cash between accounts", + "entry_type": "JNLC", + "event_id": "01F535Y1FVY8WZHE763HCNS8SZ", + "idempotency_key": "72bdf18f-7410-43ec-b4e8-b6e41c5c0160", + "idempotency_key_type": "single", + "journal_id": "2f144d2a-91e6-46ff-8e37-959a701cc58d", + "status_from": "queued", + "status_to": "executed" + } + ], + "properties": { + "at": { + "description": "Timestamp of event", "format": "date-time", - "nullable": true, + "minLength": 1, "type": "string" }, - "client_order_id": { - "description": "Client unique order ID", - "example": "61e69015-8549-4bfd-b9c3-01e75843f47d", - "maxLength": 128, + "batch_error_message": { + "description": "If journal submitted in batch (that is, idempotency_key_type is batch), this is the error message of the batch journal execution.", "type": "string" }, - "commission": { - "description": "The dollar value commission you want to charge the end user.", - "example": "3.14", - "format": "decimal", + "description": { + "description": "The description of the journal event when submitted", + "example": "Journal of cash between accounts", "type": "string" }, - "commission_bps": { - "deprecated": true, - "description": "**deprecated**: Please use the commission_type = bps instead and set the desired bps value in the `commission` field.\nThe percent commission you want to charge the end user on the order (expressed in bps). Alpaca will convert the order to a notional amount for purposes of calculating commission.\n", - "example": "10", - "format": "decimal", + "entry_type": { + "$ref": "#/components/schemas/JournalEntryType" + }, + "event_id": { + "description": "lexically sortable, monotonically increasing character array", + "format": "ulid", "type": "string" }, - "commission_type": { - "$ref": "#/components/schemas/CommissionType" + "idempotency_key": { + "description": "The idempotency key of the journal event", + "format": "uuid", + "type": "string" }, - "created_at": { - "description": "Time when order was entered", - "example": "2021-03-16T18:38:01.942282Z", - "format": "date-time", + "idempotency_key_type": { + "description": "The type of idempotency key", + "enum": [ + "single", + "batch" + ], "type": "string" }, - "expired_at": { - "description": "Can be null", - "example": "2021-03-16T18:38:01.942282Z", - "format": "date-time", - "nullable": true, + "journal_id": { + "description": "The UUID of the related Journal", + "format": "uuid", "type": "string" }, - "extended_hours": { - "example": true, - "type": "boolean" + "status_from": { + "$ref": "#/components/schemas/JournalStatusFrom" }, - "failed_at": { - "description": "Can be null", - "example": "2021-03-16T18:38:01.942282Z", - "format": "date-time", - "nullable": true, - "type": "string" + "status_to": { + "$ref": "#/components/schemas/JournalStatus" + } + }, + "required": [ + "at", + "entry_type", + "event_id", + "journal_id", + "status_from", + "status_to" + ], + "title": "JournalStatusEvent", + "type": "object" + }, + "JournalStatusFrom": { + "description": "Previous journal status in stream status-change events. Empty string is used for the initial status-change event when there is no previous status.", + "oneOf": [ + { + "$ref": "#/components/schemas/JournalStatus" }, - "filled_at": { - "description": "Time the order was filled. Can be null if not filled", - "example": "2021-03-16T18:38:01.942282Z", - "format": "date-time", - "nullable": true, + { + "enum": [ + "" + ], "type": "string" + } + ] + }, + "KYCResultType": { + "description": "A KYC reason identifier used as a property name in `KYCResultTypeSet`. The set is extensible, so integrations must tolerate and preserve unrecognized values.", + "examples": [ + "IDENTITY_VERIFICATION", + "compromised_document" + ], + "title": "KYCResultType", + "type": "string" + }, + "KYCResultTypeSet": { + "additionalProperties": { + "maxProperties": 0, + "type": "object" + }, + "description": "A nullable set of KYC reason identifiers represented as property names with empty object values. See `KYCResults` for documented identifiers and interpretation guidance.", + "examples": [ + null, + {}, + { + "IDENTITY_VERIFICATION": {}, + "WATCHLIST_HIT": {} + } + ], + "propertyNames": { + "$ref": "#/components/schemas/KYCResultType" + }, + "title": "KYCResultTypeSet", + "type": [ + "object", + "null" + ] + }, + "KYCResults": { + "description": "Holds information about the result of KYC.\n\n`accept`, `indeterminate`, and `reject` are nullable sets represented as objects. Each property name is a KYC reason identifier, and every property value is always an empty object (`{}`) carrying no nested data. For example:\n\n```json\n{\n \"IDENTITY_VERIFICATION\": {},\n \"WATCHLIST_HIT\": {}\n}\n```\n\nInterpret each reason together with its enclosing result category and `additional_information`.\n\nDocumented reason identifiers:\n- `IDENTITY_VERIFICATION`: A government-issued identity document is required to verify the account owner's identity.\n- `TAX_IDENTIFICATION`: A tax identification document is required to verify the account owner's tax identification number.\n- `ADDRESS_VERIFICATION`: Additional documentation, such as a government-issued ID or statement, may be required to verify the account owner's residential address.\n- `DATE_OF_BIRTH`: A government-issued identity document is required to verify the account owner's date of birth.\n- `SELFIE_VERIFICATION`: A live selfie of the account owner is required to verify identity.\n- `PEP`: The account owner disclosed politically exposed person status, and additional information may be required.\n- `FAMILY_MEMBER_PEP`: The account owner disclosed that an immediate family member is politically exposed, and additional information may be required.\n- `CONTROL_PERSON`: Additional company information may be required because the account owner disclosed a control position.\n- `AFFILIATED`: Additional firm information may be required because the account owner disclosed an affiliation with FINRA or an exchange.\n- `VISA_TYPE_OTHER`: The account owner's visa type and expiration date may be required because the submitted visa type was \"other.\"\n- `W8BEN_CORRECTION`: A new W-8BEN with corrected identifying information is required.\n- `COUNTRY_NOT_SUPPORTED`: The account owner's country of tax residence or legal residence is not supported for this account.\n- `WATCHLIST_HIT`: Watchlist screening returned a result that requires further review; no action is needed from the account owner unless separately requested.\n- `OTHER`: See `additional_information` for a custom message describing what is required from the account owner.\n- `OTHER_PARTNER`: See `additional_information` for a custom message intended for the Broker API partner.\n- `CITIZENSHIP_COUNTRY_NOT_SUPPORTED`: The account owner's country of citizenship is not supported for this account.\n- `DIFFERING_COUNTRY`: A submitted identity document was issued by a country different from the account owner's country of tax residence.\n- `WL_PEP_SCREENING`: Watchlist screening returned a result related to possible political exposure.\n- `WL_LEGAL_REGULATORY_WARNING`: Watchlist screening returned a legal or regulatory warning result.\n- `UNDER_MINIMUM_AGE`: The account owner does not meet the minimum age requirement for this account.\n- `PEP_FOREIGN`: The account owner disclosed foreign politically exposed person status, and additional information may be required.\n- `FAMILY_MEMBER_PEP_FOREIGN`: The account owner disclosed that an immediate family member has foreign politically exposed person status, and additional information may be required.\n- `DISCRETIONARY`: The account is designated for discretionary trading by a registered investment adviser and requires review.\n- `HIO`: Additional information and a declaration form may be required because the account owner disclosed being the head of an international organization.\n- `INTERNAL_CHECK`: A non-specific account-opening check contributed to the KYC outcome; no more specific reason is available.\n- `compromised_document`: The identity document check indicated that the document may be compromised.\n- `data_comparison`: Data extracted from the identity document did not pass comparison checks and requires review.\n- `image_integrity`: A document or selfie image did not pass image-integrity checks and requires review.\n\nThe set of reason identifiers is extensible, so integrations must tolerate and preserve unrecognized values.\n", + "properties": { + "accept": { + "$ref": "#/components/schemas/KYCResultTypeSet" }, - "filled_avg_price": { - "description": "Filled average price. Can be 0 until order is processed in case order is passed outside of market hours", - "example": "4.2", - "format": "decimal", - "nullable": true, + "additional_information": { + "description": "Used to display a custom message.", "type": "string" }, - "filled_qty": { - "description": "Filled quantity", - "example": "4.2", - "format": "decimal", - "type": "string" + "indeterminate": { + "$ref": "#/components/schemas/KYCResultTypeSet" }, - "hwm": { - "description": "The highest (lowest) market price seen since the trailing stop order was submitted.", - "example": "3.14", - "format": "decimal", - "nullable": true, - "type": "string" + "reject": { + "$ref": "#/components/schemas/KYCResultTypeSet" }, - "id": { - "description": "Order ID generated by Alpaca", - "example": "61e69015-8549-4bfd-b9c3-01e75843f47d", - "format": "uuid", + "summary": { + "description": "Either `pass` or `fail`. Used to indicate if KYC has completed and passed or not. This field is used for internal purposes only.", "type": "string" - }, - "legs": { - "description": "Always null for an order leg; legs are not nested beyond one level.", - "example": null, + } + }, + "type": [ + "object", + "null" + ] + }, + "ListAPRTiersResponse": { + "description": "Response to a successful request for a list of APR tiers.\n", + "properties": { + "apr_tiers": { + "description": "All configured APR tiers available for assignment to accounts", "items": { - "type": "object" + "$ref": "#/components/schemas/APRTier" + }, + "type": "array" + } + }, + "type": "object" + }, + "ListFPSLLoansResponse": { + "description": "Response to a successful request for a list of FPSL loans.\n", + "properties": { + "loans": { + "description": "All FPSL loans matching the filter criteria", + "items": { + "$ref": "#/components/schemas/FPSLLoan" }, - "nullable": true, "type": "array" }, - "limit_price": { - "description": "Limit price", - "example": "3.14", - "format": "decimal", - "nullable": true, - "type": "string" - }, - "notional": { - "description": "Ordered notional amount. If entered, qty will be null. Can take up to 2 decimal points.", - "example": "4.2", - "format": "decimal", - "nullable": true, - "type": "string" - }, - "order_class": { - "$ref": "#/components/schemas/OrderClass" - }, - "order_type": { - "$ref": "#/components/schemas/OrderType" + "next_page_token": { + "description": "The token to use to retrieve the next page of results. If `null`, there are no more pages.\n", + "type": [ + "string", + "null" + ] + } + }, + "required": [ + "loans", + "next_page_token" + ], + "type": "object" + }, + "ListFundingDetails": { + "properties": { + "funding_details": { + "items": { + "$ref": "#/components/schemas/FundingDetail" + }, + "type": "array" + } + }, + "type": "object" + }, + "ListSortBy": { + "enum": [ + "created_at", + "amount", + "deadline" + ], + "type": "string" + }, + "ListTransfersResponse": { + "properties": { + "transfers": { + "items": { + "$ref": "#/components/schemas/FundingWalletTransfer" + }, + "type": "array" + } + }, + "type": "object" + }, + "MAActivityV2": { + "allOf": [ + { + "$ref": "#/components/schemas/CommonCaActivityV2" }, - "position_intent": { - "$ref": "#/components/schemas/PositionIntent" + { + "$ref": "#/components/schemas/CommonMAActivityV2" }, - "qty": { - "description": "Ordered quantity. If entered, notional will be null. Can take up to 2 decimal points.", - "example": "4.2", - "format": "decimal", - "nullable": true, - "type": "string" - }, - "replaced_at": { - "description": "Can be null", - "example": "2021-03-16T18:38:01.942282Z", - "format": "date-time", - "nullable": true, - "type": "string" + { + "properties": { + "acquiree_qty": { + "description": "Quantity of the acquiree", + "type": "string" + }, + "acquirer_qty": { + "description": "Quantity of the acquirer", + "type": "string" + }, + "cash_payout": { + "description": "The cash payout", + "type": "string" + }, + "cash_rate": { + "description": "The cash rate", + "type": "string" + } + }, + "required": [ + "acquiree_qty" + ], + "type": "object" + } + ], + "description": "Merger and acquisition", + "type": "object" + }, + "MEMActivityV2": { + "allOf": [ + { + "$ref": "#/components/schemas/CommonNTAActivityV2" }, - "replaced_by": { - "description": "The order ID that this order was replaced by. (Can be null)", - "example": "61e69015-8549-4bfd-b9c3-01e75843f47d", - "format": "uuid", - "nullable": true, - "type": "string" + { + "properties": { + "transfer_id": { + "description": "The transfer's ID", + "format": "uuid", + "type": "string" + } + }, + "required": [ + "transfer_id" + ], + "type": "object" + } + ], + "description": "Instant funding memopost", + "type": "object" + }, + "MLegOrderLeg": { + "description": "Represents an individual leg of a multileg options order.", + "properties": { + "position_intent": { + "$ref": "#/components/schemas/PositionIntent" }, - "replaces": { - "description": "The order ID that this order replaces. (Can be null)", - "example": "61e69015-8549-4bfd-b9c3-01e75843f47d", - "format": "uuid", - "nullable": true, + "ratio_qty": { + "description": "proportional quantity of this leg in relation to the overall multileg order qty", + "example": "1", "type": "string" }, "side": { "$ref": "#/components/schemas/OrderSide" }, - "status": { - "$ref": "#/components/schemas/OrderStatus" - }, - "stop_price": { - "description": "Stop price", - "example": "3.14", - "format": "decimal", - "nullable": true, + "symbol": { + "description": "symbol or asset ID to identify the asset to trade", "type": "string" + } + }, + "required": [ + "symbol", + "ratio_qty" + ], + "title": "MLegOrderLeg", + "type": "object" + }, + "NCActivityV2": { + "allOf": [ + { + "$ref": "#/components/schemas/CommonCaActivityV2" }, - "submitted_at": { - "description": "Time the order was submitted for execution or, if not yet submitted the created_at time. Because orders are submitted for execution asynchronous to database updates, at times this may be before the created_at time.", - "example": "2021-03-16T18:38:01.942282Z", + { + "$ref": "#/components/schemas/CommonNCActivityV2" + }, + { + "properties": { + "position_qty": { + "description": "The position quantity", + "type": "string" + } + }, + "required": [ + "position_qty" + ], + "type": "object" + } + ], + "description": "Name change", + "type": "object" + }, + "NextPageToken": { + "description": "Use this token in your next API call to paginate through the dataset and retrieve the next page of results. A null token indicates there are no more data to fetch.\n", + "example": "MTAwMA==", + "type": [ + "string", + "null" + ] + }, + "NonTradeActivity": { + "properties": { + "activity_sub_type": { + "$ref": "#/components/schemas/ActivitySubType" + }, + "created_at": { + "description": "Valid only for non-trading activity types. Null for trading activites.", + "example": "2021-05-10T14:01:04.650275Z", "format": "date-time", "type": "string" }, - "swap_fee_bps": { - "description": "Fee in basis points on top swap rate charged by the correspondent on every order", + "currency": { + "description": "Currency denomination of the activity. USD by default.", "type": "string" }, - "swap_rate": { - "description": "Swap rate is the exchange rate (without mark-up) used to convert the price into local currency or crypto asset", + "cusip": { + "description": "Valid only for non-trading activity types. Null for trading activites.", + "example": "037833100", "type": "string" }, - "symbol": { - "description": "The asset symbol", - "example": "AALP", + "date": { + "description": "Valid only for non-trading activity types. Null for trading activites.", + "example": "2021-05-21", + "format": "date", "type": "string" }, - "time_in_force": { - "$ref": "#/components/schemas/TimeInForce" + "description": { + "description": "Valid only for non-trading activity types. Null for trading activites.", + "example": "Example description", + "type": "string" }, - "trail_percent": { - "description": "The percent value away from the high water mark for trailing stop orders.", - "example": "5.0", + "group_id": { + "description": "ID used to link activities who share a sibling relationship", + "example": "13d96cf3-1cbe-4632-b2f2-86a9df5a3b9d", + "format": "uuid", + "type": "string" + }, + "net_amount": { + "description": "Valid only for non-trading activity types. Null for trading activites.", + "example": "1234", "format": "decimal", - "nullable": true, "type": "string" }, - "trail_price": { - "description": "The dollar value away from the high water mark for trailing stop orders.", - "example": "3.14", + "per_share_amount": { + "description": "Valid only for non-trading activity types. Null for trading activites.", + "example": "0.38921", "format": "decimal", - "nullable": true, "type": "string" }, - "type": { - "$ref": "#/components/schemas/OrderType" + "qty": { + "description": "Valid only for non-trading activity types. Null for trading activites.", + "example": "0.38921", + "format": "decimal", + "type": "string" }, - "updated_at": { - "description": "Time of most recent change to the order", - "example": "2021-03-16T18:38:01.942282Z", - "format": "date-time", + "status": { + "description": "Valid only for non-trading activity types. Null for trading activites.", + "enum": [ + "executed", + "correct", + "canceled" + ], + "example": "executed", "type": "string" }, - "usd": { - "description": "Nested object to encompass the USD equivalent fields for the local currency fields", - "type": "object" + "symbol": { + "description": "Valid only for non-trading activity types. Null for trading activites.", + "example": "AAPL", + "type": "string" + }, + "transfer_id": { + "description": "The transfer ID of the associated deposit or withdrawal. Populated for `OCT` (On Chain Transactions) activities.", + "example": "bacddbc5-1a45-4a54-8f6d-c3f2f9d2e2a1", + "format": "uuid", + "type": "string" } }, - "required": [ - "id", - "symbol" - ], + "title": "NonTradeActivity", "type": "object" }, - "OrderSide": { - "description": "Represents what side of the transaction an order was on. Required for all order classes except for `mleg`.", - "enum": [ - "buy", - "sell", - "buy_minus", - "sell_plus", - "sell_short", - "sell_short_exempt", - "undisclosed", - "cross", - "cross_short" + "NonTradeActivityEvent": { + "description": "Represents a non-trade activity SSE event", + "examples": [ + { + "account_id": "8e00606a-c9ac-409a-ba45-f55e8f77984a", + "at": "2024-11-26T15:25:17.803914Z", + "description": "", + "entry_type": "JNLC", + "event_ulid": "01JDMH7BKCCB4XY5F11HN63NZX", + "id": "afb78fa2-7e4c-4e0a-bca2-2e0d26a87f88", + "net_amount": 10, + "qty": 0, + "settle_date": "2024-11-26", + "status": "executed", + "symbol": "", + "system_date": "2024-11-26" + }, + { + "account_id": "11629972-5fd6-4e14-ad9e-0f0cabd2777f", + "at": "2024-11-12T01:15:53.132425Z", + "description": "TAF fee for proceed of 0.010052895 shares (1 trades) on 2024-11-11 by 613651765", + "entry_sub_type": "TAF", + "entry_type": "FEE", + "event_ulid": "01JCEZ1ZDCE44GFX6WZ75N4199", + "id": "d96cea8a-6a77-46cd-bb7b-df6d74442653", + "net_amount": -0.01, + "qty": 0, + "settle_date": "2024-11-12", + "status": "executed", + "symbol": "", + "system_date": "2024-11-11" + } ], - "example": "buy", - "type": "string" - }, - "OrderStatus": { - "enum": [ - "new", - "partially_filled", - "filled", - "done_for_day", - "canceled", - "expired", - "replaced", - "pending_cancel", - "pending_replace", - "accepted", - "pending_new", - "accepted_for_bidding", - "stopped", - "rejected", - "suspended", - "calculated" - ], - "example": "filled", - "type": "string" - }, - "OrderType": { - "description": "The order types supported by Alpaca vary based on the order's security type. The following provides a comprehensive breakdown of the supported order types for each category:\n - Equity trading: market, limit, stop, stop_limit, trailing_stop.\n - Options trading: market, limit.\n - Options Multileg trading: market, limit.\n - Crypto trading: market, limit, stop_limit.", - "enum": [ - "market", - "limit", - "stop", - "stop_limit", - "trailing_stop" - ], - "example": "market", - "title": "OrderType", - "type": "string" - }, - "OvercontributedIRAAccount": { "properties": { "account_id": { - "description": "The account id", + "description": "Account UUID", + "format": "uuid", "type": "string" }, - "tax_year": { - "description": "The tax year", - "type": "number" - }, - "total_contribution_amount": { - "description": "The total conntribution amount", - "type": "number" - } - }, - "type": "object" - }, - "OwnerDocument": { - "description": "A document associated with an owner of the account", - "example": { - "created_at": "2019-09-30T23:55:31.185998Z", - "document_sub_type": "passport", - "document_type": "identity_verification", - "id": "0d18ae51-3c94-4511-b209-101e1666416b", - "mime_type": "image/jpeg" - }, - "properties": { - "created_at": { + "at": { + "description": "Timestamp of when the event was emitted", "format": "date-time", "type": "string" }, - "document_sub_type": { - "description": "The sub-type of the document. This is a free-form property.", + "cusip": { + "description": "CUSIP the event is associated with, not present when no CUSIP is applicable", + "example": "037833100", "type": "string" }, - "document_type": { - "$ref": "#/components/schemas/OwnerDocumentType" - }, - "id": { - "format": "uuid", + "description": { + "description": "Additional information about the event, empty string if not applicable", + "example": "Example description", "type": "string" }, - "mime_type": { - "type": "string" - } - }, - "required": [ - "id", - "document_type", - "created_at" - ], - "type": "object" - }, - "OwnerDocumentType": { - "description": "The type of the owner document", - "enum": [ - "account_approval_letter", - "address_verification", - "cip_result", - "company_formation", - "date_of_birth_verification", - "entity_operating_document", - "entity_registration", - "hio_declaration_form", - "identity_verification", - "limited_trading_authorization", - "pep_declaration_form", - "tax_id_verification", - "w8ben", - "w9" - ], - "example": "identity_verification", - "type": "string" - }, - "OwnerDocumentUploadRequest": { - "description": "The request to upload a document for an owner of the account", - "example": { - "content": "/9j/Cg==", - "document_sub_type": "passport", - "document_type": "identity_verification", - "mime_type": "image/jpeg" - }, - "properties": { - "content": { - "description": "The base64 string encoding of the document contents. This property is required unless content_data is provided.", - "example": "/9j/Cg==", - "format": "base64", + "entry_type": { + "description": "Type of entry for e.g JNLC, FEE, INT, DIVNRA etc", + "example": "JNLC", "type": "string" }, - "content_data": { - "$ref": "#/components/schemas/W8benDocument" + "event_id": { + "description": "Monotonically increasing 64bit integer", + "type": "integer" }, - "document_sub_type": { - "description": "The specific type of document, e.g. passport. This is a free-form property.", - "example": "passport", + "event_ulid": { + "description": "lexically sortable, monotonically increasing character array", + "format": "ulid", "type": "string" }, - "document_type": { - "$ref": "#/components/schemas/OwnerDocumentType" - }, - "mime_type": { - "description": "This field is required if content is specified. ENUM: application/pdf, image/png, or image/jpeg. If document_type is w8ben then application/json is also accepted", - "example": "image/jpeg", + "id": { + "description": "Record UUID", + "format": "uuid", "type": "string" - } - }, - "required": [ - "document_type", - "content" - ], - "title": "OwnerDocumentUploadRequest", - "type": "object", - "x-examples": { - "example-1": { - "content": "/9j/Cg==", - "document_sub_type": "passport", - "document_type": "identity_verification", - "mime_type": "image/jpeg" - } - } - }, - "Portfolio": { - "properties": { - "cooldown_days": { - "description": "Count of calendar days following a rebalance before a subscription is eligible to trigger another rebalance", - "type": "integer" }, - "created_at": { - "description": "Portfolio creation timestamp", - "type": "string" + "net_amount": { + "description": "Net amount if applicable, 0 otherwise", + "example": 1, + "format": "decimal", + "type": "number" }, - "description": { - "description": "Text to describe portfolio", - "type": "string" + "per_share_amount": { + "description": "Per share amount if applicable", + "example": 0.3, + "format": "decimal", + "type": "number" }, - "id": { - "description": "Portfolio ID", + "price": { + "description": "Price if applicable.", + "example": "0.38921", + "format": "decimal", "type": "string" }, - "name": { - "description": "Name of portfolio", - "type": "string" + "qty": { + "description": "Quantity of the stock affected. 0 for cash events", + "format": "decimal", + "type": "number" }, - "rebalancing_conditions": { - "description": "Rebalancing conditions for portfolio", + "settle_date": { + "description": "Date of settlement if applicable", + "format": "date", "type": "string" }, "status": { - "description": "Current status of portfolio", + "description": "Status of the event. `pending` is an internal/preprocessing status for journal-type NTAs and is only emitted when the subscriber opts in with `include_preprocessing=true`.\n", "enum": [ - "active", - "inactive", - "needs_adjustment" + "executed", + "correct", + "canceled", + "pending" ], + "example": "executed", "type": "string" }, - "updated_at": { - "description": "Portfolio updated timestamp", + "symbol": { + "description": "Symbol the event is associated with, empty string when no symbol is applicable", + "example": "AAPL", "type": "string" }, - "weights": { - "description": "Weight configuration to portfolio. Sum of \"percent\" values in the weights array must be 100.00", - "items": { - "$ref": "#/components/schemas/PortfolioWeights" - }, - "type": "array" + "system_date": { + "description": "Date of the event recorded in the system", + "format": "date", + "type": "string" } }, "required": [ - "cooldown_days" + "account_id", + "at", + "id", + "event_ulid", + "system_date", + "settle_date", + "net_amount", + "entry_type", + "description" ], - "title": "Portfolio", + "title": "NonTradeActivityEvent", "type": "object" }, - "PortfolioHistory": { - "description": "Timeseries data for equity and profit loss information of the account.", + "OAuthTokenRequest": { + "description": "This model is used for both the Issue and Authorize OAuth token routes", + "example": { + "account_id": "0d18ae51-3c94-4511-b209-101e1666416b", + "client_id": "7a3c52a910e1dc2abbb14da2b6b8e711", + "client_secret": "bbb14da2b6b8e7117a3c52a910e1dc2a", + "redirect_uri": "http://localhost", + "scope": "general" + }, "properties": { - "base_value": { - "description": "basis in dollar of the profit loss calculation", - "type": "number" + "account_id": { + "description": "end-user account ID", + "format": "uuid", + "type": "string" }, - "base_value_asof": { - "description": "If included, then it indicates that the base_value is the account's closing\nequity value at this trading date.\n\nIf not specified, then the baseline calculation is done against the earliest returned data item. This could happen for\naccounts without prior closing balances (e.g. new account) or for queries with 1D timeframes, where the first data point\nis used as a reference point.\n", - "example": "2023-10-20", - "format": "date", + "client_id": { + "description": "OAuth client ID", "type": "string" }, - "cashflow": { - "description": "accumulated value in dollar amount as of the end of each time window", - "type": "object" + "client_secret": { + "description": "OAuth client secret", + "type": "string" }, - "equity": { - "description": "equity value of the account in dollar amount as of the end of each time window", - "items": { - "type": "number" - }, - "type": "array" - }, - "profit_loss": { - "description": "profit/loss in dollar from the base value", - "items": { - "type": "number" - }, - "type": "array" - }, - "profit_loss_pct": { - "description": "profit/loss in percentage from the base value", - "example": [ - 0.001, - 0.002 - ], - "items": { - "type": "number" - }, - "type": "array" - }, - "timeframe": { - "description": "time window size of each data element", - "example": "15Min", + "redirect_uri": { + "description": "redirect URI for the OAuth flow", "type": "string" }, - "timestamp": { - "description": "Time of each data element, left-labeled (the beginning of time window).\n\nThe values returned are in [UNIX epoch format](https://en.wikipedia.org/wiki/Unix_time).\n", - "items": { - "type": "integer" - }, - "type": "array" + "scope": { + "description": "scopes requested by the OAuth flow", + "type": "string" } }, "required": [ - "timestamp", - "equity", - "profit_loss", - "profit_loss_pct", - "base_value", - "timeframe" + "client_id", + "client_secret", + "redirect_uri", + "scope", + "account_id" ], - "title": "PortfolioHistory", - "type": "object", - "x-examples": { - "example-cashflows": { - "base_value": 8413.04, - "base_value_asof": "2026-04-06", - "cashflow": { - "CSD": [ - 9.93, - 10.13, - 10.16, - 0 - ], - "DIV": [ - 0, - 0, - 0.63, - 0 - ] - }, - "equity": [ - 8425.21, - 8639.77, - 8766.1, - 8835.31 - ], - "profit_loss": [ - 12.17, - 214.56, - 126.33, - 69.21 - ], - "profit_loss_pct": [ - 0.0014, - 0.0255, - 0.0146, - 0.0079 - ], - "timeframe": "1D", - "timestamp": [ - 1775606400, - 1775692800, - 1775779200, - 1775865600 - ] + "title": "OAuthTokenRequest", + "type": "object" + }, + "OCTActivityV2": { + "allOf": [ + { + "$ref": "#/components/schemas/CommonNTAActivityV2" }, - "example-intraday-query-15min-1d": { - "base_value": 2774.16, - "base_value_asof": "2023-10-18", - "equity": [ - 2773.79, - 2769.04, - 2768.65, - 2765.11, - 2763.03, - 2763.17, - 2763.17, - 2763.47, - 2763.91, - 2768.13, - 2774.98, - 2757.94, - 2757.65, - 2774.54, - 2775.58, - 2775.28, - 2767.9, - 2762.26, - 2762.56, - 2756.99, - 2756.84, - 2752.43, - 2752.13, - 2748.44, - 2751.23, - 2747.54, - 2748.74 - ], - "profit_loss": [ - -0.37, - -5.12, - -5.51, - -9.05, - -11.13, - -10.99, - -10.99, - -10.69, - -10.25, - -6.03, - 0.82, - -16.22, - -16.51, - 0.38, - 1.42, - 1.12, - -6.26, - -11.9, - -11.6, - -17.17, - -17.32, - -21.73, - -22.03, - -25.72, - -22.93, - -26.62, - -25.42 - ], - "profit_loss_pct": [ - -0.0001, - -0.0018, - -0.002, - -0.0033, - -0.004, - -0.004, - -0.004, - -0.0039, - -0.0037, - -0.0022, - 0.0003, - -0.0058, - -0.006, - 0.0001, - 0.0005, - 0.0004, - -0.0023, - -0.0043, - -0.0042, - -0.0062, - -0.0062, - -0.0078, - -0.0079, - -0.0093, - -0.0083, - -0.0096, - -0.0092 + { + "properties": { + "symbol": { + "description": "The symbol of the asset involved with the on chain transaction", + "type": "string" + }, + "transfer_id": { + "description": "The transfer ID of the associated deposit or withdrawal", + "format": "uuid", + "type": "string" + } + }, + "required": [ + "transfer_id", + "symbol" ], - "timeframe": "15Min", - "timestamp": [ - 1697722200, - 1697723100, - 1697724000, - 1697724900, - 1697725800, - 1697726700, - 1697727600, - 1697728500, - 1697729400, - 1697730300, - 1697731200, - 1697732100, - 1697733000, - 1697733900, - 1697734800, - 1697735700, - 1697736600, - 1697737500, - 1697738400, - 1697739300, - 1697740200, - 1697741100, - 1697742000, - 1697742900, - 1697743800, - 1697744700, - 1697745600 - ] + "type": "object" + } + ], + "description": "On chain transaction (blockchain deposit/withdrawal)", + "type": "object" + }, + "OPASNActivityV2": { + "allOf": [ + { + "$ref": "#/components/schemas/CommonOptionsActivityV2" }, - "example-query-1d-7d": { - "base_value": 2784.79, - "equity": [ - 2784.79, - 2794.79, - 2805.46, - 2774.16, - 2748.73 - ], - "profit_loss": [ - 0, - 10.0022, - 10.6692, - -31.2996, - -25.4232 - ], - "profit_loss_pct": [ - 0, - 0.0035, - 0.0074, - -0.0038, - -0.0129 - ], - "timeframe": "1D", - "timestamp": [ - 1697241600, - 1697500800, - 1697587200, - 1697673600, - 1697760000 - ] + { + "type": "object" } - } + ], + "description": "Option assignment", + "type": "object" }, - "PortfolioRun": { - "properties": { - "account_id": { - "description": "Account ID for given run", - "type": "string" + "OPCSHActivityV2": { + "allOf": [ + { + "$ref": "#/components/schemas/CommonOptionsActivityV2" }, - "canceled_at": { - "description": "RFC3339 format", - "type": "string" + { + "type": "object" + } + ], + "description": "Option cash deliverable for non-standard contracts", + "type": "object" + }, + "OPEXCActivityV2": { + "allOf": [ + { + "$ref": "#/components/schemas/CommonOptionsActivityV2" }, - "completed_at": { - "description": "RFC3339 format", - "type": "string" + { + "type": "object" + } + ], + "description": "Option exercise", + "type": "object" + }, + "OPEXPActivityV2": { + "allOf": [ + { + "$ref": "#/components/schemas/CommonOptionsActivityV2" }, - "failed_orders": { - "description": "Array of failed orders for this run", - "items": { - "$ref": "#/components/schemas/Order" - }, - "type": "array" + { + "type": "object" + } + ], + "description": "Option expiry", + "type": "object" + }, + "OPTRDActivityV2": { + "allOf": [ + { + "$ref": "#/components/schemas/CommonOptionsActivityV2" }, - "id": { - "description": "Run ID", + { + "type": "object" + } + ], + "description": "Trading activity that is paired with the assignment/exercise", + "type": "object" + }, + "OathClientResponse": { + "example": { + "client_id": "7a3c52a910e1dc2abbb14da2b6b8e711", + "description": "Sample description", + "live_trading_approved": true, + "name": "TradingApp", + "privacy_policy": "", + "redirect_uri": [ + "http://localhost" + ], + "status": "ACTIVE", + "terms_of_use": "", + "url": "http://test.com" + }, + "examples": [ + { + "client_id": "7a3c52a910e1dc2abbb14da2b6b8e711", + "description": "Sample description", + "live_trading_approved": true, + "name": "TradingApp", + "privacy_policy": "", + "redirect_uri": [ + "http://localhost" + ], + "status": "ACTIVE", + "terms_of_use": "", + "url": "http://test.com" + } + ], + "properties": { + "client_id": { + "description": "OAuth client id", "type": "string" }, - "initiated_from": { - "description": "system or api", - "enum": [ - "system", - "api" - ], + "description": { "type": "string" }, - "orders": { - "description": "Array of executed orders for this run", - "items": { - "$ref": "#/components/schemas/Order" - }, - "type": "array" + "live_trading_approved": { + "example": true, + "type": "boolean" }, - "portfolio_id": { - "description": "Portfolio ID for given run", + "name": { + "description": "Broker name (your name)", "type": "string" }, - "reason": { - "description": "Explainer text in case of failed runs", + "privacy_policy": { + "description": "URL of Privacy Policy", "type": "string" }, - "skipped_orders": { - "description": "Array of skipped order for this run", + "redirect_uri": { "items": { - "$ref": "#/components/schemas/SkippedOrder" + "type": "string" }, "type": "array" }, "status": { - "$ref": "#/components/schemas/PortfolioRunStatus" - }, - "type": { - "description": "full_rebalance or invest_cash", + "description": "ACTIVE or DISABLED", "enum": [ - "full_rebalance", - "invest_cash" + "ACTIVE", + "DISABLED" ], + "example": "ACTIVE", "type": "string" }, - "updated_at": { - "description": "RFC3339 format", + "terms_of_use": { + "description": "URL of Terms of Use", "type": "string" }, - "weights": { - "description": "Considered weighting for this run", - "items": { - "$ref": "#/components/schemas/PortfolioWeights" - }, - "type": "array" + "url": { + "type": "string" } }, - "title": "PortfolioRun", + "title": "OathClientResponse", "type": "object" }, - "PortfolioRunStatus": { - "description": "| Status | Final | Represented State | Notes |\n|:------------------:|:-----:|:--------------------------------------------------------------------------------:|:--------------------------------------------------------------------------------------------------------------------:|\n| QUEUED | No | The run has been queued, waiting for our system to process it. | Runs only executed when the US market is open and there's at least 15 minutes before the market closes. |\n| IN_PROGRESS | No | Portfolio adjustment is in progress. | |\n| CANCELED | Yes | Portfolio run canceled, before being picked up by Alpaca's background processing | |\n| CANCELED_MID_RUN | Yes | Portfolio run canceled while executing. | The portfolio's state is in between the pre-run and post-run state, manual remediation or job re-run is recommended. |\n| ERROR | Yes | There was an error while rebalancing the portfolio. | The portfolio's state is in between the pre-run and post-run state, manual remediation or job re-run is recommended. |\n| TIMEOUT | Yes | A timeout occurred while rebalancing the portfolio. | The portfolio's state is in between the pre-run and post-run state, manual remediation or job re-run is recommended. |\n| COMPLETED_ADJUSTED | Yes | The portfolio has been adjusted | The adjustments have been prepared, but the run details haven't yet updated with the list of resulting orders |\n| COMPLETED_SUCCESS | Yes | The portfolio has been adjusted, run status updated | |", - "enum": [ - "QUEUED", - "IN_PROGRESS", - "CANCELED", - "CANCELED_MID_RUN", - "ERROR", - "TIMEOUT", - "COMPLETED_ADJUSTED", - "COMPLETED_SUCCESS" - ], - "title": "PortfolioRunStatus", + "OnfidoSDKOutcome": { + "description": "\"NOT_STARTED\"\tThe user has not started the SDK flow yet. outcome is set to this default value upon token generation\n\"USER_EXITED\"\tThe user exited the SDK flow\n\"SDK_ERROR\"\tAn error occurred in the SDK flow\n\"USER_COMPLETED\"\tThe user completed the SDK flow", + "title": "OnfidoSDKOutcome", "type": "string" }, - "PortfolioSubscription": { - "properties": { - "account_id": { - "description": "Account ID subscribing to portfolio", - "type": "string" + "OpcaCDIVActivityV2": { + "allOf": [ + { + "$ref": "#/components/schemas/CommonOPCAActivityV2" }, - "created_at": { - "description": "Subscription creation timestamp", - "format": "date-time", - "type": "string" + { + "$ref": "#/components/schemas/CommonCDIVActivityV2" + } + ], + "description": "Options corporate action of cash dividend or return of capital", + "type": "object" + }, + "OpcaFSPLITActivityV2": { + "allOf": [ + { + "$ref": "#/components/schemas/CommonOPCAActivityV2" }, - "id": { - "description": "Subscription ID", - "type": "string" + { + "$ref": "#/components/schemas/CommonSplitActivityV2" }, - "last_rebalanced_at": { - "description": "Last rebalancing event for this subscription. Can be null.", - "type": "string" + { + "properties": { + "due_bill_redemption_date": { + "description": "When due bills related to the split are redeemed", + "format": "date", + "minLength": 1, + "type": "string" + }, + "ex_date": { + "description": "The ex_date for this corporate action", + "format": "date", + "minLength": 1, + "type": "string" + }, + "record_date": { + "description": "The record_date for this corporate action", + "format": "date", + "minLength": 1, + "type": "string" + }, + "symbol": { + "description": "The symbol of the security involved with the activity", + "type": "string" + } + }, + "required": [ + "symbol" + ], + "type": "object" + } + ], + "description": "Options corporate action of forward-splits", + "type": "object" + }, + "OpcaMAActivityV2": { + "allOf": [ + { + "$ref": "#/components/schemas/CommonOPCAActivityV2" }, - "portfolio_id": { - "description": "Portfolio ID for subscription", - "type": "string" + { + "$ref": "#/components/schemas/CommonMAActivityV2" } - }, - "title": "PortfolioSubscription", + ], + "description": "Options corporate action of Mergers & Acquisitions", "type": "object" }, - "PortfolioWeights": { - "properties": { - "percent": { - "description": "Must be a positive value, up to two decimal places", - "type": "string" + "OpcaNCActivityV2": { + "allOf": [ + { + "$ref": "#/components/schemas/CommonOPCAActivityV2" }, - "symbol": { - "description": "Must be fractionable asset. Only provided if type = \"asset\"", - "type": "string" + { + "$ref": "#/components/schemas/CommonNCActivityV2" + } + ], + "description": "Options corporate action of name changes", + "type": "object" + }, + "OpcaRSPLITActivityV2": { + "allOf": [ + { + "$ref": "#/components/schemas/CommonOPCAActivityV2" }, - "type": { - "description": "Possible values of cash or asset", - "type": "string" + { + "$ref": "#/components/schemas/CommonSplitActivityV2" + }, + { + "properties": { + "ex_date": { + "description": "The ex_date for this corporate action", + "format": "date", + "minLength": 1, + "type": "string" + }, + "new_symbol": { + "description": "Symbol of the new security after the split", + "type": "string" + }, + "record_date": { + "description": "The record_date for this corporate action", + "format": "date", + "minLength": 1, + "type": "string" + }, + "symbol": { + "description": "The symbol of the security involved with the activity", + "type": "string" + } + }, + "required": [ + "symbol" + ], + "type": "object" } - }, - "title": "PortfolioWeights", + ], + "description": "Options corporate action of reverse-splits", "type": "object" }, - "Position": { + "OpcaSDIVActivityV2": { + "allOf": [ + { + "$ref": "#/components/schemas/CommonOPCAActivityV2" + }, + { + "$ref": "#/components/schemas/CommonSDIVActivityV2" + } + ], + "description": "Options corporate action of stock dividend", + "type": "object" + }, + "OpcaSPINActivityV2": { + "allOf": [ + { + "$ref": "#/components/schemas/CommonOPCAActivityV2" + }, + { + "$ref": "#/components/schemas/CommonSpinoffActivityV2" + } + ], + "description": "Options corporate action of spin-offs", + "type": "object" + }, + "OpcaUSPLITActivityV2": { + "allOf": [ + { + "$ref": "#/components/schemas/CommonOPCAActivityV2" + }, + { + "$ref": "#/components/schemas/CommonSplitActivityV2" + }, + { + "properties": { + "alternate_cusip": { + "description": "CUSIP for the alternate security after the split", + "type": "string" + }, + "alternate_rate": { + "description": "Ratio of alternate shares received", + "type": "string" + }, + "alternate_symbol": { + "description": "Symbol for the alternate security after the split", + "type": "string" + }, + "effective_date": { + "description": "When the unit split becomes effective", + "format": "date", + "minLength": 1, + "type": "string" + }, + "new_symbol": { + "description": "Symbol of the new security after the unit split", + "type": "string" + }, + "old_symbol": { + "description": "The old symbol of the security involved with the activity", + "type": "string" + } + }, + "required": [ + "old_symbol", + "new_symbol", + "alternate_cusip", + "alternate_symbol", + "alternate_rate", + "effective_date" + ], + "type": "object" + } + ], + "description": "Options corporate action of unit-splits", + "type": "object" + }, + "OptionContract": { "properties": { - "asset_class": { - "$ref": "#/components/schemas/AssetClass" + "close_price": { + "description": "The close price of the option contract.", + "example": "148.38", + "type": "string" }, - "asset_id": { - "description": "Asset ID (For options this represents the option contract ID)", - "example": "904837e3-3b76-47ec-b432-046db621571b", - "format": "uuid", + "close_price_date": { + "description": "The date of the close price data.", + "example": "2023-12-11", + "format": "date", "type": "string" }, - "asset_marginable": { - "description": "Indicates if this asset is marginable", - "type": "boolean" + "deliverables": { + "description": "Represents the deliverables tied to the option contract. While standard contracts entail a single deliverable, non-standard ones can encompass multiple deliverables, each potentially customized with distinct parameters.\nThis array is included in the list contracts response only if the query parameter show_deliverables=true is provided.\n", + "items": { + "$ref": "#/components/schemas/OptionDeliverable" + }, + "type": "array" }, - "avg_entry_price": { - "description": "Average entry price of the position", - "example": "100.0", + "expiration_date": { + "description": "The expiration date of the option contract.", + "example": "2025-06-20", + "format": "date", "type": "string" }, - "avg_entry_swap_rate": { - "description": "The average swap rate of the position. This is only returned for LCT accounts.", - "example": "1.40", - "format": "decimal", + "id": { + "description": "The unique identifier of the option contract.", + "example": "98359ef7-5124-49f3-85ea-5cf02df6defa", "type": "string" }, - "change_today": { - "description": "Percent change from last day price (by a factor of 1)", - "example": "0.0084", - "format": "decimal", + "multiplier": { + "description": "The multiplier of the option contract is crucial for calculating both the trade premium and the extended strike price. In standard contracts, the multiplier is always set to 100.\nFor instance, if a contract is traded at $1.50 and the multiplier is 100, the total amount debited when buying the contract would be $150.00.\nSimilarly, when exercising a call contract, the total cost will be equal to the strike price times the multiplier.", + "example": "100", "type": "string" }, - "cost_basis": { - "description": "Total cost basis", - "example": "500.0", - "format": "decimal", + "name": { + "description": "The name of the option contract.", + "example": "AAPL Jun 20 2025 100 Call", "type": "string" }, - "current_price": { - "description": "Current asset price per share", - "example": "120.0", - "format": "decimal", + "open_interest": { + "description": "The open interest of the option contract.", + "example": "237", "type": "string" }, - "exchange": { - "description": "Exchange name of the asset", - "example": "NASDAQ", + "open_interest_date": { + "description": "The date of the open interest data.", + "example": "2023-12-11", + "format": "date", "type": "string" }, - "lastday_price": { - "description": "Last day's asset price per share based on the closing value of the last trading day", - "example": "119.0", - "format": "decimal", + "ppind": { + "description": "The ppind (Penny Program Indicator) field indicates whether an option contract is eligible for penny price increments,\nwith `true` meaning it is part of the Penny Program and `false` meaning it is not.", + "example": true, + "type": "boolean" + }, + "root_symbol": { + "description": "The root symbol of the option contract.", + "example": "AAPL", "type": "string" }, - "market_value": { - "description": "Total market value of the position", - "example": "600.0", - "format": "decimal", + "size": { + "description": "Represents the number of underlying shares to be delivered in case the contract is exercised/assigned. For standard contracts, this is always 100.\nThis field should **not** be used as a multiplier, specially for non-standard contracts.", + "example": "100", "type": "string" }, - "qty": { - "description": "The number of shares", - "example": "5", + "status": { + "description": "The status of the option contract.", + "enum": [ + "active", + "inactive" + ], + "example": "active", "type": "string" }, - "qty_available": { - "description": "Total number of shares available minus open orders / locked for options covered call", - "example": "5", + "strike_price": { + "description": "The strike price of the option contract.", + "example": "100", "type": "string" }, - "side": { + "style": { + "description": "The style of the option contract.", "enum": [ - "long", - "short" + "american", + "european" ], - "example": "long", - "type": "string" - }, - "swap_rate": { - "description": "The latest swap rate. This is only returned for LCT accounts.", - "example": "1.50", - "format": "decimal", + "example": "american", "type": "string" }, "symbol": { - "description": "Asset symbol", - "example": "AAPL", + "description": "The symbol representing the option contract.", + "example": "AAPL250620C00100000", "type": "string" }, - "unrealized_intraday_pl": { - "description": "Unrealized profit/loss for the day", - "example": "10.0", - "format": "decimal", - "type": "string" + "tradable": { + "description": "Indicates whether the option contract is tradable.", + "example": true, + "type": "boolean" }, - "unrealized_intraday_plpc": { - "description": "Unrealized interday profit/loss percent (by a factor of 1)", - "example": "0.0084", - "format": "decimal", + "type": { + "description": "The type of the option contract.", + "enum": [ + "call", + "put" + ], + "example": "call", "type": "string" }, - "unrealized_pl": { - "description": "Unrealized profit/loss", - "example": "100.0", - "format": "decimal", + "underlying_asset_id": { + "description": "The unique identifier of the underlying asset.", + "example": "b0b6dd9d-8b9b-48a9-ba46-b9d54906e415", "type": "string" }, - "unrealized_plpc": { - "description": "Unrealized profit/loss percent (by a factor of 1)", - "example": "0.20", - "format": "decimal", + "underlying_symbol": { + "description": "The underlying symbol of the option contract.", + "example": "AAPL", "type": "string" - }, - "usd": { - "$ref": "#/components/schemas/USDPosition" } }, "required": [ - "asset_id", + "id", "symbol", - "exchange", - "asset_class", - "avg_entry_price", - "qty", - "qty_available", - "side", - "market_value", - "cost_basis", - "unrealized_pl", - "unrealized_plpc", - "unrealized_intraday_pl", - "unrealized_intraday_plpc", - "current_price", - "lastday_price", - "change_today" + "name", + "status", + "tradable", + "expiration_date", + "underlying_symbol", + "underlying_asset_id", + "type", + "style", + "strike_price", + "multiplier", + "size", + "ppind" ], - "type": "object", - "x-examples": { - "example-1": { - "asset_class": "us_equity", - "asset_id": "93f58d0b-6c53-432d-b8ce-2bad264dbd94", - "asset_marginable": false, - "avg_entry_price": "172.08", - "change_today": "0.0189483657034581", - "cost_basis": "688.32", - "current_price": "172.08", - "exchange": "NASDAQ", - "lastday_price": "168.88", - "market_value": "688.32", - "qty": "4", - "qty_available": "4", - "side": "long", - "symbol": "AAPL", - "unrealized_intraday_pl": "0", - "unrealized_intraday_plpc": "0", - "unrealized_pl": "0", - "unrealized_plpc": "0" - } - } + "type": "object" }, - "PositionClosedResponse": { - "description": "Represents the result of asking the api to close a position.\n\n`body` is the Order used to close out the position.", + "OptionDeliverable": { "properties": { - "body": { - "$ref": "#/components/schemas/Order" + "allocation_percentage": { + "description": "Cost allocation percentage of the deliverable.\nThis is used to determine the cost basis of the equity shares received from the exercise, specially for non-standard contracts with multiple deliverables.\n", + "example": "100", + "type": "string" }, - "status": { - "description": "Http status code for the attempt to close this position", - "type": "integer" + "amount": { + "description": "The deliverable amount. For cash deliverables, this is the cash amount.\nFor standard contract, this is always 100.\nThis field can be null in case the deliverable settlement is delayed and the amount is yet to be determined.\n", + "example": "100", + "type": "string" + }, + "asset_id": { + "description": "Unique identifier of the deliverable asset. For standard contracts, this is equivalent to underlying_asset_id of the contracts.\nThis field is not returned for cash deliverables.\n", + "example": "b0b6dd9d-8b9b-48a9-ba46-b9d54906e415", + "type": "string" + }, + "delayed_settlement": { + "description": "If true, the settlement of the deliverable will be delayed.\nFor instance, in the event of a contract with a delayed deliverable being exercised, both the availability of the deliverable and its settlement may be postponed beyond the typical timeframe.\n", + "example": false, + "type": "boolean" + }, + "settlement_method": { + "description": "Indicates the settlement method that will be used:\n- **BTOB**: Broker to Broker\n- **CADF**: Cash Difference\n- **CAFX**: Cash Fixed\n- **CCC**: Correspondent Clearing Corp\n", + "enum": [ + "BTOB", + "CADF", + "CAFX", + "CCC" + ], + "example": "CCC", + "type": "string" + }, + "settlement_type": { + "description": "Indicates when the deliverable will be settled if the contract is exercised/assigned.\n", + "enum": [ + "T+0", + "T+1", + "T+2", + "T+3", + "T+4", + "T+5" + ], + "example": "T+2", + "type": "string" }, "symbol": { - "description": "Symbol name of the asset", + "description": "Symbol of the deliverable. For standard contracts, this is equivalent to the underlying symbol of the contract.\n", + "example": "AAPL", + "type": "string" + }, + "type": { + "description": "Type of deliverable, indicating whether it's cash or equity. For standard contracts, it is always \"equity\".\n", + "enum": [ + "cash", + "equity" + ], + "example": "equity", "type": "string" } }, "required": [ + "type", "symbol", - "status" + "amount", + "allocation_percentage", + "settlement_type", + "settlement_method", + "delayed_settlement" ], - "title": "PositionClosedResponse", - "type": "object", - "x-examples": { - "example-1": { - "body": { - "asset_class": "us_equity", - "asset_id": "b0b6dd9d-8b9b-48a9-ba46-b9d54906e415", - "canceled_at": null, - "client_order_id": "52f8574c-96d5-49b6-94c1-2570a268434e", - "commision": "1.0", - "created_at": "2022-02-04T16:53:29.53427917Z", - "expired_at": null, - "extended_hours": false, - "failed_at": null, - "filled_at": null, - "filled_avg_price": null, - "filled_qty": "0", - "hwm": null, - "id": "f7f25e89-939a-4587-aaf6-414a6b3c341d", - "legs": null, - "limit_price": null, - "notional": null, - "order_class": "", - "order_type": "market", - "qty": "2", - "replaced_at": null, - "replaced_by": null, - "replaces": null, - "side": "sell", - "status": "accepted", - "stop_price": null, - "submitted_at": "2022-02-04T16:53:29.533738219Z", - "symbol": "AAPL", - "time_in_force": "day", - "trail_percent": null, - "trail_price": null, - "type": "market", - "updated_at": "2022-02-04T16:53:29.53427917Z" - }, - "status": 200, - "symbol": "AAPL" - } - } - }, - "PositionIntent": { - "description": "Represents the desired position strategy.", - "enum": [ - "buy_to_open", - "buy_to_close", - "sell_to_open", - "sell_to_close" - ], - "example": "buy_to_open", - "title": "PositionIntent", - "type": "string" + "type": "object" }, - "RebalancingConditions": { + "OptionsApprovalEvent": { + "description": "This property is included when the account's approved options level changes.\n", "properties": { - "day": { - "description": "Used to specify the rebalancing day for conditions of type = calendar. Only permitted and required for type = calendar. In scenarios when the specified day aligns to a non-trading day, the rebalance will be triggered on the preceding trading day. For type= annually, the value must be passed in MM-DD format. If 02-29 is specified and the current year is not a leap year, the rebalance will occur on the trading day preceding 2/29. For type= quarterly and monthly the value must be an integer between 1 and 31 inclusive. This represents the day of month. For quarterly, rebalancing will trigger on this day in January, March, June, and December. If the specified day is non-existent for the month then the rebalancing will trigger on the preceding trading day. For type = weekly, permitted values are Monday, Tuesday, Wednesday, Thursday, Friday.", - "type": "string" - }, - "percent": { - "description": "Must be a positive value, up to two decimal places. Only permitted and required for type = drift_band. This is the max allowable drift percent from any target weight (+/-)", - "type": "string" - }, - "sub_type": { - "description": "For type = drift_band: absolute or relative. For type = calendar: weekly,monthly, quarterly or annually. For type = on_portfolio_update: there is no subtype", - "type": "string" + "approved_level_from": { + "description": "The approved options level before the change", + "type": "integer" }, - "type": { - "description": "Possible values of drift_band, calendar or on_portfolio_update", - "type": "string" + "approved_level_to": { + "description": "The approved options level after the change", + "type": "integer" } }, - "title": "RebalancingConditions", "type": "object" }, - "ReportsResponse": { + "OptionsApprovalRequest": { "properties": { - "account_no": { + "level": { + "description": "The desired option trading level. 1=Covered Call/Cash-Secured Put, 2=Long Call/Put, 3=Spreads/Straddles.", + "enum": [ + 1, + 2, + 3 + ], + "example": 3, + "type": "integer" + } + }, + "type": "object" + }, + "OptionsApprovalResponse": { + "properties": { + "account_id": { + "description": "The account ID.", + "example": "c8f1ef5d-edc0-4f23-9ee4-378f19cb92a4", + "format": "uuid", "type": "string" }, - "deadline": { - "format": "date", - "type": "string" + "approved_level": { + "description": "The option trading level approved for this request. Only present once the request has completed processiing.\nNote that a subsequent request may be approved for a different level.\n0=Disabled, 1=Covered Call/Cash-Secured Put, 2=Long Call/Put, 3=Spreads/Straddles.\"\n", + "enum": [ + 0, + 1, + 2, + 3 + ], + "example": 3, + "type": "integer" }, - "instant_funding_transfers": { - "items": { - "$ref": "#/components/schemas/InstantFunding" - }, - "type": "array" + "created_at": { + "description": "The time when the request was submitted.", + "example": "2021-03-16T18:38:01.942282Z", + "format": "date-time", + "type": "string" }, - "system_date": { - "format": "date", + "id": { + "description": "The request ID.", + "example": "88b5f678-fef5-447b-af15-f21e367e6d8c", + "format": "uuid", "type": "string" }, - "total_amount_owed": { - "format": "decimal", + "requested_level": { + "description": "The request option trading level. 0=Disabled, 1=Covered Call/Cash-Secured Put, 2=Long Call/Put, 3=Spreads/Straddles.", + "enum": [ + 0, + 1, + 2, + 3 + ], + "example": 3, + "type": "integer" + }, + "requester": { + "description": "The requester of the options approval request.", + "enum": [ + "CORRESPONDENT", + "ALPACA_ADMIN" + ], + "example": "CORRESPONDENT", "type": "string" }, - "total_interest_penalty": { - "format": "decimal", + "status": { + "$ref": "#/components/schemas/OptionsApprovalStatus" + }, + "updated_at": { + "description": "The time when the request was last updated.", + "example": "2021-03-16T18:38:01.942282Z", + "format": "date-time", "type": "string" } }, - "required": [ - "system_date", - "account_no", - "total_amount_owed", - "total_interest_penalty", - "deadline" - ], "type": "object" }, - "RestrictToLiquidationReasons": { - "description": "Reasons why the liquidation only flag was set", + "OptionsApprovalStatus": { + "description": "The request status.\n- PENDING: The request is under review.\n- APPROVED: The request has been successfully approved, the account is now able to trade options.\n- LOWER_LEVEL_APPROVED: The request has been approved for a level lower than the requested one.\n- REJECTED: The request has been rejected.\n", + "enum": [ + "PENDING", + "APPROVED", + "LOWER_LEVEL_APPROVED", + "REJECTED" + ], + "example": "PENDING", + "type": "string" + }, + "OptionsApprovalsList": { + "description": "A list of options approval requests.", "properties": { - "ach_return": { - "description": "Set when an incoming ACH transfer gets rejected", - "type": "boolean" - }, - "position_to_equity_ratio": { - "description": "Set when the position to equity ration exceeds the maximum limit", - "type": "boolean" + "next_page_token": { + "$ref": "#/components/schemas/NextPageToken" }, - "unspecified": { - "description": "Default value for unknown reason", - "type": "boolean" + "options_approvals": { + "description": "An array of options approval requests.", + "items": { + "$ref": "#/components/schemas/OptionsApprovalResponse" + }, + "type": "array" } }, - "title": "RestrictToLiquidationReasons", "type": "object" }, - "ReverseBatchJournalRequest": { + "Order": { "properties": { - "entries": { - "items": { - "properties": { - "amount": { - "description": "Journal amount in USD", - "type": "string" - }, - "description": { - "description": "Journal description, gets returned in the response", - "type": "string" - }, - "from_account": { - "description": "The ID of the from_account that you want to journal from", - "type": "string" - } - }, - "type": "object" - }, - "type": "array" + "asset_class": { + "$ref": "#/components/schemas/AssetClass" }, - "entry_type": { - "description": "Only supports `JNLC` for now", - "enum": [ - "JNLC" - ], + "asset_id": { + "description": "The asset ID (For options this represents the option contract ID)", + "example": "61e69015-8549-4bfd-b9c3-01e75843f47d", + "format": "uuid", "type": "string" }, - "to_account": { - "description": "The destination of funds. Most likely is your Sweep Firm Account", + "cancel_requested_at": { + "description": "Time when cancellation or bust was requested (if applicable)", + "example": "2021-03-16T18:38:01.942282Z", + "format": "date-time", + "type": [ + "string", + "null" + ] + }, + "canceled_at": { + "description": "Can be null", + "example": "2021-03-16T18:38:01.942282Z", + "format": "date-time", + "type": [ + "string", + "null" + ] + }, + "client_order_id": { + "description": "Client unique order ID", + "example": "61e69015-8549-4bfd-b9c3-01e75843f47d", + "maxLength": 128, "type": "string" - } - }, - "type": "object", - "x-examples": { - "Example 1": { - "entries": [ - { - "amount": "10", - "from_account": "8e00606a-c9ac-409a-ba45-f55e8f77984a" - }, - { - "amount": "100", - "from_account": "b9b19618-22dd-4e80-8432-fc9e1ba0b27d" - }, - { - "amount": "100", - "from_account": "c96a5e16-7fca-425a-b67b-0814d064bfc0" - } - ], - "entry_type": "JNLC", - "to_account": "8f8c8cee-2591-4f83-be12-82c659b5e748" - } - } - }, - "ReverseSplitActivityV2": { - "allOf": [ - { - "$ref": "#/components/schemas/CommonSplitStockActivityV2" }, - { - "properties": { - "ex_date": { - "description": "The ex_date for this corporate action", - "format": "date", - "minLength": 1, - "type": "string" - }, - "new_symbol": { - "description": "Symbol of the new security after the split", - "type": "string" - }, - "record_date": { - "description": "The record_date for this corporate action", - "format": "date", - "minLength": 1, - "type": "string" - }, - "symbol": { - "description": "The symbol of the security involved with the activity", - "type": "string" - } - }, - "required": [ - "symbol" - ], - "type": "object" - } - ], - "description": "Reverse stock split", - "type": "object" - }, - "RightsDistributionActivityV2": { - "allOf": [ - { - "$ref": "#/components/schemas/CommonCaActivityV2" - }, - { - "properties": { - "ex_date": { - "description": "The ex_date for this corporate action", - "format": "date", - "minLength": 1, - "type": "string" - }, - "expiration_date": { - "description": "The expiration date for the rights distribution", - "format": "date", - "type": "string" - }, - "new_cusip": { - "description": "The new CUSIP", - "type": "string" - }, - "new_qty": { - "description": "The new quantity", - "type": "string" - }, - "new_symbol": { - "description": "The new symbol", - "type": "string" - }, - "payable_date": { - "description": "The payable_date for this corporate action", - "format": "date", - "minLength": 1, - "type": "string" - }, - "rate": { - "description": "The rate for the rights distribution", - "type": "string" - }, - "record_date": { - "description": "The record_date for this corporate action", - "format": "date", - "minLength": 1, - "type": "string" - }, - "source_cusip": { - "description": "The source CUSIP", - "type": "string" - }, - "source_qty": { - "description": "The source quantity", - "type": "string" - }, - "source_symbol": { - "description": "The source symbol", - "type": "string" - } - }, - "required": [ - "source_cusip", - "source_symbol", - "source_qty", - "new_cusip", - "new_symbol", - "new_qty", - "rate" - ], - "type": "object" - } - ], - "description": "Rights distribution", - "type": "object" - }, - "RightsSubscriptionElectionActivityV2": { - "allOf": [ - { - "$ref": "#/components/schemas/CommonNTAActivityV2" - }, - { - "$ref": "#/components/schemas/CommonVOFSubtypeActivityV2" - } - ], - "type": "object" - }, - "SDIVActivityV2": { - "allOf": [ - { - "$ref": "#/components/schemas/CommonCaActivityV2" - }, - { - "$ref": "#/components/schemas/CommonSDIVActivityV2" - }, - { - "properties": { - "entitled_qty": { - "description": "Quantity of shares entitled to receive the dividend", - "type": "string" - }, - "new_qty": { - "description": "The total number of shares after the dividend", - "type": "string" - }, - "paid_qty": { - "description": "The paid quantity", - "type": "string" - } - }, - "required": [ - "entitled_qty", - "paid_qty", - "new_qty" - ], - "type": "object" - } - ], - "description": "Stock dividend", - "type": "object" - }, - "SettlementAccount": { - "properties": { - "account_number": { - "description": "The account number of the client account to settle\n", + "commission": { + "description": "The dollar value commission for this order.", + "example": "3.14", + "format": "decimal", "type": "string" }, - "amount": { + "commission_bps": { + "deprecated": true, + "description": "**deprecated**: Please use the commission_type = bps instead and set the desired bps value in the `commission` field.\nThe percent commission you want to charge the end user on the order (expressed in bps). Alpaca will convert the order to a notional amount for purposes of calculating commission.\n", + "example": "10", "format": "decimal", "type": "string" }, - "transmitter_info": { - "$ref": "#/components/schemas/TransmitterInfo" - } - }, - "required": [ - "account_number", - "amount", - "transmitter_info" - ], - "type": "object" - }, - "SettlementResponse": { - "description": "A settlement response, either from creation or retrieval\n", - "properties": { - "additional_info": { + "commission_type": { + "$ref": "#/components/schemas/CommissionType" + }, + "created_at": { + "description": "Time when order was entered", + "example": "2021-03-16T18:38:01.942282Z", + "format": "date-time", "type": "string" }, - "asset_class": { - "$ref": "#/components/schemas/JITAssetClass" + "expired_at": { + "description": "Can be null", + "example": "2021-03-16T18:38:01.942282Z", + "format": "date-time", + "type": [ + "string", + "null" + ] }, - "completed_at": { + "extended_hours": { + "example": true, + "type": "boolean" + }, + "failed_at": { + "description": "Can be null", + "example": "2021-03-16T18:38:01.942282Z", "format": "date-time", - "type": "string" + "type": [ + "string", + "null" + ] }, - "created_at": { + "filled_at": { + "description": "Time the order was filled. Can be null if not filled", + "example": "2021-03-16T18:38:01.942282Z", "format": "date-time", - "type": "string" + "type": [ + "string", + "null" + ] }, - "currency": { - "description": "The currency of the settlement. Only applicable to JIT settlements.\n", + "filled_avg_price": { + "description": "Filled average price. Can be 0 until order is processed in case order is passed outside of market hours", + "example": "4.2", + "format": "decimal", + "type": [ + "string", + "null" + ] + }, + "filled_qty": { + "description": "Filled quantity", + "example": "4.2", + "format": "decimal", "type": "string" }, + "hwm": { + "description": "The highest (lowest) market price seen since the trailing stop order was submitted.", + "example": "3.14", + "format": "decimal", + "type": [ + "string", + "null" + ] + }, "id": { + "description": "Order ID generated by Alpaca", + "example": "61e69015-8549-4bfd-b9c3-01e75843f47d", "format": "uuid", "type": "string" }, - "interest_amount": { - "description": "The total interest amount accrued on the transfers included in this settlement. Only applicable for instant funding settlements.\n", + "legs": { + "description": "When querying non-simple order_class orders in a nested style, an array of Order entities associated with this order. Otherwise, null.", + "items": { + "$ref": "#/components/schemas/OrderLeg" + }, + "type": [ + "array", + "null" + ] + }, + "limit_price": { + "description": "Limit price", + "example": "3.14", "format": "decimal", - "type": "string" + "type": [ + "string", + "null" + ] }, - "reason": { - "description": "The reason for failure, if applicable\n", - "type": "string" + "notional": { + "description": "Ordered notional amount. If entered, qty will be null. Can take up to 2 decimal points.", + "example": "4.2", + "format": "decimal", + "type": [ + "string", + "null" + ] }, - "source_account_number": { - "type": "string" + "order_class": { + "$ref": "#/components/schemas/OrderClass" }, - "status": { - "$ref": "#/components/schemas/SettlementStatus" + "order_type": { + "$ref": "#/components/schemas/OrderType" }, - "total_amount": { + "position_intent": { + "$ref": "#/components/schemas/PositionIntent" + }, + "qty": { + "description": "Ordered quantity. If entered, notional will be null. Can take up to 2 decimal points.", + "example": "4.2", "format": "decimal", - "type": "string" + "type": [ + "string", + "null" + ] }, - "updated_at": { + "replaced_at": { + "description": "Can be null", + "example": "2021-03-16T18:38:01.942282Z", "format": "date-time", - "type": "string" - } - }, - "required": [ - "id", - "total_amount", - "status", - "created_at", - "updated_at" - ], - "type": "object" - }, - "SettlementStatus": { - "description": "Values:\n * `PENDING`: Created and waiting to be processed\n * `AWAITING_ADDITIONAL_FUNDS`: Waiting for additional funds to be deposited\n * `COMPLETED`: All transactions are reconciled\n * `FAILED`: Attempt to process this settlement has failed. If this is observed\n additional remittance information may be required, or one of the transfers being settled\n accrued additional interest since the settlement was created.\n", - "enum": [ - "PENDING", - "AWAITING_ADDITIONAL_FUNDS", - "COMPLETED", - "FAILED" - ], - "type": "string" - }, - "SettlementTransfer": { - "properties": { - "instant_transfer_id": { + "type": [ + "string", + "null" + ] + }, + "replaced_by": { + "description": "The order ID that this order was replaced by. (Can be null)", + "example": "61e69015-8549-4bfd-b9c3-01e75843f47d", "format": "uuid", - "type": "string" + "type": [ + "string", + "null" + ] }, - "transmitter_info": { - "$ref": "#/components/schemas/TransmitterInfo" - } - }, - "required": [ - "instant_transfer_id", - "transmitter_info" - ], - "type": "object" - }, - "SettlementsResponse": { - "description": "Response to a successful settlements request\n", - "properties": { - "settlements": { - "items": { - "$ref": "#/components/schemas/SettlementResponse" - }, - "type": "array" - } - }, - "required": [ - "settlements" - ], - "type": "object" - }, - "SkippedOrder": { - "description": "Skipped orders model contains information for such orders that the rebalancing engine didn't send to our order system due to some validation issues.", - "properties": { - "currency": { - "description": "Currency of the order", - "type": "string" + "replaces": { + "description": "The order ID that this order replaces. (Can be null)", + "example": "61e69015-8549-4bfd-b9c3-01e75843f47d", + "format": "uuid", + "type": [ + "string", + "null" + ] }, - "notional": { - "description": "Notional value of the order", - "type": "string" + "side": { + "$ref": "#/components/schemas/OrderSide" }, - "reason": { - "description": "Reason for the order being skipped", + "status": { + "$ref": "#/components/schemas/OrderStatus" + }, + "stop_price": { + "description": "Stop price", + "example": "3.14", + "format": "decimal", + "type": [ + "string", + "null" + ] + }, + "submitted_at": { + "description": "Time the order was submitted for execution or, if not yet submitted the created_at time. Because orders are submitted for execution asynchronous to database updates, at times this may be after the created_at time.", + "example": "2021-03-16T18:38:01.942282Z", + "format": "date-time", "type": "string" }, - "reason_details": { - "description": "Formatted error message with the cause of the skip", + "swap_fee_bps": { + "description": "Fee in basis points on top swap rate charged by the correspondent on every order", "type": "string" }, - "side": { - "description": "Side of the order (buy, sell, sell_short)", + "swap_rate": { + "description": "Swap rate is the exchange rate (without mark-up) used to convert the price into local currency or crypto asset", "type": "string" }, "symbol": { - "description": "Symbol for which the adjustment was skipped", + "description": "The asset symbol", + "example": "AALP", + "type": "string" + }, + "time_in_force": { + "$ref": "#/components/schemas/TimeInForce" + }, + "trail_percent": { + "description": "The percent value away from the high water mark for trailing stop orders.", + "example": "5.0", + "format": "decimal", + "type": [ + "string", + "null" + ] + }, + "trail_price": { + "description": "The dollar value away from the high water mark for trailing stop orders.", + "example": "3.14", + "format": "decimal", + "type": [ + "string", + "null" + ] + }, + "type": { + "$ref": "#/components/schemas/OrderType" + }, + "updated_at": { + "description": "Time of most recent change to the order", + "example": "2021-03-16T18:38:01.942282Z", + "format": "date-time", "type": "string" + }, + "usd": { + "description": "Nested object to encompass the USD equivalent fields for the local currency fields", + "type": "object" } }, "required": [ - "symbol", - "reason", - "reason_details" + "id", + "symbol" ], - "title": "SkippedOrder", "type": "object" }, - "SortOrder": { + "OrderClass": { + "description": "The order classes supported by Alpaca vary based on the order's security type. The following provides a comprehensive breakdown of the supported order classes for each category:\n - Equity trading: simple (or \"\"), oco, oto, bracket.\n - Options trading:\n - simple (or \"\")\n - mleg (required for multi-leg complex option strategies)\n - Crypto trading: simple (or \"\").", "enum": [ - "ASC", - "DESC" + "simple", + "bracket", + "oco", + "oto", + "mleg", + "" ], + "example": "", "type": "string" }, - "SpinoffActivityV2": { - "allOf": [ - { - "$ref": "#/components/schemas/CommonCaActivityV2" + "OrderClosedResponse": { + "description": "Represents the result of asking the api to cancel an Order.\n", + "properties": { + "body": { + "$ref": "#/components/schemas/Order" }, - { - "$ref": "#/components/schemas/CommonSpinoffActivityV2" + "id": { + "description": "UUID of the order that was canceled", + "format": "uuid", + "type": "string" }, - { - "properties": { - "new_qty": { - "description": "The new quantity", - "type": "string" - }, - "source_qty": { - "description": "The source quantity", - "type": "string" - } - }, - "required": [ - "source_qty", - "new_qty" - ], - "type": "object" + "status": { + "description": "Http status code for the attempt to close this Order", + "type": "integer" } + }, + "required": [ + "id", + "status" ], - "description": "Spinoff", + "title": "OrderClosedResponse", "type": "object" }, - "StatusFundingEvent": { - "description": "Represents a change in a Funding entity's status, sent over the events streaming api. Currently, the suppported entities are: bank relationships, bank wires, transfers and funding wallets.", + "OrderLeg": { "properties": { - "account_id": { - "description": "Account UUID", + "asset_class": { + "$ref": "#/components/schemas/AssetClass" + }, + "asset_id": { + "description": "The asset ID (For options this represents the option contract ID)", + "example": "61e69015-8549-4bfd-b9c3-01e75843f47d", "format": "uuid", - "minLength": 1, "type": "string" }, - "at": { - "description": "Timestamp of when the transfer status changed", - "minLength": 1, - "type": "string" + "canceled_at": { + "description": "Can be null", + "example": "2021-03-16T18:38:01.942282Z", + "format": "date-time", + "type": [ + "string", + "null" + ] }, - "correspondent": { - "description": "Correspondent's code", - "format": "ABCD", - "maxLength": 4, - "minLength": 1, + "client_order_id": { + "description": "Client unique order ID", + "example": "61e69015-8549-4bfd-b9c3-01e75843f47d", + "maxLength": 128, "type": "string" }, - "entity_id": { - "description": "Entity's UUID", - "format": "uuid", - "minLength": 1, + "commission": { + "description": "The dollar value commission you want to charge the end user.", + "example": "3.14", + "format": "decimal", "type": "string" }, - "entity_type": { - "description": "Valid values are BankRelationship, WireBank, Transfer and FundingWallet.", - "format": "uuid", - "minLength": 1, + "commission_bps": { + "deprecated": true, + "description": "**deprecated**: Please use the commission_type = bps instead and set the desired bps value in the `commission` field.\nThe percent commission you want to charge the end user on the order (expressed in bps). Alpaca will convert the order to a notional amount for purposes of calculating commission.\n", + "example": "10", + "format": "decimal", "type": "string" }, - "event_id": { - "description": "lexically sortable, monotonically increasing character array", - "format": "ulid", - "minLength": 1, - "type": "string" + "commission_type": { + "$ref": "#/components/schemas/CommissionType" }, - "reason": { - "description": "Used when an a bank relationship is rejected, a wire bank is canceled, etc.", + "created_at": { + "description": "Time when order was entered", + "example": "2021-03-16T18:38:01.942282Z", + "format": "date-time", "type": "string" }, - "status_from": { - "description": "Valid values are based on entity type:\n- BankRelationship:\n - QUEUED\n - CANCEL_REQUESTED\n - CANCEL_SENT\n - CANCEL_FAILED\n - PENDING\n - SENT_TO_CLEARING\n - APPROVED\n - CANCELED\n - REJECTED\n- WireBank:\n - QUEUED\n - SENT_TO_CLEARING\n - APPROVED\n - CANCELED\n - REJECTED\n- Transfer:\n - QUEUED\n - APPROVAL_PENDING\n - CANCELED\n - EXPIRED\n - APPROVED\n - REJECTED\n - SENT_TO_CLEARING\n - COMPLETE\n - RETURNED\n- FundingWallet:\n - active\n - pending\n", - "type": "string" + "expired_at": { + "description": "Can be null", + "example": "2021-03-16T18:38:01.942282Z", + "format": "date-time", + "type": [ + "string", + "null" + ] }, - "status_to": { - "description": "Valid values are based on entity type:\n- BankRelationship:\n - QUEUED\n - CANCEL_REQUESTED\n - CANCEL_SENT\n - CANCEL_FAILED\n - PENDING\n - SENT_TO_CLEARING\n - APPROVED\n - CANCELED\n - REJECTED\n- WireBank:\n - QUEUED\n - SENT_TO_CLEARING\n - APPROVED\n - CANCELED\n - REJECTED\n- Transfer:\n - QUEUED\n - APPROVAL_PENDING\n - CANCELED\n - EXPIRED\n - APPROVED\n - REJECTED\n - SENT_TO_CLEARING\n - COMPLETE\n - RETURNED\n- FundingWallet:\n - active\n - pending\n", - "minLength": 1, - "type": "string" - } - }, - "required": [ - "event_id", - "at", - "account_id", - "correspondent", - "entity_id", - "entity_type", - "status_to" - ], - "title": "StatusFundingEvent", - "type": "object", - "x-examples": { - "example-1": { - "account_id": "8e00606a-c9ac-409a-ba45-f55e8f77984a", - "at": "2025-04-11T19:52:24.066998Z", - "correspondent": "LPCA", - "entity_id": "c4ed4206-697b-4859-ab71-b9de6649859d", - "entity_type": "BankRelationship", - "event_id": "01F7VQQ782DM57SJNWAYMD14J9", - "status_from": null, - "status_to": "QUEUED" + "extended_hours": { + "example": true, + "type": "boolean" }, - "example-2": { - "account_id": "8e00606a-c9ac-409a-ba45-f55e8f77984a", - "at": "2025-04-11T19:52:24.066998Z", - "correspondent": "LPCA", - "entity_id": "c4ed4206-697b-4859-ab71-b9de6649859d", - "entity_type": "BankRelationship", - "event_id": "01F7VQQ782DM57SJNWAYMD14J9", - "reason": "bank account owner name does not match brokerage account name", - "status_from": "QUEUED", - "status_to": "REJECTED" - } - } - }, - "StreetAddress": { - "example": "20 N San Mateo Dr", - "type": "string" - }, - "SystemEventV2": { - "description": "Represents that system event had occurred and sent over the events streaming api.\n", - "properties": { - "at": { - "description": "Timestamp of the event", + "failed_at": { + "description": "Can be null", + "example": "2021-03-16T18:38:01.942282Z", "format": "date-time", - "type": "string" + "type": [ + "string", + "null" + ] }, - "description": { - "description": "The human readable description of the system event", - "type": "string" + "filled_at": { + "description": "Time the order was filled. Can be null if not filled", + "example": "2021-03-16T18:38:01.942282Z", + "format": "date-time", + "type": [ + "string", + "null" + ] }, - "event_id": { - "description": "lexically sortable, monotonically increasing character array", - "format": "ulid", - "type": "string" + "filled_avg_price": { + "description": "Filled average price. Can be 0 until order is processed in case order is passed outside of market hours", + "example": "4.2", + "format": "decimal", + "type": [ + "string", + "null" + ] }, - "system_date": { - "description": "the system date to which this system event belongs to", - "format": "date", + "filled_qty": { + "description": "Filled quantity", + "example": "4.2", + "format": "decimal", "type": "string" }, - "type": { - "description": "the machine readable type of the system event", - "enum": [ - "eod_balances_ready", - "eod_positions_ready" - ], - "type": "string" - } - }, - "required": [ - "at", - "event_id", - "type", - "system_date", - "description" - ], - "title": "SystemEvent", - "type": "object", - "x-examples": { - "example-1": { - "$ref": "#/components/examples/SystemEventV2" - } - } - }, - "TaxIdType": { - "description": "Required if `tax_id` is set.\n\nAn Enum of the various kinds of Tax ID formats Alpaca supports.\n\nPossible Values are:\n\n\n- **USA_SSN**\nUSA Social Security Number\n\n- **USA_ITIN**\nUSA Individual Taxpayer Identification Number\n\n- **ARG_AR_CUIT**\nArgentina CUIT\n\n- **AUS_TFN**\nAustralian Tax File Number\n\n- **AUS_ABN**\nAustralian Business Number\n\n- **BOL_NIT**\nBolivia NIT\n\n- **BRA_CPF**\nBrazil CPF\n\n- **CHL_RUT**\nChile RUT\n\n- **COL_NIT**\nColombia NIT\n\n- **CRI_NITE**\nCosta Rica NITE\n\n- **DEU_TAX_ID**\nGermany Tax ID (Identifikationsnummer)\n\n- **DOM_RNC**\nDominican Republic RNC\n\n- **ECU_RUC**\nEcuador RUC\n\n- **FRA_SPI**\nFrance SPI (Reference Tax Number)\n\n- **GBR_UTR**\nUK UTR (Unique Taxpayer Reference)\n\n- **GBR_NINO**\nUK NINO (National Insurance Number)\n\n- **GTM_NIT**\nGuatemala NIT\n\n- **HND_RTN**\nHonduras RTN\n\n- **HUN_TIN**\nHungary TIN Number\n\n- **IDN_KTP**\nIndonesia KTP\n\n- **IND_PAN**\nIndia PAN Number\n\n- **ISR_TAX_ID**\nIsrael Tax ID (Teudat Zehut)\n\n- **ITA_TAX_ID**\nItaly Tax ID (Codice Fiscale)\n\n- **JPN_TAX_ID**\nJapan Tax ID (Koijin Bango)\n\n- **MEX_RFC**\nMexico RFC\n\n- **NIC_RUC**\nNicaragua RUC\n\n- **NLD_TIN**\nNetherlands TIN Number\n\n- **PAN_RUC**\nPanama RUC\n\n- **PER_RUC**\nPeru RUC\n\n- **PRY_RUC**\nParaguay RUC\n\n- **SGP_NRIC**\nSingapore NRIC\n\n- **SGP_FIN**\nSingapore FIN\n\n- **SGP_ASGD**\nSingapore ASGD\n\n- **SGP_ITR**\nSingapore ITR\n\n- **SLV_NIT**\nEl Salvador NIT\n\n- **SWE_TAX_ID**\nSweden Tax ID (Personnummer)\n\n- **URY_RUT**\nUruguay RUT\n\n- **VEN_RIF**\nVenezuela RIF\n\n- **NATIONAL_ID**\nNational ID number, if a tax ID number is not available\n\n- **PASSPORT**\nPassport number, if a tax ID number is not available\n\n- **PERMANENT_RESIDENT**\nPermanent resident number, if a tax ID number is not available\n\n- **DRIVER_LICENSE**\nDriver's license number, if a tax ID number is not available\n\n- **OTHER_GOV_ID**\nOther government issued identifier, if a tax ID number is not available\n\n- **NOT_SPECIFIED**\nOther Tax IDs", - "enum": [ - "USA_SSN", - "USA_ITIN", - "ARG_AG_CUIT", - "AUS_TFN", - "AUS_ABN", - "BOL_NIT", - "BRA_CPF", - "CHL_RUT", - "COL_NIT", - "CRI_NITE", - "DEU_TAX_ID", - "DOM_RNC", - "ECU_RUC", - "FRA_SPI", - "GBR_UTR", - "GBR_NINO", - "GTM_NIT", - "HND_RTN", - "HUN_TIN", - "IDN_KTP", - "IND_PAN", - "ISR_TAX_ID", - "ITA_TAX_ID", - "JPN_TAX_ID", - "MEX_RFC", - "NIC_RUC", - "NLD_TIN", - "PAN_RUC", - "PER_RUC", - "PRY_RUC", - "SGP_NRIC", - "SGP_FIN", - "SGP_ASGD", - "SGP_ITR", - "SLV_NIT", - "SWE_TAX_ID", - "URY_RUT", - "VEN_RIF", - "NATIONAL_ID", - "PASSPORT", - "PERMANENT_RESIDENT", - "DRIVER_LICENSE", - "OTHER_GOV_ID", - "NOT_SPECIFIED" - ], - "example": "USA_SSN", - "title": "TaxIdType", - "type": "string" - }, - "TenderOfferActivityV2": { - "allOf": [ - { - "$ref": "#/components/schemas/CommonNTAActivityV2" + "hwm": { + "description": "The highest (lowest) market price seen since the trailing stop order was submitted.", + "example": "3.14", + "format": "decimal", + "type": [ + "string", + "null" + ] }, - { - "$ref": "#/components/schemas/CommonVOFSubtypeActivityV2" - } - ], - "type": "object" - }, - "TimeInForce": { - "description": "The Time-In-Force values supported by Alpaca vary based on the order's security type. Here is a breakdown of the supported TIFs for each specific security type:\n- Equity trading: day, gtc, opg, cls, ioc, fok.\n- Options trading: day.\n- Crypto trading: gtc, ioc.\n\nBelow are the descriptions of each TIF:\n- day:\n A day order is eligible for execution only on the day it is live. By default, the order is only valid during Regular Trading Hours (9:30am - 4:00pm ET). If unfilled after the closing auction, it is automatically canceled. If submitted after the close, it is queued and submitted the following trading day. However, if marked as eligible for extended hours, the order can also execute during supported extended hours.\n\n- gtc:\n The order is good until canceled. Non-marketable GTC limit orders are subject to price adjustments to offset corporate actions affecting the issue. We do not currently support Do Not Reduce (DNR) orders to opt out of such price adjustments.\n\n- opg:\n Use this TIF with a market/limit order type to submit \"market on open\" (MOO) and \"limit on open\" (LOO) orders. This order is eligible to execute only in the market opening auction. Any unfilled orders after the open will be cancelled. OPG orders submitted after 9:28am but before 7:00pm ET will be rejected. OPG orders submitted after 7:00pm will be queued and routed to the following day's opening auction. On open/on close orders are routed to the primary exchange. Such orders do not necessarily execute exactly at 9:30am / 4:00pm ET but execute per the exchange's auction rules.\n\n- cls:\n Use this TIF with a market/limit order type to submit \"market on close\" (MOC) and \"limit on close\" (LOC) orders. This order is eligible to execute only in the market closing auction. Any unfilled orders after the close will be cancelled. CLS orders submitted after 3:50pm but before 7:00pm ET will be rejected. CLS orders submitted after 7:00pm will be queued and routed to the following day's closing auction.\n\n- ioc:\n An Immediate Or Cancel (IOC) order requires all or part of the order to be executed immediately. Any unfilled portion of the order is canceled. Most market makers who receive IOC orders will attempt to fill the order on a principal basis only, and cancel any unfilled balance. On occasion, this can result in the entire order being cancelled if the market maker does not have any existing inventory of the security in question.\n\n- fok:\n A Fill or Kill (FOK) order is only executed if the entire order quantity can be filled, otherwise the order is canceled.", - "enum": [ - "day", - "gtc", - "opg", - "cls", - "ioc", - "fok" - ], - "example": "gtc", - "title": "TimeInForce", - "type": "string" - }, - "TokenizationIssuer": { - "description": "The tokenized asset's issuer", - "enum": [ - "xstocks", - "st0x" - ], - "example": "xstocks", - "title": "TokenizationIssuer", - "type": "string" - }, - "TokenizationMintCallback": { - "properties": { - "account_id": { - "description": "Alpaca account ID (UUID) of the Authorized Participant. Exactly one of `client_id` or `account_id` must be provided, matching the identifier used on the original mint request.", + "id": { + "description": "Order ID generated by Alpaca", + "example": "61e69015-8549-4bfd-b9c3-01e75843f47d", "format": "uuid", "type": "string" }, - "client_id": { - "description": "Customer's client identifier on the issuer's platform. Exactly one of `client_id` or `account_id` must be provided, matching the identifier used on the original mint request.", - "type": "string" + "legs": { + "description": "Always null for an order leg; legs are not nested beyond one level. An empty array is accepted for generated-client compatibility.", + "example": null, + "items": { + "type": "object" + }, + "maxItems": 0, + "type": [ + "array", + "null" + ] }, - "network": { - "$ref": "#/components/schemas/TokenizationNetwork" + "limit_price": { + "description": "Limit price", + "example": "3.14", + "format": "decimal", + "type": [ + "string", + "null" + ] }, - "tokenization_request_id": { - "description": "Unique identifier of the tokenization request", - "format": "uuid", - "type": "string" + "notional": { + "description": "Ordered notional amount. If entered, qty will be null. Can take up to 2 decimal points.", + "example": "4.2", + "format": "decimal", + "type": [ + "string", + "null" + ] }, - "tx_hash": { - "description": "Transaction hash of the completed request on the blockchain", - "type": "string" + "order_class": { + "$ref": "#/components/schemas/OrderClass" }, - "wallet_address": { - "description": "The wallet address to receive the tokenized asset", - "type": "string" - } - }, - "required": [ - "tokenization_request_id", - "tx_hash" - ], - "title": "TokenizationMintCallback", - "type": "object" - }, - "TokenizationMintRequest": { - "properties": { - "issuer": { - "$ref": "#/components/schemas/TokenizationIssuer" + "order_type": { + "$ref": "#/components/schemas/OrderType" }, - "network": { - "$ref": "#/components/schemas/TokenizationNetwork" + "position_intent": { + "$ref": "#/components/schemas/PositionIntent" }, "qty": { - "description": "The underlying quantity to convert into the tokenized asset. It can be fractional.", - "type": "string" + "description": "Ordered quantity. If entered, notional will be null. Can take up to 2 decimal points.", + "example": "4.2", + "format": "decimal", + "type": [ + "string", + "null" + ] }, - "underlying_symbol": { - "description": "The underlying asset symbol", - "type": "string" + "replaced_at": { + "description": "Can be null", + "example": "2021-03-16T18:38:01.942282Z", + "format": "date-time", + "type": [ + "string", + "null" + ] }, - "wallet_address": { - "description": "The wallet address to receive the tokenized asset", - "type": "string" - } - }, - "required": [ - "underlying_symbol", - "qty", - "issuer", - "network", - "wallet_address" - ], - "title": "TokenizationMintRequest", - "type": "object" - }, - "TokenizationMintResponse": { - "properties": { - "created_at": { - "format": "date-time", - "type": "string" + "replaced_by": { + "description": "The order ID that this order was replaced by. (Can be null)", + "example": "61e69015-8549-4bfd-b9c3-01e75843f47d", + "format": "uuid", + "type": [ + "string", + "null" + ] }, - "issuer": { - "$ref": "#/components/schemas/TokenizationIssuer" + "replaces": { + "description": "The order ID that this order replaces. (Can be null)", + "example": "61e69015-8549-4bfd-b9c3-01e75843f47d", + "format": "uuid", + "type": [ + "string", + "null" + ] }, - "network": { - "$ref": "#/components/schemas/TokenizationNetwork" + "side": { + "$ref": "#/components/schemas/OrderSide" }, - "qty": { - "description": "The quantity to convert for this tokenization request. It can be fractional.", + "status": { + "$ref": "#/components/schemas/OrderStatus" + }, + "stop_price": { + "description": "Stop price", + "example": "3.14", + "format": "decimal", + "type": [ + "string", + "null" + ] + }, + "submitted_at": { + "description": "Time the order was submitted for execution or, if not yet submitted the created_at time. Because orders are submitted for execution asynchronous to database updates, at times this may be after the created_at time.", + "example": "2021-03-16T18:38:01.942282Z", + "format": "date-time", "type": "string" }, - "status": { - "$ref": "#/components/schemas/TokenizationRequestStatus" + "swap_fee_bps": { + "description": "Fee in basis points on top swap rate charged by the correspondent on every order", + "type": "string" }, - "token_symbol": { - "description": "The tokenized asset symbol", + "swap_rate": { + "description": "Swap rate is the exchange rate (without mark-up) used to convert the price into local currency or crypto asset", "type": "string" }, - "tokenization_request_id": { - "description": "Unique identifier of the tokenization request set by Alpaca", + "symbol": { + "description": "The asset symbol", + "example": "AALP", "type": "string" }, - "underlying_symbol": { - "description": "The underlying asset symbol", + "time_in_force": { + "$ref": "#/components/schemas/TimeInForce" + }, + "trail_percent": { + "description": "The percent value away from the high water mark for trailing stop orders.", + "example": "5.0", + "format": "decimal", + "type": [ + "string", + "null" + ] + }, + "trail_price": { + "description": "The dollar value away from the high water mark for trailing stop orders.", + "example": "3.14", + "format": "decimal", + "type": [ + "string", + "null" + ] + }, + "type": { + "$ref": "#/components/schemas/OrderType" + }, + "updated_at": { + "description": "Time of most recent change to the order", + "example": "2021-03-16T18:38:01.942282Z", + "format": "date-time", "type": "string" + }, + "usd": { + "description": "Nested object to encompass the USD equivalent fields for the local currency fields", + "type": "object" } }, "required": [ - "tokenization_request_id", - "status", - "underlying_symbol", - "token_symbol", - "qty", - "created_at", - "issuer", - "network" + "id", + "symbol" ], - "title": "TokenizationMintResponse", "type": "object" }, - "TokenizationNetwork": { - "description": "The token's blockchain network", + "OrderSide": { + "description": "Represents what side of the transaction an order was on. Required for all order classes except for `mleg`.", "enum": [ - "solana", - "arbitrum", - "ethereum", - "binance", - "base", - "ton", - "tron", - "mantle" + "buy", + "sell", + "buy_minus", + "sell_plus", + "sell_short", + "sell_short_exempt", + "undisclosed", + "cross", + "cross_short" ], - "example": "solana", - "title": "TokenizationNetwork", + "example": "buy", "type": "string" }, - "TokenizationRedeemRequest": { + "OrderStatus": { + "enum": [ + "new", + "partially_filled", + "filled", + "done_for_day", + "canceled", + "expired", + "replaced", + "pending_cancel", + "pending_replace", + "accepted", + "pending_new", + "accepted_for_bidding", + "stopped", + "rejected", + "suspended", + "calculated" + ], + "example": "filled", + "type": "string" + }, + "OrderType": { + "description": "The order types supported by Alpaca vary based on the order's security type. The following provides a comprehensive breakdown of the supported order types for each category:\n - Equity trading: market, limit, stop, stop_limit, trailing_stop.\n - Options trading: market, limit, stop, stop_limit.\n - Options Multileg trading: market, limit.\n - Crypto trading: market, limit, stop_limit.", + "enum": [ + "market", + "limit", + "stop", + "stop_limit", + "trailing_stop" + ], + "example": "market", + "title": "OrderType", + "type": "string" + }, + "OvercontributedIRAAccount": { "properties": { "account_id": { - "description": "Alpaca account ID (UUID) of the Authorized Participant. Exactly one of `client_id` or `account_id` must be provided to identify the Authorized Participant whose account will receive the underlying asset.", - "format": "uuid", - "type": "string" - }, - "client_id": { - "description": "Customer's client identifier on the issuer's platform. Exactly one of `client_id` or `account_id` must be provided to identify the Authorized Participant whose account will receive the underlying asset.", - "type": "string" - }, - "issuer_request_id": { - "description": "Unique identifier of the redemption request set by the issuer", - "type": "string" - }, - "network": { - "$ref": "#/components/schemas/TokenizationNetwork" - }, - "qty": { - "description": "The quantity to convert into the underlying asset. It can be fractional.", - "type": "string" - }, - "token_symbol": { - "description": "The tokenized asset symbol", - "type": "string" - }, - "tx_hash": { - "description": "Transaction hash of the completed request on the blockchain", + "description": "The account id", "type": "string" }, - "underlying_symbol": { - "description": "The underlying asset symbol", - "type": "string" + "tax_year": { + "description": "The tax year", + "type": "number" }, - "wallet_address": { - "description": "The address where the redeemed tokens were originally held", - "type": "string" + "total_contribution_amount": { + "description": "The total conntribution amount", + "type": "number" } }, - "required": [ - "issuer_request_id", - "underlying_symbol", - "token_symbol", - "qty", - "network", - "wallet_address", - "tx_hash" - ], - "title": "TokenizationRedeemRequest", "type": "object" }, - "TokenizationRedeemResponse": { + "OwnerDocument": { + "description": "A document associated with an owner of the account", + "example": { + "created_at": "2019-09-30T23:55:31.185998Z", + "document_sub_type": "passport", + "document_type": "identity_verification", + "id": "0d18ae51-3c94-4511-b209-101e1666416b", + "mime_type": "image/jpeg" + }, "properties": { "created_at": { "format": "date-time", "type": "string" }, - "issuer": { - "$ref": "#/components/schemas/TokenizationIssuer" - }, - "issuer_request_id": { - "description": "Unique identifier of the redemption request set by the issuer", - "type": "string" - }, - "network": { - "$ref": "#/components/schemas/TokenizationNetwork" - }, - "qty": { - "description": "The quantity to convert into the underlying asset. It can be fractional.", - "type": "string" - }, - "status": { - "$ref": "#/components/schemas/TokenizationRequestStatus" - }, - "token_symbol": { - "description": "The tokenized asset symbol", - "type": "string" - }, - "tokenization_request_id": { - "description": "Unique identifier of the tokenization request set by Alpaca", - "type": "string" - }, - "tx_hash": { - "description": "Transaction hash of the completed request on the blockchain", + "document_sub_type": { + "description": "The sub-type of the document. This is a free-form property.", "type": "string" }, - "type": { - "$ref": "#/components/schemas/TokenizationRequestType" + "document_type": { + "$ref": "#/components/schemas/OwnerDocumentType" }, - "underlying_symbol": { - "description": "The underlying asset symbol", + "id": { + "format": "uuid", "type": "string" }, - "wallet_address": { - "description": "The address where the redeemed tokens were originally held", + "mime_type": { "type": "string" } }, "required": [ - "tokenization_request_id", - "issuer_request_id", - "created_at", - "type", - "status", - "underlying_symbol", - "token_symbol", - "qty", - "issuer", - "network", - "wallet_address", - "tx_hash" + "id", + "document_type", + "created_at" ], - "title": "TokenizationRedeemResponse", "type": "object" }, - "TokenizationRequest": { + "OwnerDocumentType": { + "description": "The type of the owner document", + "enum": [ + "account_approval_letter", + "address_verification", + "cip_result", + "company_formation", + "date_of_birth_verification", + "entity_operating_document", + "entity_registration", + "hio_declaration_form", + "identity_verification", + "limited_trading_authorization", + "pep_declaration_form", + "tax_id_verification", + "w8ben", + "w9" + ], + "example": "identity_verification", + "type": "string" + }, + "OwnerDocumentUploadRequest": { + "description": "The request to upload a document for an owner of the account", + "example": { + "content": "/9j/Cg==", + "document_sub_type": "passport", + "document_type": "identity_verification", + "mime_type": "image/jpeg" + }, + "examples": [ + { + "content": "/9j/Cg==", + "document_sub_type": "passport", + "document_type": "identity_verification", + "mime_type": "image/jpeg" + } + ], "properties": { - "account": { - "description": "Alpaca account ID associated with this tokenization request", + "content": { + "contentEncoding": "base64", + "description": "The base64 string encoding of the document contents. This property is required unless content_data is provided.", + "example": "/9j/Cg==", "type": "string" }, - "created_at": { - "format": "date-time", - "type": "string" + "content_data": { + "$ref": "#/components/schemas/W8benDocument" }, - "fees": { - "description": "Fees charged for this tokenization request", - "nullable": true, + "document_sub_type": { + "description": "The specific type of document, e.g. passport. This is a free-form property.", + "example": "passport", "type": "string" }, - "issuer": { - "$ref": "#/components/schemas/TokenizationIssuer" + "document_type": { + "$ref": "#/components/schemas/OwnerDocumentType" }, - "issuer_account": { - "description": "Issuer's account ID associated with this tokenization request", + "mime_type": { + "description": "This field is required if content is specified. ENUM: application/pdf, image/png, or image/jpeg. If document_type is w8ben then application/json is also accepted", + "example": "image/jpeg", "type": "string" + } + }, + "required": [ + "document_type", + "content" + ], + "title": "OwnerDocumentUploadRequest", + "type": "object" + }, + "Portfolio": { + "properties": { + "cooldown_days": { + "description": "Count of calendar days following a rebalance before a subscription is eligible to trigger another rebalance", + "type": "integer" }, - "issuer_request_id": { - "description": "Unique identifier of the tokenization request set by the issuer", - "nullable": true, + "created_at": { + "description": "Portfolio creation timestamp", "type": "string" }, - "network": { - "$ref": "#/components/schemas/TokenizationNetwork" - }, - "qty": { - "description": "The quantity to convert for this tokenization request. It can be fractional.", + "description": { + "description": "Text to describe portfolio", "type": "string" }, - "status": { - "$ref": "#/components/schemas/TokenizationRequestStatus" - }, - "token_symbol": { - "description": "The tokenized asset symbol", + "id": { + "description": "Portfolio ID", "type": "string" }, - "tokenization_request_id": { - "description": "Unique identifier of the tokenization request set by Alpaca", + "name": { + "description": "Name of portfolio", "type": "string" }, - "tx_hash": { - "description": "Transaction hash of the completed request on the blockchain", - "nullable": true, + "rebalancing_conditions": { + "description": "Rebalancing conditions for portfolio", "type": "string" }, - "type": { - "$ref": "#/components/schemas/TokenizationRequestType" - }, - "underlying_symbol": { - "description": "The underlying asset symbol", + "status": { + "description": "Current status of portfolio", + "enum": [ + "active", + "inactive", + "needs_adjustment" + ], "type": "string" }, "updated_at": { - "format": "date-time", - "nullable": true, + "description": "Portfolio updated timestamp", "type": "string" }, - "wallet_address": { - "description": "The wallet address associated with this tokenization request", - "type": "string" + "weights": { + "description": "Weight configuration to portfolio. Sum of \"percent\" values in the weights array must be 100.00", + "items": { + "$ref": "#/components/schemas/PortfolioWeights" + }, + "type": "array" } }, "required": [ - "tokenization_request_id", - "created_at", - "type", - "status", - "underlying_symbol", - "token_symbol", - "qty", - "issuer", - "network", - "wallet_address" + "cooldown_days" ], - "title": "TokenizationRequest", + "title": "Portfolio", "type": "object" }, - "TokenizationRequestStatus": { - "description": "Status of the tokenization request", - "enum": [ - "pending", - "rejected", - "completed" - ], - "example": "completed", - "title": "TokenizationRequestStatus", - "type": "string" - }, - "TokenizationRequestType": { - "description": "Tokenization request type", - "enum": [ - "mint", - "redeem" - ], - "example": "mint", - "title": "TokenizationRequestType", - "type": "string" - }, - "TradeAccount": { - "description": "This is an extended version of the Account model found [in the trading api](https://alpaca.markets/docs/api-references/trading-api/account/#account-entity).\n\nExtra data has been added that would be useful for brokers.", + "PortfolioHistory": { + "description": "Timeseries data for equity and profit loss information of the account.", "properties": { - "account_blocked": { - "description": "If true, the account activity by user is prohibited.", - "example": false, - "type": "boolean" - }, - "account_number": { - "description": "The account number", - "example": "927584925", - "nullable": true, - "type": "string" - }, - "accrued_fees": { - "description": "Accrued fees", - "example": "12345.6789", - "format": "decimal", - "type": "string" - }, - "admin_configurations": { - "$ref": "#/components/schemas/AdminConfigurations" + "base_value": { + "description": "basis in dollar of the profit loss calculation", + "type": [ + "number", + "null" + ] }, - "balance_asof": { - "description": "The date of the snapshot for `last_*` fields", - "example": "2021-04-01", + "base_value_asof": { + "description": "If included, then it indicates that the base_value is the account's closing\nequity value at this trading date.\n\nIf not specified, then the baseline calculation is done against the earliest returned data item. This could happen for\naccounts without prior closing balances (e.g. new account) or for queries with 1D timeframes, where the first data point\nis used as a reference point.\n", + "example": "2023-10-20", + "format": "date", "type": "string" }, - "buying_power": { - "description": "Current available cash buying power. If multiplier = 2 then buying_power = max(equity-initial_margin(0) * 2). If multiplier = 1 then buying_power = cash.", - "example": "12345.6789", - "format": "decimal", - "type": "string" + "cashflow": { + "additionalProperties": { + "items": { + "type": "number" + }, + "type": "array" + }, + "description": "accumulated value in dollar amount as of the end of each time window", + "type": "object" }, - "cash": { - "description": "Cash balance", - "example": "12345.6789", - "format": "decimal", - "type": "string" + "equity": { + "description": "equity value of the account in dollar amount as of the end of each time window", + "items": { + "type": [ + "number", + "null" + ] + }, + "type": "array" }, - "cash_transferable": { - "description": "Cash available for transfer (JNLC)", - "example": "12345.6789", - "type": "string" + "profit_loss": { + "description": "profit/loss in dollar from the base value", + "items": { + "type": [ + "number", + "null" + ] + }, + "type": "array" }, - "cash_withdrawable": { - "description": "Cash available for withdrawal", - "example": "12345.6789", - "format": "decimal", - "type": "string" + "profit_loss_pct": { + "description": "profit/loss in percentage from the base value", + "example": [ + 0.001, + 0.002 + ], + "items": { + "type": [ + "number", + "null" + ] + }, + "type": "array" }, - "clearing_broker": { - "description": "Clearing broker", - "example": "Velox", + "timeframe": { + "description": "time window size of each data element", + "example": "15Min", "type": "string" }, - "created_at": { - "description": "Timestamp this account was created at", - "example": "2021-03-01T13:28:49.270232Z", + "timestamp": { + "description": "Time of each data element, left-labeled (the beginning of time window).\n\nThe values returned are in [UNIX epoch format](https://en.wikipedia.org/wiki/Unix_time).\n", + "items": { + "type": "integer" + }, + "type": "array" + } + }, + "required": [ + "timestamp", + "equity", + "profit_loss", + "profit_loss_pct", + "base_value", + "timeframe" + ], + "title": "PortfolioHistory", + "type": "object" + }, + "PortfolioRun": { + "properties": { + "account_id": { + "description": "Account ID for given run", "type": "string" }, - "crypto_status": { - "$ref": "#/components/schemas/AccountStatus" - }, - "currency": { - "description": "Always USD", - "example": "USD", + "canceled_at": { + "description": "RFC3339 format", "type": "string" }, - "effective_buying_power": { - "description": "Effective buying power (duplicate of buying power)", - "example": "12345.6789", - "format": "decimal", + "completed_at": { + "description": "RFC3339 format", "type": "string" }, - "equity": { - "description": "cash + long_market_value + short_market_value", - "example": "12345.6789", - "format": "decimal", - "type": "string" + "failed_orders": { + "description": "Array of failed orders for this run", + "items": { + "$ref": "#/components/schemas/Order" + }, + "type": "array" }, "id": { - "description": "The account ID", - "example": "c8f1ef5d-edc0-4f23-9ee4-378f19cb92a4", - "format": "uuid", - "type": "string" - }, - "initial_margin": { - "description": "Reg T initial margin requirement (continuously updated value)", - "example": "12345.6789", - "format": "decimal", + "description": "Run ID", "type": "string" }, - "intraday_adjustments": { - "description": "The intraday adjustment by non_trade_activities such as fund deposit/withdraw.\n", - "example": "0", + "initiated_from": { + "description": "system or api", + "enum": [ + "system", + "api" + ], "type": "string" }, - "last_buying_power": { - "description": "Value of buying_power as of previous trading day at 16:00:00 ET", - "example": "12345.6789", - "type": "string" + "orders": { + "description": "Array of executed orders for this run", + "items": { + "$ref": "#/components/schemas/Order" + }, + "type": "array" }, - "last_cash": { - "description": "Value of all cash as of previous trading day at 16:00:00 ET", - "example": "12345.6789", + "portfolio_id": { + "description": "Portfolio ID for given run", "type": "string" }, - "last_equity": { - "description": "Equity as of previous trading day at 16:00:00 ET", - "example": "12345.6789", - "format": "decimal", + "reason": { + "description": "Explainer text in case of failed runs", "type": "string" }, - "last_initial_margin": { - "description": "Value of Reg T margin as of previous trading day at 16:00:00 ET", - "example": "12345.6789", - "type": "string" + "skipped_orders": { + "description": "Array of skipped order for this run", + "items": { + "$ref": "#/components/schemas/SkippedOrder" + }, + "type": "array" }, - "last_long_market_value": { - "description": "Value of all long positions as of previous trading day at 16:00:00 ET", - "example": "12345.6789", - "type": "string" + "status": { + "$ref": "#/components/schemas/PortfolioRunStatus" }, - "last_maintenance_margin": { - "description": "Maintenance margin requirement on the previous trading day", - "example": "12345.6789", - "format": "decimal", + "type": { + "description": "full_rebalance or invest_cash", + "enum": [ + "full_rebalance", + "invest_cash" + ], "type": "string" }, - "last_options_buying_power": { - "description": "Value of option buying power as of previous trading day at 16:00:00 ET", - "example": "12345.6789", + "updated_at": { + "description": "RFC3339 format", "type": "string" }, - "last_regt_buying_power": { - "description": "Value of Reg T buying power as of previous trading day at 16:00:00 ET", - "example": "12345.6789", + "weights": { + "description": "Considered weighting for this run", + "items": { + "$ref": "#/components/schemas/PortfolioWeights" + }, + "type": "array" + } + }, + "title": "PortfolioRun", + "type": "object" + }, + "PortfolioRunStatus": { + "description": "| Status | Final | Represented State | Notes |\n|:------------------:|:-----:|:--------------------------------------------------------------------------------:|:--------------------------------------------------------------------------------------------------------------------:|\n| QUEUED | No | The run has been queued, waiting for our system to process it. | Runs only executed when the US market is open and there's at least 15 minutes before the market closes. |\n| IN_PROGRESS | No | Portfolio adjustment is in progress. | |\n| CANCELED | Yes | Portfolio run canceled, before being picked up by Alpaca's background processing | |\n| CANCELED_MID_RUN | Yes | Portfolio run canceled while executing. | The portfolio's state is in between the pre-run and post-run state, manual remediation or job re-run is recommended. |\n| ERROR | Yes | There was an error while rebalancing the portfolio. | The portfolio's state is in between the pre-run and post-run state, manual remediation or job re-run is recommended. |\n| TIMEOUT | Yes | A timeout occurred while rebalancing the portfolio. | The portfolio's state is in between the pre-run and post-run state, manual remediation or job re-run is recommended. |\n| COMPLETED_ADJUSTED | Yes | The portfolio has been adjusted | The adjustments have been prepared, but the run details haven't yet updated with the list of resulting orders |\n| COMPLETED_SUCCESS | Yes | The portfolio has been adjusted, run status updated | |", + "enum": [ + "QUEUED", + "IN_PROGRESS", + "CANCELED", + "CANCELED_MID_RUN", + "ERROR", + "TIMEOUT", + "COMPLETED_ADJUSTED", + "COMPLETED_SUCCESS" + ], + "title": "PortfolioRunStatus", + "type": "string" + }, + "PortfolioSubscription": { + "properties": { + "account_id": { + "description": "Account ID subscribing to portfolio", "type": "string" }, - "last_short_market_value": { - "description": "Value of all short positions as of previous trading day at 16:00:00 ET", - "example": "0", + "created_at": { + "description": "Subscription creation timestamp", + "format": "date-time", "type": "string" }, - "long_market_value": { - "description": "Real-time MtM value of all long positions held in the account", - "example": "12345.6789", - "format": "decimal", + "id": { + "description": "Subscription ID", "type": "string" }, - "maintenance_margin": { - "description": "Maintenance margin requirement (continuously updated value)", - "example": "12345.6789", - "format": "decimal", + "last_rebalanced_at": { + "description": "Last rebalancing event for this subscription. Can be null.", "type": "string" }, - "memoposts": { - "description": "Outstanding memopost value", - "example": "100", + "portfolio_id": { + "description": "Portfolio ID for subscription", "type": "string" + } + }, + "title": "PortfolioSubscription", + "type": "object" + }, + "PortfolioWeightRequest": { + "oneOf": [ + { + "properties": { + "type": { + "enum": [ + "cash" + ] + } + } }, - "multiplier": { - "description": "\"1\", \"2\", \"3\", or \"4\"", - "example": "2", - "format": "decimal", - "type": "string" + { + "properties": { + "symbol": { + "type": "string" + }, + "type": { + "enum": [ + "asset" + ] + } + }, + "required": [ + "symbol" + ] + } + ], + "properties": { + "percent": { + "description": "Percentage allocated to this weight. Values must be greater than zero, and all supplied weights must total exactly 100.", + "type": [ + "string", + "number" + ] }, - "non_marginable_buying_power": { - "description": "Non-marginable buying power (currently used for only crypto trading)", - "example": "12345.6789", - "format": "decimal", - "type": "string" + "symbol": { + "description": "Asset symbol. Required and non-null when `type` is `asset`.", + "type": [ + "string", + "null" + ] }, - "options_approved_level": { - "description": "The options trading level that was approved for this account.\n0=disabled, 1=Covered Call/Cash-Secured Put, 2=Long Call/Put, 3=Spreads/Straddles.\n", + "type": { + "description": "Type of weight entry in a portfolio.", "enum": [ - 0, - 1, - 2, - 3 + "cash", + "asset" ], - "example": 3, - "type": "integer" - }, - "options_buying_power": { - "description": "Your buying power for options trading", - "example": "12345.6789", - "format": "decimal", + "type": "string" + } + }, + "required": [ + "type", + "percent" + ], + "title": "PortfolioWeightRequest", + "type": "object" + }, + "PortfolioWeights": { + "properties": { + "percent": { + "description": "Percentage allocated to this weight as a decimal string.", "type": "string" }, - "options_trading_level": { - "description": "The effective options trading level of the account.\nThis is the minimum between account options_approved_level and account configurations max_options_trading_level.\n0=disabled, 1=Covered Call/Cash-Secured Put, 2=Long Call/Put, 3=Spreads/Straddles.\n", + "symbol": { + "description": "Asset symbol. `null` for cash weights. Always present.", + "type": [ + "string", + "null" + ] + }, + "type": { + "description": "Type of weight entry in a portfolio.", "enum": [ - 0, - 1, - 2, - 3 + "cash", + "asset" ], - "example": 3, - "type": "integer" - }, - "pending_reg_taf_fees": { - "description": "Pending regulatory fees for the account.", - "example": "0.01", + "type": "string" + } + }, + "required": [ + "type", + "symbol", + "percent" + ], + "title": "PortfolioWeights", + "type": "object" + }, + "Position": { + "examples": [ + { + "asset_class": "us_equity", + "asset_id": "93f58d0b-6c53-432d-b8ce-2bad264dbd94", + "asset_marginable": false, + "avg_entry_price": "172.08", + "change_today": "0.0189483657034581", + "cost_basis": "688.32", + "current_price": "172.08", + "exchange": "NASDAQ", + "lastday_price": "168.88", + "market_value": "688.32", + "qty": "4", + "qty_available": "4", + "side": "long", + "symbol": "AAPL", + "unrealized_intraday_pl": "0", + "unrealized_intraday_plpc": "0", + "unrealized_pl": "0", + "unrealized_plpc": "0" + } + ], + "properties": { + "asset_class": { + "$ref": "#/components/schemas/AssetClass" + }, + "asset_id": { + "description": "Asset ID (For options this represents the option contract ID)", + "example": "904837e3-3b76-47ec-b432-046db621571b", + "format": "uuid", "type": "string" }, - "pending_transfer_out": { - "description": "Cash pending transfer out", - "example": "12345.6789", + "asset_marginable": { + "description": "Indicates if this asset is marginable", + "type": "boolean" + }, + "avg_entry_price": { + "description": "Average entry price of the position", + "example": "100.0", "type": "string" }, - "portfolio_value": { - "description": "Total value of cash + holding positions. (This field is deprecated. It is equivalent to the equity field.)", - "example": "12345.6789", + "avg_entry_swap_rate": { + "description": "The average swap rate of the position. This is only returned for LCT accounts.", + "example": "1.40", "format": "decimal", "type": "string" }, - "position_market_value": { - "description": "Real-time MtM value of all the positions held in the account", - "example": "0", + "change_today": { + "description": "Percent change from last day price (by a factor of 1)", + "example": "0.0084", "format": "decimal", "type": "string" }, - "previous_close": { - "description": "Previous sessions close time", - "example": "2021-04-01T19:00:00-04:00", + "cost_basis": { + "description": "Total cost basis", + "example": "500.0", + "format": "decimal", "type": "string" }, - "regt_buying_power": { - "description": "User's buying power under Regulation T (excess equity - (equity - margin value) - * margin multiplier)", - "example": "12345.6789", + "current_price": { + "description": "Current asset price per share", + "example": "120.0", "format": "decimal", "type": "string" }, - "short_market_value": { - "description": "Real-time MtM value of all short positions held in the account", - "example": "0", + "exchange": { + "description": "Exchange name of the asset", + "example": "NASDAQ", + "type": "string" + }, + "lastday_price": { + "description": "Last day's asset price per share based on the closing value of the last trading day", + "example": "119.0", "format": "decimal", "type": "string" }, - "shorting_enabled": { - "description": "Flag to denote whether or not the account is permitted to short", - "example": false, - "type": "boolean" + "market_value": { + "description": "Total market value of the position", + "example": "600.0", + "format": "decimal", + "type": "string" }, - "sma": { - "description": "Value of Special Memorandum Account (will be used at a later date to provide additional buying_power)", - "example": "12345.6789", + "qty": { + "description": "The number of shares", + "example": "5", + "type": "string" + }, + "qty_available": { + "description": "Total number of shares available minus open orders / locked for options covered call", + "example": "5", + "type": "string" + }, + "side": { + "enum": [ + "long", + "short" + ], + "example": "long", + "type": "string" + }, + "swap_rate": { + "description": "The latest swap rate. This is only returned for LCT accounts.", + "example": "1.50", "format": "decimal", "type": "string" }, - "status": { - "$ref": "#/components/schemas/AccountStatus" + "symbol": { + "description": "Asset symbol", + "example": "AAPL", + "type": "string" }, - "trade_suspended_by_user": { - "description": "If true, the account is not allowed to place orders.", - "example": false, - "type": "boolean" + "unrealized_intraday_pl": { + "description": "Unrealized profit/loss for the day", + "example": "10.0", + "format": "decimal", + "type": "string" }, - "trading_blocked": { - "description": "If true, the account is not allowed to place orders.", - "example": false, - "type": "boolean" + "unrealized_intraday_plpc": { + "description": "Unrealized interday profit/loss percent (by a factor of 1)", + "example": "0.0084", + "format": "decimal", + "type": "string" }, - "transfers_blocked": { - "description": "If true, the account is not allowed to request money transfers.", - "example": false, - "type": "boolean" + "unrealized_pl": { + "description": "Unrealized profit/loss", + "example": "100.0", + "format": "decimal", + "type": "string" }, - "user_configurations": { - "allOf": [ - { - "$ref": "#/components/schemas/AccountConfigurations" - } - ], - "description": "Custom user-level configuration overrides for this account.\n\nThis field is always present in the response, but its value will be `null` until user-level configurations have been explicitly set for the account.", - "nullable": true, - "type": "object" + "unrealized_plpc": { + "description": "Unrealized profit/loss percent (by a factor of 1)", + "example": "0.20", + "format": "decimal", + "type": "string" + }, + "usd": { + "$ref": "#/components/schemas/USDPosition" } }, "required": [ - "id", - "admin_configurations", - "user_configurations", - "account_number", - "status", - "crypto_status", - "currency", - "buying_power", - "regt_buying_power", - "effective_buying_power", - "non_marginable_buying_power", - "accrued_fees", - "portfolio_value", - "trading_blocked", - "transfers_blocked", - "account_blocked", - "created_at", - "trade_suspended_by_user", - "multiplier", - "shorting_enabled", - "equity", - "last_equity", - "long_market_value", - "short_market_value", - "position_market_value", - "initial_margin", - "maintenance_margin", - "last_maintenance_margin", - "sma", - "balance_asof", - "cash" + "asset_id", + "symbol", + "exchange", + "asset_class", + "avg_entry_price", + "qty", + "qty_available", + "side", + "market_value", + "cost_basis", + "unrealized_pl", + "unrealized_plpc", + "unrealized_intraday_pl", + "unrealized_intraday_plpc", + "current_price", + "lastday_price", + "change_today" ], - "type": "object", - "x-examples": { - "example-1": { - "account_blocked": false, - "account_number": "927584925", - "accrued_fees": "0", - "balance_asof": "2021-04-01", - "buying_power": "103556.8572572922", - "cash": "24861.91", - "cash_transferable": "24861.91", - "cash_withdrawable": "17861.91", - "clearing_broker": "VELOX", - "created_at": "2021-03-01T13:28:49.270232Z", - "currency": "USD", - "equity": "28059.3882330664", - "id": "c8f1ef5d-edc0-4f23-9ee4-378f19cb92a4", - "initial_margin": "1598.7391165332", - "intraday_adjustments": "0", - "last_buying_power": "104433.9158860662", - "last_cash": "23861.91", - "last_equity": "26977.323677655", - "last_initial_margin": "1557.7068388275", - "last_long_market_value": "3115.413677655", - "last_maintenance_margin": "934.6241032965", - "last_options_buying_power": "25419.62", - "last_regt_buying_power": "50839.233677655", - "last_short_market_value": "0", - "long_market_value": "3197.4782330664", - "maintenance_margin": "959.24346991992", - "multiplier": "2", - "options_approved_level": 0, - "options_buying_power": "26460.65", - "options_trading_level": 0, - "pending_reg_taf_fees": "0", - "pending_transfer_out": "0", - "portfolio_value": "28059.3882330664", - "previous_close": "2021-04-01T19:00:00-04:00", - "regt_buying_power": "52921.2982330664", - "short_market_value": "0", - "shorting_enabled": true, - "sma": "26758.0590204615", - "status": "ACTIVE", - "trade_suspended_by_user": false, - "trading_blocked": false, - "transfers_blocked": false - }, - "example-2": { - "account_blocked": false, - "account_number": "601612064", - "accrued_fees": "0", - "balance_asof": "2022-02-08", - "buying_power": "83567.42", - "cash": "83567.42", - "cash_transferable": "41783.71", - "cash_withdrawable": "0", - "clearing_broker": "VELOX", - "created_at": "2022-01-21T21:25:26.713802Z", - "crypto_status": "PAPER_ONLY", - "currency": "USD", - "equity": "83567.42", - "id": "56712986-9ff7-4d8f-8e52-077e099e533e", - "initial_margin": "0", - "intraday_adjustments": "0", - "last_buying_power": "41783.71", - "last_cash": "41783.71", - "last_equity": "41783.71", - "last_initial_margin": "0", - "last_long_market_value": "0", - "last_maintenance_margin": "0", - "last_options_buying_power": "41783.71", - "last_regt_buying_power": "41783.71", - "last_short_market_value": "0", - "long_market_value": "0", - "maintenance_margin": "0", - "multiplier": "1", - "non_marginable_buying_power": "41783.71", - "options_approved_level": 2, - "options_buying_power": "83567.42", - "options_trading_level": 1, - "pending_reg_taf_fees": "0.01", - "pending_transfer_in": "0", - "pending_transfer_out": "0", - "portfolio_value": "83567.42", - "previous_close": "2022-02-08T19:00:00-05:00", - "regt_buying_power": "83567.42", - "short_market_value": "0", - "shorting_enabled": false, - "sma": "0", - "status": "ACTIVE", - "trade_suspended_by_user": false, - "trading_blocked": false, - "transfers_blocked": false - } - } + "type": "object" }, - "TradeActivity": { + "PositionClosedResponse": { + "description": "Represents the result of asking the api to close a position.\n\n`body` is the Order used to close out the position.", + "examples": [ + { + "body": { + "asset_class": "us_equity", + "asset_id": "b0b6dd9d-8b9b-48a9-ba46-b9d54906e415", + "canceled_at": null, + "client_order_id": "52f8574c-96d5-49b6-94c1-2570a268434e", + "commision": "1.0", + "created_at": "2022-02-04T16:53:29.53427917Z", + "expired_at": null, + "extended_hours": false, + "failed_at": null, + "filled_at": null, + "filled_avg_price": null, + "filled_qty": "0", + "hwm": null, + "id": "f7f25e89-939a-4587-aaf6-414a6b3c341d", + "legs": null, + "limit_price": null, + "notional": null, + "order_class": "", + "order_type": "market", + "qty": "2", + "replaced_at": null, + "replaced_by": null, + "replaces": null, + "side": "sell", + "status": "accepted", + "stop_price": null, + "submitted_at": "2022-02-04T16:53:29.533738219Z", + "symbol": "AAPL", + "time_in_force": "day", + "trail_percent": null, + "trail_price": null, + "type": "market", + "updated_at": "2022-02-04T16:53:29.53427917Z" + }, + "status": 200, + "symbol": "AAPL" + } + ], "properties": { - "cum_qty": { - "description": "Valid only for trading activity types. Null for non-trading activites.", - "example": "0.9723", - "format": "decimal", - "type": "string" - }, - "leaves_qty": { - "description": "Valid only for trading activity types. Null for non-trading activites.", - "example": "0.5123", - "format": "decimal", - "type": "string" - }, - "order_id": { - "description": "Valid only for trading activity types. Null for non-trading activites.", - "example": "fe060a1b-5b45-4eba-ba46-c3a3345d8255", - "format": "uuid", - "type": "string" + "body": { + "$ref": "#/components/schemas/Order" }, - "order_status": { - "$ref": "#/components/schemas/OrderStatus" + "status": { + "description": "Http status code for the attempt to close this position", + "type": "integer" }, - "price": { - "description": "Valid only for trading activity types. Null for non-trading activites.", - "example": "3.1415", - "format": "decimal", + "symbol": { + "description": "Symbol name of the asset", "type": "string" + } + }, + "required": [ + "symbol", + "status" + ], + "title": "PositionClosedResponse", + "type": "object" + }, + "PositionIntent": { + "description": "Represents the desired position strategy.", + "enum": [ + "buy_to_open", + "buy_to_close", + "sell_to_open", + "sell_to_close" + ], + "example": "buy_to_open", + "title": "PositionIntent", + "type": "string" + }, + "REOActivityV2": { + "allOf": [ + { + "$ref": "#/components/schemas/CommonCaActivityV2" }, - "qty": { - "description": "Valid only for trading activity types. Null for non-trading activites.", - "example": "0.38921", - "format": "decimal", + { + "$ref": "#/components/schemas/CommonREOActivityV2" + } + ], + "description": "Reorganization (REO)", + "type": "object" + }, + "RebalancingConditions": { + "properties": { + "day": { + "description": "Used to specify the rebalancing day for conditions of type = calendar. Only permitted and required for type = calendar. In scenarios when the specified day aligns to a non-trading day, the rebalance will be triggered on the preceding trading day. For type= annually, the value must be passed in MM-DD format. If 02-29 is specified and the current year is not a leap year, the rebalance will occur on the trading day preceding 2/29. For type= quarterly and monthly the value must be an integer between 1 and 31 inclusive. This represents the day of month. For quarterly, rebalancing will trigger on this day in January, March, June, and December. If the specified day is non-existent for the month then the rebalancing will trigger on the preceding trading day. For type = weekly, permitted values are Monday, Tuesday, Wednesday, Thursday, Friday.", "type": "string" }, - "side": { - "$ref": "#/components/schemas/OrderSide" - }, - "symbol": { - "description": "Valid only for trading activity types. Null for non-trading activites.", - "example": "AAPL", + "percent": { + "description": "Must be a positive value, up to two decimal places. Only permitted and required for type = drift_band. This is the max allowable drift percent from any target weight (+/-)", "type": "string" }, - "transaction_time": { - "description": "Valid only for trading activity types. Null for non-trading activites.", - "example": "2021-05-10T14:01:04.650275Z", - "format": "date-time", + "sub_type": { + "description": "For type = drift_band: absolute or relative. For type = calendar: weekly,monthly, quarterly or annually. For type = on_portfolio_update: there is no subtype", "type": "string" }, "type": { - "description": "Valid only for trading activity types. Null for non-trading activites.", - "enum": [ - "fill", - "partial_fill" - ], - "example": "fill", + "description": "Possible values of drift_band, calendar or on_portfolio_update", "type": "string" } }, - "title": "TradeActivity", + "title": "RebalancingConditions", "type": "object" }, - "TradeUpdateEvent": { - "description": "Represents an update to an order/trade, sent over the events streaming api.", + "ReportsResponse": { "properties": { - "account_id": { - "description": "Account UUID", - "format": "uuid", - "minLength": 1, - "type": "string" - }, - "at": { - "description": "Timestamp of event", - "format": "date-time", - "minLength": 1, - "type": "string" - }, - "event": { - "$ref": "#/components/schemas/TradeUpdateEventType" - }, - "event_id": { - "description": "Monotonically increasing 64bit integer", - "format": "int64", - "type": "integer" - }, - "event_ulid": { - "description": "lexically sortable, monotonically increasing character array", - "format": "ulid", - "type": "string" - }, - "execution_id": { - "description": "Corresponding execution of an order. If an order gets filled over two executions (a partial_fill for example), you will receive two events with different IDs.", - "format": "uuid", + "account_no": { "type": "string" }, - "order": { - "$ref": "#/components/schemas/Order" - }, - "position_qty": { - "description": "Only present when event is either `fill` or `partial_fill`. The size of your total position, after this fill event, in shares. Positive for long positions, negative for short positions.", + "deadline": { + "format": "date", "type": "string" }, - "previous_execution_id": { - "description": "ID of the original execution that was busted or corrected (present only in trade_bust and trade_correct events).", - "format": "uuid", - "type": "string" + "instant_funding_transfers": { + "items": { + "$ref": "#/components/schemas/InstantFunding" + }, + "type": "array" }, - "price": { - "description": "Only present when event is either `fill` or `partial_fill`. The average price per share at which the order was filled.", + "system_date": { + "format": "date", "type": "string" }, - "qty": { - "description": "Only present when event is either `fill` or `partial_fill`. The amount of shares this Trade order was for", + "total_amount_owed": { + "format": "decimal", "type": "string" }, - "timestamp": { - "description": "Has various different meanings depending on the value of `event`, please see the [Trading Events](https://alpaca.markets/docs/api-references/broker-api/events/#trade-events)\nEnum in the documentation or the TradeUpdateEventType model for more details on when it means different things.\n", - "format": "date-time", + "total_interest_penalty": { + "format": "decimal", "type": "string" } }, "required": [ - "account_id", - "at", - "event", - "event_id", - "event_ulid", - "execution_id", - "order", - "timestamp" + "system_date", + "account_no", + "total_amount_owed", + "total_interest_penalty", + "deadline" ], - "title": "TradeUpdateEvent", - "type": "object", - "x-examples": { - "example-1": { - "account_id": "529248ad-c4cc-4a50-bea4-6bfd2953f83a", - "at": "2022-04-19T14:12:30.656741Z", - "event": "new", - "event_id": 37782, - "event_ulid": "01G112NTT0XAXKDZK3AABK68TH", - "execution_id": "7e544af3-3104-4e1a-8cbc-dab2624949ff", - "order": { - "asset_class": "us_equity", - "asset_id": "a4778bc8-fad1-47b7-87fe-d5cde10d43f4", - "canceled_at": null, - "client_order_id": "6d873193-dac6-4f72-8e13-c57853a9339d", - "commission": "1", - "created_at": "2022-04-19T10:12:30.57117938-04:00", - "expired_at": null, - "extended_hours": false, - "failed_at": null, - "filled_at": null, - "filled_avg_price": null, - "filled_qty": "0", - "hwm": null, - "id": "edada91a-8b55-4916-a153-8c7a9817e708", - "legs": null, - "limit_price": "700", - "notional": null, - "order_class": "", - "order_type": "limit", - "qty": "4", - "replaced_at": null, - "replaced_by": null, - "replaces": null, - "side": "buy", - "status": "new", - "stop_price": null, - "submitted_at": "2022-04-19T10:12:30.403135025-04:00", - "symbol": "TSLA", - "time_in_force": "day", - "trail_percent": null, - "trail_price": null, - "type": "limit", - "updated_at": "2022-04-19T10:12:30.609783218-04:00" - }, - "timestamp": "2022-04-19T14:12:30.602193534Z" - } - } - }, - "TradeUpdateEventType": { - "description": "**Common events**\n\nThese are the events that are the expected results of actions you may have taken by sending API requests.\n\nThe meaning of the `timestamp` field changes for each type; the meanings have been specified here for which types the\ntimestamp field will be present.\n\n- `new`: Sent when an order has been routed to exchanges for execution.\n- `fill`: Sent when your order has been completely filled.\n - `timestamp`: The time at which the order was filled.\n- `partial_fill`: Sent when a number of shares less than the total remaining quantity on your order has been filled.\n - `timestamp`: The time at which the shares were filled.\n- `canceled`: Sent when your requested cancellation of an order is processed.\n - `timestamp`: The time at which the order was canceled.\n- `expired`: Sent when an order has reached the end of its lifespan, as determined by the order's time in force value.\n - `timestamp`: The time at which the order expired.\n- `done_for_day`: Sent when the order is done executing for the day, and will not receive further updates until the next trading day.\n- `replaced`: Sent when your requested replacement of an order is processed.\n - `timestamp`: The time at which the order was replaced.\n\n**Rarer events**\n\nThese are events that may rarely be sent due to unexpected circumstances on the exchanges. It is unlikely you will need to design your code around them, but you may still wish to account for the possibility that they will occur.\n\n- `rejected`: Sent when your order has been rejected.\n - `timestamp`: The time at which the rejection occurred.\n- `pending_new`: Sent when the order has been received by Alpaca and routed to the exchanges, but has not yet been accepted for execution.\n- `stopped`: Sent when your order has been stopped, and a trade is guaranteed for the order, usually at a stated price or better, but has not yet occurred.\n- `pending_cancel`: Sent when the order is awaiting cancellation. Most cancellations will occur without the order entering this state.\n- `pending_replace`: Sent when the order is awaiting replacement.\n- `calculated`: Sent when the order has been completed for the day - it is either `filled` or `done_for_day` - but remaining settlement calculations are still pending.\n- `suspended`: Sent when the order has been suspended and is not eligible for trading.\n- `order_replace_rejected`: Sent when the order replace has been rejected.\n- `order_cancel_rejected`: Sent when the order cancel has been rejected.\n- `trade_bust`: Sent when a previously reported execution has been canceled (\"busted\") by the upstream exchange.\n- `trade_correct`: Sent when a previously reported trade has been corrected. For example, the exchange may have updated the price, quantity, or another execution parameter after the trade was initially reported.\n- `restated`: Sent when the order is manually modified.\n", - "enum": [ - "new", - "fill", - "partial_fill", - "canceled", - "expired", - "done_for_day", - "replaced", - "rejected", - "pending_new", - "stopped", - "pending_cancel", - "pending_replace", - "calculated", - "suspended", - "order_replace_rejected", - "order_cancel_rejected", - "trade_bust", - "trade_correct" - ], - "type": "string" + "type": "object" }, - "TradeUpdateEventV2": { - "description": "Represents an update to an order/trade, sent over the events streaming api.", + "RestrictToLiquidationReasons": { + "description": "Reasons why the liquidation only flag was set", "properties": { - "account_id": { - "description": "Account UUID", - "format": "uuid", - "minLength": 1, - "type": "string" - }, - "at": { - "description": "Timestamp of event", - "format": "date-time", - "minLength": 1, - "type": "string" - }, - "event": { - "$ref": "#/components/schemas/TradeUpdateEventType" - }, - "event_id": { - "description": "lexically sortable, monotonically increasing character array", - "format": "ulid", - "type": "string" + "ach_return": { + "description": "Set when an incoming ACH transfer gets rejected", + "type": "boolean" }, - "execution_id": { - "description": "Corresponding execution of an order. If an order gets filled over two executions (a partial_fill for example), you will receive two events with different IDs.\nNot present for `MultilegOptions`\n", - "format": "uui", - "type": "string" + "position_to_equity_ratio": { + "description": "Set when the position to equity ration exceeds the maximum limit", + "type": "boolean" }, - "legs": { - "description": "Only present when event is for `MultilegOptions`. Represents filled qty/price of legs.\n", + "unspecified": { + "description": "Default value for unknown reason", + "type": "boolean" + } + }, + "title": "RestrictToLiquidationReasons", + "type": "object" + }, + "ReverseBatchJournalRequest": { + "examples": [ + { + "entries": [ + { + "amount": "10", + "from_account": "8e00606a-c9ac-409a-ba45-f55e8f77984a" + }, + { + "amount": "100", + "from_account": "b9b19618-22dd-4e80-8432-fc9e1ba0b27d" + }, + { + "amount": "100", + "from_account": "c96a5e16-7fca-425a-b67b-0814d064bfc0" + } + ], + "entry_type": "JNLC", + "to_account": "8f8c8cee-2591-4f83-be12-82c659b5e748" + } + ], + "properties": { + "entries": { "items": { - "$ref": "#/components/schemas/TradeUpdateEventV2Leg" + "properties": { + "amount": { + "description": "Journal amount in USD", + "type": "string" + }, + "description": { + "description": "Journal description, gets returned in the response", + "type": "string" + }, + "from_account": { + "description": "The ID of the from_account that you want to journal from", + "type": "string" + } + }, + "type": "object" }, "type": "array" }, - "order": { - "$ref": "#/components/schemas/Order" - }, - "position_qty": { - "description": "Only present when event is either `fill` or `partial_fill` other than `MultilegOptions`. The size of your total position, after this fill event, in shares. Positive for long positions, negative for short positions.\n", - "type": "string" - }, - "position_qtys": { - "description": "Only present when event is either `fill` or `partial_fill` for `MultilegOptions`. The size of your total position, after this fill event, in shares. Positive for long positions, negative for short positions.\n", - "type": "string" - }, - "previous_execution_id": { - "description": "ID of the original execution that was busted or corrected (present only in trade_bust and trade_correct events).", - "format": "uuid", - "type": "string" - }, - "price": { - "description": "Only present when event is either `fill` or `partial_fill`. The average price per share at which the order was filled.", - "type": "string" - }, - "qty": { - "description": "Only present when event is either `fill`, `partial_fill`, `trade_bust` and `trade_correct`. The amount of shares this Trade order was for.
For `trade_bust` events, the qty field may be negative, representing a reversal of the original quantity.", - "type": "string" - }, - "settle_date": { - "description": "Settlement date of the trade in format `YYYY-MM-DD` for `fill` and `partial_fill` events", - "example": "2026-03-03", - "format": "date", - "type": "string" - }, - "swap_rate": { - "description": "Only present for `local currency trading account` or `crypto asset trade` when event is either `fill` or `partial_fill`. The swap rate at which the current trade was filled.", + "entry_type": { + "description": "Only supports `JNLC` for now", + "enum": [ + "JNLC" + ], "type": "string" }, - "timestamp": { - "description": "Has various different meanings depending on the value of `event`, please see the [Trading Events](https://alpaca.markets/docs/api-references/broker-api/events/#trade-events)\nEnum in the documentation or the TradeUpdateEventType model for more details on when it means different things.\n", - "format": "date-time", + "to_account": { + "description": "The destination of funds. Most likely is your Sweep Firm Account", "type": "string" } }, - "title": "TradeUpdateEvent", "type": "object" }, - "TradeUpdateEventV2Leg": { - "description": "Represents filled qty/price of legs.", - "properties": { - "execution_id": { - "description": "Corresponding execution of an order. If an order gets filled over two executions (a partial_fill for example), you will receive two events with different IDs.", - "format": "uuid", - "type": "string" - }, - "order_id": { - "description": "Order UUID", - "format": "uuid", - "minLength": 1, - "type": "string" - }, - "price": { - "description": "The average price per share for this event.", - "type": "string" - }, - "qty": { - "description": "The amount of shares filled for this event", - "type": "string" - }, - "symbol": { - "description": "Symbol of an asset", - "type": "string" + "ReverseSplitActivityV2": { + "allOf": [ + { + "$ref": "#/components/schemas/CommonSplitStockActivityV2" }, - "timestamp": { - "description": "Timestamp of this event leg\n", - "format": "date-time", - "type": "string" + { + "properties": { + "ex_date": { + "description": "The ex_date for this corporate action", + "format": "date", + "minLength": 1, + "type": "string" + }, + "new_symbol": { + "description": "Symbol of the new security after the split", + "type": "string" + }, + "record_date": { + "description": "The record_date for this corporate action", + "format": "date", + "minLength": 1, + "type": "string" + }, + "symbol": { + "description": "The symbol of the security involved with the activity", + "type": "string" + } + }, + "required": [ + "symbol" + ], + "type": "object" } - }, - "title": "TradeUpdateEventV2Leg", + ], + "description": "Reverse stock split", "type": "object" }, - "Transaction": { - "properties": { - "account_id": { - "description": "The ledger ID", - "type": "string" - }, - "account_name": { - "description": "The ledger name", - "type": "string" - }, - "account_no": { - "description": "The ledger account number", - "type": "string" - }, - "amount": { - "description": "Total amount of the transaction", - "type": "string" + "RightsDistributionActivityV2": { + "allOf": [ + { + "$ref": "#/components/schemas/CommonCaActivityV2" }, - "balance": { - "description": "Ending balance after thetransaction has been applied", - "type": "string" + { + "properties": { + "ex_date": { + "description": "The ex_date for this corporate action", + "format": "date", + "minLength": 1, + "type": "string" + }, + "expiration_date": { + "description": "The expiration date for the rights distribution", + "format": "date", + "type": "string" + }, + "new_cusip": { + "description": "The new CUSIP", + "type": "string" + }, + "new_qty": { + "description": "The new quantity", + "type": "string" + }, + "new_symbol": { + "description": "The new symbol", + "type": "string" + }, + "payable_date": { + "description": "The payable_date for this corporate action", + "format": "date", + "minLength": 1, + "type": "string" + }, + "rate": { + "description": "The rate for the rights distribution", + "type": "string" + }, + "record_date": { + "description": "The record_date for this corporate action", + "format": "date", + "minLength": 1, + "type": "string" + }, + "source_cusip": { + "description": "The source CUSIP", + "type": "string" + }, + "source_qty": { + "description": "The source quantity", + "type": "string" + }, + "source_symbol": { + "description": "The source symbol", + "type": "string" + } + }, + "required": [ + "source_cusip", + "source_symbol", + "source_qty", + "new_cusip", + "new_symbol", + "new_qty", + "rate" + ], + "type": "object" + } + ], + "description": "Rights distribution", + "type": "object" + }, + "RightsSubscriptionElectionActivityV2": { + "allOf": [ + { + "$ref": "#/components/schemas/CommonNTAActivityV2" }, - "contra_account_name": { - "description": "Contra account of transaction", - "type": "string" + { + "$ref": "#/components/schemas/CommonVOFSubtypeActivityV2" + } + ], + "type": "object" + }, + "SDIVActivityV2": { + "allOf": [ + { + "$ref": "#/components/schemas/CommonCaActivityV2" }, - "description": { - "description": "Plain text overview of the transaction", - "type": "string" + { + "$ref": "#/components/schemas/CommonSDIVActivityV2" }, - "entry_type": { - "description": "Type of transaction", - "type": "string" + { + "properties": { + "entitled_qty": { + "description": "Quantity of shares entitled to receive the dividend", + "type": "string" + }, + "new_qty": { + "description": "The total number of shares after the dividend", + "type": "string" + }, + "paid_qty": { + "description": "The paid quantity", + "type": "string" + } + }, + "required": [ + "entitled_qty", + "paid_qty", + "new_qty" + ], + "type": "object" + } + ], + "description": "Stock dividend", + "type": "object" + }, + "SearchVASPsResponse": { + "description": "Paginated VASP directory search results.", + "properties": { + "page": { + "example": 1, + "type": "integer" }, - "system_date": { - "description": "Date of transaction", - "type": "string" + "pages": { + "example": 1, + "type": "integer" + }, + "total": { + "description": "Total count of matching records.", + "example": 1, + "type": "integer" + }, + "vasps": { + "example": [ + { + "id": "vasp-1", + "name": "Example Exchange" + } + ], + "items": { + "$ref": "#/components/schemas/VASP" + }, + "type": "array" } }, - "type": "object", - "x-examples": { - "Example 1": { - "account_id": "06b9d76e-1733-460c-844e-48d1ae0f10c3", - "account_name": "Just In Time Receivable - JITS", - "account_no": "JTRJITS00", - "amount": "1", - "balance": "4186.36", - "contra_account_name": "Just In Time Interest Income", - "description": "debit balance = 4185.36, base_rate = 0.0375, spread = 0.05", - "entry_type": "JNLC", - "system_date": "2022-06-10" - } - } + "type": "object" }, - "Transfer": { - "description": "Transfers allow you to transfer money/balance into your end customers' account (deposits) or out (withdrawal).\n\n[Main docs here](https://alpaca.markets/docs/api-references/broker-api/funding/transfers/#the-transfer-object)", + "SettlementAccount": { "properties": { - "account_id": { - "description": "The account ID", - "format": "uuid", - "type": "string" - }, - "additional_information": { - "description": "Additional information. Only applies when type = \"wire\".", - "nullable": true, + "account_number": { + "description": "The account number of the client account to settle\n", "type": "string" }, "amount": { - "description": "Must be > 0.00", "format": "decimal", "type": "string" }, - "bank_id": { - "description": "The ID of the Bank, only present if type = \"wire\"", - "format": "uuid", - "type": "string" - }, - "created_at": { - "description": "Timestamp when transfer was created", - "format": "date-time", + "transmitter_info": { + "$ref": "#/components/schemas/TransmitterInfo" + } + }, + "required": [ + "account_number", + "amount", + "transmitter_info" + ], + "type": "object" + }, + "SettlementResponse": { + "description": "A settlement response, either from creation or retrieval\n", + "properties": { + "additional_info": { "type": "string" }, - "direction": { - "$ref": "#/components/schemas/TransferDirection" + "asset_class": { + "$ref": "#/components/schemas/JITAssetClass" }, - "expires_at": { - "description": "Timestamp when transfer expires", + "completed_at": { "format": "date-time", "type": "string" }, - "fee": { - "description": "Fee amount to be collected. Only applies when type = \"wire\".", - "format": "decimal", - "nullable": true, - "type": "string" - }, - "fee_payment_method": { - "description": "Either \"user\" or \"invoice\". Only applies when type = \"wire\".", - "nullable": true, + "created_at": { + "format": "date-time", "type": "string" }, - "hold_until": { - "format": "date-time", + "currency": { + "description": "The currency of the settlement. Only applicable to JIT settlements.\n", "type": "string" }, "id": { - "description": "The transfer ID", "format": "uuid", "type": "string" }, - "instant_amount": { + "interest_amount": { + "description": "The total interest amount accrued on the transfers included in this settlement. Only applicable for instant funding settlements.\n", + "format": "decimal", "type": "string" }, - "ira": { - "$ref": "#/components/schemas/TransferIRADetails" - }, "reason": { - "description": "Cause of the status", - "nullable": true, - "type": "string" - }, - "relationship_id": { - "description": "The ACH relationship ID only present if type = \"ach\"", - "format": "uuid", + "description": "The reason for failure, if applicable\n", "type": "string" }, - "requested_amount": { - "description": "Must be > 0.00. Only applies when type = \"wire\".", - "format": "decimal", - "nullable": true, + "source_account_number": { "type": "string" }, "status": { - "$ref": "#/components/schemas/TransferStatus" + "$ref": "#/components/schemas/SettlementStatus" }, - "type": { - "$ref": "#/components/schemas/TransferType" + "total_amount": { + "format": "decimal", + "type": "string" }, "updated_at": { - "description": "Timestamp when transfer was updated", "format": "date-time", "type": "string" } }, "required": [ "id", - "account_id", - "type", + "total_amount", "status", - "amount", - "direction", - "created_at" + "created_at", + "updated_at" ], - "title": "Transfer", - "type": "object", - "x-examples": { - "example-1": { - "account_id": "449e7a5c-69d3-4b8a-aaaf-5c9b713ebc65", - "additional_information": "string", - "amount": "string", - "bank_id": "f1ae96de-94c1-468e-93a3-6b7213930ca8", - "created_at": "2019-08-24T14:15:22Z", - "direction": "INCOMING", - "expires_at": "2019-08-24T14:15:22Z", - "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08", - "ira": { - "distribution_reason": "normal", - "fed_withholding_amount": "102.5", - "fed_withholding_pct": "10.25", - "state_withholding_amount": "97.5", - "state_withholding_pct": "9.75", - "tax_year": "2024" - }, - "reason": "string", - "relationship_id": "81412018-ffa2-43f9-a3eb-d39f1c5e0f87", - "status": "QUEUED", - "type": "ach", - "updated_at": "2019-08-24T14:15:22Z" - } - } + "type": "object" }, - "TransferDirection": { - "description": "- **INCOMING**\nFunds incoming to user's account (deposit).\n- **OUTGOING**\nFunds outgoing from user's account (withdrawal).\n", + "SettlementStatus": { + "description": "Values:\n * `PENDING`: Created and waiting to be processed\n * `AWAITING_ADDITIONAL_FUNDS`: Waiting for additional funds to be deposited\n * `COMPLETED`: All transactions are reconciled\n * `FAILED`: Attempt to process this settlement has failed. If this is observed\n additional remittance information may be required, or one of the transfers being settled\n accrued additional interest since the settlement was created.\n", "enum": [ - "INCOMING", - "OUTGOING" + "PENDING", + "AWAITING_ADDITIONAL_FUNDS", + "COMPLETED", + "FAILED" ], - "example": "INCOMING", "type": "string" }, - "TransferFee": { + "SettlementTransfer": { "properties": { - "amount": { - "format": "decimal", + "instant_transfer_id": { + "format": "uuid", "type": "string" }, - "currency": { - "$ref": "#/components/schemas/Currency" - }, - "payment_type": { - "$ref": "#/components/schemas/FeePaymentType" - }, - "type": { - "$ref": "#/components/schemas/FeeType" + "transmitter_info": { + "$ref": "#/components/schemas/TransmitterInfo" } }, "required": [ - "type", - "amount", - "currency", - "payment_type" + "instant_transfer_id", + "transmitter_info" ], "type": "object" }, - "TransferIRA": { - "description": "This field is used for IRA Account only", + "SettlementsResponse": { + "description": "Response to a successful settlements request\n", "properties": { - "distribution_reason": { - "example": "normal", - "type": "string" - }, - "tax_withholding": { - "$ref": "#/components/schemas/TransferIRATaxWithholding" - }, - "tax_year": { - "example": "2024", - "type": "string" + "settlements": { + "items": { + "$ref": "#/components/schemas/SettlementResponse" + }, + "type": "array" } }, + "required": [ + "settlements" + ], "type": "object" }, - "TransferIRADetails": { + "SkippedOrder": { + "description": "Skipped orders model contains information for such orders that the rebalancing engine didn't send to our order system due to some validation issues.", "properties": { - "distribution_reason": { - "example": "normal", + "currency": { + "description": "Currency of the order", "type": "string" }, - "fed_withholding_amount": { - "example": "102.5", + "notional": { + "description": "Notional value of the order", "type": "string" }, - "fed_withholding_pct": { - "example": "10.25", + "reason": { + "description": "Reason for the order being skipped", "type": "string" }, - "state_withholding_amount": { - "example": "97.5", + "reason_details": { + "description": "Formatted error message with the cause of the skip", "type": "string" }, - "state_withholding_pct": { - "example": "9.75", + "side": { + "description": "Side of the order (buy, sell, sell_short)", "type": "string" }, - "tax_year": { - "example": "2024", + "symbol": { + "description": "Symbol for which the adjustment was skipped", "type": "string" } }, + "required": [ + "symbol", + "reason", + "reason_details" + ], + "title": "SkippedOrder", "type": "object" }, - "TransferIRATaxWithholding": { - "properties": { - "fed_pct": { - "example": "10.25", - "type": "string" + "SortOrder": { + "enum": [ + "ASC", + "DESC" + ], + "type": "string" + }, + "SpinoffActivityV2": { + "allOf": [ + { + "$ref": "#/components/schemas/CommonCaActivityV2" }, - "state_pct": { - "example": "8.25", - "type": "string" + { + "$ref": "#/components/schemas/CommonSpinoffActivityV2" + }, + { + "properties": { + "new_qty": { + "description": "The new quantity", + "type": "string" + }, + "source_qty": { + "description": "The source quantity", + "type": "string" + } + }, + "required": [ + "source_qty", + "new_qty" + ], + "type": "object" } - }, + ], + "description": "Spinoff", "type": "object" }, - "TransferStatus": { - "description": "- **QUEUED**\nTransfer is in queue to be processed.\n- **APPROVAL_PENDING**\nTransfer is pending approval.\n- **PENDING**\nTransfer is pending processing.\n- **SENT_TO_CLEARING**\nTransfer is being processed by the clearing firm.\n- **REJECTED**\nTransfer is rejected.\n- **CANCELED**\nClient initiated transfer cancellation.\n- **APPROVED**\nTransfer is approved.\n- **COMPLETE**\nTransfer is completed.\n- **RETURNED**\nThe bank issued an ACH return for the transfer.\n", - "enum": [ - "QUEUED", - "APPROVAL_PENDING", - "PENDING", - "SENT_TO_CLEARING", - "REJECTED", - "CANCELED", - "APPROVED", - "COMPLETE", - "RETURNED" + "StatusFundingEvent": { + "description": "Represents a change in a Funding entity's status, sent over the events streaming api. Currently, the suppported entities are: bank relationships, bank wires, transfers and funding wallets.", + "examples": [ + { + "account_id": "8e00606a-c9ac-409a-ba45-f55e8f77984a", + "at": "2025-04-11T19:52:24.066998Z", + "correspondent": "LPCA", + "entity_id": "c4ed4206-697b-4859-ab71-b9de6649859d", + "entity_type": "BankRelationship", + "event_id": "01F7VQQ782DM57SJNWAYMD14J9", + "status_to": "QUEUED" + }, + { + "account_id": "8e00606a-c9ac-409a-ba45-f55e8f77984a", + "at": "2025-04-11T19:52:24.066998Z", + "correspondent": "LPCA", + "entity_id": "c4ed4206-697b-4859-ab71-b9de6649859d", + "entity_type": "BankRelationship", + "event_id": "01F7VQQ782DM57SJNWAYMD14J9", + "reason": "bank account owner name does not match brokerage account name", + "status_from": "QUEUED", + "status_to": "REJECTED" + } ], - "example": "QUEUED", - "type": "string" - }, - "TransferStatusEvent": { - "description": "Represents a change in a Transfer's status, sent over the events streaming api.", "properties": { "account_id": { "description": "Account UUID", @@ -12316,1540 +12527,3426 @@ }, "at": { "description": "Timestamp of when the transfer status changed", - "format": "date-time", "minLength": 1, "type": "string" }, - "event_id": { - "description": "Monotonically increasing 64bit integer", - "type": "integer" + "correspondent": { + "description": "Correspondent's code", + "format": "ABCD", + "maxLength": 4, + "minLength": 1, + "type": "string" }, - "event_ulid": { + "entity_id": { + "description": "Entity's UUID", + "format": "uuid", + "minLength": 1, + "type": "string" + }, + "entity_type": { + "description": "Valid values are BankRelationship, WireBank, Transfer and FundingWallet.", + "format": "uuid", + "minLength": 1, + "type": "string" + }, + "event_id": { "description": "lexically sortable, monotonically increasing character array", "format": "ulid", + "minLength": 1, + "type": "string" + }, + "reason": { + "description": "Used when an a bank relationship is rejected, a wire bank is canceled, etc.", "type": "string" }, "status_from": { - "$ref": "#/components/schemas/TransferStatus" + "description": "Valid values are based on entity type:\n- BankRelationship:\n - QUEUED\n - CANCEL_REQUESTED\n - CANCEL_SENT\n - CANCEL_FAILED\n - PENDING\n - SENT_TO_CLEARING\n - APPROVED\n - CANCELED\n - REJECTED\n- WireBank:\n - QUEUED\n - SENT_TO_CLEARING\n - APPROVED\n - CANCELED\n - REJECTED\n- Transfer:\n - QUEUED\n - APPROVAL_PENDING\n - CANCELED\n - EXPIRED\n - APPROVED\n - REJECTED\n - SENT_TO_CLEARING\n - COMPLETE\n - RETURNED\n- FundingWallet:\n - active\n - pending\n", + "type": "string" }, "status_to": { - "$ref": "#/components/schemas/TransferStatus" - }, - "transfer_id": { - "description": "Transfer UUID", - "format": "uuid", + "description": "Valid values are based on entity type:\n- BankRelationship:\n - QUEUED\n - CANCEL_REQUESTED\n - CANCEL_SENT\n - CANCEL_FAILED\n - PENDING\n - SENT_TO_CLEARING\n - APPROVED\n - CANCELED\n - REJECTED\n- WireBank:\n - QUEUED\n - SENT_TO_CLEARING\n - APPROVED\n - CANCELED\n - REJECTED\n- Transfer:\n - QUEUED\n - APPROVAL_PENDING\n - CANCELED\n - EXPIRED\n - APPROVED\n - REJECTED\n - SENT_TO_CLEARING\n - COMPLETE\n - RETURNED\n- FundingWallet:\n - active\n - pending\n", "minLength": 1, "type": "string" } }, "required": [ - "account_id", - "at", "event_id", - "event_ulid", - "status_from", - "status_to", - "transfer_id" + "at", + "account_id", + "correspondent", + "entity_id", + "entity_type", + "status_to" ], - "title": "TransferStatusEvent", - "type": "object", - "x-examples": { - "example-1": { - "account_id": "8e00606a-c9ac-409a-ba45-f55e8f77984a", - "at": "2021-06-10T19:52:24.066998Z", - "event_id": 15961, - "event_ulid": "01F7VQQ782DM57SJNWAYMD14J9", - "status_from": "QUEUED", - "status_to": "SENT_TO_CLEARING", - "transfer_id": "c4ed4206-697b-4859-ab71-b9de6649859d" - }, - "example-2": { - "account_id": "8e00606a-c9ac-409a-ba45-f55e8f77984a", - "at": "2021-06-10T20:02:24.280178Z", - "event_id": 15962, - "status_from": "SENT_TO_CLEARING", - "status_to": "COMPLETE", - "transfer_id": "c4ed4206-697b-4859-ab71-b9de6649859d" - } - } + "title": "StatusFundingEvent", + "type": "object" }, - "TransferType": { - "description": "- **ach**\nTransfer via ACH (US Only). Supports both `INCOMING` (deposit) and `OUTGOING` (withdrawal) directions.\n- **wire**\nTransfer via wire. `OUTGOING` (withdrawal) only.\n", - "enum": [ - "ach", - "wire" - ], - "example": "ach", - "title": "TransferType", + "StreetAddress": { + "example": "20 N San Mateo Dr", "type": "string" }, - "TransmitterInfo": { - "description": "Information about the transmitter to satisfy travel rule requirements. Required if the requesting correspondent qualifies as a financial institution\n", + "SystemEventV2": { + "description": "Represents that system event had occurred and sent over the events streaming api.\n", + "examples": [ + { + "at": "2026-01-05T10:28:23.163857Z", + "description": "End-of-day balances are now available.", + "event_id": "01F535Y1FVY8WZHE763HCNS8SZ", + "system_date": "2026-01-05", + "type": "eod_balances_ready" + } + ], "properties": { - "originator_bank_account_number": { - "description": "Required if the requesting correspondent qualifies as a financial institution\n", - "type": "string" - }, - "originator_bank_name": { - "description": "Required if the requesting correspondent qualifies as a financial institution\n", - "type": "string" - }, - "originator_city": { - "type": "string" - }, - "originator_country": { - "description": "Required if the requesting correspondent qualifies as a financial institution\n", - "type": "string" - }, - "originator_full_name": { - "description": "Required if the requesting correspondent qualifies as a financial institution\n", + "at": { + "description": "Timestamp of the event", + "format": "date-time", "type": "string" }, - "originator_postal_code": { + "description": { + "description": "The human readable description of the system event", "type": "string" }, - "originator_state": { + "event_id": { + "description": "lexically sortable, monotonically increasing character array", + "format": "ulid", "type": "string" }, - "originator_street_address": { + "system_date": { + "description": "the system date to which this system event belongs to", + "format": "date", "type": "string" }, - "other_identifying_information": { - "description": "Used to facilitate transfer lookup in the event it is required. Recommended to be the originating bank's reference number for the transfer\n", + "type": { + "description": "the machine readable type of the system event", + "enum": [ + "eod_balances_ready", + "eod_positions_ready" + ], "type": "string" } }, + "required": [ + "at", + "event_id", + "type", + "system_date", + "description" + ], + "title": "SystemEvent", "type": "object" }, - "TrustedContact": { - "description": "This model input is optional. However, the client should\nmake reasonable effort to obtain the trusted contact information.\nSee more details in [FINRA Notice 17-11](https://www.finra.org/sites/default/files/Regulatory-Notice-17-11.pdf)\n\nOnly one of the following is required:\n\n* email_address\t\n* phone_number\t\n* street_address\t\n", - "example": { - "email_address": "jane.doe@example.com", - "family_name": "Doe", - "given_name": "Jane" - }, - "properties": { - "city": { - "description": "required if `street_address` is set\n", - "type": "string" + "TaxIdType": { + "description": "An Enum of the various kinds of Tax ID formats Alpaca supports.\n\nPossible Values are:\n\n\n- **USA_SSN**\nUSA Social Security Number\n\n- **USA_ITIN**\nUSA Individual Taxpayer Identification Number\n\n- **ARG_AR_CUIT**\nArgentina CUIT\n\n- **AUS_TFN**\nAustralian Tax File Number\n\n- **AUS_ABN**\nAustralian Business Number\n\n- **BOL_NIT**\nBolivia NIT\n\n- **BRA_CPF**\nBrazil CPF\n\n- **CHL_RUT**\nChile RUT\n\n- **COL_NIT**\nColombia NIT\n\n- **CRI_NITE**\nCosta Rica NITE\n\n- **DEU_TAX_ID**\nGermany Tax ID (Identifikationsnummer)\n\n- **DOM_RNC**\nDominican Republic RNC\n\n- **ECU_RUC**\nEcuador RUC\n\n- **FRA_SPI**\nFrance SPI (Reference Tax Number)\n\n- **GBR_UTR**\nUK UTR (Unique Taxpayer Reference)\n\n- **GBR_NINO**\nUK NINO (National Insurance Number)\n\n- **GTM_NIT**\nGuatemala NIT\n\n- **HND_RTN**\nHonduras RTN\n\n- **HUN_TIN**\nHungary TIN Number\n\n- **IDN_KTP**\nIndonesia KTP\n\n- **IND_PAN**\nIndia PAN Number\n\n- **ISR_TAX_ID**\nIsrael Tax ID (Teudat Zehut)\n\n- **ITA_TAX_ID**\nItaly Tax ID (Codice Fiscale)\n\n- **JPN_TAX_ID**\nJapan Tax ID (Koijin Bango)\n\n- **MEX_RFC**\nMexico RFC\n\n- **NIC_RUC**\nNicaragua RUC\n\n- **NLD_TIN**\nNetherlands TIN Number\n\n- **PAN_RUC**\nPanama RUC\n\n- **PER_RUC**\nPeru RUC\n\n- **PRY_RUC**\nParaguay RUC\n\n- **SGP_NRIC**\nSingapore NRIC\n\n- **SGP_FIN**\nSingapore FIN\n\n- **SGP_ASGD**\nSingapore ASGD\n\n- **SGP_ITR**\nSingapore ITR\n\n- **SLV_NIT**\nEl Salvador NIT\n\n- **SWE_TAX_ID**\nSweden Tax ID (Personnummer)\n\n- **URY_RUT**\nUruguay RUT\n\n- **VEN_RIF**\nVenezuela RIF\n\n- **NATIONAL_ID**\nNational ID number, if a tax ID number is not available\n\n- **PASSPORT**\nPassport number, if a tax ID number is not available\n\n- **PERMANENT_RESIDENT**\nPermanent resident number, if a tax ID number is not available\n\n- **DRIVER_LICENSE**\nDriver's license number, if a tax ID number is not available\n\n- **OTHER_GOV_ID**\nOther government issued identifier, if a tax ID number is not available\n\n- **NOT_SPECIFIED**\nOther Tax IDs", + "enum": [ + "USA_SSN", + "USA_ITIN", + "ARG_AG_CUIT", + "AUS_TFN", + "AUS_ABN", + "BOL_NIT", + "BRA_CPF", + "CHL_RUT", + "COL_NIT", + "CRI_NITE", + "DEU_TAX_ID", + "DOM_RNC", + "ECU_RUC", + "FRA_SPI", + "GBR_UTR", + "GBR_NINO", + "GTM_NIT", + "HND_RTN", + "HUN_TIN", + "IDN_KTP", + "IND_PAN", + "ISR_TAX_ID", + "ITA_TAX_ID", + "JPN_TAX_ID", + "MEX_RFC", + "NIC_RUC", + "NLD_TIN", + "PAN_RUC", + "PER_RUC", + "PRY_RUC", + "SGP_NRIC", + "SGP_FIN", + "SGP_ASGD", + "SGP_ITR", + "SLV_NIT", + "SWE_TAX_ID", + "URY_RUT", + "VEN_RIF", + "NATIONAL_ID", + "PASSPORT", + "PERMANENT_RESIDENT", + "DRIVER_LICENSE", + "OTHER_GOV_ID", + "NOT_SPECIFIED" + ], + "example": "USA_SSN", + "title": "TaxIdType", + "type": "string" + }, + "TenderOfferActivityV2": { + "allOf": [ + { + "$ref": "#/components/schemas/CommonNTAActivityV2" }, - "country": { - "description": "[ISO 3166-1 alpha-3](https://www.iso.org/iso-3166-country-codes.html).\nrequired if `street_address` is set\n", + { + "$ref": "#/components/schemas/CommonVOFSubtypeActivityV2" + } + ], + "type": "object" + }, + "TimeInForce": { + "description": "The Time-In-Force values supported by Alpaca vary based on the order's security type. Here is a breakdown of the supported TIFs for each specific security type:\n- Equity trading: day, gtc, opg, cls, ioc, fok.\n- Options trading: day, gtc.\n- Crypto trading: gtc, ioc.\n\nBelow are the descriptions of each TIF:\n- day:\n A day order is eligible for execution only on the day it is live. By default, the order is only valid during Regular Trading Hours (9:30am - 4:00pm ET). If unfilled after the closing auction, it is automatically canceled. If submitted after the close, it is queued and submitted the following trading day. However, if marked as eligible for extended hours, the order can also execute during supported extended hours.\n\n- gtc:\n The order is good until canceled. Non-marketable GTC limit orders are subject to price adjustments to offset corporate actions affecting the issue. We do not currently support Do Not Reduce (DNR) orders to opt out of such price adjustments.\n\n- opg:\n Use this TIF with a market/limit order type to submit \"market on open\" (MOO) and \"limit on open\" (LOO) orders. This order is eligible to execute only in the market opening auction. Any unfilled orders after the open will be cancelled. OPG orders submitted after 9:28am but before 7:00pm ET will be rejected. OPG orders submitted after 7:00pm will be queued and routed to the following day's opening auction. On open/on close orders are routed to the primary exchange. Such orders do not necessarily execute exactly at 9:30am / 4:00pm ET but execute per the exchange's auction rules.\n\n- cls:\n Use this TIF with a market/limit order type to submit \"market on close\" (MOC) and \"limit on close\" (LOC) orders. This order is eligible to execute only in the market closing auction. Any unfilled orders after the close will be cancelled. CLS orders submitted after 3:50pm but before 7:00pm ET will be rejected. CLS orders submitted after 7:00pm will be queued and routed to the following day's closing auction.\n\n- ioc:\n An Immediate Or Cancel (IOC) order requires all or part of the order to be executed immediately. Any unfilled portion of the order is canceled. Most market makers who receive IOC orders will attempt to fill the order on a principal basis only, and cancel any unfilled balance. On occasion, this can result in the entire order being cancelled if the market maker does not have any existing inventory of the security in question.\n\n- fok:\n A Fill or Kill (FOK) order is only executed if the entire order quantity can be filled, otherwise the order is canceled.", + "enum": [ + "day", + "gtc", + "opg", + "cls", + "ioc", + "fok" + ], + "example": "gtc", + "title": "TimeInForce", + "type": "string" + }, + "TokenizationIssuer": { + "description": "The tokenized asset's issuer", + "enum": [ + "binance", + "coinbase", + "ondo", + "st0x", + "xstocks" + ], + "example": "xstocks", + "title": "TokenizationIssuer", + "type": "string" + }, + "TokenizationMintCallback": { + "properties": { + "client_account_id": { + "description": "Alpaca account ID (UUID) of the Authorized Participant. Exactly one of `client_external_account_id` or `client_account_id` must be provided, matching the identifier used on the original mint request. The deprecated `client_id` field is still accepted as an alias for `client_external_account_id` during the deprecation window.", + "format": "uuid", "type": "string" }, - "email_address": { - "description": "at least one of `email_address`, `phone_number` or\n`street_address` is required\n", - "example": "jane.doe@example.com", - "format": "email", + "client_external_account_id": { + "description": "Customer's client identifier on the issuer's platform. Exactly one of `client_external_account_id` or `client_account_id` must be provided, matching the identifier used on the original mint request. The deprecated `client_id` field is still accepted as an alias for `client_external_account_id` during the deprecation window.", "type": "string" }, - "family_name": { - "example": "Doe", - "type": "string" + "client_id": { + "deprecated": true, + "description": "Customer's client identifier on the issuer's platform. Use `client_external_account_id` instead.", + "type": "string", + "x-deprecation": { + "reason": "Use client_external_account_id instead.", + "since": "2026-07-15", + "sunset": "2026-10-15" + } }, - "given_name": { - "example": "Jane", - "type": "string" + "network": { + "$ref": "#/components/schemas/TokenizationNetwork" }, - "phone_number": { - "description": "at least one of `email_address`, `phone_number` or\n`street_address` is required\n", + "tokenization_request_id": { + "description": "Unique identifier of the tokenization request", + "format": "uuid", "type": "string" }, - "postal_code": { - "description": "required if `street_address` is set\n", + "tx_hash": { + "description": "Transaction hash of the completed request on the blockchain", "type": "string" }, - "state": { - "description": "required if `street_address` is set\n", + "wallet_address": { + "description": "The wallet address to receive the tokenized asset", "type": "string" - }, - "street_address": { - "description": "at least one of `email_address`, `phone_number` or\n`street_address` is required\n", - "items": { - "type": "string" - }, - "type": "array" } }, "required": [ - "given_name", - "family_name" + "tokenization_request_id", + "tx_hash" ], + "title": "TokenizationMintCallback", "type": "object" }, - "USDAccountTradingLimit": { + "TokenizationMintRequest": { "properties": { - "available": { - "description": "The remaining net buying limit that can be used for trading.", - "format": "decimal", - "type": "string" + "issuer": { + "$ref": "#/components/schemas/TokenizationIssuer" }, - "daily_net_limit": { - "description": "The net buying limit that can be reached before further trading activity is restricted. Please reach out to learn more about how this limit is determined.", - "format": "decimal", + "network": { + "$ref": "#/components/schemas/TokenizationNetwork" + }, + "qty": { + "description": "The underlying quantity to convert into the tokenized asset. It can be fractional.", "type": "string" }, - "held": { - "description": "The limit that is currently being held for open orders", - "format": "decimal", + "underlying_symbol": { + "description": "The underlying asset symbol", "type": "string" }, - "used": { - "description": "The real time net value of cash inflows (buy trades, etc.) with cash outflows (sell trades, etc). This will be dynamic throughout the day based on user activity, with executed orders being reset at the start of the next trading day.", - "format": "decimal", + "wallet_address": { + "description": "The wallet address to receive the tokenized asset", "type": "string" } }, - "title": "USDAccountTradingLimit", + "required": [ + "underlying_symbol", + "qty", + "issuer", + "network", + "wallet_address" + ], + "title": "TokenizationMintRequest", "type": "object" }, - "USDPosition": { - "description": "Position values in USD. This is returned for LCT (non-USD) accounts only.", + "TokenizationMintResponse": { "properties": { - "avg_entry_price": { - "description": "Average entry price of the position in USD", - "example": "71.43", - "format": "decimal", + "created_at": { + "format": "date-time", "type": "string" }, - "change_today": { - "description": "Percent change from last day price (by a factor of 1)", - "example": "0.67", - "format": "decimal", - "type": "string" + "issuer": { + "$ref": "#/components/schemas/TokenizationIssuer" }, - "cost_basis": { - "description": "Total cost basis in USD", - "example": "333.33", - "format": "decimal", - "type": "string" + "network": { + "$ref": "#/components/schemas/TokenizationNetwork" }, - "current_price": { - "description": "Current asset price per share in USD", - "example": "80.0", - "format": "decimal", + "qty": { + "description": "The quantity to convert for this tokenization request. It can be fractional.", "type": "string" }, - "lastday_price": { - "description": "Last day's asset price per share based on the closing value of the last trading day in USD", - "example": "79.33", - "format": "decimal", - "type": "string" - }, - "market_value": { - "description": "Total market value of the position in USD", - "example": "400.00", - "format": "decimal", - "type": "string" - }, - "unrealized_intraday_pl": { - "description": "Unrealized profit/loss in USD for the day", - "example": "6.67", - "format": "decimal", - "type": "string" + "status": { + "$ref": "#/components/schemas/TokenizationRequestStatus" }, - "unrealized_intraday_plpc": { - "description": "Unrealized interday profit/loss percent (by a factor of 1)", - "example": "0.0084", - "format": "decimal", + "token_symbol": { + "description": "The tokenized asset symbol", "type": "string" }, - "unrealized_pl": { - "description": "Unrealized profit/loss in USD", - "example": "66.67", - "format": "decimal", + "tokenization_request_id": { + "description": "Unique identifier of the tokenization request set by Alpaca", "type": "string" }, - "unrealized_plpc": { - "description": "Unrealized profit/loss percent (by a factor of 1)", - "example": "0.2", - "format": "decimal", + "underlying_symbol": { + "description": "The underlying asset symbol", "type": "string" } }, - "title": "USDPosition", + "required": [ + "tokenization_request_id", + "status", + "underlying_symbol", + "token_symbol", + "qty", + "created_at", + "issuer", + "network" + ], + "title": "TokenizationMintResponse", "type": "object" }, - "UnitSplitActivityV2": { - "allOf": [ - { - "$ref": "#/components/schemas/CommonSplitStockActivityV2" - }, - { - "properties": { - "alternate_cusip": { - "description": "CUSIP for the alternate security after the split", - "type": "string" - }, - "alternate_qty": { - "description": "Quantity of alternate shares received", - "type": "string" - }, - "alternate_rate": { - "description": "Ratio of alternate shares received", - "type": "string" - }, - "alternate_symbol": { - "description": "Symbol for the alternate security after the split", - "type": "string" - }, - "effective_date": { - "description": "When the unit split becomes effective", - "format": "date", - "minLength": 1, - "type": "string" - }, - "new_symbol": { - "description": "Symbol of the new security after the split", - "type": "string" - }, - "old_symbol": { - "description": "Symbol of the old security before the split", - "type": "string" - } - }, - "required": [ - "old_symbol", - "new_symbol", - "alternate_cusip", - "alternate_symbol", - "alternate_rate", - "alternate_qty", - "effective_date" - ], - "type": "object" - } + "TokenizationNetwork": { + "description": "The token's blockchain network", + "enum": [ + "arbitrum", + "base", + "binance", + "cronos", + "ethereum", + "hypercore", + "hyperevm", + "mantle", + "robinhood", + "solana", + "ton", + "tron" ], - "description": "Unit split", - "type": "object" + "example": "solana", + "title": "TokenizationNetwork", + "type": "string" }, - "UpdateOrderRequest": { - "description": "Represents the fields that are editable in an order replace/update call.\n\nNote: client_order_id is currently not editable on its own, one of the other fields must be changed at the same time to effectively replace the order.\n\nNote: `qty` and `notional` are mutually exclusive on a single replace request. `notional` is only valid for IPO indications of interest (`asset_class: \"ipo\"`); non-IPO notional orders cannot be replaced at all.", + "TokenizationRedeemRequest": { "properties": { - "client_order_id": { - "description": "A unique identifier for the new order. Automatically generated if not sent. (<= 128 characters)", - "example": "61e69015-8549-4bfd-b9c3-01e75843f47d", - "maxLength": 128, + "client_account_id": { + "description": "Alpaca account ID (UUID) of the Authorized Participant. Exactly one of `client_external_account_id` or `client_account_id` must be provided to identify the Authorized Participant whose account will receive the underlying asset. The deprecated `client_id` field is still accepted as an alias for `client_external_account_id` during the deprecation window.", + "format": "uuid", "type": "string" }, - "limit_price": { - "description": "Required if original order's `type` field was `limit` or `stop_limit`.\nIn case of `mleg`, the limit_price parameter is expressed with the following notation:\n- A positive value indicates a debit, representing a cost or payment to be made.\n- A negative value signifies a credit, reflecting an amount to be received.", - "example": "3.14", - "format": "decimal", + "client_external_account_id": { + "description": "Customer's client identifier on the issuer's platform. Exactly one of `client_external_account_id` or `client_account_id` must be provided to identify the Authorized Participant whose account will receive the underlying asset. The deprecated `client_id` field is still accepted as an alias for `client_external_account_id` during the deprecation window.", "type": "string" }, - "notional": { - "description": "New notional (dollar amount) for the order. Only valid for IPO indications of interest (`asset_class: \"ipo\"`); will be rejected for any other asset class.\nMutually exclusive with `qty` on the same replace request.", - "example": "750", - "format": "decimal", + "client_id": { + "deprecated": true, + "description": "Customer's client identifier on the issuer's platform. Use `client_external_account_id` instead.", + "type": "string", + "x-deprecation": { + "reason": "Use client_external_account_id instead.", + "since": "2026-07-15", + "sunset": "2026-10-15" + } + }, + "issuer_request_id": { + "description": "Unique identifier of the redemption request set by the issuer", "type": "string" }, + "network": { + "$ref": "#/components/schemas/TokenizationNetwork" + }, "qty": { - "description": "You can only patch full shares for now.\n\nQty of equity fractional orders are not allowed to change. Non-IPO notional orders cannot be replaced at all - no fields (qty, limit_price, stop_price, etc.) can be modified; cancel and resubmit instead.\nIn case of multi-leg orders represents the number of units to trade of this strategy.", - "example": "4", - "format": "decimal", + "description": "The quantity to convert into the underlying asset. It can be fractional.", "type": "string" }, - "stop_price": { - "description": "Required if original order's `type` field was stop or stop_limit", - "example": "3.14", - "format": "decimal", + "token_symbol": { + "description": "The tokenized asset symbol", "type": "string" }, - "time_in_force": { - "$ref": "#/components/schemas/TimeInForce" + "tx_hash": { + "description": "Transaction hash of the completed request on the blockchain", + "type": "string" }, - "trail": { - "description": "The new value of the trail_price or trail_percent", - "example": "3.14", - "format": "decimal", + "underlying_symbol": { + "description": "The underlying asset symbol", "type": "string" - } - }, - "title": "OrderUpdateRequest", - "type": "object" - }, - "Usd": { - "properties": { - "amount": { - "format": "decimal", + }, + "wallet_address": { + "description": "The address where the redeemed tokens were originally held", "type": "string" } }, "required": [ - "amount" + "issuer_request_id", + "underlying_symbol", + "token_symbol", + "qty", + "network", + "wallet_address", + "tx_hash" ], + "title": "TokenizationRedeemRequest", "type": "object" }, - "W8benDocument": { - "description": "Use this property (instead of the content property) to upload W-8 BEN data in JSON format.", + "TokenizationRedeemResponse": { "properties": { - "additional_conditions": { - "description": "Any additional conditions to specify", - "type": "string" - }, - "country_citizen": { - "description": "The country that the applicant is a citizen of", - "type": "string" - }, - "date": { - "description": "date signed", - "format": "date", - "type": "string" - }, - "date_of_birth": { - "description": "date of birth of applicant", - "format": "date", - "type": "string" - }, - "foreign_tax_id": { - "description": "Applicant's tax id in their home country", + "created_at": { + "format": "date-time", "type": "string" }, - "ftin_not_required": { - "description": "Required if foreign_tax_id and tax_id_ssn are empty.", - "type": "boolean" + "issuer": { + "$ref": "#/components/schemas/TokenizationIssuer" }, - "full_name": { - "description": "Full name of applicant", + "issuer_request_id": { + "description": "Unique identifier of the redemption request set by the issuer", "type": "string" }, - "income_type": { - "description": "Income type of applicant", - "type": "string" + "network": { + "$ref": "#/components/schemas/TokenizationNetwork" }, - "ip_address": { - "description": "IP address of applicant when signed", + "qty": { + "description": "The quantity to convert into the underlying asset. It can be fractional.", "type": "string" }, - "mailing_address_city_state": { - "description": "Mailing city/state of applicant", - "type": "string" + "status": { + "$ref": "#/components/schemas/TokenizationRequestStatus" }, - "mailing_address_country": { - "description": "Mailing country for applicant", + "token_symbol": { + "description": "The tokenized asset symbol", "type": "string" }, - "mailing_address_street": { - "description": "Mailing street address for applicant", + "tokenization_request_id": { + "description": "Unique identifier of the tokenization request set by Alpaca", "type": "string" }, - "paragraph_number": { + "tx_hash": { + "description": "Transaction hash of the completed request on the blockchain", "type": "string" }, - "percent_rate_withholding": { - "type": "integer" - }, - "permanent_address_city_state": { - "description": "Permanent city/state of applicant", - "type": "string" + "type": { + "$ref": "#/components/schemas/TokenizationRequestType" }, - "permanent_address_country": { - "description": "Permanent country of residence of applicant", + "underlying_symbol": { + "description": "The underlying asset symbol", "type": "string" }, - "permanent_address_street": { - "description": "Permanent street address of applicant", - "type": "string" - }, - "reference_number": { - "type": "string" - }, - "residency": { - "description": "Country of residency of applicant", - "type": "string" - }, - "revision": { - "description": "Revision of the W8BEN form", - "type": "string" - }, - "signer_full_name": { - "description": "Full name of signing user", - "type": "string" - }, - "tax_id_ssn": { - "description": "TaxID/SSN of applicant", - "type": "string" - }, - "timestamp": { - "description": "Timestamp when form data was gathered", - "format": "time", + "wallet_address": { + "description": "The address where the redeemed tokens were originally held", "type": "string" } }, "required": [ - "country_citizen", - "date", - "date_of_birth", - "full_name", - "ip_address", - "permanent_address_city_state", - "permanent_address_country", - "permanent_address_street", - "revision", - "timestamp", - "signer_full_name" - ], - "type": "object", - "x-examples": { - "Example 1": { - "additional_conditions": "None", - "country_citizen": "Australia", - "date": "2021-06-14", - "date_of_birth": "1970-01-01", - "foreign_tax_id": "123 456 789", - "ftin_not_required": false, - "full_name": "John Doe", - "income_type": "interest", - "ip_address": "127.0.0.1", - "mailing_address_city_state": "Adelaide, South Australia", - "mailing_address_country": "Australia", - "mailing_address_street": "51 Main St", - "paragraph_number": "15", - "percent_rate_withholding": 5, - "permanent_address_city_state": "Adelaide, South Australia", - "permanent_address_country": "Australia", - "permanent_address_street": "20 Main St", - "reference_number": "abc123", - "residency": "Australia", - "revision": "10-2021", - "signer_full_name": "Mr. Signing User", - "tax_id_ssn": "123-00-456", - "timestamp": "2021-06-14T09:31:05Z" - } - } - }, - "WRMActivityV2": { - "allOf": [ - { - "$ref": "#/components/schemas/CommonCaActivityV2" - }, - { - "properties": { - "cusip": { - "description": "The CUSIP of the security involved with the activity", - "type": "string" - }, - "removed_qty": { - "description": "The removed quantity", - "type": "string" - }, - "symbol": { - "description": "The symbol of the security involved with the activity", - "type": "string" - } - }, - "required": [ - "cusip", - "symbol", - "removed_qty" - ], - "type": "object" - } - ], - "description": "Worthless Removal", - "type": "object" - }, - "WarrantExerciseElectionActivityV2": { - "allOf": [ - { - "$ref": "#/components/schemas/CommonNTAActivityV2" - }, - { - "$ref": "#/components/schemas/CommonVOFSubtypeActivityV2" - } + "tokenization_request_id", + "issuer_request_id", + "created_at", + "type", + "status", + "underlying_symbol", + "token_symbol", + "qty", + "issuer", + "network", + "wallet_address", + "tx_hash" ], + "title": "TokenizationRedeemResponse", "type": "object" }, - "Watchlist": { - "description": "Represents a set of securities observed by a user.", + "TokenizationRequest": { "properties": { - "account_id": { - "description": "Unique identifier of the account that owns this watchlist.\n", + "account": { + "deprecated": true, + "description": "Alpaca account ID associated with this tokenization request. Use `client_account_id` instead.", + "type": "string", + "x-deprecation": { + "reason": "Use client_account_id instead.", + "since": "2026-07-15", + "sunset": "2026-10-15" + } + }, + "client_account_id": { + "description": "Alpaca account UUID of the Authorized Participant associated with this tokenization request", "format": "uuid", "type": "string" }, - "assets": { - "description": "The contents of the watchlist, in the order as registered", - "items": { - "$ref": "#/components/schemas/Asset" - }, - "type": "array" + "client_external_account_id": { + "description": "Issuer-side account identifier of the Authorized Participant associated with this tokenization request", + "type": "string" + }, + "client_request_id": { + "description": "Authorized Participant-supplied label associated with this tokenization request", + "type": "string" }, "created_at": { - "description": "When watchlist was created", "format": "date-time", "type": "string" }, - "id": { - "description": "Unique identifier of the watchlist itself.\n", - "format": "uuid", - "type": "string" + "fees": { + "description": "Fees charged for this tokenization request", + "type": [ + "string", + "null" + ] }, - "name": { - "description": "User friendly Name of watchlist", - "pattern": "^[a-zA-Z0-9]+$", - "type": "string" + "issuer": { + "$ref": "#/components/schemas/TokenizationIssuer" }, - "updated_at": { - "description": "When watchlist was last updated", - "format": "date-time", - "type": "string" - } - }, - "required": [ - "id", - "account_id", - "created_at", - "updated_at", - "name" - ], - "title": "Watchlist", - "type": "object" - }, - "WatchlistWithoutAsset": { - "description": "Represents a set of securities observed by a user.", - "properties": { - "account_id": { - "description": "Unique identifier of the account that owns this watchlist.\n", - "format": "uuid", + "issuer_account": { + "deprecated": true, + "description": "Issuer's account ID associated with this tokenization request. Use `client_external_account_id` instead.", + "type": "string", + "x-deprecation": { + "reason": "Use client_external_account_id instead.", + "since": "2026-07-15", + "sunset": "2026-10-15" + } + }, + "issuer_request_id": { + "description": "Unique identifier of the tokenization request set by the issuer", + "type": [ + "string", + "null" + ] + }, + "network": { + "$ref": "#/components/schemas/TokenizationNetwork" + }, + "qty": { + "description": "The quantity to convert for this tokenization request. It can be fractional.", "type": "string" }, - "created_at": { - "description": "When watchlist was created", - "format": "date-time", + "status": { + "$ref": "#/components/schemas/TokenizationRequestStatus" + }, + "token_symbol": { + "description": "The tokenized asset symbol", "type": "string" }, - "id": { - "description": "Unique identifier of the watchlist itself.\n", - "format": "uuid", + "tokenization_request_id": { + "description": "Unique identifier of the tokenization request set by Alpaca", "type": "string" }, - "name": { - "description": "User friendly Name of watchlist", - "pattern": "^[a-zA-Z0-9]+$", + "tx_hash": { + "description": "Transaction hash of the completed request on the blockchain", + "type": [ + "string", + "null" + ] + }, + "type": { + "$ref": "#/components/schemas/TokenizationRequestType" + }, + "underlying_symbol": { + "description": "The underlying asset symbol", "type": "string" }, "updated_at": { - "description": "When watchlist was last updated", "format": "date-time", + "type": [ + "string", + "null" + ] + }, + "wallet_address": { + "description": "The wallet address associated with this tokenization request", "type": "string" } }, "required": [ - "id", - "account_id", + "tokenization_request_id", "created_at", - "updated_at", - "name" + "type", + "status", + "underlying_symbol", + "token_symbol", + "qty", + "issuer", + "network", + "wallet_address" ], - "title": "Watchlist", + "title": "TokenizationRequest", "type": "object" }, - "WhitelistedAddress": { - "properties": { - "address": { - "description": "The whitelisted address", - "type": "string" - }, - "asset": { - "description": "Symbol of underlying asset for the whitelisted address", - "type": "string" - }, - "chain": { - "description": "Underlying network this address represents", - "type": "string" - }, - "created_at": { - "description": "Timestamp (RFC3339) of account creation.", - "format": "date-time", - "type": "string" - }, - "id": { - "description": "Unique ID for whitelisted address", - "type": "string" - }, - "status": { - "description": "Status of whitelisted address which is either APPROVED or PENDING. Whitelisted addresses will be subjected to a 24 waiting period. After the waiting period is over the status will become APPROVED.", - "enum": [ - "APPROVED", - "PENDING" - ], - "type": "string" - } - }, - "type": "object" - }, - "bic": { - "description": "Business Identifier Code (BIC/SWIFT).", - "example": "IRVTUS3NXXX", - "maxLength": 11, - "minLength": 11, - "pattern": "^[A-Z0-9]{11}$", + "TokenizationRequestStatus": { + "description": "Status of the tokenization request", + "enum": [ + "pending", + "rejected", + "completed" + ], + "example": "completed", + "title": "TokenizationRequestStatus", "type": "string" }, - "bond_status": { - "description": "Status of the bond.", + "TokenizationRequestType": { + "description": "Tokenization request type", "enum": [ - "outstanding", - "matured", - "pre_issuance" + "mint", + "redeem" ], + "example": "mint", + "title": "TokenizationRequestType", "type": "string" }, - "calendar_day": { - "description": "A calendar day.", + "TradeAccount": { + "description": "This is an extended version of the Account model found [in the trading api](https://alpaca.markets/docs/api-references/trading-api/account/#account-entity).\n\nExtra data has been added that would be useful for brokers.", + "examples": [ + { + "account_blocked": false, + "account_number": "927584925", + "accrued_fees": "0", + "admin_configurations": {}, + "balance_asof": "2021-04-01", + "buying_power": "103556.8572572922", + "cash": "24861.91", + "cash_transferable": "24861.91", + "cash_withdrawable": "17861.91", + "clearing_broker": "VELOX", + "created_at": "2021-03-01T13:28:49.270232Z", + "crypto_status": "ACTIVE", + "currency": "USD", + "effective_buying_power": "103556.8572572922", + "equity": "28059.3882330664", + "id": "c8f1ef5d-edc0-4f23-9ee4-378f19cb92a4", + "initial_margin": "1598.7391165332", + "intraday_adjustments": "0", + "last_buying_power": "104433.9158860662", + "last_cash": "23861.91", + "last_equity": "26977.323677655", + "last_initial_margin": "1557.7068388275", + "last_long_market_value": "3115.413677655", + "last_maintenance_margin": "934.6241032965", + "last_options_buying_power": "25419.62", + "last_regt_buying_power": "50839.233677655", + "last_short_market_value": "0", + "long_market_value": "3197.4782330664", + "maintenance_margin": "959.24346991992", + "multiplier": "2", + "non_marginable_buying_power": "24861.91", + "options_approved_level": 0, + "options_buying_power": "26460.65", + "options_trading_level": 0, + "pending_reg_taf_fees": "0", + "pending_transfer_out": "0", + "portfolio_value": "28059.3882330664", + "position_market_value": "3197.4782330664", + "previous_close": "2021-04-01T19:00:00-04:00", + "regt_buying_power": "52921.2982330664", + "short_market_value": "0", + "shorting_enabled": true, + "sma": "26758.0590204615", + "status": "ACTIVE", + "trade_suspended_by_user": false, + "trading_blocked": false, + "transfers_blocked": false + }, + { + "account_blocked": false, + "account_number": "601612064", + "accrued_fees": "0", + "admin_configurations": {}, + "balance_asof": "2022-02-08", + "buying_power": "83567.42", + "cash": "83567.42", + "cash_transferable": "41783.71", + "cash_withdrawable": "0", + "clearing_broker": "VELOX", + "created_at": "2022-01-21T21:25:26.713802Z", + "crypto_status": "PAPER_ONLY", + "currency": "USD", + "effective_buying_power": "83567.42", + "equity": "83567.42", + "id": "56712986-9ff7-4d8f-8e52-077e099e533e", + "initial_margin": "0", + "intraday_adjustments": "0", + "last_buying_power": "41783.71", + "last_cash": "41783.71", + "last_equity": "41783.71", + "last_initial_margin": "0", + "last_long_market_value": "0", + "last_maintenance_margin": "0", + "last_options_buying_power": "41783.71", + "last_regt_buying_power": "41783.71", + "last_short_market_value": "0", + "long_market_value": "0", + "maintenance_margin": "0", + "multiplier": "1", + "non_marginable_buying_power": "41783.71", + "options_approved_level": 2, + "options_buying_power": "83567.42", + "options_trading_level": 1, + "pending_reg_taf_fees": "0.01", + "pending_transfer_in": "0", + "pending_transfer_out": "0", + "portfolio_value": "83567.42", + "position_market_value": "0", + "previous_close": "2022-02-08T19:00:00-05:00", + "regt_buying_power": "83567.42", + "short_market_value": "0", + "shorting_enabled": false, + "sma": "0", + "status": "ACTIVE", + "trade_suspended_by_user": false, + "trading_blocked": false, + "transfers_blocked": false + } + ], "properties": { - "core_end": { - "description": "The end time of the core market session.", - "example": "2025-01-02T16:00:00-05:00", - "format": "date-time", - "type": "string" + "account_blocked": { + "description": "If true, the account activity by user is prohibited.", + "example": false, + "type": "boolean" }, - "core_start": { - "description": "The start time of the core market session.", - "example": "2025-01-02T09:30:00-05:00", - "format": "date-time", - "type": "string" + "account_number": { + "description": "The account number", + "example": "927584925", + "type": [ + "string", + "null" + ] }, - "date": { - "description": "The date of the calendar day.", - "example": "2025-01-02", - "format": "date", + "accrued_fees": { + "description": "Accrued fees", + "example": "12345.6789", + "format": "decimal", "type": "string" }, - "lunch_end": { - "description": "The end time of the lunch session.", - "format": "date-time", + "admin_configurations": { + "$ref": "#/components/schemas/AdminConfigurations" + }, + "balance_asof": { + "description": "The date of the snapshot for `last_*` fields", + "example": "2021-04-01", "type": "string" }, - "lunch_start": { - "description": "The start time of the lunch session.", - "format": "date-time", + "buying_power": { + "description": "Current available cash buying power. If multiplier = 2 then buying_power = max(equity-initial_margin(0) * 2). If multiplier = 1 then buying_power = cash.", + "example": "12345.6789", + "format": "decimal", "type": "string" }, - "post_end": { - "description": "The end time of the after-hours session.", - "example": "2025-01-02T20:00:00-05:00", - "format": "date-time", + "cash": { + "description": "Cash balance", + "example": "12345.6789", + "format": "decimal", "type": "string" }, - "post_start": { - "description": "The start time of the after-hours session.", - "example": "2025-01-02T16:00:00-05:00", - "format": "date-time", + "cash_transferable": { + "description": "Cash available for transfer (JNLC)", + "example": "12345.6789", "type": "string" }, - "pre_end": { - "description": "The end time of the pre-market session.", - "example": "2025-01-02T09:30:00-05:00", - "format": "date-time", + "cash_withdrawable": { + "description": "Cash available for withdrawal", + "example": "12345.6789", + "format": "decimal", "type": "string" }, - "pre_start": { - "description": "The start time of the pre-market session.", - "example": "2025-01-02T04:00:00-05:00", - "format": "date-time", + "clearing_broker": { + "description": "Clearing broker", + "example": "Velox", "type": "string" }, - "settlement_date": { - "description": "The settlement date.", - "example": "2025-01-03", - "format": "date", + "created_at": { + "description": "Timestamp this account was created at", + "example": "2021-03-01T13:28:49.270232Z", "type": "string" - } - }, - "required": [ - "date", - "core_start", - "core_end" - ], - "type": "object" - }, - "call_type": { - "description": "The type of call on the bond refers to one of a variety of circumstances under which a callable bond may be called.", - "enum": [ - "ordinary", - "make_whole", - "regulatory", - "special" - ], - "type": "string" - }, - "clock": { - "properties": { - "is_market_day": { - "description": "Whether the clock is on a market day.", - "type": "boolean" }, - "market": { - "$ref": "#/components/schemas/public_market" + "crypto_status": { + "$ref": "#/components/schemas/AccountStatus" }, - "next_market_close": { - "description": "Next market close timestamp", - "format": "date-time", + "currency": { + "description": "Always USD", + "example": "USD", "type": "string" }, - "next_market_open": { - "description": "Next market open timestamp", - "format": "date-time", + "effective_buying_power": { + "description": "Effective buying power (duplicate of buying power)", + "example": "12345.6789", + "format": "decimal", "type": "string" }, - "phase": { - "$ref": "#/components/schemas/phase" + "equity": { + "description": "cash + long_market_value + short_market_value", + "example": "12345.6789", + "format": "decimal", + "type": "string" }, - "phase_until": { - "description": "The end of the current phase.", - "format": "date-time", + "id": { + "description": "The account ID", + "example": "c8f1ef5d-edc0-4f23-9ee4-378f19cb92a4", + "format": "uuid", "type": "string" }, - "timestamp": { - "description": "The time on the clock.", - "format": "date-time", - "type": "string" - } - }, - "required": [ - "market", - "timestamp", - "is_market_day", - "next_market_open", - "next_market_close", - "phase", - "phase_until" - ], - "type": "object" - }, - "clock_resp": { - "description": "Clock response.", - "properties": { - "clocks": { - "items": { - "$ref": "#/components/schemas/clock" - }, - "type": "array" - } - }, - "required": [ - "clocks" - ], - "type": "object", - "x-go-name": "ClockResp" - }, - "coupon_frequency": { - "description": "How often the coupon is paid", - "enum": [ - "annual", - "semi_annual", - "quarterly", - "monthly", - "zero" - ], - "type": "string" - }, - "coupon_type": { - "description": "The type of the coupon rate", - "enum": [ - "fixed", - "floating", - "zero" - ], - "type": "string" - }, - "day_count": { - "description": "The day count convention used to calculate accrued interest.\n\n- `A/360`: calculates the daily interest using a 360-day year and then multiplies that by the actual number of days in each time period.\n- `A/365`: calculates the daily interest using a 365-day year and then multiplies that by the actual number of days in each time period.\n- `30/360`: calculates the daily interest using a 360-day year and then multiplies that by 30 (standardized month).\n- `30/365`: calculates the daily interest using a 365-day year and then multiplies that by 30 (standardized month).\n- `A/A`: calculates the daily interest using the actual number of days in the year and then multiplies that by the actual number of days in each time period.\n- `30E/360`: number of days equals to the actual number of days (for February). If the start date or the end date of the period is the 31st of a month, that date is set to the 30th. The number of days in a year is 360.\n- `B/252`: calculates the daily interest using a 252-business-day year and then multiplies that by the actual number of days in each time period.\n- `A/364`: calculates the daily interest using a 364-day year and then multiplies that by the actual number of days in each time period.\n", - "enum": [ - "A/360", - "A/365", - "30/360", - "30/365", - "A/A", - "30E/360", - "B/252", - "A/364" - ], - "type": "string" - }, - "legacy_calendar_day": { - "description": "A calendar day.", - "properties": { - "close": { - "description": "The time the market closes at on this date in HH:MM format.", - "example": "16:00", + "initial_margin": { + "description": "Reg T initial margin requirement (continuously updated value)", + "example": "12345.6789", + "format": "decimal", "type": "string" }, - "date": { - "description": "Date string in YYYY-MM-DD format.", - "example": "2025-01-02", - "format": "date", + "intraday_adjustments": { + "description": "The intraday adjustment by non_trade_activities such as fund deposit/withdraw.\n", + "example": "0", "type": "string" }, - "open": { - "description": "The time the market opens at on this date in HH:MM format.", - "example": "09:30", + "last_buying_power": { + "description": "Value of buying_power as of previous trading day at 16:00:00 ET", + "example": "12345.6789", "type": "string" }, - "session_close": { - "description": "The time the session closes at on this date in HHMM format.", - "example": "2000", + "last_cash": { + "description": "Value of all cash as of previous trading day at 16:00:00 ET", + "example": "12345.6789", "type": "string" }, - "session_open": { - "description": "The time the session opens at on this date in HHMM format.", - "example": "0400", + "last_equity": { + "description": "Equity as of previous trading day at 16:00:00 ET", + "example": "12345.6789", + "format": "decimal", "type": "string" }, - "settlement_date": { - "description": "Date string in YYYY-MM-DD format. Representing the settlement date for the trade date.", - "example": "2025-01-03", - "format": "date", + "last_initial_margin": { + "description": "Value of Reg T margin as of previous trading day at 16:00:00 ET", + "example": "12345.6789", "type": "string" - } - }, - "required": [ - "date", - "open", - "close", - "session_open", - "session_close", - "settlement_date" - ], - "type": "object" - }, - "legacy_clock": { - "example": { - "is_open": true, - "next_close": "2025-06-24T16:00:00-04:00", - "next_open": "2025-06-25T09:30:00-04:00", - "timestamp": "2025-06-24T14:15:22-04:00" - }, - "properties": { - "is_open": { - "description": "Whether or not the market is open.", - "type": "boolean" }, - "next_close": { - "description": "Next market close timestamp.", - "format": "date-time", + "last_long_market_value": { + "description": "Value of all long positions as of previous trading day at 16:00:00 ET", + "example": "12345.6789", "type": "string" }, - "next_open": { - "description": "Next market open timestamp.", - "format": "date-time", + "last_maintenance_margin": { + "description": "Maintenance margin requirement on the previous trading day", + "example": "12345.6789", + "format": "decimal", "type": "string" }, - "timestamp": { - "description": "Current timestamp.", - "format": "date-time", + "last_options_buying_power": { + "description": "Value of option buying power as of previous trading day at 16:00:00 ET", + "example": "12345.6789", "type": "string" - } - }, - "required": [ - "timestamp", - "is_open", - "next_open", - "next_close" - ], - "title": "Clock", - "type": "object" - }, - "legacy_public_calendar_resp": { - "items": { - "$ref": "#/components/schemas/legacy_calendar_day" - }, - "type": "array" - }, - "market": { - "description": "The market identifier (MIC, BIC, or acronym).", - "enum": [ - "BMO", - "BNYM", - "BOATS", - "CEUX", - "CHIX", - "HKEX", - "IEX", - "IEXG", - "ISE", - "LSE", - "MTA", - "MTAA", - "NASDAQ", - "NYSE", - "OCEA", - "OPRA", - "OTC", - "OTCM", - "SIFMA", - "TADAWUL", - "XAMS", - "XBRU", - "XDUB", - "XETR", - "XETRA", - "XHKG", - "XLIS", - "XLON", - "XNAS", - "XNYS", - "XPAR", - "XSAU" - ], - "type": "string" - }, - "market_acronym": { - "description": "The acronym of the market.", - "example": "NYSE", - "type": "string" - }, - "market_name": { - "description": "The full name of the market.", - "example": "New York Stock Exchange", - "type": "string" - }, - "market_timezone": { - "description": "The timezone of the market.", - "example": "America/New_York", - "type": "string" - }, - "mic": { - "description": "Market identifier code (ISO 10383).", - "example": "XNYS", - "maxLength": 4, - "minLength": 4, - "pattern": "^[A-Z0-9]{4}$", - "type": "string" - }, - "phase": { - "enum": [ - "closed", - "pre", - "core", - "lunch", - "post" - ], - "type": "string" - }, - "public_calendar_resp": { - "description": "Calendar response.", - "properties": { - "calendar": { - "description": "The market calendar.", - "items": { - "$ref": "#/components/schemas/calendar_day" - }, - "type": "array" }, - "market": { - "$ref": "#/components/schemas/public_market" - } - }, - "required": [ - "market", - "calendar" - ], - "type": "object", - "x-go-name": "CalendarResp" - }, - "public_market": { - "description": "A market.", - "properties": { - "acronym": { - "$ref": "#/components/schemas/market_acronym" - }, - "bic": { - "$ref": "#/components/schemas/bic" - }, - "mic": { - "$ref": "#/components/schemas/mic" - }, - "name": { - "$ref": "#/components/schemas/market_name" - }, - "timezone": { - "$ref": "#/components/schemas/market_timezone" - } - }, - "required": [ - "acronym", - "name", - "timezone" - ], - "type": "object" - }, - "sp_outlook": { - "description": "A Standard & Poor's rating outlook indicates S&P's view regarding the potential direction of a long-term credit rating over the intermediate term (2 years for investment grade, 1 year for speculative grade)", - "enum": [ - "positive", - "negative", - "developing", - "stable", - "not_rated", - "not_meaningful" - ], - "type": "string" - }, - "treasury_subtype": { - "description": "The subtype of the treasury.", - "enum": [ - "bond", - "bill", - "note", - "strips", - "tips", - "floating" - ], - "type": "string" - }, - "us_corporate": { - "description": "A US corporate", - "properties": { - "accrued_interest": { - "description": "The interest that has accumulated on a bond in dollars per bond between the last interest payment and the present date that has not yet been paid to the bondholder", - "format": "double", - "type": "number", - "x-go-type": "decimal.Decimal", - "x-go-type-import": { - "name": "decimal", - "path": "github.com/alpacahq/alpacadecimal" - } - }, - "bond_status": { - "$ref": "#/components/schemas/bond_status" - }, - "call_type": { - "$ref": "#/components/schemas/call_type" - }, - "callable": { - "description": "Whether the bond is callable, meaning the issuer has the right, but not the obligation to redeem the bond - in other words, pay out the bondholder - before its maturity date at a set price (the call price)", - "type": "boolean" + "last_regt_buying_power": { + "description": "Value of Reg T buying power as of previous trading day at 16:00:00 ET", + "example": "12345.6789", + "type": "string" }, - "close_price": { - "description": "The price of the last transaction of a security before the market closes for normal trading, shown as a percentage of par value", - "format": "double", - "type": "number", - "x-go-type": "decimal.Decimal", - "x-go-type-import": { - "name": "decimal", - "path": "github.com/alpacahq/alpacadecimal" - } + "last_short_market_value": { + "description": "Value of all short positions as of previous trading day at 16:00:00 ET", + "example": "0", + "type": "string" }, - "close_price_date": { - "description": "The date of the close price", - "format": "date", + "long_market_value": { + "description": "Real-time MtM value of all long positions held in the account", + "example": "12345.6789", + "format": "decimal", "type": "string" }, - "close_yield_to_maturity": { - "description": "Yield to maturity of the treasury after the last close", - "format": "double", - "type": "number", - "x-go-type": "decimal.Decimal", - "x-go-type-import": { - "name": "decimal", - "path": "github.com/alpacahq/alpacadecimal" - } + "maintenance_margin": { + "description": "Maintenance margin requirement (continuously updated value)", + "example": "12345.6789", + "format": "decimal", + "type": "string" }, - "close_yield_to_worst": { - "description": "Yield to worst of the treasury after the last close", - "format": "double", - "type": "number", - "x-go-type": "decimal.Decimal", - "x-go-type-import": { - "name": "decimal", - "path": "github.com/alpacahq/alpacadecimal" - } + "memoposts": { + "description": "Outstanding memopost value", + "example": "100", + "type": "string" }, - "convertible": { - "description": "A flag indicating whether the bond is convertible", - "type": "boolean" + "multiplier": { + "description": "\"1\", \"2\", \"3\", or \"4\"", + "example": "2", + "format": "decimal", + "type": "string" }, - "country_domicile": { - "description": "The country where the corporate is domiciled in the 2-alpha country code format (e.g., US, CA)", + "non_marginable_buying_power": { + "description": "Non-marginable buying power (currently used for only crypto trading)", + "example": "12345.6789", + "format": "decimal", "type": "string" }, - "coupon": { - "description": "The annual interest rate paid on the bond as a percentage of par value", - "format": "double", - "type": "number", - "x-go-type": "decimal.Decimal", - "x-go-type-import": { - "name": "decimal", - "path": "github.com/alpacahq/alpacadecimal" - } + "options_approved_level": { + "description": "The options trading level that was approved for this account.\n0=disabled, 1=Covered Call/Cash-Secured Put, 2=Long Call/Put, 3=Spreads/Straddles.\n", + "enum": [ + 0, + 1, + 2, + 3 + ], + "example": 3, + "type": "integer" }, - "coupon_frequency": { - "$ref": "#/components/schemas/coupon_frequency" + "options_buying_power": { + "description": "Your buying power for options trading", + "example": "12345.6789", + "format": "decimal", + "type": "string" }, - "coupon_type": { - "$ref": "#/components/schemas/coupon_type" + "options_trading_level": { + "description": "The effective options trading level of the account.\nThis is the minimum between account options_approved_level and account configurations max_options_trading_level.\n0=disabled, 1=Covered Call/Cash-Secured Put, 2=Long Call/Put, 3=Spreads/Straddles.\n", + "enum": [ + 0, + 1, + 2, + 3 + ], + "example": 3, + "type": "integer" }, - "cusip": { - "description": "CUSIP is a nine-character alphanumeric code that uniquely identifies the security", - "maxLength": 12, - "minLength": 12, - "pattern": "^[A-Z0-9]{9}$", + "pending_reg_taf_fees": { + "description": "Pending regulatory fees for the account.", + "example": "0.01", "type": "string" }, - "dated_date": { - "description": "The dated date marks the beginning of the period for which interest starts accruing on the bond", - "format": "date", + "pending_transfer_out": { + "description": "Cash pending transfer out", + "example": "12345.6789", "type": "string" }, - "day_count": { - "$ref": "#/components/schemas/day_count" - }, - "description": { - "description": "Description of the corporate bond", + "portfolio_value": { + "description": "Total value of cash + holding positions. (This field is deprecated. It is equivalent to the equity field.)", + "example": "12345.6789", + "format": "decimal", "type": "string" }, - "description_short": { - "description": "Short description of the corporate bond", + "position_market_value": { + "description": "Real-time MtM value of all the positions held in the account", + "example": "0", + "format": "decimal", "type": "string" }, - "first_coupon_date": { - "description": "The date of the first coupon payment", - "format": "date", + "previous_close": { + "description": "Previous sessions close time", + "example": "2021-04-01T19:00:00-04:00", "type": "string" }, - "isin": { - "description": "International Securities Identification Number", - "maxLength": 12, - "minLength": 12, - "pattern": "^[A-Z]{2}[A-Z0-9]{9}[0-9]$", + "regt_buying_power": { + "description": "User's buying power under Regulation T (excess equity - (equity - margin value) - * margin multiplier)", + "example": "12345.6789", + "format": "decimal", "type": "string" }, - "issue_date": { - "description": "The date on which the bond was issued", - "format": "date", + "short_market_value": { + "description": "Real-time MtM value of all short positions held in the account", + "example": "0", + "format": "decimal", "type": "string" }, - "issue_minimum_denomination": { - "description": "The smallest unit of the bond that can be purchased at its initial offering", - "type": "number" - }, - "issue_price": { - "description": "The price at which the bond was originally issued as a percentage of par value", - "format": "double", - "type": "number", - "x-go-type": "decimal.Decimal", - "x-go-type-import": { - "name": "decimal", - "path": "github.com/alpacahq/alpacadecimal" - } - }, - "issue_size": { - "description": "The total size amount of the bond issue in the issuing currency", - "type": "number" - }, - "issuer": { - "description": "The name of the issuer of the corporate bond", - "type": "string" + "shorting_enabled": { + "description": "Flag to denote whether or not the account is permitted to short", + "example": false, + "type": "boolean" }, - "last_coupon_date": { - "description": "The date of the last coupon payment", - "format": "date", + "sma": { + "description": "Value of Special Memorandum Account (will be used at a later date to provide additional buying_power)", + "example": "12345.6789", + "format": "decimal", "type": "string" }, - "liquidity_institutional_aggregate": { - "description": "Score (from 1-5 or null if the bond is not priced/tradable) reflecting the historical depth of executable liquidity to buy or sell (no minimum trading sizes)", - "type": "number" + "status": { + "$ref": "#/components/schemas/AccountStatus" }, - "liquidity_institutional_buy": { - "description": "Score (from 1-5 or null if the bond is not priced/tradable) reflecting the historical depth of executable liquidity to buy (no minimum trading sizes)", - "type": "number" + "trade_suspended_by_user": { + "description": "If true, the account is not allowed to place orders.", + "example": false, + "type": "boolean" }, - "liquidity_institutional_sell": { - "description": "Score (from 1-5 or null if the bond is not priced/tradable) reflecting the historical depth of executable liquidity to sell (no minimum trading sizes)", - "type": "number" + "trading_blocked": { + "description": "If true, the account is not allowed to place orders.", + "example": false, + "type": "boolean" }, - "liquidity_micro_aggregate": { - "description": "Score (from 1-5 or null if the bond is not priced/tradable) reflecting the historical depth of executable liquidity to buy or sell with minimum trading sizes less than or equal to $1,000.00", - "type": "number" - }, - "liquidity_micro_buy": { - "description": "Score (from 1-5 if the bond is priced, or null if the bond is not tradable) reflecting the historical depth of executable liquidity to buy with minimum trading sizes less than or equal to $1,000.00", - "type": "number" - }, - "liquidity_micro_sell": { - "description": "Score (from 1-5 or null if the bond is not priced/tradable) reflecting the historical depth of executable liquidity to sell with minimum trading sizes less than or equal to $1,000.00", - "type": "number" + "transfers_blocked": { + "description": "If true, the account is not allowed to request money transfers.", + "example": false, + "type": "boolean" }, - "liquidity_retail_aggregate": { - "description": "Score (from 1-5 or null if the bond is not priced/tradable) reflecting the historical depth of executable liquidity to buy or sell with minimum trading sizes less than or equal to 10,000", - "type": "number" + "user_configurations": { + "anyOf": [ + { + "$ref": "#/components/schemas/AccountConfigurations" + }, + { + "type": "null" + } + ], + "description": "Custom user-level configuration overrides for this account.\n\nThis field is always present in the response, but its value will be `null` until user-level configurations have been explicitly set for the account." + } + }, + "required": [ + "id", + "admin_configurations", + "account_number", + "status", + "crypto_status", + "currency", + "buying_power", + "regt_buying_power", + "effective_buying_power", + "non_marginable_buying_power", + "accrued_fees", + "portfolio_value", + "trading_blocked", + "transfers_blocked", + "account_blocked", + "created_at", + "trade_suspended_by_user", + "multiplier", + "shorting_enabled", + "equity", + "last_equity", + "long_market_value", + "short_market_value", + "position_market_value", + "initial_margin", + "maintenance_margin", + "last_maintenance_margin", + "sma", + "balance_asof", + "cash" + ], + "type": "object" + }, + "TradeActivity": { + "properties": { + "cum_qty": { + "description": "Valid only for trading activity types. Null for non-trading activites.", + "example": "0.9723", + "format": "decimal", + "type": "string" }, - "liquidity_retail_buy": { - "description": "Score (from 1-5 or null if the bond is not priced/tradable) reflecting the historical depth of executable liquidity to buy with minimum trading sizes less than or equal to $10,000.00", - "type": "number" + "leaves_qty": { + "description": "Valid only for trading activity types. Null for non-trading activites.", + "example": "0.5123", + "format": "decimal", + "type": "string" }, - "liquidity_retail_sell": { - "description": "Score (from 1-5 or null if the bond is not priced/tradable) reflecting the historical depth of executable liquidity to sell with minimum trading sizes less than or equal to 10,000", - "type": "number" + "order_id": { + "description": "Valid only for trading activity types. Null for non-trading activites.", + "example": "fe060a1b-5b45-4eba-ba46-c3a3345d8255", + "format": "uuid", + "type": "string" }, - "marginable": { - "description": "Whether the corporate is marginable", - "type": "boolean" + "order_status": { + "$ref": "#/components/schemas/OrderStatus" }, - "maturity_date": { - "description": "The date on which the bond matures", - "format": "date", + "price": { + "description": "Valid only for trading activity types. Null for non-trading activites.", + "example": "3.1415", + "format": "decimal", "type": "string" }, - "next_call_date": { - "description": "The date of the next possible call on the bond.", - "format": "date", + "qty": { + "description": "Valid only for trading activity types. Null for non-trading activites.", + "example": "0.38921", + "format": "decimal", "type": "string" }, - "next_call_price": { - "description": "The price at which a callable bond can be redeemed by the issuer on the next call date, as a percentage of par.", - "format": "double", - "type": "number", - "x-go-type": "decimal.Decimal", - "x-go-type-import": { - "name": "decimal", - "path": "github.com/alpacahq/alpacadecimal" - } + "side": { + "$ref": "#/components/schemas/OrderSide" }, - "next_coupon_date": { - "description": "The date of the next coupon payment", - "format": "date", + "symbol": { + "description": "Valid only for trading activity types. Null for non-trading activites.", + "example": "AAPL", "type": "string" }, - "par_value": { - "description": "The amount that the issuer of the bond will pay back to the bondholder upon maturity", - "type": "number" + "transaction_time": { + "description": "Valid only for trading activity types. Null for non-trading activites.", + "example": "2021-05-10T14:01:04.650275Z", + "format": "date-time", + "type": "string" }, - "perpetual": { - "description": "A flag representing whether a bond is perpetual", - "type": "boolean" + "type": { + "description": "Valid only for trading activity types. Null for non-trading activites.", + "enum": [ + "fill", + "partial_fill" + ], + "example": "fill", + "type": "string" + } + }, + "title": "TradeActivity", + "type": "object" + }, + "TradeUpdateEventType": { + "description": "**Common events**\n\nThese are the events that are the expected results of actions you may have taken by sending API requests.\n\nThe meaning of the `timestamp` field changes for each type; the meanings have been specified here for which types the\ntimestamp field will be present.\n\n- `accepted`: Sent when an order is received and accepted by Alpaca.\n- `new`: Sent when an order has been routed to exchanges for execution.\n- `fill`: Sent when your order has been completely filled.\n - `timestamp`: The time at which the order was filled.\n- `partial_fill`: Sent when a number of shares less than the total remaining quantity on your order has been filled.\n - `timestamp`: The time at which the shares were filled.\n- `canceled`: Sent when the order transitions to the `canceled` state. This can be triggered by a user-submitted cancel request, by Alpaca as part of automated processing (e.g. corporate-action sweeps, aged-GTC expiration, overnight-session lifecycle), or by an upstream execution venue. When Alpaca populates a machine-readable cause, it is exposed on the top-level `reason` field — see the `reason` property on `TradeUpdateEventV2` for known values (e.g. `CORPORATE_ACTION`).\n - `timestamp`: The time at which the order was canceled.\n- `expired`: Sent when an order has reached the end of its lifespan, as determined by the order's time in force value.\n - `timestamp`: The time at which the order expired.\n- `done_for_day`: Sent when the order is done executing for the day, and will not receive further updates until the next trading day.\n- `replaced`: Sent when your requested replacement of an order is processed.\n - `timestamp`: The time at which the order was replaced.\n\n**Rarer events**\n\nThese are events that may rarely be sent due to unexpected circumstances on the exchanges. It is unlikely you will need to design your code around them, but you may still wish to account for the possibility that they will occur.\n\n- `rejected`: Sent when your order has been rejected.\n - `timestamp`: The time at which the rejection occurred.\n- `pending_new`: Sent when the order has been received by Alpaca and routed to the exchanges, but has not yet been accepted for execution.\n- `stopped`: Sent when your order has been stopped, and a trade is guaranteed for the order, usually at a stated price or better, but has not yet occurred.\n- `pending_cancel`: Sent when the order is awaiting cancellation. Most cancellations will occur without the order entering this state.\n- `pending_replace`: Sent when the order is awaiting replacement.\n- `calculated`: Sent when the order has been completed for the day - it is either `filled` or `done_for_day` - but remaining settlement calculations are still pending.\n- `suspended`: Sent when the order has been suspended and is not eligible for trading.\n- `order_replace_rejected`: Sent when the order replace has been rejected.\n- `order_cancel_rejected`: Sent when the order cancel has been rejected.\n- `trade_bust`: Sent when a previously reported execution has been canceled (\"busted\") by the upstream exchange.\n- `trade_correct`: Sent when a previously reported trade has been corrected. For example, the exchange may have updated the price, quantity, or another execution parameter after the trade was initially reported.\n- `restated`: Sent when the order is manually modified.\n", + "enum": [ + "new", + "fill", + "partial_fill", + "canceled", + "expired", + "done_for_day", + "replaced", + "rejected", + "pending_new", + "accepted", + "stopped", + "pending_cancel", + "pending_replace", + "calculated", + "suspended", + "order_replace_rejected", + "order_cancel_rejected", + "trade_bust", + "trade_correct" + ], + "type": "string" + }, + "TradeUpdateEventV2": { + "description": "Represents an update to an order/trade, sent over the events streaming api.", + "properties": { + "account_id": { + "description": "Account UUID", + "format": "uuid", + "minLength": 1, + "type": "string" }, - "puttable": { - "description": "Whether the bond is puttable, meaning the bondholder has the right, but not the obligation to sell the bond back to the issuer at a set price (the put price) on specified dates before maturity", - "type": "boolean" + "at": { + "description": "Timestamp of event", + "format": "date-time", + "minLength": 1, + "type": "string" }, - "reg_s": { - "description": "Indicates whether the security falls under Regulation S, a rule that provides an exemption from the registration requirements for securities offerings made outside the United States", - "type": "boolean" + "event": { + "$ref": "#/components/schemas/TradeUpdateEventType" }, - "reissue_date": { - "description": "The date on which the corporate was reissued", - "format": "date", + "event_id": { + "description": "lexically sortable, monotonically increasing character array", + "format": "ulid", "type": "string" }, - "reissue_price": { - "description": "The price at which the corporate was reissued as a percentage of par value", - "format": "double", - "type": "number", - "x-go-type": "decimal.Decimal", - "x-go-type-import": { - "name": "decimal", - "path": "github.com/alpacahq/alpacadecimal" - } + "execution_id": { + "description": "Corresponding execution of an order. If an order gets filled over two executions (a partial_fill for example), you will receive two events with different IDs.\nNot present for `MultilegOptions`\n", + "format": "uui", + "type": "string" }, - "reissue_size": { - "description": "The total size amount of the corporate reissue in the issuing currency", - "type": "number" + "legs": { + "description": "Only present when event is for `MultilegOptions`. Represents filled qty/price of legs.\n", + "items": { + "$ref": "#/components/schemas/TradeUpdateEventV2Leg" + }, + "type": "array" }, - "sector": { - "description": "The sector of the corporate bond", - "type": "string" + "order": { + "$ref": "#/components/schemas/Order" }, - "seniority": { - "description": "The seniority of the corporate bond", + "position_qty": { + "description": "Only present when event is either `fill` or `partial_fill` other than `MultilegOptions`. The size of your total position, after this fill event, in shares. Positive for long positions, negative for short positions.\n", "type": "string" }, - "sp_creditwatch": { - "description": "S&P's CreditWatch highlights S&P's opinion regarding the potential direction of a short-term or long-term rating", + "position_qtys": { + "description": "Only present when event is either `fill` or `partial_fill` for `MultilegOptions`. The size of your total position, after this fill event, in shares. Positive for long positions, negative for short positions.\n", "type": "string" }, - "sp_creditwatch_date": { - "description": "The date of the most recent Standard & Poor's CreditWatch for the bond in YYYY-MM-DD format", - "format": "date", + "previous_execution_id": { + "description": "ID of the original execution that was busted or corrected (present only in trade_bust and trade_correct events).", + "format": "uuid", "type": "string" }, - "sp_outlook": { - "$ref": "#/components/schemas/sp_outlook" - }, - "sp_outlook_date": { - "description": "The date of the most recent Standard & Poor's outlook for the bond in YYYY-MM-DD format", - "format": "date", + "price": { + "description": "Only present when event is either `fill` or `partial_fill`. The average price per share at which the order was filled.", "type": "string" }, - "sp_rating": { - "description": "Standard & Poor's rating for the bond in the standard AAA - D format", + "qty": { + "description": "Only present when event is either `fill`, `partial_fill`, `trade_bust` and `trade_correct`. The amount of shares this Trade order was for.
For `trade_bust` events, the qty field may be negative, representing a reversal of the original quantity.", "type": "string" }, - "sp_rating_date": { - "description": "The date in the timezone of the issuing country of the most recent Standard & Poor's rating for the bond in YYYY-MM-DD format", + "reason": { + "description": "Machine-readable code that explains **why** the event occurred. Only present when a reason applies; omitted from routine lifecycle events such as `new`, `fill`, and `partial_fill`.\n\nKnown values:\n\n| Value | Event(s) | Meaning |\n| --- | --- | --- |\n| `CORPORATE_ACTION` | `canceled` | Alpaca canceled the order internally because the underlying is scheduled for a mandatory corporate action other than a cash dividend (e.g. reverse split, symbol/CUSIP change, merger, liquidation). Emitted by the overnight-session lifecycle and, where enabled by Alpaca, the daytime GTC corporate-action sweep. Not emitted when the cancel is routed to the upstream venue instead (e.g. day-session, `extended_hours=false`, agency- or mixed-capacity orders); those `canceled` events carry the venue's own text in `reason` if any. |\n| `TOO_LATE_TO_CANCEL` | `order_cancel_rejected`, `order_replace_rejected` | The cancel or replace request arrived after the order had already reached a terminal state (typically `filled`). |\n| `TRADE_BUST` | `trade_bust` | Alpaca operations replayed or synthesized a bust of a previously reported execution. Busts that originate from the upstream venue surface the venue's own text in `reason` (or omit it entirely), so consumers should not rely on `TRADE_BUST` alone to identify all busts — key on the `event` field (`trade_bust`) for that. |\n\nFree-form strings may also appear in `reason`. In particular, `canceled`, `trade_bust`, and `trade_correct` events originating from the upstream venue carry the venue's own text field (when the venue provides one) rather than an Alpaca-defined code. Consumers should tolerate unknown values and, where they need to distinguish cases, key on `event` first and use `reason` as a secondary hint.\n", + "example": "CORPORATE_ACTION", + "type": [ + "string", + "null" + ] + }, + "settle_date": { + "description": "Settlement date of the trade in format `YYYY-MM-DD` for `fill` and `partial_fill` events", + "example": "2026-03-03", "format": "date", "type": "string" }, - "ticker": { - "description": "The ticker symbol of the corporate", + "swap_rate": { + "description": "Only present for `local currency trading account` or `crypto asset trade` when event is either `fill` or `partial_fill`. The swap rate at which the current trade was filled.", "type": "string" }, - "tradable": { - "description": "Whether the treasury is tradable", - "type": "boolean" + "timestamp": { + "description": "Has various different meanings depending on the value of `event`, please see the [Trading Events](https://alpaca.markets/docs/api-references/broker-api/events/#trade-events)\nEnum in the documentation or the TradeUpdateEventType model for more details on when it means different things.\n", + "format": "date-time", + "type": "string" } }, - "required": [ - "isin", - "cusip", - "bond_status", - "tradable", - "marginable", - "issue_date", - "country_domicile", - "ticker", - "seniority", - "issuer", - "sector", - "description", - "description_short", - "coupon", - "coupon_type", - "coupon_frequency", - "perpetual", - "day_count", - "dated_date", - "issue_size", - "issue_price", - "issue_minimum_denomination", - "par_value", - "callable", - "puttable", - "convertible", - "reg_s" - ], + "title": "TradeUpdateEvent", "type": "object" }, - "us_corporates_resp": { + "TradeUpdateEventV2Leg": { + "description": "Represents filled qty/price of legs.", "properties": { - "us_corporates": { - "items": { - "$ref": "#/components/schemas/us_corporate" - }, - "type": "array" + "execution_id": { + "description": "Corresponding execution of an order. If an order gets filled over two executions (a partial_fill for example), you will receive two events with different IDs.", + "format": "uuid", + "type": "string" + }, + "order_id": { + "description": "Order UUID", + "format": "uuid", + "minLength": 1, + "type": "string" + }, + "price": { + "description": "The average price per share for this event.", + "type": "string" + }, + "qty": { + "description": "The amount of shares filled for this event", + "type": "string" + }, + "symbol": { + "description": "Symbol of an asset", + "type": "string" + }, + "timestamp": { + "description": "Timestamp of this event leg\n", + "format": "date-time", + "type": "string" } }, - "required": [ - "us_corporates" - ], + "title": "TradeUpdateEventV2Leg", "type": "object" }, - "us_treasuries_resp": { - "properties": { - "us_treasuries": { - "items": { - "$ref": "#/components/schemas/us_treasury" - }, - "type": "array" + "Transaction": { + "examples": [ + { + "account_id": "06b9d76e-1733-460c-844e-48d1ae0f10c3", + "account_name": "Just In Time Receivable - JITS", + "account_no": "JTRJITS00", + "amount": "1", + "balance": "4186.36", + "contra_account_name": "Just In Time Interest Income", + "description": "debit balance = 4185.36, base_rate = 0.0375, spread = 0.05", + "entry_type": "JNLC", + "system_date": "2022-06-10" } - }, - "required": [ - "us_treasuries" ], - "type": "object" - }, - "us_treasury": { - "description": "A US treasury", "properties": { - "bond_status": { - "$ref": "#/components/schemas/bond_status" - }, - "close_price": { - "description": "The price of the last transaction of a security before the market closes for normal trading, shown as a percentage of par value", - "format": "double", - "type": "number" + "account_id": { + "description": "The ledger ID", + "type": "string" }, - "close_price_date": { - "description": "The date of the close price", - "format": "date", + "account_name": { + "description": "The ledger name", "type": "string" }, - "close_yield_to_maturity": { - "description": "Yield to maturity of the treasury after the last close", - "format": "double", - "type": "number" + "account_no": { + "description": "The ledger account number", + "type": "string" }, - "close_yield_to_worst": { - "description": "Yield to worst of the treasury after the last close", - "format": "double", - "type": "number" + "amount": { + "description": "Total amount of the transaction", + "type": "string" }, - "coupon": { - "description": "The annual interest rate paid on the bond as a percentage of par value", - "format": "double", - "type": "number" + "balance": { + "description": "Ending balance after thetransaction has been applied", + "type": "string" }, - "coupon_frequency": { - "$ref": "#/components/schemas/coupon_frequency" + "contra_account_name": { + "description": "Contra account of transaction", + "type": "string" }, - "coupon_type": { - "$ref": "#/components/schemas/coupon_type" + "description": { + "description": "Plain text overview of the transaction", + "type": "string" }, - "cusip": { - "description": "CUSIP is a nine-character alphanumeric code that uniquely identifies the security", - "maxLength": 12, - "minLength": 12, - "pattern": "^[A-Z0-9]{9}$", + "entry_type": { + "description": "Type of transaction", "type": "string" }, - "description": { - "description": "Description of the treasury", + "system_date": { + "description": "Date of transaction", + "type": "string" + } + }, + "type": "object" + }, + "Transfer": { + "description": "Transfers allow you to transfer money/balance into your end customers' account (deposits) or out (withdrawal).\n\n[Main docs here](https://alpaca.markets/docs/api-references/broker-api/funding/transfers/#the-transfer-object)", + "examples": [ + { + "account_id": "449e7a5c-69d3-4b8a-aaaf-5c9b713ebc65", + "additional_information": "string", + "amount": "string", + "bank_id": "f1ae96de-94c1-468e-93a3-6b7213930ca8", + "created_at": "2019-08-24T14:15:22Z", + "direction": "INCOMING", + "expires_at": "2019-08-24T14:15:22Z", + "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08", + "ira": { + "distribution_reason": "normal", + "fed_withholding_amount": "102.5", + "fed_withholding_pct": "10.25", + "state_withholding_amount": "97.5", + "state_withholding_pct": "9.75", + "tax_year": "2024" + }, + "reason": "string", + "relationship_id": "81412018-ffa2-43f9-a3eb-d39f1c5e0f87", + "status": "QUEUED", + "type": "ach", + "updated_at": "2019-08-24T14:15:22Z" + } + ], + "properties": { + "account_id": { + "description": "The account ID", + "format": "uuid", "type": "string" }, - "description_short": { - "description": "Short description of the treasury", + "additional_information": { + "description": "Additional information. Only applies when type = \"wire\".", + "type": [ + "string", + "null" + ] + }, + "amount": { + "description": "Must be > 0.00", + "format": "decimal", "type": "string" }, - "first_coupon_date": { - "description": "The date of the first coupon payment", - "format": "date", + "bank_id": { + "description": "The ID of the Bank, only present if type = \"wire\"", + "format": "uuid", "type": "string" }, - "isin": { - "description": "International Securities Identification Number", - "maxLength": 12, - "minLength": 12, - "pattern": "^[A-Z]{2}[A-Z0-9]{9}[0-9]$", + "created_at": { + "description": "Timestamp when transfer was created", + "format": "date-time", "type": "string" }, - "issue_date": { - "description": "The date on which the bond was issued", - "format": "date", + "direction": { + "$ref": "#/components/schemas/TransferDirection" + }, + "expires_at": { + "description": "Timestamp when transfer expires", + "format": "date-time", "type": "string" }, - "last_coupon_date": { - "description": "The date of the last coupon payment", - "format": "date", + "fee": { + "description": "Fee amount to be collected. Only applies when type = \"wire\".", + "format": "decimal", + "type": [ + "string", + "null" + ] + }, + "fee_payment_method": { + "description": "Either \"user\" or \"invoice\". Only applies when type = \"wire\".", + "type": [ + "string", + "null" + ] + }, + "hold_until": { + "format": "date-time", "type": "string" }, - "maturity_date": { - "description": "The date on which the bond matures", - "format": "date", + "id": { + "description": "The transfer ID", + "format": "uuid", "type": "string" }, - "next_coupon_date": { - "description": "The date of the next coupon payment", - "format": "date", + "instant_amount": { "type": "string" }, - "subtype": { - "$ref": "#/components/schemas/treasury_subtype" + "ira": { + "$ref": "#/components/schemas/TransferIRADetails" }, - "tradable": { - "description": "Whether the treasury is tradable", - "type": "boolean" - } - }, - "required": [ - "isin", - "cusip", - "bond_status", - "tradable", - "subtype", + "reason": { + "description": "Cause of the status", + "type": [ + "string", + "null" + ] + }, + "relationship_id": { + "description": "The ACH relationship ID only present if type = \"ach\"", + "format": "uuid", + "type": "string" + }, + "requested_amount": { + "description": "Must be > 0.00. Only applies when type = \"wire\".", + "format": "decimal", + "type": [ + "string", + "null" + ] + }, + "status": { + "$ref": "#/components/schemas/TransferStatus" + }, + "type": { + "$ref": "#/components/schemas/TransferType" + }, + "updated_at": { + "description": "Timestamp when transfer was updated", + "format": "date-time", + "type": "string" + } + }, + "required": [ + "id", + "account_id", + "type", + "status", + "amount", + "direction", + "created_at" + ], + "title": "Transfer", + "type": "object" + }, + "TransferDirection": { + "description": "- **INCOMING**\nFunds incoming to user's account (deposit).\n- **OUTGOING**\nFunds outgoing from user's account (withdrawal).\n", + "enum": [ + "INCOMING", + "OUTGOING" + ], + "example": "INCOMING", + "type": "string" + }, + "TransferFee": { + "properties": { + "amount": { + "format": "decimal", + "type": "string" + }, + "currency": { + "$ref": "#/components/schemas/Currency" + }, + "payment_type": { + "$ref": "#/components/schemas/FeePaymentType" + }, + "type": { + "$ref": "#/components/schemas/FeeType" + } + }, + "required": [ + "type", + "amount", + "currency", + "payment_type" + ], + "type": "object" + }, + "TransferIRA": { + "description": "This field is used for IRA Account only", + "properties": { + "distribution_reason": { + "example": "normal", + "type": "string" + }, + "tax_withholding": { + "$ref": "#/components/schemas/TransferIRATaxWithholding" + }, + "tax_year": { + "example": "2024", + "type": "string" + } + }, + "type": "object" + }, + "TransferIRADetails": { + "properties": { + "distribution_reason": { + "example": "normal", + "type": "string" + }, + "fed_withholding_amount": { + "example": "102.5", + "type": "string" + }, + "fed_withholding_pct": { + "example": "10.25", + "type": "string" + }, + "state_withholding_amount": { + "example": "97.5", + "type": "string" + }, + "state_withholding_pct": { + "example": "9.75", + "type": "string" + }, + "tax_year": { + "example": "2024", + "type": "string" + } + }, + "type": "object" + }, + "TransferIRATaxWithholding": { + "properties": { + "fed_pct": { + "example": "10.25", + "type": "string" + }, + "state_pct": { + "example": "8.25", + "type": "string" + } + }, + "type": "object" + }, + "TransferStatus": { + "description": "- **QUEUED**\nTransfer is in queue to be processed.\n- **APPROVAL_PENDING**\nTransfer is pending approval.\n- **PENDING**\nTransfer is pending processing.\n- **SENT_TO_CLEARING**\nTransfer is being processed by the clearing firm.\n- **REJECTED**\nTransfer is rejected.\n- **CANCELED**\nClient initiated transfer cancellation.\n- **APPROVED**\nTransfer is approved.\n- **COMPLETE**\nTransfer is completed.\n- **RETURNED**\nThe bank issued an ACH return for the transfer.\n", + "enum": [ + "QUEUED", + "APPROVAL_PENDING", + "PENDING", + "SENT_TO_CLEARING", + "REJECTED", + "CANCELED", + "APPROVED", + "COMPLETE", + "RETURNED" + ], + "example": "QUEUED", + "type": "string" + }, + "TransferStatusEvent": { + "description": "Represents a change in a Transfer's status, sent over the events streaming api.", + "examples": [ + { + "account_id": "8e00606a-c9ac-409a-ba45-f55e8f77984a", + "at": "2021-06-10T19:52:24.066998Z", + "event_id": 15961, + "event_ulid": "01F7VQQ782DM57SJNWAYMD14J9", + "status_from": "QUEUED", + "status_to": "SENT_TO_CLEARING", + "transfer_id": "c4ed4206-697b-4859-ab71-b9de6649859d" + }, + { + "account_id": "8e00606a-c9ac-409a-ba45-f55e8f77984a", + "at": "2021-06-10T20:02:24.280178Z", + "event_id": 15962, + "event_ulid": "01F7VQQ782DM57SJNWAYMD14JA", + "status_from": "SENT_TO_CLEARING", + "status_to": "COMPLETE", + "transfer_id": "c4ed4206-697b-4859-ab71-b9de6649859d" + } + ], + "properties": { + "account_id": { + "description": "Account UUID", + "format": "uuid", + "minLength": 1, + "type": "string" + }, + "at": { + "description": "Timestamp of when the transfer status changed", + "format": "date-time", + "minLength": 1, + "type": "string" + }, + "event_id": { + "description": "Monotonically increasing 64-bit integer not available to new partners, and for backward compatibility purposes only; use `event_ulid` as the stable identifier where possible\n", + "type": "integer" + }, + "event_ulid": { + "description": "lexically sortable, monotonically increasing character array", + "format": "ulid", + "type": "string" + }, + "status_from": { + "$ref": "#/components/schemas/TransferStatusFrom" + }, + "status_to": { + "$ref": "#/components/schemas/TransferStatus" + }, + "transfer_id": { + "description": "Transfer UUID", + "format": "uuid", + "minLength": 1, + "type": "string" + } + }, + "required": [ + "account_id", + "at", + "event_ulid", + "status_from", + "status_to", + "transfer_id" + ], + "title": "TransferStatusEvent", + "type": "object" + }, + "TransferStatusFrom": { + "description": "Previous transfer status in stream status-change events. Empty string is used for the initial status-change event when there is no previous status.", + "oneOf": [ + { + "$ref": "#/components/schemas/TransferStatus" + }, + { + "enum": [ + "" + ], + "type": "string" + } + ] + }, + "TransferType": { + "description": "- **ach**\nTransfer via ACH (US Only). Supports both `INCOMING` (deposit) and `OUTGOING` (withdrawal) directions.\n- **wire**\nTransfer via wire. `OUTGOING` (withdrawal) only.\n", + "enum": [ + "ach", + "wire" + ], + "example": "ach", + "title": "TransferType", + "type": "string" + }, + "TransmitterInfo": { + "description": "Information about the transmitter to satisfy travel rule requirements. Required if the requesting correspondent qualifies as a financial institution\n", + "properties": { + "originator_bank_account_number": { + "description": "Required if the requesting correspondent qualifies as a financial institution\n", + "type": "string" + }, + "originator_bank_name": { + "description": "Required if the requesting correspondent qualifies as a financial institution\n", + "type": "string" + }, + "originator_city": { + "type": "string" + }, + "originator_country": { + "description": "Required if the requesting correspondent qualifies as a financial institution\n", + "type": "string" + }, + "originator_full_name": { + "description": "Required if the requesting correspondent qualifies as a financial institution\n", + "type": "string" + }, + "originator_postal_code": { + "type": "string" + }, + "originator_state": { + "type": "string" + }, + "originator_street_address": { + "type": "string" + }, + "other_identifying_information": { + "description": "Used to facilitate transfer lookup in the event it is required. Recommended to be the originating bank's reference number for the transfer\n", + "type": "string" + } + }, + "type": "object" + }, + "TravelRuleErrorResponse": { + "description": "A wallet or travel rule validation error.", + "oneOf": [ + { + "$ref": "#/components/schemas/TravelRuleValidationError" + }, + { + "$ref": "#/components/schemas/WalletError" + } + ] + }, + "TravelRuleFieldOption": { + "description": "Allowed value for a travel rule field that accepts a fixed set of choices.", + "properties": { + "description": { + "type": "string" + }, + "value": { + "type": "string" + } + }, + "required": [ + "value", + "description" + ], + "type": "object" + }, + "TravelRuleInfo": { + "description": "Travel rule information pertaining to the wallet being whitelisted. Identify the destination by providing beneficiary_vasp_id, setting beneficiary_is_self_hosted to true, or providing beneficiary_manual_entry. Either beneficiary_entity_name (for legal entities) or beneficiary_given_name and beneficiary_family_name (for natural persons) must be provided.", + "properties": { + "beneficiary_country_of_residence": { + "description": "ISO 3166-1 country code for the beneficiary's country of residence.", + "example": "US", + "type": "string" + }, + "beneficiary_entity_name": { + "description": "The beneficiary's legal entity name (required for legal entities)", + "example": "Alpaca Crypto LLC", + "minLength": 1, + "type": "string" + }, + "beneficiary_family_name": { + "description": "The beneficiary's last name (required for natural persons)", + "example": "Doe", + "minLength": 1, + "type": "string" + }, + "beneficiary_geographic_address_building_number": { + "description": "Building number in the beneficiary's geographic address.", + "example": "1", + "type": "string" + }, + "beneficiary_geographic_address_country": { + "description": "ISO 3166-1 alpha-2 or alpha-3 country code in the beneficiary's geographic address.", + "example": "US", + "type": "string" + }, + "beneficiary_geographic_address_post_code": { + "description": "Postal code in the beneficiary's geographic address.", + "example": "10001", + "type": "string" + }, + "beneficiary_geographic_address_street_name": { + "description": "Street name in the beneficiary's geographic address.", + "example": "Main Street", + "type": "string" + }, + "beneficiary_geographic_address_town_name": { + "description": "Town or city in the beneficiary's geographic address.", + "example": "New York", + "type": "string" + }, + "beneficiary_given_name": { + "description": "The beneficiary's first name (required for natural persons)", + "example": "John", + "minLength": 1, + "type": "string" + }, + "beneficiary_is_self_hosted": { + "description": "Beneficiary is receiving funds to a self-hosted / non-custodial wallet", + "example": true, + "type": "boolean" + }, + "beneficiary_manual_entry": { + "$ref": "#/components/schemas/TravelRuleManualEntry" + }, + "beneficiary_vasp_id": { + "description": "The W3C Decentralized Identifier (DID) representing the beneficiary's VASP.", + "example": "did:ethr:0xf1c002f9e7ca88018d6dcf1e86403fb2f5f055f4", + "pattern": "^did:[a-zA-Z0-9]*:.*$", + "type": "string" + } + }, + "title": "TravelRuleInfo", + "type": "object" + }, + "TravelRuleManualEntry": { + "description": "Information about a beneficiary VASP that is not available in the VASP directory.", + "properties": { + "vasp_name": { + "description": "The name of the receiving exchange.", + "example": "Alpaca", + "type": "string" + }, + "vasp_website": { + "description": "The receiving exchange's website.", + "example": "https://alpaca.markets", + "format": "uri", + "type": "string" + } + }, + "required": [ + "vasp_name", + "vasp_website" + ], + "type": "object" + }, + "TravelRuleNextAction": { + "description": "Action the client must take to satisfy travel rule requirements.", + "properties": { + "description": { + "type": "string" + }, + "name": { + "type": "string" + }, + "required_fields": { + "items": { + "$ref": "#/components/schemas/TravelRuleRequiredField" + }, + "type": "array" + }, + "type": { + "enum": [ + "REQUIRED", + "ONE_OF" + ], + "type": "string" + } + }, + "required": [ + "name", + "description", + "type" + ], + "type": "object" + }, + "TravelRuleRequiredField": { + "description": "Request field required to complete a travel rule action.", + "properties": { + "description": { + "type": "string" + }, + "field": { + "type": "string" + }, + "options": { + "description": "List of valid choices for 'enum' types. Null for simple strings or booleans.", + "items": { + "$ref": "#/components/schemas/TravelRuleFieldOption" + }, + "type": [ + "array", + "null" + ] + }, + "value_type": { + "enum": [ + "string", + "bool" + ], + "type": "string" + } + }, + "required": [ + "field", + "value_type", + "description" + ], + "type": "object" + }, + "TravelRuleValidationError": { + "description": "Travel rule validation failure and the actions required to correct it.", + "properties": { + "error_code": { + "$ref": "#/components/schemas/TravelRuleValidationErrorCode" + }, + "message": { + "example": "Please provide the beneficiary's name. Select one of the following options.", + "type": "string" + }, + "next_actions": { + "description": "A list of actions to be taken to fulfill information requirements.", + "example": [ + { + "description": "Provide the recipient's first and last name.", + "name": "supply_beneficiaryName_naturalPerson", + "required_fields": [ + { + "description": "The recipient's first name.", + "field": "beneficiary_given_name", + "value_type": "string" + }, + { + "description": "The recipient's last name.", + "field": "beneficiary_family_name", + "value_type": "string" + } + ], + "type": "ONE_OF" + }, + { + "description": "Provide the entity's legal name.", + "name": "supply_beneficiaryName_legalEntity", + "required_fields": [ + { + "description": "The legal name of the receiving entity.", + "field": "beneficiary_entity_name", + "value_type": "string" + } + ], + "type": "ONE_OF" + } + ], + "items": { + "$ref": "#/components/schemas/TravelRuleNextAction" + }, + "type": "array" + } + }, + "required": [ + "error_code", + "message", + "next_actions" + ], + "type": "object" + }, + "TravelRuleValidationErrorCode": { + "description": "Machine-readable code identifying the travel rule validation failure.", + "enum": [ + "INVALID_DATA", + "UNKNOWN_VASP", + "ADDRESS_BELONGS_TO_VASP", + "TRAVEL_RULE_ACTION_REQUIRED", + "BENEFICIARY_NAME_REQUIRED" + ], + "example": "BENEFICIARY_NAME_REQUIRED", + "type": "string" + }, + "TrustedContact": { + "anyOf": [ + { + "required": [ + "email_address" + ] + }, + { + "required": [ + "phone_number" + ] + }, + { + "required": [ + "street_address" + ] + } + ], + "dependentRequired": { + "street_address": [ + "city", + "state", + "postal_code", + "country" + ] + }, + "description": "This model input is optional. However, the client should make a reasonable effort to obtain the trusted contact information. See [FINRA Notice 17-11](https://www.finra.org/sites/default/files/Regulatory-Notice-17-11.pdf) for more details.\n\nAt least one of the following is required:\n- `email_address`\n- `phone_number`\n- `street_address`\n", + "example": { + "email_address": "jane.doe@example.com", + "family_name": "Doe", + "given_name": "Jane" + }, + "properties": { + "city": { + "description": "Required if `street_address` is set.", + "type": "string" + }, + "country": { + "description": "The country in [ISO 3166-1 alpha-3](https://www.iso.org/iso-3166-country-codes.html) format. Required if `street_address` is set.", + "type": "string" + }, + "email_address": { + "description": "At least one of `email_address`, `phone_number`, or `street_address` is required.", + "example": "jane.doe@example.com", + "format": "email", + "type": "string" + }, + "family_name": { + "example": "Doe", + "type": "string" + }, + "given_name": { + "example": "Jane", + "type": "string" + }, + "phone_number": { + "description": "At least one of `email_address`, `phone_number`, or `street_address` is required.", + "type": "string" + }, + "postal_code": { + "description": "Required if `street_address` is set.", + "type": "string" + }, + "state": { + "description": "Required if `street_address` is set.", + "type": "string" + }, + "street_address": { + "description": "At least one of `email_address`, `phone_number`, or `street_address` is required.", + "items": { + "type": "string" + }, + "type": "array" + } + }, + "required": [ + "given_name", + "family_name" + ], + "type": "object" + }, + "USDAccountTradingLimit": { + "properties": { + "available": { + "description": "The remaining net buying limit that can be used for trading.", + "format": "decimal", + "type": "string" + }, + "daily_net_limit": { + "description": "The net buying limit that can be reached before further trading activity is restricted. Please reach out to learn more about how this limit is determined.", + "format": "decimal", + "type": "string" + }, + "held": { + "description": "The limit that is currently being held for open orders", + "format": "decimal", + "type": "string" + }, + "used": { + "description": "The real time net value of cash inflows (buy trades, etc.) with cash outflows (sell trades, etc). This will be dynamic throughout the day based on user activity, with executed orders being reset at the start of the next trading day.", + "format": "decimal", + "type": "string" + } + }, + "title": "USDAccountTradingLimit", + "type": "object" + }, + "USDPosition": { + "description": "Position values in USD. This is returned for LCT (non-USD) accounts only.", + "properties": { + "avg_entry_price": { + "description": "Average entry price of the position in USD", + "example": "71.43", + "format": "decimal", + "type": "string" + }, + "change_today": { + "description": "Percent change from last day price (by a factor of 1)", + "example": "0.67", + "format": "decimal", + "type": "string" + }, + "cost_basis": { + "description": "Total cost basis in USD", + "example": "333.33", + "format": "decimal", + "type": "string" + }, + "current_price": { + "description": "Current asset price per share in USD", + "example": "80.0", + "format": "decimal", + "type": "string" + }, + "lastday_price": { + "description": "Last day's asset price per share based on the closing value of the last trading day in USD", + "example": "79.33", + "format": "decimal", + "type": "string" + }, + "market_value": { + "description": "Total market value of the position in USD", + "example": "400.00", + "format": "decimal", + "type": "string" + }, + "unrealized_intraday_pl": { + "description": "Unrealized profit/loss in USD for the day", + "example": "6.67", + "format": "decimal", + "type": "string" + }, + "unrealized_intraday_plpc": { + "description": "Unrealized interday profit/loss percent (by a factor of 1)", + "example": "0.0084", + "format": "decimal", + "type": "string" + }, + "unrealized_pl": { + "description": "Unrealized profit/loss in USD", + "example": "66.67", + "format": "decimal", + "type": "string" + }, + "unrealized_plpc": { + "description": "Unrealized profit/loss percent (by a factor of 1)", + "example": "0.2", + "format": "decimal", + "type": "string" + } + }, + "title": "USDPosition", + "type": "object" + }, + "UnitSplitActivityV2": { + "allOf": [ + { + "$ref": "#/components/schemas/CommonSplitStockActivityV2" + }, + { + "properties": { + "alternate_cusip": { + "description": "CUSIP for the alternate security after the split", + "type": "string" + }, + "alternate_isin": { + "description": "ISIN for the alternate security after the split", + "type": "string" + }, + "alternate_qty": { + "description": "Quantity of alternate shares received", + "type": "string" + }, + "alternate_rate": { + "description": "Ratio of alternate shares received", + "type": "string" + }, + "alternate_symbol": { + "description": "Symbol for the alternate security after the split", + "type": "string" + }, + "effective_date": { + "description": "When the unit split becomes effective", + "format": "date", + "minLength": 1, + "type": "string" + }, + "new_symbol": { + "description": "Symbol of the new security after the split", + "type": "string" + }, + "old_symbol": { + "description": "Symbol of the old security before the split", + "type": "string" + } + }, + "required": [ + "old_symbol", + "new_symbol", + "alternate_cusip", + "alternate_symbol", + "alternate_rate", + "alternate_qty", + "effective_date" + ], + "type": "object" + } + ], + "description": "Unit split", + "type": "object" + }, + "UpdateOrderRequest": { + "description": "Represents the fields that are editable in an order replace/update call.\n\nNote: client_order_id is currently not editable on its own, one of the other fields must be changed at the same time to effectively replace the order.\n\nNote: `qty` and `notional` are mutually exclusive on a single replace request. `notional` is only valid for IPO indications of interest (`asset_class: \"ipo\"`); non-IPO notional orders cannot be replaced at all.", + "properties": { + "advanced_instructions": { + "$ref": "#/components/schemas/AdvancedInstructions" + }, + "client_order_id": { + "description": "A unique identifier for the new order. Automatically generated if not sent. (<= 128 characters)", + "example": "61e69015-8549-4bfd-b9c3-01e75843f47d", + "maxLength": 128, + "type": "string" + }, + "limit_price": { + "description": "Required if original order's `type` field was `limit` or `stop_limit`.\nIn case of `mleg`, the limit_price parameter is expressed with the following notation:\n- A positive value indicates a debit, representing a cost or payment to be made.\n- A negative value signifies a credit, reflecting an amount to be received.", + "example": "3.14", + "format": "decimal", + "type": "string" + }, + "notional": { + "description": "New notional (dollar amount) for the order. Only valid for IPO indications of interest (`asset_class: \"ipo\"`); will be rejected for any other asset class.\nMutually exclusive with `qty` on the same replace request.", + "example": "750", + "format": "decimal", + "type": "string" + }, + "qty": { + "description": "You can only patch full shares for now.\n\nQty of equity fractional orders are not allowed to change. Non-IPO notional orders cannot be replaced at all - no fields (qty, limit_price, stop_price, etc.) can be modified; cancel and resubmit instead.\nIn case of multi-leg orders represents the number of units to trade of this strategy.", + "example": "4", + "format": "decimal", + "type": "string" + }, + "stop_price": { + "description": "Required if original order's `type` field was stop or stop_limit", + "example": "3.14", + "format": "decimal", + "type": "string" + }, + "time_in_force": { + "$ref": "#/components/schemas/TimeInForce" + }, + "trail": { + "description": "The new value of the trail_price or trail_percent", + "example": "3.14", + "format": "decimal", + "type": "string" + } + }, + "title": "OrderUpdateRequest", + "type": "object" + }, + "UpdateWhitelistedAddressTravelRuleInfoRequest": { + "description": "Travel rule information to associate with a whitelisted address.", + "properties": { + "travel_rule_info": { + "$ref": "#/components/schemas/TravelRuleInfo" + } + }, + "required": [ + "travel_rule_info" + ], + "type": "object" + }, + "Usd": { + "properties": { + "amount": { + "format": "decimal", + "type": "string" + } + }, + "required": [ + "amount" + ], + "type": "object" + }, + "VASP": { + "description": "Virtual Asset Service Provider returned by the directory search.", + "properties": { + "address": { + "type": "string" + }, + "city": { + "type": "string" + }, + "country": { + "description": "ISO 3166-1 alpha-2 country code for the VASP.", + "example": "US", + "type": "string" + }, + "did": { + "description": "The W3C Decentralized Identifier (DID) representing the VASP.", + "example": "did:ethr:0xf1c002f9e7ca88018d6dcf1e86403fb2f5f055f4", + "pattern": "^did:[a-zA-Z0-9]*:.*$", + "type": "string" + }, + "emailDomain": { + "type": "string" + }, + "id": { + "description": "Opaque identifier for the VASP directory record.", + "example": "vasp-1", + "type": "string" + }, + "name": { + "type": "string" + }, + "postalCode": { + "type": "string" + }, + "state": { + "type": "string" + }, + "website": { + "format": "uri", + "type": "string" + } + }, + "required": [ + "id", + "name" + ], + "type": "object" + }, + "W8benDocument": { + "description": "Use this property (instead of the content property) to upload W-8 BEN data in JSON format.", + "examples": [ + { + "additional_conditions": "None", + "country_citizen": "Australia", + "date": "2021-06-14", + "date_of_birth": "1970-01-01", + "foreign_tax_id": "123 456 789", + "ftin_not_required": false, + "full_name": "John Doe", + "income_type": "interest", + "ip_address": "127.0.0.1", + "mailing_address_city_state": "Adelaide, South Australia", + "mailing_address_country": "Australia", + "mailing_address_street": "51 Main St", + "paragraph_number": "15", + "percent_rate_withholding": 5, + "permanent_address_city_state": "Adelaide, South Australia", + "permanent_address_country": "Australia", + "permanent_address_street": "20 Main St", + "reference_number": "abc123", + "residency": "Australia", + "revision": "10-2021", + "signer_full_name": "Mr. Signing User", + "tax_id_ssn": "123-00-456", + "timestamp": "2021-06-14T09:31:05Z" + } + ], + "properties": { + "additional_conditions": { + "description": "Any additional conditions to specify", + "type": "string" + }, + "country_citizen": { + "description": "The country that the applicant is a citizen of", + "type": "string" + }, + "date": { + "description": "date signed", + "format": "date", + "type": "string" + }, + "date_of_birth": { + "description": "date of birth of applicant", + "format": "date", + "type": "string" + }, + "foreign_tax_id": { + "description": "Applicant's tax id in their home country", + "type": "string" + }, + "ftin_not_required": { + "description": "Required if foreign_tax_id and tax_id_ssn are empty.", + "type": "boolean" + }, + "full_name": { + "description": "Full name of applicant", + "type": "string" + }, + "income_type": { + "description": "Income type of applicant", + "type": "string" + }, + "ip_address": { + "description": "IP address of applicant when signed", + "type": "string" + }, + "mailing_address_city_state": { + "description": "Mailing city/state of applicant", + "type": "string" + }, + "mailing_address_country": { + "description": "Mailing country for applicant", + "type": "string" + }, + "mailing_address_street": { + "description": "Mailing street address for applicant", + "type": "string" + }, + "paragraph_number": { + "type": "string" + }, + "percent_rate_withholding": { + "type": "integer" + }, + "permanent_address_city_state": { + "description": "Permanent city/state of applicant", + "type": "string" + }, + "permanent_address_country": { + "description": "Permanent country of residence of applicant", + "type": "string" + }, + "permanent_address_street": { + "description": "Permanent street address of applicant", + "type": "string" + }, + "reference_number": { + "type": "string" + }, + "residency": { + "description": "Country of residency of applicant", + "type": "string" + }, + "revision": { + "description": "Revision of the W8BEN form", + "type": "string" + }, + "signer_full_name": { + "description": "Full name of signing user", + "type": "string" + }, + "tax_id_ssn": { + "description": "TaxID/SSN of applicant", + "type": "string" + }, + "timestamp": { + "description": "Timestamp when form data was gathered", + "format": "time", + "type": "string" + } + }, + "required": [ + "country_citizen", + "date", + "date_of_birth", + "full_name", + "ip_address", + "permanent_address_city_state", + "permanent_address_country", + "permanent_address_street", + "revision", + "timestamp", + "signer_full_name" + ], + "type": "object" + }, + "WRMActivityV2": { + "allOf": [ + { + "$ref": "#/components/schemas/CommonCaActivityV2" + }, + { + "properties": { + "cusip": { + "description": "The CUSIP of the security involved with the activity", + "type": "string" + }, + "isin": { + "description": "The ISIN of the security involved with the activity", + "type": "string" + }, + "removed_qty": { + "description": "The removed quantity", + "type": "string" + }, + "symbol": { + "description": "The symbol of the security involved with the activity", + "type": "string" + } + }, + "required": [ + "cusip", + "symbol", + "removed_qty" + ], + "type": "object" + } + ], + "description": "Worthless Removal", + "type": "object" + }, + "WalletError": { + "additionalProperties": false, + "description": "A wallet error response.", + "properties": { + "message": { + "example": "travel_rule_info is missing", + "type": "string" + } + }, + "required": [ + "message" + ], + "type": "object" + }, + "WarrantExerciseElectionActivityV2": { + "allOf": [ + { + "$ref": "#/components/schemas/CommonNTAActivityV2" + }, + { + "$ref": "#/components/schemas/CommonVOFSubtypeActivityV2" + } + ], + "type": "object" + }, + "Watchlist": { + "description": "Represents a set of securities observed by a user.", + "properties": { + "account_id": { + "description": "Unique identifier of the account that owns this watchlist.\n", + "format": "uuid", + "type": "string" + }, + "assets": { + "description": "The contents of the watchlist, in the order as registered", + "items": { + "$ref": "#/components/schemas/Asset" + }, + "type": "array" + }, + "created_at": { + "description": "When watchlist was created", + "format": "date-time", + "type": "string" + }, + "id": { + "description": "Unique identifier of the watchlist itself.\n", + "format": "uuid", + "type": "string" + }, + "name": { + "description": "User friendly Name of watchlist", + "pattern": "^[a-zA-Z0-9]+$", + "type": "string" + }, + "updated_at": { + "description": "When watchlist was last updated", + "format": "date-time", + "type": "string" + } + }, + "required": [ + "id", + "account_id", + "created_at", + "updated_at", + "name" + ], + "title": "Watchlist", + "type": "object" + }, + "WatchlistWithoutAsset": { + "description": "Represents a set of securities observed by a user.", + "properties": { + "account_id": { + "description": "Unique identifier of the account that owns this watchlist.\n", + "format": "uuid", + "type": "string" + }, + "created_at": { + "description": "When watchlist was created", + "format": "date-time", + "type": "string" + }, + "id": { + "description": "Unique identifier of the watchlist itself.\n", + "format": "uuid", + "type": "string" + }, + "name": { + "description": "User friendly Name of watchlist", + "pattern": "^[a-zA-Z0-9]+$", + "type": "string" + }, + "updated_at": { + "description": "When watchlist was last updated", + "format": "date-time", + "type": "string" + } + }, + "required": [ + "id", + "account_id", + "created_at", + "updated_at", + "name" + ], + "title": "Watchlist", + "type": "object" + }, + "WhitelistedAddress": { + "properties": { + "address": { + "description": "The whitelisted address", + "type": "string" + }, + "asset": { + "description": "Symbol of underlying asset for the whitelisted address", + "type": "string" + }, + "chain": { + "description": "Underlying network this address represents", + "type": "string" + }, + "created_at": { + "description": "Timestamp (RFC3339) of account creation.", + "format": "date-time", + "type": "string" + }, + "id": { + "description": "Unique ID for whitelisted address", + "type": "string" + }, + "status": { + "description": "Status of whitelisted address which is either APPROVED or PENDING. Whitelisted addresses will be subjected to a 24 waiting period. After the waiting period is over the status will become APPROVED.", + "enum": [ + "APPROVED", + "PENDING" + ], + "type": "string" + }, + "travel_rule_info": { + "allOf": [ + { + "$ref": "#/components/schemas/TravelRuleInfo" + } + ], + "description": "Travel rule information associated with the whitelisted address." + } + }, + "type": "object" + }, + "bic": { + "description": "Business Identifier Code (BIC/SWIFT).", + "example": "IRVTUS3NXXX", + "maxLength": 11, + "minLength": 11, + "pattern": "^[A-Z0-9]{11}$", + "type": "string" + }, + "bond_status": { + "description": "Status of the bond.", + "enum": [ + "outstanding", + "matured", + "pre_issuance" + ], + "type": "string" + }, + "calendar_day": { + "description": "A calendar day.", + "properties": { + "core_end": { + "description": "The end time of the core market session.", + "example": "2025-01-02T16:00:00-05:00", + "format": "date-time", + "type": "string" + }, + "core_start": { + "description": "The start time of the core market session.", + "example": "2025-01-02T09:30:00-05:00", + "format": "date-time", + "type": "string" + }, + "date": { + "description": "The date of the calendar day.", + "example": "2025-01-02", + "format": "date", + "type": "string" + }, + "lunch_end": { + "description": "The end time of the lunch session.", + "format": "date-time", + "type": "string" + }, + "lunch_start": { + "description": "The start time of the lunch session.", + "format": "date-time", + "type": "string" + }, + "post_end": { + "description": "The end time of the after-hours session.", + "example": "2025-01-02T20:00:00-05:00", + "format": "date-time", + "type": "string" + }, + "post_start": { + "description": "The start time of the after-hours session.", + "example": "2025-01-02T16:00:00-05:00", + "format": "date-time", + "type": "string" + }, + "pre_end": { + "description": "The end time of the pre-market session.", + "example": "2025-01-02T09:30:00-05:00", + "format": "date-time", + "type": "string" + }, + "pre_start": { + "description": "The start time of the pre-market session.", + "example": "2025-01-02T04:00:00-05:00", + "format": "date-time", + "type": "string" + }, + "settlement_date": { + "description": "The settlement date.", + "example": "2025-01-03", + "format": "date", + "type": "string" + } + }, + "required": [ + "date", + "core_start", + "core_end" + ], + "type": "object" + }, + "call_type": { + "description": "The type of call on the bond refers to one of a variety of circumstances under which a callable bond may be called.", + "enum": [ + "ordinary", + "make_whole", + "regulatory", + "special" + ], + "type": "string" + }, + "clock": { + "properties": { + "is_market_day": { + "description": "Whether the clock is on a market day.", + "type": "boolean" + }, + "market": { + "$ref": "#/components/schemas/public_market" + }, + "next_market_close": { + "description": "Next market close timestamp", + "format": "date-time", + "type": "string" + }, + "next_market_open": { + "description": "Next market open timestamp", + "format": "date-time", + "type": "string" + }, + "phase": { + "$ref": "#/components/schemas/phase" + }, + "phase_until": { + "description": "The end of the current phase.", + "format": "date-time", + "type": "string" + }, + "timestamp": { + "description": "The time on the clock.", + "format": "date-time", + "type": "string" + } + }, + "required": [ + "market", + "timestamp", + "is_market_day", + "next_market_open", + "next_market_close", + "phase", + "phase_until" + ], + "type": "object" + }, + "clock_resp": { + "description": "Clock response.", + "properties": { + "clocks": { + "items": { + "$ref": "#/components/schemas/clock" + }, + "type": "array" + } + }, + "required": [ + "clocks" + ], + "type": "object" + }, + "coupon_frequency": { + "description": "How often the coupon is paid", + "enum": [ + "annual", + "semi_annual", + "quarterly", + "monthly", + "zero" + ], + "type": "string" + }, + "coupon_type": { + "description": "The type of the coupon rate", + "enum": [ + "fixed", + "floating", + "zero" + ], + "type": "string" + }, + "day_count": { + "description": "The day count convention used to calculate accrued interest.\n\n- `A/360`: calculates the daily interest using a 360-day year and then multiplies that by the actual number of days in each time period.\n- `A/365`: calculates the daily interest using a 365-day year and then multiplies that by the actual number of days in each time period.\n- `30/360`: calculates the daily interest using a 360-day year and then multiplies that by 30 (standardized month).\n- `30/365`: calculates the daily interest using a 365-day year and then multiplies that by 30 (standardized month).\n- `A/A`: calculates the daily interest using the actual number of days in the year and then multiplies that by the actual number of days in each time period.\n- `30E/360`: number of days equals to the actual number of days (for February). If the start date or the end date of the period is the 31st of a month, that date is set to the 30th. The number of days in a year is 360.\n- `B/252`: calculates the daily interest using a 252-business-day year and then multiplies that by the actual number of days in each time period.\n- `A/364`: calculates the daily interest using a 364-day year and then multiplies that by the actual number of days in each time period.\n", + "enum": [ + "A/360", + "A/365", + "30/360", + "30/365", + "A/A", + "30E/360", + "B/252", + "A/364" + ], + "type": "string" + }, + "legacy_calendar_day": { + "description": "A calendar day.", + "properties": { + "close": { + "description": "The time the market closes at on this date in HH:MM format.", + "example": "16:00", + "type": "string" + }, + "date": { + "description": "Date string in YYYY-MM-DD format.", + "example": "2025-01-02", + "format": "date", + "type": "string" + }, + "open": { + "description": "The time the market opens at on this date in HH:MM format.", + "example": "09:30", + "type": "string" + }, + "session_close": { + "description": "The time the session closes at on this date in HHMM format.", + "example": "2000", + "type": "string" + }, + "session_open": { + "description": "The time the session opens at on this date in HHMM format.", + "example": "0400", + "type": "string" + }, + "settlement_date": { + "description": "Date string in YYYY-MM-DD format. Representing the settlement date for the trade date.", + "example": "2025-01-03", + "format": "date", + "type": "string" + } + }, + "required": [ + "date", + "open", + "close", + "session_open", + "session_close", + "settlement_date" + ], + "type": "object" + }, + "legacy_clock": { + "example": { + "is_open": true, + "next_close": "2025-06-24T16:00:00-04:00", + "next_open": "2025-06-25T09:30:00-04:00", + "timestamp": "2025-06-24T14:15:22-04:00" + }, + "properties": { + "is_open": { + "description": "Whether or not the market is open.", + "type": "boolean" + }, + "next_close": { + "description": "Next market close timestamp.", + "format": "date-time", + "type": "string" + }, + "next_open": { + "description": "Next market open timestamp.", + "format": "date-time", + "type": "string" + }, + "timestamp": { + "description": "Current timestamp.", + "format": "date-time", + "type": "string" + } + }, + "required": [ + "timestamp", + "is_open", + "next_open", + "next_close" + ], + "title": "Clock", + "type": "object" + }, + "legacy_public_calendar_resp": { + "items": { + "$ref": "#/components/schemas/legacy_calendar_day" + }, + "type": "array" + }, + "market": { + "description": "The market identifier (MIC, BIC, or acronym).", + "enum": [ + "BMO", + "BNYM", + "BOATS", + "CEUX", + "CHIX", + "HKEX", + "IEX", + "IEXG", + "ISE", + "LSE", + "MTA", + "MTAA", + "NASDAQ", + "NYSE", + "OCEA", + "OPRA", + "OTC", + "OTCM", + "SIFMA", + "TADAWUL", + "XAMS", + "XBRU", + "XDUB", + "XETR", + "XETRA", + "XHKG", + "XLIS", + "XLON", + "XNAS", + "XNYS", + "XPAR", + "XSAU" + ], + "type": "string" + }, + "market_acronym": { + "description": "The acronym of the market.", + "example": "NYSE", + "type": "string" + }, + "market_name": { + "description": "The full name of the market.", + "example": "New York Stock Exchange", + "type": "string" + }, + "market_timezone": { + "description": "The timezone of the market.", + "example": "America/New_York", + "type": "string" + }, + "mic": { + "description": "Market identifier code (ISO 10383).", + "example": "XNYS", + "maxLength": 4, + "minLength": 4, + "pattern": "^[A-Z0-9]{4}$", + "type": "string" + }, + "phase": { + "enum": [ + "closed", + "pre", + "core", + "lunch", + "post" + ], + "type": "string" + }, + "public_calendar_resp": { + "description": "Calendar response.", + "properties": { + "calendar": { + "description": "The market calendar.", + "items": { + "$ref": "#/components/schemas/calendar_day" + }, + "type": "array" + }, + "market": { + "$ref": "#/components/schemas/public_market" + } + }, + "required": [ + "market", + "calendar" + ], + "type": "object" + }, + "public_market": { + "description": "A market.", + "properties": { + "acronym": { + "$ref": "#/components/schemas/market_acronym" + }, + "bic": { + "$ref": "#/components/schemas/bic" + }, + "mic": { + "$ref": "#/components/schemas/mic" + }, + "name": { + "$ref": "#/components/schemas/market_name" + }, + "timezone": { + "$ref": "#/components/schemas/market_timezone" + } + }, + "required": [ + "acronym", + "name", + "timezone" + ], + "type": "object" + }, + "sp_outlook": { + "description": "A Standard & Poor's rating outlook indicates S&P's view regarding the potential direction of a long-term credit rating over the intermediate term (2 years for investment grade, 1 year for speculative grade)", + "enum": [ + "positive", + "negative", + "developing", + "stable", + "not_rated", + "not_meaningful" + ], + "type": "string" + }, + "treasury_subtype": { + "description": "The subtype of the treasury.", + "enum": [ + "bond", + "bill", + "note", + "strips", + "tips", + "floating" + ], + "type": "string" + }, + "us_corporate": { + "description": "A US corporate", + "properties": { + "accrued_interest": { + "description": "The interest that has accumulated on a bond in dollars per bond between the last interest payment and the present date that has not yet been paid to the bondholder", + "format": "double", + "type": "number" + }, + "bond_status": { + "$ref": "#/components/schemas/bond_status" + }, + "call_type": { + "$ref": "#/components/schemas/call_type" + }, + "callable": { + "description": "Whether the bond is callable, meaning the issuer has the right, but not the obligation to redeem the bond - in other words, pay out the bondholder - before its maturity date at a set price (the call price)", + "type": "boolean" + }, + "close_price": { + "description": "The price of the last transaction of a security before the market closes for normal trading, shown as a percentage of par value", + "format": "double", + "type": "number" + }, + "close_price_date": { + "description": "The date of the close price", + "format": "date", + "type": "string" + }, + "close_yield_to_maturity": { + "description": "Yield to maturity of the treasury after the last close", + "format": "double", + "type": "number" + }, + "close_yield_to_worst": { + "description": "Yield to worst of the treasury after the last close", + "format": "double", + "type": "number" + }, + "convertible": { + "description": "A flag indicating whether the bond is convertible", + "type": "boolean" + }, + "country_domicile": { + "description": "The country where the corporate is domiciled in the 2-alpha country code format (e.g., US, CA)", + "type": "string" + }, + "coupon": { + "description": "The annual interest rate paid on the bond as a percentage of par value", + "format": "double", + "type": "number" + }, + "coupon_frequency": { + "$ref": "#/components/schemas/coupon_frequency" + }, + "coupon_type": { + "$ref": "#/components/schemas/coupon_type" + }, + "cusip": { + "description": "CUSIP is a nine-character alphanumeric code that uniquely identifies the security", + "maxLength": 9, + "minLength": 9, + "pattern": "^[A-Z0-9]{9}$", + "type": "string" + }, + "dated_date": { + "description": "The dated date marks the beginning of the period for which interest starts accruing on the bond", + "format": "date", + "type": "string" + }, + "day_count": { + "$ref": "#/components/schemas/day_count" + }, + "description": { + "description": "Description of the corporate bond", + "type": "string" + }, + "description_short": { + "description": "Short description of the corporate bond", + "type": "string" + }, + "first_coupon_date": { + "description": "The date of the first coupon payment", + "format": "date", + "type": "string" + }, + "fractionable": { + "description": "Whether the corporate can be traded in fractional amounts", + "type": "boolean" + }, + "isin": { + "description": "International Securities Identification Number", + "maxLength": 12, + "minLength": 12, + "pattern": "^[A-Z]{2}[A-Z0-9]{9}[0-9]$", + "type": "string" + }, + "issue_date": { + "description": "The date on which the bond was issued", + "format": "date", + "type": "string" + }, + "issue_minimum_denomination": { + "description": "The smallest unit of the bond that can be purchased at its initial offering", + "type": "number" + }, + "issue_price": { + "description": "The price at which the bond was originally issued as a percentage of par value", + "format": "double", + "type": "number" + }, + "issue_size": { + "description": "The total size amount of the bond issue in the issuing currency", + "type": "number" + }, + "issuer": { + "description": "The name of the issuer of the corporate bond", + "type": "string" + }, + "last_coupon_date": { + "description": "The date of the last coupon payment", + "format": "date", + "type": "string" + }, + "liquidity_institutional_aggregate": { + "description": "Score (from 1-5 or null if the bond is not priced/tradable) reflecting the historical depth of executable liquidity to buy or sell (no minimum trading sizes)", + "type": "number" + }, + "liquidity_institutional_buy": { + "description": "Score (from 1-5 or null if the bond is not priced/tradable) reflecting the historical depth of executable liquidity to buy (no minimum trading sizes)", + "type": "number" + }, + "liquidity_institutional_sell": { + "description": "Score (from 1-5 or null if the bond is not priced/tradable) reflecting the historical depth of executable liquidity to sell (no minimum trading sizes)", + "type": "number" + }, + "liquidity_micro_aggregate": { + "description": "Score (from 1-5 or null if the bond is not priced/tradable) reflecting the historical depth of executable liquidity to buy or sell with minimum trading sizes less than or equal to $1,000.00", + "type": "number" + }, + "liquidity_micro_buy": { + "description": "Score (from 1-5 if the bond is priced, or null if the bond is not tradable) reflecting the historical depth of executable liquidity to buy with minimum trading sizes less than or equal to $1,000.00", + "type": "number" + }, + "liquidity_micro_sell": { + "description": "Score (from 1-5 or null if the bond is not priced/tradable) reflecting the historical depth of executable liquidity to sell with minimum trading sizes less than or equal to $1,000.00", + "type": "number" + }, + "liquidity_retail_aggregate": { + "description": "Score (from 1-5 or null if the bond is not priced/tradable) reflecting the historical depth of executable liquidity to buy or sell with minimum trading sizes less than or equal to 10,000", + "type": "number" + }, + "liquidity_retail_buy": { + "description": "Score (from 1-5 or null if the bond is not priced/tradable) reflecting the historical depth of executable liquidity to buy with minimum trading sizes less than or equal to $10,000.00", + "type": "number" + }, + "liquidity_retail_sell": { + "description": "Score (from 1-5 or null if the bond is not priced/tradable) reflecting the historical depth of executable liquidity to sell with minimum trading sizes less than or equal to 10,000", + "type": "number" + }, + "marginable": { + "description": "Whether the corporate is marginable", + "type": "boolean" + }, + "maturity_date": { + "description": "The date on which the bond matures", + "format": "date", + "type": "string" + }, + "next_call_date": { + "description": "The date of the next possible call on the bond.", + "format": "date", + "type": "string" + }, + "next_call_price": { + "description": "The price at which a callable bond can be redeemed by the issuer on the next call date, as a percentage of par.", + "format": "double", + "type": "number" + }, + "next_coupon_date": { + "description": "The date of the next coupon payment", + "format": "date", + "type": "string" + }, + "par_value": { + "description": "The amount that the issuer of the bond will pay back to the bondholder upon maturity", + "type": "number" + }, + "perpetual": { + "description": "A flag representing whether a bond is perpetual", + "type": "boolean" + }, + "puttable": { + "description": "Whether the bond is puttable, meaning the bondholder has the right, but not the obligation to sell the bond back to the issuer at a set price (the put price) on specified dates before maturity", + "type": "boolean" + }, + "reg_s": { + "description": "Indicates whether the security falls under Regulation S, a rule that provides an exemption from the registration requirements for securities offerings made outside the United States", + "type": "boolean" + }, + "reissue_date": { + "description": "The date on which the corporate was reissued", + "format": "date", + "type": "string" + }, + "reissue_price": { + "description": "The price at which the corporate was reissued as a percentage of par value", + "format": "double", + "type": "number" + }, + "reissue_size": { + "description": "The total size amount of the corporate reissue in the issuing currency", + "type": "number" + }, + "sector": { + "description": "The sector of the corporate bond", + "type": "string" + }, + "seniority": { + "description": "The seniority of the corporate bond", + "type": "string" + }, + "sp_creditwatch": { + "description": "S&P's CreditWatch highlights S&P's opinion regarding the potential direction of a short-term or long-term rating", + "type": "string" + }, + "sp_creditwatch_date": { + "description": "The date of the most recent Standard & Poor's CreditWatch for the bond in YYYY-MM-DD format", + "format": "date", + "type": "string" + }, + "sp_outlook": { + "$ref": "#/components/schemas/sp_outlook" + }, + "sp_outlook_date": { + "description": "The date of the most recent Standard & Poor's outlook for the bond in YYYY-MM-DD format", + "format": "date", + "type": "string" + }, + "sp_rating": { + "description": "Standard & Poor's rating for the bond in the standard AAA - D format", + "type": "string" + }, + "sp_rating_date": { + "description": "The date in the timezone of the issuing country of the most recent Standard & Poor's rating for the bond in YYYY-MM-DD format", + "format": "date", + "type": "string" + }, + "ticker": { + "description": "The ticker symbol of the corporate", + "type": "string" + }, + "tradable": { + "description": "Whether the treasury is tradable", + "type": "boolean" + } + }, + "required": [ + "isin", + "cusip", + "bond_status", + "tradable", + "marginable", + "fractionable", + "issue_date", + "country_domicile", + "ticker", + "seniority", + "issuer", + "sector", + "description", + "description_short", + "coupon", + "coupon_type", + "coupon_frequency", + "perpetual", + "day_count", + "dated_date", + "issue_size", + "issue_price", + "issue_minimum_denomination", + "par_value", + "callable", + "puttable", + "convertible", + "reg_s" + ], + "type": "object" + }, + "us_corporates_resp": { + "properties": { + "us_corporates": { + "items": { + "$ref": "#/components/schemas/us_corporate" + }, + "type": "array" + } + }, + "required": [ + "us_corporates" + ], + "type": "object" + }, + "us_treasuries_resp": { + "properties": { + "us_treasuries": { + "items": { + "$ref": "#/components/schemas/us_treasury" + }, + "type": "array" + } + }, + "required": [ + "us_treasuries" + ], + "type": "object" + }, + "us_treasury": { + "description": "A US treasury", + "properties": { + "bond_status": { + "$ref": "#/components/schemas/bond_status" + }, + "close_price": { + "description": "The price of the last transaction of a security before the market closes for normal trading, shown as a percentage of par value", + "format": "double", + "type": "number" + }, + "close_price_date": { + "description": "The date of the close price", + "format": "date", + "type": "string" + }, + "close_yield_to_maturity": { + "description": "Yield to maturity of the treasury after the last close", + "format": "double", + "type": "number" + }, + "close_yield_to_worst": { + "description": "Yield to worst of the treasury after the last close", + "format": "double", + "type": "number" + }, + "coupon": { + "description": "The annual interest rate paid on the bond as a percentage of par value", + "format": "double", + "type": "number" + }, + "coupon_frequency": { + "$ref": "#/components/schemas/coupon_frequency" + }, + "coupon_type": { + "$ref": "#/components/schemas/coupon_type" + }, + "cusip": { + "description": "CUSIP is a nine-character alphanumeric code that uniquely identifies the security", + "maxLength": 9, + "minLength": 9, + "pattern": "^[A-Z0-9]{9}$", + "type": "string" + }, + "description": { + "description": "Description of the treasury", + "type": "string" + }, + "description_short": { + "description": "Short description of the treasury", + "type": "string" + }, + "first_coupon_date": { + "description": "The date of the first coupon payment", + "format": "date", + "type": "string" + }, + "fractionable": { + "description": "Whether the treasury can be traded in fractional amounts", + "type": "boolean" + }, + "isin": { + "description": "International Securities Identification Number", + "maxLength": 12, + "minLength": 12, + "pattern": "^[A-Z]{2}[A-Z0-9]{9}[0-9]$", + "type": "string" + }, + "issue_date": { + "description": "The date on which the bond was issued", + "format": "date", + "type": "string" + }, + "last_coupon_date": { + "description": "The date of the last coupon payment", + "format": "date", + "type": "string" + }, + "maturity_date": { + "description": "The date on which the bond matures", + "format": "date", + "type": "string" + }, + "next_coupon_date": { + "description": "The date of the next coupon payment", + "format": "date", + "type": "string" + }, + "subtype": { + "$ref": "#/components/schemas/treasury_subtype" + }, + "tradable": { + "description": "Whether the treasury is tradable", + "type": "boolean" + } + }, + "required": [ + "isin", + "cusip", + "bond_status", + "tradable", + "fractionable", + "subtype", "issue_date", "maturity_date", "description", @@ -13858,42 +15955,736 @@ "coupon_type", "coupon_frequency" ], - "type": "object" + "type": "object" + } + }, + "securitySchemes": { + "BasicAuth": { + "scheme": "basic", + "type": "http" + } + } + }, + "info": { + "contact": { + "email": "support@alpaca.markets", + "name": "Alpaca Support", + "url": "https://alpaca.markets/support" + }, + "description": "Open brokerage accounts, enable stock, options and crypto trading. Manage the ongoing user experience and brokerage customer lifecycle with the Alpaca Broker API", + "termsOfService": "https://s3.amazonaws.com/files.alpaca.markets/disclosures/library/TermsAndConditions.pdf", + "title": "Broker API", + "version": "1.1.1" + }, + "openapi": "3.1.2", + "paths": { + "/v1beta/accounts/{account_id}/funding_wallet": { + "get": { + "description": "Returns the funding wallet for the specified account. Responds with 404 if the account has not been onboarded to funding wallets or a wallet has not been created.", + "operationId": "getFundingWallet", + "parameters": [ + { + "description": "UUID alpaca account ID", + "in": "path", + "name": "account_id", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/FundingWallet" + } + } + }, + "description": "funding wallet" + }, + "default": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + }, + "description": "error" + } + }, + "summary": "Retrieve funding wallet", + "tags": [ + "Funding Wallets" + ] + }, + "post": { + "description": "Creates a funding wallet for the specified account. The request is rejected if the account already has a funding wallet.", + "operationId": "createFundingWallet", + "parameters": [ + { + "description": "UUID alpaca account ID", + "in": "path", + "name": "account_id", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/FundingWallet" + } + } + }, + "description": "funding wallet" + }, + "default": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + }, + "description": "error" + } + }, + "summary": "Creates a funding wallet", + "tags": [ + "Funding Wallets" + ] + } + }, + "/v1beta/accounts/{account_id}/funding_wallet/funding_details": { + "get": { + "description": "Returns a list of funding details if it exists. Query parameters must be passed to create a new funding details object if none exist.", + "operationId": "listFundingDetails", + "parameters": [ + { + "description": "UUID alpaca account ID", + "in": "path", + "name": "account_id", + "required": true, + "schema": { + "type": "string" + } + }, + { + "description": "The type of SSI to be returned, priority (SWIFT) or regular (local). A null value returns all payment types.", + "in": "query", + "name": "payment_type", + "required": false, + "schema": { + "$ref": "#/components/schemas/FundingDetailPaymentType" + } + }, + { + "description": "Should be provided in ISO 4217 standard", + "in": "query", + "name": "currency", + "required": false, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "items": { + "$ref": "#/components/schemas/ListFundingDetails" + } + } + } + }, + "description": "list of wallets" + }, + "default": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + }, + "description": "error" + } + }, + "summary": "Retrieve funding details", + "tags": [ + "Funding Wallets" + ] + } + }, + "/v1beta/accounts/{account_id}/funding_wallet/recipient_bank": { + "delete": { + "description": "deletes a recipient bank.", + "operationId": "deleteFundingWalletRecipientBank", + "parameters": [ + { + "description": "UUID alpaca account ID", + "in": "path", + "name": "account_id", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "recipient bank" + }, + "default": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + }, + "description": "Error response." + } + }, + "summary": "Delete a recipient bank", + "tags": [ + "Funding Wallets" + ] + }, + "get": { + "description": "Returns the recipient bank registered against the specified account's funding wallet. Responds with 404 if no recipient bank has been registered for the account.", + "operationId": "getFundingWalletRecipientBank", + "parameters": [ + { + "description": "UUID alpaca account ID", + "in": "path", + "name": "account_id", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "recipient bank" + }, + "default": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + }, + "description": "Error response." + } + }, + "summary": "Retrieve recipient bank", + "tags": [ + "Funding Wallets" + ] + }, + "post": { + "description": "Creates a new recipient bank. Returns the new recipient bank entity on success. entity.", + "operationId": "createFundingWalletRecipientBank", + "parameters": [ + { + "description": "UUID alpaca account ID", + "in": "path", + "name": "account_id", + "required": true, + "schema": { + "type": "string" + } + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "properties": { + "account_number": { + "description": "Bank account number.", + "type": "string" + }, + "account_type": { + "description": "Bank account type.", + "enum": [ + "checking", + "savings" + ], + "type": "string" + }, + "bank_account_holder_name": { + "description": "Bank account holder's name.", + "type": "string" + }, + "bank_country": { + "description": "Two-letter code for the country in which the beneficiary's bank account is held.", + "type": "string" + }, + "bank_name": { + "type": "string" + }, + "bic_swift": { + "description": "BIC/SWIFT code", + "type": "string" + }, + "city": { + "description": "City", + "type": "string" + }, + "currency": { + "description": "Currency in which money is held in the beneficiary's bank account. ISO-3 currency code.", + "type": "string" + }, + "iban": { + "description": "IBAN code", + "type": "string" + }, + "postal_code": { + "description": "Postal code", + "type": "string" + }, + "routing_code": { + "description": "Routing code for routing_code_type. If supplied, routing_code_type should also be supplied.", + "type": "string" + }, + "routing_code_type": { + "description": "Local payment routing system. If supplied, routing_code should also be supplied.", + "enum": [ + "sort_code", + "aba", + "bsb_code", + "institution_no", + "bank_code", + "branch_code", + "clabe", + "cnaps", + "ifsc" + ], + "type": "string" + }, + "state_or_province": { + "description": "State or province.", + "type": "string" + }, + "street_address": { + "description": "First line of address.", + "type": "string" + } + }, + "required": [ + "bank_country", + "currency", + "bank_name", + "street_address", + "city", + "account_number" + ] + } + } + } + }, + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/FundingWalletRecipientBank" + } + } + }, + "description": "Success." + }, + "default": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + }, + "description": "error" + } + }, + "summary": "Create a recipient bank", + "tags": [ + "Funding Wallets" + ] + } + }, + "/v1beta/accounts/{account_id}/funding_wallet/transfers": { + "get": { + "description": "Returns all funding wallet transfers (deposits and withdrawals) associated with the specified account.", + "operationId": "getFundingWalletTransfers", + "parameters": [ + { + "description": "UUID alpaca account ID", + "in": "path", + "name": "account_id", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ListTransfersResponse" + } + } + }, + "description": "transfers" + }, + "default": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + }, + "description": "error" + } + }, + "summary": "Retrieve funding wallet transfers", + "tags": [ + "Funding Wallets" + ] + } + }, + "/v1beta/accounts/{account_id}/funding_wallet/transfers/{transfer_id}": { + "get": { + "description": "Returns a single funding wallet transfer for the specified account by `transfer_id`. Responds with 404 if no matching transfer exists for the account.", + "operationId": "getFundingWalletTransferByID", + "parameters": [ + { + "description": "Alpaca account UUID", + "in": "path", + "name": "account_id", + "required": true, + "schema": { + "type": "string" + } + }, + { + "description": "transfer UUID", + "in": "path", + "name": "transfer_id", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/FundingWalletTransfer" + } + } + }, + "description": "transfer response if found" + }, + "404": { + "description": "error response if transfer is not found" + }, + "default": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + }, + "description": "error" + } + }, + "summary": "Retrieve funding wallet transfer by ID", + "tags": [ + "Funding Wallets" + ] } }, - "securitySchemes": { - "BasicAuth": { - "scheme": "basic", - "type": "http" + "/v1beta/accounts/{account_id}/funding_wallet/withdrawal": { + "post": { + "description": "Creates a withdrawal", + "operationId": "createFundingWalletWithdrawal", + "parameters": [ + { + "description": "UUID alpaca account ID", + "in": "path", + "name": "account_id", + "required": true, + "schema": { + "type": "string" + } + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CreateFundingWalletWithdrawalRequest" + } + } + }, + "required": true + }, + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/FundingWalletTransfer" + } + } + }, + "description": "Transfer" + }, + "default": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + }, + "description": "Error response." + } + }, + "summary": "Create a withdrawal", + "tags": [ + "Funding Wallets" + ] } - } - }, - "info": { - "contact": { - "email": "support@alpaca.markets", - "name": "Alpaca Support", - "url": "https://alpaca.markets/support" }, - "description": "Open brokerage accounts, enable stock, options and crypto trading. Manage the ongoing user experience and brokerage customer lifecycle with the Alpaca Broker API", - "termsOfService": "https://s3.amazonaws.com/files.alpaca.markets/disclosures/library/TermsAndConditions.pdf", - "title": "Broker API", - "version": "1.1.1" - }, - "openapi": "3.0.0", - "paths": { - "/v1beta/accounts/{account_id}/funding_wallet": { + "/v1beta/accounts/funding_wallet": { + "post": { + "description": "Creates funding wallets for a list of accounts in a single request. Every `account_id` in the request must belong to your firm, and the request is rejected if a funding wallet already exists for any of the supplied accounts.", + "operationId": "batchCreateFundingWallets", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BatchCreateFundingWalletRequest" + } + } + }, + "required": true + }, + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BatchCreateFundingWalletResponse" + } + } + }, + "description": "Funding wallets" + }, + "default": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + }, + "description": "Error response." + } + }, + "summary": "Batch create funding wallets", + "tags": [ + "Funding Wallets" + ] + } + }, + "/v1beta/demo/banking/funding": { + "post": { + "description": "Simulates an inbound deposit into an account's funding wallet for end-to-end testing of the deposit flow.\n\nThe transfer can only credit accounts that belong to your firm. Available in non-production (sandbox) environments only.", + "operationId": "demoDepositFunding", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DemoFundingTransfer" + } + } + }, + "required": true + }, + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DemoFundingTransfer" + } + } + }, + "description": "Demo deposit funding transfer" + }, + "default": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + }, + "description": "Error response." + } + }, + "summary": "Create sandbox deposit transfer", + "tags": [ + "Funding Wallets" + ] + } + }, + "/v1beta1/acats": { "get": { - "description": "Returns the funding wallet for the specified account. Responds with 404 if the account has not been onboarded to funding wallets or a wallet has not been created.", - "operationId": "getFundingWallet", + "description": "List all incoming and outgoing transfers, for all accounts. The list may include transfers not initiated using this API. The list is paginated: use the `page_token` query parameter with the value returned in the response to continue listing transfers.", + "operationId": "listACATSTransfers", "parameters": [ { - "description": "UUID alpaca account ID", - "in": "path", - "name": "account_id", - "required": true, + "description": "Filter by the account number associated with the transfer", + "in": "query", + "name": "account_number", + "required": false, + "schema": { + "type": "string" + } + }, + { + "description": "Filter by the type of the transfer", + "in": "query", + "name": "transfer_type", + "required": false, + "schema": { + "$ref": "#/components/schemas/AcatsDtccTransferType" + } + }, + { + "description": "Filter by the direction of the transfer", + "in": "query", + "name": "transfer_direction", + "required": false, + "schema": { + "$ref": "#/components/schemas/AcatsTransferDirection" + } + }, + { + "description": "Filter by the current status of the transfer", + "in": "query", + "name": "transfer_status", + "required": false, + "schema": { + "$ref": "#/components/schemas/AcatsTransferStatus" + } + }, + { + "description": "Filter by the current DTCC status of the transfer", + "in": "query", + "name": "dtcc_status", + "required": false, + "schema": { + "$ref": "#/components/schemas/AcatsDtccStatus" + } + }, + { + "description": "Filter by the contra broker number of the transfer", + "in": "query", + "name": "contra_broker_number", + "required": false, + "schema": { + "type": "string" + } + }, + { + "description": "List transfers created at or after this RFC-3339 timestamp (e.g. `2026-01-01T00:00:00Z`).", + "in": "query", + "name": "since", + "required": false, + "schema": { + "$ref": "#/components/schemas/AcatsTimestamp" + } + }, + { + "description": "List transfers created strictly before this RFC-3339 timestamp (exclusive -- e.g. `2026-01-01T00:00:00Z` excludes transfers created exactly at that instant).", + "in": "query", + "name": "until", + "required": false, + "schema": { + "$ref": "#/components/schemas/AcatsTimestamp" + } + }, + { + "description": "Specify whether transfers should be sorted by creation time in ascending or descending order", + "in": "query", + "name": "sort_direction", + "required": false, + "schema": { + "$ref": "#/components/schemas/AcatsSortDirection" + } + }, + { + "description": "Specify the field to sort by. Defaults to `created_at`.", + "in": "query", + "name": "sort_by", + "required": false, "schema": { + "default": "created_at", + "enum": [ + "account_number", + "total_transfer_value", + "created_at", + "contra_account_number", + "contra_broker_number", + "external_transfer_id", + "settled_at", + "status", + "transfer_type", + "updated_at" + ], "type": "string" } + }, + { + "description": "Maximum number of transfers to return on one page", + "in": "query", + "name": "limit", + "required": false, + "schema": { + "format": "int32", + "type": "integer" + } + }, + { + "description": "Page token for the next or previous page, used to continue listing transfers", + "in": "query", + "name": "page_token", + "required": false, + "schema": { + "$ref": "#/components/schemas/AcatsPageToken" + } } ], "responses": { @@ -13901,100 +16692,81 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/FundingWallet" + "$ref": "#/components/schemas/AcatsListTransfersResponse" } } }, - "description": "funding wallet" + "description": "OK" }, - "default": { + "400": { "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/Error" + "$ref": "#/components/schemas/AcatsError" } } }, - "description": "error" - } - }, - "summary": "Retrieve funding wallet", - "tags": [ - "Funding Wallets" - ] - }, - "post": { - "description": "Creates a funding wallet for the specified account. The request is rejected if the account already has a funding wallet.", - "operationId": "createFundingWallet", - "parameters": [ - { - "description": "UUID alpaca account ID", - "in": "path", - "name": "account_id", - "required": true, - "schema": { - "type": "string" - } - } - ], - "responses": { - "200": { + "description": "Bad Request" + }, + "403": { "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/FundingWallet" + "$ref": "#/components/schemas/AcatsError" } } }, - "description": "funding wallet" + "description": "Forbidden" }, - "default": { + "500": { "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/Error" + "$ref": "#/components/schemas/AcatsError" } } }, - "description": "error" + "description": "Internal Server Error" } }, - "summary": "Creates a funding wallet", + "summary": "List transfers", "tags": [ - "Funding Wallets" + "ACATS" ] } }, - "/v1beta/accounts/{account_id}/funding_wallet/funding_details": { + "/v1beta1/acats/{account_id}": { "get": { - "description": "Returns a list of funding details if it exists. Query parameters must be passed to create a new funding details object if none exist.", - "operationId": "listFundingDetails", + "description": "List all incoming and outgoing transfers where the account was one of the parties. The list may include transfers not initiated using this API. By default, the response includes all transfers for the account for backwards compatibility. To paginate results, provide the `limit` query parameter and use the `page_token` value returned in the response to continue listing transfers.", + "operationId": "listACATSTransfersByAccount", "parameters": [ { - "description": "UUID alpaca account ID", + "description": "Account ID associated with the ACATS transfer", "in": "path", "name": "account_id", "required": true, "schema": { + "format": "uuid", "type": "string" } }, { - "description": "The type of SSI to be returned, priority (SWIFT) or regular (local). A null value returns all payment types.", + "description": "Maximum number of transfers to return on one page", "in": "query", - "name": "payment_type", + "name": "limit", "required": false, "schema": { - "$ref": "#/components/schemas/FundingDetailPaymentType" + "format": "int32", + "type": "integer" } }, { - "description": "Should be provided in ISO 4217 standard", + "description": "Page token for the next or previous page, used to continue listing transfers", "in": "query", - "name": "currency", + "name": "page_token", "required": false, "schema": { - "type": "string" + "$ref": "#/components/schemas/AcatsPageToken" } } ], @@ -14003,110 +16775,49 @@ "content": { "application/json": { "schema": { - "items": { - "$ref": "#/components/schemas/ListFundingDetails" - } - } - } - }, - "description": "list of wallets" - }, - "default": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/Error" + "$ref": "#/components/schemas/AcatsListTransfersResponse" } } }, - "description": "error" - } - }, - "summary": "Retrieve funding details", - "tags": [ - "Funding Wallets" - ] - } - }, - "/v1beta/accounts/{account_id}/funding_wallet/recipient_bank": { - "delete": { - "description": "deletes a recipient bank.", - "operationId": "deleteFundingWalletRecipientBank", - "parameters": [ - { - "description": "UUID alpaca account ID", - "in": "path", - "name": "account_id", - "required": true, - "schema": { - "type": "string" - } - } - ], - "responses": { - "200": { - "description": "recipient bank" + "description": "OK" }, - "default": { + "403": { "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/Error" + "$ref": "#/components/schemas/AcatsError" } } }, - "description": "Error response." - } - }, - "summary": "Delete a recipient bank", - "tags": [ - "Funding Wallets" - ] - }, - "get": { - "description": "Returns the recipient bank registered against the specified account's funding wallet. Responds with 404 if no recipient bank has been registered for the account.", - "operationId": "getFundingWalletRecipientBank", - "parameters": [ - { - "description": "UUID alpaca account ID", - "in": "path", - "name": "account_id", - "required": true, - "schema": { - "type": "string" - } - } - ], - "responses": { - "200": { - "description": "recipient bank" + "description": "Forbidden" }, - "default": { + "500": { "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/Error" + "$ref": "#/components/schemas/AcatsError" } } }, - "description": "Error response." + "description": "Internal Server Error" } }, - "summary": "Retrieve recipient bank", + "summary": "List transfers for an account", "tags": [ - "Funding Wallets" + "ACATS" ] }, "post": { - "description": "Creates a new recipient bank. Returns the new recipient bank entity on success. entity.", - "operationId": "createFundingWalletRecipientBank", + "description": "This endpoint allows you to create new incoming ACATS requests. We currently support full and partial transfer types, for US equities and cash. Note that after submitting a request, it might take some time for the transfer to show up on the other endpoints. Transfers are not immediately sent to DTCC, and will appear in the `PENDING` state initially.", + "operationId": "requestACATSTransfer", "parameters": [ { - "description": "UUID alpaca account ID", + "description": "Account ID associated with the ACATS transfer", "in": "path", "name": "account_id", "required": true, "schema": { + "format": "uuid", "type": "string" } } @@ -14115,181 +16826,107 @@ "content": { "application/json": { "schema": { - "properties": { - "account_number": { - "description": "Bank account number.", - "type": "string" - }, - "account_type": { - "description": "Bank account type.", - "enum": [ - "checking", - "savings" - ], - "type": "string" - }, - "bank_account_holder_name": { - "description": "Bank account holder's name.", - "type": "string" - }, - "bank_country": { - "description": "Two-letter code for the country in which the beneficiary's bank account is held.", - "type": "string" - }, - "bank_name": { - "type": "string" - }, - "bic_swift": { - "description": "BIC/SWIFT code", - "type": "string" - }, - "city": { - "description": "City", - "type": "string" - }, - "currency": { - "description": "Currency in which money is held in the beneficiary's bank account. ISO-3 currency code.", - "type": "string" - }, - "iban": { - "description": "IBAN code", - "type": "string" - }, - "postal_code": { - "description": "Postal code", - "type": "string" - }, - "routing_code": { - "description": "Routing code for routing_code_type. If supplied, routing_code_type should also be supplied.", - "type": "string" - }, - "routing_code_type": { - "description": "Local payment routing system. If supplied, routing_code should also be supplied.", - "enum": [ - "sort_code", - "aba", - "bsb_code", - "institution_no", - "bank_code", - "branch_code", - "clabe", - "cnaps", - "ifsc" - ], - "type": "string" - }, - "state_or_province": { - "description": "State or province.", - "type": "string" - }, - "street_address": { - "description": "First line of address.", - "type": "string" - } - }, - "required": [ - "bank_country", - "currency", - "bank_name", - "street_address", - "city", - "account_number" - ] + "$ref": "#/components/schemas/AcatsInitiateTransferRequest" } } - } + }, + "description": "Details of the incoming ACATS transfer to initiate.", + "required": true }, "responses": { - "200": { + "202": { "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/FundingWalletRecipientBank" + "$ref": "#/components/schemas/AcatsInitiateTransferResponse" + } + } + }, + "description": "Accepted" + }, + "400": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/AcatsError" } } }, - "description": "Success." + "description": "Bad Request" }, - "default": { + "403": { "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/Error" + "$ref": "#/components/schemas/AcatsError" } } }, - "description": "error" - } - }, - "summary": "Create a recipient bank", - "tags": [ - "Funding Wallets" - ] - } - }, - "/v1beta/accounts/{account_id}/funding_wallet/transfers": { - "get": { - "description": "Returns all funding wallet transfers (deposits and withdrawals) associated with the specified account.", - "operationId": "getFundingWalletTransfers", - "parameters": [ - { - "description": "UUID alpaca account ID", - "in": "path", - "name": "account_id", - "required": true, - "schema": { - "type": "string" - } - } - ], - "responses": { - "200": { + "description": "Forbidden" + }, + "422": { "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/ListTransfersResponse" + "$ref": "#/components/schemas/AcatsError" } } }, - "description": "transfers" + "description": "Unprocessable Entity. Returned when:\n- the cleansed contra account number contains no digit at all;\n- `validate_account_number` is `true` and the contra account number matches none of the contra broker's enabled format rules;\n- the calling correspondent is pending migration, in which case the account is treated as being in distribution.\n" }, - "default": { + "500": { "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/Error" + "$ref": "#/components/schemas/AcatsError" } } }, - "description": "error" + "description": "Internal Server Error" } }, - "summary": "Retrieve funding wallet transfers", + "summary": "Initiate an incoming transfer", "tags": [ - "Funding Wallets" - ] + "ACATS" + ], + "x-readme": { + "code-samples": [ + { + "code": "curl --request POST \\\n --url https://broker-api.alpaca.markets/v1beta1/acats/{account_id} \\\n -u \"$BROKER_API_KEY:$BROKER_API_SECRET\" \\\n --header 'content-type: application/json' \\\n --data '{\n \"contra_account_details\": {\n \"broker_number\": \"0158\",\n \"account_number\": \"A00000123\",\n \"customer_name\": \"Jane Doe\",\n \"customer_tax_id\": \"123456789\"\n },\n \"transfer_details\": {\n \"type\": \"PARTIAL\",\n \"cash\": [\n { \"amount\": \"10000\", \"currency\": \"USD\" }\n ],\n \"securities\": {\n \"equities\": [\n { \"symbol\": \"AAPL\", \"qty\": \"2\", \"position\": \"LONG\" },\n { \"cusip\": \"123456789\", \"qty\": \"5\", \"position\": \"LONG\" },\n { \"symbol\": \"TSLA\", \"position\": \"SHORT\" }\n ]\n }\n },\n \"ip_address\": \"203.0.113.42\",\n \"signed_at\": \"2026-04-29T13:00:00Z\"\n }'", + "language": "shell", + "name": "Partial transfer (cash + equities)" + }, + { + "code": "curl --request POST \\\n --url https://broker-api.alpaca.markets/v1beta1/acats/{account_id} \\\n -u \"$BROKER_API_KEY:$BROKER_API_SECRET\" \\\n --header 'content-type: application/json' \\\n --data '{\n \"contra_account_details\": {\n \"broker_number\": \"0158\",\n \"account_number\": \"A00000123\",\n \"customer_name\": \"Jane Doe\",\n \"customer_tax_id\": \"123456789\"\n },\n \"transfer_details\": { \"type\": \"FULL\" },\n \"ip_address\": \"203.0.113.42\",\n \"signed_at\": \"2026-04-29T13:00:00Z\"\n }'", + "language": "shell", + "name": "Full transfer (entire account)" + } + ] + } } }, - "/v1beta/accounts/{account_id}/funding_wallet/transfers/{transfer_id}": { + "/v1beta1/acats/{account_id}/{acats_id}": { "get": { - "description": "Returns a single funding wallet transfer for the specified account by `transfer_id`. Responds with 404 if no matching transfer exists for the account.", - "operationId": "getFundingWalletTransferByID", + "description": "Includes the same information returned by the list transfers endpoints, but for just one transfer.", + "operationId": "getACATSTransfer", "parameters": [ { - "description": "Alpaca account UUID", + "description": "Account ID associated with the ACATS transfer", "in": "path", "name": "account_id", "required": true, "schema": { + "format": "uuid", "type": "string" } }, { - "description": "transfer UUID", + "description": "The ID of the ACATS transfer", "in": "path", - "name": "transfer_id", + "name": "acats_id", "required": true, "schema": { + "format": "uuid", "type": "string" } } @@ -14299,166 +16936,176 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/FundingWalletTransfer" + "$ref": "#/components/schemas/AcatsTransfer" } } }, - "description": "transfer response if found" + "description": "OK" + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/AcatsError" + } + } + }, + "description": "Forbidden" }, "404": { - "description": "error response if transfer is not found" + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/AcatsError" + } + } + }, + "description": "Not Found. No transfer with this `acats_id` exists for the given `account_id`." }, - "default": { + "500": { "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/Error" + "$ref": "#/components/schemas/AcatsError" } } }, - "description": "error" + "description": "Internal Server Error" } }, - "summary": "Retrieve funding wallet transfer by ID", + "summary": "Get transfer details", "tags": [ - "Funding Wallets" + "ACATS" ] } }, - "/v1beta/accounts/{account_id}/funding_wallet/withdrawal": { - "post": { - "description": "Creates a withdrawal", - "operationId": "createFundingWalletWithdrawal", + "/v1beta1/acats/{account_id}/{acats_id}/assets": { + "get": { + "description": "Assets are only available for partial transfers and for transfers where the deliverer has already responded with the list of assets. The asset list may change as the transfer is updated by the deliverer, all the way up until the transfer settles.", + "operationId": "listACATSAssets", "parameters": [ { - "description": "UUID alpaca account ID", + "description": "Account ID associated with the ACATS transfer", "in": "path", "name": "account_id", "required": true, "schema": { + "format": "uuid", + "type": "string" + } + }, + { + "description": "The ID of the ACATS transfer", + "in": "path", + "name": "acats_id", + "required": true, + "schema": { + "format": "uuid", "type": "string" } } ], - "requestBody": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/CreateFundingWalletWithdrawalRequest" - } - } - }, - "required": true - }, "responses": { "200": { "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/FundingWalletTransfer" + "$ref": "#/components/schemas/AcatsListAssetsResponse" } } }, - "description": "Transfer" + "description": "OK" }, - "default": { + "403": { "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/Error" + "$ref": "#/components/schemas/AcatsError" } } }, - "description": "Error response." - } - }, - "summary": "Create a withdrawal", - "tags": [ - "Funding Wallets" - ] - } - }, - "/v1beta/accounts/funding_wallet": { - "post": { - "description": "Creates funding wallets for a list of accounts in a single request. Every `account_id` in the request must belong to your firm, and the request is rejected if a funding wallet already exists for any of the supplied accounts.", - "operationId": "batchCreateFundingWallets", - "requestBody": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/BatchCreateFundingWalletRequest" - } - } + "description": "Forbidden" }, - "required": true - }, - "responses": { - "200": { + "404": { "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/BatchCreateFundingWalletResponse" + "$ref": "#/components/schemas/AcatsError" } } }, - "description": "Funding wallets" + "description": "Not Found. No transfer with this `acats_id` exists for the given `account_id`." }, - "default": { + "500": { "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/Error" + "$ref": "#/components/schemas/AcatsError" } } }, - "description": "Error response." + "description": "Internal Server Error" } }, - "summary": "Batch create funding wallets", + "summary": "List assets for a transfer", "tags": [ - "Funding Wallets" + "ACATS" ] } }, - "/v1beta/demo/banking/funding": { - "post": { - "description": "Simulates an inbound deposit into an account's funding wallet for end-to-end testing of the deposit flow.\n\nThe transfer can only credit accounts that belong to your firm. Available in non-production (sandbox) environments only.", - "operationId": "demoDepositFunding", - "requestBody": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/DemoFundingTransfer" - } - } - }, - "required": true - }, + "/v1beta1/acats/contrabrokers": { + "get": { + "description": "This information is provided based on the latest ACATS Participant Master File, and contains the DTCC account numbers of valid contra brokers. Only participants that are either brokers or banks are returned. These numbers are the valid values for the `broker_number` property that is required for initiating transfers.", + "operationId": "listACATSContraBrokers", "responses": { "200": { "content": { "application/json": { + "example": { + "brokers": [ + { + "name": "Example Brokerage Inc.", + "number": "0001", + "type": "BROKER" + }, + { + "name": "Example Bank, N.A.", + "number": "0002", + "type": "BANK" + } + ] + }, "schema": { - "$ref": "#/components/schemas/DemoFundingTransfer" + "$ref": "#/components/schemas/AcatsListContraBrokersResponse" } } }, - "description": "Demo deposit funding transfer" + "description": "OK" }, - "default": { + "403": { "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/Error" + "$ref": "#/components/schemas/AcatsError" } } }, - "description": "Error response." + "description": "Forbidden" + }, + "500": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/AcatsError" + } + } + }, + "description": "Internal Server Error" } }, - "summary": "Create sandbox deposit transfer", + "summary": "List contra brokers", "tags": [ - "Funding Wallets" + "ACATS" ] } }, @@ -14579,11 +17226,139 @@ ] }, "post": { - "description": "Submit an account application with KYC information. This will create a trading account for the end user. The account status may or may not be ACTIVE immediately and you will receive account status updates on the event API.\n\n**Multi-Live Accounts (MLA)**: To open an additional account for an existing account holder, supply `primary_account_holder_id` at the top level instead of `contact`/`identity` data. In that case, supplying `contact` or `identity` returns HTTP 400. Only `account_type` of `trading` and `ira` are supported via this flow.\n", + "description": "Create an account for a new or existing account holder. Required fields, eligibility, and initial status depend on the account type, enabled assets, and your correspondent configuration. Account status changes are available through the Account Status Events stream.\n\nTo create an account for an existing holder, provide `primary_account_holder_id` instead of inline holder information. Among the documented account types, existing holders are supported for trading and IRA accounts. Donor-advised accounts instead reference an existing legal entity with `entity_id`.\n", "operationId": "createAccount", "requestBody": { "content": { "application/json": { + "examples": { + "custodial-account": { + "summary": "Custodial account with guardian and minor information", + "value": { + "account_type": "custodial", + "agreements": [ + { + "agreement": "customer_agreement", + "ip_address": "185.13.21.99", + "signed_at": "2020-09-11T18:13:44Z" + } + ], + "contact": { + "city": "San Mateo", + "country": "USA", + "email_address": "john.doe@example.com", + "postal_code": "94401", + "state": "CA", + "street_address": [ + "123 Main Street" + ] + }, + "disclosures": { + "immediate_family_exposed": false, + "is_affiliated_exchange_or_finra": false, + "is_control_person": false, + "is_politically_exposed": false + }, + "enabled_assets": [ + "us_equity" + ], + "identity": { + "country_of_birth": "USA", + "country_of_citizenship": "USA", + "country_of_tax_residence": "USA", + "date_of_birth": "1990-01-01", + "family_name": "Doe", + "funding_source": [ + "employment_income" + ], + "given_name": "John", + "tax_id": "666-55-4321", + "tax_id_type": "USA_SSN" + }, + "minor_identity": { + "country_of_birth": "USA", + "country_of_citizenship": "USA", + "country_of_tax_residence": "USA", + "date_of_birth": "2012-04-05", + "email": "jamie.doe@example.com", + "family_name": "Doe", + "given_name": "Jamie", + "state": "CA", + "tax_id": "666-55-1234", + "tax_id_type": "USA_SSN" + } + } + }, + "donor-advised-account": { + "summary": "Donor-advised account for an existing legal entity", + "value": { + "account_type": "donor_advised", + "entity_id": "14fbfc51-3704-4c74-8984-8326c4117aee" + } + }, + "existing-holder-ira": { + "summary": "IRA account for an existing holder", + "value": { + "account_sub_type": "traditional", + "account_type": "ira", + "agreements": [ + { + "agreement": "customer_agreement", + "ip_address": "185.13.21.99", + "signed_at": "2020-09-11T18:13:44Z" + } + ], + "enabled_assets": [ + "us_equity" + ], + "primary_account_holder_id": "8b5b7e4c-9f2d-4f4a-9a7b-1b2c3d4e5f60" + } + }, + "trading-account": { + "summary": "Trading account with inline holder information", + "value": { + "agreements": [ + { + "agreement": "customer_agreement", + "ip_address": "185.13.21.99", + "signed_at": "2020-09-11T18:13:44Z" + } + ], + "contact": { + "city": "San Mateo", + "country": "USA", + "email_address": "john.doe@example.com", + "postal_code": "94401", + "state": "CA", + "street_address": [ + "123 Main Street" + ] + }, + "disclosures": { + "immediate_family_exposed": false, + "is_affiliated_exchange_or_finra": false, + "is_control_person": false, + "is_politically_exposed": false + }, + "enabled_assets": [ + "us_equity" + ], + "identity": { + "country_of_birth": "USA", + "country_of_citizenship": "USA", + "country_of_tax_residence": "USA", + "date_of_birth": "1990-01-01", + "family_name": "Doe", + "funding_source": [ + "employment_income" + ], + "given_name": "John", + "tax_id": "666-55-4321", + "tax_id_type": "USA_SSN" + } + } + } + }, "schema": { "$ref": "#/components/schemas/AccountCreationRequest" } @@ -14605,12 +17380,21 @@ "400": { "content": { "application/json": { + "examples": { + "malformed-request-body": { + "summary": "Malformed request body", + "value": { + "code": 40010000, + "message": "request body format is invalid: error decoding string '\"\"': can't convert \"\" to decimal" + } + } + }, "schema": { - "type": "string" + "$ref": "#/components/schemas/Error" } } }, - "description": "The post body is not well formed. For Multi-Live Accounts (MLA), this also covers:\n- Supplying `primary_account_holder_id` together with `contact` or `identity`.\n- Supplying `primary_account_holder_id` with an `account_type` other than `trading` or `ira`.\n- The referenced primary account holder is not eligible to open an additional account.\n" + "description": "The request body is malformed or cannot be decoded." }, "409": { "description": "There is already an existing account registered with the same email address." @@ -14618,12 +17402,21 @@ "422": { "content": { "application/json": { + "examples": { + "conflicting-holder-data": { + "summary": "Conflicting inline and referenced holder data", + "value": { + "code": 40010001, + "message": "cannot specify primary_account_holder_id together with identity or contact or disclosures or minor_identity" + } + } + }, "schema": { - "type": "string" + "$ref": "#/components/schemas/Error" } } }, - "description": "One of the input values is not a valid value." + "description": "The request is well-formed but fails account-creation validation. This can include conflicting inline and referenced holder data, an unsupported account-type and holder combination, or an ineligible referenced party." } }, "summary": "Create an Account", @@ -14659,7 +17452,7 @@ } ], "patch": { - "description": "This operation updates account information.\n\nIf all parameters are valid and updates have been made, it returns with status code 200. The response is the account model.\n\n**Multi-Live Accounts (MLA)**: The `primary_account_holder_id` field is read-only after the account is created. Omitting it or supplying the value already associated with the account is a no-op; supplying a different value returns HTTP 400.", + "description": "This operation updates account information.\n\nIf all parameters are valid and updates have been made, it returns with status code 200. The response is the account model.", "operationId": "patchAccount", "requestBody": { "content": { @@ -14690,7 +17483,21 @@ } } }, - "description": "The post body is not well formed. This also includes attempts to change the primary account holder via `primary_account_holder_id`:\nthe field is immutable after account creation, so supplying a value different from the account's current primary account holder returns HTTP 400.\n" + "description": "The post body is not well formed." + }, + "403": { + "content": { + "application/json": { + "example": { + "code": 40310000, + "message": "cannot change primary account holder to: d4dbe130-839d-4e0a-8d2f-312a2011729c" + }, + "schema": { + "$ref": "#/components/schemas/Error" + } + } + }, + "description": "The requested update is forbidden. This includes attempting to change the immutable `primary_account_holder_id`." }, "422": { "content": { @@ -15118,6 +17925,15 @@ "requestBody": { "content": { "application/json": { + "examples": { + "Example 1": { + "value": { + "outcome": "USER_EXITED", + "reason": "User denied consent", + "token": "header.payload.signature" + } + } + }, "schema": { "properties": { "outcome": { @@ -15136,14 +17952,7 @@ "outcome", "token" ], - "type": "object", - "x-examples": { - "Example 1": { - "outcome": "USER_EXITED", - "reason": "User denied consent", - "token": "header.payload.signature" - } - } + "type": "object" } } } @@ -15201,18 +18010,20 @@ "200": { "content": { "application/json": { + "examples": { + "Example 1": { + "value": { + "token": "header.payload.signature" + } + } + }, "schema": { "properties": { "token": { "type": "string" } }, - "type": "object", - "x-examples": { - "Example 1": { - "token": "header.payload.signature" - } - } + "type": "object" } } }, @@ -15503,7 +18314,7 @@ "content": { "application/json": { "example": { - "code": 40010001, + "code": 42210001, "message": "tokenization_request_id is required, tx_hash is required" }, "schema": { @@ -15522,11 +18333,14 @@ }, "/v1/accounts/{account_id}/tokenization/callback/redeem": { "post": { - "description": "This endpoint is used by the tokenized asset issuer to confirm the redemption of tokens previously held in the Authorized Participant's wallet. When the issuer invokes this endpoint, Alpaca will initiate a journal of the underlying asset into the Authorized Participant's account.", + "description": "This endpoint is used by the tokenized asset issuer to confirm the redemption of tokens previously held in the Authorized Participant's wallet. When the issuer invokes this endpoint, Alpaca will initiate a journal of the underlying asset into the Authorized Participant's account.\n\n**Idempotency**: When the `Idempotency-Key` header is supplied, this endpoint is\nidempotent. Multiple requests with the same key and identical request body will\ncreate only one redeem request. A subsequent request returns the previously created\nrequest with the same response (no duplicate is created). If the same key is used\nwith a different request body, the API returns `422 Unprocessable Entity`.\n\n**Recommended for production**: Always supply `Idempotency-Key` when requesting a\nredemption. This allows safe retries on timeouts, network errors, or 5xx responses\nwithout risking duplicate requests. Use a client-generated unique value (e.g. UUID).", "operationId": "postTokenizationRedeem", "parameters": [ { "$ref": "#/components/parameters/AccountID" + }, + { + "$ref": "#/components/parameters/LegacyIdempotencyKey" } ], "requestBody": { @@ -15562,7 +18376,7 @@ } } }, - "description": "Bad request (e.g. malformed input, insufficient quantity, or account not authorized to redeem)." + "description": "Bad request (e.g. malformed input, insufficient quantity, or account not authorized to redeem). Also returned when the `Idempotency-Key` header is malformed, or when replaying a request that was originally rejected." }, "401": { "content": { @@ -15596,7 +18410,7 @@ "content": { "application/json": { "example": { - "code": 40010001, + "code": 42210001, "message": "issuer_request_id is required, tx_hash is required" }, "schema": { @@ -15604,7 +18418,7 @@ } } }, - "description": "One or more request parameters are missing or invalid." + "description": "One or more request parameters are missing or invalid, or the same `Idempotency-Key` was reused with a different request body." } }, "summary": "Redeem a Tokenized Asset", @@ -15615,11 +18429,14 @@ }, "/v1/accounts/{account_id}/tokenization/mint": { "post": { - "description": "This endpoint is used by an Authorized Participant to request the minting of a tokenized asset. Alpaca will initiate a journal of the underlying asset from the Authorized Participant's account to the issuer's account.", + "description": "This endpoint is used by an Authorized Participant to request the minting of a tokenized asset. Alpaca will initiate a journal of the underlying asset from the Authorized Participant's account to the issuer's account.\n\n**Idempotency**: When the `Idempotency-Key` header is supplied, this endpoint is\nidempotent. Multiple requests with the same key and identical request body will\ncreate only one mint request. A subsequent request returns the previously created\nrequest with the same response (no duplicate is created). If the same key is used\nwith a different request body, the API returns `422 Unprocessable Entity`.\n\n**Recommended for production**: Always supply `Idempotency-Key` when requesting a\nmint. This allows safe retries on timeouts, network errors, or 5xx responses\nwithout risking duplicate requests. Use a client-generated unique value (e.g. UUID).", "operationId": "postTokenizationMintBroker", "parameters": [ { "$ref": "#/components/parameters/AccountID" + }, + { + "$ref": "#/components/parameters/LegacyIdempotencyKey" } ], "requestBody": { @@ -15655,7 +18472,7 @@ } } }, - "description": "Bad request (e.g. malformed input, insufficient position, or account not authorized to mint)." + "description": "Bad request (e.g. malformed input, insufficient position, or account not authorized to mint). Also returned when the `Idempotency-Key` header is malformed, or when replaying a request that was originally rejected." }, "401": { "content": { @@ -15689,7 +18506,7 @@ "content": { "application/json": { "example": { - "code": 40010001, + "code": 42210001, "message": "issuer is required, network is required" }, "schema": { @@ -15697,7 +18514,7 @@ } } }, - "description": "One or more request parameters are missing or invalid." + "description": "One or more request parameters are missing or invalid, or the same `Idempotency-Key` was reused with a different request body." } }, "summary": "Mint a Tokenized Asset", @@ -15764,22 +18581,320 @@ } }, { - "description": "The response will include only requests created after this timestamp (exclusive)", - "in": "query", - "name": "after", - "schema": { - "example": "2025-09-30T18:38:01.942282Z", - "format": "date-time", - "type": "string" - } + "description": "The response will include only requests created after this timestamp (exclusive)", + "in": "query", + "name": "after", + "schema": { + "example": "2025-09-30T18:38:01.942282Z", + "format": "date-time", + "type": "string" + } + }, + { + "description": "The response will include only requests created before this timestamp (exclusive)", + "in": "query", + "name": "before", + "schema": { + "example": "2025-09-30T18:38:01.942282Z", + "format": "date-time", + "type": "string" + } + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "items": { + "$ref": "#/components/schemas/TokenizationRequest" + }, + "type": "array" + } + } + }, + "description": "Successful response\n\nA list of tokenization requests" + }, + "401": { + "content": { + "application/json": { + "example": { + "code": 40110000, + "message": "unauthorized" + }, + "schema": { + "$ref": "#/components/schemas/Error" + } + } + }, + "description": "Authentication credentials are missing or invalid." + }, + "403": { + "content": { + "application/json": { + "example": { + "code": 40310000, + "message": "forbidden" + }, + "schema": { + "$ref": "#/components/schemas/Error" + } + } + }, + "description": "Caller is not authorized to perform this operation." + }, + "422": { + "content": { + "application/json": { + "example": { + "code": 42210001, + "message": "failed to parse after" + }, + "schema": { + "$ref": "#/components/schemas/Error" + } + } + }, + "description": "One or more request parameters are missing or invalid." + } + }, + "summary": "List Tokenization Requests", + "tags": [ + "Tokenization" + ] + } + }, + "/v1/accounts/{account_id}/tokenization/requests/{tokenization_request_id}": { + "get": { + "description": "Retrieve a single tokenization request by its `tokenization_request_id`. This endpoint may be used by the tokenized asset issuer or by an Authorized Participant (AP), and returns either a mint or a redeem request.", + "operationId": "getTokenizationRequestByIDBroker", + "parameters": [ + { + "$ref": "#/components/parameters/AccountID" + }, + { + "description": "Unique identifier of the tokenization request, as returned by the mint endpoint or the list-requests endpoint.", + "in": "path", + "name": "tokenization_request_id", + "required": true, + "schema": { + "format": "uuid", + "type": "string" + } + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "example": { + "created_at": "2025-11-04T18:38:01.942282Z", + "fees": "0.5", + "issuer": "xstocks", + "network": "solana", + "qty": "1.0", + "status": "completed", + "token_symbol": "TSLAx", + "tokenization_request_id": "5b1d6a3e-7f0a-4d2c-b8e1-9e6f1c0d2c4a", + "tx_hash": "5J7ZxK4QwR3vH2pY1aB6sN8cM9tD0fA2eXyVwUjL3kPqR4mZcF7nB1tA8sH6dG2pE", + "type": "mint", + "underlying_symbol": "TSLA", + "updated_at": "2025-11-04T18:38:42.117004Z", + "wallet_address": "5dXY1aH2tQpV3wXmJg6Z7c8B4nKvF9bA1pQrSt2uVwYxXz" + }, + "schema": { + "$ref": "#/components/schemas/TokenizationRequest" + } + } + }, + "description": "Successful response" + }, + "401": { + "content": { + "application/json": { + "example": { + "code": 40110000, + "message": "unauthorized" + }, + "schema": { + "$ref": "#/components/schemas/Error" + } + } + }, + "description": "Authentication credentials are missing or invalid." + }, + "403": { + "content": { + "application/json": { + "example": { + "code": 40310000, + "message": "forbidden" + }, + "schema": { + "$ref": "#/components/schemas/Error" + } + } + }, + "description": "Caller is not authorized to perform this operation." + }, + "404": { + "content": { + "application/json": { + "example": { + "code": 40410000, + "message": "tokenization request not found" + }, + "schema": { + "$ref": "#/components/schemas/Error" + } + } + }, + "description": "No tokenization request with the supplied `tokenization_request_id` exists for this account." + }, + "422": { + "content": { + "application/json": { + "example": { + "code": 42210001, + "message": "tokenization_request_id is not a valid UUID" + }, + "schema": { + "$ref": "#/components/schemas/Error" + } + } + }, + "description": "The `tokenization_request_id` path parameter is not a valid UUID." + } + }, + "summary": "Get Tokenization Request by ID", + "tags": [ + "Tokenization" + ] + } + }, + "/v1/accounts/{account_id}/tokenization/requests:by_client_request_id": { + "get": { + "description": "Retrieve a single tokenization request by the Authorized Participant's `client_request_id`, the AP-supplied label originally passed on the mint request. Available to Authorized Participants and to issuers that are affiliated with the AP. Returns the matching mint request. If the AP has reused a `client_request_id` across multiple requests, the most recently created request is returned.", + "operationId": "getTokenizationRequestByClientRequestIDBroker", + "parameters": [ + { + "$ref": "#/components/parameters/AccountID" + }, + { + "description": "AP-supplied request identifier originally passed on the mint request, echoed back on the tokenization-request response.", + "in": "query", + "name": "client_request_id", + "required": true, + "schema": { + "example": "ap-request-2025-11-04-001", + "type": "string" + } + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "example": { + "created_at": "2025-11-04T18:38:01.942282Z", + "fees": "0.5", + "issuer": "xstocks", + "network": "solana", + "qty": "1.0", + "status": "completed", + "token_symbol": "TSLAx", + "tokenization_request_id": "5b1d6a3e-7f0a-4d2c-b8e1-9e6f1c0d2c4a", + "tx_hash": "5J7ZxK4QwR3vH2pY1aB6sN8cM9tD0fA2eXyVwUjL3kPqR4mZcF7nB1tA8sH6dG2pE", + "type": "mint", + "underlying_symbol": "TSLA", + "updated_at": "2025-11-04T18:38:42.117004Z", + "wallet_address": "5dXY1aH2tQpV3wXmJg6Z7c8B4nKvF9bA1pQrSt2uVwYxXz" + }, + "schema": { + "$ref": "#/components/schemas/TokenizationRequest" + } + } + }, + "description": "Successful response" + }, + "401": { + "content": { + "application/json": { + "example": { + "code": 40110000, + "message": "unauthorized" + }, + "schema": { + "$ref": "#/components/schemas/Error" + } + } + }, + "description": "Authentication credentials are missing or invalid." + }, + "403": { + "content": { + "application/json": { + "example": { + "code": 40310000, + "message": "forbidden" + }, + "schema": { + "$ref": "#/components/schemas/Error" + } + } + }, + "description": "Caller is not authorized to perform this operation." + }, + "404": { + "content": { + "application/json": { + "example": { + "code": 40410000, + "message": "tokenization request not found" + }, + "schema": { + "$ref": "#/components/schemas/Error" + } + } + }, + "description": "No tokenization request with the supplied `client_request_id` exists for this account." + }, + "422": { + "content": { + "application/json": { + "example": { + "code": 42210001, + "message": "client_request_id query parameter is required" + }, + "schema": { + "$ref": "#/components/schemas/Error" + } + } + }, + "description": "The `client_request_id` query parameter is missing." + } + }, + "summary": "Get Tokenization Request by `client_request_id`", + "tags": [ + "Tokenization" + ] + } + }, + "/v1/accounts/{account_id}/tokenization/requests:by_issuer_request_id": { + "get": { + "description": "Retrieve a single tokenization request by the `issuer_request_id`. Returns either a mint or a redeem request. Can be called by issuers only.", + "operationId": "getTokenizationRequestByIssuerRequestIDBroker", + "parameters": [ + { + "$ref": "#/components/parameters/AccountID" }, { - "description": "The response will include only requests created before this timestamp (exclusive)", + "description": "Issuer-assigned request identifier returned to the AP on the original mint or redeem response and visible to the issuer on rows it owns.", "in": "query", - "name": "before", + "name": "issuer_request_id", + "required": true, "schema": { - "example": "2025-09-30T18:38:01.942282Z", - "format": "date-time", + "example": "ABCDEF123", "type": "string" } } @@ -15788,15 +18903,28 @@ "200": { "content": { "application/json": { + "example": { + "created_at": "2025-11-04T18:38:01.942282Z", + "fees": "0.5", + "issuer": "xstocks", + "issuer_request_id": "issuer-req-12345", + "network": "solana", + "qty": "1.0", + "status": "completed", + "token_symbol": "TSLAx", + "tokenization_request_id": "5b1d6a3e-7f0a-4d2c-b8e1-9e6f1c0d2c4a", + "tx_hash": "5J7ZxK4QwR3vH2pY1aB6sN8cM9tD0fA2eXyVwUjL3kPqR4mZcF7nB1tA8sH6dG2pE", + "type": "mint", + "underlying_symbol": "TSLA", + "updated_at": "2025-11-04T18:38:42.117004Z", + "wallet_address": "5dXY1aH2tQpV3wXmJg6Z7c8B4nKvF9bA1pQrSt2uVwYxXz" + }, "schema": { - "items": { - "$ref": "#/components/schemas/TokenizationRequest" - }, - "type": "array" + "$ref": "#/components/schemas/TokenizationRequest" } } }, - "description": "Successful response\n\nA list of tokenization requests" + "description": "Successful response" }, "401": { "content": { @@ -15826,22 +18954,36 @@ }, "description": "Caller is not authorized to perform this operation." }, + "404": { + "content": { + "application/json": { + "example": { + "code": 40410000, + "message": "tokenization request not found" + }, + "schema": { + "$ref": "#/components/schemas/Error" + } + } + }, + "description": "No tokenization request with the supplied `issuer_request_id` exists for this account, or the caller is not the tokenized asset issuer." + }, "422": { "content": { "application/json": { "example": { - "code": 40010001, - "message": "failed to parse after" + "code": 42210001, + "message": "issuer_request_id query parameter is required" }, "schema": { "$ref": "#/components/schemas/Error" } } }, - "description": "One or more request parameters are missing or invalid." + "description": "The `issuer_request_id` query parameter is missing." } }, - "summary": "List Tokenization Requests", + "summary": "Get Tokenization Request by `issuer_request_id`", "tags": [ "Tokenization" ] @@ -16208,6 +19350,26 @@ } }, "description": "Successfully requested a transfer." + }, + "400": { + "content": { + "application/json": { + "examples": { + "TravelRuleValidationError": { + "$ref": "#/components/examples/TravelRuleValidationError" + }, + "WalletError": { + "value": { + "message": "asset cannot be empty" + } + } + }, + "schema": { + "$ref": "#/components/schemas/TravelRuleErrorResponse" + } + } + }, + "description": "The request or travel rule information is invalid." } }, "summary": "Request a New Withdrawal", @@ -16289,7 +19451,7 @@ } ], "post": { - "description": "Submits a new whitelisted withdrawal address for the specified account. The chain is derived from the supplied asset symbol and address, and the same address cannot be whitelisted twice on the same chain.\n\nWhere travel rule reporting applies to the account, address details are validated against the travel rule provider before being persisted.\n\nNote that whitelisted addresses must wait at least 24 hours before they can be used as a withdrawal destination.", + "description": "Submits a new whitelisted withdrawal address for the specified account. The chain is derived from the supplied asset symbol and address, and the same address cannot be whitelisted twice on the same chain.\n\nWhere travel rule reporting applies to the account, supply travel rule information for the destination wallet. Use the `Search VASPs` endpoint to find the VASP DID for the destination exchange. For a self-hosted wallet, set `beneficiary_is_self_hosted` to true. If the exchange is not in the directory, provide its information in `beneficiary_manual_entry`.\n\nNote that whitelisted addresses must wait at least 24 hours before they can be used as a withdrawal destination.", "operationId": "createWhitelistedAddress", "requestBody": { "content": { @@ -16314,6 +19476,9 @@ "ARB" ], "type": "string" + }, + "travel_rule_info": { + "$ref": "#/components/schemas/TravelRuleInfo" } }, "type": "object" @@ -16332,6 +19497,26 @@ } }, "description": "Successfully requested a whitelisted address" + }, + "400": { + "content": { + "application/json": { + "examples": { + "TravelRuleValidationError": { + "$ref": "#/components/examples/TravelRuleValidationError" + }, + "WalletError": { + "value": { + "message": "asset cannot be empty" + } + } + }, + "schema": { + "$ref": "#/components/schemas/TravelRuleErrorResponse" + } + } + }, + "description": "The request or travel rule information is invalid." } }, "summary": "Request a new whitelisted address", @@ -16369,6 +19554,67 @@ } ] }, + "/v1/accounts/{account_id}/wallets/whitelists/{whitelisted_address_id}/travel-rule-info": { + "parameters": [ + { + "$ref": "#/components/parameters/AccountID" + }, + { + "description": "The whitelisted address update travel rule information for", + "in": "path", + "name": "whitelisted_address_id", + "required": true, + "schema": { + "format": "uuid", + "type": "string" + } + } + ], + "patch": { + "description": "You are required to supply travel rule information for the crypto wallets you're withdrawing to.\n\nIt is preferred that you use the `Search VASPs` endpoint to find the VASP DID for the exchange you are withdrawing to,\notherwise, if it's a self-hosted wallet, please set `beneficiary_is_self_hosted` to true.\n\nIf the exchange cannot be found in our directory, please supply the information manually using the `beneficiary_manual_entry` object.", + "operationId": "updateWhitelistedAddressTravelRuleInfo", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UpdateWhitelistedAddressTravelRuleInfoRequest" + } + } + }, + "description": "Travel rule information to associate with the whitelisted address.", + "required": true + }, + "responses": { + "200": { + "description": "Successfully updated travel rule information" + }, + "400": { + "content": { + "application/json": { + "examples": { + "TravelRuleValidationError": { + "$ref": "#/components/examples/TravelRuleValidationError" + }, + "WalletError": { + "value": { + "message": "travel_rule_info is missing" + } + } + }, + "schema": { + "$ref": "#/components/schemas/TravelRuleErrorResponse" + } + } + }, + "description": "The request or travel rule information is invalid." + } + }, + "summary": "Update travel rule information for a whitelisted wallet", + "tags": [ + "Crypto Funding" + ] + } + }, "/v1/accounts/activities": { "get": { "description": "Returns a list of activities\n\nNotes:\n* Pagination is handled using the `page_token` and `page_size` parameters.\n* `page_token` represents the ID of the last item on your current page of results.\n For example, if the ID of the last activity in your first response is `20220203000000000::045b3b8d-c566-4bef-b741-2bf598dd6ae7`, you would pass that value as `page_token` to retrieve the next page of results.\n\n* If specified with a `direction` of `desc`, for example, the results will end before the activity with the specified ID.\n* If specified with a `direction` of `asc`, results will begin with the activity immediately after the one specified.\n* `page_size` is the maximum number of entries to return in the response.\n* If `date` is not specified, the default and maximum value is 100.\n* If `date` is specified, the default behavior is to return all results, and there is no maximum page size.", @@ -16408,6 +19654,16 @@ "type": "string" } }, + { + "description": "Filter activities associated with a specific order. Useful for retrieving the fills that make up a completely filled order.", + "in": "query", + "name": "order_id", + "schema": { + "example": "fe060a1b-5b45-4eba-ba46-c3a3345d8255", + "format": "uuid", + "type": "string" + } + }, { "description": "Filter activities by their creation date (created_at), not the activity's settlement date. For non-trade activities such as fees, the creation date is typically the day after the trade date (in UTC). Both formats YYYY-MM-DD and YYYY-MM-DDTHH:MM:SSZ are supported.", "in": "query", @@ -16493,6 +19749,16 @@ "type": "string" } }, + { + "description": "Filter activities associated with a specific order. Useful for retrieving the fills that make up a completely filled order.", + "in": "query", + "name": "order_id", + "schema": { + "example": "fe060a1b-5b45-4eba-ba46-c3a3345d8255", + "format": "uuid", + "type": "string" + } + }, { "description": "Filter activities by their creation date (created_at), not the activity's settlement date. For non-trade activities such as fees, the creation date is typically the day after the trade date (in UTC). Both formats YYYY-MM-DD and YYYY-MM-DDTHH:MM:SSZ are supported.", "in": "query", @@ -17390,7 +20656,50 @@ "200": { "content": { "application/json": { - "examples": {}, + "examples": { + "countries": { + "summary": "Example response", + "value": { + "USA": { + "crypto_risk_rating": "medium", + "crypto_supported_states": [ + "CA", + "CT", + "GA", + "IA", + "ID", + "IL", + "IN", + "KS", + "KY", + "MA", + "MD", + "ME", + "MI", + "MS", + "MO", + "MT", + "NC", + "ND", + "NE", + "OH", + "RI", + "SD", + "UT", + "VT", + "WA" + ], + "full_name": "United States of America", + "securities_risk_rating": "medium" + }, + "ZAF": { + "crypto_risk_rating": "high", + "full_name": "South Africa", + "securities_risk_rating": "high" + } + } + } + }, "schema": { "$ref": "#/components/schemas/CountryInfos" } @@ -17407,7 +20716,7 @@ }, "/v1/events/accounts/status": { "get": { - "description": "The accounts events API provides streaming of account changes as they occur, via SSE (server sent events). Past events can also be queried.\n\nEvents are generated for changes to the following account properties:\n- account_blocked\n- admin_configurations\n- cash_interest\n- crypto_status\n- kyc_results\n- options\n- status\n- trading_blocked\n\nOnly the changed properties are included in the event payload.\n\nQuery Parameter Rules:\n- `since` is required if `until` specified\n- `since_id` is required if `until_id` specified\n- `since_ulid` is required if `until_ulid` specified\n- `since`, `since_id` and `since_ulid` can't be used at the same time\n\nBehavior:\nThis API supports querying a range of events, starting now or in the past. If the end of the range is in the future or not specified, the connection is kept open and future events are pushed.\n\nTo be specific:\n- if `since`, `since_id` or `since_ulid` is not specified, this will not return any historic data\n- if `until`, `until_id` or `until_ulid` is reached, the stream will end with a status of 200\n\n---\n\nNote for people using the clients generated from this OAS spec. Currently OAS-3 doesn't have full support for representing SSE style responses from an API, so if you are using a generated client and don't specify a `since` and `until` there is a good chance the generated clients will hang waiting for the response to end.\n\nIf you require the streaming capabilities we recommend not using the generated clients for this specific usecase until the OAS-3 standards come to a consensus on how to represent this correctly in OAS-3.\n", + "description": "The accounts events API provides streaming of account changes as they occur, via SSE (server sent events). Past events can also be queried.\n\nEvents are generated for changes to the following account properties:\n- account_blocked\n- admin_configurations\n- cash_interest\n- crypto_status\n- kyc_results\n- options\n- status\n- trading_blocked\n\nOnly the changed properties are included in the event payload.\n\nQuery Parameter Rules:\n- `since_id` and `until_id` are deprecated and available only to select broker partners; use `since_ulid` and `until_ulid` instead\n- `since` is required if `until` specified\n- `since_id` is required if `until_id` specified\n- `since_ulid` is required if `until_ulid` specified\n- `since`, `since_id` and `since_ulid` can't be used at the same time\n\nBehavior:\nThis API supports querying a range of events, starting now or in the past. If the end of the range is in the future or not specified, the connection is kept open and future events are pushed.\n\nTo be specific:\n- if `since`, `since_id` or `since_ulid` is not specified, this will not return any historic data\n- if `until`, `until_id` or `until_ulid` is reached, the stream will end with a status of 200\n\n---\n\nNote for people using the clients generated from this OAS spec. Currently OAS-3 doesn't have full support for representing SSE style responses from an API, so if you are using a generated client and don't specify a `since` and `until` there is a good chance the generated clients will hang waiting for the response to end.\n\nIf you require the streaming capabilities we recommend not using the generated clients for this specific usecase until the OAS-3 standards come to a consensus on how to represent this correctly in OAS-3.\n", "operationId": "suscribeToAccountStatusSSE", "parameters": [ { @@ -17429,17 +20738,29 @@ } }, { + "deprecated": true, "in": "query", "name": "since_id", "schema": { "type": "integer" + }, + "x-deprecation": { + "reason": "Use since_ulid instead.", + "since": "2023-08-01", + "sunset": "2027-02-15" } }, { + "deprecated": true, "in": "query", "name": "until_id", "schema": { "type": "integer" + }, + "x-deprecation": { + "reason": "Use until_ulid instead.", + "since": "2023-08-01", + "sunset": "2027-02-15" } }, { @@ -17490,7 +20811,7 @@ }, "/v1/events/journals/status": { "get": { - "description": "The Events API provides event push as well as historical queries via SSE (server sent events).\n\nYou can listen to journal status updates as they get processed by our backoffice.\n\nHistorical events are streamed immediately if queried, and updates are pushed as events occur.\n\nQuery Params Rules:\n- `since` required if `until` specified\n- `since_id` required if `until_id` specified\n- `since_ulid` required if `until_ulid` specified\n- `since`, `since_id` or `since_ulid` can't be used at the same time\nBehavior:\n- if `since`, `since_id` or `since_ulid` not specified this will not return any historic data\n- if `until`, `until_id` or `until_ulid` reached stream will end (status 200)\n\n---\n\nThere is no compatibility between /v1/events/journals/status and /v2/events/journals/status, the ids (ulid) are always different, and the number of events might also different\n\nPlease note that the new `/v2` endpoint, is the same as, and was originally available under `/v2beta1`.\nWe encourage all customers to adjust their codebase from that interim beta endpoint to the `/v2` stable endpoint.\nIn the near future we will setup permanent redirect from `/v2beta1` to `/v2` before we completely remove the beta endpoint.\n\n---\n\nNote for people using the clients generated from this OAS spec. Currently OAS-3 doesn't have full support for representing SSE style responses from an API, so if you are using a generated client and don't specify a `since` and `until` there is a good chance the generated clients will hang waiting for the response to end.\n\nIf you require the streaming capabilities we recommend not using the generated clients for this specific usecase until the OAS-3 standards come to a consensus on how to represent this correctly in OAS-3.\n", + "description": "The Events API provides event push as well as historical queries via SSE (server sent events).\n\nYou can listen to journal status updates as they get processed by our backoffice.\n\nHistorical events are streamed immediately if queried, and updates are pushed as events occur.\n\nQuery Params Rules:\n- `since_id` and `until_id` are deprecated and available only to select broker partners; use `since_ulid` and `until_ulid` instead\n- `since` required if `until` specified\n- `since_id` required if `until_id` specified\n- `since_ulid` required if `until_ulid` specified\n- `since`, `since_id` or `since_ulid` can't be used at the same time\nBehavior:\n- if `since`, `since_id` or `since_ulid` not specified this will not return any historic data\n- if `until`, `until_id` or `until_ulid` reached stream will end (status 200)\n\n---\n\nThere is no compatibility between /v1/events/journals/status and /v2/events/journals/status, the ids (ulid) are always different, and the number of events might also different\n\nPlease note that the new `/v2` endpoint, is the same as, and was originally available under `/v2beta1`.\nWe encourage all customers to adjust their codebase from that interim beta endpoint to the `/v2` stable endpoint.\nIn the near future we will setup permanent redirect from `/v2beta1` to `/v2` before we completely remove the beta endpoint.\n\n---\n\nNote for people using the clients generated from this OAS spec. Currently OAS-3 doesn't have full support for representing SSE style responses from an API, so if you are using a generated client and don't specify a `since` and `until` there is a good chance the generated clients will hang waiting for the response to end.\n\nIf you require the streaming capabilities we recommend not using the generated clients for this specific usecase until the OAS-3 standards come to a consensus on how to represent this correctly in OAS-3.\n", "operationId": "subscribeToJournalStatusSSE", "parameters": [ { @@ -17512,17 +20833,29 @@ } }, { + "deprecated": true, "in": "query", "name": "since_id", "schema": { "type": "integer" + }, + "x-deprecation": { + "reason": "Use since_ulid instead.", + "since": "2023-08-01", + "sunset": "2027-02-15" } }, { + "deprecated": true, "in": "query", "name": "until_id", "schema": { "type": "integer" + }, + "x-deprecation": { + "reason": "Use until_ulid instead.", + "since": "2023-08-01", + "sunset": "2027-02-15" } }, { @@ -17572,105 +20905,16 @@ }, "/v1/events/nta": { "get": { - "description": "The Events API provides event push as well as historical queries via SSE (server sent events).\n\nYou can listen to non-trading activities updates as they get processed by our backoffice, for both end-user and firm accounts.\n\nHistorical events are streamed immediately if queried, and updates are pushed as events occur.\n\nYou can listen to when NTAs are pushed such as CSDs, JNLC (journals) or FEEs.\n\nQuery Params Rules:\n- `since` required if `until` specified\n- `since_id` required if `until_id` specified\n- `since_ulid` required if `until_ulid` specified\n- `since`, `since_id` or `since_ulid` can't be used at the same time\nBehavior:\n- if `since`, `since_id` or `since_ulid` not specified this will not return any historic data\n- if `until`, `until_id` or `until_ulid` reached stream will end (status 200)'", + "description": "The Events API provides event push as well as historical queries via SSE (server sent events).\n\nYou can listen to non-trading activities updates as they get processed by our backoffice, for both end-user and firm accounts.\n\nHistorical events are streamed immediately if queried, and updates are pushed as events occur.\n\nYou can listen to when NTAs are pushed such as CSDs, JNLC (journals) or FEEs.\n\nQuery Params Rules:\n- `since_id` and `until_id` are deprecated and available only to select broker partners; use `since_ulid` and `until_ulid` instead\n- `since` required if `until` specified\n- `since_id` required if `until_id` specified\n- `since_ulid` required if `until_ulid` specified\n- `since`, `since_id` or `since_ulid` can't be used at the same time\nBehavior:\n- if `since`, `since_id` or `since_ulid` not specified this will not return any historic data\n- if `until`, `until_id` or `until_ulid` reached stream will end (status 200)'", "operationId": "get-v1-events-nta", "parameters": [ { "in": "query", "name": "id", - "schema": { - "type": "string" - } - }, - { - "description": "Format: YYYY-MM-DD", - "in": "query", - "name": "since", - "schema": { - "format": "date", - "type": "string" - } - }, - { - "description": "Format: YYYY-MM-DD", - "in": "query", - "name": "until", - "schema": { - "format": "date", - "type": "string" - } - }, - { - "in": "query", - "name": "since_id", - "schema": { - "type": "integer" - } - }, - { - "in": "query", - "name": "until_id", - "schema": { - "type": "integer" - } - }, - { - "in": "query", - "name": "since_ulid", - "schema": { - "format": "ulid", - "type": "string" - } - }, - { - "in": "query", - "name": "until_ulid", - "schema": { - "format": "ulid", - "type": "string" - } - }, - { - "in": "query", - "name": "include_preprocessing", - "schema": { - "type": "boolean" - } - }, - { - "description": "ID used to link activities who share a sibling relationship", - "in": "query", - "name": "group_id", - "schema": { - "format": "uuid", - "type": "string" - } - } - ], - "responses": { - "200": { - "content": { - "text/event-stream": { - "schema": { - "$ref": "#/components/schemas/NonTradeActivityEvent" - } - } - }, - "description": "OK" - } - }, - "summary": "Subscribe to Non-Trading Activities Events (SSE)", - "tags": [ - "Events" - ] - } - }, - "/v1/events/trades": { - "get": { - "deprecated": true, - "description": "**Deprecation notice**\n\nAs part of the deprecation process, the legacy trade events API is now only available for existing broker-partners at: `GET /v1/events/trades` only for compatibility reasons.\n\nAll new broker partners will not have the option for the legacy trade event endpoint.\n\nAll new broker partners will have to integrate with the new `/v2/events/trades` endpoint.\n\nAlso, all existing broker partners are now recommended to upgrade to the `/v2/events/trades` endpoint, which provides faster event delivery times.\n\nFor trade events of MLeg (multi-leg) order, please use the `/v2/events/trades` endpoint.\n\nPlease note that the new `/v2` endpoint, is the same as, and was originally available under `/v2beta1`.\nWe encourage all customers to adjust their codebase from that interim beta endpoint to the `/v2` stable endpoint.\nIn the near future we will setup permanent redirect from `/v2beta1` to `/v2` before we completely remove the beta endpoint.\n\n---\n\nThe Events API provides event push as well as historical queries via SSE (server sent events).\n\nYou can listen to events related to trade updates. Most market trades sent during market hours are filled instantly; you can listen to limit order updates through this endpoint.\n\nHistorical events are streamed immediately if queried, and updates are pushed as events occur.\n\nQuery Params Rules:\n- `since` required if `until` specified\n- `since_id` required if `until_id` specified\n- `since_ulid` required if `until_ulid` specified\n- `since`, `since_id` or `since_ulid` can't be used at the same time\nBehavior:\n- if `since`, `since_id` or `since_ulid` not specified this will not return any historic data\n- if `until`, `until_id` or `until_ulid` reached stream will end (status 200)\n\n---\n\nNote for people using the clients generated from this OAS spec. Currently OAS-3 doesn't have full support for representing SSE style responses from an API, so if you are using a generated client and don't specify a `since` and `until` there is a good chance the generated clients will hang waiting for the response to end.\n\nIf you require the streaming capabilities we recommend not using the generated clients for this specific usecase until the OAS-3 standards come to a consensus on how to represent this correctly in OAS-3.\n\n---\n\n**Deprecation note**\n\nLegacy event id (integer based) is unavailable for new broker partners, and it will be deprecated for existing brokers as well.\nThis deprecation is includes the `since_id` and `until_id` query parameter and the `event_id` field in the response.\n\n---\n\n**Common events**\n\nThese are the events that are the expected results of actions you may have taken by sending API requests.\n\nThe meaning of the timestamp field changes for each type; the meanings have been specified here for which types the timestamp field will be present.\n\n- `accepted` Sent when an order is received and accepted by Alpaca\n- `pending_new` Sent when the order has been received by Alpaca and routed to the exchanges, but has not yet been accepted for execution.\n- `new` Sent when an order has been routed to exchanges for execution.\n- `fill` Sent when your order has been completely filled.\n - timestamp: The time at which the order was filled.\n- `partial_fill` Sent when a number of shares less than the total remaining quantity on your order has been filled.\n - timestamp: The time at which the shares were filled.\n- `canceled` Sent when your requested cancellation of an order is processed.\n - timestamp: The time at which the order was canceled.\n- `expired` Sent when an order has reached the end of its lifespan, as determined by the order's time in force value.\n - timestamp: The time at which the order expired.\n- `done_for_day` Sent when the order is done executing for the day, and will not receive further updates until the next trading day.\n- `replaced` Sent when your requested replacement of an order is processed.\n - timestamp: The time at which the order was replaced.\n\n**Rarer events**\n\nThese are events that may rarely be sent due to unexpected circumstances on the exchanges. It is unlikely you will need to design your code around them, but you may still wish to account for the possibility that they will occur.\n\n- `rejected` Sent when your order has been rejected.\n- `held` For multi-leg orders, the secondary orders (stop loss, take profit) will enter this state while waiting to be triggered.\n- `stopped` Sent when your order has been stopped, and a trade is guaranteed for the order, usually at a stated price or better, but has not yet occurred.\n- `pending_cancel` Sent when the order is awaiting cancellation. Most cancellations will occur without the order entering this state.\n- `pending_replace` Sent when the order is awaiting replacement.\n- `calculated` Sent when the order has been completed for the day - it is either filled or done_for_day - but remaining settlement calculations are still pending.\n- `suspended` Sent when the order has been suspended and is not eligible for trading.\n- `order_replace_rejected` Sent when the order replace has been rejected.\n- `order_cancel_rejected` Sent when the order cancel has been rejected.\n- `trade_bust`: Sent when a previously reported execution has been canceled (\"busted\") by the upstream exchange.\n- `trade_correct`: Sent when a previously reported trade has been corrected. For example, the exchange may have updated the price, quantity, or another execution parameter after the trade was initially reported.", - "operationId": "subscribeToTradeSSE", - "parameters": [ + "schema": { + "type": "string" + } + }, { "description": "Format: YYYY-MM-DD", "in": "query", @@ -17690,17 +20934,29 @@ } }, { + "deprecated": true, "in": "query", "name": "since_id", "schema": { "type": "integer" + }, + "x-deprecation": { + "reason": "Use since_ulid instead.", + "since": "2023-08-01", + "sunset": "2027-02-15" } }, { + "deprecated": true, "in": "query", "name": "until_id", "schema": { "type": "integer" + }, + "x-deprecation": { + "reason": "Use until_ulid instead.", + "since": "2023-08-01", + "sunset": "2027-02-15" } }, { @@ -17718,6 +20974,22 @@ "format": "ulid", "type": "string" } + }, + { + "in": "query", + "name": "include_preprocessing", + "schema": { + "type": "boolean" + } + }, + { + "description": "ID used to link activities who share a sibling relationship", + "in": "query", + "name": "group_id", + "schema": { + "format": "uuid", + "type": "string" + } } ], "responses": { @@ -17725,20 +20997,14 @@ "content": { "text/event-stream": { "schema": { - "items": { - "$ref": "#/components/schemas/TradeUpdateEvent" - }, - "type": "array" + "$ref": "#/components/schemas/NonTradeActivityEvent" } } }, - "description": "Connected. Events will now start streaming as long as you keep the connection open." - }, - "410": { - "description": "Deprecated. The endpoint is not available for this partner." + "description": "OK" } }, - "summary": "Subscribe to Trade Events (SSE) (Legacy)", + "summary": "Subscribe to Non-Trading Activities Events (SSE)", "tags": [ "Events" ] @@ -17747,7 +21013,7 @@ "/v1/events/transfers/status": { "get": { "deprecated": true, - "description": "**Deprecation notice**\n\nAs part of the deprecation process, the legacy transfer events API is now only available for existing broker-partners at `GET /v1/events/transfers/status` and for compatibility reasons.\n\nAll new broker partners will not have the option to use the legacy transfer events endpoint.\n\nThey should integrate with the new `/v2/events/funding/status` endpoint instead.\n\nAlso, all existing broker partners are now recommended to upgrade to the `/v2/events/funding/status` endpoint, which provides faster event delivery times.\n\n---\n\nThe Events API provides event push as well as historical queries via SSE (server sent events).\n\nYou can listen to transfer status updates as they get processed by our backoffice, for both end-user and firm accounts.\n\nHistorical events are streamed immediately if queried, and updates are pushed as events occur.\n\nQuery Params Rules:\n- `since` required if `until` specified\n- `since_id` required if `until_id` specified\n- `since_ulid` required if `until_ulid` specified\n- `since`, `since_id` or `since_ulid` can't be used at the same time\nBehavior:\n- if `since`, `since_id` or `since_ulid` not specified this will not return any historic data\n- if `until`, `until_id` or `until_ulid` reached stream will end (status 200)\n\n---\n\nNote for people using the clients generated from this OAS spec. Currently OAS-3 doesn't have full support for representing SSE style responses from an API, so if you are using a generated client and don't specify a `since` and `until` there is a good chance the generated clients will hang waiting for the response to end.\n\nIf you require the streaming capabilities we recommend not using the generated clients for this specific usecase until the OAS-3 standards come to a consensus on how to represent this correctly in OAS-3.", + "description": "**Deprecation notice**\n\nAs part of the deprecation process, the legacy transfer events API is now only available for existing broker-partners at `GET /v1/events/transfers/status` and for compatibility reasons.\n\nAll new broker partners will not have the option to use the legacy transfer events endpoint.\n\nThey should integrate with the new `/v2/events/funding/status` endpoint instead.\n\nAlso, all existing broker partners are now recommended to upgrade to the `/v2/events/funding/status` endpoint, which provides faster event delivery times.\n\n---\n\nThe Events API provides event push as well as historical queries via SSE (server sent events).\n\nYou can listen to transfer status updates as they get processed by our backoffice, for both end-user and firm accounts.\n\nHistorical events are streamed immediately if queried, and updates are pushed as events occur.\n\nQuery Params Rules:\n- `since_id` and `until_id` are deprecated and available only to select broker partners; use `since_ulid` and `until_ulid` instead\n- `since` required if `until` specified\n- `since_id` required if `until_id` specified\n- `since_ulid` required if `until_ulid` specified\n- `since`, `since_id` or `since_ulid` can't be used at the same time\nBehavior:\n- if `since`, `since_id` or `since_ulid` not specified this will not return any historic data\n- if `until`, `until_id` or `until_ulid` reached stream will end (status 200)\n\n---\n\nNote for people using the clients generated from this OAS spec. Currently OAS-3 doesn't have full support for representing SSE style responses from an API, so if you are using a generated client and don't specify a `since` and `until` there is a good chance the generated clients will hang waiting for the response to end.\n\nIf you require the streaming capabilities we recommend not using the generated clients for this specific usecase until the OAS-3 standards come to a consensus on how to represent this correctly in OAS-3.", "operationId": "subscribeToTransferStatusSSE", "parameters": [ { @@ -17769,17 +21035,29 @@ } }, { + "deprecated": true, "in": "query", "name": "since_id", "schema": { "type": "integer" + }, + "x-deprecation": { + "reason": "Use since_ulid instead.", + "since": "2023-08-01", + "sunset": "2027-02-15" } }, { + "deprecated": true, "in": "query", "name": "until_id", "schema": { "type": "integer" + }, + "x-deprecation": { + "reason": "Use until_ulid instead.", + "since": "2023-08-01", + "sunset": "2027-02-15" } }, { @@ -18206,13 +21484,7 @@ "operationId": "post-v1-instant-funding", "parameters": [ { - "description": "Unique key for idempotent create. When supplied, duplicate requests (same key\nand body) return the original transfer instead of creating a new one. Use a\nclient-generated value (e.g. UUID) per logical transfer. Strongly recommended\nfor production to prevent duplicate transfers on retries.\n", - "in": "header", - "name": "Idempotency-Key", - "schema": { - "maxLength": 128, - "type": "string" - } + "$ref": "#/components/parameters/LegacyIdempotencyKey" } ], "requestBody": { @@ -18763,7 +22035,7 @@ ] }, "post": { - "description": "Creates a new JIT settlement, which will trigger the reconciliation process for all included accounts.\n", + "description": "Creates a new JIT settlement, which will trigger the reconciliation process for all included accounts. There is a limit of 150,000 accounts per settlement. If more than 150,000 accounts need to be settled, they should be batched in multiple settlements.\n", "operationId": "post-v1-jit-settlements", "requestBody": { "content": { @@ -18960,17 +22232,11 @@ ] }, "post": { - "description": "A journal can be JNLC (move cash) or JNLS (move shares), dictated by `entry_type`. Generally, journal requests are subject to approval and starts from the `pending` status. The status changes are propagated through the Event API. Under certain conditions agreed for the partner, such journal transactions that meet the criteria are executed right away.\n\n**Idempotency**: When the `Idempotency-Key` header is supplied, this endpoint is\nidempotent. Multiple requests with the same key and identical request body will\ncreate only one journal. A subsequent request returns the previously created\njournal with the same response (no duplicate is created). If the same key is used\nwith a different request body, the API returns `422 Unprocessable Entity`.\n\n**Recommended for production**: Always supply `Idempotency-Key` when creating\njournals. This allows safe retries on timeouts, network errors, or 5xx responses\nwithout risking duplicate journals. Use a client-generated unique value (e.g. UUID).\n", + "description": "A journal can be JNLC (move cash) or JNLS (move shares), dictated by `entry_type`. Generally, journal requests are subject to approval and starts from the `pending` status. The status changes are propagated through the Event API. Under certain conditions agreed for the partner, such journal transactions that meet the criteria are executed right away.\n\n**Idempotency**: Reusing the same key with an identical request returns the\npreviously created journal without creating a duplicate. Reusing the same key with\na different request returns `422 Unprocessable Entity`.\n", "operationId": "createJournal", "parameters": [ { - "description": "Unique key for idempotent create. When supplied, duplicate requests (same key\nand body) return the original journal instead of creating a new one. Use a\nclient-generated value (e.g. UUID) per logical journal. Strongly recommended\nfor production to prevent duplicate journals on retries.\n", - "in": "header", - "name": "Idempotency-Key", - "schema": { - "maxLength": 128, - "type": "string" - } + "$ref": "#/components/parameters/LegacyIdempotencyKey" } ], "requestBody": { @@ -19031,14 +22297,7 @@ "description": "One of the account is not found." }, "422": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/Error" - } - } - }, - "description": "Unprocessable Entity. Returned when an idempotency key is reused with a different request body." + "$ref": "#/components/responses/IdempotencyKeyConflict" } }, "summary": "Create a Journal", @@ -19105,13 +22364,7 @@ "operationId": "createBatchJournal", "parameters": [ { - "description": "The unique key for idempotent create. When supplied, duplicate requests (same key\nand body) return the original journal batch instead of creating a new one. Use a\nclient-generated value (e.g. UUID) per logical batch. Strongly recommended\nfor production to prevent duplicate journal batches on retries.\n", - "in": "header", - "name": "Idempotency-Key", - "schema": { - "maxLength": 128, - "type": "string" - } + "$ref": "#/components/parameters/LegacyIdempotencyKey" } ], "requestBody": { @@ -19159,7 +22412,7 @@ }, "/v1/journals/reverse_batch": { "post": { - "description": "You can also create a batch journal request by using the following endpoint. This is enabled on JNLC for now only.\n\nNote that if there is an invalid account_id the whole batch operation will be canceled.\nEvery single request must be valid for the entire batch operation to succeed.\n\nIn the case of a successful request, the response will contain an array of journal objects with an extra attribute error_message in the case when a specific account fails to submit a journal.", + "description": "You can also create a batch journal request by using the following endpoint. This is enabled on JNLC for now only.\n\nNote that if there is an invalid account_id the whole batch operation will be canceled.\nEvery single request must be valid for the entire batch operation to succeed.\n\nA successful response contains an array of JNLC journal objects. If any account or entry is invalid, the whole batch operation is canceled and no per-item error result is returned.", "operationId": "post-v1-journals-reverse_batch", "requestBody": { "content": { @@ -19176,7 +22429,7 @@ "application/json": { "schema": { "items": { - "$ref": "#/components/schemas/BatchJournalResponse" + "$ref": "#/components/schemas/JNLC" }, "type": "array" } @@ -19538,7 +22791,7 @@ } }, { - "description": "The ppind(Penny Program Indicator) field indicates whether an option contract is eligible for penny price increments,\nwith `true` meaning it is part of the Penny Program and `false` meaning it is not.", + "description": "The ppind (Penny Program Indicator) field indicates whether an option contract is eligible for penny price increments,\nwith `true` meaning it is part of the Penny Program and `false` meaning it is not.", "example": true, "in": "query", "name": "ppind", @@ -19731,7 +22984,7 @@ "weights": { "description": "Weight configuration to portfolio. Sum of \"percent\" values in the weights array must be 100.00", "items": { - "$ref": "#/components/schemas/PortfolioWeights" + "$ref": "#/components/schemas/PortfolioWeightRequest" }, "type": "array" } @@ -19762,7 +23015,7 @@ }, "/v1/rebalancing/portfolios/{portfolio_id}": { "delete": { - "description": "Sets a portfolio to \"inactive\", so it can be filtered out of the list request. Only permitted if there are no active subscriptions to this portfolio and this portfolio is not a listed in the weights of any active portfolios.\n\nInactive portfolios cannot be linked in new subscriptions or added as weights to new portfolios.", + "description": "Archives a portfolio (soft delete). Only permitted when the portfolio has no active subscriptions. Archived portfolios are hidden from the default list response and cannot be used for new subscriptions.", "operationId": "delete-v1-rebalancing-portfolios-portfolio_id", "responses": { "204": { @@ -19841,7 +23094,7 @@ "weights": { "description": "Used to update portfolio weights.", "items": { - "$ref": "#/components/schemas/PortfolioWeights" + "$ref": "#/components/schemas/PortfolioWeightRequest" }, "type": "array" } @@ -19923,10 +23176,33 @@ "content": { "application/json": { "schema": { - "items": { - "$ref": "#/components/schemas/PortfolioRun" + "properties": { + "next_page_token": { + "$ref": "#/components/schemas/NextPageToken" + }, + "runs": { + "example": [ + { + "account_id": "4db36989-6565-4011-9126-39fe6b3d9bf6", + "id": "0f9e8a7b-1c2d-4e3f-9a0b-1c2d3e4f5a6b", + "initiated_from": "api", + "portfolio_id": "9b2c1d4e-8f7a-4c3b-9d2e-1f0a8b7c6d5e", + "status": "COMPLETED_SUCCESS", + "type": "full_rebalance", + "updated_at": "2025-01-15T16:30:00Z" + } + ], + "items": { + "$ref": "#/components/schemas/PortfolioRun" + }, + "type": "array" + } }, - "type": "array" + "required": [ + "runs", + "next_page_token" + ], + "type": "object" } } }, @@ -19939,28 +23215,15 @@ ] }, "post": { - "description": "Manually creates a run.\n\nThe determination of a run's orders and the execution of a run take place during normal market hours\n\nRuns can be initiated either by the system (when the system evaluates the rebalance conditions specified at the portfolio level) or by API call (manual run creation using POST /v1/rebalancing/runs). Runs can be initiated manually outside of the normal market hours but will remain in the QUEUED status until normal market hours\n\nOnly 1 run in a non-terminal status is allowed at any time.\n\nManually executing a run is currently only allowed for accounts who do not have an active subscription.", + "description": "Creates a rebalance run.\n\nOmit `weights` or provide an empty array to create a subscription-driven run using the account's active subscription. Provide a non-empty `weights` array to create a manual one-off run. A manual run is rejected if the account already has an active subscription.\n\nThe determination of a run's orders and the execution of a run take place during normal market hours. Runs can be initiated outside normal market hours but remain `QUEUED` until normal market hours.\n\nOnly one run in a non-terminal status is allowed at any time.", "operationId": "post-v1-rebalancing-runs", "requestBody": { "content": { "application/json": { - "schema": { - "properties": { - "account_id": { - "description": "The account to create the run for.", - "type": "string" - }, - "type": { - "description": "`full_rebalance` or `invest_cash`", - "type": "string" - }, - "weights": { - "$ref": "#/components/schemas/PortfolioWeights" - } - }, - "type": "object", - "x-examples": { - "Example 1": { + "examples": { + "manualRun": { + "summary": "Manual one-off rebalance run", + "value": { "account_id": "bf2b0f93-f296-4276-a9cf-288586cf4fb7", "type": "full_rebalance", "weights": [ @@ -19981,17 +23244,62 @@ } ] } + }, + "subscriptionRun": { + "summary": "Subscription-driven rebalance run", + "value": { + "account_id": "bf2b0f93-f296-4276-a9cf-288586cf4fb7", + "type": "full_rebalance" + } } + }, + "schema": { + "properties": { + "account_id": { + "description": "The account to create the run for.", + "format": "uuid", + "type": "string" + }, + "amount": { + "description": "Optional decimal amount used by the run. JSON strings and numbers are accepted.", + "type": [ + "string", + "number", + "null" + ] + }, + "type": { + "description": "The kind of rebalance run to create.", + "enum": [ + "full_rebalance", + "invest_cash" + ], + "type": "string" + }, + "weights": { + "description": "Asset allocation weights. Omit this field or provide an empty array to use the account's active subscription. Provide a non-empty array to create a manual one-off run. A manual run is rejected if the account already has an active subscription.", + "items": { + "$ref": "#/components/schemas/PortfolioWeightRequest" + }, + "type": "array" + } + }, + "required": [ + "account_id", + "type" + ], + "type": "object" } } - } + }, + "required": true }, "responses": { "200": { "description": "OK" } }, - "summary": "Create Run (Manual rebalancing event)", + "summary": "Create Run", "tags": [ "Rebalancing" ] @@ -20630,8 +23938,8 @@ "value": "ALL" }, "cash_transfers": { - "summary": "Include cash deposits, withdraws, and transaction fees", - "value": "CSD,CSW,FEE" + "summary": "Include cash movements.", + "value": "JNLC,CSD,CSW" }, "none": { "value": "NONE" @@ -20648,6 +23956,250 @@ "200": { "content": { "application/json": { + "examples": { + "example-cashflows": { + "value": { + "base_value": 8413.04, + "base_value_asof": "2026-04-06", + "cashflow": { + "CSD": [ + 9.93, + 10.13, + 10.16, + 0 + ], + "DIV": [ + 0, + 0, + 0.63, + 0 + ] + }, + "equity": [ + 8425.21, + 8639.77, + 8766.1, + 8835.31 + ], + "profit_loss": [ + 12.17, + 214.56, + 126.33, + 69.21 + ], + "profit_loss_pct": [ + 0.0014, + 0.0255, + 0.0146, + 0.0079 + ], + "timeframe": "1D", + "timestamp": [ + 1775606400, + 1775692800, + 1775779200, + 1775865600 + ] + } + }, + "example-intraday-query-15min-1d": { + "value": { + "base_value": 2774.16, + "base_value_asof": "2023-10-18", + "cashflow": { + "CSD": [ + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 100, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0 + ] + }, + "equity": [ + 2773.79, + 2769.04, + 2768.65, + 2765.11, + 2763.03, + 2763.17, + 2763.17, + 2763.47, + 2763.91, + 2768.13, + 2774.98, + 2757.94, + 2757.65, + 2774.54, + 2775.58, + 2775.28, + 2767.9, + 2762.26, + 2762.56, + 2756.99, + 2756.84, + 2752.43, + 2752.13, + 2748.44, + 2751.23, + 2747.54, + 2748.74 + ], + "profit_loss": [ + -0.37, + -5.12, + -5.51, + -9.05, + -11.13, + -10.99, + -10.99, + -10.69, + -10.25, + -6.03, + 0.82, + -16.22, + -16.51, + 0.38, + 1.42, + 1.12, + -6.26, + -11.9, + -11.6, + -17.17, + -17.32, + -21.73, + -22.03, + -25.72, + -22.93, + -26.62, + -25.42 + ], + "profit_loss_pct": [ + -0.0001, + -0.0018, + -0.002, + -0.0033, + -0.004, + -0.004, + -0.004, + -0.0039, + -0.0037, + -0.0022, + 0.0003, + -0.0058, + -0.006, + 0.0001, + 0.0005, + 0.0004, + -0.0023, + -0.0043, + -0.0042, + -0.0062, + -0.0062, + -0.0078, + -0.0079, + -0.0093, + -0.0083, + -0.0096, + -0.0092 + ], + "timeframe": "15Min", + "timestamp": [ + 1697722200, + 1697723100, + 1697724000, + 1697724900, + 1697725800, + 1697726700, + 1697727600, + 1697728500, + 1697729400, + 1697730300, + 1697731200, + 1697732100, + 1697733000, + 1697733900, + 1697734800, + 1697735700, + 1697736600, + 1697737500, + 1697738400, + 1697739300, + 1697740200, + 1697741100, + 1697742000, + 1697742900, + 1697743800, + 1697744700, + 1697745600 + ] + } + }, + "example-query-1d-7d": { + "value": { + "base_value": 2784.79, + "cashflow": { + "CSD": [ + 0, + 0, + 100, + 0, + 0 + ] + }, + "equity": [ + 2784.79, + 2794.79, + 2805.46, + 2774.16, + 2748.73 + ], + "profit_loss": [ + 0, + 10.0022, + 10.6692, + -31.2996, + -25.4232 + ], + "profit_loss_pct": [ + 0, + 0.0035, + 0.0074, + -0.0038, + -0.0129 + ], + "timeframe": "1D", + "timestamp": [ + 1697241600, + 1697500800, + 1697587200, + 1697673600, + 1697760000 + ] + } + } + }, "schema": { "$ref": "#/components/schemas/PortfolioHistory" } @@ -20840,7 +24392,7 @@ } ], "post": { - "description": "Creating an order for your end customer. Each trading request must pass in the account_id in the URL.\n\n- Note that when submitting crypto orders, `market`, `limit` and `stop_limit` orders are supported while the supported `time_in_force` values are `gtc`, and `ioc`.\n- For equities and crypto we accept fractional orders as well with either `notional` or `qty` provided.\n- Note that submitting an options order is only available for partners who have been enabled for Options BETA.\n- In case of Fixed Income, only `market` and `limit` order types with `day` `time_in_force` are supported, and order replacement is not supported.\nNote that submitting Fixed Income orders is only available for partners who have been enabled for Fixed Income.\n- For IPO indications of interest (`asset_class: \"ipo\"`), the `symbol` is the offering reference returned by [`GET /v1/ipos`](#operation/listIPOOfferings) (e.g. `FI111225`). IPO orders are notional-only (`notional` required, `qty` must be omitted), buy-side, `market` type, and `gtc` time_in_force. Indications of interest can be replaced or canceled while the offering is open via [`PATCH /v1/trading/accounts/{account_id}/orders/{order_id}`](#operation/replaceOrderForAccount) and [`DELETE /v1/trading/accounts/{account_id}/orders/{order_id}`](#operation/deleteOrderForAccount).", + "description": "Creating an order for your end customer. Each trading request must pass in the account_id in the URL.\n\n- Note that when submitting crypto orders, `market`, `limit` and `stop_limit` orders are supported while the supported `time_in_force` values are `gtc`, and `ioc`.\n- For equities and crypto we accept fractional orders as well with either `notional` or `qty` provided.\n- Note that submitting an options order is only available for partners who have been enabled for Options BETA.\n- In case of Fixed Income, only `market` and `limit` order types with `day` `time_in_force` are supported, and order replacement is not supported.\nNote that submitting Fixed Income orders is only available for partners who have been enabled for Fixed Income.\n- Note that submitting DMA orders (using `advanced_instructions` with `algorithm: DMA`) is only available for partners/correspondents who have been enabled for DMA.\n- For IPO indications of interest (`asset_class: \"ipo\"`), the `symbol` is the offering reference returned by [`GET /v1/ipos`](#operation/listIPOOfferings) (e.g. `FI111225`). IPO orders are notional-only (`notional` required, `qty` must be omitted), buy-side, `market` type, and `gtc` time_in_force. Indications of interest can be replaced or canceled while the offering is open via [`PATCH /v1/trading/accounts/{account_id}/orders/{order_id}`](#operation/replaceOrderForAccount) and [`DELETE /v1/trading/accounts/{account_id}/orders/{order_id}`](#operation/deleteOrderForAccount).", "operationId": "createOrderForAccount", "requestBody": { "content": { @@ -21010,7 +24562,7 @@ } ], "patch": { - "description": "Replaces a single order with updated parameters. Each parameter overrides the corresponding attribute of the existing order. The other attributes remain the same as the existing order.\n\nA success return code from a replaced order does NOT guarantee the existing open order has been replaced. If the existing open order is filled before the replacing (new) order reaches the execution venue, the replacing (new) order is rejected, and these events are sent in the trade_updates stream channel found [here](https://docs.alpaca.markets/reference/subscribetotradev2sse).\n\nWhile an order is being replaced, the account's buying power is reduced by the larger of the two orders that have been placed (the old order being replaced, and the newly placed order to replace it). If you are replacing a buy entry order with a higher limit price than the original order, the buying power is calculated based on the newly placed order. If you are replacing it with a lower limit price, the buying power is calculated based on the old order.\n\nNote: Order cannot be replaced when the status is `accepted`, `pending_new`, `pending_cancel` or `pending_replace`.\n\nNote: Notional orders for non-IPO asset classes cannot be replaced. Any attempt to modify a non-IPO notional order via this endpoint will be rejected; cancel it and submit a new one instead.\n\nNote: IPO indications of interest (`asset_class: \"ipo\"`) **are** notional and **can** be replaced via this endpoint by providing a new `notional` value. `qty` and `notional` are mutually exclusive on a single replace request.", + "description": "Replaces a single order with updated parameters. Each parameter overrides the corresponding attribute of the existing order. The other attributes remain the same as the existing order.\n\nA success return code from a replaced order does NOT guarantee the existing open order has been replaced. If the existing open order is filled before the replacing (new) order reaches the execution venue, the replacing (new) order is rejected, and these events are sent in the trade_updates stream channel found [here](https://docs.alpaca.markets/reference/subscribetotradev2sse).\n\nWhile an order is being replaced, the account's buying power is reduced by the larger of the two orders that have been placed (the old order being replaced, and the newly placed order to replace it). If you are replacing a buy entry order with a higher limit price than the original order, the buying power is calculated based on the newly placed order. If you are replacing it with a lower limit price, the buying power is calculated based on the old order.\n\nNote: Order cannot be replaced when the status is `accepted`, `pending_new`, `pending_cancel` or `pending_replace`.\n\nNote: Notional orders for non-IPO asset classes cannot be replaced. Any attempt to modify a non-IPO notional order via this endpoint will be rejected; cancel it and submit a new one instead.\n\nNote: IPO indications of interest (`asset_class: \"ipo\"`) **are** notional and **can** be replaced via this endpoint by providing a new `notional` value. `qty` and `notional` are mutually exclusive on a single replace request.\n\nNote that submitting DMA orders (using `advanced_instructions` with `algorithm: DMA`) is only available for partners/correspondents who have been enabled for DMA.", "operationId": "replaceOrderForAccount", "requestBody": { "content": { @@ -21100,6 +24652,18 @@ "requestBody": { "content": { "application/json": { + "examples": { + "Example 1": { + "value": { + "notional": "40", + "side": "buy", + "swap_fee_bps": 45, + "symbol": "AAPL", + "time_in_force": "day", + "type": "market" + } + } + }, "schema": { "properties": { "notional": { @@ -21121,17 +24685,7 @@ "$ref": "#/components/schemas/OrderType" } }, - "type": "object", - "x-examples": { - "Example 1": { - "notional": "40", - "side": "buy", - "swap_fee_bps": 45, - "symbol": "AAPL", - "time_in_force": "day", - "type": "market" - } - } + "type": "object" } } } @@ -21468,7 +25022,7 @@ "description": "Invalid Symbol\n\nThe specified symbol_or_contract_id is invalid." } }, - "summary": "Do Not Exercise an Options Position (BETA)", + "summary": "Do Not Exercise an Options Position", "tags": [ "Trading" ] @@ -21538,7 +25092,7 @@ "description": "Forbidden\n\nAvailable position quantity is not sufficient." } }, - "summary": "Exercise an Options Position (BETA)", + "summary": "Exercise an Options Position", "tags": [ "Trading" ] @@ -21897,12 +25451,39 @@ }, "/v1/transfers/jit/limits": { "get": { - "description": "The JIT Securities daily trading limit is set at the correspondent level and is used as the limit for the total amount due to Alpaca on the date of settlement. The limit in use returns the real time usage of this limit and is calculated by taking the net of trade and non-trade activity inflows and outflows. If the limit in use reaches the daily net limit, further purchasing activity will be halted, however, the limit can be adjusted by reaching out to Alpaca with the proposed new limit and the reason for the change.", + "description": "The JIT Securities daily trading limit is set at the correspondent level and is used as the limit for the total amount due to Alpaca on the date of settlement. The limit in use returns the real time usage of this limit and is calculated by taking the net of trade and non-trade activity inflows and outflows. If the limit in use reaches the daily net limit, further purchasing activity will be halted, however, the limit can be adjusted by reaching out to Alpaca with the proposed new limit and the reason for the change. The limit is reset at the start of every trading day at 8.00 PM ET", "operationId": "get-v1-transfers-jit-limits", "responses": { "200": { "content": { "application/json": { + "examples": { + "open_cash_secured_put": { + "summary": "Correspondent with an open cash-secured put (cash_held present)", + "value": { + "cash_held": "20000", + "correspondent": "EXMP", + "daily_net_limit": "1000000", + "executed_buys": "43140", + "executed_sells": "0", + "in_use_limit": "103140", + "open_buys": "40000", + "open_sells": "0" + } + }, + "us_equity": { + "summary": "US equity correspondent with open and executed activity", + "value": { + "correspondent": "EXMP", + "daily_net_limit": "1000000", + "executed_buys": "55", + "executed_sells": "0", + "in_use_limit": "55", + "open_buys": "0", + "open_sells": "0" + } + } + }, "schema": { "$ref": "#/components/schemas/DailyTradingLimit" } @@ -21935,7 +25516,8 @@ "net_payment_final", "gross_summary", "gross_payment", - "gross_payment_final" + "gross_payment_final", + "obligation" ], "type": "string" } @@ -22091,6 +25673,116 @@ ] } }, + "/v1/wallets/travel-rule/vasps": { + "get": { + "description": "Search for Virtual Asset Service Providers, or crypto exchanges, on Notabene's network.\nThis endpoint can be used to find the VASP DID for your beneficiary's exchange when submitting\ntravel rule information for your whitelisted wallets. Use the `q` parameter to search for exchanges.", + "operationId": "searchVASPs", + "parameters": [ + { + "description": "General search query string.", + "in": "query", + "name": "q", + "schema": { + "type": "string" + } + }, + { + "description": "Filter by VASP email domain.", + "in": "query", + "name": "emailDomain", + "schema": { + "type": "string" + } + }, + { + "description": "Filter by Chainalysis-specific VASP name.", + "in": "query", + "name": "chainalysisName", + "schema": { + "type": "string" + } + }, + { + "description": "Specify which fields to return (comma-separated).", + "in": "query", + "name": "fields", + "schema": { + "type": "string" + } + }, + { + "description": "The zero-based page number to retrieve.", + "in": "query", + "name": "page", + "schema": { + "default": 0, + "minimum": 0, + "type": "integer" + } + }, + { + "description": "Number of items per page.", + "in": "query", + "name": "per_page", + "schema": { + "default": 10, + "maximum": 100, + "minimum": 1, + "type": "integer" + } + }, + { + "description": "Field-based sort expression, such as name:ASC. Multiple expressions may be comma-separated. Defaults to name:ASC.", + "in": "query", + "name": "order", + "schema": { + "default": "name:ASC", + "example": "name:ASC", + "pattern": "^([ A-Za-z]+(:(ASC|DESC))?(:NULLS (FIRST|LAST))?(,?))+$", + "type": "string" + } + }, + { + "description": "Whether to include child/subsidiary entities.", + "in": "query", + "name": "includeSubsidiaryVASPs", + "schema": { + "default": false, + "type": "boolean" + } + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "example": { + "page": 1, + "pages": 1, + "total": 1, + "vasps": [ + { + "country": "US", + "did": "did:ethr:0xf1c002f9e7ca88018d6dcf1e86403fb2f5f055f4", + "id": "vasp-1", + "name": "Example Exchange" + } + ] + }, + "schema": { + "$ref": "#/components/schemas/SearchVASPsResponse" + } + } + }, + "description": "Successful request. Returns an array of VASPs or an empty list if no matches found." + } + }, + "summary": "Search for VASPs", + "tags": [ + "Crypto Funding" + ] + } + }, "/v2beta1/accounts/{account_id}/events/activities/{event_id}": { "get": { "description": "Returns a single account activity (V2) event for the specified account by ULID `event_id`, delivered as a server-sent event message.\n\nThis is the single-event counterpart to the `subscribeToActivitiesSSE` streaming endpoint, intended for re-fetching an individual activity that was previously observed on the activities stream.", @@ -22412,11 +26104,7 @@ "text/event-stream": { "schema": { "items": { - "oneOf": [ - { - "$ref": "#/components/schemas/StatusFundingEvent" - } - ] + "$ref": "#/components/schemas/StatusFundingEvent" }, "type": "array" } @@ -22645,7 +26333,7 @@ }, "/v2/events/trades": { "get": { - "description": "The Events API provides event push as well as historical queries via SSE (server sent events).\n\nYou can listen to events related to trade updates. Most market trades sent during market hours are filled instantly; you can listen to limit order updates through this endpoint.\n\nHistorical events are streamed immediately if queried, and updates are pushed as events occur.\n\nQuery Params Rules:\n- `since` required if `until` specified\n- `since_id` required if `until_id` specified\n- `since` and `since_id` can't be used at the same time\nBehavior:\n- if `since` or `since_id` not specified this will not return any historic data\n- if `until` or `until_id` reached stream will end (status 200)\n\n---\n\nNote for people using the clients generated from this OAS spec. Currently OAS-3 doesn't have full support for representing SSE style responses from an API, so if you are using a generated client and don't specify a `since` and `until` there is a good chance the generated clients will hang waiting for the response to end.\n\nIf you require the streaming capabilities we recommend not using the generated clients for this specific usecase until the OAS-3 standards come to a consensus on how to represent this correctly in OAS-3.\n\n---\n\n**Legacy trade events API**\n\n**Deprecation notice**\n\nAs part of the deprecation process,\nthe legacy trade events API is now only available for existing broker-partners at: `GET /v1/events/trades` only for compatibility reasons.\n\nAll new broker partners will not have the option for the legacy trade event endpoint.\n\nAll new broker partners will have to integrate with the new `/v2/events/trades` endpoint.\n\nAlso, all existing broker partners are now recommended to upgrade to the `/v2/events/trades` endpoint, which provides faster event delivery times.\n\nThe legacy trade events api works the same way as the new one with the exception of the event_id which is an integer except of an ULID. This results in the request's since_id and until_id are also being integers. This integer is monotonically increasing over time for events.\n\nPlease note that the new `/v2` endpoint, is the same as, and was originally available under `/v2beta1`.\nWe encourage all customers to adjust their codebase from that interim beta endpoint to the `/v2` stable endpoint.\nIn the near future we will setup permanent redirect from `/v2beta1` to `/v2` before we completely remove the beta endpoint.\n\n---\n\n### Comment messages\nAccording to the SSE specification, any line that starts with a colon is a comment which does not contain data. It is typically a free text that does not follow any data schema. A few examples mentioned below for comment messages.\n\n##### Slow client\n\nThe server sends a comment when the client is not consuming messages fast enough. Example: `: you are reading too slowly, dropped 10000 messages`\n\n##### Internal server error\n\nAn error message is sent as a comment when the server closes the connection on an internal server error (only sent by the v2 and v2beta1 endpoints). Example: `: internal server error`\n\n---\n\n**Common events**\n\nThese are the events that are the expected results of actions you may have taken by sending API requests.\n\nThe meaning of the timestamp field changes for each type; the meanings have been specified here for which types the timestamp field will be present.\n\n- `accepted` Sent when an order is received and accepted by Alpaca\n- `pending_new` Sent when the order has been received by Alpaca and routed to the exchanges, but has not yet been accepted for execution.\n- `new` Sent when an order has been routed to exchanges for execution.\n- `fill` Sent when your order has been completely filled.\n - timestamp: The time at which the order was filled.\n- `partial_fill` Sent when a number of shares less than the total remaining quantity on your order has been filled.\n - timestamp: The time at which the shares were filled.\n- `canceled` Sent when your requested cancellation of an order is processed.\n - timestamp: The time at which the order was canceled.\n- `expired` Sent when an order has reached the end of its lifespan, as determined by the order's time in force value.\n - timestamp: The time at which the order expired.\n- `done_for_day` Sent when the order is done executing for the day, and will not receive further updates until the next trading day.\n- `replaced` Sent when your requested replacement of an order is processed.\n - timestamp: The time at which the order was replaced.\n\n**Rarer events**\n\nThese are events that may rarely be sent due to unexpected circumstances on the exchanges. It is unlikely you will need to design your code around them, but you may still wish to account for the possibility that they will occur.\n\n- `rejected` Sent when your order has been rejected.\n - timestamp: The time at which the rejection occurred.\n- `held` For multi-leg orders, the secondary orders (stop loss, take profit) will enter this state while waiting to be triggered.\n- `stopped` Sent when your order has been stopped, and a trade is guaranteed for the order, usually at a stated price or better, but has not yet occurred.\n- `pending_cancel` Sent when the order is awaiting cancellation. Most cancellations will occur without the order entering this state.\n- `pending_replace` Sent when the order is awaiting replacement.\n- `calculated` Sent when the order has been completed for the day - it is either filled or done_for_day - but remaining settlement calculations are still pending.\n- `suspended` Sent when the order has been suspended and is not eligible for trading.\n- `order_replace_rejected` Sent when the order replace has been rejected.\n- `order_cancel_rejected` Sent when the order cancel has been rejected.\n- `trade_bust`: Sent when a previously reported execution has been canceled (\"busted\") by the upstream exchange.\n- `trade_correct`: Sent when a previously reported trade has been corrected. For example, the exchange may have updated the price, quantity, or another execution parameter after the trade was initially reported.\n- `restated`: Sent when the order is manually modified.", + "description": "The Events API provides event push as well as historical queries via SSE (server sent events).\n\nYou can listen to events related to trade updates. Most market trades sent during market hours are filled instantly; you can listen to limit order updates through this endpoint.\n\nHistorical events are streamed immediately if queried, and updates are pushed as events occur.\n\nQuery Params Rules:\n- `since` required if `until` specified\n- `since_id` required if `until_id` specified\n- `since` and `since_id` can't be used at the same time\nBehavior:\n- if `since` or `since_id` not specified this will not return any historic data\n- if `until` or `until_id` reached stream will end (status 200)\n\n---\n\nNote for people using the clients generated from this OAS spec. Currently OAS-3 doesn't have full support for representing SSE style responses from an API, so if you are using a generated client and don't specify a `since` and `until` there is a good chance the generated clients will hang waiting for the response to end.\n\nIf you require the streaming capabilities we recommend not using the generated clients for this specific usecase until the OAS-3 standards come to a consensus on how to represent this correctly in OAS-3.\n\n---\n\n**Legacy trade events API deprecation**\n\nThe legacy v1 trade events endpoint has been fully deprecated and is no longer available. Use `/v2/events/trades` for all trade event integrations.\n\n---\n\n### Comment messages\nAccording to the SSE specification, any line that starts with a colon is a comment which does not contain data. It is typically a free text that does not follow any data schema. A few examples mentioned below for comment messages.\n\n##### Slow client\n\nThe server sends a comment when the client is not consuming messages fast enough. Example: `: you are reading too slowly, dropped 10000 messages`\n\n##### Internal server error\n\nAn error message is sent as a comment when the server closes the connection on an internal server error (only sent by the v2 and v2beta1 endpoints). Example: `: internal server error`\n\n---\n\n**Common events**\n\nThese are the events that are the expected results of actions you may have taken by sending API requests.\n\nThe meaning of the timestamp field changes for each type; the meanings have been specified here for which types the timestamp field will be present.\n\n- `accepted` Sent when an order is received and accepted by Alpaca\n- `pending_new` Sent when the order has been received by Alpaca and routed to the exchanges, but has not yet been accepted for execution.\n- `new` Sent when an order has been routed to exchanges for execution.\n- `fill` Sent when your order has been completely filled.\n - timestamp: The time at which the order was filled.\n- `partial_fill` Sent when a number of shares less than the total remaining quantity on your order has been filled.\n - timestamp: The time at which the shares were filled.\n- `canceled` Sent when your requested cancellation of an order is processed.\n - timestamp: The time at which the order was canceled.\n- `expired` Sent when an order has reached the end of its lifespan, as determined by the order's time in force value.\n - timestamp: The time at which the order expired.\n- `done_for_day` Sent when the order is done executing for the day, and will not receive further updates until the next trading day.\n- `replaced` Sent when your requested replacement of an order is processed.\n - timestamp: The time at which the order was replaced.\n\n**Rarer events**\n\nThese are events that may rarely be sent due to unexpected circumstances on the exchanges. It is unlikely you will need to design your code around them, but you may still wish to account for the possibility that they will occur.\n\n- `rejected` Sent when your order has been rejected.\n - timestamp: The time at which the rejection occurred.\n- `held` For multi-leg orders, the secondary orders (stop loss, take profit) will enter this state while waiting to be triggered.\n- `stopped` Sent when your order has been stopped, and a trade is guaranteed for the order, usually at a stated price or better, but has not yet occurred.\n- `pending_cancel` Sent when the order is awaiting cancellation. Most cancellations will occur without the order entering this state.\n- `pending_replace` Sent when the order is awaiting replacement.\n- `calculated` Sent when the order has been completed for the day - it is either filled or done_for_day - but remaining settlement calculations are still pending.\n- `suspended` Sent when the order has been suspended and is not eligible for trading.\n- `order_replace_rejected` Sent when the order replace has been rejected.\n- `order_cancel_rejected` Sent when the order cancel has been rejected.\n- `trade_bust`: Sent when a previously reported execution has been canceled (\"busted\") by the upstream exchange.\n- `trade_correct`: Sent when a previously reported trade has been corrected. For example, the exchange may have updated the price, quantity, or another execution parameter after the trade was initially reported.\n- `restated`: Sent when the order is manually modified.", "operationId": "subscribeToTradeV2SSE", "parameters": [ { @@ -22805,6 +26493,9 @@ { "name": "Tokenization" }, + { + "name": "ACATS" + }, { "description": "Endpoints for discovering and tracking IPO (Initial Public Offering) lifecycle data. Use [`GET /v1/ipos`](#operation/listIPOOfferings) and [`GET /v1/ipos/{offering_reference}`](#operation/getIPOOffering) to query offerings on demand, and the [IPO Events Stream](#operation/subscribeToIPOEventsSSE) under the **Events** group to receive real-time and historical lifecycle updates (new offerings, prospectus, 60-minute mail, allocations, cancellations).\n", "name": "IPO" diff --git a/module/market-data/openapi.json b/module/market-data/openapi.json index 8d26e84b..0c4ca563 100644 --- a/module/market-data/openapi.json +++ b/module/market-data/openapi.json @@ -33,6 +33,14 @@ "type": "string" } }, + "cas_data_quality": { + "description": "Controls which corporate actions are returned based on data quality.\n", + "in": "query", + "name": "data_quality", + "schema": { + "$ref": "#/components/schemas/data_quality" + } + }, "cas_end": { "description": "The inclusive end of the interval. The corporate actions are sorted by their `process_date`. Format: YYYY-MM-DD. Default: current day.\n", "examples": { @@ -48,8 +56,86 @@ "type": "string" } }, + "cas_events_last_event_id": { + "description": "Standard SSE reconnect header. ULID matching an `event_id`; replay events\nwhose `event_id` is greater than or equal to this value (inclusive -- the\nevent with this id is redelivered on reconnect, so clients should\ndeduplicate by `event_id` if needed). Overrides `since_id` when both are\nset.\n", + "example": "01J9RPMV5TKB8WX3M4F1KZ7QH2", + "in": "header", + "name": "Last-Event-Id", + "schema": { + "$ref": "#/components/schemas/event_id" + } + }, + "cas_events_region": { + "description": "Which markets to receive events for. Compared against the envelope's `region`\nfield and applied uniformly to historical and live events.\n\n- `all` (default): every event, regardless of market.\n- `us`: only events with `region == \"us\"`.\n- `non_us`: only events with `region == \"non_us\"`.\n\nUnknown values return `400` with the list of valid values in the error\nmessage. The value is case-insensitive.\n", + "in": "query", + "name": "region", + "schema": { + "default": "all", + "enum": [ + "all", + "us", + "non_us" + ], + "type": "string" + } + }, + "cas_events_since": { + "description": "Replay events emitted on or after this RFC-3339 date. Mutually exclusive with\n`since_id`. Required when `until` is specified.\n", + "example": "2026-03-20T00:00:00Z", + "in": "query", + "name": "since", + "schema": { + "format": "date-time", + "type": "string" + } + }, + "cas_events_since_id": { + "description": "ULID matching an `event_id`; replay events whose `event_id` is greater than\nor equal to this value (inclusive -- the event with this id, if it still\nexists, is redelivered). Mutually exclusive with `since`. Overridden by the\n`Last-Event-Id` header when both are set.\n", + "example": "01J9RPMV5TKB8WX3M4F1KZ7QH2", + "in": "query", + "name": "since_id", + "schema": { + "$ref": "#/components/schemas/event_id" + } + }, + "cas_events_type": { + "description": "Filter the stream to a subset of `event_type` values.\n\nPassed as a comma-separated list in the URL query, e.g.\n`?type=cash_dividend_corporateaction_event,stock_dividend_corporateaction_event`.\n\nOnly events whose `event_type` is in this list are delivered. An empty or\nomitted value is treated as \"no filter\" (deliver every type). Unknown values\nreturn `400` with the list of valid values in the error message. The filter\napplies uniformly to both replayed (historical) and live events.\n\nSee [`corporate_action_event_type`](#/components/schemas/corporate_action_event_type)\nfor the full list of accepted values.\n", + "example": [ + "cash_dividend_corporateaction_event", + "stock_dividend_corporateaction_event" + ], + "explode": false, + "in": "query", + "name": "type", + "schema": { + "items": { + "$ref": "#/components/schemas/corporate_action_event_type" + }, + "type": "array" + }, + "style": "form" + }, + "cas_events_until": { + "description": "Close the connection after the last event on this RFC-3339 date (inclusive).\nRequires `since`; cannot be in the future.\n", + "example": "2026-03-20T23:59:59Z", + "in": "query", + "name": "until", + "schema": { + "format": "date-time", + "type": "string" + } + }, + "cas_events_until_id": { + "description": "ULID matching an `event_id`; close the connection once this id has been\ndelivered (inclusive). Requires `since_id`; cannot be in the future.\n", + "example": "01J9RVB6Y4ZK8M3N7QD2WX1RFP", + "in": "query", + "name": "until_id", + "schema": { + "$ref": "#/components/schemas/event_id" + } + }, "cas_ids": { - "description": "A comma-separated list of corporate action IDs. This parameter is mutually exclusive with all other filters (symbols, types, start, end).\n", + "description": "A comma-separated list of corporate action IDs. This parameter is mutually exclusive with all other filters (symbols, types, start, end, region, data_quality).\n", "example": "1dbc7685-9517-4a77-a236-8527d49cefdc,f8489167-4e4b-431d-a0be-6017ae1cf08a", "in": "query", "name": "ids", @@ -100,7 +186,7 @@ } }, "cas_types": { - "description": "A comma-separated list of types. If not provided, search all types.\n\nThe following types are supported:\n - reverse_split\n - forward_split\n - unit_split\n - cash_dividend\n - stock_dividend\n - spin_off\n - cash_merger\n - stock_merger\n - stock_and_cash_merger\n - redemption\n - name_change\n - worthless_removal\n - rights_distribution\n - partial_call\n - reorganization\n", + "description": "A comma-separated list of types. If not provided, search all types.\n\nThe following types are supported:\n - reverse_split\n - forward_split\n - unit_split\n - cash_dividend\n - stock_dividend\n - spin_off\n - cash_merger\n - stock_merger\n - stock_and_cash_merger\n - redemption\n - name_change\n - worthless_removal\n - rights_distribution\n - partial_call\n - reorganization\n - capital_gains_distribution\n", "example": "forward_split,reverse_split", "in": "query", "name": "types", @@ -126,25 +212,6 @@ "$ref": "#/components/schemas/crypto_latest_loc" } }, - "crypto_perp_loc": { - "description": "Crypto perpetual location.", - "in": "path", - "name": "loc", - "required": true, - "schema": { - "$ref": "#/components/schemas/crypto_perp_loc" - } - }, - "crypto_perp_symbols": { - "description": "A comma-separated list of crypto symbols.", - "example": "BTCUSDT.P,LTCUSDT.P", - "in": "query", - "name": "symbols", - "required": true, - "schema": { - "type": "string" - } - }, "crypto_symbols": { "description": "A comma-separated list of crypto symbols.", "example": "BTC/USD,LTC/USD", @@ -199,16 +266,6 @@ "$ref": "#/components/schemas/forex_timeframe" } }, - "index_symbols": { - "description": "A comma-separated list of index symbols.", - "example": "SPX,VIX", - "in": "query", - "name": "symbols", - "required": true, - "schema": { - "type": "string" - } - }, "isins": { "description": "A comma-separated list of ISINs with a limit of 1000.", "example": "US912797KJ59,US912797KS58,US912797LB15", @@ -473,8 +530,7 @@ "required": true, "schema": { "type": "string" - }, - "x-go-name": "TimeFrame" + } }, "trade_size": { "description": "Filters to best bid/ask where the minimum trade size is less than or equal to the given numeric value. For any negative value the best bid/ask will be returned for the smallest trade size on either side.\n", @@ -527,258 +583,1903 @@ } }, "schemas": { - "ca_id": { - "description": "The internal Alpaca identifier of the corporate action.", - "format": "uuid", - "type": "string" - }, - "cash_dividend": { - "description": "Cash dividend.", - "example": { - "cusip": "319829107", - "ex_date": "2023-05-04", - "foreign": false, - "id": "11cfd108-292e-4cc6-bfbf-5999cdbc4029", - "payable_date": "2023-05-19", - "process_date": "2023-05-19", - "rate": 0.125, - "record_date": "2023-05-05", - "special": false, - "symbol": "FCF" - }, + "ca_event_base": { + "description": "Common fields present on every `ca` payload regardless of `event_type`.\nNot meant to be referenced directly by clients -- it exists so each\nper-type payload schema (`ca_event_cash_dividend`, `ca_event_forward_split`,\n...) can `allOf`-compose it instead of restating `id` and `process_date`.\nThis makes the \"always present\" invariant structural (guaranteed by the\nschema) rather than emergent from repetition, and matches the \"Common `ca`\nfields\" section of the event-streaming Corporate Actions SSE design doc.\n", "properties": { - "currency": { - "$ref": "#/components/schemas/currency" - }, - "cusip": { - "type": "string" - }, - "due_bill_off_date": { - "format": "date", - "type": "string" - }, - "due_bill_on_date": { - "format": "date", - "type": "string" - }, - "ex_date": { - "$ref": "#/components/schemas/ex_date" - }, - "foreign": { - "type": "boolean" - }, "id": { "$ref": "#/components/schemas/ca_id" }, - "isin": { - "$ref": "#/components/schemas/isin" - }, - "payable_date": { - "$ref": "#/components/schemas/payable_date" - }, "process_date": { "$ref": "#/components/schemas/process_date" - }, - "rate": { - "format": "double", - "type": "number" - }, - "record_date": { - "$ref": "#/components/schemas/record_date" - }, - "special": { - "type": "boolean" - }, - "sub_type": { - "description": "Sub-type of the cash dividend.", - "enum": [ - "interest", - "return_of_capital" - ], - "type": "string" - }, - "symbol": { - "type": "string" } }, "required": [ "id", - "symbol", - "cusip", - "rate", - "special", - "foreign", - "process_date", - "ex_date" + "process_date" ], "type": "object" }, - "cash_merger": { - "description": "Cash merger.", - "example": { - "acquiree_cusip": "Y2687W108", - "acquiree_symbol": "GLOP", - "effective_date": "2023-07-17", - "id": "3772bbd7-4ad5-44d4-9cc0-f69156a2f8f5", - "payable_date": "2023-07-17", - "process_date": "2023-07-17", - "rate": 5.37 - }, - "properties": { - "acquiree_cusip": { - "type": "string" - }, - "acquiree_isin": { - "$ref": "#/components/schemas/isin" - }, - "acquiree_symbol": { - "type": "string" - }, - "acquirer_cusip": { - "type": "string" - }, - "acquirer_isin": { - "$ref": "#/components/schemas/isin" - }, - "acquirer_symbol": { - "type": "string" - }, - "currency": { - "$ref": "#/components/schemas/currency" - }, - "effective_date": { - "$ref": "#/components/schemas/effective_date" - }, - "id": { - "$ref": "#/components/schemas/ca_id" - }, - "payable_date": { - "$ref": "#/components/schemas/payable_date" - }, - "process_date": { - "$ref": "#/components/schemas/process_date" + "ca_event_capital_gains_distribution": { + "allOf": [ + { + "$ref": "#/components/schemas/ca_event_base" }, - "rate": { - "format": "double", - "type": "number" + { + "properties": { + "currency": { + "$ref": "#/components/schemas/currency" + }, + "cusip": { + "description": "CUSIP of the security.", + "type": "string" + }, + "ex_date": { + "$ref": "#/components/schemas/ex_date" + }, + "isin": { + "$ref": "#/components/schemas/isin" + }, + "long_term_rate": { + "description": "Long-term capital-gains payment per share, as a decimal string.", + "examples": [ + "1.4372" + ], + "type": "string" + }, + "payable_date": { + "$ref": "#/components/schemas/payable_date" + }, + "record_date": { + "$ref": "#/components/schemas/record_date" + }, + "short_term_rate": { + "description": "Short-term capital-gains payment per share, as a decimal string.", + "examples": [ + "0.1051" + ], + "type": "string" + }, + "symbol": { + "description": "Ticker paying the distribution.", + "type": "string" + } + }, + "required": [ + "symbol", + "cusip", + "ex_date" + ], + "type": "object" } - }, - "required": [ - "id", - "acquiree_symbol", - "acquiree_cusip", - "rate", - "process_date", - "effective_date" ], - "type": "object" + "description": "Capital-gains distribution payload delivered when\n`event_type == capital_gains_distribution_corporateaction_event`. Every\ndecimal field is emitted as a JSON string to preserve precision on the\nwire. At least one of `long_term_rate` / `short_term_rate` is present;\ncombined distributions carry both.\n" }, - "corporate_actions": { - "properties": { - "cash_dividends": { - "items": { - "$ref": "#/components/schemas/cash_dividend" - }, - "type": "array", - "x-go-type-skip-optional-pointer": true - }, - "cash_mergers": { - "items": { - "$ref": "#/components/schemas/cash_merger" - }, - "type": "array", - "x-go-type-skip-optional-pointer": true - }, - "forward_splits": { - "items": { - "$ref": "#/components/schemas/forward_split" - }, - "type": "array", - "x-go-type-skip-optional-pointer": true + "ca_event_cash_dividend": { + "allOf": [ + { + "$ref": "#/components/schemas/ca_event_base" }, - "name_changes": { - "items": { - "$ref": "#/components/schemas/name_change" + { + "properties": { + "currency": { + "$ref": "#/components/schemas/currency" + }, + "cusip": { + "description": "CUSIP of the security.", + "type": "string" + }, + "due_bill_off_date": { + "description": "End of the due-bill period.", + "format": "date", + "type": "string" + }, + "due_bill_on_date": { + "description": "Start of the due-bill period.", + "format": "date", + "type": "string" + }, + "ex_date": { + "$ref": "#/components/schemas/ex_date" + }, + "foreign": { + "description": "`true` if the issuer is not US-based.", + "type": "boolean" + }, + "isin": { + "$ref": "#/components/schemas/isin" + }, + "payable_date": { + "$ref": "#/components/schemas/payable_date" + }, + "rate": { + "description": "Cash paid per share, as a decimal string.", + "examples": [ + "0.24" + ], + "type": "string" + }, + "record_date": { + "$ref": "#/components/schemas/record_date" + }, + "special": { + "description": "`true` if this is a one-off dividend outside the issuer's regular\ndistribution schedule.\n", + "type": "boolean" + }, + "sub_type": { + "description": "Optional sub-classification of the dividend.", + "enum": [ + "interest", + "return_of_capital" + ], + "type": "string" + }, + "symbol": { + "description": "Ticker paying the dividend.", + "type": "string" + } }, - "type": "array", - "x-go-type-skip-optional-pointer": true + "required": [ + "symbol", + "cusip", + "rate", + "special", + "foreign", + "ex_date" + ], + "type": "object" + } + ], + "description": "Cash dividend payload delivered when\n`event_type == cash_dividend_corporateaction_event`. Corresponds to\n`cash_dividends` on the REST\n[`GET /v1/corporate-actions`](#operation/CorporateActions) response, but\nevery decimal field is emitted as a JSON string to preserve precision on\nthe wire.\n" + }, + "ca_event_cash_merger": { + "allOf": [ + { + "$ref": "#/components/schemas/ca_event_base" }, - "partial_calls": { - "items": { - "$ref": "#/components/schemas/partial_call" + { + "properties": { + "acquiree_cusip": { + "description": "CUSIP of the company being bought.", + "type": "string" + }, + "acquiree_isin": { + "$ref": "#/components/schemas/isin" + }, + "acquiree_symbol": { + "description": "Ticker being bought (will disappear after the merger).", + "type": "string" + }, + "acquirer_cusip": { + "description": "Buyer's CUSIP.", + "type": "string" + }, + "acquirer_isin": { + "$ref": "#/components/schemas/isin" + }, + "acquirer_symbol": { + "description": "Buyer's ticker (the surviving company).", + "type": "string" + }, + "currency": { + "$ref": "#/components/schemas/currency" + }, + "effective_date": { + "$ref": "#/components/schemas/effective_date" + }, + "payable_date": { + "$ref": "#/components/schemas/payable_date" + }, + "rate": { + "description": "Cash paid per share of the acquired company, as a decimal string.", + "examples": [ + "42.50" + ], + "type": "string" + } + }, + "required": [ + "acquiree_symbol", + "acquiree_cusip", + "rate", + "effective_date" + ], + "type": "object" + } + ], + "description": "Cash merger payload delivered when\n`event_type == cash_merger_corporateaction_event`. Corresponds to\n`cash_mergers` on the REST\n[`GET /v1/corporate-actions`](#operation/CorporateActions) response, but\nevery decimal field is emitted as a JSON string to preserve precision on\nthe wire.\n" + }, + "ca_event_equity_partial_call": { + "allOf": [ + { + "$ref": "#/components/schemas/ca_event_base" + }, + { + "properties": { + "currency": { + "$ref": "#/components/schemas/currency" + }, + "cusip": { + "description": "CUSIP of the security.", + "type": "string" + }, + "dividend_rate": { + "description": "Dividend rate associated with the called shares, as a decimal string.", + "examples": [ + "0.05" + ], + "type": "string" + }, + "isin": { + "$ref": "#/components/schemas/isin" + }, + "lottery_date": { + "description": "When the lottery drawing happens.", + "format": "date", + "type": "string" + }, + "lottery_type": { + "description": "How called shares were allocated among holders.", + "enum": [ + "original", + "supplemental" + ], + "type": "string" + }, + "payable_date": { + "$ref": "#/components/schemas/payable_date" + }, + "price": { + "description": "Price paid per called share, as a decimal string.", + "examples": [ + "15.00" + ], + "type": "string" + }, + "record_date": { + "$ref": "#/components/schemas/record_date" + }, + "results_publication_date": { + "description": "When the lottery results are published.", + "format": "date", + "type": "string" + }, + "symbol": { + "description": "Ticker being partially called.", + "type": "string" + } + }, + "required": [ + "symbol" + ], + "type": "object" + } + ], + "description": "Partial redemption of an equity issue where the issuer calls back only a\nfraction of outstanding shares -- typically allocated to holders via a\nlottery. Delivered when\n`event_type == equity_partial_call_corporateaction_event`. Corresponds to\n`partial_calls` on the REST\n[`GET /v1/corporate-actions`](#operation/CorporateActions) response, but\nevery decimal field is emitted as a JSON string to preserve precision on\nthe wire.\n" + }, + "ca_event_forward_split": { + "allOf": [ + { + "$ref": "#/components/schemas/ca_event_base" + }, + { + "properties": { + "currency": { + "$ref": "#/components/schemas/currency" + }, + "cusip": { + "description": "CUSIP of the security.", + "type": "string" + }, + "due_bill_redemption_date": { + "$ref": "#/components/schemas/due_bill_redemption_date" + }, + "ex_date": { + "$ref": "#/components/schemas/ex_date" + }, + "isin": { + "$ref": "#/components/schemas/isin" + }, + "new_rate": { + "description": "Shares after the split, as a decimal string.", + "examples": [ + "4" + ], + "type": "string" + }, + "old_rate": { + "description": "Shares before the split, as a decimal string.", + "examples": [ + "1" + ], + "type": "string" + }, + "payable_date": { + "$ref": "#/components/schemas/payable_date" + }, + "record_date": { + "$ref": "#/components/schemas/record_date" + }, + "symbol": { + "description": "Ticker being split.", + "type": "string" + } + }, + "required": [ + "symbol", + "cusip", + "old_rate", + "new_rate", + "ex_date" + ], + "type": "object" + } + ], + "description": "Forward stock split payload delivered when\n`event_type == forward_split_corporateaction_event`. Corresponds to\n`forward_splits` on the REST\n[`GET /v1/corporate-actions`](#operation/CorporateActions) response, but\nevery decimal field is emitted as a JSON string to preserve precision on\nthe wire.\n" + }, + "ca_event_name_change": { + "allOf": [ + { + "$ref": "#/components/schemas/ca_event_base" + }, + { + "properties": { + "currency": { + "$ref": "#/components/schemas/currency" + }, + "new_cusip": { + "description": "CUSIP after the change.", + "type": "string" + }, + "new_isin": { + "$ref": "#/components/schemas/isin" + }, + "new_symbol": { + "description": "Ticker after the change.", + "type": "string" + }, + "old_cusip": { + "description": "CUSIP before the change.", + "type": "string" + }, + "old_isin": { + "$ref": "#/components/schemas/isin" + }, + "old_symbol": { + "description": "Ticker before the change.", + "type": "string" + } + }, + "required": [ + "old_symbol", + "old_cusip", + "new_symbol", + "new_cusip" + ], + "type": "object" + } + ], + "description": "Name/ticker change payload delivered when\n`event_type == name_change_corporateaction_event`. Corresponds to\n`name_changes` on the REST\n[`GET /v1/corporate-actions`](#operation/CorporateActions) response.\n" + }, + "ca_event_redemption": { + "allOf": [ + { + "$ref": "#/components/schemas/ca_event_base" + }, + { + "properties": { + "currency": { + "$ref": "#/components/schemas/currency" + }, + "cusip": { + "description": "CUSIP of the security.", + "type": "string" + }, + "isin": { + "$ref": "#/components/schemas/isin" + }, + "payable_date": { + "$ref": "#/components/schemas/payable_date" + }, + "rate": { + "description": "Cash paid per share, as a decimal string.", + "examples": [ + "25.00" + ], + "type": "string" + }, + "symbol": { + "description": "Ticker being redeemed.", + "type": "string" + } + }, + "required": [ + "symbol", + "cusip", + "rate" + ], + "type": "object" + } + ], + "description": "Full redemption payload delivered when\n`event_type == redemption_corporateaction_event`. Corresponds to\n`redemptions` on the REST\n[`GET /v1/corporate-actions`](#operation/CorporateActions) response, but\nevery decimal field is emitted as a JSON string to preserve precision on\nthe wire.\n" + }, + "ca_event_reorganization": { + "allOf": [ + { + "$ref": "#/components/schemas/ca_event_base" + }, + { + "properties": { + "cash_rate": { + "description": "Cash paid per source share, as a decimal string.", + "examples": [ + "5.00" + ], + "type": "string" + }, + "currency": { + "$ref": "#/components/schemas/currency" + }, + "cusip": { + "description": "CUSIP of the original security.", + "type": "string" + }, + "effective_date": { + "$ref": "#/components/schemas/effective_date" + }, + "isin": { + "$ref": "#/components/schemas/isin" + }, + "payable_date": { + "$ref": "#/components/schemas/payable_date" + }, + "stock_movements": { + "description": "One entry per replacement security delivered to holders.", + "items": { + "$ref": "#/components/schemas/ca_event_reorganization_stock_movement" + }, + "type": "array" + }, + "symbol": { + "description": "Ticker undergoing the reorganization.", + "type": "string" + } + }, + "required": [ + "symbol", + "cusip", + "effective_date" + ], + "type": "object" + } + ], + "description": "General-purpose corporate restructuring (e.g. Chapter 11 emergence) where\nexisting holders receive a mix of cash and/or one or more replacement\nsecurities. Each entry in `stock_movements` describes a separate\nshare-class distribution. Delivered when\n`event_type == reorganization_corporateaction_event`. Corresponds to\n`reorganizations` on the REST\n[`GET /v1/corporate-actions`](#operation/CorporateActions) response, but\nevery decimal field is emitted as a JSON string to preserve precision on\nthe wire.\n" + }, + "ca_event_reorganization_stock_movement": { + "additionalProperties": false, + "description": "A single replacement-security leg of a\n[`ca_event_reorganization`](#/components/schemas/ca_event_reorganization)\npayload. All decimal fields are emitted as JSON strings to preserve\nprecision on the wire.\n", + "properties": { + "cusip": { + "description": "Replacement CUSIP.", + "type": "string" + }, + "isin": { + "$ref": "#/components/schemas/isin" + }, + "new_rate": { + "description": "Replacement shares delivered per `source_rate`, as a decimal string.", + "examples": [ + "1" + ], + "type": "string" + }, + "source_rate": { + "description": "Source shares required for each `new_rate`, as a decimal string.", + "examples": [ + "10" + ], + "type": "string" + }, + "symbol": { + "description": "Replacement ticker.", + "type": "string" + } + }, + "required": [ + "symbol", + "cusip", + "new_rate", + "source_rate" + ], + "type": "object" + }, + "ca_event_reverse_split": { + "allOf": [ + { + "$ref": "#/components/schemas/ca_event_base" + }, + { + "properties": { + "currency": { + "$ref": "#/components/schemas/currency" + }, + "ex_date": { + "$ref": "#/components/schemas/ex_date" + }, + "new_cusip": { + "description": "CUSIP after the split.", + "type": "string" + }, + "new_isin": { + "$ref": "#/components/schemas/isin" + }, + "new_rate": { + "description": "Shares after the split, as a decimal string.", + "examples": [ + "1" + ], + "type": "string" + }, + "new_symbol": { + "description": "Ticker after the split when the issuer also changes symbol (e.g. with\na \"D\" suffix). Omitted when the ticker doesn't change.\n", + "type": "string" + }, + "old_cusip": { + "description": "CUSIP before the split.", + "type": "string" + }, + "old_isin": { + "$ref": "#/components/schemas/isin" + }, + "old_rate": { + "description": "Shares before the split, as a decimal string.", + "examples": [ + "10" + ], + "type": "string" + }, + "payable_date": { + "$ref": "#/components/schemas/payable_date" + }, + "record_date": { + "$ref": "#/components/schemas/record_date" + }, + "symbol": { + "description": "Ticker being split.", + "type": "string" + } + }, + "required": [ + "symbol", + "old_cusip", + "new_cusip", + "old_rate", + "new_rate", + "ex_date" + ], + "type": "object" + } + ], + "description": "Reverse stock split payload delivered when\n`event_type == reverse_split_corporateaction_event`. Corresponds to\n`reverse_splits` on the REST\n[`GET /v1/corporate-actions`](#operation/CorporateActions) response, but\nevery decimal field is emitted as a JSON string to preserve precision on\nthe wire.\n" + }, + "ca_event_rights_distribution": { + "allOf": [ + { + "$ref": "#/components/schemas/ca_event_base" + }, + { + "properties": { + "currency": { + "$ref": "#/components/schemas/currency" + }, + "ex_date": { + "$ref": "#/components/schemas/ex_date" + }, + "expiration_date": { + "$ref": "#/components/schemas/expiration_date" + }, + "new_cusip": { + "description": "CUSIP of the new right.", + "type": "string" + }, + "new_isin": { + "$ref": "#/components/schemas/isin" + }, + "new_symbol": { + "description": "Ticker of the new right/warrant.", + "type": "string" + }, + "payable_date": { + "$ref": "#/components/schemas/payable_date" + }, + "rate": { + "description": "Rights given per share you own, as a decimal string.", + "examples": [ + "0.25" + ], + "type": "string" + }, + "record_date": { + "$ref": "#/components/schemas/record_date" + }, + "source_cusip": { + "description": "CUSIP of the underlying ticker.", + "type": "string" + }, + "source_isin": { + "$ref": "#/components/schemas/isin" + }, + "source_symbol": { + "description": "Ticker whose holders receive the rights.", + "type": "string" + } + }, + "required": [ + "source_symbol", + "source_cusip", + "new_symbol", + "new_cusip", + "rate", + "ex_date", + "payable_date" + ], + "type": "object" + } + ], + "description": "Rights distribution payload delivered when\n`event_type == rights_distribution_corporateaction_event`. Corresponds to\n`rights_distributions` on the REST\n[`GET /v1/corporate-actions`](#operation/CorporateActions) response, but\nevery decimal field is emitted as a JSON string to preserve precision on\nthe wire.\n" + }, + "ca_event_spin_off": { + "allOf": [ + { + "$ref": "#/components/schemas/ca_event_base" + }, + { + "properties": { + "currency": { + "$ref": "#/components/schemas/currency" + }, + "due_bill_redemption_date": { + "$ref": "#/components/schemas/due_bill_redemption_date" + }, + "ex_date": { + "$ref": "#/components/schemas/ex_date" + }, + "new_cusip": { + "description": "CUSIP of the new spin-off company.", + "type": "string" + }, + "new_isin": { + "$ref": "#/components/schemas/isin" + }, + "new_rate": { + "description": "New-company shares distributed per `source_rate` parent shares, as a decimal string.", + "examples": [ + "0.5" + ], + "type": "string" + }, + "new_symbol": { + "description": "Ticker of the new spin-off company.", + "type": "string" + }, + "payable_date": { + "$ref": "#/components/schemas/payable_date" + }, + "record_date": { + "$ref": "#/components/schemas/record_date" + }, + "source_cusip": { + "description": "Parent CUSIP.", + "type": "string" + }, + "source_isin": { + "$ref": "#/components/schemas/isin" + }, + "source_rate": { + "description": "Parent shares required per ratio unit, as a decimal string.", + "examples": [ + "1" + ], + "type": "string" + }, + "source_symbol": { + "description": "Parent company's ticker.", + "type": "string" + } + }, + "required": [ + "source_symbol", + "source_cusip", + "source_rate", + "new_symbol", + "new_cusip", + "new_rate", + "ex_date" + ], + "type": "object" + } + ], + "description": "Spin-off payload delivered when\n`event_type == spin_off_corporateaction_event`. Corresponds to\n`spin_offs` on the REST\n[`GET /v1/corporate-actions`](#operation/CorporateActions) response, but\nevery decimal field is emitted as a JSON string to preserve precision on\nthe wire.\n" + }, + "ca_event_stock_and_cash_merger": { + "allOf": [ + { + "$ref": "#/components/schemas/ca_event_base" + }, + { + "properties": { + "acquiree_cusip": { + "description": "CUSIP of the company being bought.", + "type": "string" + }, + "acquiree_isin": { + "$ref": "#/components/schemas/isin" + }, + "acquiree_rate": { + "description": "Bought-side ratio, as a decimal string.", + "examples": [ + "1" + ], + "type": "string" + }, + "acquiree_symbol": { + "description": "Ticker being bought (will disappear).", + "type": "string" + }, + "acquirer_cusip": { + "description": "Buyer's CUSIP.", + "type": "string" + }, + "acquirer_isin": { + "$ref": "#/components/schemas/isin" + }, + "acquirer_rate": { + "description": "Buyer shares given per unit ratio, as a decimal string.", + "examples": [ + "0.5" + ], + "type": "string" + }, + "acquirer_symbol": { + "description": "Buyer's ticker (the surviving company).", + "type": "string" + }, + "cash_rate": { + "description": "Extra cash paid per share of the acquired company, as a decimal string.", + "examples": [ + "10.00" + ], + "type": "string" + }, + "currency": { + "$ref": "#/components/schemas/currency" + }, + "effective_date": { + "$ref": "#/components/schemas/effective_date" + }, + "payable_date": { + "$ref": "#/components/schemas/payable_date" + } + }, + "required": [ + "acquirer_symbol", + "acquirer_cusip", + "acquirer_rate", + "acquiree_symbol", + "acquiree_cusip", + "acquiree_rate", + "cash_rate", + "effective_date" + ], + "type": "object" + } + ], + "description": "Merger paying a mix of stock and cash. Delivered when\n`event_type == stock_and_cash_merger_corporateaction_event`. Corresponds\nto `stock_and_cash_mergers` on the REST\n[`GET /v1/corporate-actions`](#operation/CorporateActions) response, but\nevery decimal field is emitted as a JSON string to preserve precision on\nthe wire.\n" + }, + "ca_event_stock_dividend": { + "allOf": [ + { + "$ref": "#/components/schemas/ca_event_base" + }, + { + "properties": { + "currency": { + "$ref": "#/components/schemas/currency" + }, + "cusip": { + "description": "CUSIP of the security.", + "type": "string" + }, + "ex_date": { + "$ref": "#/components/schemas/ex_date" + }, + "isin": { + "$ref": "#/components/schemas/isin" + }, + "payable_date": { + "$ref": "#/components/schemas/payable_date" + }, + "rate": { + "description": "Extra shares received per share held, as a decimal string.", + "examples": [ + "0.05" + ], + "type": "string" + }, + "record_date": { + "$ref": "#/components/schemas/record_date" + }, + "symbol": { + "description": "Ticker paying the stock dividend.", + "type": "string" + } + }, + "required": [ + "symbol", + "cusip", + "rate", + "ex_date" + ], + "type": "object" + } + ], + "description": "Stock dividend payload delivered when\n`event_type == stock_dividend_corporateaction_event`. Corresponds to\n`stock_dividends` on the REST\n[`GET /v1/corporate-actions`](#operation/CorporateActions) response, but\nevery decimal field is emitted as a JSON string to preserve precision on\nthe wire.\n" + }, + "ca_event_stock_merger": { + "allOf": [ + { + "$ref": "#/components/schemas/ca_event_base" + }, + { + "properties": { + "acquiree_cusip": { + "description": "CUSIP of the company being bought.", + "type": "string" + }, + "acquiree_isin": { + "$ref": "#/components/schemas/isin" + }, + "acquiree_rate": { + "description": "Bought-side ratio, as a decimal string.", + "examples": [ + "1" + ], + "type": "string" + }, + "acquiree_symbol": { + "description": "Ticker being bought (will disappear).", + "type": "string" + }, + "acquirer_cusip": { + "description": "Buyer's CUSIP.", + "type": "string" + }, + "acquirer_isin": { + "$ref": "#/components/schemas/isin" + }, + "acquirer_rate": { + "description": "Buyer-side ratio, as a decimal string.", + "examples": [ + "0.75" + ], + "type": "string" + }, + "acquirer_symbol": { + "description": "Buyer's ticker (the surviving company).", + "type": "string" + }, + "currency": { + "$ref": "#/components/schemas/currency" + }, + "effective_date": { + "$ref": "#/components/schemas/effective_date" + }, + "payable_date": { + "$ref": "#/components/schemas/payable_date" + } + }, + "required": [ + "acquirer_symbol", + "acquirer_cusip", + "acquirer_rate", + "acquiree_symbol", + "acquiree_cusip", + "acquiree_rate", + "effective_date" + ], + "type": "object" + } + ], + "description": "All-stock merger payload delivered when\n`event_type == stock_merger_corporateaction_event`. Corresponds to\n`stock_mergers` on the REST\n[`GET /v1/corporate-actions`](#operation/CorporateActions) response, but\nevery decimal field is emitted as a JSON string to preserve precision on\nthe wire.\n" + }, + "ca_event_unit_split": { + "allOf": [ + { + "$ref": "#/components/schemas/ca_event_base" + }, + { + "properties": { + "alternate_cusip": { + "description": "CUSIP of the secondary leg.", + "type": "string" + }, + "alternate_isin": { + "$ref": "#/components/schemas/isin" + }, + "alternate_rate": { + "description": "Secondary-leg shares delivered per unit, as a decimal string.", + "examples": [ + "1" + ], + "type": "string" + }, + "alternate_symbol": { + "description": "Ticker of the secondary leg (usually a warrant).", + "type": "string" + }, + "currency": { + "$ref": "#/components/schemas/currency" + }, + "effective_date": { + "$ref": "#/components/schemas/effective_date" + }, + "new_cusip": { + "description": "CUSIP of the primary leg.", + "type": "string" + }, + "new_isin": { + "$ref": "#/components/schemas/isin" + }, + "new_rate": { + "description": "Primary-leg shares delivered per unit, as a decimal string.", + "examples": [ + "1" + ], + "type": "string" + }, + "new_symbol": { + "description": "Ticker of the primary leg (usually the common share).", + "type": "string" + }, + "old_cusip": { + "description": "CUSIP of the unit.", + "type": "string" + }, + "old_isin": { + "$ref": "#/components/schemas/isin" + }, + "old_rate": { + "description": "Units per ratio unit, as a decimal string.", + "examples": [ + "1" + ], + "type": "string" + }, + "old_symbol": { + "description": "Ticker of the unit being split.", + "type": "string" + }, + "payable_date": { + "$ref": "#/components/schemas/payable_date" + } + }, + "required": [ + "old_symbol", + "old_cusip", + "old_rate", + "new_symbol", + "new_cusip", + "new_rate", + "alternate_symbol", + "alternate_cusip", + "alternate_rate", + "effective_date" + ], + "type": "object" + } + ], + "description": "Unit split payload delivered when\n`event_type == unit_split_corporateaction_event`. Corresponds to\n`unit_splits` on the REST\n[`GET /v1/corporate-actions`](#operation/CorporateActions) response, but\nevery decimal field is emitted as a JSON string to preserve precision on\nthe wire.\n" + }, + "ca_event_worthless_removal": { + "allOf": [ + { + "$ref": "#/components/schemas/ca_event_base" + }, + { + "properties": { + "currency": { + "$ref": "#/components/schemas/currency" + }, + "cusip": { + "description": "CUSIP of the security.", + "type": "string" + }, + "isin": { + "$ref": "#/components/schemas/isin" + }, + "symbol": { + "description": "Ticker being removed.", + "type": "string" + } + }, + "required": [ + "symbol", + "cusip" + ], + "type": "object" + } + ], + "description": "Worthless removal payload delivered when\n`event_type == worthless_removal_corporateaction_event`. Corresponds to\n`worthless_removals` on the REST\n[`GET /v1/corporate-actions`](#operation/CorporateActions) response.\n" + }, + "ca_id": { + "description": "The internal Alpaca identifier of the corporate action.", + "format": "uuid", + "type": "string" + }, + "capital_gains_distribution": { + "description": "Capital gains distribution paid by a fund. A single event may carry a long-term\nleg (`long_term_rate`), a short-term leg (`short_term_rate`), or both when\ncombined. At least one of the two rates is always present.\n", + "examples": [ + { + "currency": "USD", + "cusip": "36261K509", + "ex_date": "2025-12-26", + "id": "98c905a1-08c7-450c-8669-b8e7aee8b44d", + "isin": "US36261K5090", + "long_term_rate": 0.45855, + "payable_date": "2026-01-09", + "process_date": "2026-01-08", + "record_date": "2025-12-29", + "short_term_rate": 0.11019, + "symbol": "GCAD" + } + ], + "properties": { + "currency": { + "$ref": "#/components/schemas/currency" + }, + "cusip": { + "type": "string" + }, + "ex_date": { + "$ref": "#/components/schemas/ex_date" + }, + "id": { + "$ref": "#/components/schemas/ca_id" + }, + "isin": { + "$ref": "#/components/schemas/isin" + }, + "long_term_rate": { + "description": "Long-term capital-gain payment per share.", + "format": "double", + "type": "number" + }, + "payable_date": { + "$ref": "#/components/schemas/payable_date" + }, + "process_date": { + "$ref": "#/components/schemas/process_date" + }, + "record_date": { + "$ref": "#/components/schemas/record_date" + }, + "short_term_rate": { + "description": "Short-term capital-gain payment per share.", + "format": "double", + "type": "number" + }, + "symbol": { + "type": "string" + } + }, + "required": [ + "id", + "symbol", + "cusip", + "process_date", + "ex_date" + ], + "type": "object" + }, + "cash_dividend": { + "description": "Cash dividend.", + "examples": [ + { + "cusip": "319829107", + "ex_date": "2023-05-04", + "foreign": false, + "id": "11cfd108-292e-4cc6-bfbf-5999cdbc4029", + "payable_date": "2023-05-19", + "process_date": "2023-05-19", + "rate": 0.125, + "record_date": "2023-05-05", + "special": false, + "symbol": "FCF" + } + ], + "properties": { + "currency": { + "$ref": "#/components/schemas/currency" + }, + "cusip": { + "type": "string" + }, + "due_bill_off_date": { + "format": "date", + "type": "string" + }, + "due_bill_on_date": { + "format": "date", + "type": "string" + }, + "ex_date": { + "$ref": "#/components/schemas/ex_date" + }, + "foreign": { + "type": "boolean" + }, + "id": { + "$ref": "#/components/schemas/ca_id" + }, + "isin": { + "$ref": "#/components/schemas/isin" + }, + "payable_date": { + "$ref": "#/components/schemas/payable_date" + }, + "process_date": { + "$ref": "#/components/schemas/process_date" + }, + "rate": { + "format": "double", + "type": "number" + }, + "record_date": { + "$ref": "#/components/schemas/record_date" + }, + "special": { + "type": "boolean" + }, + "sub_type": { + "description": "Sub-type of the cash dividend.", + "enum": [ + "interest", + "return_of_capital" + ], + "type": "string" + }, + "symbol": { + "type": "string" + } + }, + "required": [ + "id", + "symbol", + "cusip", + "rate", + "special", + "foreign", + "process_date", + "ex_date" + ], + "type": "object" + }, + "cash_merger": { + "description": "Cash merger.", + "examples": [ + { + "acquiree_cusip": "Y2687W108", + "acquiree_symbol": "GLOP", + "effective_date": "2023-07-17", + "id": "3772bbd7-4ad5-44d4-9cc0-f69156a2f8f5", + "payable_date": "2023-07-17", + "process_date": "2023-07-17", + "rate": 5.37 + } + ], + "properties": { + "acquiree_cusip": { + "type": "string" + }, + "acquiree_isin": { + "$ref": "#/components/schemas/isin" + }, + "acquiree_symbol": { + "type": "string" + }, + "acquirer_cusip": { + "type": "string" + }, + "acquirer_isin": { + "$ref": "#/components/schemas/isin" + }, + "acquirer_symbol": { + "type": "string" + }, + "currency": { + "$ref": "#/components/schemas/currency" + }, + "effective_date": { + "$ref": "#/components/schemas/effective_date" + }, + "id": { + "$ref": "#/components/schemas/ca_id" + }, + "payable_date": { + "$ref": "#/components/schemas/payable_date" + }, + "process_date": { + "$ref": "#/components/schemas/process_date" + }, + "rate": { + "format": "double", + "type": "number" + } + }, + "required": [ + "id", + "acquiree_symbol", + "acquiree_cusip", + "rate", + "process_date", + "effective_date" + ], + "type": "object" + }, + "corporate_action_event": { + "description": "A single corporate-action mutation delivered over the\n[Corporate Actions Events Stream](#operation/SubscribeToCorporateActionsEventsSSE).\n\nEvery event uses the same envelope. The `event_type` field selects which of\nthe 15 per-type schemas populates `ca`; see\n[`corporate_action_event_type`](#/components/schemas/corporate_action_event_type)\nfor the full mapping and follow the link on each row to the per-type `ca`\npayload schema.\n\nOptional fields (including `currency` and, on most CA types, `isin`) are\nomitted from the JSON when empty; `null` is never emitted.\n", + "discriminator": { + "mapping": { + "capital_gains_distribution_corporateaction_event": "#/components/schemas/corporate_action_event_capital_gains_distribution", + "cash_dividend_corporateaction_event": "#/components/schemas/corporate_action_event_cash_dividend", + "cash_merger_corporateaction_event": "#/components/schemas/corporate_action_event_cash_merger", + "equity_partial_call_corporateaction_event": "#/components/schemas/corporate_action_event_equity_partial_call", + "forward_split_corporateaction_event": "#/components/schemas/corporate_action_event_forward_split", + "name_change_corporateaction_event": "#/components/schemas/corporate_action_event_name_change", + "redemption_corporateaction_event": "#/components/schemas/corporate_action_event_redemption", + "reorganization_corporateaction_event": "#/components/schemas/corporate_action_event_reorganization", + "reverse_split_corporateaction_event": "#/components/schemas/corporate_action_event_reverse_split", + "rights_distribution_corporateaction_event": "#/components/schemas/corporate_action_event_rights_distribution", + "spin_off_corporateaction_event": "#/components/schemas/corporate_action_event_spin_off", + "stock_and_cash_merger_corporateaction_event": "#/components/schemas/corporate_action_event_stock_and_cash_merger", + "stock_dividend_corporateaction_event": "#/components/schemas/corporate_action_event_stock_dividend", + "stock_merger_corporateaction_event": "#/components/schemas/corporate_action_event_stock_merger", + "unit_split_corporateaction_event": "#/components/schemas/corporate_action_event_unit_split", + "worthless_removal_corporateaction_event": "#/components/schemas/corporate_action_event_worthless_removal" + }, + "propertyName": "event_type" + }, + "oneOf": [ + { + "$ref": "#/components/schemas/corporate_action_event_capital_gains_distribution" + }, + { + "$ref": "#/components/schemas/corporate_action_event_cash_dividend" + }, + { + "$ref": "#/components/schemas/corporate_action_event_cash_merger" + }, + { + "$ref": "#/components/schemas/corporate_action_event_equity_partial_call" + }, + { + "$ref": "#/components/schemas/corporate_action_event_forward_split" + }, + { + "$ref": "#/components/schemas/corporate_action_event_name_change" + }, + { + "$ref": "#/components/schemas/corporate_action_event_redemption" + }, + { + "$ref": "#/components/schemas/corporate_action_event_reorganization" + }, + { + "$ref": "#/components/schemas/corporate_action_event_reverse_split" + }, + { + "$ref": "#/components/schemas/corporate_action_event_rights_distribution" + }, + { + "$ref": "#/components/schemas/corporate_action_event_spin_off" + }, + { + "$ref": "#/components/schemas/corporate_action_event_stock_and_cash_merger" + }, + { + "$ref": "#/components/schemas/corporate_action_event_stock_dividend" + }, + { + "$ref": "#/components/schemas/corporate_action_event_stock_merger" + }, + { + "$ref": "#/components/schemas/corporate_action_event_unit_split" + }, + { + "$ref": "#/components/schemas/corporate_action_event_worthless_removal" + } + ] + }, + "corporate_action_event_action": { + "description": "Kind of mutation that produced this event on the upstream corporate actions\nstore:\n\n- `insert`: a new corporate action was created.\n- `update`: an existing corporate action was modified (e.g. a date or rate\n correction). `ca.id` matches the original event.\n- `delete`: a previously published corporate action was removed. `ca.id`\n matches the original event; subsequent events for the same id (if any)\n will be new inserts.\n", + "enum": [ + "insert", + "update", + "delete" + ], + "type": "string" + }, + "corporate_action_event_base": { + "description": "Common envelope fields shared by every variant of\n[`corporate_action_event`](#/components/schemas/corporate_action_event).\nThis schema is not meant to be used directly by clients -- it exists so each\nper-`event_type` `oneOf` branch can `allOf`-compose the envelope basics\n(`event_id`, `at`, `action`, `region`) alongside its narrowed `event_type` /\n`ca` pair. Keeping these in one place is what makes the envelope's\n`discriminator` play nicely with strict OpenAPI validators.\n", + "properties": { + "action": { + "$ref": "#/components/schemas/corporate_action_event_action" + }, + "at": { + "description": "RFC-3339 timestamp when the streaming service emitted this event.", + "examples": [ + "2026-03-20T12:24:58.807230Z" + ], + "format": "date-time", + "type": "string" + }, + "event_id": { + "$ref": "#/components/schemas/event_id" + }, + "region": { + "$ref": "#/components/schemas/corporate_action_event_region" + } + }, + "required": [ + "event_id", + "at", + "action", + "region" + ], + "type": "object" + }, + "corporate_action_event_capital_gains_distribution": { + "allOf": [ + { + "$ref": "#/components/schemas/corporate_action_event_base" + }, + { + "properties": { + "ca": { + "$ref": "#/components/schemas/ca_event_capital_gains_distribution" + }, + "event_type": { + "enum": [ + "capital_gains_distribution_corporateaction_event" + ], + "type": "string" + } + }, + "required": [ + "event_type", + "ca" + ], + "type": "object" + } + ], + "description": "`corporate_action_event` envelope specialised to `event_type ==\ncapital_gains_distribution_corporateaction_event`. Emitted through\n[Corporate Actions Events Stream](#operation/SubscribeToCorporateActionsEventsSSE).\n" + }, + "corporate_action_event_cash_dividend": { + "allOf": [ + { + "$ref": "#/components/schemas/corporate_action_event_base" + }, + { + "properties": { + "ca": { + "$ref": "#/components/schemas/ca_event_cash_dividend" + }, + "event_type": { + "enum": [ + "cash_dividend_corporateaction_event" + ], + "type": "string" + } + }, + "required": [ + "event_type", + "ca" + ], + "type": "object" + } + ], + "description": "`corporate_action_event` envelope specialised to `event_type ==\ncash_dividend_corporateaction_event`. Emitted through\n[Corporate Actions Events Stream](#operation/SubscribeToCorporateActionsEventsSSE).\n" + }, + "corporate_action_event_cash_merger": { + "allOf": [ + { + "$ref": "#/components/schemas/corporate_action_event_base" + }, + { + "properties": { + "ca": { + "$ref": "#/components/schemas/ca_event_cash_merger" + }, + "event_type": { + "enum": [ + "cash_merger_corporateaction_event" + ], + "type": "string" + } + }, + "required": [ + "event_type", + "ca" + ], + "type": "object" + } + ], + "description": "`corporate_action_event` envelope specialised to `event_type ==\ncash_merger_corporateaction_event`. Emitted through\n[Corporate Actions Events Stream](#operation/SubscribeToCorporateActionsEventsSSE).\n" + }, + "corporate_action_event_equity_partial_call": { + "allOf": [ + { + "$ref": "#/components/schemas/corporate_action_event_base" + }, + { + "properties": { + "ca": { + "$ref": "#/components/schemas/ca_event_equity_partial_call" + }, + "event_type": { + "enum": [ + "equity_partial_call_corporateaction_event" + ], + "type": "string" + } + }, + "required": [ + "event_type", + "ca" + ], + "type": "object" + } + ], + "description": "`corporate_action_event` envelope specialised to `event_type ==\nequity_partial_call_corporateaction_event`. Emitted through\n[Corporate Actions Events Stream](#operation/SubscribeToCorporateActionsEventsSSE).\n" + }, + "corporate_action_event_forward_split": { + "allOf": [ + { + "$ref": "#/components/schemas/corporate_action_event_base" + }, + { + "properties": { + "ca": { + "$ref": "#/components/schemas/ca_event_forward_split" + }, + "event_type": { + "enum": [ + "forward_split_corporateaction_event" + ], + "type": "string" + } + }, + "required": [ + "event_type", + "ca" + ], + "type": "object" + } + ], + "description": "`corporate_action_event` envelope specialised to `event_type ==\nforward_split_corporateaction_event`. Emitted through\n[Corporate Actions Events Stream](#operation/SubscribeToCorporateActionsEventsSSE).\n" + }, + "corporate_action_event_name_change": { + "allOf": [ + { + "$ref": "#/components/schemas/corporate_action_event_base" + }, + { + "properties": { + "ca": { + "$ref": "#/components/schemas/ca_event_name_change" + }, + "event_type": { + "enum": [ + "name_change_corporateaction_event" + ], + "type": "string" + } + }, + "required": [ + "event_type", + "ca" + ], + "type": "object" + } + ], + "description": "`corporate_action_event` envelope specialised to `event_type ==\nname_change_corporateaction_event`. Emitted through\n[Corporate Actions Events Stream](#operation/SubscribeToCorporateActionsEventsSSE).\n" + }, + "corporate_action_event_redemption": { + "allOf": [ + { + "$ref": "#/components/schemas/corporate_action_event_base" + }, + { + "properties": { + "ca": { + "$ref": "#/components/schemas/ca_event_redemption" + }, + "event_type": { + "enum": [ + "redemption_corporateaction_event" + ], + "type": "string" + } + }, + "required": [ + "event_type", + "ca" + ], + "type": "object" + } + ], + "description": "`corporate_action_event` envelope specialised to `event_type ==\nredemption_corporateaction_event`. Emitted through\n[Corporate Actions Events Stream](#operation/SubscribeToCorporateActionsEventsSSE).\n" + }, + "corporate_action_event_region": { + "description": "Envelope-level classification derived from `metadata.global` on the upstream\ncorporate-action record. This is the only field the SSE `region` filter\ninspects on each event.\n\n- `us`: US-listed / US-regulated corporate action.\n- `non_us`: everything else.\n", + "enum": [ + "us", + "non_us" + ], + "type": "string" + }, + "corporate_action_event_reorganization": { + "allOf": [ + { + "$ref": "#/components/schemas/corporate_action_event_base" + }, + { + "properties": { + "ca": { + "$ref": "#/components/schemas/ca_event_reorganization" + }, + "event_type": { + "enum": [ + "reorganization_corporateaction_event" + ], + "type": "string" + } }, - "type": "array", - "x-go-type-skip-optional-pointer": true + "required": [ + "event_type", + "ca" + ], + "type": "object" + } + ], + "description": "`corporate_action_event` envelope specialised to `event_type ==\nreorganization_corporateaction_event`. Emitted through\n[Corporate Actions Events Stream](#operation/SubscribeToCorporateActionsEventsSSE).\n" + }, + "corporate_action_event_reverse_split": { + "allOf": [ + { + "$ref": "#/components/schemas/corporate_action_event_base" + }, + { + "properties": { + "ca": { + "$ref": "#/components/schemas/ca_event_reverse_split" + }, + "event_type": { + "enum": [ + "reverse_split_corporateaction_event" + ], + "type": "string" + } + }, + "required": [ + "event_type", + "ca" + ], + "type": "object" + } + ], + "description": "`corporate_action_event` envelope specialised to `event_type ==\nreverse_split_corporateaction_event`. Emitted through\n[Corporate Actions Events Stream](#operation/SubscribeToCorporateActionsEventsSSE).\n" + }, + "corporate_action_event_rights_distribution": { + "allOf": [ + { + "$ref": "#/components/schemas/corporate_action_event_base" + }, + { + "properties": { + "ca": { + "$ref": "#/components/schemas/ca_event_rights_distribution" + }, + "event_type": { + "enum": [ + "rights_distribution_corporateaction_event" + ], + "type": "string" + } + }, + "required": [ + "event_type", + "ca" + ], + "type": "object" + } + ], + "description": "`corporate_action_event` envelope specialised to `event_type ==\nrights_distribution_corporateaction_event`. Emitted through\n[Corporate Actions Events Stream](#operation/SubscribeToCorporateActionsEventsSSE).\n" + }, + "corporate_action_event_spin_off": { + "allOf": [ + { + "$ref": "#/components/schemas/corporate_action_event_base" + }, + { + "properties": { + "ca": { + "$ref": "#/components/schemas/ca_event_spin_off" + }, + "event_type": { + "enum": [ + "spin_off_corporateaction_event" + ], + "type": "string" + } + }, + "required": [ + "event_type", + "ca" + ], + "type": "object" + } + ], + "description": "`corporate_action_event` envelope specialised to `event_type ==\nspin_off_corporateaction_event`. Emitted through\n[Corporate Actions Events Stream](#operation/SubscribeToCorporateActionsEventsSSE).\n" + }, + "corporate_action_event_stock_and_cash_merger": { + "allOf": [ + { + "$ref": "#/components/schemas/corporate_action_event_base" + }, + { + "properties": { + "ca": { + "$ref": "#/components/schemas/ca_event_stock_and_cash_merger" + }, + "event_type": { + "enum": [ + "stock_and_cash_merger_corporateaction_event" + ], + "type": "string" + } + }, + "required": [ + "event_type", + "ca" + ], + "type": "object" + } + ], + "description": "`corporate_action_event` envelope specialised to `event_type ==\nstock_and_cash_merger_corporateaction_event`. Emitted through\n[Corporate Actions Events Stream](#operation/SubscribeToCorporateActionsEventsSSE).\n" + }, + "corporate_action_event_stock_dividend": { + "allOf": [ + { + "$ref": "#/components/schemas/corporate_action_event_base" + }, + { + "properties": { + "ca": { + "$ref": "#/components/schemas/ca_event_stock_dividend" + }, + "event_type": { + "enum": [ + "stock_dividend_corporateaction_event" + ], + "type": "string" + } + }, + "required": [ + "event_type", + "ca" + ], + "type": "object" + } + ], + "description": "`corporate_action_event` envelope specialised to `event_type ==\nstock_dividend_corporateaction_event`. Emitted through\n[Corporate Actions Events Stream](#operation/SubscribeToCorporateActionsEventsSSE).\n" + }, + "corporate_action_event_stock_merger": { + "allOf": [ + { + "$ref": "#/components/schemas/corporate_action_event_base" + }, + { + "properties": { + "ca": { + "$ref": "#/components/schemas/ca_event_stock_merger" + }, + "event_type": { + "enum": [ + "stock_merger_corporateaction_event" + ], + "type": "string" + } + }, + "required": [ + "event_type", + "ca" + ], + "type": "object" + } + ], + "description": "`corporate_action_event` envelope specialised to `event_type ==\nstock_merger_corporateaction_event`. Emitted through\n[Corporate Actions Events Stream](#operation/SubscribeToCorporateActionsEventsSSE).\n" + }, + "corporate_action_event_type": { + "description": "Discriminator that determines the shape of the `ca` field on a\n[`corporate_action_event`](#/components/schemas/corporate_action_event).\n\nEach value corresponds to a per-type payload schema:\n\n| `event_type` | `ca` schema |\n| --- | --- |\n| `capital_gains_distribution_corporateaction_event` | [`ca_event_capital_gains_distribution`](#/components/schemas/ca_event_capital_gains_distribution) |\n| `cash_dividend_corporateaction_event` | [`ca_event_cash_dividend`](#/components/schemas/ca_event_cash_dividend) |\n| `cash_merger_corporateaction_event` | [`ca_event_cash_merger`](#/components/schemas/ca_event_cash_merger) |\n| `equity_partial_call_corporateaction_event` | [`ca_event_equity_partial_call`](#/components/schemas/ca_event_equity_partial_call) |\n| `forward_split_corporateaction_event` | [`ca_event_forward_split`](#/components/schemas/ca_event_forward_split) |\n| `name_change_corporateaction_event` | [`ca_event_name_change`](#/components/schemas/ca_event_name_change) |\n| `redemption_corporateaction_event` | [`ca_event_redemption`](#/components/schemas/ca_event_redemption) |\n| `reorganization_corporateaction_event` | [`ca_event_reorganization`](#/components/schemas/ca_event_reorganization) |\n| `reverse_split_corporateaction_event` | [`ca_event_reverse_split`](#/components/schemas/ca_event_reverse_split) |\n| `rights_distribution_corporateaction_event` | [`ca_event_rights_distribution`](#/components/schemas/ca_event_rights_distribution) |\n| `spin_off_corporateaction_event` | [`ca_event_spin_off`](#/components/schemas/ca_event_spin_off) |\n| `stock_and_cash_merger_corporateaction_event` | [`ca_event_stock_and_cash_merger`](#/components/schemas/ca_event_stock_and_cash_merger) |\n| `stock_dividend_corporateaction_event` | [`ca_event_stock_dividend`](#/components/schemas/ca_event_stock_dividend) |\n| `stock_merger_corporateaction_event` | [`ca_event_stock_merger`](#/components/schemas/ca_event_stock_merger) |\n| `unit_split_corporateaction_event` | [`ca_event_unit_split`](#/components/schemas/ca_event_unit_split) |\n| `worthless_removal_corporateaction_event` | [`ca_event_worthless_removal`](#/components/schemas/ca_event_worthless_removal) |\n", + "enum": [ + "capital_gains_distribution_corporateaction_event", + "cash_dividend_corporateaction_event", + "cash_merger_corporateaction_event", + "equity_partial_call_corporateaction_event", + "forward_split_corporateaction_event", + "name_change_corporateaction_event", + "redemption_corporateaction_event", + "reorganization_corporateaction_event", + "reverse_split_corporateaction_event", + "rights_distribution_corporateaction_event", + "spin_off_corporateaction_event", + "stock_and_cash_merger_corporateaction_event", + "stock_dividend_corporateaction_event", + "stock_merger_corporateaction_event", + "unit_split_corporateaction_event", + "worthless_removal_corporateaction_event" + ], + "type": "string" + }, + "corporate_action_event_unit_split": { + "allOf": [ + { + "$ref": "#/components/schemas/corporate_action_event_base" + }, + { + "properties": { + "ca": { + "$ref": "#/components/schemas/ca_event_unit_split" + }, + "event_type": { + "enum": [ + "unit_split_corporateaction_event" + ], + "type": "string" + } + }, + "required": [ + "event_type", + "ca" + ], + "type": "object" + } + ], + "description": "`corporate_action_event` envelope specialised to `event_type ==\nunit_split_corporateaction_event`. Emitted through\n[Corporate Actions Events Stream](#operation/SubscribeToCorporateActionsEventsSSE).\n" + }, + "corporate_action_event_worthless_removal": { + "allOf": [ + { + "$ref": "#/components/schemas/corporate_action_event_base" + }, + { + "properties": { + "ca": { + "$ref": "#/components/schemas/ca_event_worthless_removal" + }, + "event_type": { + "enum": [ + "worthless_removal_corporateaction_event" + ], + "type": "string" + } + }, + "required": [ + "event_type", + "ca" + ], + "type": "object" + } + ], + "description": "`corporate_action_event` envelope specialised to `event_type ==\nworthless_removal_corporateaction_event`. Emitted through\n[Corporate Actions Events Stream](#operation/SubscribeToCorporateActionsEventsSSE).\n" + }, + "corporate_actions": { + "properties": { + "capital_gains_distributions": { + "items": { + "$ref": "#/components/schemas/capital_gains_distribution" + }, + "type": "array" + }, + "cash_dividends": { + "items": { + "$ref": "#/components/schemas/cash_dividend" + }, + "type": "array" + }, + "cash_mergers": { + "items": { + "$ref": "#/components/schemas/cash_merger" + }, + "type": "array" + }, + "forward_splits": { + "items": { + "$ref": "#/components/schemas/forward_split" + }, + "type": "array" + }, + "name_changes": { + "items": { + "$ref": "#/components/schemas/name_change" + }, + "type": "array" + }, + "partial_calls": { + "items": { + "$ref": "#/components/schemas/partial_call" + }, + "type": "array" }, "redemptions": { "items": { "$ref": "#/components/schemas/redemption" }, - "type": "array", - "x-go-type-skip-optional-pointer": true + "type": "array" }, "reorganizations": { "items": { "$ref": "#/components/schemas/reorganization" }, - "type": "array", - "x-go-type-skip-optional-pointer": true + "type": "array" }, "reverse_splits": { "items": { "$ref": "#/components/schemas/reverse_split" }, - "type": "array", - "x-go-type-skip-optional-pointer": true + "type": "array" }, "rights_distributions": { "items": { "$ref": "#/components/schemas/rights_distribution" }, - "type": "array", - "x-go-type-skip-optional-pointer": true + "type": "array" }, "spin_offs": { "items": { "$ref": "#/components/schemas/spin_off" }, - "type": "array", - "x-go-type-skip-optional-pointer": true + "type": "array" }, "stock_and_cash_mergers": { "items": { "$ref": "#/components/schemas/stock_and_cash_merger" }, - "type": "array", - "x-go-type-skip-optional-pointer": true + "type": "array" }, "stock_dividends": { "items": { "$ref": "#/components/schemas/stock_dividend" }, - "type": "array", - "x-go-type-skip-optional-pointer": true + "type": "array" }, "stock_mergers": { "items": { "$ref": "#/components/schemas/stock_merger" }, - "type": "array", - "x-go-type-skip-optional-pointer": true + "type": "array" }, "unit_splits": { "items": { "$ref": "#/components/schemas/unit_split" }, - "type": "array", - "x-go-type-skip-optional-pointer": true + "type": "array" }, "worthless_removals": { "items": { "$ref": "#/components/schemas/worthless_removal" }, - "type": "array", - "x-go-type-skip-optional-pointer": true + "type": "array" } }, "type": "object" @@ -800,46 +2501,43 @@ }, "crypto_bar": { "description": "OHLC aggregate of all the trades in a given interval.", - "example": { - "c": 29003, - "h": 29003, - "l": 28999, - "n": 4, - "o": 28999, - "t": "2022-05-27T10:18:00Z", - "v": 0.01, - "vw": 29001 - }, + "examples": [ + { + "c": 29003, + "h": 29003, + "l": 28999, + "n": 4, + "o": 28999, + "t": "2022-05-27T10:18:00Z", + "v": 0.01, + "vw": 29001 + } + ], "properties": { "c": { "description": "Closing price.", "format": "double", - "type": "number", - "x-go-name": "Close" + "type": "number" }, "h": { "description": "High price.", "format": "double", - "type": "number", - "x-go-name": "High" + "type": "number" }, "l": { "description": "Low price.", "format": "double", - "type": "number", - "x-go-name": "Low" + "type": "number" }, "n": { "description": "Trade count in the bar.", "format": "int64", - "type": "integer", - "x-go-name": "TradeCount" + "type": "integer" }, "o": { "description": "Opening price.", "format": "double", - "type": "number", - "x-go-name": "Open" + "type": "number" }, "t": { "$ref": "#/components/schemas/timestamp" @@ -847,14 +2545,12 @@ "v": { "description": "Bar volume.", "format": "double", - "type": "number", - "x-go-name": "Volume" + "type": "number" }, "vw": { "description": "Volume weighted average price.", "format": "double", - "type": "number", - "x-go-name": "VWAP" + "type": "number" } }, "required": [ @@ -899,8 +2595,7 @@ "eu-1", "bs-1" ], - "type": "string", - "x-go-name": "TypeHistoricalLoc" + "type": "string" }, "crypto_latest_bars_resp": { "properties": { @@ -925,8 +2620,7 @@ "eu-1", "bs-1" ], - "type": "string", - "x-go-name": "TypeLatestLoc" + "type": "string" }, "crypto_latest_orderbooks_resp": { "properties": { @@ -972,43 +2666,43 @@ }, "crypto_orderbook": { "description": "Snapshot of the orderbook.", - "example": { - "a": [ - { - "p": 20902, - "s": 0.0097 - }, - { - "p": 21444, - "s": 0 - } - ], - "b": [ - { - "p": 20846, - "s": 0.1902 - }, - { - "p": 20350, - "s": 0 - } - ], - "t": "2022-06-24T08:00:14.137774336Z" - }, + "examples": [ + { + "a": [ + { + "p": 20902, + "s": 0.0097 + }, + { + "p": 21444, + "s": 0 + } + ], + "b": [ + { + "p": 20846, + "s": 0.1902 + }, + { + "p": 20350, + "s": 0 + } + ], + "t": "2022-06-24T08:00:14.137774336Z" + } + ], "properties": { "a": { "items": { "$ref": "#/components/schemas/crypto_orderbook_entry" }, - "type": "array", - "x-go-name": "Asks" + "type": "array" }, "b": { "items": { "$ref": "#/components/schemas/crypto_orderbook_entry" }, - "type": "array", - "x-go-name": "Bids" + "type": "array" }, "t": { "$ref": "#/components/schemas/timestamp" @@ -1023,22 +2717,22 @@ }, "crypto_orderbook_entry": { "description": "A single entry in a crypto orderbook.", - "example": { - "p": 20846, - "s": 0.1902 - }, + "examples": [ + { + "p": 20846, + "s": 0.1902 + } + ], "properties": { "p": { "description": "Price.", "format": "double", - "type": "number", - "x-go-name": "Price" + "type": "number" }, "s": { "description": "Size.", "format": "double", - "type": "number", - "x-go-name": "Size" + "type": "number" } }, "required": [ @@ -1047,116 +2741,37 @@ ], "type": "object" }, - "crypto_perp_futures_pricing": { - "description": "Crypto perpetual futures pricing data.", - "example": { - "fr": 0.000565699, - "ft": "2022-05-27T10:18:00Z", - "ip": 50702.8, - "mp": 50652.3553, - "oi": 90.7367, - "t": "2022-05-27T10:18:00Z" - }, - "properties": { - "fr": { - "description": "Funding rate.", - "format": "double", - "type": "number", - "x-go-name": "FundingRate" - }, - "ft": { - "description": "Next funding time", - "format": "date-time", - "type": "string", - "x-go-name": "NextFundingTime" - }, - "ip": { - "description": "Index price.", - "format": "double", - "type": "number", - "x-go-name": "IndexPrice" - }, - "mp": { - "description": "Mark price.", - "format": "double", - "type": "number", - "x-go-name": "MarkPrice" - }, - "oi": { - "description": "Open interest.", - "format": "double", - "type": "number", - "x-go-name": "OpenInterest" - }, - "t": { - "$ref": "#/components/schemas/timestamp" - } - }, - "required": [ - "t", - "ft", - "oi", - "ip", - "mp", - "fr" - ], - "type": "object" - }, - "crypto_perp_latest_futures_pricing_resp": { - "properties": { - "pricing": { - "additionalProperties": { - "$ref": "#/components/schemas/crypto_perp_futures_pricing" - }, - "type": "object" - } - }, - "required": [ - "pricing" - ], - "type": "object" - }, - "crypto_perp_loc": { - "description": "Crypto perpetual location.", - "enum": [ - "global" - ], - "type": "string", - "x-go-name": "TypePerpLoc" - }, "crypto_quote": { "description": "The best bid and ask information for a given security.", - "example": { - "ap": 29059, - "as": 3.252, - "bp": 29058, - "bs": 0.3544, - "t": "2022-05-26T11:47:18.44347136Z" - }, + "examples": [ + { + "ap": 29059, + "as": 3.252, + "bp": 29058, + "bs": 0.3544, + "t": "2022-05-26T11:47:18.44347136Z" + } + ], "properties": { "ap": { "description": "Ask price.", "format": "double", - "type": "number", - "x-go-name": "AskPrice" + "type": "number" }, "as": { "description": "Ask size.", "format": "double", - "type": "number", - "x-go-name": "AskSize" + "type": "number" }, "bp": { "description": "Bid price.", "format": "double", - "type": "number", - "x-go-name": "BidPrice" + "type": "number" }, "bs": { "description": "Bid size.", "format": "double", - "type": "number", - "x-go-name": "BidSize" + "type": "number" }, "t": { "$ref": "#/components/schemas/timestamp" @@ -1229,39 +2844,37 @@ }, "crypto_trade": { "description": "A crypto trade.", - "example": { - "i": 31455277, - "p": 29798, - "s": 0.1209, - "t": "2022-05-18T12:00:05.225055Z", - "tks": "S" - }, + "examples": [ + { + "i": 31455277, + "p": 29798, + "s": 0.1209, + "t": "2022-05-18T12:00:05.225055Z", + "tks": "S" + } + ], "properties": { "i": { "description": "Trade ID.", "format": "int64", - "type": "integer", - "x-go-name": "ID" + "type": "integer" }, "p": { "description": "Trade price.", "format": "double", - "type": "number", - "x-go-name": "Price" + "type": "number" }, "s": { "description": "Trade size.", "format": "double", - "type": "number", - "x-go-name": "Size" + "type": "number" }, "t": { "$ref": "#/components/schemas/timestamp" }, "tks": { "description": "Taker side: B for buyer, S for seller\n", - "type": "string", - "x-go-name": "TakerSide" + "type": "string" } }, "required": [ @@ -1298,11 +2911,19 @@ "description": "The ISO 4217 currency code associated with the corporate action.\nEmpty value can mean USD, non-applicable (e.g. for name changes) or unknown\n(can change later to a valid currency).\n", "type": "string" }, + "data_quality": { + "default": "complete", + "description": "Controls which corporate actions are returned based on data quality.\n\n- `complete` (default): exclude corporate actions that are still missing required\n fields (for example, ex-date or CUSIP/ISIN) and have not yet been processed.\n Already-processed corporate actions are always included, even if they would\n otherwise be considered incomplete.\n- `all`: return matching corporate actions regardless of field completeness.\n", + "enum": [ + "complete", + "all" + ], + "type": "string" + }, "date": { "description": "Date in RFC-3339.", "format": "date", - "type": "string", - "x-go-name": "Date" + "type": "string" }, "due_bill_redemption_date": { "description": "The date when due bill obligations are redeemed.", @@ -1314,6 +2935,15 @@ "format": "date", "type": "string" }, + "event_id": { + "description": "Lexically sortable, monotonically increasing 26-character\n[ULID](https://github.com/ulid/spec) (Crockford Base32, uppercase) that\nidentifies a single SSE emission. Unique per message -- an `update` or\n`delete` for the same underlying corporate action carries a fresh\n`event_id`. Because ULIDs sort in emission order, they can be used as\nresume cursors via `since_id`, `until_id`, or the standard `Last-Event-Id`\nreconnect header.\n", + "examples": [ + "01J9RPMV5TKB8WX3M4F1KZ7QH2" + ], + "format": "ulid", + "pattern": "^[0-7][0-9A-HJKMNP-TV-Z]{25}$", + "type": "string" + }, "ex_date": { "description": "The ex-date marks the cutoff point for shareholders to be credited.", "format": "date", @@ -1324,6 +2954,19 @@ "type": "string" }, "fixed_income_latest_prices_resp": { + "description": "Latest fixed-income prices keyed by ISIN.", + "examples": [ + { + "prices": { + "US912797KJ59": { + "p": 99.6459, + "t": "2025-02-14T20:58:00.648Z", + "ytm": 4.249, + "ytw": 4.249 + } + } + } + ], "properties": { "prices": { "additionalProperties": { @@ -1338,6 +2981,26 @@ "type": "object" }, "fixed_income_latest_quotes_resp": { + "description": "Latest fixed-income best bid and ask quotes keyed by ISIN.", + "examples": [ + { + "quotes": { + "US912797SX61": { + "ams": 1000, + "ap": 99.91958333, + "as": 1000000, + "aytm": 2.226923, + "aytw": 2.226923, + "bms": 1000, + "bp": 99.81091667, + "bs": 1000000, + "bytm": 5.236154, + "bytw": 5.236154, + "t": "2026-05-21T06:56:01.882466873Z" + } + } + } + ], "properties": { "quotes": { "additionalProperties": { @@ -1353,18 +3016,19 @@ }, "fixed_income_price": { "description": "The price of the instrument as a percentage of its par value.", - "example": { - "p": 99.6459, - "t": "2025-02-14T20:58:00.648Z", - "ytm": 4.249, - "ytw": 4.249 - }, + "examples": [ + { + "p": 99.6459, + "t": "2025-02-14T20:58:00.648Z", + "ytm": 4.249, + "ytw": 4.249 + } + ], "properties": { "p": { "description": "Price", "format": "double", - "type": "number", - "x-go-name": "Price" + "type": "number" }, "t": { "$ref": "#/components/schemas/timestamp" @@ -1372,14 +3036,12 @@ "ytm": { "description": "Yield to maturity.", "format": "double", - "type": "number", - "x-go-name": "YieldToMaturity" + "type": "number" }, "ytw": { "description": "Yield to worst.", "format": "double", - "type": "number", - "x-go-name": "YieldToWorst" + "type": "number" } }, "required": [ @@ -1390,79 +3052,71 @@ }, "fixed_income_quote": { "description": "The best bid and ask information for a given fixed income security. A value of 0 means there is no active bid or ask for that field.\n", - "example": { - "ams": 1000, - "ap": 99.91958333, - "as": 1000000, - "aytm": 2.226923, - "aytw": 2.226923, - "bms": 1000, - "bp": 99.81091667, - "bs": 1000000, - "bytm": 5.236154, - "bytw": 5.236154, - "t": "2026-05-21T06:56:01.882466873Z" - }, + "examples": [ + { + "ams": 1000, + "ap": 99.91958333, + "as": 1000000, + "aytm": 2.226923, + "aytw": 2.226923, + "bms": 1000, + "bp": 99.81091667, + "bs": 1000000, + "bytm": 5.236154, + "bytw": 5.236154, + "t": "2026-05-21T06:56:01.882466873Z" + } + ], "properties": { "ams": { "description": "Best ask minimum trade size in par value.", "format": "int64", - "type": "integer", - "x-go-name": "AskMinTradeSize" + "type": "integer" }, "ap": { "description": "Best ask price. 0 means there is no active ask.", "format": "double", - "type": "number", - "x-go-name": "AskPrice" + "type": "number" }, "as": { "description": "Best ask size in par value. 0 means there is no active ask.", "format": "int64", - "type": "integer", - "x-go-name": "AskSize" + "type": "integer" }, "aytm": { "description": "Best ask yield to maturity.", "format": "double", - "type": "number", - "x-go-name": "AskYieldToMaturity" + "type": "number" }, "aytw": { "description": "Best ask yield to worst.", "format": "double", - "type": "number", - "x-go-name": "AskYieldToWorst" + "type": "number" }, "bms": { "description": "Best bid minimum trade size in par value.", "format": "int64", - "type": "integer", - "x-go-name": "BidMinTradeSize" + "type": "integer" }, "bp": { "description": "Best bid price. 0 means there is no active bid.", "format": "double", - "type": "number", - "x-go-name": "BidPrice" + "type": "number" }, "bs": { "description": "Best bid size in par value. 0 means there is no active bid.", "format": "int64", - "type": "integer", - "x-go-name": "BidSize" + "type": "integer" }, "bytm": { "description": "Best bid yield to maturity.", "format": "double", - "type": "number", - "x-go-name": "BidYieldToMaturity" + "type": "number" }, "bytw": { "description": "Best bid yield to worst.", "format": "double", - "type": "number", - "x-go-name": "BidYieldToWorst" + "type": "number" }, "t": { "$ref": "#/components/schemas/timestamp" @@ -1485,9 +3139,11 @@ }, "forex_currency_pairs": { "description": "A comma-separated string with currency pairs.", - "example": "USDJPY,USDMXN", - "type": "string", - "x-go-name": "TypeCurrencyPairs" + "examples": [ + "USDJPY", + "USDMXN" + ], + "type": "string" }, "forex_latest_rates_resp": { "description": "The response object of the latest forex rates.", @@ -1506,36 +3162,34 @@ }, "forex_rate": { "description": "A foreign exchange rate between two currencies at a given time.", - "example": { - "ap": 127.763, - "bp": 127.702, - "mp": 127.757, - "t": "2022-04-20T18:23:00Z" - }, + "examples": [ + { + "ap": 127.763, + "bp": 127.702, + "mp": 127.757, + "t": "2022-04-20T18:23:00Z" + } + ], "properties": { "ap": { "description": "The last ask price value of the currency at the end of the timeframe.", "format": "double", - "type": "number", - "x-go-name": "AskPrice" + "type": "number" }, "bp": { "description": "The last bid price value of the currency at the end of the timeframe.", "format": "double", - "type": "number", - "x-go-name": "BidPrice" + "type": "number" }, "mp": { "description": "The last mid price value of the currency at the end of the timeframe.", "format": "double", - "type": "number", - "x-go-name": "MidPrice" + "type": "number" }, "t": { "description": "Timestamp of the rate.", "format": "date-time", - "type": "string", - "x-go-name": "Timestamp" + "type": "string" } }, "required": [ @@ -1574,18 +3228,20 @@ }, "forward_split": { "description": "Forward split.", - "example": { - "cusip": "816851109", - "due_bill_redemption_date": "2023-08-23", - "ex_date": "2023-08-22", - "id": "189bd849-ab9f-4b4d-aaaa-a6d415fd976d", - "new_rate": 2, - "old_rate": 1, - "payable_date": "2023-08-21", - "process_date": "2023-08-22", - "record_date": "2023-08-14", - "symbol": "SRE" - }, + "examples": [ + { + "cusip": "816851109", + "due_bill_redemption_date": "2023-08-23", + "ex_date": "2023-08-22", + "id": "189bd849-ab9f-4b4d-aaaa-a6d415fd976d", + "new_rate": 2, + "old_rate": 1, + "payable_date": "2023-08-21", + "process_date": "2023-08-22", + "record_date": "2023-08-14", + "symbol": "SRE" + } + ], "properties": { "currency": { "$ref": "#/components/schemas/currency" @@ -1637,65 +3293,6 @@ ], "type": "object" }, - "index_latest_values_resp": { - "description": "The response object of the latest index values.", - "properties": { - "values": { - "additionalProperties": { - "$ref": "#/components/schemas/index_value" - }, - "type": "object" - } - }, - "required": [ - "values" - ], - "type": "object" - }, - "index_value": { - "description": "The value of an index at a given point in time.", - "example": { - "t": "2026-05-18T17:00:02.0256Z", - "v": 7383.46 - }, - "properties": { - "t": { - "$ref": "#/components/schemas/timestamp" - }, - "v": { - "description": "Index value.", - "format": "double", - "type": "number", - "x-go-name": "Value" - } - }, - "required": [ - "t", - "v" - ], - "type": "object" - }, - "index_values_resp": { - "properties": { - "next_page_token": { - "$ref": "#/components/schemas/next_page_token" - }, - "values": { - "additionalProperties": { - "items": { - "$ref": "#/components/schemas/index_value" - }, - "type": "array" - }, - "type": "object" - } - }, - "required": [ - "values", - "next_page_token" - ], - "type": "object" - }, "isin": { "description": "International Securities Identification Number (ISIN) as defined by ISO 6166.\nMay be empty for US corporate actions.\n", "type": "string" @@ -1710,11 +3307,13 @@ }, "most_active": { "description": "A stock that is most active by either volume or trade count.", - "example": { - "symbol": "AAPL", - "trade_count": 639626, - "volume": 122709184 - }, + "examples": [ + { + "symbol": "AAPL", + "trade_count": 639626, + "volume": 122709184 + } + ], "properties": { "symbol": { "type": "string" @@ -1759,12 +3358,14 @@ }, "mover": { "description": "A symbol whose price moved significantly.", - "example": { - "change": 2.46, - "percent_change": 145.56, - "price": 4.15, - "symbol": "AGRI" - }, + "examples": [ + { + "change": 2.46, + "percent_change": 145.56, + "price": 4.15, + "symbol": "AGRI" + } + ], "properties": { "change": { "description": "Difference in change for the day.", @@ -1830,14 +3431,16 @@ }, "name_change": { "description": "Name change.", - "example": { - "id": "5a774c35-edec-4532-a812-a56d0bbb623a", - "new_cusip": "Y9390M103", - "new_symbol": "VFS", - "old_cusip": "G11537100", - "old_symbol": "BSAQ", - "process_date": "2023-08-15" - }, + "examples": [ + { + "id": "5a774c35-edec-4532-a812-a56d0bbb623a", + "new_cusip": "Y9390M103", + "new_symbol": "VFS", + "old_cusip": "G11537100", + "old_symbol": "BSAQ", + "process_date": "2023-08-15" + } + ], "properties": { "currency": { "$ref": "#/components/schemas/currency" @@ -1938,8 +3541,10 @@ "url": { "description": "URL of article (if applicable).", "format": "uri", - "nullable": true, - "type": "string" + "type": [ + "string", + "null" + ] } }, "required": [ @@ -1966,7 +3571,9 @@ "small", "large" ], - "example": "thumb", + "examples": [ + "thumb" + ], "minLength": 1, "type": "string" }, @@ -2003,51 +3610,50 @@ }, "next_page_token": { "description": "Pagination token for the next page.", - "nullable": true, - "type": "string" + "type": [ + "string", + "null" + ] }, "option_bar": { "description": "OHLC aggregate of all the trades in a given interval.", - "example": { - "c": 0.23, - "h": 0.28, - "l": 0.23, - "n": 26, - "o": 0.28, - "t": "2024-01-18T05:00:00Z", - "v": 224, - "vw": 0.245045 - }, + "examples": [ + { + "c": 0.23, + "h": 0.28, + "l": 0.23, + "n": 26, + "o": 0.28, + "t": "2024-01-18T05:00:00Z", + "v": 224, + "vw": 0.245045 + } + ], "properties": { "c": { "description": "Closing price.", "format": "double", - "type": "number", - "x-go-name": "Close" + "type": "number" }, "h": { "description": "High price.", "format": "double", - "type": "number", - "x-go-name": "High" + "type": "number" }, "l": { "description": "Low price.", "format": "double", - "type": "number", - "x-go-name": "Low" + "type": "number" }, "n": { "description": "Trade count in the bar.", "format": "int64", - "type": "integer", - "x-go-name": "TradeCount" + "type": "integer" }, "o": { "description": "Opening price.", "format": "double", - "type": "number", - "x-go-name": "Open" + "type": "number" }, "t": { "$ref": "#/components/schemas/timestamp" @@ -2055,14 +3661,12 @@ "v": { "description": "Bar volume.", "format": "int64", - "type": "integer", - "x-go-name": "Volume" + "type": "integer" }, "vw": { "description": "Volume weighted average price.", "format": "double", - "type": "number", - "x-go-name": "VWAP" + "type": "number" } }, "required": [ @@ -2105,21 +3709,25 @@ "additionalProperties": { "type": "string" }, - "example": { - "a": "SLAN - Single Leg Auction Non ISO", - "e": "SLFT - Single Leg Floor Trade", - "g": "MLAT - Multi Leg Auction" - }, + "examples": [ + { + "a": "SLAN - Single Leg Auction Non ISO", + "e": "SLFT - Single Leg Floor Trade", + "g": "MLAT - Multi Leg Auction" + } + ], "type": "object" }, "option_exchanges": { "additionalProperties": { "type": "string" }, - "example": { - "A": "NYSE American Options", - "Q": "Nasdaq Options" - }, + "examples": [ + { + "A": "NYSE American Options", + "Q": "Nasdaq Options" + } + ], "type": "object" }, "option_feed": { @@ -2193,55 +3801,50 @@ }, "option_quote": { "description": "The best bid and ask information for a given option.\n", - "example": { - "ap": 0.16, - "as": 669, - "ax": "w", - "bp": 0.15, - "bs": 164, - "bx": "W", - "c": "A", - "t": "2024-02-28T15:30:28.046330624Z" - }, + "examples": [ + { + "ap": 0.16, + "as": 669, + "ax": "w", + "bp": 0.15, + "bs": 164, + "bx": "W", + "c": "A", + "t": "2024-02-28T15:30:28.046330624Z" + } + ], "properties": { "ap": { "description": "Ask price.", "format": "double", - "type": "number", - "x-go-name": "AskPrice" + "type": "number" }, "as": { "description": "Ask size.", "format": "uint32", - "type": "integer", - "x-go-name": "AskSize" + "type": "integer" }, "ax": { "description": "Ask exchange.", - "type": "string", - "x-go-name": "AskExchange" + "type": "string" }, "bp": { "description": "Bid price.", "format": "double", - "type": "number", - "x-go-name": "BidPrice" + "type": "number" }, "bs": { "description": "Bid size.", "format": "uint32", - "type": "integer", - "x-go-name": "BidSize" + "type": "integer" }, "bx": { "description": "Bid exchange.", - "type": "string", - "x-go-name": "BidExchange" + "type": "string" }, "c": { "description": "Quote condition.", - "type": "string", - "x-go-name": "Condition" + "type": "string" }, "t": { "$ref": "#/components/schemas/timestamp" @@ -2308,37 +3911,35 @@ }, "option_trade": { "description": "An option trade.", - "example": { - "c": "I", - "p": 0.37, - "s": 1, - "t": "2024-01-18T15:03:44.56339456Z", - "x": "B" - }, + "examples": [ + { + "c": "I", + "p": 0.37, + "s": 1, + "t": "2024-01-18T15:03:44.56339456Z", + "x": "B" + } + ], "properties": { "c": { "description": "Trade condition.", - "type": "string", - "x-go-name": "Condition" + "type": "string" }, "p": { "description": "Trade price.", "format": "double", - "type": "number", - "x-go-name": "Price" + "type": "number" }, "s": { "description": "Trade size.", "format": "uint32", - "type": "integer", - "x-go-name": "Size" + "type": "integer" }, "t": { "$ref": "#/components/schemas/timestamp" }, "x": { - "type": "string", - "x-go-name": "Exchange" + "type": "string" } }, "required": [ @@ -2450,14 +4051,16 @@ }, "redemption": { "description": "Redemption.", - "example": { - "cusip": "687305102", - "id": "395da031-0e57-4918-a6fb-64a7c713aca4", - "payable_date": "2023-06-13", - "process_date": "2023-06-13", - "rate": 0.141134, - "symbol": "ORPHY" - }, + "examples": [ + { + "cusip": "687305102", + "id": "395da031-0e57-4918-a6fb-64a7c713aca4", + "payable_date": "2023-06-13", + "process_date": "2023-06-13", + "rate": 0.141134, + "symbol": "ORPHY" + } + ], "properties": { "currency": { "$ref": "#/components/schemas/currency" @@ -2502,8 +4105,7 @@ "non_us", "all" ], - "type": "string", - "x-go-name": "Region" + "type": "string" }, "reorganization": { "description": "Reorganization (cash and/or multiple stock allocations).", @@ -2583,17 +4185,19 @@ }, "reverse_split": { "description": "Reverse split.", - "example": { - "ex_date": "2023-08-24", - "id": "913de862-c02c-46dc-a89c-fc8779a50d30", - "new_cusip": "60879E200", - "new_rate": 1, - "old_cusip": "60879E101", - "old_rate": 50, - "process_date": "2023-08-24", - "record_date": "2023-08-24", - "symbol": "MNTS" - }, + "examples": [ + { + "ex_date": "2023-08-24", + "id": "913de862-c02c-46dc-a89c-fc8779a50d30", + "new_cusip": "60879E200", + "new_rate": 1, + "old_cusip": "60879E101", + "old_rate": 50, + "process_date": "2023-08-24", + "record_date": "2023-08-24", + "symbol": "MNTS" + } + ], "properties": { "currency": { "$ref": "#/components/schemas/currency" @@ -2655,19 +4259,21 @@ }, "rights_distribution": { "description": "Rights distribution.", - "example": { - "ex_date": "2024-04-17", - "expiration_date": "2024-05-14", - "id": "69794cfd-0adc-4e11-9211-9210a9cf8932", - "new_cusip": "454089111", - "new_symbol": "IFN.RTWI", - "payable_date": "2024-04-19", - "process_date": "2024-04-19", - "rate": 1, - "record_date": "2024-04-18", - "source_cusip": "454089103", - "source_symbol": "IFN" - }, + "examples": [ + { + "ex_date": "2024-04-17", + "expiration_date": "2024-05-14", + "id": "69794cfd-0adc-4e11-9211-9210a9cf8932", + "new_cusip": "454089111", + "new_symbol": "IFN.RTWI", + "payable_date": "2024-04-19", + "process_date": "2024-04-19", + "rate": 1, + "record_date": "2024-04-18", + "source_cusip": "454089103", + "source_symbol": "IFN" + } + ], "properties": { "currency": { "$ref": "#/components/schemas/currency" @@ -2733,23 +4339,24 @@ "asc", "desc" ], - "type": "string", - "x-go-name": "TypeSort" + "type": "string" }, "spin_off": { "description": "Spin-off.", - "example": { - "ex_date": "2023-08-15", - "id": "82e602f6-35bc-4651-a5dd-f6d88ff37c55", - "new_cusip": "85237B101", - "new_rate": 1, - "new_symbol": "SRM", - "process_date": "2023-08-15", - "record_date": "2023-08-15", - "source_cusip": "48208F105", - "source_rate": 19.35, - "source_symbol": "JUPW" - }, + "examples": [ + { + "ex_date": "2023-08-15", + "id": "82e602f6-35bc-4651-a5dd-f6d88ff37c55", + "new_cusip": "85237B101", + "new_rate": 1, + "new_symbol": "SRM", + "process_date": "2023-08-15", + "record_date": "2023-08-15", + "source_cusip": "48208F105", + "source_rate": 19.35, + "source_symbol": "JUPW" + } + ], "properties": { "currency": { "$ref": "#/components/schemas/currency" @@ -2814,19 +4421,21 @@ }, "stock_and_cash_merger": { "description": "Stock and cash merger.", - "example": { - "acquiree_cusip": "561409103", - "acquiree_rate": 1, - "acquiree_symbol": "MLVF", - "acquirer_cusip": "31931U102", - "acquirer_rate": 0.7733, - "acquirer_symbol": "FRBA", - "cash_rate": 7.8, - "effective_date": "2023-07-18", - "id": "e5248356-2c06-42cf-aeb9-1595bd616cdb", - "payable_date": "2023-07-18", - "process_date": "2023-07-18" - }, + "examples": [ + { + "acquiree_cusip": "561409103", + "acquiree_rate": 1, + "acquiree_symbol": "MLVF", + "acquirer_cusip": "31931U102", + "acquirer_rate": 0.7733, + "acquirer_symbol": "FRBA", + "cash_rate": 7.8, + "effective_date": "2023-07-18", + "id": "e5248356-2c06-42cf-aeb9-1595bd616cdb", + "payable_date": "2023-07-18", + "process_date": "2023-07-18" + } + ], "properties": { "acquiree_cusip": { "type": "string" @@ -2890,29 +4499,28 @@ }, "stock_auction": { "description": "An auction\n", - "example": { - "c": "O", - "p": 135, - "t": "2022-10-13T13:30:01.688322951Z", - "x": "Q" - }, + "examples": [ + { + "c": "O", + "p": 135, + "t": "2022-10-13T13:30:01.688322951Z", + "x": "Q" + } + ], "properties": { "c": { "description": "The condition flag indicating that this is an auction. See `v2/stocks/meta/conditions/trade` for more details.\n", - "type": "string", - "x-go-name": "Condition" + "type": "string" }, "p": { "description": "Auction price.", "format": "double", - "type": "number", - "x-go-name": "Price" + "type": "number" }, "s": { "description": "Auction trade size.", "format": "int64", - "type": "integer", - "x-go-name": "Size" + "type": "integer" }, "t": { "$ref": "#/components/schemas/timestamp" @@ -2985,46 +4593,43 @@ }, "stock_bar": { "description": "OHLC aggregate of all the trades in a given interval.\n", - "example": { - "c": 178.08, - "h": 178.34, - "l": 177.76, - "n": 1727, - "o": 178.26, - "t": "2022-01-03T09:00:00Z", - "v": 60937, - "vw": 177.954244 - }, + "examples": [ + { + "c": 178.08, + "h": 178.34, + "l": 177.76, + "n": 1727, + "o": 178.26, + "t": "2022-01-03T09:00:00Z", + "v": 60937, + "vw": 177.954244 + } + ], "properties": { "c": { "description": "Closing price.", "format": "double", - "type": "number", - "x-go-name": "Close" + "type": "number" }, "h": { "description": "High price.", "format": "double", - "type": "number", - "x-go-name": "High" + "type": "number" }, "l": { "description": "Low price.", "format": "double", - "type": "number", - "x-go-name": "Low" + "type": "number" }, "n": { "description": "Trade count in the bar.", "format": "int64", - "type": "integer", - "x-go-name": "TradeCount" + "type": "integer" }, "o": { "description": "Opening price.", "format": "double", - "type": "number", - "x-go-name": "Open" + "type": "number" }, "t": { "$ref": "#/components/schemas/timestamp" @@ -3032,14 +4637,12 @@ "v": { "description": "Bar volume.", "format": "int64", - "type": "integer", - "x-go-name": "Volume" + "type": "integer" }, "vw": { "description": "Volume weighted average price.", "format": "double", - "type": "number", - "x-go-name": "VWAP" + "type": "number" } }, "required": [ @@ -3107,11 +4710,13 @@ "additionalProperties": { "type": "string" }, - "example": { - "@": "Regular Sale", - "A": "Acquisition", - "B": "Bunched Trade" - }, + "examples": [ + { + "@": "Regular Sale", + "A": "Acquisition", + "B": "Bunched Trade" + } + ], "type": "object" }, "stock_daily_auctions": { @@ -3122,8 +4727,7 @@ "items": { "$ref": "#/components/schemas/stock_auction" }, - "type": "array", - "x-go-name": "Closes" + "type": "array" }, "d": { "$ref": "#/components/schemas/date" @@ -3133,8 +4737,7 @@ "items": { "$ref": "#/components/schemas/stock_auction" }, - "type": "array", - "x-go-name": "Opens" + "type": "array" } }, "required": [ @@ -3146,16 +4749,18 @@ }, "stock_dividend": { "description": "Stock dividend.", - "example": { - "cusip": "605015106", - "ex_date": "2023-05-19", - "id": "3ae94c30-2d37-473a-bf29-5f7b4ab6d3ca", - "payable_date": "2023-05-05", - "process_date": "2023-05-19", - "rate": 0.05, - "record_date": "2023-05-22", - "symbol": "MSBC" - }, + "examples": [ + { + "cusip": "605015106", + "ex_date": "2023-05-19", + "id": "3ae94c30-2d37-473a-bf29-5f7b4ab6d3ca", + "payable_date": "2023-05-05", + "process_date": "2023-05-19", + "rate": 0.05, + "record_date": "2023-05-22", + "symbol": "MSBC" + } + ], "properties": { "currency": { "$ref": "#/components/schemas/currency" @@ -3203,10 +4808,12 @@ "additionalProperties": { "type": "string" }, - "example": { - "N": "New York Stock Exchange", - "V": "IEX" - }, + "examples": [ + { + "N": "New York Stock Exchange", + "V": "IEX" + } + ], "type": "object" }, "stock_historical_feed": { @@ -3337,18 +4944,20 @@ }, "stock_merger": { "description": "Stock merger.", - "example": { - "acquiree_cusip": "53223X107", - "acquiree_rate": 1, - "acquiree_symbol": "LSI", - "acquirer_cusip": "30225T102", - "acquirer_rate": 0.895, - "acquirer_symbol": "EXR", - "effective_date": "2023-07-20", - "id": "728f8cb2-a00e-4bc7-ad14-d15fe82bbcff", - "payable_date": "2023-07-20", - "process_date": "2023-07-20" - }, + "examples": [ + { + "acquiree_cusip": "53223X107", + "acquiree_rate": 1, + "acquiree_symbol": "LSI", + "acquirer_cusip": "30225T102", + "acquirer_rate": 0.895, + "acquirer_symbol": "EXR", + "effective_date": "2023-07-20", + "id": "728f8cb2-a00e-4bc7-ad14-d15fe82bbcff", + "payable_date": "2023-07-20", + "process_date": "2023-07-20" + } + ], "properties": { "acquiree_cusip": { "type": "string" @@ -3407,61 +5016,56 @@ }, "stock_quote": { "description": "The best bid and ask information for a given security.", - "example": { - "ap": 387.7, - "as": 1, - "ax": "C", - "bp": 387.67, - "bs": 1, - "bx": "N", - "c": [ - "R" - ], - "t": "2021-02-06T13:35:08.946977536Z", - "z": "C" - }, + "examples": [ + { + "ap": 387.7, + "as": 1, + "ax": "C", + "bp": 387.67, + "bs": 1, + "bx": "N", + "c": [ + "R" + ], + "t": "2021-02-06T13:35:08.946977536Z", + "z": "C" + } + ], "properties": { "ap": { "description": "Ask price. 0 means the security has no active ask.", "format": "double", - "type": "number", - "x-go-name": "AskPrice" + "type": "number" }, "as": { "description": "Ask size in shares (round lots prior to November 3, 2025).", "format": "uint32", - "type": "integer", - "x-go-name": "AskSize" + "type": "integer" }, "ax": { "description": "Ask exchange. See `v2/stocks/meta/exchanges` for more details.", - "type": "string", - "x-go-name": "AskExchange" + "type": "string" }, "bp": { "description": "Bid price. 0 means the security has no active bid.", "format": "double", - "type": "number", - "x-go-name": "BidPrice" + "type": "number" }, "bs": { "description": "Bid size in shares (round lots prior to November 3, 2025).", "format": "uint32", - "type": "integer", - "x-go-name": "BidSize" + "type": "integer" }, "bx": { "description": "Bid exchange. See `v2/stocks/meta/exchanges` for more details.", - "type": "string", - "x-go-name": "BidExchange" + "type": "string" }, "c": { "description": "Condition flags. See `v2/stocks/meta/conditions/quote` for more details. If the array contains one flag, it applies to both the bid and ask. If the array contains two flags, the first one applies to the bid and the second one to the ask.\n", "items": { "type": "string" }, - "type": "array", - "x-go-name": "Conditions" + "type": "array" }, "t": { "$ref": "#/components/schemas/timestamp" @@ -3586,62 +5190,57 @@ "N", "O" ], - "type": "string", - "x-go-name": "Tape" + "type": "string" }, "stock_trade": { "description": "A stock trade.", - "example": { - "c": [ - "@", - "T" - ], - "i": 1, - "p": 178.26, - "s": 246, - "t": "2022-01-03T09:00:00.086175744Z", - "x": "P", - "z": "C" - }, + "examples": [ + { + "c": [ + "@", + "T" + ], + "i": 1, + "p": 178.26, + "s": 246, + "t": "2022-01-03T09:00:00.086175744Z", + "x": "P", + "z": "C" + } + ], "properties": { "c": { "description": "Condition flags. See `v2/stocks/meta/conditions/trade` for more details.", "items": { "type": "string" }, - "type": "array", - "x-go-name": "Conditions" + "type": "array" }, "i": { "description": "Trade ID sent by the exchange.", "format": "uint64", - "type": "integer", - "x-go-name": "ID" + "type": "integer" }, "p": { "description": "Trade price.", "format": "double", - "type": "number", - "x-go-name": "Price" + "type": "number" }, "s": { "description": "Trade size.", "format": "uint32", - "type": "integer", - "x-go-name": "Size" + "type": "integer" }, "t": { "$ref": "#/components/schemas/timestamp" }, "u": { "description": "Update to the trade. This field is optional, if it's missing, the trade is valid. Otherwise, it can have these values:\n - canceled: indicates that the trade has been canceled\n - incorrect: indicates that the trade has been corrected and the given trade is no longer valid\n - corrected: indicates that this trade is the correction of a previous (incorrect) trade\n", - "type": "string", - "x-go-name": "Update" + "type": "string" }, "x": { "description": "Exchange code. See `v2/stocks/meta/exchanges` for more details.", - "type": "string", - "x-go-name": "Exchange" + "type": "string" }, "z": { "$ref": "#/components/schemas/stock_tape" @@ -3710,25 +5309,26 @@ "timestamp": { "description": "Timestamp in RFC-3339 format with nanosecond precision.", "format": "date-time", - "type": "string", - "x-go-name": "Timestamp" + "type": "string" }, "unit_split": { "description": "Unit split.", - "example": { - "alternate_cusip": "G5391L110", - "alternate_rate": 0.3333, - "alternate_symbol": "LVROW", - "effective_date": "2023-03-01", - "id": "3e68e87e-ae95-4d68-91d1-715d52ef143a", - "new_cusip": "G5391L102", - "new_rate": 1, - "new_symbol": "LVRO", - "old_cusip": "G8990L119", - "old_rate": 1, - "old_symbol": "TPBAU", - "process_date": "2023-03-01" - }, + "examples": [ + { + "alternate_cusip": "G5391L110", + "alternate_rate": 0.3333, + "alternate_symbol": "LVROW", + "effective_date": "2023-03-01", + "id": "3e68e87e-ae95-4d68-91d1-715d52ef143a", + "new_cusip": "G5391L102", + "new_rate": 1, + "new_symbol": "LVRO", + "old_cusip": "G8990L119", + "old_rate": 1, + "old_symbol": "TPBAU", + "process_date": "2023-03-01" + } + ], "properties": { "alternate_cusip": { "type": "string" @@ -3803,12 +5403,14 @@ }, "worthless_removal": { "description": "Worthless removal.", - "example": { - "cusip": "078771300", - "id": "106c2149-ee04-4d2e-a943-98dbb4d21a3c", - "process_date": "2024-12-19", - "symbol": "BLPH" - }, + "examples": [ + { + "cusip": "078771300", + "id": "106c2149-ee04-4d2e-a943-98dbb4d21a3c", + "process_date": "2024-12-19", + "symbol": "BLPH" + } + ], "properties": { "currency": { "$ref": "#/components/schemas/currency" @@ -3839,6 +5441,10 @@ } }, "securitySchemes": { + "BasicAuth": { + "scheme": "basic", + "type": "http" + }, "apiKey": { "in": "header", "name": "APCA-API-KEY-ID", @@ -3866,367 +5472,461 @@ "title": "Market Data API", "version": "1.1" }, - "openapi": "3.0.0", + "openapi": "3.1.2", "paths": { - "/v1beta1/crypto-perps/{loc}/latest/bars": { - "get": { - "description": "The latest bars endpoint returns the latest bar data for the crypto perpetual futures symbols provided.\n", - "operationId": "CryptoPerpLatestBars", - "parameters": [ - { - "$ref": "#/components/parameters/crypto_perp_loc" - }, - { - "$ref": "#/components/parameters/crypto_perp_symbols" - } - ], - "responses": { - "200": { - "content": { - "application/json": { - "examples": { - "bars": { - "value": { - "bars": { - "BTCUSDT.P": { - "c": 29003, - "h": 29003, - "l": 28999, - "n": 4, - "o": 28999, - "t": "2022-05-27T10:18:00Z", - "v": 0.01, - "vw": 29001 - } - } - } - } - }, - "schema": { - "$ref": "#/components/schemas/crypto_latest_bars_resp" - } - } - }, - "description": "OK", - "headers": { - "X-RateLimit-Limit": { - "$ref": "#/components/headers/ratelimit_limit" - }, - "X-RateLimit-Remaining": { - "$ref": "#/components/headers/ratelimit_remaining" - }, - "X-RateLimit-Reset": { - "$ref": "#/components/headers/ratelimit_reset" - } - } - }, - "400": { - "$ref": "#/components/responses/400" - }, - "401": { - "$ref": "#/components/responses/401" - }, - "403": { - "$ref": "#/components/responses/403" - }, - "429": { - "$ref": "#/components/responses/429" - }, - "500": { - "$ref": "#/components/responses/500" - } - }, - "security": [], - "summary": "Latest bars", - "tags": [ - "Crypto perpetual futures" - ] - } - }, - "/v1beta1/crypto-perps/{loc}/latest/orderbooks": { - "get": { - "description": "The latest orderbook endpoint returns the latest bid and ask orderbook for the crypto perpetual futures symbols provided.\n", - "operationId": "CryptoPerpLatestOrderbooks", - "parameters": [ - { - "$ref": "#/components/parameters/crypto_perp_loc" - }, - { - "$ref": "#/components/parameters/crypto_perp_symbols" - } - ], - "responses": { - "200": { - "content": { - "application/json": { - "examples": { - "orderbooks": { - "value": { - "orderbooks": { - "BTCUSDT.P": { - "a": [ - { - "p": 20902, - "s": 0.0097 - }, - { - "p": 21444, - "s": 0 - } - ], - "b": [ - { - "p": 20846, - "s": 0.1902 - }, - { - "p": 20350, - "s": 0 - } - ], - "t": "2022-06-24T08:00:14.137774336Z" - } - } - } - } - }, - "schema": { - "$ref": "#/components/schemas/crypto_latest_orderbooks_resp" - } - } - }, - "description": "OK", - "headers": { - "X-RateLimit-Limit": { - "$ref": "#/components/headers/ratelimit_limit" - }, - "X-RateLimit-Remaining": { - "$ref": "#/components/headers/ratelimit_remaining" - }, - "X-RateLimit-Reset": { - "$ref": "#/components/headers/ratelimit_reset" - } - } - }, - "400": { - "$ref": "#/components/responses/400" - }, - "401": { - "$ref": "#/components/responses/401" - }, - "403": { - "$ref": "#/components/responses/403" - }, - "429": { - "$ref": "#/components/responses/429" - }, - "500": { - "$ref": "#/components/responses/500" - } - }, - "security": [], - "summary": "Latest orderbook", - "tags": [ - "Crypto perpetual futures" - ] - } - }, - "/v1beta1/crypto-perps/{loc}/latest/pricing": { - "get": { - "description": "The latest futures pricing endpoint returns the latest pricing data for the crypto perpetual futures symbols provided.\n", - "operationId": "CryptoPerpLatestFuturesPricing", - "parameters": [ - { - "$ref": "#/components/parameters/crypto_perp_loc" - }, - { - "$ref": "#/components/parameters/crypto_perp_symbols" - } - ], - "responses": { - "200": { - "content": { - "application/json": { - "examples": { - "pricing": { - "value": { - "pricing": { - "BTCUSDT.P": { - "fr": 0.000565699, - "ft": "2022-05-27T10:18:00Z", - "ip": 50702.8, - "mp": 50652.3553, - "oi": 90.7367, - "t": "2022-05-27T10:18:00Z" - } - } - } - } - }, - "schema": { - "$ref": "#/components/schemas/crypto_perp_latest_futures_pricing_resp" - } - } - }, - "description": "OK", - "headers": { - "X-RateLimit-Limit": { - "$ref": "#/components/headers/ratelimit_limit" - }, - "X-RateLimit-Remaining": { - "$ref": "#/components/headers/ratelimit_remaining" - }, - "X-RateLimit-Reset": { - "$ref": "#/components/headers/ratelimit_reset" - } - } - }, - "400": { - "$ref": "#/components/responses/400" - }, - "401": { - "$ref": "#/components/responses/401" - }, - "403": { - "$ref": "#/components/responses/403" - }, - "429": { - "$ref": "#/components/responses/429" - }, - "500": { - "$ref": "#/components/responses/500" - } - }, - "security": [], - "summary": "Latest pricing", - "tags": [ - "Crypto perpetual futures" - ] - } - }, - "/v1beta1/crypto-perps/{loc}/latest/quotes": { + "/v1beta1/events/corporate-actions": { "get": { - "description": "The latest quotes endpoint returns the latest bid and ask prices for the crypto perpetual futures symbols provided.\n", - "operationId": "CryptoPerpLatestQuotes", + "description": "Server-Sent Events (SSE) stream that delivers every corporate-action mutation (`insert` / `update` / `delete`) across all supported CA types on a single long-lived `text/event-stream` connection. When `since`, `since_id`, or the `Last-Event-Id` reconnect header is provided, historical events are replayed first; otherwise only live events are pushed.\n\nEach event carries an `event_type` discriminator that selects the shape of the `ca` payload -- see the [`corporate_action_event`](#/components/schemas/corporate_action_event) schema for the mapping to each per-type schema. The same underlying data is available on demand via [`GET /v1/corporate-actions`](#operation/CorporateActions).\n", + "operationId": "SubscribeToCorporateActionsEventsSSE", "parameters": [ { - "$ref": "#/components/parameters/crypto_perp_loc" - }, - { - "$ref": "#/components/parameters/crypto_perp_symbols" - } - ], - "responses": { - "200": { - "content": { - "application/json": { - "examples": { - "quotes": { - "value": { - "quotes": { - "BTCUSDT.P": { - "ap": 29059, - "as": 3.252, - "bp": 29058, - "bs": 0.3544, - "t": "2022-05-26T11:47:18.44347136Z" - }, - "ETHUSDT.P": { - "ap": 1817.7, - "as": 6.137, - "bp": 1817, - "bs": 4.76, - "t": "2022-05-26T11:47:18.499478272Z" - } - } - } - } - }, - "schema": { - "$ref": "#/components/schemas/crypto_latest_quotes_resp" - } - } - }, - "description": "OK", - "headers": { - "X-RateLimit-Limit": { - "$ref": "#/components/headers/ratelimit_limit" - }, - "X-RateLimit-Remaining": { - "$ref": "#/components/headers/ratelimit_remaining" - }, - "X-RateLimit-Reset": { - "$ref": "#/components/headers/ratelimit_reset" - } - } - }, - "400": { - "$ref": "#/components/responses/400" + "$ref": "#/components/parameters/cas_events_type" }, - "401": { - "$ref": "#/components/responses/401" + { + "$ref": "#/components/parameters/cas_events_region" }, - "403": { - "$ref": "#/components/responses/403" + { + "$ref": "#/components/parameters/cas_events_since" }, - "429": { - "$ref": "#/components/responses/429" + { + "$ref": "#/components/parameters/cas_events_until" }, - "500": { - "$ref": "#/components/responses/500" - } - }, - "security": [], - "summary": "Latest quotes", - "tags": [ - "Crypto perpetual futures" - ] - } - }, - "/v1beta1/crypto-perps/{loc}/latest/trades": { - "get": { - "description": "The latest trades endpoint returns the latest trade data for the crypto perpetual futures symbols provided.\n", - "operationId": "CryptoPerpLatestTrades", - "parameters": [ { - "$ref": "#/components/parameters/crypto_perp_loc" + "$ref": "#/components/parameters/cas_events_since_id" }, { - "$ref": "#/components/parameters/crypto_perp_symbols" + "$ref": "#/components/parameters/cas_events_until_id" + }, + { + "$ref": "#/components/parameters/cas_events_last_event_id" } ], "responses": { "200": { "content": { - "application/json": { + "text/event-stream": { "examples": { - "trades": { - "value": { - "trades": { - "BTCUSDT.P": { - "i": 31455289, - "p": 29791, - "s": 0.0016, - "t": "2022-05-18T12:01:00.537052Z", - "tks": "S" + "CapitalGainsDistribution": { + "description": "Example SSE payload delivered when\n`event_type == capital_gains_distribution_corporateaction_event`.\nCombined long-term + short-term distribution; single-leg events\ncarry only the applicable `long_term_rate` or `short_term_rate`.\n", + "summary": "Capital gains distribution inserted (US)", + "value": [ + { + "action": "insert", + "at": "2026-03-20T18:00:00.000000Z", + "ca": { + "currency": "USD", + "cusip": "36261K509", + "ex_date": "2025-12-26", + "id": "98c905a1-08c7-450c-8669-b8e7aee8b44d", + "isin": "US36261K5090", + "long_term_rate": "0.45855", + "payable_date": "2026-01-09", + "process_date": "2026-01-08", + "record_date": "2025-12-29", + "short_term_rate": "0.11019", + "symbol": "GCAD" }, - "ETHUSDT.P": { - "i": 31455287, - "p": 2027.6, - "s": 0.06, - "t": "2022-05-18T12:01:00.363547Z", - "tks": "S" - } + "event_id": "01KMAGXP2FCJ4NR1TDX8ZWKR6E", + "event_type": "capital_gains_distribution_corporateaction_event", + "region": "us" } - } + ] + }, + "CashDividend": { + "description": "Example SSE payload delivered when `event_type == cash_dividend_corporateaction_event`.", + "summary": "Cash dividend inserted (US)", + "value": [ + { + "action": "insert", + "at": "2026-03-20T12:24:58.807230Z", + "ca": { + "currency": "USD", + "cusip": "037833100", + "ex_date": "2026-05-09", + "foreign": false, + "id": "1dbc7685-9517-4a77-a236-8527d49cefdc", + "payable_date": "2026-05-15", + "process_date": "2026-05-15", + "rate": "0.24", + "record_date": "2026-05-12", + "special": false, + "symbol": "AAPL" + }, + "event_id": "01J9RPMV5TKB8WX3M4F1KZ7QH2", + "event_type": "cash_dividend_corporateaction_event", + "region": "us" + } + ] + }, + "CashMerger": { + "description": "Example SSE payload delivered when `event_type == cash_merger_corporateaction_event`.", + "summary": "Cash merger inserted (US)", + "value": [ + { + "action": "insert", + "at": "2026-03-20T15:42:11.118274Z", + "ca": { + "acquiree_cusip": "90184L102", + "acquiree_symbol": "TWTR", + "acquirer_cusip": "912656104", + "acquirer_symbol": "X", + "currency": "USD", + "effective_date": "2026-04-01", + "id": "f8489167-4e4b-431d-a0be-6017ae1cf08a", + "payable_date": "2026-04-05", + "process_date": "2026-04-01", + "rate": "54.20" + }, + "event_id": "01J9RQ5HNZQK7M3RDVJ8XBPCT1", + "event_type": "cash_merger_corporateaction_event", + "region": "us" + } + ] + }, + "EquityPartialCall": { + "description": "Example SSE payload delivered when `event_type == equity_partial_call_corporateaction_event`.", + "summary": "Equity partial call inserted (US)", + "value": [ + { + "action": "insert", + "at": "2026-03-19T23:26:46.853262Z", + "ca": { + "currency": "USD", + "cusip": "123456789", + "id": "b2f8b1a4-4c4a-4e8e-9c9a-1f3b8a7c5d6e", + "lottery_date": "2026-06-08", + "lottery_type": "original", + "payable_date": "2026-06-15", + "price": "25.00", + "process_date": "2026-06-10", + "record_date": "2026-06-05", + "results_publication_date": "2026-06-09", + "symbol": "EXMP" + }, + "event_id": "01J9RSGD8JR2A4K7MZNTPVQ5XW", + "event_type": "equity_partial_call_corporateaction_event", + "region": "us" + } + ] + }, + "ForwardSplit": { + "description": "Example SSE payload delivered when `event_type == forward_split_corporateaction_event`.", + "summary": "Forward split updated (US)", + "value": [ + { + "action": "update", + "at": "2026-03-19T22:50:11.729329Z", + "ca": { + "currency": "USD", + "cusip": "67066G104", + "ex_date": "2026-06-10", + "id": "78467a10-9aa2-4222-8927-abcdef012345", + "new_rate": "10", + "old_rate": "1", + "payable_date": "2026-06-09", + "process_date": "2026-06-07", + "record_date": "2026-06-06", + "symbol": "NVDA" + }, + "event_id": "01KM5CDQXQAE67Z5NHCJZHQ5XV", + "event_type": "forward_split_corporateaction_event", + "region": "us" + } + ] + }, + "NameChange": { + "description": "Example SSE payload delivered when `event_type == name_change_corporateaction_event`.", + "summary": "Name change inserted (US)", + "value": [ + { + "action": "insert", + "at": "2026-03-20T00:15:42.318901Z", + "ca": { + "currency": "USD", + "id": "c1a2b3c4-d5e6-4f78-8912-abcdef012345", + "new_cusip": "30303M102", + "new_symbol": "META", + "old_cusip": "30303M102", + "old_symbol": "FB", + "process_date": "2026-06-09" + }, + "event_id": "01KM5CDZN6KX5V8WQ8KXDH917J", + "event_type": "name_change_corporateaction_event", + "region": "us" + } + ] + }, + "Redemption": { + "description": "Example SSE payload delivered when `event_type == redemption_corporateaction_event`.", + "summary": "Redemption inserted (US)", + "value": [ + { + "action": "insert", + "at": "2026-03-20T12:24:52.325158Z", + "ca": { + "currency": "USD", + "cusip": "987654321", + "id": "4e5f6a7b-8c9d-4e01-a234-56789abcdef0", + "payable_date": "2026-07-03", + "process_date": "2026-07-01", + "rate": "10.00", + "symbol": "XYZ" + }, + "event_id": "01J9RVB6Y4ZK8M3N7QD2WX1RFP", + "event_type": "redemption_corporateaction_event", + "region": "us" + } + ] + }, + "Reorganization": { + "description": "Example SSE payload delivered when `event_type == reorganization_corporateaction_event`.", + "summary": "Reorganization inserted (US)", + "value": [ + { + "action": "insert", + "at": "2026-03-20T13:12:03.500000Z", + "ca": { + "cash_rate": "5.00", + "currency": "USD", + "cusip": "111222333", + "effective_date": "2026-08-15", + "id": "6a7b8c9d-0e1f-4234-5678-90abcdef1234", + "payable_date": "2026-08-20", + "process_date": "2026-08-15", + "stock_movements": [ + { + "cusip": "444555666", + "new_rate": "1", + "source_rate": "10", + "symbol": "NEWCO" + } + ], + "symbol": "OLDCO" + }, + "event_id": "01KM5CE7CNA84WKP9M7T9BM2EX", + "event_type": "reorganization_corporateaction_event", + "region": "us" + } + ] + }, + "ReverseSplit": { + "description": "Example SSE payload delivered when `event_type == reverse_split_corporateaction_event`.", + "summary": "Reverse split inserted (non-US)", + "value": [ + { + "action": "insert", + "at": "2026-03-20T14:00:00.000000Z", + "ca": { + "currency": "GBP", + "ex_date": "2026-09-01", + "id": "7b8c9d0e-1f23-4456-7890-abcdef123456", + "new_cusip": "G12345679", + "new_isin": "GB0001234568", + "new_rate": "1", + "old_cusip": "G12345678", + "old_isin": "GB0001234567", + "old_rate": "20", + "payable_date": "2026-09-02", + "process_date": "2026-09-01", + "record_date": "2026-08-30", + "symbol": "EXMP.L" + }, + "event_id": "01KM5CEF44KN9DZNWV9NV456V1", + "event_type": "reverse_split_corporateaction_event", + "region": "non_us" + } + ] + }, + "RightsDistribution": { + "description": "Example SSE payload delivered when `event_type == rights_distribution_corporateaction_event`.", + "summary": "Rights distribution inserted (US)", + "value": [ + { + "action": "insert", + "at": "2026-03-20T14:30:15.100000Z", + "ca": { + "currency": "USD", + "ex_date": "2026-05-15", + "expiration_date": "2026-06-15", + "id": "8c9d0e1f-2345-4678-9012-abcdef234567", + "new_cusip": "123456790", + "new_symbol": "EXMPR", + "payable_date": "2026-05-22", + "process_date": "2026-05-20", + "rate": "0.25", + "record_date": "2026-05-18", + "source_cusip": "123456789", + "source_symbol": "EXMP" + }, + "event_id": "01KM5CEPVKF869Z7RD7EJNP360", + "event_type": "rights_distribution_corporateaction_event", + "region": "us" + } + ] + }, + "SpinOff": { + "description": "Example SSE payload delivered when `event_type == spin_off_corporateaction_event`.", + "summary": "Spin-off updated (US)", + "value": [ + { + "action": "update", + "at": "2026-03-20T15:00:22.220000Z", + "ca": { + "currency": "USD", + "ex_date": "2026-04-08", + "id": "9d0e1f23-4567-4890-1234-abcdef345678", + "new_cusip": "9344D2109", + "new_rate": "0.241917", + "new_symbol": "WBD", + "payable_date": "2026-04-11", + "process_date": "2026-04-08", + "record_date": "2026-04-05", + "source_cusip": "00206R102", + "source_rate": "1", + "source_symbol": "T" + }, + "event_id": "01KM5CEYK2TRA4S2456P21NJZ0", + "event_type": "spin_off_corporateaction_event", + "region": "us" + } + ] + }, + "StockAndCashMerger": { + "description": "Example SSE payload delivered when `event_type == stock_and_cash_merger_corporateaction_event`.", + "summary": "Stock-and-cash merger inserted (US)", + "value": [ + { + "action": "insert", + "at": "2026-03-20T15:30:45.300000Z", + "ca": { + "acquiree_cusip": "35137L204", + "acquiree_rate": "1", + "acquiree_symbol": "FOXA", + "acquirer_cusip": "254687106", + "acquirer_rate": "0.35", + "acquirer_symbol": "DIS", + "cash_rate": "10.00", + "currency": "USD", + "effective_date": "2026-05-01", + "id": "0e1f2345-6789-4a01-b234-cdef56789012", + "payable_date": "2026-05-05", + "process_date": "2026-05-01" + }, + "event_id": "01KM5CF6AHE2DGEP087XGX6XDW", + "event_type": "stock_and_cash_merger_corporateaction_event", + "region": "us" + } + ] + }, + "StockDividend": { + "description": "Example SSE payload delivered when `event_type == stock_dividend_corporateaction_event`.", + "summary": "Stock dividend inserted (US)", + "value": [ + { + "action": "insert", + "at": "2026-03-20T16:00:00.400000Z", + "ca": { + "currency": "USD", + "cusip": "123456789", + "ex_date": "2026-05-28", + "id": "1f234567-89ab-4c01-d234-ef5678901234", + "payable_date": "2026-06-01", + "process_date": "2026-06-01", + "rate": "0.05", + "record_date": "2026-05-30", + "symbol": "EXMP" + }, + "event_id": "01KM5CFE200AT1VX7S34MY33YT", + "event_type": "stock_dividend_corporateaction_event", + "region": "us" + } + ] + }, + "StockMerger": { + "description": "Example SSE payload delivered when `event_type == stock_merger_corporateaction_event`.", + "summary": "Stock merger inserted (US)", + "value": [ + { + "action": "insert", + "at": "2026-03-20T16:30:11.500000Z", + "ca": { + "acquiree_cusip": "555666777", + "acquiree_rate": "1", + "acquiree_symbol": "SMLCO", + "acquirer_cusip": "222333444", + "acquirer_rate": "0.75", + "acquirer_symbol": "BIGCO", + "currency": "USD", + "effective_date": "2026-07-15", + "id": "23456789-abcd-4e01-f234-567890123456", + "payable_date": "2026-07-18", + "process_date": "2026-07-15" + }, + "event_id": "01KM5CFNSFKSQR5SAEEJ783SWY", + "event_type": "stock_merger_corporateaction_event", + "region": "us" + } + ] + }, + "UnitSplit": { + "description": "Example SSE payload delivered when `event_type == unit_split_corporateaction_event`.", + "summary": "Unit split inserted (US)", + "value": [ + { + "action": "insert", + "at": "2026-03-20T17:00:30.600000Z", + "ca": { + "alternate_cusip": "888999002", + "alternate_rate": "0.5", + "alternate_symbol": "SPACW", + "currency": "USD", + "effective_date": "2026-08-01", + "id": "3456789a-bcde-4f01-2345-67890abcdef1", + "new_cusip": "888999001", + "new_rate": "1", + "new_symbol": "SPAC", + "old_cusip": "888999000", + "old_rate": "1", + "old_symbol": "SPACU", + "payable_date": "2026-08-03", + "process_date": "2026-08-01" + }, + "event_id": "01KM5CFXGY9EYPZRTCVJX88FQ8", + "event_type": "unit_split_corporateaction_event", + "region": "us" + } + ] + }, + "WorthlessRemoval": { + "description": "Example SSE payload delivered when `event_type == worthless_removal_corporateaction_event`.", + "summary": "Worthless removal deleted (US)", + "value": [ + { + "action": "delete", + "at": "2026-03-20T17:30:00.700000Z", + "ca": { + "currency": "USD", + "cusip": "777888999", + "id": "456789ab-cdef-4012-3456-7890abcdef12", + "process_date": "2026-09-15", + "symbol": "DEADCO" + }, + "event_id": "01KM5CG58DTRZ8RKZY3KNYMN3D", + "event_type": "worthless_removal_corporateaction_event", + "region": "us" + } + ] } }, "schema": { - "$ref": "#/components/schemas/crypto_latest_trades_resp" + "items": { + "$ref": "#/components/schemas/corporate_action_event" + }, + "type": "array" } } }, - "description": "OK", + "description": "Connected. Corporate-action events will now start streaming as long as you keep the connection open.", "headers": { "X-RateLimit-Limit": { "$ref": "#/components/headers/ratelimit_limit" @@ -4255,10 +5955,32 @@ "$ref": "#/components/responses/500" } }, - "security": [], - "summary": "Latest trades", + "security": [ + { + "apiKey": [], + "apiSecret": [] + }, + { + "BasicAuth": [] + } + ], + "servers": [ + { + "description": "Production", + "url": "https://stream.data.alpaca.markets" + }, + { + "description": "Sandbox", + "url": "https://stream.data.sandbox.alpaca.markets" + }, + { + "description": "Staging", + "url": "https://stream.data.staging-v2.tradetalk.us" + } + ], + "summary": "Subscribe to Corporate Actions Events (SSE)", "tags": [ - "Crypto perpetual futures" + "Corporate actions" ] } }, @@ -4335,6 +6057,15 @@ "$ref": "#/components/responses/500" } }, + "security": [ + { + "apiKey": [], + "apiSecret": [] + }, + { + "BasicAuth": [] + } + ], "summary": "Latest prices", "tags": [ "Fixed income" @@ -4432,6 +6163,15 @@ "$ref": "#/components/responses/500" } }, + "security": [ + { + "apiKey": [], + "apiSecret": [] + }, + { + "BasicAuth": [] + } + ], "summary": "Latest quotes", "tags": [ "Fixed income" @@ -4458,184 +6198,15 @@ "USDJPY": { "ap": 128.112, "bp": 127.752, - "mp": 127.779, - "t": "2022-05-20T05:38:41.311530885Z" - } - } - } - } - }, - "schema": { - "$ref": "#/components/schemas/forex_latest_rates_resp" - } - } - }, - "description": "OK", - "headers": { - "X-RateLimit-Limit": { - "$ref": "#/components/headers/ratelimit_limit" - }, - "X-RateLimit-Remaining": { - "$ref": "#/components/headers/ratelimit_remaining" - }, - "X-RateLimit-Reset": { - "$ref": "#/components/headers/ratelimit_reset" - } - } - }, - "400": { - "$ref": "#/components/responses/400" - }, - "401": { - "$ref": "#/components/responses/401" - }, - "403": { - "$ref": "#/components/responses/403" - }, - "429": { - "$ref": "#/components/responses/429" - }, - "500": { - "$ref": "#/components/responses/500" - } - }, - "summary": "Latest rates for currency pairs", - "tags": [ - "Forex" - ] - } - }, - "/v1beta1/forex/rates": { - "get": { - "description": "Get historical forex rates for the given currency pairs in the given time interval and at the given timeframe (snapshot frequency).\n", - "operationId": "Rates", - "parameters": [ - { - "$ref": "#/components/parameters/forex_currency_pairs" - }, - { - "$ref": "#/components/parameters/forex_timeframe" - }, - { - "$ref": "#/components/parameters/start" - }, - { - "$ref": "#/components/parameters/end" - }, - { - "$ref": "#/components/parameters/limit" - }, - { - "$ref": "#/components/parameters/sort" - }, - { - "$ref": "#/components/parameters/page_token" - } - ], - "responses": { - "200": { - "content": { - "application/json": { - "examples": { - "USDJPY": { - "value": { - "next_page_token": "VVNESlBZfDIwMjItMDEtMDNUMDA6MDM6MDBa", - "rates": { - "USDJPY": [ - { - "ap": 115.18, - "bp": 114.192, - "mp": 115.144, - "t": "2022-01-03T00:01:00Z" - }, - { - "ap": 115.185, - "bp": 114.189, - "mp": 115.138, - "t": "2022-01-03T00:02:00Z" - }, - { - "ap": 115.148, - "bp": 115.122, - "mp": 115.131, - "t": "2022-01-03T00:03:00Z" - } - ] - } - } - } - }, - "schema": { - "$ref": "#/components/schemas/forex_rates_resp" - } - } - }, - "description": "OK", - "headers": { - "X-RateLimit-Limit": { - "$ref": "#/components/headers/ratelimit_limit" - }, - "X-RateLimit-Remaining": { - "$ref": "#/components/headers/ratelimit_remaining" - }, - "X-RateLimit-Reset": { - "$ref": "#/components/headers/ratelimit_reset" - } - } - }, - "400": { - "$ref": "#/components/responses/400" - }, - "401": { - "$ref": "#/components/responses/401" - }, - "403": { - "$ref": "#/components/responses/403" - }, - "429": { - "$ref": "#/components/responses/429" - }, - "500": { - "$ref": "#/components/responses/500" - } - }, - "summary": "Historical rates for currency pairs", - "tags": [ - "Forex" - ] - } - }, - "/v1beta1/indices/latest/values": { - "get": { - "description": "Get the latest values for the given indices.", - "operationId": "IndexLatestValues", - "parameters": [ - { - "$ref": "#/components/parameters/index_symbols" - } - ], - "responses": { - "200": { - "content": { - "application/json": { - "examples": { - "values": { - "value": { - "values": { - "SPX": { - "t": "2026-05-18T17:00:02.0256Z", - "v": 7383.46 - }, - "VIX": { - "t": "2026-05-18T17:00:46.354Z", - "v": 18.45 + "mp": 127.779, + "t": "2022-05-20T05:38:41.311530885Z" } } } } }, "schema": { - "$ref": "#/components/schemas/index_latest_values_resp" + "$ref": "#/components/schemas/forex_latest_rates_resp" } } }, @@ -4668,19 +6239,31 @@ "$ref": "#/components/responses/500" } }, - "summary": "Latest values for indices", + "security": [ + { + "BasicAuth": [] + }, + { + "apiKey": [], + "apiSecret": [] + } + ], + "summary": "Latest rates for currency pairs", "tags": [ - "Index" + "Forex" ] } }, - "/v1beta1/indices/values": { + "/v1beta1/forex/rates": { "get": { - "description": "Get historical values for the given indices in the given time interval.\n", - "operationId": "IndexValues", + "description": "Get historical forex rates for the given currency pairs in the given time interval and at the given timeframe (snapshot frequency).\n", + "operationId": "Rates", "parameters": [ { - "$ref": "#/components/parameters/index_symbols" + "$ref": "#/components/parameters/forex_currency_pairs" + }, + { + "$ref": "#/components/parameters/forex_timeframe" }, { "$ref": "#/components/parameters/start" @@ -4692,10 +6275,10 @@ "$ref": "#/components/parameters/limit" }, { - "$ref": "#/components/parameters/page_token" + "$ref": "#/components/parameters/sort" }, { - "$ref": "#/components/parameters/sort" + "$ref": "#/components/parameters/page_token" } ], "responses": { @@ -4703,36 +6286,28 @@ "content": { "application/json": { "examples": { - "values": { + "USDJPY": { "value": { - "next_page_token": "U1BYfDIwMjYtMDUtMThUMTc6MDA6MDIuMDI1Nlo", - "values": { - "SPX": [ - { - "t": "2026-05-18T17:00:00.0302Z", - "v": 7384.41 - }, - { - "t": "2026-05-18T17:00:01.0918Z", - "v": 7384.07 - }, - { - "t": "2026-05-18T17:00:02.0256Z", - "v": 7383.46 - } - ], - "VIX": [ + "next_page_token": "VVNESlBZfDIwMjItMDEtMDNUMDA6MDM6MDBa", + "rates": { + "USDJPY": [ { - "t": "2026-05-18T17:00:01.3355Z", - "v": 18.4 + "ap": 115.18, + "bp": 114.192, + "mp": 115.144, + "t": "2022-01-03T00:01:00Z" }, { - "t": "2026-05-18T17:00:16.3453Z", - "v": 18.41 + "ap": 115.185, + "bp": 114.189, + "mp": 115.138, + "t": "2022-01-03T00:02:00Z" }, { - "t": "2026-05-18T17:00:46.354Z", - "v": 18.45 + "ap": 115.148, + "bp": 115.122, + "mp": 115.131, + "t": "2022-01-03T00:03:00Z" } ] } @@ -4740,7 +6315,7 @@ } }, "schema": { - "$ref": "#/components/schemas/index_values_resp" + "$ref": "#/components/schemas/forex_rates_resp" } } }, @@ -4773,9 +6348,18 @@ "$ref": "#/components/responses/500" } }, - "summary": "Historical values for indices", + "security": [ + { + "BasicAuth": [] + }, + { + "apiKey": [], + "apiSecret": [] + } + ], + "summary": "Historical rates for currency pairs", "tags": [ - "Index" + "Forex" ] } }, @@ -4802,7 +6386,7 @@ "content": { "image/png": { "schema": { - "format": "binary", + "contentMediaType": "image/png", "type": "string" } } @@ -4836,6 +6420,15 @@ "$ref": "#/components/responses/500" } }, + "security": [ + { + "apiKey": [], + "apiSecret": [] + }, + { + "BasicAuth": [] + } + ], "summary": "Logos", "tags": [ "Logos" @@ -4947,6 +6540,15 @@ "$ref": "#/components/responses/500" } }, + "security": [ + { + "apiKey": [], + "apiSecret": [] + }, + { + "BasicAuth": [] + } + ], "summary": "News articles", "tags": [ "News" @@ -5039,6 +6641,15 @@ "$ref": "#/components/responses/500" } }, + "security": [ + { + "apiKey": [], + "apiSecret": [] + }, + { + "BasicAuth": [] + } + ], "summary": "Historical bars", "tags": [ "Option" @@ -5102,6 +6713,15 @@ "$ref": "#/components/responses/500" } }, + "security": [ + { + "apiKey": [], + "apiSecret": [] + }, + { + "BasicAuth": [] + } + ], "summary": "Condition codes", "tags": [ "Option" @@ -5150,6 +6770,15 @@ "$ref": "#/components/responses/500" } }, + "security": [ + { + "apiKey": [], + "apiSecret": [] + }, + { + "BasicAuth": [] + } + ], "summary": "Exchange codes", "tags": [ "Option" @@ -5234,6 +6863,15 @@ "$ref": "#/components/responses/500" } }, + "security": [ + { + "apiKey": [], + "apiSecret": [] + }, + { + "BasicAuth": [] + } + ], "summary": "Latest quotes", "tags": [ "Option" @@ -5335,6 +6973,15 @@ "$ref": "#/components/responses/500" } }, + "security": [ + { + "apiKey": [], + "apiSecret": [] + }, + { + "BasicAuth": [] + } + ], "summary": "Snapshots", "tags": [ "Option" @@ -5502,6 +7149,15 @@ "$ref": "#/components/responses/500" } }, + "security": [ + { + "apiKey": [], + "apiSecret": [] + }, + { + "BasicAuth": [] + } + ], "summary": "Option chain", "tags": [ "Option" @@ -5595,6 +7251,15 @@ "$ref": "#/components/responses/500" } }, + "security": [ + { + "apiKey": [], + "apiSecret": [] + }, + { + "BasicAuth": [] + } + ], "summary": "Historical trades", "tags": [ "Option" @@ -5666,6 +7331,15 @@ "$ref": "#/components/responses/500" } }, + "security": [ + { + "apiKey": [], + "apiSecret": [] + }, + { + "BasicAuth": [] + } + ], "summary": "Latest trades", "tags": [ "Option" @@ -5758,6 +7432,15 @@ "$ref": "#/components/responses/500" } }, + "security": [ + { + "apiKey": [], + "apiSecret": [] + }, + { + "BasicAuth": [] + } + ], "summary": "Top market movers", "tags": [ "Screener" @@ -5814,6 +7497,15 @@ "$ref": "#/components/responses/500" } }, + "security": [ + { + "apiKey": [], + "apiSecret": [] + }, + { + "BasicAuth": [] + } + ], "summary": "Most active stocks", "tags": [ "Screener" @@ -5909,7 +7601,15 @@ "$ref": "#/components/responses/500" } }, - "security": [], + "security": [ + { + "apiKey": [], + "apiSecret": [] + }, + { + "BasicAuth": [] + } + ], "summary": "Historical bars", "tags": [ "Crypto" @@ -5984,7 +7684,15 @@ "$ref": "#/components/responses/500" } }, - "security": [], + "security": [ + { + "apiKey": [], + "apiSecret": [] + }, + { + "BasicAuth": [] + } + ], "summary": "Latest bars", "tags": [ "Crypto" @@ -6072,7 +7780,15 @@ "$ref": "#/components/responses/500" } }, - "security": [], + "security": [ + { + "apiKey": [], + "apiSecret": [] + }, + { + "BasicAuth": [] + } + ], "summary": "Latest orderbook", "tags": [ "Crypto" @@ -6151,7 +7867,15 @@ "$ref": "#/components/responses/500" } }, - "security": [], + "security": [ + { + "apiKey": [], + "apiSecret": [] + }, + { + "BasicAuth": [] + } + ], "summary": "Latest quotes", "tags": [ "Crypto" @@ -6230,7 +7954,15 @@ "$ref": "#/components/responses/500" } }, - "security": [], + "security": [ + { + "apiKey": [], + "apiSecret": [] + }, + { + "BasicAuth": [] + } + ], "summary": "Latest trades", "tags": [ "Crypto" @@ -6329,7 +8061,15 @@ "$ref": "#/components/responses/500" } }, - "security": [], + "security": [ + { + "apiKey": [], + "apiSecret": [] + }, + { + "BasicAuth": [] + } + ], "summary": "Historical quotes", "tags": [ "Crypto" @@ -6440,7 +8180,15 @@ "$ref": "#/components/responses/500" } }, - "security": [], + "security": [ + { + "apiKey": [], + "apiSecret": [] + }, + { + "BasicAuth": [] + } + ], "summary": "Snapshots", "tags": [ "Crypto" @@ -6546,7 +8294,15 @@ "$ref": "#/components/responses/500" } }, - "security": [], + "security": [ + { + "apiKey": [], + "apiSecret": [] + }, + { + "BasicAuth": [] + } + ], "summary": "Historical trades", "tags": [ "Crypto" @@ -6555,7 +8311,7 @@ }, "/v1/corporate-actions": { "get": { - "description": "This endpoint provides data about the corporate actions for each given symbol over a specified time period.\n\n> ⚠️ Warning\n>\n> Currently Alpaca has no guarantees on the creation time of corporate actions. There may be delays in receiving corporate actions from our data providers, and there may be delays in processing and making them available via this API. As a result, corporate actions may not be available immediately after they are announced.\n", + "description": "This endpoint provides data about the corporate actions for each given symbol over a specified time period.\n\nBy default (`data_quality=complete`), corporate actions that are still incomplete (for example, missing required fields such as ex-date or CUSIP/ISIN) and have not yet been processed are excluded from the response. Pass `data_quality=all` to also receive those early, incomplete records. Already-processed corporate actions are always returned when `data_quality=complete`, even if they would otherwise be considered incomplete.\n\n> ⚠️ Warning\n>\n> Currently Alpaca has no guarantees on the creation time of corporate actions. There may be delays in receiving corporate actions from our data providers, and there may be delays in processing and making them available via this API. As a result, corporate actions may not be available immediately after they are announced.\n", "operationId": "CorporateActions", "parameters": [ { @@ -6582,6 +8338,9 @@ { "$ref": "#/components/parameters/cas_limit" }, + { + "$ref": "#/components/parameters/cas_data_quality" + }, { "$ref": "#/components/parameters/page_token" }, @@ -6627,6 +8386,15 @@ "$ref": "#/components/responses/500" } }, + "security": [ + { + "apiKey": [], + "apiSecret": [] + }, + { + "BasicAuth": [] + } + ], "summary": "Corporate actions", "tags": [ "Corporate actions" @@ -6745,6 +8513,15 @@ "$ref": "#/components/responses/500" } }, + "security": [ + { + "apiKey": [], + "apiSecret": [] + }, + { + "BasicAuth": [] + } + ], "summary": "Historical auctions (single)", "tags": [ "Stock" @@ -6848,6 +8625,15 @@ "$ref": "#/components/responses/500" } }, + "security": [ + { + "apiKey": [], + "apiSecret": [] + }, + { + "BasicAuth": [] + } + ], "summary": "Historical bars (single symbol)", "tags": [ "Stock" @@ -6924,6 +8710,15 @@ "$ref": "#/components/responses/500" } }, + "security": [ + { + "apiKey": [], + "apiSecret": [] + }, + { + "BasicAuth": [] + } + ], "summary": "Latest bar (single symbol)", "tags": [ "Stock" @@ -7037,6 +8832,15 @@ "$ref": "#/components/responses/500" } }, + "security": [ + { + "apiKey": [], + "apiSecret": [] + }, + { + "BasicAuth": [] + } + ], "summary": "Historical quotes (single symbol)", "tags": [ "Stock" @@ -7116,6 +8920,15 @@ "$ref": "#/components/responses/500" } }, + "security": [ + { + "apiKey": [], + "apiSecret": [] + }, + { + "BasicAuth": [] + } + ], "summary": "Latest quote (single symbol)", "tags": [ "Stock" @@ -7237,6 +9050,15 @@ "$ref": "#/components/responses/500" } }, + "security": [ + { + "apiKey": [], + "apiSecret": [] + }, + { + "BasicAuth": [] + } + ], "summary": "Snapshot (single symbol)", "tags": [ "Stock" @@ -7348,6 +9170,15 @@ "$ref": "#/components/responses/500" } }, + "security": [ + { + "apiKey": [], + "apiSecret": [] + }, + { + "BasicAuth": [] + } + ], "summary": "Historical trades (single symbol)", "tags": [ "Stock" @@ -7426,6 +9257,15 @@ "$ref": "#/components/responses/500" } }, + "security": [ + { + "apiKey": [], + "apiSecret": [] + }, + { + "BasicAuth": [] + } + ], "summary": "Latest trade (single symbol)", "tags": [ "Stock" @@ -7670,6 +9510,15 @@ "$ref": "#/components/responses/500" } }, + "security": [ + { + "apiKey": [], + "apiSecret": [] + }, + { + "BasicAuth": [] + } + ], "summary": "Historical auctions", "tags": [ "Stock" @@ -7774,6 +9623,15 @@ "$ref": "#/components/responses/500" } }, + "security": [ + { + "apiKey": [], + "apiSecret": [] + }, + { + "BasicAuth": [] + } + ], "summary": "Historical bars", "tags": [ "Stock" @@ -7861,6 +9719,15 @@ "$ref": "#/components/responses/500" } }, + "security": [ + { + "apiKey": [], + "apiSecret": [] + }, + { + "BasicAuth": [] + } + ], "summary": "Latest bars", "tags": [ "Stock" @@ -7938,6 +9805,15 @@ "$ref": "#/components/responses/500" } }, + "security": [ + { + "apiKey": [], + "apiSecret": [] + }, + { + "BasicAuth": [] + } + ], "summary": "Condition codes", "tags": [ "Stock" @@ -7986,6 +9862,15 @@ "$ref": "#/components/responses/500" } }, + "security": [ + { + "apiKey": [], + "apiSecret": [] + }, + { + "BasicAuth": [] + } + ], "summary": "Exchange codes", "tags": [ "Stock" @@ -8100,6 +9985,15 @@ "$ref": "#/components/responses/500" } }, + "security": [ + { + "apiKey": [], + "apiSecret": [] + }, + { + "BasicAuth": [] + } + ], "summary": "Historical quotes", "tags": [ "Stock" @@ -8193,6 +10087,15 @@ "$ref": "#/components/responses/500" } }, + "security": [ + { + "apiKey": [], + "apiSecret": [] + }, + { + "BasicAuth": [] + } + ], "summary": "Latest quotes", "tags": [ "Stock" @@ -8372,6 +10275,15 @@ "$ref": "#/components/responses/500" } }, + "security": [ + { + "apiKey": [], + "apiSecret": [] + }, + { + "BasicAuth": [] + } + ], "summary": "Snapshots", "tags": [ "Stock" @@ -8486,6 +10398,15 @@ "$ref": "#/components/responses/500" } }, + "security": [ + { + "apiKey": [], + "apiSecret": [] + }, + { + "BasicAuth": [] + } + ], "summary": "Historical trades", "tags": [ "Stock" @@ -8566,6 +10487,15 @@ "$ref": "#/components/responses/500" } }, + "security": [ + { + "apiKey": [], + "apiSecret": [] + }, + { + "BasicAuth": [] + } + ], "summary": "Latest trades", "tags": [ "Stock" @@ -8573,12 +10503,6 @@ } } }, - "security": [ - { - "apiKey": [], - "apiSecret": [] - } - ], "servers": [ { "description": "Production", @@ -8606,10 +10530,6 @@ "description": "Endpoints for cryptocurrencies.", "name": "Crypto" }, - { - "description": "Endpoints for crypto perpetual futures.", - "name": "Crypto perpetual futures" - }, { "description": "Endpoints for fixed income data.", "name": "Fixed income" @@ -8633,6 +10553,10 @@ { "description": "Corporate actions (splits, dividends, etc.).", "name": "Corporate actions" + }, + { + "description": "Endpoints for event contract (prediction market) assets metadata.", + "name": "Event contracts" } ] } diff --git a/module/trading/openapi.json b/module/trading/openapi.json index a8748e87..c7170a87 100644 --- a/module/trading/openapi.json +++ b/module/trading/openapi.json @@ -269,6 +269,45 @@ "type": "limit", "updated_at": "2023-12-12T21:35:49.102504673Z" } + }, + "TravelRuleValidationError": { + "description": "The request requires the beneficiary's name before the transfer can proceed.", + "summary": "Missing beneficiary name", + "value": { + "error_code": "BENEFICIARY_NAME_REQUIRED", + "message": "Please provide the beneficiary's name. Select one of the following options.", + "next_actions": [ + { + "description": "Provide the recipient's first and last name.", + "name": "supply_beneficiaryName_naturalPerson", + "required_fields": [ + { + "description": "The recipient's first name.", + "field": "beneficiary_given_name", + "value_type": "string" + }, + { + "description": "The recipient's last name.", + "field": "beneficiary_family_name", + "value_type": "string" + } + ], + "type": "ONE_OF" + }, + { + "description": "Provide the entity's legal name.", + "name": "supply_beneficiaryName_legalEntity", + "required_fields": [ + { + "description": "The legal name of the receiving entity.", + "field": "beneficiary_entity_name", + "value_type": "string" + } + ], + "type": "ONE_OF" + } + ] + } } }, "headers": { @@ -295,6 +334,16 @@ } }, "parameters": { + "LegacyIdempotencyKey": { + "description": "Optional client-generated key for safe retries and duplicate request detection.\nThis endpoint currently accepts keys up to 128 characters. Alpaca is moving toward\na 36-character maximum; new implementations should generate a unique UUIDv7 or\nUUIDv4 value (36 characters including hyphens) for each logical operation. Do not\nreuse a key across operations.\n", + "in": "header", + "name": "Idempotency-Key", + "required": false, + "schema": { + "maxLength": 128, + "type": "string" + } + }, "PageToken": { "description": "Used for pagination, this token retrieves the next page of results. It is obtained from the response of the preceding page when additional pages are available.", "in": "query", @@ -334,8 +383,7 @@ "name": "end", "schema": { "format": "date-time", - "type": "string", - "x-go-type": "legacyTime" + "type": "string" } }, "legacy_start": { @@ -344,8 +392,7 @@ "name": "start", "schema": { "format": "date-time", - "type": "string", - "x-go-type": "legacyTime" + "type": "string" } }, "market": { @@ -415,12 +462,6 @@ } } }, - "401": { - "description": "Authentication headers are missing or invalid. Make sure you authenticate your request with a valid API key.\n" - }, - "403": { - "description": "The requested resource is forbidden.\n" - }, "429": { "description": "Too many requests. You hit the rate limit. Use the X-RateLimit-... response headers to make sure you're under the rate limit.\n", "headers": { @@ -478,6 +519,39 @@ }, "Account": { "description": "The account API serves important information related to an account, including account status, funds available for trade, funds available for withdrawal, and various flags relevant to an account's ability to trade. An account maybe be blocked for just for trades (trades_blocked flag) or for both trades and transfers (account_blocked flag) if Alpaca identifies the account to engaging in any suspicious activity. Please note that cryptocurrencies are not eligible assets to be used as collateral for margin accounts and will require the asset be traded using cash only.\n", + "examples": [ + { + "account_blocked": false, + "account_number": "010203ABCD", + "balance_asof": "2023-09-27", + "buying_power": "262113.632", + "cash": "-23140.2", + "created_at": "2019-06-12T22:47:07.99658Z", + "currency": "USD", + "equity": "103820.56", + "id": "e6fe16f3-64a4-4921-8928-cadf02f92f98", + "initial_margin": "63480.38", + "intraday_adjustments": "0", + "last_equity": "103529.24", + "last_maintenance_margin": "38000.832", + "long_market_value": "126960.76", + "maintenance_margin": "38088.228", + "multiplier": "4", + "options_approved_level": 2, + "options_buying_power": "40340.18", + "options_trading_level": 1, + "pending_reg_taf_fees": "0", + "portfolio_value": "103820.56", + "regt_buying_power": "80680.36", + "short_market_value": "0", + "shorting_enabled": true, + "sma": "0", + "status": "ACTIVE", + "trade_suspended_by_user": false, + "trading_blocked": false, + "transfers_blocked": false + } + ], "properties": { "account_blocked": { "description": "If true, the account activity by user is prohibited.", @@ -638,43 +712,20 @@ "status" ], "title": "Account", - "type": "object", - "x-examples": { - "example-1": { - "account_blocked": false, - "account_number": "010203ABCD", - "balance_asof": "2023-09-27", - "buying_power": "262113.632", - "cash": "-23140.2", - "created_at": "2019-06-12T22:47:07.99658Z", - "currency": "USD", - "equity": "103820.56", - "id": "e6fe16f3-64a4-4921-8928-cadf02f92f98", - "initial_margin": "63480.38", - "intraday_adjustments": "0", - "last_equity": "103529.24", - "last_maintenance_margin": "38000.832", - "long_market_value": "126960.76", - "maintenance_margin": "38088.228", - "multiplier": "4", - "options_approved_level": 2, - "options_buying_power": "40340.18", - "options_trading_level": 1, - "pending_reg_taf_fees": "0", - "portfolio_value": "103820.56", - "regt_buying_power": "80680.36", - "short_market_value": "0", - "shorting_enabled": true, - "sma": "0", - "status": "ACTIVE", - "trade_suspended_by_user": false, - "trading_blocked": false, - "transfers_blocked": false - } - } + "type": "object" }, "AccountConfigurations": { "description": "The account configuration API provides custom configurations about your trading account settings. These configurations control various allow you to modify settings to suit your trading needs.", + "examples": [ + { + "disable_overnight_trading": false, + "fractional_trading": true, + "max_margin_multiplier": "4", + "no_shorting": false, + "suspend_trade": false, + "trade_confirm_email": "all" + } + ], "properties": { "disable_overnight_trading": { "description": "If true, overnight trading is disabled.", @@ -716,17 +767,7 @@ } }, "title": "AccountConfigurations", - "type": "object", - "x-examples": { - "example-1": { - "disable_overnight_trading": false, - "fractional_trading": true, - "max_margin_multiplier": "4", - "no_shorting": false, - "suspend_trade": false, - "trade_confirm_email": "all" - } - } + "type": "object" }, "AccountStatus": { "description": "An enum representing the various possible account status values.\n\nMost likely, the account status is ACTIVE unless there is any problem. The account status may get in ACCOUNT_UPDATED when personal information is being updated from the dashboard, in which case you may not be allowed trading for a short period of time until the change is approved.\n\n- ONBOARDING\n The account is onboarding.\n- SUBMISSION_FAILED\n The account application submission failed for some reason.\n- SUBMITTED\n The account application has been submitted for review.\n- ACCOUNT_UPDATED\n The account information is being updated.\n- APPROVAL_PENDING\n The final account approval is pending.\n- ACTIVE\n The account is active for trading.\n- REJECTED\n The account application has been rejected.", @@ -747,11 +788,11 @@ "LIMITED" ], "example": "ACTIVE", + "examples": [ + "ACTIVE" + ], "title": "AccountStatus", - "type": "string", - "x-examples": { - "example-1": "ACTIVE" - } + "type": "string" }, "ActivityEventV2": { "allOf": [ @@ -872,12 +913,12 @@ "type": "object" }, "ActivitySubType": { - "description": "Represents a more specific classification to the `activity_type`.\nThis field is optional and may not always be populated, depending on the activity type and the available data.\nEach `activity_type` has a set of valid `activity_sub_type` values.\n\nFull mapping of `activity_type` to `activity_sub_type`:\n\n- **DIV**: Dividend activity sub-types:\n - **CDIV**: Cash Dividend\n - **SDIV**: Stock Dividend\n - **SPD**: Substitute Payment In Lieu Of Dividend\n\n- **FEE**: Fee-related activity sub-types:\n - **REG**: Regulatory Fee\n - **TAF**: Trading Activity Fee\n - **LCT**: Local Currency Trading Fee\n - **ORF**: Options Regulatory Fee\n - **OCC**: Options Clearing Corporation Fee\n - **NRC**: Non-Retail Commission Fee\n - **NRV**: Non-Retail Venue Fee\n - **COM**: Commission\n - **CAT**: Consolidated Audit Trail Fee\n\n- **INT**: Interest-related activity sub-types:\n - **MGN**: Margin Interest\n - **CDT**: Credit Interest\n - **SWP**: Sweep Interest\n - **QII**: Qualified Interest\n\n- **MA**: Merger and Acquisition activity sub-types:\n - **CMA**: Cash Merger\n - **SMA**: Stock Merger\n - **SCMA**: Stock & Cash Merger\n\n- **NC**: Name Change activity sub types\n - **SNC**: Symbol Name Change\n - **CNC**: CUSIP Name Change\n - **SCNC**: Symbol & CUSIP Name Change\n\n- **OPCA**: Option Corporate Action activity sub-types:\n - **DIV.CDIV**: Cash Dividend\n - **DIV.SDIV**: Stock Dividend\n - **MA.CMA**: Cash Merger\n - **MA.SMA**: Stock Merger\n - **MA.SCMA**: Stock & Cash Merger\n - **NC.CNC**: CUSIP Name Change\n - **NC.SNC**: Symbol Name Change\n - **NC.SCNC**: Symbol & CUSIP Name Change\n - **SPIN**: Spin-off\n - **SPLIT.FSPLIT**: Forward Stock Split\n - **SPLIT.RSPLIT**: Reverse Stock Split\n - **SPLIT.USPLIT**: Unit Split\n\n- **REORG**: Reorganization activity sub-types:\n - **WRM**: Worthless Removal\n\n- **SPLIT**: Stock Split activity sub-types:\n - **FSPLIT**: Forward Stock Split\n - **RSPLIT**: Reverse Stock Split\n - **USPLIT**: Unit Split\n\n- **VOF**: Voluntary Offering activity sub-types:\n - **VTND**: Tender Offer\n - **VWRT**: Warrant Exercise\n - **VRGT**: Rights Offer\n - **VEXH**: Exchange Offer\n\n- **WH**: Withholding activity sub-types:\n - **SWH**: State Withholding\n - **FWH**: Federal Withholding\n - **SLWH**: Sales Withholding", + "description": "Represents a more specific classification to the `activity_type`.\nThis field is optional and may not always be populated, depending on the activity type and the available data.\nEach `activity_type` has a set of valid `activity_sub_type` values.\n\nFull mapping of `activity_type` to `activity_sub_type`:\n\n- **CGD**: Capital Gains Distribution activity sub-types:\n - **LTCG**: Long-Term Capital Gain\n - **STCG**: Short-Term Capital Gain\n\n- **DIV**: Dividend activity sub-types:\n - **CDIV**: Cash Dividend\n - **ROC**: Return of Capital\n - **SDIV**: Stock Dividend\n - **SPD**: Substitute Payment In Lieu Of Dividend\n\n- **FEE**: Fee-related activity sub-types:\n - **REG**: Regulatory Fee\n - **TAF**: Trading Activity Fee\n - **LCT**: Local Currency Trading Fee\n - **ORF**: Options Regulatory Fee\n - **OCC**: Options Clearing Corporation Fee\n - **NRC**: Non-Retail Commission Fee\n - **NRV**: Non-Retail Venue Fee\n - **COM**: Commission\n - **CAT**: Consolidated Audit Trail Fee\n\n- **INT**: Interest-related activity sub-types:\n - **MGN**: Margin Interest\n - **CDT**: Credit Interest\n - **SWP**: Sweep Interest\n - **QII**: Qualified Interest\n - **FI**: Fixed Income Interest (coupon or accrued)\n\n- **MA**: Merger and Acquisition activity sub-types:\n - **CMA**: Cash Merger\n - **SMA**: Stock Merger\n - **SCMA**: Stock & Cash Merger\n\n- **NC**: Name Change activity sub types\n - **SNC**: Symbol Name Change\n - **CNC**: CUSIP Name Change\n - **SCNC**: Symbol & CUSIP Name Change\n\n- **OPCA**: Option Corporate Action activity sub-types:\n - **DIV.CDIV**: Cash Dividend\n - **DIV.ROC**: Return of Capital\n - **DIV.SDIV**: Stock Dividend\n - **MA.CMA**: Cash Merger\n - **MA.SMA**: Stock Merger\n - **MA.SCMA**: Stock & Cash Merger\n - **NC.CNC**: CUSIP Name Change\n - **NC.SNC**: Symbol Name Change\n - **NC.SCNC**: Symbol & CUSIP Name Change\n - **SPIN**: Spin-off\n - **SPLIT.FSPLIT**: Forward Stock Split\n - **SPLIT.RSPLIT**: Reverse Stock Split\n - **SPLIT.USPLIT**: Unit Split\n\n- **REO**: Reorganization activity sub-types\n - **REOS**: Stock only (1 or more stock legs)\n - **REOC**: Cash only (1 cash leg)\n - **REOSC**: Stock and cash\n\n- **REORG**: Activity sub-types:\n - **WRM**: Worthless Removal\n\n- **SPLIT**: Stock Split activity sub-types:\n - **FSPLIT**: Forward Stock Split\n - **RSPLIT**: Reverse Stock Split\n - **USPLIT**: Unit Split\n\n- **VOF**: Voluntary Offering activity sub-types:\n - **VTND**: Tender Offer\n - **VWRT**: Warrant Exercise\n - **VRGT**: Rights Offer\n - **VEXH**: Exchange Offer\n\n- **WH**: Withholding activity sub-types:\n - **SWH**: State Withholding\n - **FWH**: Federal Withholding\n - **SLWH**: Sales Withholding", "title": "ActivitySubType", "type": "string" }, "ActivityType": { - "description": "- FILL\n Order fills (both partial and full fills)\n\n- TRANS\n Cash transactions (both CSD and CSW)\n\n- MISC\n Miscellaneous or rarely used activity types (All types except those in TRANS, DIV, or FILL)\n\n- ACATC\n ACATS IN/OUT (Cash)\n\n- ACATS\n ACATS IN/OUT (Securities)\n\n- CFEE\n Crypto fee\n\n- CGD\n Capital gain distribution\n\n- CSD\n Cash deposit(+)\n\n- CSW\n Cash withdrawal(-)\n\n- DIV\n Dividends\n\n- DIVCGL\n Dividend (capital gain long term)\n\n- DIVCGS\n Dividend (capital gain short term)\n\n- DIVFEE\n Dividend fee\n\n- DIVFT\n Dividend adjusted (Foreign Tax Withheld)\n\n- DIVNRA\n Dividend adjusted (NRA Withheld)\n\n- DIVROC\n Dividend return of capital\n\n- DIVTW\n Dividend adjusted (Tefra Withheld)\n\n- DIVTXEX\n Dividend (tax exempt)\n\n- FEE\n Fee denominated in USD\n\n- INT\n Interest (credit/margin)\n\n- INTNRA\n Interest adjusted (NRA Withheld)\n\n- INTTW\n Interest adjusted (Tefra Withheld)\n\n- JNL\n Journal entry\n\n- JNLC\n Journal entry (cash)\n\n- JNLS\n Journal entry (stock)\n\n- MA\n Merger/Acquisition\n\n- NC\n Name change\n\n- OPASN\n Option assignment\n\n- OPCA\n Option corporate action\n\n- OPCSH\n Option cash deliverable for non-standard contracts\n\n- OPEXC\n Option exercise\n\n- OPEXP\n Option expiration\n\n- OPTRD\n Option trade\n\n- PTC\n Pass Thru Charge\n\n- PTR\n Pass Thru Rebate\n\n- REORG\n Reorg CA\n\n- SPIN\n Stock spinoff\n\n- SPLIT\n Stock split\n\n- FOPT\n Free of Payment Transfers", + "description": "- FILL\n Order fills (both partial and full fills)\n\n- TRANS\n Cash transactions (both CSD and CSW)\n\n- MISC\n Miscellaneous or rarely used activity types (All types except those in TRANS, DIV, or FILL)\n\n- ACATC\n ACATS IN/OUT (Cash)\n\n- ACATS\n ACATS IN/OUT (Securities)\n\n- CFEE\n Crypto fee\n\n- CGD\n Capital gains distribution\n\n- CSD\n Cash deposit(+)\n\n- CSW\n Cash withdrawal(-)\n\n- DIV\n Dividends\n\n- DIVCGL\n Dividend (capital gains long term)\n\n- DIVCGS\n Dividend (capital gains short term)\n\n- DIVFEE\n Dividend fee\n\n- DIVFT\n Dividend adjusted (Foreign Tax Withheld)\n\n- DIVNRA\n Dividend adjusted (NRA Withheld)\n\n- DIVROC\n Dividend return of capital\n\n- DIVTW\n Dividend adjusted (Tefra Withheld)\n\n- DIVTXEX\n Dividend (tax exempt)\n\n- FEE\n Fee denominated in USD\n\n- INT\n Interest (credit/margin)\n\n- INTNRA\n Interest adjusted (NRA Withheld)\n\n- INTTW\n Interest adjusted (Tefra Withheld)\n\n- JNL\n Journal entry\n\n- JNLC\n Journal entry (cash)\n\n- JNLS\n Journal entry (stock)\n\n- MA\n Merger/Acquisition\n\n- NC\n Name change\n\n- OPASN\n Option assignment\n\n- OPCA\n Option corporate action\n\n- OPCSH\n Option cash deliverable for non-standard contracts\n\n- OPEXC\n Option exercise\n\n- OPEXP\n Option expiration\n\n- OPTRD\n Option trade\n\n- PTC\n Pass Thru Charge\n\n- PTR\n Pass Thru Rebate\n\n- REO\n Reorganization\n\n- REORG\n Worthless removal CA\n\n- SPIN\n Stock spinoff\n\n- SPLIT\n Stock split\n\n- FOPT\n Free of Payment Transfers\n\n- OCT\n On chain transactions (blockchain deposits/withdrawals)", "enum": [ "FILL", "TRANS", @@ -914,210 +955,260 @@ "OPTRD", "PTC", "PTR", + "REO", "REORG", "SPIN", "SPLIT", - "FOPT" + "FOPT", + "OCT" + ], + "examples": [ + "FILL" ], "title": "ActivityType", - "type": "string", - "x-examples": { - "example-1": "FILL" - } + "type": "string" }, "ActivityV2DetailNTA": { - "allOf": [ + "oneOf": [ { - "oneOf": [ - { - "$ref": "#/components/schemas/DIVSPDActivityV2" - }, - { - "$ref": "#/components/schemas/CDIVActivityV2" - }, - { - "$ref": "#/components/schemas/SDIVActivityV2" - }, - { - "$ref": "#/components/schemas/ForwardSplitActivityV2" - }, - { - "$ref": "#/components/schemas/ReverseSplitActivityV2" - }, - { - "$ref": "#/components/schemas/UnitSplitActivityV2" - }, - { - "$ref": "#/components/schemas/SpinoffActivityV2" - }, - { - "$ref": "#/components/schemas/MAActivityV2" - }, - { - "$ref": "#/components/schemas/NCActivityV2" - }, - { - "$ref": "#/components/schemas/FixedIncomeRedemptionActivityV2" - }, - { - "$ref": "#/components/schemas/RightsDistributionActivityV2" - }, - { - "$ref": "#/components/schemas/RightsSubscriptionElectionActivityV2" - }, - { - "$ref": "#/components/schemas/TenderOfferActivityV2" - }, - { - "$ref": "#/components/schemas/ExchangeOfferActivityV2" - }, - { - "$ref": "#/components/schemas/WarrantExerciseElectionActivityV2" - }, - { - "$ref": "#/components/schemas/WRMActivityV2" - }, - { - "$ref": "#/components/schemas/OpcaCDIVActivityV2" - }, - { - "$ref": "#/components/schemas/OpcaSDIVActivityV2" - }, - { - "$ref": "#/components/schemas/OpcaMAActivityV2" - }, - { - "$ref": "#/components/schemas/OpcaNCActivityV2" - }, - { - "$ref": "#/components/schemas/OpcaSPINActivityV2" - }, - { - "$ref": "#/components/schemas/OpcaFSPLITActivityV2" - }, - { - "$ref": "#/components/schemas/OpcaUSPLITActivityV2" - }, - { - "$ref": "#/components/schemas/OpcaRSPLITActivityV2" - }, - { - "$ref": "#/components/schemas/OPASNActivityV2" - }, - { - "$ref": "#/components/schemas/OPEXCActivityV2" - }, - { - "$ref": "#/components/schemas/OPEXPActivityV2" - }, - { - "$ref": "#/components/schemas/OPTRDActivityV2" - }, - { - "$ref": "#/components/schemas/AcatsActivityV2" - }, - { - "$ref": "#/components/schemas/AcatcActivityV2" - }, - { - "$ref": "#/components/schemas/FOPTActivityV2" - }, - { - "$ref": "#/components/schemas/DIVNRAActivityV2" - }, - { - "$ref": "#/components/schemas/JNLSActivityV2" - }, - { - "$ref": "#/components/schemas/JNLCActivityV2" - }, - { - "$ref": "#/components/schemas/CSWActivityV2" - }, - { - "$ref": "#/components/schemas/FEEActivityV2" - } - ] - } - ], - "type": "object" - }, - "ActivityV2DetailTRD": { - "description": "Activity details for a fill or partial_fill event", - "properties": { - "asset_id": { - "description": "Asset ID (For options this represents the option contract ID)", - "format": "uuid", - "type": "string" + "$ref": "#/components/schemas/DIVSPDActivityV2" }, - "client_order_id": { - "description": "Order ID provided by the customer", - "type": "string" + { + "$ref": "#/components/schemas/CDIVActivityV2" }, - "commission": { - "description": "Commission to collect from the account holder", - "format": "decimal", - "type": "string" + { + "$ref": "#/components/schemas/SDIVActivityV2" }, - "cum_qty": { - "description": "Total filled quantity on the order", - "format": "decimal", - "type": "string" + { + "$ref": "#/components/schemas/CGDActivityV2" }, - "cusip": { - "description": "CUSIP of the security involved with the activity", - "type": "string" + { + "$ref": "#/components/schemas/ForwardSplitActivityV2" }, - "execution_type": { - "description": "The execution type", - "enum": [ - "fill", - "trade_correct", - "trade_bust" - ], - "type": "string" + { + "$ref": "#/components/schemas/ReverseSplitActivityV2" }, - "leaves_qty": { - "description": "Unfilled quantity on the order, when order is filled value could be 0", - "format": "decimal", - "type": "string" + { + "$ref": "#/components/schemas/UnitSplitActivityV2" }, - "order_id": { - "description": "Order ID generated by Alpaca", - "format": "uuid", - "type": "string" + { + "$ref": "#/components/schemas/SpinoffActivityV2" }, - "order_status": { - "description": "Identifies the current status of the order", - "type": "string" + { + "$ref": "#/components/schemas/MAActivityV2" }, - "side": { - "description": "Represents what side of the transaction an order was on", - "enum": [ - "buy", - "sell" - ], - "type": "string" + { + "$ref": "#/components/schemas/NCActivityV2" }, - "symbol": { - "description": "The symbol of the security involved with the activity", - "type": "string" - } - }, - "required": [ - "order_id", - "side", - "symbol", - "asset_id", - "leaves_qty", + { + "$ref": "#/components/schemas/FixedIncomeRedemptionActivityV2" + }, + { + "$ref": "#/components/schemas/FixedIncomeInterestActivityV2" + }, + { + "$ref": "#/components/schemas/REOActivityV2" + }, + { + "$ref": "#/components/schemas/RightsDistributionActivityV2" + }, + { + "$ref": "#/components/schemas/RightsSubscriptionElectionActivityV2" + }, + { + "$ref": "#/components/schemas/TenderOfferActivityV2" + }, + { + "$ref": "#/components/schemas/ExchangeOfferActivityV2" + }, + { + "$ref": "#/components/schemas/WarrantExerciseElectionActivityV2" + }, + { + "$ref": "#/components/schemas/WRMActivityV2" + }, + { + "$ref": "#/components/schemas/OpcaCDIVActivityV2" + }, + { + "$ref": "#/components/schemas/OpcaSDIVActivityV2" + }, + { + "$ref": "#/components/schemas/OpcaMAActivityV2" + }, + { + "$ref": "#/components/schemas/OpcaNCActivityV2" + }, + { + "$ref": "#/components/schemas/OpcaSPINActivityV2" + }, + { + "$ref": "#/components/schemas/OpcaFSPLITActivityV2" + }, + { + "$ref": "#/components/schemas/OpcaUSPLITActivityV2" + }, + { + "$ref": "#/components/schemas/OpcaRSPLITActivityV2" + }, + { + "$ref": "#/components/schemas/OPASNActivityV2" + }, + { + "$ref": "#/components/schemas/OPEXCActivityV2" + }, + { + "$ref": "#/components/schemas/OPEXPActivityV2" + }, + { + "$ref": "#/components/schemas/OPTRDActivityV2" + }, + { + "$ref": "#/components/schemas/OPCSHActivityV2" + }, + { + "$ref": "#/components/schemas/AcatsActivityV2" + }, + { + "$ref": "#/components/schemas/AcatcActivityV2" + }, + { + "$ref": "#/components/schemas/FOPTActivityV2" + }, + { + "$ref": "#/components/schemas/DIVNRAActivityV2" + }, + { + "$ref": "#/components/schemas/DIVWHActivityV2" + }, + { + "$ref": "#/components/schemas/JNLSActivityV2" + }, + { + "$ref": "#/components/schemas/JNLCActivityV2" + }, + { + "$ref": "#/components/schemas/CSWActivityV2" + }, + { + "$ref": "#/components/schemas/FEEActivityV2" + }, + { + "$ref": "#/components/schemas/MEMActivityV2" + }, + { + "$ref": "#/components/schemas/OCTActivityV2" + } + ], + "type": "object" + }, + "ActivityV2DetailTRD": { + "description": "Activity details for a fill or partial_fill event", + "properties": { + "asset_id": { + "description": "Asset ID (For options this represents the option contract ID)", + "format": "uuid", + "type": "string" + }, + "client_order_id": { + "description": "Order ID provided by the customer", + "type": "string" + }, + "commission": { + "description": "Commission to collect from the account holder", + "format": "decimal", + "type": "string" + }, + "cum_qty": { + "description": "Total filled quantity on the order", + "format": "decimal", + "type": "string" + }, + "cusip": { + "description": "CUSIP of the security involved with the activity", + "type": "string" + }, + "execution_type": { + "description": "The execution type", + "enum": [ + "fill", + "trade_correct", + "trade_bust" + ], + "type": "string" + }, + "leaves_qty": { + "description": "Unfilled quantity on the order, when order is filled value could be 0", + "format": "decimal", + "type": "string" + }, + "order_id": { + "description": "Order ID generated by Alpaca", + "format": "uuid", + "type": "string" + }, + "order_status": { + "description": "Identifies the current status of the order", + "type": "string" + }, + "side": { + "description": "Represents what side of the transaction an order was on", + "enum": [ + "buy", + "sell" + ], + "type": "string" + }, + "symbol": { + "description": "The symbol of the security involved with the activity", + "type": "string" + } + }, + "required": [ + "order_id", + "side", + "symbol", + "asset_id", + "leaves_qty", "cum_qty", "order_status", "execution_type" ], "type": "object" }, + "AddAssetToWatchlistRequest": { + "description": "Request format used for appending an asset to an existing watchlist.", + "properties": { + "symbol": { + "description": "the symbol name to add to the watchlist", + "example": "AAPL", + "type": "string" + } + }, + "title": "AddAssetToWatchlistRequest", + "type": "object" + }, "AdvancedInstructions": { "description": "Advanced instructions for Elite Smart Router: https://docs.alpaca.markets/docs/alpaca-elite-smart-router", + "examples": [ + { + "algorithm": "DMA", + "destination": "NYSE", + "display_qty": "100" + }, + { + "algorithm": "TWAP", + "end_time": "2025-07-21T15:30:00-04:00", + "max_percentage": "0.314", + "start_time": "2025-07-21T09:30:00-04:00" + }, + { + "algorithm": "VWAP", + "end_time": "2025-07-21T15:30:00-04:00", + "max_percentage": "0.314", + "start_time": "2025-07-21T09:30:00-04:00" + } + ], "properties": { "algorithm": { "description": "The advanced routing algorithm to use for the order", @@ -1134,7 +1225,9 @@ "enum": [ "NYSE", "NASDAQ", - "ARCA" + "ARCA", + "IEX", + "MEMX" ], "example": "NYSE", "type": "string" @@ -1165,26 +1258,7 @@ } }, "title": "AdvancedInstructions", - "type": "object", - "x-examples": { - "example-1": { - "algorithm": "DMA", - "destination": "NYSE", - "display_qty": 100 - }, - "example-2": { - "algorithm": "TWAP", - "end_time": "2025-07-21T15:30:00-04:00", - "max_percentage": 0.314, - "start_time": "2025-07-21T09:30:00-04:00" - }, - "example-3": { - "algorithm": "VWAP", - "end_time": "2025-07-21T15:30:00-04:00", - "max_percentage": 0.314, - "start_time": "2025-07-21T09:30:00-04:00" - } - } + "type": "object" }, "AssetAttribute": { "description": "Unique characteristic of an asset. Supported values:\n- `ptp_no_exception`: Asset is a Publicly Traded Partnership (PTP) without a qualified notice; non-U.S. customers may incur 10% withholding on gross proceeds as per IRS guidance, and are blocked from being purchased by default.\n- `ptp_with_exception`: Users can open positions in these PTPs without general restrictions.\n- `ipo`: Accepting limit orders only before the stock begins trading on the secondary market.\n- `has_options`: The underlying equity has listed options available on the platform. Note: if the equity had inactive/expired contracts in the past, this will still show up.\n- `options_late_close`: Indicates the underlying asset's options contracts close at 4:15pm ET instead of the standard 4:00pm ET.\n- `fractional_eh_enabled`: Indicates the asset accepts fractional orders during extended hours sessions (pre-market, post-market, and overnight if enabled).\n- `overnight_tradable`: Asset is eligible for overnight (24x5) trading in supported venues on the platform.\n- `overnight_halted`: Asset is eligible for overnight trading but is currently halted/blocked for overnight sessions due to risk, corporate action, compliance, or venue constraints.", @@ -1216,14 +1290,30 @@ "ipo" ], "example": "us_equity", + "examples": [ + "us_equity" + ], "title": "AssetClass", - "type": "string", - "x-examples": { - "example-1": "us_equity" - } + "type": "string" }, "Assets": { "description": "The assets API serves as the master list of assets available for trade and data consumption from Alpaca. Assets are sorted by asset class, exchange and symbol. Some assets are only available for data consumption via Polygon, and are not tradable with Alpaca. These assets will be marked with the flag tradable=false.\n", + "examples": [ + { + "borrow_status": "easy_to_borrow", + "class": "us_equity", + "easy_to_borrow": true, + "exchange": "NASDAQ", + "fractionable": true, + "id": "b0b6dd9d-8b9b-48a9-ba46-b9d54906e415", + "marginable": true, + "name": "Apple Inc. Common Stock", + "shortable": true, + "status": "active", + "symbol": "AAPL", + "tradable": true + } + ], "properties": { "attributes": { "example": [ @@ -1250,18 +1340,10 @@ "cusip": { "description": "The CUSIP identifier for the asset (US Equities only).\nTo request a specific CUSIP, please reach out to Alpaca support.\n", "example": "987654321", - "nullable": true, - "type": "string" - }, - "easy_to_borrow": { - "deprecated": true, - "description": "**deprecated**: Please use borrow_status instead.\nAsset is easy-to-borrow or not (filtering for easy_to_borrow = True is the best way to check whether the name is currently available to short at Alpaca).\n", - "type": "boolean", - "x-deprecation": { - "reason": "Use borrow_status instead.", - "since": "2026-06-22", - "sunset": "2026-09-22" - } + "type": [ + "string", + "null" + ] }, "exchange": { "$ref": "#/components/schemas/Exchange" @@ -1281,11 +1363,15 @@ "type": "number" }, "margin_requirement_long": { - "description": "The margin requirement percentage for the asset's long positions (equities only).", + "description": "The margin requirement percentage for the asset's long positions (equities only), encoded as a decimal string.", + "example": "100", + "format": "decimal", "type": "string" }, "margin_requirement_short": { - "description": "The margin requirement percentage for the asset's short positions (equities only).", + "description": "The margin requirement percentage for the asset's short positions (equities only), encoded as a decimal string.", + "example": "30", + "format": "decimal", "type": "string" }, "marginable": { @@ -1345,27 +1431,10 @@ "tradable", "marginable", "shortable", - "easy_to_borrow", "fractionable" ], "title": "Assets", - "type": "object", - "x-examples": { - "example-1": { - "borrow_status": "easy_to_borrow", - "class": "us_equity", - "easy_to_borrow": true, - "exchange": "NASDAQ", - "fractionable": true, - "id": "b0b6dd9d-8b9b-48a9-ba46-b9d54906e415", - "marginable": true, - "name": "Apple Inc. Common Stock", - "shortable": true, - "status": "active", - "symbol": "AAPL", - "tradable": true - } - } + "type": "object" }, "CDIVActivityV2": { "allOf": [ @@ -1382,7 +1451,7 @@ "type": "string" }, "entitled_qty": { - "description": "Quantity of shares entitled to receive the dividend", + "description": "Quantity of shares entitled to receive the dividend or return of capital", "type": "string" } }, @@ -1393,56 +1462,127 @@ "type": "object" } ], - "description": "Cash dividend", + "description": "Cash-dividend related details. Used for both Cash Dividend and Return of Capital.", "type": "object" }, - "CSWActivityV2": { + "CGDActivityV2": { "allOf": [ { - "$ref": "#/components/schemas/CommonNTAActivityV2" + "$ref": "#/components/schemas/CommonCaActivityV2" }, { "properties": { - "bank_transaction_id": { - "description": "The bank transaction's ID", - "format": "uuid", + "cash_payout": { + "description": "Cash amount paid for this leg", "type": "string" - } - }, - "type": "object" - } - ], - "description": "Cash withdrawal", - "type": "object" - }, - "CanceledOrderResponse": { - "description": "Represents the result of a request to cancel and order", - "properties": { - "id": { - "description": "orderId", - "format": "uuid", - "type": "string" - }, - "status": { - "description": "http response code", - "example": 200, - "type": "integer" - } - }, - "title": "CanceledOrderResponse", - "type": "object", - "x-examples": { - "example-1": { - "id": "d56ba3ea-6d04-48ce-8175-817e242ee608", - "status": 200 - } - } - }, - "CommonAcatActivityV2": { - "properties": { - "external_id": { - "description": "The ID that DTCC assigned to this transfer", - "type": "string" + }, + "cusip": { + "description": "The CUSIP of the security involved with the activity", + "type": "string" + }, + "entitled_qty": { + "description": "Quantity of shares entitled to receive the capital gains distribution", + "type": "string" + }, + "ex_date": { + "description": "The ex_date for this corporate action", + "format": "date", + "minLength": 1, + "type": "string" + }, + "isin": { + "description": "The ISIN of the security involved with the activity", + "type": "string" + }, + "long_term_rate": { + "description": "Long-term capital gains distribution rate per share, when applicable", + "type": "string" + }, + "payable_date": { + "description": "The payable_date for this corporate action", + "format": "date", + "minLength": 1, + "type": "string" + }, + "rate": { + "description": "Distribution rate per share for this leg (LTCG or STCG)", + "type": "string" + }, + "record_date": { + "description": "The record_date for this corporate action", + "format": "date", + "minLength": 1, + "type": "string" + }, + "short_term_rate": { + "description": "Short-term capital gains distribution rate per share, when applicable", + "type": "string" + }, + "symbol": { + "description": "The symbol of the security involved with the activity", + "type": "string" + } + }, + "required": [ + "entitled_qty", + "cash_payout", + "symbol", + "cusip", + "rate" + ], + "type": "object" + } + ], + "description": "Capital gains distribution. Used for both Long-Term (LTCG) and Short-Term (STCG) sub-types. Each activity represents a single leg; `long_term_rate` and/or `short_term_rate` are populated based on the source event, and `rate` matches the leg's own rate.", + "type": "object" + }, + "CSWActivityV2": { + "allOf": [ + { + "$ref": "#/components/schemas/CommonNTAActivityV2" + }, + { + "properties": { + "bank_transaction_id": { + "description": "The bank transaction's ID", + "format": "uuid", + "type": "string" + } + }, + "type": "object" + } + ], + "description": "Cash withdrawal", + "type": "object" + }, + "CanceledOrderResponse": { + "description": "Represents the result of a request to cancel and order", + "examples": [ + { + "id": "d56ba3ea-6d04-48ce-8175-817e242ee608", + "status": 200 + } + ], + "properties": { + "id": { + "description": "orderId", + "format": "uuid", + "type": "string" + }, + "status": { + "description": "http response code", + "example": 200, + "type": "integer" + } + }, + "title": "CanceledOrderResponse", + "type": "object" + }, + "CommonAcatActivityV2": { + "properties": { + "external_id": { + "description": "The ID that DTCC assigned to this transfer", + "type": "string" }, "hold_date": { "description": "Hold date when the transfers settle", @@ -1487,8 +1627,17 @@ "type": "string" }, "foreign": { - "description": "Indicates if related to a non-US security", - "type": "boolean" + "description": "Indicates if related to a non-US security. Serialized as the JSON strings `\"true\"` or `\"false\"`, not a JSON boolean.", + "enum": [ + "true", + "false" + ], + "example": "false", + "type": "string" + }, + "isin": { + "description": "The ISIN of the security involved with the activity", + "type": "string" }, "payable_date": { "description": "The payable_date for this corporate action", @@ -1507,8 +1656,13 @@ "type": "string" }, "special": { - "description": "Indicates if this is a special dividend", - "type": "boolean" + "description": "Indicates if this is a special dividend. Serialized as the JSON strings `\"true\"` or `\"false\"`, not a JSON boolean.", + "enum": [ + "true", + "false" + ], + "example": "false", + "type": "string" }, "symbol": { "description": "The symbol of the security involved with the activity", @@ -1555,6 +1709,76 @@ ], "type": "object" }, + "CommonFixedIncomeInterestActivityV2": { + "description": "Fixed income interest fields (INT/FI). Variants from the same activity type:\n\n- Coupon: `interest_type` is `coupon`. Includes `rate` (no `price` / `accrued_interest_rate`). No `order_id`.\n- Accrued interest on call: `interest_type` is `accrued`. Includes `price` and `accrued_interest_rate` (no `rate`). No `order_id`.\n- Trade accrued: `interest_type` is `accrued`. Includes `order_id` so the interest NTA can be linked to the fill. `parent_id` is set when a parent trade `ref_id` is available.", + "properties": { + "accrued_interest_rate": { + "description": "Accrued interest rate (present for accrued-interest-on-call variants)", + "format": "decimal", + "type": "string" + }, + "cash_payout": { + "description": "The cash payout for this interest activity", + "format": "decimal", + "type": "string" + }, + "cusip": { + "description": "The CUSIP of the security involved with the activity", + "type": "string" + }, + "entitled_qty": { + "description": "The entitled quantity (principal / face amount)", + "example": "1000", + "type": "string" + }, + "interest_type": { + "$ref": "#/components/schemas/FixedIncomeInterestType" + }, + "isin": { + "description": "The ISIN of the security involved with the activity", + "type": "string" + }, + "order_id": { + "description": "The order that generated this accrued interest. Present for trade-accrued INT/FI.", + "example": "257bf308-a6b7-4777-8cf6-00dc441dc4d3", + "format": "uuid", + "type": "string" + }, + "parent_id": { + "description": "The ref_id of the parent trade this interest is attached to. Present for trade-accrued INT/FI when a parent trade `ref_id` is available.", + "example": "46aecae1-4629-4bcf-b441-0d8c847a1787", + "format": "uuid", + "type": "string" + }, + "payment_date": { + "description": "The payment date", + "format": "date", + "type": "string" + }, + "price": { + "description": "Call price used for accrued-interest-on-call variants", + "format": "decimal", + "type": "string" + }, + "rate": { + "description": "Coupon rate amount (present for regular coupon payments)", + "format": "decimal", + "type": "string" + }, + "record_date": { + "description": "The record date", + "format": "date", + "type": "string" + } + }, + "required": [ + "entitled_qty", + "cash_payout", + "cusip", + "interest_type" + ], + "type": "object" + }, "CommonJournalActivityV2": { "allOf": [ { @@ -1571,6 +1795,7 @@ "type": "object" } ], + "description": "Shared fields for journal non-trade activity records.", "type": "object" }, "CommonMAActivityV2": { @@ -1579,6 +1804,10 @@ "description": "CUSIP of the acquiree", "type": "string" }, + "acquiree_isin": { + "description": "ISIN of the acquiree", + "type": "string" + }, "acquiree_rate": { "description": "Rate of the acquiree", "type": "string" @@ -1591,6 +1820,10 @@ "description": "CUSIP of the acquirer", "type": "string" }, + "acquirer_isin": { + "description": "ISIN of the acquirer", + "type": "string" + }, "acquirer_rate": { "description": "Rate of the acquirer", "type": "string" @@ -1648,6 +1881,7 @@ "type": "object" }, "CommonNTAActivityV2": { + "description": "Shared fields for non-trade (NTA) activity records.", "properties": { "group_id": { "description": "Optional group ID which can help grouping together related activities", @@ -1705,12 +1939,111 @@ }, "CommonOptionsActivityV2": { "allOf": [ + { + "description": "Shared fields for option-related non-trade activity records.", + "properties": { + "contract_symbol": { + "description": "The contract symbol of the security involved with the activity", + "type": "string" + }, + "cusip": { + "description": "The CUSIP of the security involved with the activity", + "type": "string" + }, + "symbol": { + "description": "The symbol of the security involved with the activity", + "type": "string" + } + }, + "required": [ + "group_id" + ], + "type": "object" + }, { "$ref": "#/components/schemas/CommonNTAActivityV2" } - ], + ] + }, + "CommonREOActivityV2": { + "description": "Reorganization (REO) activity sub-types:\n- **REOS**: Stock only (1 or more stock legs)\n- **REOC**: Cash only (one cash leg)\n- **REOSC**: Stock and cash", + "properties": { + "cash_payout": { + "description": "Cash payout amount", + "format": "decimal", + "type": "string" + }, + "cash_rate": { + "description": "Cash rate per share", + "format": "decimal", + "type": "string" + }, + "cusip": { + "description": "CUSIP received on this stock leg", + "type": "string" + }, + "effective_date": { + "description": "Effective date of the reorganization", + "format": "date", + "type": "string" + }, + "isin": { + "description": "ISIN received on this stock leg", + "type": "string" + }, + "new_rate": { + "description": "New ratio for this stock leg", + "format": "decimal", + "type": "string" + }, + "payable_date": { + "description": "Payable date of the reorganization", + "format": "date", + "type": "string" + }, + "qty": { + "description": "Quantity received on this stock leg", + "format": "decimal", + "type": "string" + }, + "removed_qty": { + "description": "Positive quantity removed from the source position", + "format": "decimal", + "type": "string" + }, + "source_cusip": { + "description": "CUSIP of the source security", + "type": "string" + }, + "source_isin": { + "description": "ISIN of the source security", + "type": "string" + }, + "source_qty": { + "description": "Source position quantity", + "format": "decimal", + "type": "string" + }, + "source_rate": { + "description": "Source ratio for this stock leg", + "format": "decimal", + "type": "string" + }, + "source_symbol": { + "description": "Symbol of the source security", + "example": "CEPT", + "type": "string" + }, + "symbol": { + "description": "Symbol received on this stock leg", + "example": "SECZ", + "type": "string" + } + }, "required": [ - "group_id" + "source_symbol", + "source_cusip", + "payable_date" ], "type": "object" }, @@ -1726,6 +2059,10 @@ "minLength": 1, "type": "string" }, + "isin": { + "description": "The ISIN of the security involved with the activity", + "type": "string" + }, "payable_date": { "description": "The payable_date for this corporate action", "format": "date", @@ -1772,6 +2109,10 @@ "description": "CUSIP of the new security", "type": "string" }, + "new_isin": { + "description": "ISIN of the new security", + "type": "string" + }, "new_price": { "description": "Market price of new shares after the spinoff", "type": "string" @@ -1800,6 +2141,10 @@ "description": "CUSIP of the parent security", "type": "string" }, + "source_isin": { + "description": "ISIN of the parent security", + "type": "string" + }, "source_price": { "description": "Market price of parent shares before the spinoff", "type": "string" @@ -1831,6 +2176,10 @@ "description": "CUSIP of the new security after the split", "type": "string" }, + "new_isin": { + "description": "ISIN of the new security after the split", + "type": "string" + }, "new_rate": { "description": "Ratio of new shares received", "type": "string" @@ -1839,6 +2188,10 @@ "description": "CUSIP of the old security before the split", "type": "string" }, + "old_isin": { + "description": "ISIN of the old security before the split", + "type": "string" + }, "old_rate": { "description": "Ratio of old shares exchanged", "type": "string" @@ -1911,14 +2264,110 @@ ], "type": "object" }, - "CreateCryptoTransferRequest": { + "CorporateActionCaType": { + "description": "The type of corporate action.", + "enum": [ + "Spinoff", + "Merger", + "Split", + "Reorg", + "Dividend" + ], + "example": "Dividend", + "title": "CorporateActionCaType", + "type": "string" + }, + "CorporateAnnouncement": { + "description": "A corporate action announcement.", + "examples": [ + { + "ca_sub_type": "DIV", + "ca_type": "Dividend", + "cash": "0.018", + "corporate_action_id": "F58684224_XY37", + "declaration_date": "2021-01-05", + "effective_date": "2021-01-08", + "ex_date": "2021-01-12", + "id": "be3c368a-4c7c-4384-808e-f02c9f5a8afe", + "initiating_original_cusip": "55275E101", + "initiating_symbol": "MLLAX", + "new_rate": "1", + "old_rate": "1", + "payable_date": "2021-01-14", + "record_date": "2021-01-13", + "target_original_cusip": "55275E101", + "target_symbol": "MLLAX" + } + ], "properties": { - "address": { - "description": "The destination wallet address", + "ca_sub_type": { "type": "string" }, - "amount": { - "description": "The amount, denoted in the specified asset, to be withdrawn from the user's wallet", + "ca_type": { + "$ref": "#/components/schemas/CorporateActionCaType" + }, + "cash": { + "format": "decimal", + "type": "string" + }, + "corporate_action_id": { + "type": "string" + }, + "declaration_date": { + "format": "date", + "type": "string" + }, + "effective_date": { + "format": "date", + "type": "string" + }, + "ex_date": { + "format": "date", + "type": "string" + }, + "id": { + "type": "string" + }, + "initiating_original_cusip": { + "type": "string" + }, + "initiating_symbol": { + "type": "string" + }, + "new_rate": { + "format": "decimal", + "type": "string" + }, + "old_rate": { + "format": "decimal", + "type": "string" + }, + "payable_date": { + "format": "date", + "type": "string" + }, + "record_date": { + "format": "date", + "type": "string" + }, + "target_original_cusip": { + "type": "string" + }, + "target_symbol": { + "type": "string" + } + }, + "title": "CorporateAnnouncement", + "type": "object" + }, + "CreateCryptoTransferRequest": { + "properties": { + "address": { + "description": "The destination wallet address", + "type": "string" + }, + "amount": { + "description": "The amount, denoted in the specified asset, to be withdrawn from the user's wallet", "type": "string" }, "asset": { @@ -1955,8 +2404,10 @@ "limit_price": { "description": "Maximum acceptable locate fee per share, as a decimal string in USD.\nIf omitted, any quoted fee is accepted.\n", "example": "0.05", - "nullable": true, - "type": "string" + "type": [ + "string", + "null" + ] }, "qty": { "description": "Number of shares to locate. Must be positive and in round lots of 100; invalid quantities return HTTP 400.", @@ -1977,6 +2428,176 @@ ], "type": "object" }, + "CreateOrderRequest": { + "description": "Represents a request to create an order.", + "properties": { + "advanced_instructions": { + "$ref": "#/components/schemas/AdvancedInstructions" + }, + "client_order_id": { + "description": "A unique identifier for the order. Automatically generated if not sent. (<= 128 characters)", + "example": "5680c4bc-9ac1-4a12-a44c-df427ba53032", + "maxLength": 128, + "type": "string" + }, + "extended_hours": { + "description": "(default) false. If true, order will be eligible for execution in the pre-market, after-hours, and overnight sessions. Only works with type `limit` and time_in_force set to either `day` or `gtc`", + "example": false, + "type": "boolean" + }, + "legs": { + "description": "list of order legs (<= 4)", + "example": [ + { + "position_intent": "buy_to_open", + "ratio_qty": "3", + "side": "buy", + "symbol": "AAPL241213C00250000" + } + ], + "items": { + "$ref": "#/components/schemas/MLegOrderLeg" + }, + "maxItems": 4, + "type": "array" + }, + "limit_price": { + "description": "Required if type is `limit` or `stop_limit`.\nIn case of `mleg`, the limit_price parameter is expressed with the following notation:\n- A positive value indicates a debit, representing a cost or payment to be made.\n- A negative value signifies a credit, reflecting an amount to be received.", + "example": "150", + "type": "string" + }, + "notional": { + "description": "dollar amount to trade. Cannot work with `qty`. Can only work for market order types and day for time in force.", + "example": "500", + "type": "string" + }, + "order_class": { + "$ref": "#/components/schemas/OrderClass" + }, + "position_intent": { + "$ref": "#/components/schemas/PositionIntent" + }, + "qty": { + "description": "number of shares to trade. Can be fractionable for only market and day order types. Required for `mleg` order class, represents the number of units to trade of this strategy.", + "example": "2", + "type": "string" + }, + "side": { + "$ref": "#/components/schemas/OrderSide" + }, + "stop_loss": { + "description": "Takes in string/number values for stop_price and limit_price", + "properties": { + "limit_price": { + "example": "3.14", + "format": "decimal", + "type": "string" + }, + "stop_price": { + "example": "3.14", + "format": "decimal", + "type": "string" + } + }, + "type": "object" + }, + "stop_price": { + "description": "required if type is `stop` or `stop_limit`", + "example": "140", + "type": "string" + }, + "symbol": { + "description": "symbol, asset ID, or currency pair to identify the asset to trade, required for all order classes except for `mleg`.", + "example": "AAPL", + "type": "string" + }, + "take_profit": { + "description": "Takes in a string/number value for limit_price", + "properties": { + "limit_price": { + "example": "3.14", + "format": "decimal", + "type": "string" + } + }, + "type": "object" + }, + "time_in_force": { + "$ref": "#/components/schemas/TimeInForce" + }, + "trail_percent": { + "description": "this or `trail_price` is required if type is `trailing_stop`", + "example": "1.5", + "type": "string" + }, + "trail_price": { + "description": "this or `trail_percent` is required if type is `trailing_stop`", + "example": "1.00", + "type": "string" + }, + "type": { + "$ref": "#/components/schemas/OrderType" + } + }, + "required": [ + "type", + "time_in_force" + ], + "title": "CreateOrderRequest", + "type": "object" + }, + "CreateWatchlistRequest": { + "description": "Request format used for creating a new watchlist with a set of assets and name.", + "properties": { + "name": { + "description": "The watchlist name.", + "example": "Tech Stocks", + "type": "string" + }, + "symbols": { + "description": "List of asset symbols to include in the watchlist.", + "example": [ + "AAPL", + "TSLA" + ], + "items": { + "type": [ + "string", + "null" + ] + }, + "type": "array" + } + }, + "required": [ + "name" + ], + "title": "PostWatchlistRequest", + "type": "object" + }, + "CreateWhitelistedAddressRequest": { + "description": "Request format used for whitelisting a crypto address for transfers.", + "properties": { + "address": { + "description": "The address to be whitelisted", + "example": "5dXY1aH2tQpV3wXmJg6Z7c8B4nKvF9bA1pQrSt2uVwYxXz", + "type": "string" + }, + "asset": { + "description": "Symbol of underlying asset for the whitelisted address", + "example": "USDT", + "type": "string" + }, + "chain": { + "$ref": "#/components/schemas/CryptoChain" + }, + "travel_rule_info": { + "$ref": "#/components/schemas/TravelRuleInfo" + } + }, + "title": "CreateWhitelistedAddressRequest", + "type": "object" + }, "CryptoChain": { "description": "Chain identifier for multi-chain crypto assets.", "enum": [ @@ -2083,11 +2704,23 @@ }, "parent_id": { "description": "The ref_id of the parent dividend", + "format": "uuid", "type": "string" }, "symbol": { "description": "The symbol of the security involved with the activity", "type": "string" + }, + "tax_country": { + "description": "Tax country used as the primary jurisdiction when determining withholding treatment for the dividend. Other factors may affect the final withholding rate.\nThe value is an [ISO 3166-1 alpha-3](https://www.iso.org/iso-3166-country-codes.html) country code.\n", + "example": "THA", + "type": "string" + }, + "tax_rate": { + "description": "The tax withholding rate applied to the dividend, represented as a value between 0 and 1", + "example": "0.15", + "format": "decimal", + "type": "string" } }, "required": [ @@ -2139,8 +2772,17 @@ "type": "string" }, "foreign": { - "description": "Indicates if related to a non-US security", - "type": "boolean" + "description": "Indicates if related to a non-US security. Serialized as the JSON strings `\"true\"` or `\"false\"`, not a JSON boolean.", + "enum": [ + "true", + "false" + ], + "example": "false", + "type": "string" + }, + "isin": { + "description": "The ISIN of the security involved with the activity", + "type": "string" }, "payable_date": { "description": "The payable_date for this corporate action", @@ -2159,8 +2801,13 @@ "type": "string" }, "special": { - "description": "Indicates if this is a special dividend", - "type": "boolean" + "description": "Indicates if this is a special dividend. Serialized as the JSON strings `\"true\"` or `\"false\"`, not a JSON boolean.", + "enum": [ + "true", + "false" + ], + "example": "false", + "type": "string" }, "symbol": { "description": "The symbol of the security involved with the activity", @@ -2182,6 +2829,42 @@ "description": "Substitute payment in lieu of dividend", "type": "object" }, + "DIVWHActivityV2": { + "allOf": [ + { + "$ref": "#/components/schemas/CommonNTAActivityV2" + }, + { + "properties": { + "cusip": { + "description": "The CUSIP of the security involved with the activity", + "type": "string" + }, + "isin": { + "description": "The ISIN of the security involved with the activity", + "type": "string" + }, + "parent_id": { + "description": "The ref_id of the parent dividend", + "format": "uuid", + "type": "string" + }, + "symbol": { + "description": "The symbol of the security involved with the activity", + "type": "string" + } + }, + "required": [ + "cusip", + "symbol", + "parent_id" + ], + "type": "object" + } + ], + "description": "Dividend tax withholding (e.g. foreign withholding on global securities)", + "type": "object" + }, "Error": { "properties": { "code": { @@ -2305,6 +2988,28 @@ "description": "Free-of-payment (FOP) transfers", "type": "object" }, + "FixedIncomeInterestActivityV2": { + "allOf": [ + { + "$ref": "#/components/schemas/CommonCaActivityV2" + }, + { + "$ref": "#/components/schemas/CommonFixedIncomeInterestActivityV2" + } + ], + "description": "Fixed income interest (INT/FI)", + "type": "object" + }, + "FixedIncomeInterestType": { + "description": "Distinguishes coupon payments from accrued interest on INT/FI activities.", + "enum": [ + "coupon", + "accrued" + ], + "example": "accrued", + "title": "FixedIncomeInterestType", + "type": "string" + }, "FixedIncomeRedemptionActivityV2": { "allOf": [ { @@ -2392,6 +3097,9 @@ "allOf": [ { "$ref": "#/components/schemas/CommonJournalActivityV2" + }, + { + "type": "object" } ], "description": "Journal entry (cash)", @@ -2488,8 +3196,10 @@ }, "next_page_token": { "description": "The token to use to retrieve the next page of results. If `null`, there are no more pages.", - "nullable": true, - "type": "string" + "type": [ + "string", + "null" + ] } }, "required": [ @@ -2529,8 +3239,10 @@ "description": "Time when the active locate expires. Omitted when rejected.", "example": "2026-01-03T01:00:00Z", "format": "date-time", - "nullable": true, - "type": "string" + "type": [ + "string", + "null" + ] }, "id": { "description": "Locate ID.", @@ -2541,27 +3253,35 @@ "limit_price": { "description": "Maximum acceptable fee per share from the request.", "example": "0.05", - "nullable": true, - "type": "string" + "type": [ + "string", + "null" + ] }, "located_price": { "description": "Locate fee per share in USD. Omitted when rejected.", "example": "0.05", - "nullable": true, - "type": "string" + "type": [ + "string", + "null" + ] }, "located_qty": { "description": "Number of shares located. Omitted when rejected.", "example": 100, "format": "int64", - "nullable": true, - "type": "integer" + "type": [ + "integer", + "null" + ] }, "rejection_reason": { "description": "Machine-readable rejection reason.", "example": "inventory_unavailable", - "nullable": true, - "type": "string" + "type": [ + "string", + "null" + ] }, "requested_qty": { "description": "Number of shares requested.", @@ -2580,8 +3300,10 @@ "total_fee": { "description": "Total locate fee in USD. Omitted when rejected.", "example": "5.00", - "nullable": true, - "type": "string" + "type": [ + "string", + "null" + ] } }, "required": [ @@ -2608,7 +3330,9 @@ "security_not_found", "insufficient_buying_power", "easy_to_borrow", - "threshold_security" + "threshold_security", + "idempotency_key_conflict", + "quote_unavailable" ], "example": "invalid_symbols", "type": "string" @@ -2677,7 +3401,8 @@ "symbol_not_found", "easy_to_borrow", "threshold_security", - "corporate_action" + "corporate_action", + "quote_unavailable" ], "example": "symbol_not_found", "type": "string" @@ -2746,13 +3471,35 @@ "description": "Merger and acquisition", "type": "object" }, - "MLegOrderLeg": { - "description": "Represents an individual leg of a multi-leg options order.", - "properties": { - "position_intent": { - "$ref": "#/components/schemas/PositionIntent" + "MEMActivityV2": { + "allOf": [ + { + "$ref": "#/components/schemas/CommonNTAActivityV2" }, - "ratio_qty": { + { + "properties": { + "transfer_id": { + "description": "The transfer's ID", + "format": "uuid", + "type": "string" + } + }, + "required": [ + "transfer_id" + ], + "type": "object" + } + ], + "description": "Instant funding memopost", + "type": "object" + }, + "MLegOrderLeg": { + "description": "Represents an individual leg of a multi-leg options order.", + "properties": { + "position_intent": { + "$ref": "#/components/schemas/PositionIntent" + }, + "ratio_qty": { "description": "proportional quantity of this leg in relation to the overall multi-leg order qty", "type": "string" }, @@ -2798,10 +3545,27 @@ "NextPageToken": { "description": "Use this token in your next API call to paginate through the dataset and retrieve the next page of results. A null token indicates there are no more data to fetch.\n", "example": "MTAwMA==", - "nullable": true, - "type": "string" + "type": [ + "string", + "null" + ] }, "NonTradeActivities": { + "examples": [ + { + "activity_sub_type": "SDIV", + "activity_type": "DIV", + "created_at": "2021-05-10T14:01:04.650275Z", + "currency": "USD", + "date": "2019-08-01", + "id": "20190801011955195::5f596936-6f23-4cef-bdf1-3806aae57dbf", + "net_amount": "1.02", + "per_share_amount": "0.51", + "qty": "2", + "status": "executed", + "symbol": "T" + } + ], "properties": { "activity_sub_type": { "$ref": "#/components/schemas/ActivitySubType" @@ -2863,36 +3627,71 @@ } }, "title": "AccountNonTradeActivities", - "type": "object", - "x-examples": { - "example-1": { - "activity_sub_type": "SDIV", - "activity_type": "DIV", - "created_at": "2021-05-10T14:01:04.650275Z", - "currency": "USD", - "date": "2019-08-01", - "id": "20190801011955195::5f596936-6f23-4cef-bdf1-3806aae57dbf", - "net_amount": "1.02", - "per_share_amount": "0.51", - "qty": "2", - "status": "executed", - "symbol": "T" + "type": "object" + }, + "OCTActivityV2": { + "allOf": [ + { + "$ref": "#/components/schemas/CommonNTAActivityV2" + }, + { + "properties": { + "symbol": { + "description": "The symbol of the asset involved with the on chain transaction", + "type": "string" + }, + "transfer_id": { + "description": "The transfer ID of the associated deposit or withdrawal", + "format": "uuid", + "type": "string" + } + }, + "required": [ + "transfer_id", + "symbol" + ], + "type": "object" } - } + ], + "description": "On chain transaction (blockchain deposit/withdrawal)", + "example": { + "symbol": "USDCUSD", + "system_date": "2026-07-31", + "transfer_id": "08d7a129-bd3e-4480-94ff-1a7288166845" + }, + "type": "object" }, "OPASNActivityV2": { "allOf": [ { "$ref": "#/components/schemas/CommonOptionsActivityV2" + }, + { + "type": "object" } ], "description": "Option assignment", "type": "object" }, + "OPCSHActivityV2": { + "allOf": [ + { + "$ref": "#/components/schemas/CommonOptionsActivityV2" + }, + { + "type": "object" + } + ], + "description": "Option cash deliverable for non-standard contracts", + "type": "object" + }, "OPEXCActivityV2": { "allOf": [ { "$ref": "#/components/schemas/CommonOptionsActivityV2" + }, + { + "type": "object" } ], "description": "Option exercise", @@ -2902,6 +3701,9 @@ "allOf": [ { "$ref": "#/components/schemas/CommonOptionsActivityV2" + }, + { + "type": "object" } ], "description": "Option expiry", @@ -2911,6 +3713,9 @@ "allOf": [ { "$ref": "#/components/schemas/CommonOptionsActivityV2" + }, + { + "type": "object" } ], "description": "Trading activity that is paired with the assignment/exercise", @@ -2925,7 +3730,7 @@ "$ref": "#/components/schemas/CommonCDIVActivityV2" } ], - "description": "Options corporate action of Cash dividend", + "description": "Options corporate action of cash dividend or return of capital", "type": "object" }, "OpcaFSPLITActivityV2": { @@ -3163,6 +3968,11 @@ "format": "date", "type": "string" }, + "ppind": { + "description": "The ppind (Penny Program Indicator) field indicates whether an option contract is eligible for penny price increments,\nwith `true` meaning it is part of the Penny Program and `false` meaning it is not.", + "example": true, + "type": "boolean" + }, "root_symbol": { "description": "The root symbol of the option contract.", "example": "AAPL", @@ -3229,7 +4039,8 @@ "style", "strike_price", "multiplier", - "size" + "size", + "ppind" ], "type": "object" }, @@ -3251,6 +4062,44 @@ "example": "call", "type": "string" }, + "OptionContractsResponse": { + "description": "Paginated list of option contracts.", + "properties": { + "next_page_token": { + "$ref": "#/components/schemas/NextPageToken" + }, + "option_contracts": { + "example": [ + { + "expiration_date": "2025-06-20", + "id": "98359ef7-5124-49f3-85ea-5cf02df6defa", + "multiplier": "100", + "name": "AAPL Jun 20 2025 100 Call", + "ppind": true, + "root_symbol": "AAPL", + "size": "100", + "status": "active", + "strike_price": "100", + "style": "american", + "symbol": "AAPL250620C00100000", + "tradable": true, + "type": "call", + "underlying_asset_id": "b0b6dd9d-8b9b-48a9-ba46-b9d54906e415", + "underlying_symbol": "AAPL" + } + ], + "items": { + "$ref": "#/components/schemas/OptionContract" + }, + "type": "array" + } + }, + "required": [ + "option_contracts" + ], + "title": "OptionContractsResponse", + "type": "object" + }, "OptionDeliverable": { "properties": { "allocation_percentage": { @@ -3337,8 +4186,10 @@ }, "canceled_at": { "format": "date-time", - "nullable": true, - "type": "string" + "type": [ + "string", + "null" + ] }, "client_order_id": { "description": "Client unique order ID", @@ -3351,8 +4202,10 @@ }, "expired_at": { "format": "date-time", - "nullable": true, - "type": "string" + "type": [ + "string", + "null" + ] }, "expires_at": { "format": "date-time", @@ -3364,18 +4217,24 @@ }, "failed_at": { "format": "date-time", - "nullable": true, - "type": "string" + "type": [ + "string", + "null" + ] }, "filled_at": { "format": "date-time", - "nullable": true, - "type": "string" + "type": [ + "string", + "null" + ] }, "filled_avg_price": { "description": "Filled average price", - "nullable": true, - "type": "string" + "type": [ + "string", + "null" + ] }, "filled_qty": { "description": "Filled quantity", @@ -3384,8 +4243,10 @@ }, "hwm": { "description": "The highest (lowest) market price seen since the trailing stop order was submitted.", - "nullable": true, - "type": "string" + "type": [ + "string", + "null" + ] }, "id": { "description": "Order ID", @@ -3394,22 +4255,29 @@ }, "legs": { "description": "When querying non-simple order_class orders in a nested style, an array of Order entities associated with this order. Otherwise, null. Required if order class is `mleg`.", + "example": null, "items": { "$ref": "#/components/schemas/OrderLeg" }, - "nullable": true, - "type": "array" + "type": [ + "array", + "null" + ] }, "limit_price": { "description": "Limit price", - "nullable": true, - "type": "string" + "type": [ + "string", + "null" + ] }, "notional": { "description": "Ordered notional amount. If entered, qty will be null. Can take up to 9 decimal points.", "minLength": 1, - "nullable": true, - "type": "string" + "type": [ + "string", + "null" + ] }, "order_class": { "$ref": "#/components/schemas/OrderClass" @@ -3425,31 +4293,41 @@ "qty": { "description": "Ordered quantity. If entered, notional will be null. Can take up to 9 decimal points. Required if order class is `mleg`.", "minLength": 1, - "nullable": true, - "type": "string" + "type": [ + "string", + "null" + ] }, "ratio_qty": { "description": "The proportional quantity of this leg in relation to the overall multi-leg order quantity.", "format": "decimal", - "nullable": true, - "type": "string" + "type": [ + "string", + "null" + ] }, "replaced_at": { "format": "date-time", - "nullable": true, - "type": "string" + "type": [ + "string", + "null" + ] }, "replaced_by": { "description": "The order ID that this order was replaced by", "format": "uuid", - "nullable": true, - "type": "string" + "type": [ + "string", + "null" + ] }, "replaces": { "description": "The order ID that this order replaces", "format": "uuid", - "nullable": true, - "type": "string" + "type": [ + "string", + "null" + ] }, "side": { "$ref": "#/components/schemas/OrderSide" @@ -3459,13 +4337,17 @@ }, "stop_price": { "description": "Stop price", - "nullable": true, - "type": "string" + "type": [ + "string", + "null" + ] }, "submitted_at": { "format": "date-time", - "nullable": true, - "type": "string" + "type": [ + "string", + "null" + ] }, "symbol": { "description": "Asset symbol, required for all order classes except for `mleg`", @@ -3477,21 +4359,27 @@ }, "trail_percent": { "description": "The percent value away from the high water mark for trailing stop orders.", - "nullable": true, - "type": "string" + "type": [ + "string", + "null" + ] }, "trail_price": { "description": "The dollar value away from the high water mark for trailing stop orders.", - "nullable": true, - "type": "string" + "type": [ + "string", + "null" + ] }, "type": { "$ref": "#/components/schemas/OrderType" }, "updated_at": { "format": "date-time", - "nullable": true, - "type": "string" + "type": [ + "string", + "null" + ] } }, "required": [ @@ -3529,8 +4417,10 @@ }, "canceled_at": { "format": "date-time", - "nullable": true, - "type": "string" + "type": [ + "string", + "null" + ] }, "client_order_id": { "description": "Client unique order ID", @@ -3543,8 +4433,10 @@ }, "expired_at": { "format": "date-time", - "nullable": true, - "type": "string" + "type": [ + "string", + "null" + ] }, "extended_hours": { "description": "If true, eligible for execution outside regular trading hours.", @@ -3552,18 +4444,24 @@ }, "failed_at": { "format": "date-time", - "nullable": true, - "type": "string" + "type": [ + "string", + "null" + ] }, "filled_at": { "format": "date-time", - "nullable": true, - "type": "string" + "type": [ + "string", + "null" + ] }, "filled_avg_price": { "description": "Filled average price", - "nullable": true, - "type": "string" + "type": [ + "string", + "null" + ] }, "filled_qty": { "description": "Filled quantity", @@ -3572,8 +4470,10 @@ }, "hwm": { "description": "The highest (lowest) market price seen since the trailing stop order was submitted.", - "nullable": true, - "type": "string" + "type": [ + "string", + "null" + ] }, "id": { "description": "Order ID", @@ -3581,24 +4481,31 @@ "type": "string" }, "legs": { - "description": "Always null for an order leg; legs are not nested beyond one level.", + "description": "Always null for an order leg; legs are not nested beyond one level. An empty array is accepted for generated-client compatibility.", "example": null, "items": { "type": "object" }, - "nullable": true, - "type": "array" + "maxItems": 0, + "type": [ + "array", + "null" + ] }, "limit_price": { "description": "Limit price", - "nullable": true, - "type": "string" + "type": [ + "string", + "null" + ] }, "notional": { "description": "Ordered notional amount. If entered, qty will be null. Can take up to 9 decimal points.", "minLength": 1, - "nullable": true, - "type": "string" + "type": [ + "string", + "null" + ] }, "order_class": { "$ref": "#/components/schemas/OrderClass" @@ -3614,25 +4521,33 @@ "qty": { "description": "Ordered quantity. If entered, notional will be null. Can take up to 9 decimal points.", "minLength": 1, - "nullable": true, - "type": "string" + "type": [ + "string", + "null" + ] }, "replaced_at": { "format": "date-time", - "nullable": true, - "type": "string" + "type": [ + "string", + "null" + ] }, "replaced_by": { "description": "The order ID that this order was replaced by", "format": "uuid", - "nullable": true, - "type": "string" + "type": [ + "string", + "null" + ] }, "replaces": { "description": "The order ID that this order replaces", "format": "uuid", - "nullable": true, - "type": "string" + "type": [ + "string", + "null" + ] }, "side": { "$ref": "#/components/schemas/OrderSide" @@ -3642,13 +4557,17 @@ }, "stop_price": { "description": "Stop price", - "nullable": true, - "type": "string" + "type": [ + "string", + "null" + ] }, "submitted_at": { "format": "date-time", - "nullable": true, - "type": "string" + "type": [ + "string", + "null" + ] }, "symbol": { "description": "Asset symbol", @@ -3660,21 +4579,27 @@ }, "trail_percent": { "description": "The percent value away from the high water mark for trailing stop orders.", - "nullable": true, - "type": "string" + "type": [ + "string", + "null" + ] }, "trail_price": { "description": "The dollar value away from the high water mark for trailing stop orders.", - "nullable": true, - "type": "string" + "type": [ + "string", + "null" + ] }, "type": { "$ref": "#/components/schemas/OrderType" }, "updated_at": { "format": "date-time", - "nullable": true, - "type": "string" + "type": [ + "string", + "null" + ] } }, "required": [ @@ -3724,7 +4649,7 @@ "type": "string" }, "OrderType": { - "description": "The order types supported by Alpaca vary based on the order's security type. The following provides a comprehensive breakdown of the supported order types for each category:\n - Equity trading: market, limit, stop, stop_limit, trailing_stop.\n - Options trading: market, limit.\n - Multileg Options trading: market, limit.\n - Crypto trading: market, limit, stop_limit.", + "description": "The order types supported by Alpaca vary based on the order's security type. The following provides a comprehensive breakdown of the supported order types for each category:\n - Equity trading: market, limit, stop, stop_limit, trailing_stop.\n - Options trading: market, limit, stop, stop_limit.\n - Multileg Options trading: market, limit.\n - Crypto trading: market, limit, stop_limit.", "enum": [ "market", "limit", @@ -3784,7 +4709,10 @@ "properties": { "base_value": { "description": "basis in dollar of the profit loss calculation", - "type": "number" + "type": [ + "number", + "null" + ] }, "base_value_asof": { "description": "If included, then it indicates that the base_value is the account's closing\nequity value at this trading date.\n\nIf not specified, then the baseline calculation is done against the earliest returned data item. This could happen for\naccounts without prior closing balances (e.g. new account) or for queries with 1D timeframes, where the first data point\nis used as a reference point.\n", @@ -3793,20 +4721,32 @@ "type": "string" }, "cashflow": { + "additionalProperties": { + "items": { + "type": "number" + }, + "type": "array" + }, "description": "accumulated value in dollar amount as of the end of each time window", "type": "object" }, "equity": { "description": "equity value of the account in dollar amount as of the end of each time window", "items": { - "type": "number" + "type": [ + "number", + "null" + ] }, "type": "array" }, "profit_loss": { "description": "profit/loss in dollar from the base value", "items": { - "type": "number" + "type": [ + "number", + "null" + ] }, "type": "array" }, @@ -3817,7 +4757,10 @@ 0.002 ], "items": { - "type": "number" + "type": [ + "number", + "null" + ] }, "type": "array" }, @@ -3843,224 +4786,68 @@ "timeframe" ], "title": "PortfolioHistory", - "type": "object", - "x-examples": { - "example-cashflows": { - "base_value": 8413.04, - "base_value_asof": "2026-04-06", - "cashflow": { - "CSD": [ - 9.93, - 10.13, - 10.16, - 0 - ], - "DIV": [ - 0, - 0, - 0.63, - 0 - ] - }, - "equity": [ - 8425.21, - 8639.77, - 8766.1, - 8835.31 - ], - "profit_loss": [ - 12.17, - 214.56, - 126.33, - 69.21 - ], - "profit_loss_pct": [ - 0.0014, - 0.0255, - 0.0146, - 0.0079 - ], - "timeframe": "1D", - "timestamp": [ - 1775606400, - 1775692800, - 1775779200, - 1775865600 - ] - }, - "example-intraday-query-15min-1d": { - "base_value": 2774.16, - "base_value_asof": "2023-10-18", - "equity": [ - 2773.79, - 2769.04, - 2768.65, - 2765.11, - 2763.03, - 2763.17, - 2763.17, - 2763.47, - 2763.91, - 2768.13, - 2774.98, - 2757.94, - 2757.65, - 2774.54, - 2775.58, - 2775.28, - 2767.9, - 2762.26, - 2762.56, - 2756.99, - 2756.84, - 2752.43, - 2752.13, - 2748.44, - 2751.23, - 2747.54, - 2748.74 - ], - "profit_loss": [ - -0.37, - -5.12, - -5.51, - -9.05, - -11.13, - -10.99, - -10.99, - -10.69, - -10.25, - -6.03, - 0.82, - -16.22, - -16.51, - 0.38, - 1.42, - 1.12, - -6.26, - -11.9, - -11.6, - -17.17, - -17.32, - -21.73, - -22.03, - -25.72, - -22.93, - -26.62, - -25.42 - ], - "profit_loss_pct": [ - -0.0001, - -0.0018, - -0.002, - -0.0033, - -0.004, - -0.004, - -0.004, - -0.0039, - -0.0037, - -0.0022, - 0.0003, - -0.0058, - -0.006, - 0.0001, - 0.0005, - 0.0004, - -0.0023, - -0.0043, - -0.0042, - -0.0062, - -0.0062, - -0.0078, - -0.0079, - -0.0093, - -0.0083, - -0.0096, - -0.0092 - ], - "timeframe": "15Min", - "timestamp": [ - 1697722200, - 1697723100, - 1697724000, - 1697724900, - 1697725800, - 1697726700, - 1697727600, - 1697728500, - 1697729400, - 1697730300, - 1697731200, - 1697732100, - 1697733000, - 1697733900, - 1697734800, - 1697735700, - 1697736600, - 1697737500, - 1697738400, - 1697739300, - 1697740200, - 1697741100, - 1697742000, - 1697742900, - 1697743800, - 1697744700, - 1697745600 - ] - }, - "example-query-1d-7d": { - "base_value": 2784.79, - "equity": [ - 2784.79, - 2794.79, - 2805.46, - 2774.16, - 2748.73 - ], - "profit_loss": [ - 0, - 10.0022, - 10.6692, - -31.2996, - -25.4232 - ], - "profit_loss_pct": [ - 0, - 0.0035, - 0.0074, - -0.0038, - -0.0129 - ], - "timeframe": "1D", - "timestamp": [ - 1697241600, - 1697500800, - 1697587200, - 1697673600, - 1697760000 - ] - } - } + "type": "object" }, "Position": { "description": "The positions API provides information about an account's current open positions. The response will include information such as cost basis, shares traded, and market value, which will be updated live as price information is updated. Once a position is closed, it will no longer be queryable through this API.", - "properties": { - "asset_class": { - "$ref": "#/components/schemas/AssetClass" - }, - "asset_id": { - "description": "Asset ID (For options this represents the option contract ID)", - "format": "uuid", - "type": "string" - }, - "asset_marginable": { - "type": "boolean" - }, - "avg_entry_price": { - "description": "Average entry price of the position", - "minLength": 1, - "type": "string" + "examples": [ + { + "asset_class": "us_equity", + "asset_id": "904837e3-3b76-47ec-b432-046db621571b", + "asset_marginable": true, + "avg_entry_price": "100.0", + "change_today": "0.0084", + "cost_basis": "500.0", + "current_price": "120.0", + "exchange": "NASDAQ", + "lastday_price": "119.0", + "market_value": "600.0", + "qty": "5", + "qty_available": "4", + "side": "long", + "symbol": "AAPL", + "unrealized_intraday_pl": "10.0", + "unrealized_intraday_plpc": "0.0084", + "unrealized_pl": "100.0", + "unrealized_plpc": "0.20" + }, + { + "asset_class": "us_equity", + "asset_id": "b0b6dd9d-8b9b-48a9-ba46-b9d54906e415", + "asset_marginable": false, + "avg_entry_price": "174.78", + "change_today": "-0.0018326556325525", + "cost_basis": "349.56", + "current_price": "174.29", + "exchange": "NASDAQ", + "lastday_price": "174.61", + "market_value": "348.58", + "qty": "2", + "qty_available": "2", + "side": "long", + "symbol": "AAPL", + "unrealized_intraday_pl": "-0.98", + "unrealized_intraday_plpc": "-0.0028035244307129", + "unrealized_pl": "-0.98", + "unrealized_plpc": "-0.0028035244307129" + } + ], + "properties": { + "asset_class": { + "$ref": "#/components/schemas/AssetClass" + }, + "asset_id": { + "description": "Asset ID (For options this represents the option contract ID)", + "format": "uuid", + "type": "string" + }, + "asset_marginable": { + "type": "boolean" + }, + "avg_entry_price": { + "description": "Average entry price of the position", + "minLength": 1, + "type": "string" }, "avg_entry_swap_rate": { "description": "The weighted-average exchange rate at the time the position was entered.", @@ -4172,72 +4959,12 @@ "asset_marginable" ], "title": "Position", - "type": "object", - "x-examples": { - "example-1": { - "asset_class": "us_equity", - "asset_id": "904837e3-3b76-47ec-b432-046db621571b", - "avg_entry_price": "100.0", - "change_today": "0.0084", - "cost_basis": "500.0", - "current_price": "120.0", - "exchange": "NASDAQ", - "lastday_price": "119.0", - "market_value": "600.0", - "qty": "5", - "qty_available": "4", - "side": "long", - "symbol": "AAPL", - "unrealized_intraday_pl": "10.0", - "unrealized_intraday_plpc": "0.0084", - "unrealized_pl": "100.0", - "unrealized_plpc": "0.20" - }, - "example-2": { - "asset_class": "us_equity", - "asset_id": "b0b6dd9d-8b9b-48a9-ba46-b9d54906e415", - "asset_marginable": false, - "avg_entry_price": "174.78", - "change_today": "-0.0018326556325525", - "cost_basis": "349.56", - "current_price": "174.29", - "exchange": "NASDAQ", - "lastday_price": "174.61", - "market_value": "348.58", - "qty": "2", - "qty_available": "2", - "side": "long", - "symbol": "AAPL", - "unrealized_intraday_pl": "-0.98", - "unrealized_intraday_plpc": "-0.0028035244307129", - "unrealized_pl": "-0.98", - "unrealized_plpc": "-0.0028035244307129" - } - } + "type": "object" }, "PositionClosedReponse": { "description": "Represents the result of asking the api to close a position. ", - "properties": { - "body": { - "$ref": "#/components/schemas/Order" - }, - "status": { - "description": "HTTP status code for the attempt to close this position", - "type": "integer" - }, - "symbol": { - "description": "Symbol name of the asset", - "type": "string" - } - }, - "required": [ - "symbol", - "status" - ], - "title": "PositionClosedReponse", - "type": "object", - "x-examples": { - "example-1": { + "examples": [ + { "body": { "asset_class": "us_equity", "asset_id": "b0b6dd9d-8b9b-48a9-ba46-b9d54906e415", @@ -4275,7 +5002,26 @@ "status": 200, "symbol": "AAPL" } - } + ], + "properties": { + "body": { + "$ref": "#/components/schemas/Order" + }, + "status": { + "description": "HTTP status code for the attempt to close this position", + "type": "integer" + }, + "symbol": { + "description": "Symbol name of the asset", + "type": "string" + } + }, + "required": [ + "symbol", + "status" + ], + "title": "PositionClosedReponse", + "type": "object" }, "PositionIntent": { "description": "Represents the desired position strategy.", @@ -4289,6 +5035,18 @@ "title": "PositionIntent", "type": "string" }, + "REOActivityV2": { + "allOf": [ + { + "$ref": "#/components/schemas/CommonCaActivityV2" + }, + { + "$ref": "#/components/schemas/CommonREOActivityV2" + } + ], + "description": "Reorganization (REO)", + "type": "object" + }, "ReverseSplitActivityV2": { "allOf": [ { @@ -4445,6 +5203,37 @@ "description": "Stock dividend", "type": "object" }, + "SearchVASPsResponse": { + "description": "Paginated VASP directory search results.", + "properties": { + "page": { + "example": 1, + "type": "integer" + }, + "pages": { + "example": 1, + "type": "integer" + }, + "total": { + "description": "Total count of matching records.", + "example": 1, + "type": "integer" + }, + "vasps": { + "example": [ + { + "id": "vasp-1", + "name": "Example Exchange" + } + ], + "items": { + "$ref": "#/components/schemas/VASP" + }, + "type": "array" + } + }, + "type": "object" + }, "SpinoffActivityV2": { "allOf": [ { @@ -4486,7 +5275,7 @@ "type": "object" }, "TimeInForce": { - "description": "The Time-In-Force values supported by Alpaca vary based on the order's security type. Here is a breakdown of the supported TIFs for each specific security type:\n- Equity trading: day, gtc, opg, cls, ioc, fok.\n- Options trading: day.\n- Crypto trading: gtc, ioc.\n\nBelow are the descriptions of each TIF:\n- day:\n A day order is eligible for execution only on the day it is live. By default, the order is only valid during Regular Trading Hours (9:30am - 4:00pm ET). If unfilled after the closing auction, it is automatically canceled. If submitted after the close, it is queued and submitted the following trading day. However, if marked as eligible for extended hours, the order can also execute during supported extended hours.\n\n- gtc:\n The order is good until canceled. Non-marketable GTC limit orders are subject to price adjustments to offset corporate actions affecting the issue. We do not currently support Do Not Reduce (DNR) orders to opt out of such price adjustments.\n\n- opg:\n Use this TIF with a market/limit order type to submit \"market on open\" (MOO) and \"limit on open\" (LOO) orders. This order is eligible to execute only in the market opening auction. Any unfilled orders after the open will be cancelled. OPG orders submitted after 9:28am but before 7:00pm ET will be rejected. OPG orders submitted after 7:00pm will be queued and routed to the following day's opening auction. On open/on close orders are routed to the primary exchange. Such orders do not necessarily execute exactly at 9:30am / 4:00pm ET but execute per the exchange's auction rules.\n\n- cls:\n Use this TIF with a market/limit order type to submit \"market on close\" (MOC) and \"limit on close\" (LOC) orders. This order is eligible to execute only in the market closing auction. Any unfilled orders after the close will be cancelled. CLS orders submitted after 3:50pm but before 7:00pm ET will be rejected. CLS orders submitted after 7:00pm will be queued and routed to the following day's closing auction. Only available with API v2.\n\n- ioc:\n An Immediate Or Cancel (IOC) order requires all or part of the order to be executed immediately. Any unfilled portion of the order is canceled. Only available with API v2. Most market makers who receive IOC orders will attempt to fill the order on a principal basis only, and cancel any unfilled balance. On occasion, this can result in the entire order being cancelled if the market maker does not have any existing inventory of the security in question.\n\n- fok:\n A Fill or Kill (FOK) order is only executed if the entire order quantity can be filled, otherwise the order is canceled. Only available with API v2.", + "description": "The Time-In-Force values supported by Alpaca vary based on the order's security type. Here is a breakdown of the supported TIFs for each specific security type:\n- Equity trading: day, gtc, opg, cls, ioc, fok.\n- Options trading: day, gtc.\n- Crypto trading: gtc, ioc.\n\nBelow are the descriptions of each TIF:\n- day:\n A day order is eligible for execution only on the day it is live. By default, the order is only valid during Regular Trading Hours (9:30am - 4:00pm ET). If unfilled after the closing auction, it is automatically canceled. If submitted after the close, it is queued and submitted the following trading day. However, if marked as eligible for extended hours, the order can also execute during supported extended hours.\n\n- gtc:\n The order is good until canceled. Non-marketable GTC limit orders are subject to price adjustments to offset corporate actions affecting the issue. We do not currently support Do Not Reduce (DNR) orders to opt out of such price adjustments.\n\n- opg:\n Use this TIF with a market/limit order type to submit \"market on open\" (MOO) and \"limit on open\" (LOO) orders. This order is eligible to execute only in the market opening auction. Any unfilled orders after the open will be cancelled. OPG orders submitted after 9:28am but before 7:00pm ET will be rejected. OPG orders submitted after 7:00pm will be queued and routed to the following day's opening auction. On open/on close orders are routed to the primary exchange. Such orders do not necessarily execute exactly at 9:30am / 4:00pm ET but execute per the exchange's auction rules.\n\n- cls:\n Use this TIF with a market/limit order type to submit \"market on close\" (MOC) and \"limit on close\" (LOC) orders. This order is eligible to execute only in the market closing auction. Any unfilled orders after the close will be cancelled. CLS orders submitted after 3:50pm but before 7:00pm ET will be rejected. CLS orders submitted after 7:00pm will be queued and routed to the following day's closing auction. Only available with API v2.\n\n- ioc:\n An Immediate Or Cancel (IOC) order requires all or part of the order to be executed immediately. Any unfilled portion of the order is canceled. Only available with API v2. Most market makers who receive IOC orders will attempt to fill the order on a principal basis only, and cancel any unfilled balance. On occasion, this can result in the entire order being cancelled if the market maker does not have any existing inventory of the security in question.\n\n- fok:\n A Fill or Kill (FOK) order is only executed if the entire order quantity can be filled, otherwise the order is canceled. Only available with API v2.", "enum": [ "day", "gtc", @@ -4502,8 +5291,11 @@ "TokenizationIssuer": { "description": "The tokenized asset's issuer", "enum": [ - "xstocks", - "st0x" + "binance", + "coinbase", + "ondo", + "st0x", + "xstocks" ], "example": "xstocks", "title": "TokenizationIssuer", @@ -4588,14 +5380,18 @@ "TokenizationNetwork": { "description": "The token's blockchain network", "enum": [ - "solana", "arbitrum", - "ethereum", - "binance", "base", + "binance", + "cronos", + "ethereum", + "hypercore", + "hyperevm", + "mantle", + "robinhood", + "solana", "ton", - "tron", - "mantle" + "tron" ], "example": "solana", "title": "TokenizationNetwork", @@ -4604,35 +5400,73 @@ "TokenizationRequest": { "properties": { "account": { - "description": "Alpaca account ID associated with this tokenization request", + "deprecated": true, + "description": "Alpaca account ID associated with this tokenization request. Use `client_account_id` instead.", + "example": "06b9d76e-1733-460c-844e-48d1ae0f10c3", + "type": "string", + "x-deprecation": { + "reason": "Use client_account_id instead.", + "since": "2026-07-15", + "sunset": "2026-10-15" + } + }, + "client_account_id": { + "description": "Alpaca account UUID of the Authorized Participant associated with this tokenization request", + "example": "06b9d76e-1733-460c-844e-48d1ae0f10c3", + "format": "uuid", + "type": "string" + }, + "client_external_account_id": { + "description": "Issuer-side account identifier of the Authorized Participant associated with this tokenization request", + "example": "AP-EXT-001", + "type": "string" + }, + "client_request_id": { + "description": "Authorized Participant-supplied label associated with this tokenization request", + "example": "ap-mint-2026-01-05-001", "type": "string" }, "created_at": { + "example": "2026-01-02T15:04:05Z", "format": "date-time", "type": "string" }, "fees": { "description": "Fees charged for this tokenization request", - "nullable": true, - "type": "string" + "example": null, + "type": [ + "string", + "null" + ] }, "issuer": { "$ref": "#/components/schemas/TokenizationIssuer" }, "issuer_account": { - "description": "Issuer's account ID associated with this tokenization request", - "type": "string" + "deprecated": true, + "description": "Issuer's account ID associated with this tokenization request. Use `client_external_account_id` instead.", + "example": "AP-EXT-001", + "type": "string", + "x-deprecation": { + "reason": "Use client_external_account_id instead.", + "since": "2026-07-15", + "sunset": "2026-10-15" + } }, "issuer_request_id": { "description": "Unique identifier of the tokenization request set by the issuer", - "nullable": true, - "type": "string" + "example": null, + "type": [ + "string", + "null" + ] }, "network": { "$ref": "#/components/schemas/TokenizationNetwork" }, "qty": { "description": "The quantity to convert for this tokenization request. It can be fractional.", + "example": "10", "type": "string" }, "status": { @@ -4640,31 +5474,41 @@ }, "token_symbol": { "description": "The tokenized asset symbol", + "example": "AAPLx", "type": "string" }, "tokenization_request_id": { "description": "Unique identifier of the tokenization request set by Alpaca", + "example": "5b1d6a3e-7f0a-4d2c-b8e1-9e6f1c0d2c4a", "type": "string" }, "tx_hash": { "description": "Transaction hash of the completed request on the blockchain", - "nullable": true, - "type": "string" + "example": null, + "type": [ + "string", + "null" + ] }, "type": { "$ref": "#/components/schemas/TokenizationRequestType" }, "underlying_symbol": { "description": "The underlying asset symbol", + "example": "AAPL", "type": "string" }, "updated_at": { + "example": null, "format": "date-time", - "nullable": true, - "type": "string" + "type": [ + "string", + "null" + ] }, "wallet_address": { "description": "The wallet address associated with this tokenization request", + "example": "5dXY1aH2tQpV3wXmJg6Z7c8B4nKvF9bA1pQrSt2uVwYxXz", "type": "string" } }, @@ -4705,6 +5549,22 @@ "type": "string" }, "TradingActivities": { + "examples": [ + { + "activity_type": "FILL", + "cum_qty": "2", + "id": "20220202135509981::2d7be4ff-d1f3-43e9-856a-0f5cf5c5088e", + "leaves_qty": "0", + "order_id": "b5abe576-6a8a-49f3-a353-46b72c1ccae9", + "order_status": "filled", + "price": "174.78", + "qty": "2", + "side": "buy", + "symbol": "AAPL", + "transaction_time": "2022-02-02T18:55:09.981482Z", + "type": "fill" + } + ], "properties": { "activity_type": { "$ref": "#/components/schemas/ActivityType" @@ -4762,23 +5622,7 @@ } }, "title": "AccountTradingActivities", - "type": "object", - "x-examples": { - "example-1": { - "activity_type": "FILL", - "cum_qty": "2", - "id": "20220202135509981::2d7be4ff-d1f3-43e9-856a-0f5cf5c5088e", - "leaves_qty": "0", - "order_id": "b5abe576-6a8a-49f3-a353-46b72c1ccae9", - "order_status": "filled", - "price": "174.78", - "qty": "2", - "side": "buy", - "symbol": "AAPL", - "transaction_time": "2022-02-02T18:55:09.981482Z", - "type": "fill" - } - } + "type": "object" }, "TransferDirection": { "enum": [ @@ -4788,52 +5632,301 @@ "example": "INCOMING", "type": "string" }, - "USDPositionValues": { - "description": "Position values in USD. This is returned for LCT (non-USD) accounts only.", + "TravelRuleErrorResponse": { + "description": "A wallet or travel rule validation error.", + "oneOf": [ + { + "$ref": "#/components/schemas/TravelRuleValidationError" + }, + { + "$ref": "#/components/schemas/WalletError" + } + ] + }, + "TravelRuleFieldOption": { + "description": "Allowed value for a travel rule field that accepts a fixed set of choices.", "properties": { - "avg_entry_price": { - "description": "Average entry price of the position in USD", - "example": "71.43", - "format": "decimal", + "description": { "type": "string" }, - "change_today": { - "description": "Percent change from last day price (by a factor of 1)", - "example": "0.67", - "format": "decimal", + "value": { + "type": "string" + } + }, + "required": [ + "value", + "description" + ], + "type": "object" + }, + "TravelRuleInfo": { + "description": "Travel rule information pertaining to the wallet being whitelisted. Identify the destination by providing beneficiary_vasp_id, setting beneficiary_is_self_hosted to true, or providing beneficiary_manual_entry. Either beneficiary_entity_name (for legal entities) or beneficiary_given_name and beneficiary_family_name (for natural persons) must be provided.", + "properties": { + "beneficiary_country_of_residence": { + "description": "ISO 3166-1 country code for the beneficiary's country of residence.", + "example": "US", "type": "string" }, - "cost_basis": { - "description": "Total cost basis in USD", - "example": "333.33", - "format": "decimal", + "beneficiary_entity_name": { + "description": "The beneficiary's legal entity name (required for legal entities)", + "example": "Alpaca Crypto LLC", + "minLength": 1, "type": "string" }, - "current_price": { - "description": "Current asset price per share in USD", - "example": "80.0", - "format": "decimal", + "beneficiary_family_name": { + "description": "The beneficiary's last name (required for natural persons)", + "example": "Doe", + "minLength": 1, "type": "string" }, - "lastday_price": { - "description": "Last day's asset price per share based on the closing value of the last trading day in USD", - "example": "79.33", - "format": "decimal", + "beneficiary_geographic_address_building_number": { + "description": "Building number in the beneficiary's geographic address.", + "example": "1", "type": "string" }, - "market_value": { - "description": "Total market value of the position in USD", - "example": "400.00", - "format": "decimal", + "beneficiary_geographic_address_country": { + "description": "ISO 3166-1 alpha-2 or alpha-3 country code in the beneficiary's geographic address.", + "example": "US", "type": "string" }, - "unrealized_intraday_pl": { - "description": "Unrealized profit/loss in USD for the day", - "example": "6.67", - "format": "decimal", + "beneficiary_geographic_address_post_code": { + "description": "Postal code in the beneficiary's geographic address.", + "example": "10001", "type": "string" }, - "unrealized_intraday_plpc": { + "beneficiary_geographic_address_street_name": { + "description": "Street name in the beneficiary's geographic address.", + "example": "Main Street", + "type": "string" + }, + "beneficiary_geographic_address_town_name": { + "description": "Town or city in the beneficiary's geographic address.", + "example": "New York", + "type": "string" + }, + "beneficiary_given_name": { + "description": "The beneficiary's first name (required for natural persons)", + "example": "John", + "minLength": 1, + "type": "string" + }, + "beneficiary_is_self_hosted": { + "description": "Beneficiary is receiving funds to a self-hosted / non-custodial wallet", + "example": true, + "type": "boolean" + }, + "beneficiary_manual_entry": { + "$ref": "#/components/schemas/TravelRuleManualEntry" + }, + "beneficiary_vasp_id": { + "description": "The W3C Decentralized Identifier (DID) representing the beneficiary's VASP.", + "example": "did:ethr:0xf1c002f9e7ca88018d6dcf1e86403fb2f5f055f4", + "pattern": "^did:[a-zA-Z0-9]*:.*$", + "type": "string" + } + }, + "title": "TravelRuleInfo", + "type": "object" + }, + "TravelRuleManualEntry": { + "description": "Information about a beneficiary VASP that is not available in the VASP directory.", + "properties": { + "vasp_name": { + "description": "The name of the receiving exchange.", + "example": "Alpaca", + "type": "string" + }, + "vasp_website": { + "description": "The receiving exchange's website.", + "example": "https://alpaca.markets", + "format": "uri", + "type": "string" + } + }, + "required": [ + "vasp_name", + "vasp_website" + ], + "type": "object" + }, + "TravelRuleNextAction": { + "description": "Action the client must take to satisfy travel rule requirements.", + "properties": { + "description": { + "type": "string" + }, + "name": { + "type": "string" + }, + "required_fields": { + "items": { + "$ref": "#/components/schemas/TravelRuleRequiredField" + }, + "type": "array" + }, + "type": { + "enum": [ + "REQUIRED", + "ONE_OF" + ], + "type": "string" + } + }, + "required": [ + "name", + "description", + "type" + ], + "type": "object" + }, + "TravelRuleRequiredField": { + "description": "Request field required to complete a travel rule action.", + "properties": { + "description": { + "type": "string" + }, + "field": { + "type": "string" + }, + "options": { + "description": "List of valid choices for 'enum' types. Null for simple strings or booleans.", + "items": { + "$ref": "#/components/schemas/TravelRuleFieldOption" + }, + "type": [ + "array", + "null" + ] + }, + "value_type": { + "enum": [ + "string", + "bool" + ], + "type": "string" + } + }, + "required": [ + "field", + "value_type", + "description" + ], + "type": "object" + }, + "TravelRuleValidationError": { + "description": "Travel rule validation failure and the actions required to correct it.", + "properties": { + "error_code": { + "$ref": "#/components/schemas/TravelRuleValidationErrorCode" + }, + "message": { + "example": "Please provide the beneficiary's name. Select one of the following options.", + "type": "string" + }, + "next_actions": { + "description": "A list of actions to be taken to fulfill information requirements.", + "example": [ + { + "description": "Provide the recipient's first and last name.", + "name": "supply_beneficiaryName_naturalPerson", + "required_fields": [ + { + "description": "The recipient's first name.", + "field": "beneficiary_given_name", + "value_type": "string" + }, + { + "description": "The recipient's last name.", + "field": "beneficiary_family_name", + "value_type": "string" + } + ], + "type": "ONE_OF" + }, + { + "description": "Provide the entity's legal name.", + "name": "supply_beneficiaryName_legalEntity", + "required_fields": [ + { + "description": "The legal name of the receiving entity.", + "field": "beneficiary_entity_name", + "value_type": "string" + } + ], + "type": "ONE_OF" + } + ], + "items": { + "$ref": "#/components/schemas/TravelRuleNextAction" + }, + "type": "array" + } + }, + "required": [ + "error_code", + "message", + "next_actions" + ], + "type": "object" + }, + "TravelRuleValidationErrorCode": { + "description": "Machine-readable code identifying the travel rule validation failure.", + "enum": [ + "INVALID_DATA", + "UNKNOWN_VASP", + "ADDRESS_BELONGS_TO_VASP", + "TRAVEL_RULE_ACTION_REQUIRED", + "BENEFICIARY_NAME_REQUIRED" + ], + "example": "BENEFICIARY_NAME_REQUIRED", + "type": "string" + }, + "USDPositionValues": { + "description": "Position values in USD. This is returned for LCT (non-USD) accounts only.", + "properties": { + "avg_entry_price": { + "description": "Average entry price of the position in USD", + "example": "71.43", + "format": "decimal", + "type": "string" + }, + "change_today": { + "description": "Percent change from last day price (by a factor of 1)", + "example": "0.67", + "format": "decimal", + "type": "string" + }, + "cost_basis": { + "description": "Total cost basis in USD", + "example": "333.33", + "format": "decimal", + "type": "string" + }, + "current_price": { + "description": "Current asset price per share in USD", + "example": "80.0", + "format": "decimal", + "type": "string" + }, + "lastday_price": { + "description": "Last day's asset price per share based on the closing value of the last trading day in USD", + "example": "79.33", + "format": "decimal", + "type": "string" + }, + "market_value": { + "description": "Total market value of the position in USD", + "example": "400.00", + "format": "decimal", + "type": "string" + }, + "unrealized_intraday_pl": { + "description": "Unrealized profit/loss in USD for the day", + "example": "6.67", + "format": "decimal", + "type": "string" + }, + "unrealized_intraday_plpc": { "description": "Unrealized intraday profit/loss percent (by a factor of 1)", "example": "0.0084", "format": "decimal", @@ -4870,6 +5963,10 @@ "description": "CUSIP for the alternate security after the split", "type": "string" }, + "alternate_isin": { + "description": "ISIN for the alternate security after the split", + "type": "string" + }, "alternate_qty": { "description": "Quantity of alternate shares received", "type": "string" @@ -4913,25 +6010,96 @@ "type": "object" }, "UpdateWatchlistRequest": { - "description": "Request format used for creating a new watchlist or updating an existing watchlist with a set of assets and name.", + "anyOf": [ + { + "required": [ + "name" + ] + }, + { + "required": [ + "symbols" + ] + } + ], + "description": "Request format used for updating an existing watchlist with a set of assets and/or a name.", "properties": { "name": { - "description": "The watchlist name.", + "description": "The new watchlist name.", "type": "string" }, "symbols": { - "description": "List of asset symbols to include in the watchlist.", + "description": "List of asset symbols to include in the watchlist. The existing assets will be replaced with the new list.", "items": { - "nullable": true, - "type": "string" + "type": [ + "string", + "null" + ] }, "type": "array" } }, + "title": "PutWatchlistRequest", + "type": "object" + }, + "UpdateWhitelistedAddressTravelRuleInfoRequest": { + "description": "Travel rule information to associate with a whitelisted address.", + "properties": { + "travel_rule_info": { + "$ref": "#/components/schemas/TravelRuleInfo" + } + }, + "required": [ + "travel_rule_info" + ], + "type": "object" + }, + "VASP": { + "description": "Virtual Asset Service Provider returned by the directory search.", + "properties": { + "address": { + "type": "string" + }, + "city": { + "type": "string" + }, + "country": { + "description": "ISO 3166-1 alpha-2 country code for the VASP.", + "example": "US", + "type": "string" + }, + "did": { + "description": "The W3C Decentralized Identifier (DID) representing the VASP.", + "example": "did:ethr:0xf1c002f9e7ca88018d6dcf1e86403fb2f5f055f4", + "pattern": "^did:[a-zA-Z0-9]*:.*$", + "type": "string" + }, + "emailDomain": { + "type": "string" + }, + "id": { + "description": "Opaque identifier for the VASP directory record.", + "example": "vasp-1", + "type": "string" + }, + "name": { + "type": "string" + }, + "postalCode": { + "type": "string" + }, + "state": { + "type": "string" + }, + "website": { + "format": "uri", + "type": "string" + } + }, "required": [ + "id", "name" ], - "title": "PostWatchlistRequest", "type": "object" }, "WRMActivityV2": { @@ -4945,6 +6113,10 @@ "description": "The CUSIP of the security involved with the activity", "type": "string" }, + "isin": { + "description": "The ISIN of the security involved with the activity", + "type": "string" + }, "removed_qty": { "description": "The removed quantity", "type": "string" @@ -4965,6 +6137,20 @@ "description": "Worthless Removal", "type": "object" }, + "WalletError": { + "additionalProperties": false, + "description": "A wallet error response.", + "properties": { + "message": { + "example": "travel_rule_info is missing", + "type": "string" + } + }, + "required": [ + "message" + ], + "type": "object" + }, "WalletFeeEstimateResponse": { "description": "Estimated on-chain fee breakdown for a proposed crypto withdrawal, including the total fee and the underlying network (gas) fee.", "properties": { @@ -4994,6 +6180,30 @@ }, "Watchlist": { "description": "The watchlist API provides CRUD operation for the account's watchlist. An account can have multiple watchlists and each is uniquely identified by id but can also be addressed by user-defined name. Each watchlist is an ordered list of assets.\n", + "examples": [ + { + "account_id": "abe25343-a7ba-4255-bdeb-f7e013e9ee5d", + "assets": [ + { + "class": "us_equity", + "easy_to_borrow": true, + "exchange": "NASDAQ", + "fractionable": true, + "id": "8ccae427-5dd0-45b3-b5fe-7ba5e422c766", + "marginable": true, + "name": "Tesla, Inc. Common Stock", + "shortable": true, + "status": "active", + "symbol": "TSLA", + "tradable": true + } + ], + "created_at": "2022-01-31T21:49:05.14628Z", + "id": "3174d6df-7726-44b4-a5bd-7fda5ae6e009", + "name": "Primary Watchlist", + "updated_at": "2022-01-31T21:49:05.14628Z" + } + ], "properties": { "account_id": { "description": "account ID", @@ -5034,34 +6244,19 @@ "name" ], "title": "Watchlist", - "type": "object", - "x-examples": { - "example-1": { + "type": "object" + }, + "WatchlistWithoutAsset": { + "description": "The watchlist API provides CRUD operation for the account's watchlist. An account can have multiple watchlists and each is uniquely identified by id but can also be addressed by user-defined name.\n", + "examples": [ + { "account_id": "abe25343-a7ba-4255-bdeb-f7e013e9ee5d", - "assets": [ - { - "class": "us_equity", - "easy_to_borrow": true, - "exchange": "NASDAQ", - "fractionable": true, - "id": "8ccae427-5dd0-45b3-b5fe-7ba5e422c766", - "marginable": true, - "name": "Tesla, Inc. Common Stock", - "shortable": true, - "status": "active", - "symbol": "TSLA", - "tradable": true - } - ], "created_at": "2022-01-31T21:49:05.14628Z", "id": "3174d6df-7726-44b4-a5bd-7fda5ae6e009", "name": "Primary Watchlist", "updated_at": "2022-01-31T21:49:05.14628Z" } - } - }, - "WatchlistWithoutAsset": { - "description": "The watchlist API provides CRUD operation for the account's watchlist. An account can have multiple watchlists and each is uniquely identified by id but can also be addressed by user-defined name.\n", + ], "properties": { "account_id": { "description": "account ID", @@ -5095,16 +6290,7 @@ "name" ], "title": "Watchlist", - "type": "object", - "x-examples": { - "example-1": { - "account_id": "abe25343-a7ba-4255-bdeb-f7e013e9ee5d", - "created_at": "2022-01-31T21:49:05.14628Z", - "id": "3174d6df-7726-44b4-a5bd-7fda5ae6e009", - "name": "Primary Watchlist", - "updated_at": "2022-01-31T21:49:05.14628Z" - } - } + "type": "object" }, "WhitelistedAddress": { "properties": { @@ -5136,6 +6322,14 @@ "PENDING" ], "type": "string" + }, + "travel_rule_info": { + "allOf": [ + { + "$ref": "#/components/schemas/TravelRuleInfo" + } + ], + "description": "Travel rule information associated with the whitelisted address." } }, "type": "object" @@ -5148,15 +6342,6 @@ "pattern": "^[A-Z0-9]{11}$", "type": "string" }, - "bond_status": { - "description": "Status of the bond.", - "enum": [ - "outstanding", - "matured", - "pre_issuance" - ], - "type": "string" - }, "calendar_day": { "description": "A calendar day.", "properties": { @@ -5226,16 +6411,6 @@ ], "type": "object" }, - "call_type": { - "description": "The type of call on the bond refers to one of a variety of circumstances under which a callable bond may be called.", - "enum": [ - "ordinary", - "make_whole", - "regulatory", - "special" - ], - "type": "string" - }, "clock": { "properties": { "is_market_day": { @@ -5293,42 +6468,7 @@ "required": [ "clocks" ], - "type": "object", - "x-go-name": "ClockResp" - }, - "coupon_frequency": { - "description": "How often the coupon is paid", - "enum": [ - "annual", - "semi_annual", - "quarterly", - "monthly", - "zero" - ], - "type": "string" - }, - "coupon_type": { - "description": "The type of the coupon rate", - "enum": [ - "fixed", - "floating", - "zero" - ], - "type": "string" - }, - "day_count": { - "description": "The day count convention used to calculate accrued interest.\n\n- `A/360`: calculates the daily interest using a 360-day year and then multiplies that by the actual number of days in each time period.\n- `A/365`: calculates the daily interest using a 365-day year and then multiplies that by the actual number of days in each time period.\n- `30/360`: calculates the daily interest using a 360-day year and then multiplies that by 30 (standardized month).\n- `30/365`: calculates the daily interest using a 365-day year and then multiplies that by 30 (standardized month).\n- `A/A`: calculates the daily interest using the actual number of days in the year and then multiplies that by the actual number of days in each time period.\n- `30E/360`: number of days equals to the actual number of days (for February). If the start date or the end date of the period is the 31st of a month, that date is set to the 30th. The number of days in a year is 360.\n- `B/252`: calculates the daily interest using a 252-business-day year and then multiplies that by the actual number of days in each time period.\n- `A/364`: calculates the daily interest using a 364-day year and then multiplies that by the actual number of days in each time period.\n", - "enum": [ - "A/360", - "A/365", - "30/360", - "30/365", - "A/A", - "30E/360", - "B/252", - "A/364" - ], - "type": "string" + "type": "object" }, "legacy_calendar_day": { "description": "A calendar day.", @@ -5508,8 +6648,7 @@ "market", "calendar" ], - "type": "object", - "x-go-name": "CalendarResp" + "type": "object" }, "public_market": { "description": "A market.", @@ -5536,818 +6675,200 @@ "timezone" ], "type": "object" + } + }, + "securitySchemes": { + "API_Key": { + "description": "", + "in": "header", + "name": "APCA-API-KEY-ID", + "type": "apiKey" }, - "sp_outlook": { - "description": "A Standard & Poor's rating outlook indicates S&P's view regarding the potential direction of a long-term credit rating over the intermediate term (2 years for investment grade, 1 year for speculative grade)", - "enum": [ - "positive", - "negative", - "developing", - "stable", - "not_rated", - "not_meaningful" - ], - "type": "string" - }, - "treasury_subtype": { - "description": "The subtype of the treasury.", - "enum": [ - "bond", - "bill", - "note", - "strips", - "tips", - "floating" - ], - "type": "string" - }, - "us_corporate": { - "description": "A US corporate", - "properties": { - "accrued_interest": { - "description": "The interest that has accumulated on a bond in dollars per bond between the last interest payment and the present date that has not yet been paid to the bondholder", - "format": "double", - "type": "number", - "x-go-type": "decimal.Decimal", - "x-go-type-import": { - "name": "decimal", - "path": "github.com/alpacahq/alpacadecimal" - } - }, - "bond_status": { - "$ref": "#/components/schemas/bond_status" - }, - "call_type": { - "$ref": "#/components/schemas/call_type" - }, - "callable": { - "description": "Whether the bond is callable, meaning the issuer has the right, but not the obligation to redeem the bond - in other words, pay out the bondholder - before its maturity date at a set price (the call price)", - "type": "boolean" + "API_Secret": { + "description": "", + "in": "header", + "name": "APCA-API-SECRET-KEY", + "type": "apiKey" + } + } + }, + "info": { + "contact": { + "email": "support@alpaca.markets", + "name": "Alpaca Support", + "url": "https://alpaca.markets/support" + }, + "description": "Alpaca's Trading API is a modern platform for algorithmic trading.", + "termsOfService": "https://s3.amazonaws.com/files.alpaca.markets/disclosures/library/TermsAndConditions.pdf", + "title": "Trading API", + "version": "2.0.1" + }, + "openapi": "3.1.2", + "paths": { + "/v1/locates": { + "get": { + "description": "Returns locates filtered by status, symbol, or date range. Results are sorted by `created_at` descending, with `id` descending as the tie-breaker. This endpoint is not available in paper trading.", + "operationId": "listLocates", + "parameters": [ + { + "$ref": "#/components/parameters/PageToken" }, - "close_price": { - "description": "The price of the last transaction of a security before the market closes for normal trading, shown as a percentage of par value", - "format": "double", - "type": "number", - "x-go-type": "decimal.Decimal", - "x-go-type-import": { - "name": "decimal", - "path": "github.com/alpacahq/alpacadecimal" + { + "description": "Maximum number of results to return.", + "in": "query", + "name": "limit", + "required": false, + "schema": { + "default": 1000, + "maximum": 10000, + "minimum": 1, + "type": "integer" } }, - "close_price_date": { - "description": "The date of the close price", - "format": "date", - "type": "string" - }, - "close_yield_to_maturity": { - "description": "Yield to maturity of the treasury after the last close", - "format": "double", - "type": "number", - "x-go-type": "decimal.Decimal", - "x-go-type-import": { - "name": "decimal", - "path": "github.com/alpacahq/alpacadecimal" + { + "description": "Filter by locate status.", + "example": "active", + "in": "query", + "name": "status", + "required": false, + "schema": { + "$ref": "#/components/schemas/LocateStatus" } }, - "close_yield_to_worst": { - "description": "Yield to worst of the treasury after the last close", - "format": "double", - "type": "number", - "x-go-type": "decimal.Decimal", - "x-go-type-import": { - "name": "decimal", - "path": "github.com/alpacahq/alpacadecimal" + { + "description": "Filter by stock symbol.", + "example": "TSLA", + "in": "query", + "name": "symbol", + "required": false, + "schema": { + "type": "string" } }, - "convertible": { - "description": "A flag indicating whether the bond is convertible", - "type": "boolean" - }, - "country_domicile": { - "description": "The country where the corporate is domiciled in the 2-alpha country code format (e.g., US, CA)", - "type": "string" - }, - "coupon": { - "description": "The annual interest rate paid on the bond as a percentage of par value", - "format": "double", - "type": "number", - "x-go-type": "decimal.Decimal", - "x-go-type-import": { - "name": "decimal", - "path": "github.com/alpacahq/alpacadecimal" + { + "description": "Filter locates with locate trading date on or after this date. Format: YYYY-MM-DD. The locate trading date uses America/New_York and rolls at 8pm ET.", + "in": "query", + "name": "start", + "required": false, + "schema": { + "format": "date", + "type": "string" } }, - "coupon_frequency": { - "$ref": "#/components/schemas/coupon_frequency" - }, - "coupon_type": { - "$ref": "#/components/schemas/coupon_type" - }, - "cusip": { - "description": "CUSIP is a nine-character alphanumeric code that uniquely identifies the security", - "maxLength": 12, - "minLength": 12, - "pattern": "^[A-Z0-9]{9}$", - "type": "string" - }, - "dated_date": { - "description": "The dated date marks the beginning of the period for which interest starts accruing on the bond", - "format": "date", - "type": "string" - }, - "day_count": { - "$ref": "#/components/schemas/day_count" - }, - "description": { - "description": "Description of the corporate bond", - "type": "string" - }, - "description_short": { - "description": "Short description of the corporate bond", - "type": "string" - }, - "first_coupon_date": { - "description": "The date of the first coupon payment", - "format": "date", - "type": "string" + { + "description": "Filter locates with locate trading date before this date (exclusive). Format: YYYY-MM-DD. The locate trading date uses America/New_York and rolls at 8pm ET.", + "in": "query", + "name": "end", + "required": false, + "schema": { + "format": "date", + "type": "string" + } + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ListLocatesResponse" + } + } + }, + "description": "Returns a list of locates for the account, or an empty list if no locates are found." }, - "isin": { - "description": "International Securities Identification Number", - "maxLength": 12, - "minLength": 12, - "pattern": "^[A-Z]{2}[A-Z0-9]{9}[0-9]$", - "type": "string" + "400": { + "content": { + "application/json": { + "examples": { + "invalid_input": { + "value": { + "code": "invalid_input", + "message": "invalid input" + } + } + }, + "schema": { + "$ref": "#/components/schemas/LocateError" + } + } + }, + "description": "Invalid locate request." }, - "issue_date": { - "description": "The date on which the bond was issued", - "format": "date", - "type": "string" + "401": { + "content": { + "application/json": { + "example": { + "message": "Unauthorized" + }, + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + }, + "description": "Authentication headers are missing or invalid. Make sure you authenticate your request with a valid API key." }, - "issue_minimum_denomination": { - "description": "The smallest unit of the bond that can be purchased at its initial offering", - "type": "number" + "403": { + "content": { + "application/json": { + "example": { + "message": "Forbidden" + }, + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + }, + "description": "User has no access to a resource." }, - "issue_price": { - "description": "The price at which the bond was originally issued as a percentage of par value", - "format": "double", - "type": "number", - "x-go-type": "decimal.Decimal", - "x-go-type-import": { - "name": "decimal", - "path": "github.com/alpacahq/alpacadecimal" + "500": { + "content": { + "application/json": { + "example": { + "message": "Internal Server Error" + }, + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + }, + "description": "Internal server error. We recommend retrying these later. If the issue persists, please contact us on Slack or on the Community Forum." + } + }, + "summary": "List Locates", + "tags": [ + "Locates" + ] + }, + "post": { + "description": "Creates a locate request for a short sale. This endpoint is not available in paper trading.\n\n**Idempotency**: Reusing the same key with the same request returns the original\nlocate response. Reusing the same key with a different request returns HTTP 422.\n", + "operationId": "createLocates", + "parameters": [ + { + "$ref": "#/components/parameters/LegacyIdempotencyKey" + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CreateLocateRequest" + } } }, - "issue_size": { - "description": "The total size amount of the bond issue in the issuing currency", - "type": "number" - }, - "issuer": { - "description": "The name of the issuer of the corporate bond", - "type": "string" - }, - "last_coupon_date": { - "description": "The date of the last coupon payment", - "format": "date", - "type": "string" - }, - "liquidity_institutional_aggregate": { - "description": "Score (from 1-5 or null if the bond is not priced/tradable) reflecting the historical depth of executable liquidity to buy or sell (no minimum trading sizes)", - "type": "number" - }, - "liquidity_institutional_buy": { - "description": "Score (from 1-5 or null if the bond is not priced/tradable) reflecting the historical depth of executable liquidity to buy (no minimum trading sizes)", - "type": "number" - }, - "liquidity_institutional_sell": { - "description": "Score (from 1-5 or null if the bond is not priced/tradable) reflecting the historical depth of executable liquidity to sell (no minimum trading sizes)", - "type": "number" - }, - "liquidity_micro_aggregate": { - "description": "Score (from 1-5 or null if the bond is not priced/tradable) reflecting the historical depth of executable liquidity to buy or sell with minimum trading sizes less than or equal to $1,000.00", - "type": "number" - }, - "liquidity_micro_buy": { - "description": "Score (from 1-5 if the bond is priced, or null if the bond is not tradable) reflecting the historical depth of executable liquidity to buy with minimum trading sizes less than or equal to $1,000.00", - "type": "number" - }, - "liquidity_micro_sell": { - "description": "Score (from 1-5 or null if the bond is not priced/tradable) reflecting the historical depth of executable liquidity to sell with minimum trading sizes less than or equal to $1,000.00", - "type": "number" - }, - "liquidity_retail_aggregate": { - "description": "Score (from 1-5 or null if the bond is not priced/tradable) reflecting the historical depth of executable liquidity to buy or sell with minimum trading sizes less than or equal to 10,000", - "type": "number" - }, - "liquidity_retail_buy": { - "description": "Score (from 1-5 or null if the bond is not priced/tradable) reflecting the historical depth of executable liquidity to buy with minimum trading sizes less than or equal to $10,000.00", - "type": "number" - }, - "liquidity_retail_sell": { - "description": "Score (from 1-5 or null if the bond is not priced/tradable) reflecting the historical depth of executable liquidity to sell with minimum trading sizes less than or equal to 10,000", - "type": "number" - }, - "marginable": { - "description": "Whether the corporate is marginable", - "type": "boolean" - }, - "maturity_date": { - "description": "The date on which the bond matures", - "format": "date", - "type": "string" - }, - "next_call_date": { - "description": "The date of the next possible call on the bond.", - "format": "date", - "type": "string" - }, - "next_call_price": { - "description": "The price at which a callable bond can be redeemed by the issuer on the next call date, as a percentage of par.", - "format": "double", - "type": "number", - "x-go-type": "decimal.Decimal", - "x-go-type-import": { - "name": "decimal", - "path": "github.com/alpacahq/alpacadecimal" - } - }, - "next_coupon_date": { - "description": "The date of the next coupon payment", - "format": "date", - "type": "string" - }, - "par_value": { - "description": "The amount that the issuer of the bond will pay back to the bondholder upon maturity", - "type": "number" - }, - "perpetual": { - "description": "A flag representing whether a bond is perpetual", - "type": "boolean" - }, - "puttable": { - "description": "Whether the bond is puttable, meaning the bondholder has the right, but not the obligation to sell the bond back to the issuer at a set price (the put price) on specified dates before maturity", - "type": "boolean" - }, - "reg_s": { - "description": "Indicates whether the security falls under Regulation S, a rule that provides an exemption from the registration requirements for securities offerings made outside the United States", - "type": "boolean" - }, - "reissue_date": { - "description": "The date on which the corporate was reissued", - "format": "date", - "type": "string" - }, - "reissue_price": { - "description": "The price at which the corporate was reissued as a percentage of par value", - "format": "double", - "type": "number", - "x-go-type": "decimal.Decimal", - "x-go-type-import": { - "name": "decimal", - "path": "github.com/alpacahq/alpacadecimal" - } - }, - "reissue_size": { - "description": "The total size amount of the corporate reissue in the issuing currency", - "type": "number" - }, - "sector": { - "description": "The sector of the corporate bond", - "type": "string" - }, - "seniority": { - "description": "The seniority of the corporate bond", - "type": "string" - }, - "sp_creditwatch": { - "description": "S&P's CreditWatch highlights S&P's opinion regarding the potential direction of a short-term or long-term rating", - "type": "string" - }, - "sp_creditwatch_date": { - "description": "The date of the most recent Standard & Poor's CreditWatch for the bond in YYYY-MM-DD format", - "format": "date", - "type": "string" - }, - "sp_outlook": { - "$ref": "#/components/schemas/sp_outlook" - }, - "sp_outlook_date": { - "description": "The date of the most recent Standard & Poor's outlook for the bond in YYYY-MM-DD format", - "format": "date", - "type": "string" - }, - "sp_rating": { - "description": "Standard & Poor's rating for the bond in the standard AAA - D format", - "type": "string" - }, - "sp_rating_date": { - "description": "The date in the timezone of the issuing country of the most recent Standard & Poor's rating for the bond in YYYY-MM-DD format", - "format": "date", - "type": "string" - }, - "ticker": { - "description": "The ticker symbol of the corporate", - "type": "string" - }, - "tradable": { - "description": "Whether the treasury is tradable", - "type": "boolean" - } - }, - "required": [ - "isin", - "cusip", - "bond_status", - "tradable", - "marginable", - "issue_date", - "country_domicile", - "ticker", - "seniority", - "issuer", - "sector", - "description", - "description_short", - "coupon", - "coupon_type", - "coupon_frequency", - "perpetual", - "day_count", - "dated_date", - "issue_size", - "issue_price", - "issue_minimum_denomination", - "par_value", - "callable", - "puttable", - "convertible", - "reg_s" - ], - "type": "object" - }, - "us_corporates_resp": { - "properties": { - "us_corporates": { - "items": { - "$ref": "#/components/schemas/us_corporate" - }, - "type": "array" - } - }, - "required": [ - "us_corporates" - ], - "type": "object" - }, - "us_treasuries_resp": { - "properties": { - "us_treasuries": { - "items": { - "$ref": "#/components/schemas/us_treasury" - }, - "type": "array" - } - }, - "required": [ - "us_treasuries" - ], - "type": "object" - }, - "us_treasury": { - "description": "A US treasury", - "properties": { - "bond_status": { - "$ref": "#/components/schemas/bond_status" - }, - "close_price": { - "description": "The price of the last transaction of a security before the market closes for normal trading, shown as a percentage of par value", - "format": "double", - "type": "number" - }, - "close_price_date": { - "description": "The date of the close price", - "format": "date", - "type": "string" - }, - "close_yield_to_maturity": { - "description": "Yield to maturity of the treasury after the last close", - "format": "double", - "type": "number" - }, - "close_yield_to_worst": { - "description": "Yield to worst of the treasury after the last close", - "format": "double", - "type": "number" - }, - "coupon": { - "description": "The annual interest rate paid on the bond as a percentage of par value", - "format": "double", - "type": "number" - }, - "coupon_frequency": { - "$ref": "#/components/schemas/coupon_frequency" - }, - "coupon_type": { - "$ref": "#/components/schemas/coupon_type" - }, - "cusip": { - "description": "CUSIP is a nine-character alphanumeric code that uniquely identifies the security", - "maxLength": 12, - "minLength": 12, - "pattern": "^[A-Z0-9]{9}$", - "type": "string" - }, - "description": { - "description": "Description of the treasury", - "type": "string" - }, - "description_short": { - "description": "Short description of the treasury", - "type": "string" - }, - "first_coupon_date": { - "description": "The date of the first coupon payment", - "format": "date", - "type": "string" - }, - "isin": { - "description": "International Securities Identification Number", - "maxLength": 12, - "minLength": 12, - "pattern": "^[A-Z]{2}[A-Z0-9]{9}[0-9]$", - "type": "string" - }, - "issue_date": { - "description": "The date on which the bond was issued", - "format": "date", - "type": "string" - }, - "last_coupon_date": { - "description": "The date of the last coupon payment", - "format": "date", - "type": "string" - }, - "maturity_date": { - "description": "The date on which the bond matures", - "format": "date", - "type": "string" - }, - "next_coupon_date": { - "description": "The date of the next coupon payment", - "format": "date", - "type": "string" - }, - "subtype": { - "$ref": "#/components/schemas/treasury_subtype" - }, - "tradable": { - "description": "Whether the treasury is tradable", - "type": "boolean" - } - }, - "required": [ - "cusip", - "isin", - "bond_status", - "tradable", - "subtype", - "issue_date", - "maturity_date", - "description", - "description_short", - "coupon", - "coupon_type", - "coupon_frequency" - ], - "type": "object" - } - }, - "securitySchemes": { - "API_Key": { - "description": "", - "in": "header", - "name": "APCA-API-KEY-ID", - "type": "apiKey" - }, - "API_Secret": { - "description": "", - "in": "header", - "name": "APCA-API-SECRET-KEY", - "type": "apiKey" - } - } - }, - "info": { - "contact": { - "email": "support@alpaca.markets", - "name": "Alpaca Support", - "url": "https://alpaca.markets/support" - }, - "description": "Alpaca's Trading API is a modern platform for algorithmic trading.", - "termsOfService": "https://s3.amazonaws.com/files.alpaca.markets/disclosures/library/TermsAndConditions.pdf", - "title": "Trading API", - "version": "2.0.1" - }, - "openapi": "3.0.0", - "paths": { - "/v1/locates": { - "get": { - "description": "Returns locates filtered by status, symbol, or date range. Results are sorted by `created_at` descending, with `id` descending as the tie-breaker.", - "operationId": "listLocates", - "parameters": [ - { - "$ref": "#/components/parameters/PageToken" - }, - { - "description": "Maximum number of results to return.", - "in": "query", - "name": "limit", - "required": false, - "schema": { - "default": 1000, - "maximum": 10000, - "minimum": 1, - "type": "integer" - } - }, - { - "description": "Filter by locate status.", - "example": "active", - "in": "query", - "name": "status", - "required": false, - "schema": { - "$ref": "#/components/schemas/LocateStatus" - } - }, - { - "description": "Filter by stock symbol.", - "example": "TSLA", - "in": "query", - "name": "symbol", - "required": false, - "schema": { - "type": "string" - } - }, - { - "description": "Filter locates with locate trading date on or after this date. Format: YYYY-MM-DD. The locate trading date uses America/New_York and rolls at 8pm ET.", - "in": "query", - "name": "start", - "required": false, - "schema": { - "format": "date", - "type": "string" - } - }, - { - "description": "Filter locates with locate trading date before this date (exclusive). Format: YYYY-MM-DD. The locate trading date uses America/New_York and rolls at 8pm ET.", - "in": "query", - "name": "end", - "required": false, - "schema": { - "format": "date", - "type": "string" - } - } - ], - "responses": { - "200": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ListLocatesResponse" - } - } - }, - "description": "Returns a list of locates for the account, or an empty list if no locates are found." - }, - "400": { - "content": { - "application/json": { - "examples": { - "invalid_input": { - "value": { - "code": "invalid_input", - "message": "invalid input" - } - } - }, - "schema": { - "$ref": "#/components/schemas/LocateError" - } - } - }, - "description": "Invalid locate request." - }, - "401": { - "content": { - "application/json": { - "example": { - "message": "Unauthorized" - }, - "schema": { - "$ref": "#/components/schemas/ErrorResponse" - } - } - }, - "description": "Authentication headers are missing or invalid. Make sure you authenticate your request with a valid API key." - }, - "403": { - "content": { - "application/json": { - "example": { - "message": "Forbidden" - }, - "schema": { - "$ref": "#/components/schemas/ErrorResponse" - } - } - }, - "description": "User has no access to a resource." - }, - "500": { - "content": { - "application/json": { - "example": { - "message": "Internal Server Error" - }, - "schema": { - "$ref": "#/components/schemas/ErrorResponse" - } - } - }, - "description": "Internal server error. We recommend retrying these later. If the issue persists, please contact us on Slack or on the Community Forum." - } - }, - "summary": "List Locates", - "tags": [ - "Locates" - ] - }, - "post": { - "description": "Creates a locate request for a short sale.", - "operationId": "createLocates", - "requestBody": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/CreateLocateRequest" - } - } - }, - "description": "Locate request details.", - "required": true - }, - "responses": { - "201": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/Locate" - } - } - }, - "description": "Created" - }, - "400": { - "content": { - "application/json": { - "examples": { - "invalid_input": { - "value": { - "code": "invalid_input", - "message": "invalid input: quantity must be in round lots of 100" - } - }, - "invalid_limit_price": { - "value": { - "code": "invalid_limit_price", - "message": "invalid limit_price" - } - }, - "invalid_request_body": { - "value": { - "code": "invalid_request_body", - "message": "invalid request body" - } - } - }, - "schema": { - "$ref": "#/components/schemas/LocateError" - } - } - }, - "description": "Invalid locate request." - }, - "401": { - "content": { - "application/json": { - "example": { - "message": "Unauthorized" - }, - "schema": { - "$ref": "#/components/schemas/ErrorResponse" - } - } - }, - "description": "Authentication headers are missing or invalid. Make sure you authenticate your request with a valid API key." - }, - "403": { - "content": { - "application/json": { - "example": { - "message": "Forbidden" - }, - "schema": { - "$ref": "#/components/schemas/ErrorResponse" - } - } - }, - "description": "User has no access to a resource." - }, - "422": { - "content": { - "application/json": { - "examples": { - "easy_to_borrow": { - "value": { - "code": "easy_to_borrow", - "message": "security is easy-to-borrow and does not require a locate" - } - }, - "insufficient_buying_power": { - "value": { - "code": "insufficient_buying_power", - "message": "insufficient buying power to cover locate fee" - } - }, - "symbol_not_found": { - "value": { - "code": "symbol_not_found", - "message": "symbol not found" - } - }, - "threshold_security": { - "value": { - "code": "threshold_security", - "message": "security is on the threshold list and cannot be located" - } - } - }, - "schema": { - "$ref": "#/components/schemas/LocateError" - } - } - }, - "description": "Cannot complete locate request." - }, - "500": { - "content": { - "application/json": { - "example": { - "message": "Internal Server Error" - }, - "schema": { - "$ref": "#/components/schemas/ErrorResponse" - } - } - }, - "description": "Internal server error. We recommend retrying these later. If the issue persists, please contact us on Slack or on the Community Forum." - } - }, - "summary": "Create Locate", - "tags": [ - "Locates" - ] - } - }, - "/v1/locates/{locate_id}": { - "get": { - "description": "Returns a locate by ID.", - "operationId": "getLocate", - "parameters": [ - { - "description": "The locate ID.", - "example": "550e8400-e29b-41d4-a716-446655440000", - "in": "path", - "name": "locate_id", - "required": true, - "schema": { - "format": "uuid", - "type": "string" - } - } - ], - "responses": { - "200": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/Locate" - } - } - }, - "description": "Returns the requested locate." + "description": "Locate request details.", + "required": true + }, + "responses": { + "201": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Locate" + } + } + }, + "description": "Created" }, "400": { "content": { @@ -6355,112 +6876,20 @@ "examples": { "invalid_input": { "value": { - "code": "invalid_input", - "message": "invalid input" - } - } - }, - "schema": { - "$ref": "#/components/schemas/LocateError" - } - } - }, - "description": "Invalid locate request." - }, - "401": { - "content": { - "application/json": { - "example": { - "message": "Unauthorized" - }, - "schema": { - "$ref": "#/components/schemas/ErrorResponse" - } - } - }, - "description": "Authentication headers are missing or invalid. Make sure you authenticate your request with a valid API key." - }, - "403": { - "content": { - "application/json": { - "example": { - "message": "Forbidden" - }, - "schema": { - "$ref": "#/components/schemas/ErrorResponse" - } - } - }, - "description": "User has no access to a resource." - }, - "404": { - "content": { - "application/json": { - "example": { - "message": "locate not found" - }, - "schema": { - "$ref": "#/components/schemas/ErrorResponse" - } - } - }, - "description": "Not found" - }, - "500": { - "content": { - "application/json": { - "example": { - "message": "Internal Server Error" - }, - "schema": { - "$ref": "#/components/schemas/ErrorResponse" - } - } - }, - "description": "Internal server error. We recommend retrying these later. If the issue persists, please contact us on Slack or on the Community Forum." - } - }, - "summary": "Get Locate", - "tags": [ - "Locates" - ] - } - }, - "/v1/locates/quotes": { - "get": { - "description": "Returns locate availability and pricing for one or more symbols.", - "operationId": "listLocateQuotes", - "parameters": [ - { - "description": "Comma-separated list of stock symbols. Maximum 100 unique symbols.", - "example": "TSLA,AAPL", - "in": "query", - "name": "symbols", - "required": true, - "schema": { - "type": "string" - } - } - ], - "responses": { - "200": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ListLocateQuotesResponse" - } - } - }, - "description": "Returns locate quotes for the requested symbols." - }, - "400": { - "content": { - "application/json": { - "examples": { - "invalid_symbols": { - "value": { - "code": "invalid_symbols", - "message": "symbols must contain at most 100 unique symbols" + "code": "invalid_input", + "message": "invalid input: quantity must be in round lots of 100" + } + }, + "invalid_limit_price": { + "value": { + "code": "invalid_limit_price", + "message": "invalid limit_price" + } + }, + "invalid_request_body": { + "value": { + "code": "invalid_request_body", + "message": "invalid request body" } } }, @@ -6469,7 +6898,7 @@ } } }, - "description": "Invalid locate quote request." + "description": "Invalid locate request." }, "401": { "content": { @@ -6482,345 +6911,102 @@ } } }, - "description": "Authentication headers are missing or invalid. Make sure you authenticate your request with a valid API key." - }, - "403": { - "content": { - "application/json": { - "example": { - "message": "Forbidden" - }, - "schema": { - "$ref": "#/components/schemas/ErrorResponse" - } - } - }, - "description": "User has no access to a resource." - }, - "500": { - "content": { - "application/json": { - "example": { - "message": "Internal Server Error" - }, - "schema": { - "$ref": "#/components/schemas/ErrorResponse" - } - } - }, - "description": "Internal server error. We recommend retrying these later. If the issue persists, please contact us on Slack or on the Community Forum." - } - }, - "summary": "Get Locate Quotes", - "tags": [ - "Locates" - ] - } - }, - "/v2beta1/events/activities": { - "get": { - "description": "The Events API sends the real-time events and provides historical queries with SSE (Server Sent Events).\n\nThis endpoint streams events on account activities.\n\nHistorical events are streamed immediately if queried, and updates are pushed as events occur.\n\nQuery parameter rules:\n- If `until` is specified, `since` is required.\n- If `until_id` is specified, `since_id` is required.\n- You cannot use `since` and `since_id` together.\nBehavior:\n- If `since` or `since_id` is not specified, this will not return any historic data.\n- If `until` or `until_id` is specified, stream will end at the specified point with status 200.\n\n---\n\nWarning: Currently, OAS-3 does not fully support responses from an SSE API.\n\nIn case the client code is generated from this OAS spec, please do not specify `since` and `until`, as the generated client may hang forever waiting for the response to end.\n\nIf you require the streaming capabilities, we recommend not using the generated clients for this specific endpoint until the OAS-3 standards define how to represent this behavior.\n\n---\n\n### Comment messages\nAccording to the SSE specification, any line that starts with a colon is a comment which does not contain data. It is typically a free text that does not follow any data schema. A few examples mentioned below for comment messages.\n\n##### Slow client\n\nThe server sends a comment when the client is not consuming messages fast enough. Example: `: you are reading too slowly, dropped 10000 messages`\n\n##### Internal server error\n\nAn error message is sent as a comment when the server closes the connection on an internal server error (only sent by the v2 and v2beta1 endpoints). Example: `: internal server error`\n\n---", - "operationId": "subscribeToActivitiesSSE", - "parameters": [ - { - "description": "Format: RFC3339 or YYYY-MM-DD", - "in": "query", - "name": "since", - "schema": { - "format": "date-time", - "type": "string" - } - }, - { - "description": "Format: RFC3339 or YYYY-MM-DD", - "in": "query", - "name": "until", - "schema": { - "format": "date-time", - "type": "string" - } - }, - { - "in": "query", - "name": "since_id", - "schema": { - "format": "ulid", - "type": "string" - } - }, - { - "in": "query", - "name": "until_id", - "schema": { - "format": "ulid", - "type": "string" - } - } - ], - "responses": { - "200": { - "content": { - "text/event-stream": { - "schema": { - "items": { - "$ref": "#/components/schemas/ActivityEventV2" - }, - "type": "array" - } - } - }, - "description": "Connected. Events will now start streaming as long as you keep the connection open." - } - }, - "summary": "Subscribe to Activity Events (SSE)", - "tags": [ - "Events" - ] - } - }, - "/v2/account": { - "get": { - "description": "Returns your account details.", - "operationId": "getAccount", - "responses": { - "200": { - "content": { - "application/json": { - "example": { - "account_blocked": false, - "account_number": "PALPACA_123", - "accrued_fees": "0", - "admin_configurations": {}, - "balance_asof": "2023-09-27", - "buying_power": "245432.61", - "cash": "122086.5", - "created_at": "2023-01-01T18:20:20.272275Z", - "crypto_status": "ACTIVE", - "crypto_tier": 1, - "currency": "USD", - "effective_buying_power": "245432.61", - "equity": "123346.11", - "id": "1d9eed04-be39-4e01-9b84-a48ac5bbafcf", - "initial_margin": "629.8", - "intraday_adjustments": "0", - "last_equity": "122011.09751111286868", - "last_maintenance_margin": "480.73", - "long_market_value": "1259.61", - "maintenance_margin": "377.88", - "multiplier": "2", - "non_marginable_buying_power": "122086.5", - "options_buying_power": "122716.305", - "options_trading_level": 2, - "pending_reg_taf_fees": "0", - "pending_transfer_in": "0", - "portfolio_value": "123346.11", - "position_market_value": "1259.61", - "regt_buying_power": "245432.61", - "short_market_value": "0", - "shorting_enabled": true, - "sma": "123369.74", - "status": "ACTIVE", - "trade_suspended_by_user": false, - "trading_blocked": false, - "transfers_blocked": false, - "user_configurations": null - }, - "schema": { - "$ref": "#/components/schemas/Account" - } - } - }, - "description": "OK" - } - }, - "summary": "Get Account", - "tags": [ - "Accounts" - ] - } - }, - "/v2/account/activities": { - "get": { - "description": "Returns a list of activities\n\nNotes:\n* Pagination is handled using the `page_token` and `page_size` parameters.\n* `page_token` represents the ID of the last item on your current page of results.\n For example, if the ID of the last activity in your first response is `20220203000000000::045b3b8d-c566-4bef-b741-2bf598dd6ae7`, you would pass that value as `page_token` to retrieve the next page of results.", - "operationId": "getAccountActivities", - "parameters": [ - { - "description": "A comma-separated list of activity types used to filter the results.", - "explode": false, - "in": "query", - "name": "activity_types", - "schema": { - "items": { - "$ref": "#/components/schemas/ActivityType" - }, - "type": "array" - }, - "style": "form" - }, - { - "description": "The activity category. Cannot be used with \"activity_types\" parameter.", - "in": "query", - "name": "category", - "schema": { - "enum": [ - "trade_activity", - "non_trade_activity" - ], - "type": "string" - } - }, - { - "description": "Filter activities by their creation date (created_at), not the activity's settlement date. For non-trade activities such as fees, the creation date is typically the day after the trade date (in UTC). Both formats YYYY-MM-DD and YYYY-MM-DDTHH:MM:SSZ are supported.", - "in": "query", - "name": "date", - "schema": { - "format": "date-time", - "type": "string" - } - }, - { - "description": "Get activities created before this date. Both formats YYYY-MM-DD and YYYY-MM-DDTHH:MM:SSZ are supported.", - "in": "query", - "name": "until", - "schema": { - "format": "date-time", - "type": "string" - } - }, - { - "description": "Get activities created after this date. Both formats YYYY-MM-DD and YYYY-MM-DDTHH:MM:SSZ are supported.", - "in": "query", - "name": "after", - "schema": { - "format": "date-time", - "type": "string" - } - }, - { - "description": "The chronological order of response based on the activity datetime.", - "in": "query", - "name": "direction", - "schema": { - "default": "desc", - "enum": [ - "asc", - "desc" - ], - "example": "desc", - "type": "string" - } - }, - { - "description": "The maximum number of entries to return in the response.", - "in": "query", - "name": "page_size", - "schema": { - "default": 100, - "maximum": 100, - "minimum": 1, - "type": "integer" - } + "description": "Authentication headers are missing or invalid. Make sure you authenticate your request with a valid API key." }, - { - "description": "Token used for pagination. Provide the ID of the last activity from the last page to retrieve the next set of results.", - "in": "query", - "name": "page_token", - "schema": { - "type": "string" - } - } - ], - "responses": { - "200": { + "403": { "content": { "application/json": { + "example": { + "message": "Forbidden" + }, "schema": { - "items": { - "anyOf": [ - { - "$ref": "#/components/schemas/TradingActivities" - }, - { - "$ref": "#/components/schemas/NonTradeActivities" - } - ], - "description": "Will be a mix of TradingActivity or NonTradeActivity objects based on what is passed in the activity_types parameter" + "$ref": "#/components/schemas/ErrorResponse" + } + } + }, + "description": "User has no access to a resource." + }, + "422": { + "content": { + "application/json": { + "examples": { + "easy_to_borrow": { + "value": { + "code": "easy_to_borrow", + "message": "security is easy-to-borrow and does not require a locate" + } }, - "type": "array" + "idempotency_key_conflict": { + "value": { + "code": "idempotency_key_conflict", + "message": "idempotency key was already used with a different request" + } + }, + "insufficient_buying_power": { + "value": { + "code": "insufficient_buying_power", + "message": "insufficient buying power to cover locate fee" + } + }, + "quote_unavailable": { + "value": { + "code": "quote_unavailable", + "message": "locate quote is unavailable for this symbol" + } + }, + "symbol_not_found": { + "value": { + "code": "symbol_not_found", + "message": "symbol not found" + } + }, + "threshold_security": { + "value": { + "code": "threshold_security", + "message": "security is on the threshold list and cannot be located" + } + } + }, + "schema": { + "$ref": "#/components/schemas/LocateError" } } }, - "description": "returns an array of Account activities" + "description": "Cannot complete locate request." + }, + "500": { + "content": { + "application/json": { + "example": { + "message": "Internal Server Error" + }, + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + }, + "description": "Internal server error. We recommend retrying these later. If the issue persists, please contact us on Slack or on the Community Forum." } }, - "summary": "Retrieve Account Activities", + "summary": "Create Locate", "tags": [ - "Account Activities" + "Locates" ] } }, - "/v2/account/activities/{activity_type}": { + "/v1/locates/{locate_id}": { "get": { - "description": "Returns account activity entries for a specific type of activity.", - "operationId": "getAccountActivitiesByActivityType", + "description": "Returns a locate by ID. This endpoint is not available in paper trading.", + "operationId": "getLocate", "parameters": [ { - "description": "Filter activities by their creation date (created_at), not the activity's settlement date. For non-trade activities such as fees, the creation date is typically the day after the trade date (in UTC). Both formats YYYY-MM-DD and YYYY-MM-DDTHH:MM:SSZ are supported.", - "in": "query", - "name": "date", - "schema": { - "format": "date-time", - "type": "string" - } - }, - { - "description": "Get activities created before this date. Both formats YYYY-MM-DD and YYYY-MM-DDTHH:MM:SSZ are supported.", - "in": "query", - "name": "until", - "schema": { - "format": "date-time", - "type": "string" - } - }, - { - "description": "Get activities created after this date. Both formats YYYY-MM-DD and YYYY-MM-DDTHH:MM:SSZ are supported.", - "in": "query", - "name": "after", - "schema": { - "format": "date-time", - "type": "string" - } - }, - { - "description": "The chronological order of response based on the activity datetime.", - "in": "query", - "name": "direction", - "schema": { - "default": "desc", - "enum": [ - "asc", - "desc" - ], - "example": "desc", - "type": "string" - } - }, - { - "description": "The maximum number of entries to return in the response.", - "in": "query", - "name": "page_size", - "schema": { - "default": 100, - "maximum": 100, - "minimum": 1, - "type": "integer" - } - }, - { - "description": "Token used for pagination. Provide the ID of the last activity from the last page to retrieve the next set of results.", - "in": "query", - "name": "page_token", + "description": "The locate ID.", + "example": "550e8400-e29b-41d4-a716-446655440000", + "in": "path", + "name": "locate_id", + "required": true, "schema": { + "format": "uuid", "type": "string" } } @@ -6830,619 +7016,503 @@ "content": { "application/json": { "schema": { - "items": { - "description": "Will be one of a TradingActivity or NonTradeActivity based on activity_type used in path", - "oneOf": [ - { - "$ref": "#/components/schemas/TradingActivities" - }, - { - "$ref": "#/components/schemas/NonTradeActivities" - } - ] - }, - "type": "array" + "$ref": "#/components/schemas/Locate" } } }, - "description": "returns an array of Account activities" - } - }, - "summary": "Retrieve Account Activities of Specific Type", - "tags": [ - "Account Activities" - ] - }, - "parameters": [ - { - "description": "The activity type you want to view entries for. A list of valid activity types can be found at the bottom of this page.", - "in": "path", - "name": "activity_type", - "required": true, - "schema": { - "type": "string" - } - } - ] - }, - "/v2/account/configurations": { - "get": { - "description": "gets the current account configuration values", - "operationId": "getAccountConfig", - "responses": { - "200": { + "description": "Returns the requested locate." + }, + "400": { "content": { "application/json": { + "examples": { + "invalid_input": { + "value": { + "code": "invalid_input", + "message": "invalid input" + } + } + }, "schema": { - "$ref": "#/components/schemas/AccountConfigurations" + "$ref": "#/components/schemas/LocateError" } } }, - "description": "Successful response" - } - }, - "summary": "Get Account Configurations", - "tags": [ - "Account Configurations" - ] - }, - "patch": { - "description": "Updates and returns the current account configuration values", - "operationId": "patchAccountConfig", - "requestBody": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/AccountConfigurations" + "description": "Invalid locate request." + }, + "401": { + "content": { + "application/json": { + "example": { + "message": "Unauthorized" + }, + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + }, + "description": "Authentication headers are missing or invalid. Make sure you authenticate your request with a valid API key." + }, + "403": { + "content": { + "application/json": { + "example": { + "message": "Forbidden" + }, + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + }, + "description": "User has no access to a resource." + }, + "404": { + "content": { + "application/json": { + "example": { + "message": "locate not found" + }, + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } } - } - } - }, - "responses": { - "200": { + }, + "description": "Not found" + }, + "500": { "content": { "application/json": { + "example": { + "message": "Internal Server Error" + }, "schema": { - "$ref": "#/components/schemas/AccountConfigurations" + "$ref": "#/components/schemas/ErrorResponse" } } }, - "description": "Successful response" + "description": "Internal server error. We recommend retrying these later. If the issue persists, please contact us on Slack or on the Community Forum." } }, - "summary": "Account Configurations", + "summary": "Get Locate", "tags": [ - "Account Configurations" + "Locates" ] } }, - "/v2/account/portfolio/history": { + "/v1/locates/quotes": { "get": { - "description": "Returns timeseries data about equity and profit/loss (P/L) of the account in requested timespan.", - "operationId": "getAccountPortfolioHistory", + "description": "Returns locate availability and pricing for one or more symbols. This endpoint is not available in paper trading.", + "operationId": "listLocateQuotes", "parameters": [ { - "description": "The duration of the data in `number` + `unit` format, such as 1D, where `unit` can be D for day, W for week, M for month and A for year. Defaults to 1M.\n\nOnly two of `start`, `end` and `period` can be specified at the same time.\n\nFor intraday timeframes (\\<1D) only 30 days or less can be queried, for 1D resolutions there is no such limit, data is available since the\ncreation of the account.\n", - "in": "query", - "name": "period", - "schema": { - "type": "string" - } - }, - { - "description": "The resolution of time window. 1Min, 5Min, 15Min, 1H, or 1D. If omitted, 1Min for less than 7 days period,\n15Min for less than 30 days, or otherwise 1D.\n\nFor queries with longer than 30 days of `period`, the system only accepts 1D as `timeframe`.\n", - "in": "query", - "name": "timeframe", - "schema": { - "type": "string" - } - }, - { - "description": "For intraday resolutions (<1D) this specifies which timestamps to return data points for:\n\nAllowed values are:\n- **market_hours**\n\n Only timestamps for the core equity trading hours are returned (usually 9:30am to 4:00pm, trading days only)\n\n- **extended_hours**\n\n Returns timestamps for the whole session including extended hours (usually 4:00am to 8:00pm, trading days only)\n\n- **continuous**\n\n Returns price data points 24/7 (for off-session times too). To calculate the equity values we are using the following prices:\n\n Between 4:00am and 10:00pm on trading days the valuation will be calculated based on the last trade (extended hours and normal hours respectively).\n\n After 10:00pm, until the next session open the equities will be valued at their official closing price on the primary exchange.\n", - "in": "query", - "name": "intraday_reporting", - "schema": { - "default": "market_hours", - "enum": [ - "market_hours", - "extended_hours", - "continuous" - ], - "type": "string" - } - }, - { - "description": "The timestamp the data is returned starting from in RFC3339 format (including timezone specification). Defaults to `end` minus `period`\n\nIf provided, the `start` value is always normalized to the `America/New_York` timezone and adjusted to the nearest `timeframe` interval, e.g. seconds are always truncated and the time is rounded backwards to the nearest interval of `1Min`, `5Min`, `15Min`, or `1H`.\n\nIf `timeframe=1D` and `start` is not a valid trading date, find the next available trading date. For example, if `start` occurs on Saturday or Sunday after converting to the America/New_York timezone, `start` is adjusted to the first weekday that is not a market holiday (e.g. Monday).\n\nIf `timeframe` is less than `1D` and `intraday_reporting` is not `continuous`, `start` always reflects the beginning of a market session. If `start` is between midnight and the end (inclusive) of an active trading day, `start` is set to the beginning of the session on the specified day. Otherwise, if `start` occurs outside of the market session, the next available market date is used.\n\nFor example, when `intraday_reporting=market_hours` and `start=2023-10-19T23:59:59-04:00`, the provided `start` date occurs outside of the regular market session. The effective `start` timestamp is adjusted to the beginning of the next session: `2023-10-20T09:30:00-04:00`\n\n`start` may be be combined with one of `end` or `period`.\n\nProviding all of `start`, `end`, and `period` is invalid.\n", - "in": "query", - "name": "start", - "schema": { - "example": "2021-03-16T18:38:01Z", - "format": "date-time", - "type": "string" - } - }, - { - "description": "`pnl_reset` defines how we are calculating the baseline values for Profit And Loss (pnl) for queries with `timeframe` less than 1D (intraday queries).\n\nThe default behavior for intraday queries is that we reset the pnl value to the previous day's closing equity for each **trading** day.\n\nIn case of crypto (given its continuous nature), this might not be desired: specifying \"no_reset\" disables this behavior and all pnl values\nreturned will be relative to the closing equity of the previous trading day.\n\nFor 1D resolution all PnL values are calculated relative to the `base_value`, we are not resetting the base value.\n", + "description": "Comma-separated list of stock symbols. Maximum 100 unique symbols.", + "example": "TSLA,AAPL", "in": "query", - "name": "pnl_reset", + "name": "symbols", + "required": true, "schema": { - "default": "per_day", - "enum": [ - "no_reset", - "per_day" - ], "type": "string" } + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ListLocateQuotesResponse" + } + } + }, + "description": "Returns locate quotes for the requested symbols." }, - { - "description": "The timestamp the data is returned up to in RFC3339 format (including timezone specification). Defaults to the current time.\n\nIf provided, the `end` value is always normalized to the `America/New_York` timezone and adjusted to the nearest `timeframe` interval, e.g. seconds are always truncated and the time is rounded backwards to the nearest interval of `1Min`, `5Min`, `15Min`, or `1H`.\n\nWhen `intraday_reporting` is either `market_hours` or `extended_hours`, the `end` value is adjusted to not occur after session close on the specified day. For example if the `intraday_reporting` is `extended_hours`, and the timestamp specified is `2023-10-19T21:33:00-04:00`, `end` is adjusted to `2023-10-19T20:00:00-04:00`.\n\n`end` may be combined with `start` or `period`.\n\nProviding all of `start`, `end`, and `period` is invalid.\n", - "in": "query", - "name": "end", - "schema": { - "example": "2021-03-16T18:38:01Z", - "format": "date-time", - "type": "string" - } + "400": { + "content": { + "application/json": { + "examples": { + "invalid_symbols": { + "value": { + "code": "invalid_symbols", + "message": "symbols must contain at most 100 unique symbols" + } + } + }, + "schema": { + "$ref": "#/components/schemas/LocateError" + } + } + }, + "description": "Invalid locate quote request." }, - { - "description": "**deprecated**: Users are strongly advised to **rely on the `intraday_reporting` query parameter** for better control\nof the reporting range.\n\nIf true, include extended hours in the result. This is effective only for timeframe less than 1D.\n", - "in": "query", - "name": "extended_hours", - "schema": { - "type": "string" - } + "401": { + "content": { + "application/json": { + "example": { + "message": "Unauthorized" + }, + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + }, + "description": "Authentication headers are missing or invalid. Make sure you authenticate your request with a valid API key." }, - { - "description": "The cashflow activities to include in the report. One of 'ALL', 'NONE', or a comma-separated list of activity types.", - "examples": { - "all": { - "value": "ALL" - }, - "cash_transfers": { - "summary": "Include cash deposits, withdraws, and transaction fees", - "value": "CSD,CSW,FEE" - }, - "none": { - "value": "NONE" + "403": { + "content": { + "application/json": { + "example": { + "message": "Forbidden" + }, + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } } }, - "in": "query", - "name": "cashflow_types", - "schema": { - "type": "string" - } - } - ], - "responses": { - "200": { + "description": "User has no access to a resource." + }, + "500": { "content": { "application/json": { + "example": { + "message": "Internal Server Error" + }, "schema": { - "$ref": "#/components/schemas/PortfolioHistory" + "$ref": "#/components/schemas/ErrorResponse" } } }, - "description": "Successful response" + "description": "Internal server error. We recommend retrying these later. If the issue persists, please contact us on Slack or on the Community Forum." } }, - "summary": "Get Account Portfolio History", + "summary": "Get Locate Quotes", "tags": [ - "Portfolio History" + "Locates" ] } }, - "/v2/assets": { + "/v2beta1/events/activities": { "get": { - "description": "The assets API serves as the master list of assets available for trade and data consumption from Alpaca. Assets are sorted by asset class, exchange and symbol.", - "operationId": "get-v2-assets", + "description": "The Events API sends the real-time events and provides historical queries with SSE (Server Sent Events).\n\nThis endpoint streams events on account activities.\n\nHistorical events are streamed immediately if queried, and updates are pushed as events occur.\n\nQuery parameter rules:\n- If `until` is specified, `since` is required.\n- If `until_id` is specified, `since_id` is required.\n- You cannot use `since` and `since_id` together.\nBehavior:\n- If `since` or `since_id` is not specified, this will not return any historic data.\n- If `until` or `until_id` is specified, stream will end at the specified point with status 200.\n\n---\n\nWarning: Currently, OAS-3 does not fully support responses from an SSE API.\n\nIn case the client code is generated from this OAS spec, please do not specify `since` and `until`, as the generated client may hang forever waiting for the response to end.\n\nIf you require the streaming capabilities, we recommend not using the generated clients for this specific endpoint until the OAS-3 standards define how to represent this behavior.\n\n---\n\n### Comment messages\nAccording to the SSE specification, any line that starts with a colon is a comment which does not contain data. It is typically a free text that does not follow any data schema. A few examples mentioned below for comment messages.\n\n##### Slow client\n\nThe server sends a comment when the client is not consuming messages fast enough. Example: `: you are reading too slowly, dropped 10000 messages`\n\n##### Internal server error\n\nAn error message is sent as a comment when the server closes the connection on an internal server error (only sent by the v2 and v2beta1 endpoints). Example: `: internal server error`\n\n---", + "operationId": "subscribeToActivitiesSSE", "parameters": [ { - "description": "e.g. \"active\". By default, all statuses are included.", + "description": "Format: RFC3339 or YYYY-MM-DD", "in": "query", - "name": "status", + "name": "since", "schema": { + "format": "date-time", "type": "string" } }, { - "description": "Defaults to us_equity.", + "description": "Format: RFC3339 or YYYY-MM-DD", "in": "query", - "name": "asset_class", + "name": "until", "schema": { + "format": "date-time", "type": "string" } }, { - "description": "Optional AMEX, ARCA, BATS, NYSE, NASDAQ, NYSEARCA, OTC or CRYPTO", "in": "query", - "name": "exchange", + "name": "since_id", "schema": { + "format": "ulid", "type": "string" } }, { - "description": "Comma separated values to query for more than one attribute. Assets which have any of the given attributes will be included.\n\nSupported values:\n- `ptp_no_exception`: Asset is a Publicly Traded Partnership (PTP) without a qualified notice; non-U.S. customers may incur 10% withholding on gross proceeds as per IRS guidance, and are blocked from being purchased by default.\n- `ptp_with_exception`: Users can open positions in these PTPs without general restrictions.\n- `ipo`: Accepting limit orders only before the stock begins trading on the secondary market.\n- `has_options`: The underlying equity has listed options available on the platform. Note: if the equity had inactive/expired contracts in the past, this will still show up.\n- `options_late_close`: Indicates the underlying asset's options contracts close at 4:15pm ET instead of the standard 4:00pm ET.\n- `fractional_eh_enabled`: Indicates the asset accepts fractional orders during extended hours sessions (pre-market, post-market, and overnight if enabled).\n- `overnight_tradable`: Asset is eligible for overnight (24x5) trading in supported venues on the platform.\n- `overnight_halted`: Asset is eligible for overnight trading but is currently halted/blocked for overnight sessions due to risk, corporate action, compliance, or venue constraints.", - "explode": false, "in": "query", - "name": "attributes", + "name": "until_id", "schema": { - "default": [], - "example": [ - "ptp_no_exception", - "ipo" - ], - "items": { - "$ref": "#/components/schemas/AssetAttribute" - }, - "type": "array" + "format": "ulid", + "type": "string" } } ], "responses": { "200": { "content": { - "application/json": { + "text/event-stream": { "schema": { "items": { - "$ref": "#/components/schemas/Assets" + "$ref": "#/components/schemas/ActivityEventV2" }, "type": "array" } } }, - "description": "An array of asset objects" + "description": "Connected. Events will now start streaming as long as you keep the connection open." } }, - "summary": "Get Assets", + "summary": "Subscribe to Activity Events (SSE)", "tags": [ - "Assets" + "Events" ] } }, - "/v2/assets/{symbol_or_asset_id}": { + "/v2/account": { "get": { - "description": "Get the asset model for a given symbol or asset_id. The symbol or asset_id should be passed in as a path parameter.\n\n**Note**: For crypto, the symbol has to follow old symbology, e.g. BTCUSD.\n\n**Note**: For coin pairs, the symbol should be separated by a slash (/), e.g. BTC/USDT. Since the slash is a special character in HTTP, use the URL encoded version instead, e.g. /v2/assets/BTC%2FUSDT", - "operationId": "get-v2-assets-symbol_or_asset_id", + "description": "Returns your account details.", + "operationId": "getAccount", "responses": { "200": { "content": { "application/json": { + "example": { + "account_blocked": false, + "account_number": "PALPACA_123", + "accrued_fees": "0", + "admin_configurations": {}, + "balance_asof": "2023-09-27", + "buying_power": "245432.61", + "cash": "122086.5", + "created_at": "2023-01-01T18:20:20.272275Z", + "crypto_status": "ACTIVE", + "crypto_tier": 1, + "currency": "USD", + "effective_buying_power": "245432.61", + "equity": "123346.11", + "id": "1d9eed04-be39-4e01-9b84-a48ac5bbafcf", + "initial_margin": "629.8", + "intraday_adjustments": "0", + "last_equity": "122011.09751111286868", + "last_maintenance_margin": "480.73", + "long_market_value": "1259.61", + "maintenance_margin": "377.88", + "multiplier": "2", + "non_marginable_buying_power": "122086.5", + "options_buying_power": "122716.305", + "options_trading_level": 2, + "pending_reg_taf_fees": "0", + "pending_transfer_in": "0", + "portfolio_value": "123346.11", + "position_market_value": "1259.61", + "regt_buying_power": "245432.61", + "short_market_value": "0", + "shorting_enabled": true, + "sma": "123369.74", + "status": "ACTIVE", + "trade_suspended_by_user": false, + "trading_blocked": false, + "transfers_blocked": false, + "user_configurations": null + }, "schema": { - "$ref": "#/components/schemas/Assets" + "$ref": "#/components/schemas/Account" } } }, - "description": "An Asset object" - }, - "404": { - "description": "Not Found" + "description": "OK" } }, - "summary": "Get an Asset by ID or Symbol", + "summary": "Get Account", "tags": [ - "Assets" + "Accounts" ] - }, - "parameters": [ - { - "description": "symbol or assetId. CUSIP is also accepted for US equities.", - "in": "path", - "name": "symbol_or_asset_id", - "required": true, - "schema": { - "type": "string" - } - } - ] + } }, - "/v2/assets/fixed_income/us_corporates": { + "/v2/account/activities": { "get": { - "description": "Serves the list of US corporates available at Alpaca. The response is sorted by ISIN.", - "operationId": "UsCorporates", + "description": "Returns a list of activities\n\nNotes:\n* Pagination is handled using the `page_token` and `page_size` parameters.\n* `page_token` represents the ID of the last item on your current page of results.\n For example, if the ID of the last activity in your first response is `20220203000000000::045b3b8d-c566-4bef-b741-2bf598dd6ae7`, you would pass that value as `page_token` to retrieve the next page of results.", + "operationId": "getAccountActivities", "parameters": [ { + "description": "A comma-separated list of activity types used to filter the results.", + "explode": false, "in": "query", - "name": "bond_status", + "name": "activity_types", "schema": { - "$ref": "#/components/schemas/bond_status" - } + "items": { + "$ref": "#/components/schemas/ActivityType" + }, + "type": "array" + }, + "style": "form" }, { - "description": "A comma-separated list of ISINs with a limit of 1000.", - "example": "US912810UG12,US912797PM34", + "description": "The activity category. Cannot be used with \"activity_types\" parameter.", "in": "query", - "name": "isins", + "name": "category", "schema": { + "enum": [ + "trade_activity", + "non_trade_activity" + ], "type": "string" } }, { - "description": "A comma-separated list of CUSIPs with a limit of 1000.", - "example": "912810UG1,912797PM3", + "description": "Filter activities associated with a specific order. Useful for retrieving the fills that make up a completely filled order.", "in": "query", - "name": "cusips", + "name": "order_id", "schema": { + "example": "fe060a1b-5b45-4eba-ba46-c3a3345d8255", + "format": "uuid", "type": "string" } }, { - "description": "A comma-separated list of tickers with a limit of 1000.", - "example": "BAC,MSFT", + "description": "Filter activities by their creation date (created_at), not the activity's settlement date. For non-trade activities such as fees, the creation date is typically the day after the trade date (in UTC). Both formats YYYY-MM-DD and YYYY-MM-DDTHH:MM:SSZ are supported.", "in": "query", - "name": "tickers", + "name": "date", "schema": { + "format": "date-time", "type": "string" } - } - ], - "responses": { - "200": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/us_corporates_resp" - } - } - }, - "description": "OK", - "headers": { - "X-RateLimit-Limit": { - "$ref": "#/components/headers/ratelimit_limit" - }, - "X-RateLimit-Remaining": { - "$ref": "#/components/headers/ratelimit_remaining" - }, - "X-RateLimit-Reset": { - "$ref": "#/components/headers/ratelimit_reset" - } - } - }, - "400": { - "$ref": "#/components/responses/400" - }, - "401": { - "$ref": "#/components/responses/401" - }, - "403": { - "$ref": "#/components/responses/403" }, - "429": { - "$ref": "#/components/responses/429" - }, - "500": { - "$ref": "#/components/responses/500" - } - }, - "summary": "Get US corporates", - "tags": [ - "Assets" - ] - } - }, - "/v2/assets/fixed_income/us_treasuries": { - "get": { - "description": "Serves the list of US treasuries available at Alpaca. The response is sorted by ISIN.", - "operationId": "UsTreasuries", - "parameters": [ { + "description": "Get activities created before this date. Both formats YYYY-MM-DD and YYYY-MM-DDTHH:MM:SSZ are supported.", "in": "query", - "name": "subtype", + "name": "until", "schema": { - "$ref": "#/components/schemas/treasury_subtype" + "format": "date-time", + "type": "string" } }, { + "description": "Get activities created after this date. Both formats YYYY-MM-DD and YYYY-MM-DDTHH:MM:SSZ are supported.", "in": "query", - "name": "bond_status", + "name": "after", "schema": { - "$ref": "#/components/schemas/bond_status" + "format": "date-time", + "type": "string" } }, { - "description": "A comma-separated list of CUSIPs with a limit of 1000.", - "example": "912810UG1,912797PM3", + "description": "The chronological order of response based on the activity datetime.", "in": "query", - "name": "cusips", + "name": "direction", "schema": { + "default": "desc", + "enum": [ + "asc", + "desc" + ], + "example": "desc", "type": "string" } }, { - "description": "A comma-separated list of ISINs with a limit of 1000.", - "example": "US912810UG12,US912797PM34", + "description": "The maximum number of entries to return in the response.", "in": "query", - "name": "isins", + "name": "page_size", "schema": { - "type": "string" - } - } - ], - "responses": { - "200": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/us_treasuries_resp" - } - } - }, - "description": "OK", - "headers": { - "X-RateLimit-Limit": { - "$ref": "#/components/headers/ratelimit_limit" - }, - "X-RateLimit-Remaining": { - "$ref": "#/components/headers/ratelimit_remaining" - }, - "X-RateLimit-Reset": { - "$ref": "#/components/headers/ratelimit_reset" - } + "default": 100, + "maximum": 100, + "minimum": 1, + "type": "integer" } }, - "400": { - "$ref": "#/components/responses/400" - }, - "403": { - "$ref": "#/components/responses/403" - }, - "429": { - "$ref": "#/components/responses/429" - }, - "500": { - "$ref": "#/components/responses/500" - } - }, - "summary": "Get US treasuries", - "tags": [ - "Assets" - ] - } - }, - "/v2/calendar": { - "get": { - "description": "The calendar API serves the full list of market days from 1970 to 2029. It can also be queried by specifying a start and/or end time to narrow down the results. In addition to the dates, the response also contains the specific open and close times for the market days, taking into account early closures.\n", - "operationId": "LegacyCalendar", - "parameters": [ - { - "$ref": "#/components/parameters/legacy_start" - }, { - "$ref": "#/components/parameters/legacy_end" - }, - { - "$ref": "#/components/parameters/legacy_date_type" - } - ], - "responses": { - "200": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/legacy_public_calendar_resp" - } - } - }, - "description": "OK", - "headers": { - "X-RateLimit-Limit": { - "$ref": "#/components/headers/ratelimit_limit" - }, - "X-RateLimit-Remaining": { - "$ref": "#/components/headers/ratelimit_remaining" - }, - "X-RateLimit-Reset": { - "$ref": "#/components/headers/ratelimit_reset" - } + "description": "Token used for pagination. Provide the ID of the last activity from the last page to retrieve the next set of results.", + "in": "query", + "name": "page_token", + "schema": { + "type": "string" } - }, - "400": { - "$ref": "#/components/responses/400" - }, - "429": { - "$ref": "#/components/responses/429" - }, - "500": { - "$ref": "#/components/responses/500" } - }, - "summary": "Get US Market Calendar", - "tags": [ - "Calendar" - ] - } - }, - "/v2/clock": { - "get": { - "description": "The clock API serves the current market timestamp, whether or not the market is currently open, as well as the times of the next market open and close.\n", - "operationId": "LegacyClock", + ], "responses": { "200": { "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/legacy_clock" + "items": { + "anyOf": [ + { + "$ref": "#/components/schemas/TradingActivities" + }, + { + "$ref": "#/components/schemas/NonTradeActivities" + } + ], + "description": "Will be a mix of TradingActivity or NonTradeActivity objects based on what is passed in the activity_types parameter" + }, + "type": "array" } } }, - "description": "OK", - "headers": { - "X-RateLimit-Limit": { - "$ref": "#/components/headers/ratelimit_limit" - }, - "X-RateLimit-Remaining": { - "$ref": "#/components/headers/ratelimit_remaining" - }, - "X-RateLimit-Reset": { - "$ref": "#/components/headers/ratelimit_reset" - } - } - }, - "429": { - "$ref": "#/components/responses/429" - }, - "500": { - "$ref": "#/components/responses/500" + "description": "returns an array of Account activities" } }, - "summary": "Get US Market Clock", + "summary": "Retrieve Account Activities", "tags": [ - "Clock" + "Account Activities" ] } }, - "/v2/corporate_actions/announcements": { + "/v2/account/activities/{activity_type}": { "get": { - "deprecated": true, - "description": "This endpoint is deprecated, please use [the new corporate actions endpoint](https://docs.alpaca.markets/reference/corporateactions-1) instead.", - "operationId": "get-v2-corporate_actions-announcements", + "description": "Returns account activity entries for a specific type of activity.", + "operationId": "getAccountActivitiesByActivityType", "parameters": [ { - "description": "A comma-delimited list of Dividend, Merger, Spinoff, or Split.", + "description": "Filter activities associated with a specific order. Useful for retrieving the fills that make up a completely filled order.", "in": "query", - "name": "ca_types", - "required": true, + "name": "order_id", "schema": { + "example": "fe060a1b-5b45-4eba-ba46-c3a3345d8255", + "format": "uuid", "type": "string" } }, { - "description": "The start (inclusive) of the date range when searching corporate action announcements. This should follow the YYYY-MM-DD format. The date range is limited to 90 days.", + "description": "Filter activities by their creation date (created_at), not the activity's settlement date. For non-trade activities such as fees, the creation date is typically the day after the trade date (in UTC). Both formats YYYY-MM-DD and YYYY-MM-DDTHH:MM:SSZ are supported.", "in": "query", - "name": "since", - "required": true, + "name": "date", "schema": { + "format": "date-time", "type": "string" } }, { - "description": "The end (inclusive) of the date range when searching corporate action announcements. This should follow the YYYY-MM-DD format. The date range is limited to 90 days.", + "description": "Get activities created before this date. Both formats YYYY-MM-DD and YYYY-MM-DDTHH:MM:SSZ are supported.", "in": "query", "name": "until", - "required": true, "schema": { + "format": "date-time", "type": "string" } }, { - "description": "The symbol of the company initiating the announcement.", + "description": "Get activities created after this date. Both formats YYYY-MM-DD and YYYY-MM-DDTHH:MM:SSZ are supported.", "in": "query", - "name": "symbol", + "name": "after", "schema": { + "format": "date-time", "type": "string" } }, { - "description": "The CUSIP of the company initiating the announcement.", + "description": "The chronological order of response based on the activity datetime.", "in": "query", - "name": "cusip", + "name": "direction", "schema": { + "default": "desc", + "enum": [ + "asc", + "desc" + ], + "example": "desc", "type": "string" } }, { - "description": "declaration_date, ex_date, record_date, or payable_date", + "description": "The maximum number of entries to return in the response.", "in": "query", - "name": "date_type", + "name": "page_size", + "schema": { + "default": 100, + "maximum": 100, + "minimum": 1, + "type": "integer" + } + }, + { + "description": "Token used for pagination. Provide the ID of the last activity from the last page to retrieve the next set of results.", + "in": "query", + "name": "page_token", "schema": { "type": "string" } @@ -7454,342 +7524,494 @@ "application/json": { "schema": { "items": { - "properties": { - "ca_sub_type": { - "type": "string" - }, - "ca_type": { - "type": "string" - }, - "cash": { - "type": "string" - }, - "corporate_action_id": { - "type": "string" - }, - "corporate_actions_id": { - "type": "string" - }, - "declaration_date": { - "type": "string" - }, - "ex_date": { - "type": "string" - }, - "expiration_date": { - "type": "string" - }, - "id": { - "type": "string" - }, - "initiating_original_cusip": { - "type": "string" - }, - "initiating_symbol": { - "type": "string" - }, - "new_rate": { - "type": "string" - }, - "old_rate": { - "type": "string" - }, - "payable_date": { - "type": "string" - }, - "record_date": { - "type": "string" - }, - "target_original_cusip": { - "type": "string" - }, - "target_symbol": { - "type": "string" - } - }, - "type": "object" - }, - "type": "array", - "x-examples": { - "Example 1": [ + "description": "Will be one of a TradingActivity or NonTradeActivity based on activity_type used in path", + "oneOf": [ { - "ca_sub_type": "DIV", - "ca_type": "Dividend", - "cash": "0.018", - "corporate_actions_id": "F58684224_XY37", - "declaration_date": "2021-01-05", - "expiration_date": "2021-01-12", - "id": "be3c368a-4c7c-4384-808e-f02c9f5a8afe", - "initiating_original_cusip": 5.5275e+105, - "initiating_symbol": "MLLAX", - "new_rate": "1", - "old_rate": "1", - "payable_date": "2021-01-14", - "record_date": "2021-01-13", - "target_original_cusip": 5.5275e+105, - "target_symbol": "MLLAX" + "$ref": "#/components/schemas/TradingActivities" }, { - "ca_sub_type": "cash", - "ca_type": "Dividend", - "cash": "0.145", - "corporate_action_id": "48251W104_AD21", - "declaration_date": "2021-11-01", - "ex_date": "2021-11-12", - "initiating_original_cusip": "G52830109", - "initiating_symbol": "KKR", - "new_rate": "1", - "old_rate": "1", - "payable_date": "2021-11-30", - "record_date": "2021-11-15", - "target_original_cusip": "G52830109", - "target_symbol": "KKR" + "$ref": "#/components/schemas/NonTradeActivities" } ] - } + }, + "type": "array" } } }, - "description": "OK" + "description": "returns an array of Account activities" } }, - "summary": "Retrieve Announcements", + "summary": "Retrieve Account Activities of Specific Type", "tags": [ - "Corporate Actions" + "Account Activities" ] - } + }, + "parameters": [ + { + "description": "The activity type you want to view entries for. A list of valid activity types can be found at the bottom of this page.", + "in": "path", + "name": "activity_type", + "required": true, + "schema": { + "type": "string" + } + } + ] }, - "/v2/corporate_actions/announcements/{id}": { + "/v2/account/configurations": { "get": { - "deprecated": true, - "description": "This endpoint is deprecated, please use [the new corporate actions endpoint](https://docs.alpaca.markets/reference/corporateactions-1) instead.", - "operationId": "get-v2-corporate_actions-announcements-id", + "description": "gets the current account configuration values", + "operationId": "getAccountConfig", "responses": { "200": { "content": { "application/json": { "schema": { - "properties": { - "ca_sub_type": { - "type": "string" - }, - "ca_type": { - "description": "A comma-delimited list of Dividend, Merger, Spinoff, or Split.", - "type": "string" - }, - "cash": { - "type": "string" - }, - "corporate_actions_id": { - "type": "string" - }, - "declaration_date": { - "type": "string" - }, - "expiration_date": { - "type": "string" - }, - "id": { - "type": "string" - }, - "initiating_original_cusip": { - "type": "string" - }, - "initiating_symbol": { - "type": "string" - }, - "new_rate": { - "type": "string" - }, - "old_rate": { - "type": "string" - }, - "payable_date": { - "type": "string" - }, - "record_date": { - "type": "string" - }, - "target_original_cusip": { - "type": "string" - }, - "target_symbol": { - "type": "string" - } - }, - "type": "object", - "x-examples": { - "Example 1": { - "ca_sub_type": "DIV", - "ca_type": "Dividend", - "cash": "0.018", - "corporate_actions_id": "F58684224_XY37", - "declaration_date": "2021-01-05", - "expiration_date": "2021-01-12", - "id": "be3c368a-4c7c-4384-808e-f02c9f5a8afe", - "initiating_original_cusip": 5.5275e+105, - "initiating_symbol": "MLLAX", - "new_rate": "1", - "old_rate": "1", - "payable_date": "2021-01-14", - "record_date": "2021-01-13", - "target_original_cusip": 5.5275e+105, - "target_symbol": "MLLAX" - } - } + "$ref": "#/components/schemas/AccountConfigurations" } } }, - "description": "OK" + "description": "Successful response" } }, - "summary": "Retrieve a Specific Announcement", + "summary": "Get Account Configurations", "tags": [ - "Corporate Actions" + "Account Configurations" ] }, - "parameters": [ - { - "description": "The corporate announcement's id", - "in": "path", - "name": "id", - "required": true, - "schema": { - "type": "string" + "patch": { + "description": "Updates and returns the current account configuration values", + "operationId": "patchAccountConfig", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/AccountConfigurations" + } + } } - } - ] + }, + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/AccountConfigurations" + } + } + }, + "description": "Successful response" + } + }, + "summary": "Account Configurations", + "tags": [ + "Account Configurations" + ] + } }, - "/v2/options/contracts": { + "/v2/account/portfolio/history": { "get": { - "description": "This endpoint allows you to retrieve a list of option contracts based on various filtering criteria.\nBy default only active contracts that expire before the upcoming weekend are returned.\n", - "operationId": "get-options-contracts", + "description": "Returns timeseries data about equity and profit/loss (P/L) of the account in requested timespan.", + "operationId": "getAccountPortfolioHistory", "parameters": [ { - "description": "Filter contracts by one or more underlying symbols.", - "example": "AAPL,SPY", + "description": "The duration of the data in `number` + `unit` format, such as 1D, where `unit` can be D for day, W for week, M for month and A for year. Defaults to 1M.\n\nOnly two of `start`, `end` and `period` can be specified at the same time.\n\nFor intraday timeframes (\\<1D) only 30 days or less can be queried, for 1D resolutions there is no such limit, data is available since the\ncreation of the account.\n", "in": "query", - "name": "underlying_symbols", + "name": "period", "schema": { "type": "string" } }, { - "description": "Include deliverables array in the response.", - "example": true, + "description": "The resolution of time window. 1Min, 5Min, 15Min, 1H, or 1D. If omitted, 1Min for less than 7 days period,\n15Min for less than 30 days, or otherwise 1D.\n\nFor queries with longer than 30 days of `period`, the system only accepts 1D as `timeframe`.\n", "in": "query", - "name": "show_deliverables", + "name": "timeframe", "schema": { - "type": "boolean" + "type": "string" } }, { - "description": "Filter contracts by status (active/inactive). By default only active contracts are returned.", - "example": "active", + "description": "For intraday resolutions (<1D) this specifies which timestamps to return data points for:\n\nAllowed values are:\n- **market_hours**\n\n Only timestamps for the core equity trading hours are returned (usually 9:30am to 4:00pm, trading days only)\n\n- **extended_hours**\n\n Returns timestamps for the whole session including extended hours (usually 4:00am to 8:00pm, trading days only)\n\n- **continuous**\n\n Returns price data points 24/7 (for off-session times too). To calculate the equity values we are using the following prices:\n\n Between 4:00am and 10:00pm on trading days the valuation will be calculated based on the last trade (extended hours and normal hours respectively).\n\n After 10:00pm, until the next session open the equities will be valued at their official closing price on the primary exchange.\n", "in": "query", - "name": "status", + "name": "intraday_reporting", "schema": { + "default": "market_hours", "enum": [ - "active", - "inactive" + "market_hours", + "extended_hours", + "continuous" ], "type": "string" } }, { - "description": "Filter contracts by the exact expiration date (format: YYYY-MM-DD).", - "example": "2025-06-20", + "description": "The timestamp the data is returned starting from in RFC3339 format (including timezone specification). Defaults to `end` minus `period`\n\nIf provided, the `start` value is always normalized to the `America/New_York` timezone and adjusted to the nearest `timeframe` interval, e.g. seconds are always truncated and the time is rounded backwards to the nearest interval of `1Min`, `5Min`, `15Min`, or `1H`.\n\nIf `timeframe=1D` and `start` is not a valid trading date, find the next available trading date. For example, if `start` occurs on Saturday or Sunday after converting to the America/New_York timezone, `start` is adjusted to the first weekday that is not a market holiday (e.g. Monday).\n\nIf `timeframe` is less than `1D` and `intraday_reporting` is not `continuous`, `start` always reflects the beginning of a market session. If `start` is between midnight and the end (inclusive) of an active trading day, `start` is set to the beginning of the session on the specified day. Otherwise, if `start` occurs outside of the market session, the next available market date is used.\n\nFor example, when `intraday_reporting=market_hours` and `start=2023-10-19T23:59:59-04:00`, the provided `start` date occurs outside of the regular market session. The effective `start` timestamp is adjusted to the beginning of the next session: `2023-10-20T09:30:00-04:00`\n\n`start` may be be combined with one of `end` or `period`.\n\nProviding all of `start`, `end`, and `period` is invalid.\n", "in": "query", - "name": "expiration_date", + "name": "start", "schema": { - "format": "date", + "example": "2021-03-16T18:38:01Z", + "format": "date-time", "type": "string" } }, { - "description": "Filter contracts with expiration date greater than or equal to the specified date.", - "example": "2025-06-20", + "description": "`pnl_reset` defines how we are calculating the baseline values for Profit And Loss (pnl) for queries with `timeframe` less than 1D (intraday queries).\n\nThe default behavior for intraday queries is that we reset the pnl value to the previous day's closing equity for each **trading** day.\n\nIn case of crypto (given its continuous nature), this might not be desired: specifying \"no_reset\" disables this behavior and all pnl values\nreturned will be relative to the closing equity of the previous trading day.\n\nFor 1D resolution all PnL values are calculated relative to the `base_value`, we are not resetting the base value.\n", "in": "query", - "name": "expiration_date_gte", + "name": "pnl_reset", "schema": { - "format": "date", + "default": "per_day", + "enum": [ + "no_reset", + "per_day" + ], "type": "string" } }, { - "description": "Filter contracts with expiration date less than or equal to the specified date. By default this is set to the next weekend.", - "example": "2025-06-20", + "description": "The timestamp the data is returned up to in RFC3339 format (including timezone specification). Defaults to the current time.\n\nIf provided, the `end` value is always normalized to the `America/New_York` timezone and adjusted to the nearest `timeframe` interval, e.g. seconds are always truncated and the time is rounded backwards to the nearest interval of `1Min`, `5Min`, `15Min`, or `1H`.\n\nWhen `intraday_reporting` is either `market_hours` or `extended_hours`, the `end` value is adjusted to not occur after session close on the specified day. For example if the `intraday_reporting` is `extended_hours`, and the timestamp specified is `2023-10-19T21:33:00-04:00`, `end` is adjusted to `2023-10-19T20:00:00-04:00`.\n\n`end` may be combined with `start` or `period`.\n\nProviding all of `start`, `end`, and `period` is invalid.\n", "in": "query", - "name": "expiration_date_lte", + "name": "end", "schema": { - "format": "date", + "example": "2021-03-16T18:38:01Z", + "format": "date-time", "type": "string" } }, { - "description": "Filter contracts by the root symbol.", - "example": "AAPL", + "description": "**deprecated**: Users are strongly advised to **rely on the `intraday_reporting` query parameter** for better control\nof the reporting range.\n\nIf true, include extended hours in the result. This is effective only for timeframe less than 1D.\n", "in": "query", - "name": "root_symbol", + "name": "extended_hours", "schema": { "type": "string" } }, { - "description": "Filter contracts by the type (call/put).", - "example": "call", - "in": "query", - "name": "type", - "schema": { - "$ref": "#/components/schemas/OptionContractType" - } - }, - { - "description": "Filter contracts by the style (american/european).", - "example": "american", + "description": "The cashflow activities to include in the report. One of 'ALL', 'NONE', or a comma-separated list of activity types.", + "examples": { + "all": { + "value": "ALL" + }, + "cash_transfers": { + "summary": "Include cash movements.", + "value": "JNLC,CSD,CSW" + }, + "none": { + "value": "NONE" + } + }, "in": "query", - "name": "style", + "name": "cashflow_types", "schema": { - "$ref": "#/components/schemas/OptionContractStyle" + "type": "string" } - }, + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "examples": { + "example-cashflows": { + "value": { + "base_value": 8413.04, + "base_value_asof": "2026-04-06", + "cashflow": { + "CSD": [ + 9.93, + 10.13, + 10.16, + 0 + ], + "DIV": [ + 0, + 0, + 0.63, + 0 + ] + }, + "equity": [ + 8425.21, + 8639.77, + 8766.1, + 8835.31 + ], + "profit_loss": [ + 12.17, + 214.56, + 126.33, + 69.21 + ], + "profit_loss_pct": [ + 0.0014, + 0.0255, + 0.0146, + 0.0079 + ], + "timeframe": "1D", + "timestamp": [ + 1775606400, + 1775692800, + 1775779200, + 1775865600 + ] + } + }, + "example-intraday-query-15min-1d": { + "value": { + "base_value": 2774.16, + "base_value_asof": "2023-10-18", + "cashflow": { + "CSD": [ + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 100, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0 + ] + }, + "equity": [ + 2773.79, + 2769.04, + 2768.65, + 2765.11, + 2763.03, + 2763.17, + 2763.17, + 2763.47, + 2763.91, + 2768.13, + 2774.98, + 2757.94, + 2757.65, + 2774.54, + 2775.58, + 2775.28, + 2767.9, + 2762.26, + 2762.56, + 2756.99, + 2756.84, + 2752.43, + 2752.13, + 2748.44, + 2751.23, + 2747.54, + 2748.74 + ], + "profit_loss": [ + -0.37, + -5.12, + -5.51, + -9.05, + -11.13, + -10.99, + -10.99, + -10.69, + -10.25, + -6.03, + 0.82, + -16.22, + -16.51, + 0.38, + 1.42, + 1.12, + -6.26, + -11.9, + -11.6, + -17.17, + -17.32, + -21.73, + -22.03, + -25.72, + -22.93, + -26.62, + -25.42 + ], + "profit_loss_pct": [ + -0.0001, + -0.0018, + -0.002, + -0.0033, + -0.004, + -0.004, + -0.004, + -0.0039, + -0.0037, + -0.0022, + 0.0003, + -0.0058, + -0.006, + 0.0001, + 0.0005, + 0.0004, + -0.0023, + -0.0043, + -0.0042, + -0.0062, + -0.0062, + -0.0078, + -0.0079, + -0.0093, + -0.0083, + -0.0096, + -0.0092 + ], + "timeframe": "15Min", + "timestamp": [ + 1697722200, + 1697723100, + 1697724000, + 1697724900, + 1697725800, + 1697726700, + 1697727600, + 1697728500, + 1697729400, + 1697730300, + 1697731200, + 1697732100, + 1697733000, + 1697733900, + 1697734800, + 1697735700, + 1697736600, + 1697737500, + 1697738400, + 1697739300, + 1697740200, + 1697741100, + 1697742000, + 1697742900, + 1697743800, + 1697744700, + 1697745600 + ] + } + }, + "example-query-1d-7d": { + "value": { + "base_value": 2784.79, + "cashflow": { + "CSD": [ + 0, + 0, + 100, + 0, + 0 + ] + }, + "equity": [ + 2784.79, + 2794.79, + 2805.46, + 2774.16, + 2748.73 + ], + "profit_loss": [ + 0, + 10.0022, + 10.6692, + -31.2996, + -25.4232 + ], + "profit_loss_pct": [ + 0, + 0.0035, + 0.0074, + -0.0038, + -0.0129 + ], + "timeframe": "1D", + "timestamp": [ + 1697241600, + 1697500800, + 1697587200, + 1697673600, + 1697760000 + ] + } + } + }, + "schema": { + "$ref": "#/components/schemas/PortfolioHistory" + } + } + }, + "description": "Successful response" + } + }, + "summary": "Get Account Portfolio History", + "tags": [ + "Portfolio History" + ] + } + }, + "/v2/assets": { + "get": { + "description": "The assets API serves as the master list of assets available for trade and data consumption from Alpaca. Assets are sorted by asset class, exchange and symbol.", + "operationId": "get-v2-assets", + "parameters": [ { - "description": "Filter contracts with strike price greater than or equal to the specified value.", - "example": 50, + "description": "e.g. \"active\". By default, all statuses are included.", "in": "query", - "name": "strike_price_gte", + "name": "status", "schema": { - "type": "number" + "type": "string" } }, { - "description": "Filter contracts with strike price less than or equal to the specified value.", - "example": 100, + "description": "Defaults to us_equity.", "in": "query", - "name": "strike_price_lte", + "name": "asset_class", "schema": { - "type": "number" + "type": "string" } }, { - "$ref": "#/components/parameters/PageToken" - }, - { - "description": "The number of contracts to limit per page (default=100, max=10000).", - "example": 100, + "description": "Optional AMEX, ARCA, BATS, NYSE, NASDAQ, NYSEARCA, OTC or CRYPTO", "in": "query", - "name": "limit", + "name": "exchange", "schema": { - "type": "integer" + "type": "string" } }, { - "description": "The ppind(Penny Program Indicator) field indicates whether an option contract is eligible for penny price increments,\nwith `true` meaning it is part of the Penny Program and `false` meaning it is not.", - "example": true, + "description": "Comma separated values to query for more than one attribute. Assets which have any of the given attributes will be included.\n\nSupported values:\n- `ptp_no_exception`: Asset is a Publicly Traded Partnership (PTP) without a qualified notice; non-U.S. customers may incur 10% withholding on gross proceeds as per IRS guidance, and are blocked from being purchased by default.\n- `ptp_with_exception`: Users can open positions in these PTPs without general restrictions.\n- `ipo`: Accepting limit orders only before the stock begins trading on the secondary market.\n- `has_options`: The underlying equity has listed options available on the platform. Note: if the equity had inactive/expired contracts in the past, this will still show up.\n- `options_late_close`: Indicates the underlying asset's options contracts close at 4:15pm ET instead of the standard 4:00pm ET.\n- `fractional_eh_enabled`: Indicates the asset accepts fractional orders during extended hours sessions (pre-market, post-market, and overnight if enabled).\n- `overnight_tradable`: Asset is eligible for overnight (24x5) trading in supported venues on the platform.\n- `overnight_halted`: Asset is eligible for overnight trading but is currently halted/blocked for overnight sessions due to risk, corporate action, compliance, or venue constraints.", + "explode": false, "in": "query", - "name": "ppind", + "name": "attributes", "schema": { - "type": "boolean" + "default": [], + "example": [ + "ptp_no_exception", + "ipo" + ], + "items": { + "$ref": "#/components/schemas/AssetAttribute" + }, + "type": "array" } } ], @@ -7798,77 +8020,51 @@ "content": { "application/json": { "schema": { - "properties": { - "next_page_token": { - "$ref": "#/components/schemas/NextPageToken" - }, - "option_contracts": { - "items": { - "$ref": "#/components/schemas/OptionContract" - }, - "type": "array" - } + "items": { + "$ref": "#/components/schemas/Assets" }, - "required": [ - "option_contracts" - ], - "type": "object" + "type": "array" } } }, - "description": "Successful Response." + "description": "An array of asset objects" } }, - "summary": "Get Option Contracts", + "summary": "Get Assets", "tags": [ "Assets" ] } }, - "/v2/options/contracts/{symbol_or_id}": { + "/v2/assets/{symbol_or_asset_id}": { "get": { - "description": "Get an option contract by symbol or contract ID. The symbol or id should be passed in as a path parameter.", - "operationId": "get-option-contract-symbol_or_id", + "description": "Get the asset model for a given symbol or asset_id. The symbol or asset_id should be passed in as a path parameter.\n\n**Note**: For crypto, the symbol has to follow old symbology, e.g. BTCUSD.\n\n**Note**: For coin pairs, the symbol should be separated by a slash (/), e.g. BTC/USDT. Since the slash is a special character in HTTP, use the URL encoded version instead, e.g. /v2/assets/BTC%2FUSDT", + "operationId": "get-v2-assets-symbol_or_asset_id", "responses": { "200": { "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/OptionContract" + "$ref": "#/components/schemas/Assets" } } }, - "description": "An option contract" + "description": "An Asset object" }, "404": { - "content": { - "application/json": { - "examples": { - "Contract Not Found": { - "value": { - "code": 40410000, - "message": "option contract TSLA231110C00021000 not found" - } - } - }, - "schema": { - "$ref": "#/components/schemas/Error" - } - } - }, "description": "Not Found" } }, - "summary": "Get an option contract by ID or Symbol", + "summary": "Get an Asset by ID or Symbol", "tags": [ "Assets" ] }, "parameters": [ { - "description": "symbol or contract ID", + "description": "symbol or assetId. CUSIP is also accepted for US equities.", "in": "path", - "name": "symbol_or_id", + "name": "symbol_or_asset_id", "required": true, "schema": { "type": "string" @@ -7876,147 +8072,156 @@ } ] }, - "/v2/orders": { - "delete": { - "description": "Attempts to cancel all open orders. A response will be provided for each order that is attempted to be cancelled. If an order is no longer cancelable, the server will respond with status 500 and reject the request.", - "operationId": "deleteAllOrders", + "/v2/calendar": { + "get": { + "description": "The calendar API serves the full list of market days from 1970 to 2029. It can also be queried by specifying a start and/or end time to narrow down the results. In addition to the dates, the response also contains the specific open and close times for the market days, taking into account early closures.\n", + "operationId": "LegacyCalendar", + "parameters": [ + { + "$ref": "#/components/parameters/legacy_start" + }, + { + "$ref": "#/components/parameters/legacy_end" + }, + { + "$ref": "#/components/parameters/legacy_date_type" + } + ], "responses": { - "207": { + "200": { "content": { "application/json": { "schema": { - "items": { - "$ref": "#/components/schemas/CanceledOrderResponse" - }, - "type": "array" + "$ref": "#/components/schemas/legacy_public_calendar_resp" } } }, - "description": "Multi-Status with body.\n\nan array of objects that include the order id and http status code for each status request." + "description": "OK", + "headers": { + "X-RateLimit-Limit": { + "$ref": "#/components/headers/ratelimit_limit" + }, + "X-RateLimit-Remaining": { + "$ref": "#/components/headers/ratelimit_remaining" + }, + "X-RateLimit-Reset": { + "$ref": "#/components/headers/ratelimit_reset" + } + } + }, + "400": { + "$ref": "#/components/responses/400" + }, + "429": { + "$ref": "#/components/responses/429" }, "500": { - "description": "Failed to cancel order." + "$ref": "#/components/responses/500" } }, - "summary": "Delete All Orders", + "summary": "Get US Market Calendar", "tags": [ - "Orders" + "Calendar" ] - }, + } + }, + "/v2/clock": { "get": { - "description": "Retrieves a list of orders for the account, filtered by the supplied query parameters.", - "operationId": "getAllOrders", - "parameters": [ - { - "description": "Order status to be queried. open, closed or all. Defaults to open.", - "in": "query", - "name": "status", - "schema": { - "enum": [ - "open", - "closed", - "all" - ], - "example": "open", - "type": "string" - } - }, - { - "description": "The maximum number of orders in response. Defaults to 50 and max is 500.", - "in": "query", - "name": "limit", - "schema": { - "type": "integer" - } - }, - { - "description": "The response will include only ones submitted after this timestamp (exclusive.)", - "in": "query", - "name": "after", - "schema": { - "type": "string" - } - }, - { - "description": "The response will include only ones submitted until this timestamp (exclusive.)", - "in": "query", - "name": "until", - "schema": { - "type": "string" - } - }, - { - "description": "The chronological order of response based on the submission time. asc or desc. Defaults to desc.", - "in": "query", - "name": "direction", - "schema": { - "enum": [ - "asc", - "desc" - ], - "type": "string" + "description": "The clock API serves the current market timestamp, whether or not the market is currently open, as well as the times of the next market open and close.\n", + "operationId": "LegacyClock", + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/legacy_clock" + } + } + }, + "description": "OK", + "headers": { + "X-RateLimit-Limit": { + "$ref": "#/components/headers/ratelimit_limit" + }, + "X-RateLimit-Remaining": { + "$ref": "#/components/headers/ratelimit_remaining" + }, + "X-RateLimit-Reset": { + "$ref": "#/components/headers/ratelimit_reset" + } } }, + "429": { + "$ref": "#/components/responses/429" + }, + "500": { + "$ref": "#/components/responses/500" + } + }, + "summary": "Get US Market Clock", + "tags": [ + "Clock" + ] + } + }, + "/v2/corporate_actions/announcements": { + "get": { + "deprecated": true, + "description": "This endpoint is deprecated, please use [the new corporate actions endpoint](https://docs.alpaca.markets/reference/corporateactions-1) instead.", + "operationId": "get-v2-corporate_actions-announcements", + "parameters": [ { - "description": "If true, the result will roll up multi-leg orders under the legs field of primary order.", + "description": "A comma-delimited list of corporate action types.", + "explode": false, "in": "query", - "name": "nested", + "name": "ca_types", + "required": true, "schema": { - "type": "boolean" - } + "items": { + "$ref": "#/components/schemas/CorporateActionCaType" + }, + "type": "array" + }, + "style": "form" }, { - "description": "A comma-separated list of symbols to filter by (ex. \"AAPL,TSLA,MSFT\"). A currency pair is required for crypto orders (ex. \"BTCUSD,BCHUSD,LTCUSD,ETCUSD\").", + "description": "The start (inclusive) of the date range when searching corporate action announcements. This should follow the YYYY-MM-DD format. The date range is limited to 90 days.", "in": "query", - "name": "symbols", + "name": "since", + "required": true, "schema": { "type": "string" } }, { - "description": "Filters down to orders that have a matching side field set.", + "description": "The end (inclusive) of the date range when searching corporate action announcements. This should follow the YYYY-MM-DD format. The date range is limited to 90 days.", "in": "query", - "name": "side", + "name": "until", + "required": true, "schema": { "type": "string" } }, { - "description": "A comma-separated list of asset classes, the response will include only orders in the specified asset classes. By specifying `us_option` as the class, you can query option orders by underlying symbol using the symbols parameter.", + "description": "The symbol of the company initiating the announcement.", "in": "query", - "name": "asset_class", + "name": "symbol", "schema": { - "items": { - "enum": [ - "us_equity", - "us_option", - "crypto", - "crypto_perp", - "treasury", - "corporate", - "global_equity", - "us_index", - "us_equity_chain", - "ipo", - "all" - ], - "type": "string" - }, - "type": "array" + "type": "string" } }, { - "description": "Return orders submitted before the order with this ID (exclusive).\nMutually exclusive with `after_order_id`. Do not combine with `after`/`until`.\n", + "description": "The CUSIP of the company initiating the announcement.", "in": "query", - "name": "before_order_id", + "name": "cusip", "schema": { "type": "string" } }, { - "description": "Return orders submitted after the order with this ID (exclusive).\nMutually exclusive with `before_order_id`. Do not combine with `after`/`until`.\n", + "description": "declaration_date, ex_date, record_date, or payable_date", "in": "query", - "name": "after_order_id", + "name": "date_type", "schema": { "type": "string" } @@ -8027,411 +8232,225 @@ "content": { "application/json": { "examples": { - "Crypto": { - "$ref": "#/components/examples/CryptoOrderResponse" - }, - "Equity": { - "$ref": "#/components/examples/EquityOrderResponse" - }, - "IPO": { - "$ref": "#/components/examples/IPOOrderResponse" - }, - "MultilegOptions": { - "$ref": "#/components/examples/MultilegOptionsOrderResponse" - }, - "Options": { - "$ref": "#/components/examples/OptionOrderResponse" - } - }, - "schema": { - "items": { - "$ref": "#/components/schemas/Order" - }, - "type": "array" - } - } - }, - "description": "Successful response\n\nAn array of Order objects" - } - }, - "summary": "Get All Orders", - "tags": [ - "Orders" - ] - }, - "post": { - "description": "Places a new order for the given account. An order request may be rejected if the account is not authorized for trading, or if the tradable balance is insufficient to fill the order.", - "operationId": "postOrder", - "requestBody": { - "content": { - "application/json": { - "examples": { - "Crypto": { - "summary": "Buy a crypto coin", - "value": { - "limit_price": "2100", - "qty": "0.02", - "side": "buy", - "symbol": "ETH/USD", - "time_in_force": "gtc", - "type": "limit" - } - }, - "Equity": { - "summary": "Buy an equity stock", - "value": { - "limit_price": "150", - "qty": "2", - "side": "buy", - "symbol": "AAPL", - "time_in_force": "gtc", - "type": "limit" - } - }, - "IPO": { - "summary": "Submit an IPO indication of interest", - "value": { - "notional": "500", - "side": "buy", - "symbol": "FI111225", - "time_in_force": "gtc", - "type": "market" - } - }, - "MultilegOptions": { - "summary": "Option Call spread", - "value": { - "legs": [ + "Example 1": { + "value": [ { - "position_intent": "buy_to_open", - "ratio_qty": "3", - "side": "buy", - "symbol": "AAPL241213C00250000" + "ca_sub_type": "DIV", + "ca_type": "Dividend", + "cash": "0.018", + "corporate_actions_id": "F58684224_XY37", + "declaration_date": "2021-01-05", + "expiration_date": "2021-01-12", + "id": "be3c368a-4c7c-4384-808e-f02c9f5a8afe", + "initiating_original_cusip": "55275E101", + "initiating_symbol": "MLLAX", + "new_rate": "1", + "old_rate": "1", + "payable_date": "2021-01-14", + "record_date": "2021-01-13", + "target_original_cusip": "55275E101", + "target_symbol": "MLLAX" }, { - "position_intent": "sell_to_open", - "ratio_qty": "1", - "side": "sell", - "symbol": "AAPL241213C00260000" - } - ], - "limit_price": "10", - "order_class": "mleg", - "qty": "3", - "time_in_force": "day", - "type": "limit" - } - }, - "Options": { - "summary": "Buy an option contract", - "value": { - "limit_price": "10", - "qty": "2", - "side": "buy", - "symbol": "AAPL250620C00100000", - "time_in_force": "day", - "type": "limit" - } - } - }, - "schema": { - "properties": { - "advanced_instructions": { - "$ref": "#/components/schemas/AdvancedInstructions" - }, - "client_order_id": { - "description": "A unique identifier for the order. Automatically generated if not sent. (<= 128 characters)", - "maxLength": 128, - "type": "string" - }, - "extended_hours": { - "description": "(default) false. If true, order will be eligible for execution in the pre-market, after-hours, and overnight sessions. Only works with type `limit` and time_in_force set to either `day` or `gtc`", - "type": "boolean" - }, - "legs": { - "description": "list of order legs (<= 4)", - "items": { - "$ref": "#/components/schemas/MLegOrderLeg" - }, - "maxItems": 4, - "type": "array" - }, - "limit_price": { - "description": "Required if type is `limit` or `stop_limit`.\nIn case of `mleg`, the limit_price parameter is expressed with the following notation:\n- A positive value indicates a debit, representing a cost or payment to be made.\n- A negative value signifies a credit, reflecting an amount to be received.", - "type": "string" - }, - "notional": { - "description": "dollar amount to trade. Cannot work with `qty`. Can only work for market order types and day for time in force.", - "type": "string" - }, - "order_class": { - "$ref": "#/components/schemas/OrderClass" - }, - "position_intent": { - "$ref": "#/components/schemas/PositionIntent" - }, - "qty": { - "description": "number of shares to trade. Can be fractionable for only market and day order types. Required for `mleg` order class, represents the number of units to trade of this strategy.", - "type": "string" - }, - "side": { - "$ref": "#/components/schemas/OrderSide" - }, - "stop_loss": { - "description": "Takes in string/number values for stop_price and limit_price", - "properties": { - "limit_price": { - "example": "3.14", - "format": "decimal", - "type": "string" - }, - "stop_price": { - "example": "3.14", - "format": "decimal", - "type": "string" - } - }, - "type": "object" - }, - "stop_price": { - "description": "required if type is `stop` or `stop_limit`", - "type": "string" - }, - "symbol": { - "description": "symbol, asset ID, or currency pair to identify the asset to trade, required for all order classes except for `mleg`.", - "type": "string" - }, - "take_profit": { - "description": "Takes in a string/number value for limit_price", - "properties": { - "limit_price": { - "example": "3.14", - "format": "decimal", - "type": "string" + "ca_sub_type": "cash", + "ca_type": "Dividend", + "cash": "0.145", + "corporate_action_id": "48251W104_AD21", + "declaration_date": "2021-11-01", + "ex_date": "2021-11-12", + "initiating_original_cusip": "G52830109", + "initiating_symbol": "KKR", + "new_rate": "1", + "old_rate": "1", + "payable_date": "2021-11-30", + "record_date": "2021-11-15", + "target_original_cusip": "G52830109", + "target_symbol": "KKR" } - }, - "type": "object" - }, - "time_in_force": { - "$ref": "#/components/schemas/TimeInForce" - }, - "trail_percent": { - "description": "this or `trail_price` is required if type is `trailing_stop`", - "type": "string" - }, - "trail_price": { - "description": "this or `trail_percent` is required if type is `trailing_stop`", - "type": "string" - }, - "type": { - "$ref": "#/components/schemas/OrderType" - } - }, - "required": [ - "type", - "time_in_force" - ], - "type": "object" - } - } - }, - "description": "", - "required": true - }, - "responses": { - "200": { - "content": { - "application/json": { - "examples": { - "Crypto": { - "$ref": "#/components/examples/CryptoOrderResponse" - }, - "Equity": { - "$ref": "#/components/examples/EquityOrderResponse" - }, - "IPO": { - "$ref": "#/components/examples/IPOOrderResponse" - }, - "MultilegOptions": { - "$ref": "#/components/examples/MultilegOptionsOrderResponse" - }, - "Options": { - "$ref": "#/components/examples/OptionOrderResponse" + ] } }, "schema": { - "$ref": "#/components/schemas/Order" + "items": { + "$ref": "#/components/schemas/CorporateAnnouncement" + }, + "type": "array" } } }, - "description": "Successful response" - }, - "403": { - "description": "Forbidden\n\nBuying power or shares is not sufficient." - }, - "422": { - "description": "Unprocessable\n\nInput parameters are not recognized." + "description": "OK" } }, - "summary": "Create an Order", + "summary": "Retrieve Announcements", "tags": [ - "Orders" + "Corporate Actions" ] } }, - "/v2/orders/{order_id}": { - "delete": { - "description": "Attempts to cancel an Open Order. If the order is no longer cancelable, the request will be rejected with status 422; otherwise accepted with return status 204.", - "operationId": "deleteOrderByOrderID", - "responses": { - "204": { - "description": "No Content" - }, - "422": { - "description": "The order status is not cancelable." - } - }, - "summary": "Delete Order by ID", - "tags": [ - "Orders" - ] - }, + "/v2/corporate_actions/announcements/{id}": { "get": { - "description": "Retrieves a single order for the given order_id.", - "operationId": "getOrderByOrderID", - "parameters": [ - { - "description": "If true, the result will roll up multi-leg orders under the legs field of primary order.", - "in": "query", - "name": "nested", - "schema": { - "type": "boolean" - } - } - ], + "deprecated": true, + "description": "This endpoint is deprecated, please use [the new corporate actions endpoint](https://docs.alpaca.markets/reference/corporateactions-1) instead.", + "operationId": "get-v2-corporate_actions-announcements-id", "responses": { "200": { "content": { "application/json": { - "examples": { - "Crypto": { - "$ref": "#/components/examples/CryptoOrderResponse" - }, - "Equity": { - "$ref": "#/components/examples/EquityOrderResponse" - }, - "IPO": { - "$ref": "#/components/examples/IPOOrderResponse" - }, - "MultilegOptions": { - "$ref": "#/components/examples/MultilegOptionsOrderResponse" - }, - "Options": { - "$ref": "#/components/examples/OptionOrderResponse" - } - }, "schema": { - "$ref": "#/components/schemas/Order" + "$ref": "#/components/schemas/CorporateAnnouncement" } } }, - "description": "Successful response" + "description": "OK" } }, - "summary": "Get Order by ID", + "summary": "Retrieve a Specific Announcement", "tags": [ - "Orders" + "Corporate Actions" ] }, "parameters": [ { - "description": "order id", + "description": "The corporate announcement's id", "in": "path", - "name": "order_id", + "name": "id", "required": true, "schema": { - "format": "uuid", "type": "string" } } - ], - "patch": { - "description": "Replaces a single order with updated parameters. Each parameter overrides the corresponding attribute of the existing order. The other attributes remain the same as the existing order.\n\nA success return code from a replaced order does NOT guarantee the existing open order has been replaced. If the existing open order is filled before the replacing (new) order reaches the execution venue, the replacing (new) order is rejected, and these events are sent in the trade_updates stream channel.\n\nWhile an order is being replaced, buying power is reduced by the larger of the two orders that have been placed (the old order being replaced, and the newly placed order to replace it). If you are replacing a buy entry order with a higher limit price than the original order, the buying power is calculated based on the newly placed order. If you are replacing it with a lower limit price, the buying power is calculated based on the old order.\n\nNote: Order cannot be replaced when the status is `accepted`, `pending_new`, `pending_cancel` or `pending_replace`.\n\nNote: Notional orders for non-IPO asset classes cannot be replaced. Any attempt to modify a non-IPO notional order via this endpoint will be rejected; cancel it and submit a new one instead.\n\nNote: IPO indications of interest (`asset_class: \"ipo\"`) **are** notional and **can** be replaced via this endpoint by providing a new `notional` value. `qty` and `notional` are mutually exclusive on a single replace request.\n", - "operationId": "patchOrderByOrderId", - "requestBody": { - "content": { - "application/json": { - "examples": { - "Equity": { - "summary": "Increase qty and limit price on an equity order", - "value": { - "limit_price": "155", - "qty": "4", - "time_in_force": "gtc" - } - }, - "IPO": { - "summary": "Update notional on an IPO indication of interest", - "value": { - "notional": "750" - } - }, - "Options": { - "summary": "Adjust an option order's limit price", - "value": { - "limit_price": "11.25", - "time_in_force": "day" - } - } - }, - "schema": { - "$ref": "#/components/schemas/PatchOrderRequest" - } - } - }, - "required": true - }, - "responses": { - "200": { - "content": { - "application/json": { - "examples": { - "Equity": { - "$ref": "#/components/examples/EquityOrderResponse" - }, - "IPO": { - "$ref": "#/components/examples/IPOOrderResponse" - }, - "Options": { - "$ref": "#/components/examples/OptionOrderResponse" - } - }, - "schema": { - "$ref": "#/components/schemas/Order" - } - } - }, - "description": "Successful response\n\nThe new Order object with the new order ID." - } - }, - "summary": "Replace Order by ID", - "tags": [ - "Orders" - ] - } + ] }, - "/v2/orders:by_client_order_id": { + "/v2/options/contracts": { "get": { - "description": "Retrieves a single order specified by the client order ID.", - "operationId": "getOrderByClientOrderId", + "description": "This endpoint allows you to retrieve a list of option contracts based on various filtering criteria.\nBy default only active contracts that expire before the upcoming weekend are returned.\n", + "operationId": "get-options-contracts", "parameters": [ { - "description": "The client-assigned order ID.", + "description": "Filter contracts by one or more underlying symbols.", + "example": "AAPL,SPY", + "in": "query", + "name": "underlying_symbols", + "schema": { + "type": "string" + } + }, + { + "description": "Include deliverables array in the response.", + "example": true, + "in": "query", + "name": "show_deliverables", + "schema": { + "type": "boolean" + } + }, + { + "description": "Filter contracts by status (active/inactive). By default only active contracts are returned.", + "example": "active", + "in": "query", + "name": "status", + "schema": { + "enum": [ + "active", + "inactive" + ], + "type": "string" + } + }, + { + "description": "Filter contracts by the exact expiration date (format: YYYY-MM-DD).", + "example": "2025-06-20", + "in": "query", + "name": "expiration_date", + "schema": { + "format": "date", + "type": "string" + } + }, + { + "description": "Filter contracts with expiration date greater than or equal to the specified date.", + "example": "2025-06-20", + "in": "query", + "name": "expiration_date_gte", + "schema": { + "format": "date", + "type": "string" + } + }, + { + "description": "Filter contracts with expiration date less than or equal to the specified date. By default this is set to the next weekend.", + "example": "2025-06-20", + "in": "query", + "name": "expiration_date_lte", + "schema": { + "format": "date", + "type": "string" + } + }, + { + "description": "Filter contracts by the root symbol.", + "example": "AAPL", + "in": "query", + "name": "root_symbol", + "schema": { + "type": "string" + } + }, + { + "description": "Filter contracts by the type (call/put).", + "example": "call", + "in": "query", + "name": "type", + "schema": { + "$ref": "#/components/schemas/OptionContractType" + } + }, + { + "description": "Filter contracts by the style (american/european).", + "example": "american", + "in": "query", + "name": "style", + "schema": { + "$ref": "#/components/schemas/OptionContractStyle" + } + }, + { + "description": "Filter contracts with strike price greater than or equal to the specified value.", + "example": 50, + "in": "query", + "name": "strike_price_gte", + "schema": { + "type": "number" + } + }, + { + "description": "Filter contracts with strike price less than or equal to the specified value.", + "example": 100, + "in": "query", + "name": "strike_price_lte", + "schema": { + "type": "number" + } + }, + { + "$ref": "#/components/parameters/PageToken" + }, + { + "description": "The number of contracts to limit per page (default=100, max=10000).", + "example": 100, "in": "query", - "name": "client_order_id", - "required": true, + "name": "limit", "schema": { - "type": "string" + "type": "integer" + } + }, + { + "description": "The ppind (Penny Program Indicator) field indicates whether an option contract is eligible for penny price increments,\nwith `true` meaning it is part of the Penny Program and `false` meaning it is not.", + "example": true, + "in": "query", + "name": "ppind", + "schema": { + "type": "boolean" } } ], @@ -8440,211 +8459,211 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/Order" + "$ref": "#/components/schemas/OptionContractsResponse" } } }, - "description": "Successfully retrieved the order matching the client_order_id." + "description": "Successful Response." } }, - "summary": "Get Order by Client Order ID", + "summary": "Get Option Contracts", "tags": [ - "Orders" + "Assets" ] } }, - "/v2/perpetuals/account_vitals": { + "/v2/options/contracts/{symbol_or_id}": { "get": { - "description": "Fetches key financial metrics for the crypto perpetuals account, providing a snapshot of its current status by detailing the relationship between the user's positions and their collateral. The response includes maintenance margin (USDT), collateral balance (USDT), total collateral (USDT), and profit/loss (USDT)", - "operationId": "getCryptoPerpAccountVitals", + "description": "Get an option contract by symbol or contract ID. The symbol or id should be passed in as a path parameter.", + "operationId": "get-option-contract-symbol_or_id", "responses": { "200": { "content": { "application/json": { "schema": { - "properties": { - "collateral_balance": { - "description": "CollateralBalance (USDT) is the sum of the unrealized PnL on each position plus TotalCollateral", - "type": "integer" - }, - "maintenance_margin": { - "description": "MaintenanceMargin (USDT) is the sum of each position's individual maintenance margin", - "type": "number" - }, - "profit_loss": { - "description": "ProfitAndLoss (USDT) is the net of realized and unrealized PnL across all positions", - "type": "integer" - }, - "total_collateral": { - "description": "TotalCollateral (USDT) across all collateral assets", - "type": "integer" + "$ref": "#/components/schemas/OptionContract" + } + } + }, + "description": "An option contract" + }, + "404": { + "content": { + "application/json": { + "examples": { + "Contract Not Found": { + "value": { + "code": 40410000, + "message": "option contract TSLA231110C00021000 not found" } - }, - "type": "object" + } + }, + "schema": { + "$ref": "#/components/schemas/Error" } } }, - "description": "OK" + "description": "Not Found" } }, - "summary": "Retrieve Account Vitals", + "summary": "Get an option contract by ID or Symbol", "tags": [ - "Crypto Perpetuals Account Vitals (Beta)" + "Assets" ] - } - }, - "/v2/perpetuals/leverage": { - "get": { - "description": "Retrieves the current leverage setting for the crypto perpetuals account, specific to a given underlying asset. To use this endpoint, provide the 'symbol' of the asset as a query parameter. The system will return the asset's symbol and the integer value representing the current leverage applied to it within the account", - "operationId": "getCryptoPerpAccountLeverage", - "parameters": [ - { - "description": "Symbol of underlying asset", - "in": "query", - "name": "symbol", - "schema": { - "type": "string" - } + }, + "parameters": [ + { + "description": "symbol or contract ID", + "in": "path", + "name": "symbol_or_id", + "required": true, + "schema": { + "type": "string" } - ], + } + ] + }, + "/v2/orders": { + "delete": { + "description": "Attempts to cancel all open orders. A response will be provided for each order that is attempted to be cancelled. If an order is no longer cancelable, the server will respond with status 500 and reject the request.", + "operationId": "deleteAllOrders", "responses": { - "200": { + "207": { "content": { "application/json": { "schema": { - "properties": { - "leverage": { - "type": "integer" - }, - "symbol": { - "description": "Symbol of underlying asset", - "type": "string" - } + "items": { + "$ref": "#/components/schemas/CanceledOrderResponse" }, - "type": "object" + "type": "array" } } }, - "description": "OK" + "description": "Multi-Status with body.\n\nan array of objects that include the order id and http status code for each status request." + }, + "500": { + "description": "Failed to cancel order." } }, - "summary": "Get Account Leverage for an Asset", + "summary": "Delete All Orders", "tags": [ - "Crypto Perpetuals Leverage (Beta)" + "Orders" ] }, - "post": { - "description": "Updates the leverage for the crypto perpetuals account for a specific underlying asset. Provide the 'symbol' of the asset and the desired 'leverage' (as an integer) using query parameters. The system will return the asset's symbol and the newly set leverage value upon successful update", - "operationId": "setCryptoPerpAccountLeverage", + "get": { + "description": "Retrieves a list of orders for the account, filtered by the supplied query parameters.", + "operationId": "getAllOrders", "parameters": [ { - "description": "Symbol of underlying asset", + "description": "Order status to be queried. open, closed or all. Defaults to open.", "in": "query", - "name": "symbol", + "name": "status", "schema": { + "enum": [ + "open", + "closed", + "all" + ], + "example": "open", "type": "string" } }, { - "description": "Leverage for the underlying asset", + "description": "The maximum number of orders in response. Defaults to 50 and max is 500.", "in": "query", - "name": "leverage", + "name": "limit", "schema": { "type": "integer" } - } - ], - "responses": { - "200": { - "content": { - "application/json": { - "schema": { - "properties": { - "leverage": { - "type": "integer" - }, - "symbol": { - "description": "Symbol of underlying asset", - "type": "string" - } - }, - "type": "object" - } - } - }, - "description": "OK" - } - }, - "summary": "Set Account Leverage for an Asset", - "tags": [ - "Crypto Perpetuals Leverage (Beta)" - ] - } - }, - "/v2/perpetuals/wallets": { - "get": { - "description": "Lists wallets for the account given in the path parameter. If an asset is specified and no wallet for the account and asset pair exists one will be created. If no asset is specified only existing wallets will be listed for the account. An account may have at most one wallet per asset", - "operationId": "listCryptoPerpFundingWallets", - "responses": { - "200": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/CryptoWallet" - } - } - }, - "description": "A single wallet object if an asset is specified or an array of wallet objects if no asset is specified" - } - }, - "summary": "Retrieve Crypto Funding Wallets", - "tags": [ - "Crypto Perpetuals Funding (Beta)" - ] - }, - "parameters": [ - { - "in": "query", - "name": "asset", - "schema": { - "type": "string" - } - } - ] - }, - "/v2/perpetuals/wallets/fees/estimate": { - "get": { - "description": "Returns the estimated on-chain network (gas) fee for a proposed withdrawal from a perpetuals wallet at current chain conditions. Pass the `asset`, `from_address`, `to_address`, and `amount` you intend to send; the response reports the network fee that would be charged for the corresponding transaction.", - "operationId": "getCryptoPerpTransferEstimate", - "parameters": [ + }, { - "description": "The asset for the proposed transaction", + "description": "The response will include only ones submitted after this timestamp (exclusive.)", "in": "query", - "name": "asset", + "name": "after", "schema": { "type": "string" } }, { - "description": "The originating address of the proposed transaction", + "description": "The response will include only ones submitted until this timestamp (exclusive.)", "in": "query", - "name": "from_address", + "name": "until", "schema": { "type": "string" } }, { - "description": "The destination address of the proposed transaction", + "description": "The chronological order of response based on the submission time. asc or desc. Defaults to desc.", "in": "query", - "name": "to_address", + "name": "direction", "schema": { + "enum": [ + "asc", + "desc" + ], "type": "string" } }, { - "description": "The amount, denoted in the specified asset, of the proposed transaction", + "description": "If true, the result will roll up multi-leg orders under the legs field of primary order.", "in": "query", - "name": "amount", + "name": "nested", + "schema": { + "type": "boolean" + } + }, + { + "description": "A comma-separated list of symbols to filter by (ex. \"AAPL,TSLA,MSFT\"). A currency pair is required for crypto orders (ex. \"BTCUSD,BCHUSD,LTCUSD,ETCUSD\").", + "in": "query", + "name": "symbols", + "schema": { + "type": "string" + } + }, + { + "description": "Filters down to orders that have a matching side field set.", + "in": "query", + "name": "side", + "schema": { + "type": "string" + } + }, + { + "description": "A comma-separated list of asset classes, the response will include only orders in the specified asset classes. By specifying `us_option` as the class, you can query option orders by underlying symbol using the symbols parameter.", + "in": "query", + "name": "asset_class", + "schema": { + "items": { + "enum": [ + "us_equity", + "us_option", + "crypto", + "crypto_perp", + "treasury", + "corporate", + "global_equity", + "us_index", + "us_equity_chain", + "ipo", + "all" + ], + "type": "string" + }, + "type": "array" + } + }, + { + "description": "Return orders submitted before the order with this ID (exclusive).\nMutually exclusive with `after_order_id`. Do not combine with `after`/`until`.\n", + "in": "query", + "name": "before_order_id", + "schema": { + "type": "string" + } + }, + { + "description": "Return orders submitted after the order with this ID (exclusive).\nMutually exclusive with `before_order_id`. Do not combine with `after`/`until`.\n", + "in": "query", + "name": "after_order_id", "schema": { "type": "string" } @@ -8654,198 +8673,337 @@ "200": { "content": { "application/json": { - "schema": { - "properties": { - "fee": { - "type": "string" - } + "examples": { + "Crypto": { + "$ref": "#/components/examples/CryptoOrderResponse" }, - "type": "object" - } - } - }, - "description": "OK" - } - }, - "summary": "Returns the estimated gas fee for a proposed transaction", - "tags": [ - "Crypto Perpetuals Funding (Beta)" - ] - } - }, - "/v2/perpetuals/wallets/transfers": { - "get": { - "description": "Returns an array of all transfers associated with the given account across all wallets", - "operationId": "listCryptoPerpFundingTransfers", - "responses": { - "200": { - "content": { - "application/json": { + "Equity": { + "$ref": "#/components/examples/EquityOrderResponse" + }, + "IPO": { + "$ref": "#/components/examples/IPOOrderResponse" + }, + "MultilegOptions": { + "$ref": "#/components/examples/MultilegOptionsOrderResponse" + }, + "Options": { + "$ref": "#/components/examples/OptionOrderResponse" + } + }, "schema": { - "$ref": "#/components/schemas/CryptoTransfer" + "items": { + "$ref": "#/components/schemas/Order" + }, + "type": "array" } } }, - "description": "An array of transfer objects" + "description": "Successful response\n\nAn array of Order objects" } }, - "summary": "Retrieve Crypto Funding Transfers", + "summary": "Get All Orders", "tags": [ - "Crypto Perpetuals Funding (Beta)" + "Orders" ] }, "post": { - "description": "Creates a withdrawal request. Note that outgoing withdrawals must be sent to a whitelisted address and you must whitelist addresses at least 24 hours in advance. If you attempt to withdraw funds to a non-whitelisted address then the transfer will be rejected", - "operationId": "createCryptoPerpTransferForAccount", + "description": "Places a new order for the given account. An order request may be rejected if the account is not authorized for trading, or if the tradable balance is insufficient to fill the order.", + "operationId": "postOrder", "requestBody": { "content": { "application/json": { + "examples": { + "Crypto": { + "summary": "Buy a crypto coin", + "value": { + "limit_price": "2100", + "qty": "0.02", + "side": "buy", + "symbol": "ETH/USD", + "time_in_force": "gtc", + "type": "limit" + } + }, + "Equity": { + "summary": "Buy an equity stock", + "value": { + "limit_price": "150", + "qty": "2", + "side": "buy", + "symbol": "AAPL", + "time_in_force": "gtc", + "type": "limit" + } + }, + "IPO": { + "summary": "Submit an IPO indication of interest", + "value": { + "notional": "500", + "side": "buy", + "symbol": "FI111225", + "time_in_force": "gtc", + "type": "market" + } + }, + "MultilegOptions": { + "summary": "Option Call spread", + "value": { + "legs": [ + { + "position_intent": "buy_to_open", + "ratio_qty": "3", + "side": "buy", + "symbol": "AAPL241213C00250000" + }, + { + "position_intent": "sell_to_open", + "ratio_qty": "1", + "side": "sell", + "symbol": "AAPL241213C00260000" + } + ], + "limit_price": "10", + "order_class": "mleg", + "qty": "3", + "time_in_force": "day", + "type": "limit" + } + }, + "Options": { + "summary": "Buy an option contract", + "value": { + "limit_price": "10", + "qty": "2", + "side": "buy", + "symbol": "AAPL250620C00100000", + "time_in_force": "day", + "type": "limit" + } + } + }, "schema": { - "$ref": "#/components/schemas/CreateCryptoTransferRequest" + "$ref": "#/components/schemas/CreateOrderRequest" } } }, + "description": "", "required": true }, "responses": { "200": { "content": { "application/json": { + "examples": { + "Crypto": { + "$ref": "#/components/examples/CryptoOrderResponse" + }, + "Equity": { + "$ref": "#/components/examples/EquityOrderResponse" + }, + "IPO": { + "$ref": "#/components/examples/IPOOrderResponse" + }, + "MultilegOptions": { + "$ref": "#/components/examples/MultilegOptionsOrderResponse" + }, + "Options": { + "$ref": "#/components/examples/OptionOrderResponse" + } + }, "schema": { - "$ref": "#/components/schemas/CryptoTransfer" + "$ref": "#/components/schemas/Order" } } }, - "description": "Successfully requested a transfer." + "description": "Successful response" + }, + "403": { + "description": "Forbidden\n\nBuying power or shares is not sufficient." + }, + "422": { + "description": "Unprocessable\n\nInput parameters are not recognized." } }, - "summary": "Request a New Withdrawal", + "summary": "Create an Order", "tags": [ - "Crypto Perpetuals Funding (Beta)" + "Orders" ] } }, - "/v2/perpetuals/wallets/transfers/{transfer_id}": { + "/v2/orders/{order_id}": { + "delete": { + "description": "Attempts to cancel an Open Order. If the order is no longer cancelable, the request will be rejected with status 422; otherwise accepted with return status 204.", + "operationId": "deleteOrderByOrderID", + "responses": { + "204": { + "description": "No Content" + }, + "422": { + "description": "The order status is not cancelable." + } + }, + "summary": "Delete Order by ID", + "tags": [ + "Orders" + ] + }, "get": { - "description": "Returns a specific wallet transfer by passing into the query the transfer_id", - "operationId": "getCryptoPerpFundingTransfer", + "description": "Retrieves a single order for the given order_id.", + "operationId": "getOrderByOrderID", + "parameters": [ + { + "description": "If true, the result will roll up multi-leg orders under the legs field of primary order.", + "in": "query", + "name": "nested", + "schema": { + "type": "boolean" + } + } + ], "responses": { "200": { "content": { "application/json": { + "examples": { + "Crypto": { + "$ref": "#/components/examples/CryptoOrderResponse" + }, + "Equity": { + "$ref": "#/components/examples/EquityOrderResponse" + }, + "IPO": { + "$ref": "#/components/examples/IPOOrderResponse" + }, + "MultilegOptions": { + "$ref": "#/components/examples/MultilegOptionsOrderResponse" + }, + "Options": { + "$ref": "#/components/examples/OptionOrderResponse" + } + }, "schema": { - "$ref": "#/components/schemas/CryptoTransfer" + "$ref": "#/components/schemas/Order" } } }, - "description": "A single crypto transfer object" + "description": "Successful response" } }, - "summary": "Retrieve a Crypto Funding Transfer", + "summary": "Get Order by ID", "tags": [ - "Crypto Perpetuals Funding (Beta)" + "Orders" ] }, "parameters": [ { - "description": "The crypto transfer to retrieve", + "description": "order id", "in": "path", - "name": "transfer_id", + "name": "order_id", "required": true, "schema": { + "format": "uuid", "type": "string" } } - ] - }, - "/v2/perpetuals/wallets/whitelists": { - "get": { - "description": "Returns the list of whitelisted withdrawal addresses for your perpetuals wallets.", - "operationId": "listWhitelistedPerpAddress", + ], + "patch": { + "description": "Replaces a single order with updated parameters. Each parameter overrides the corresponding attribute of the existing order. The other attributes remain the same as the existing order.\n\nA success return code from a replaced order does NOT guarantee the existing open order has been replaced. If the existing open order is filled before the replacing (new) order reaches the execution venue, the replacing (new) order is rejected, and these events are sent in the trade_updates stream channel.\n\nWhile an order is being replaced, buying power is reduced by the larger of the two orders that have been placed (the old order being replaced, and the newly placed order to replace it). If you are replacing a buy entry order with a higher limit price than the original order, the buying power is calculated based on the newly placed order. If you are replacing it with a lower limit price, the buying power is calculated based on the old order.\n\nNote: Order cannot be replaced when the status is `accepted`, `pending_new`, `pending_cancel` or `pending_replace`.\n\nNote: Notional orders for non-IPO asset classes cannot be replaced. Any attempt to modify a non-IPO notional order via this endpoint will be rejected; cancel it and submit a new one instead.\n\nNote: IPO indications of interest (`asset_class: \"ipo\"`) **are** notional and **can** be replaced via this endpoint by providing a new `notional` value. `qty` and `notional` are mutually exclusive on a single replace request.\n", + "operationId": "patchOrderByOrderId", + "requestBody": { + "content": { + "application/json": { + "examples": { + "Equity": { + "summary": "Increase qty and limit price on an equity order", + "value": { + "limit_price": "155", + "qty": "4", + "time_in_force": "gtc" + } + }, + "IPO": { + "summary": "Update notional on an IPO indication of interest", + "value": { + "notional": "750" + } + }, + "Options": { + "summary": "Adjust an option order's limit price", + "value": { + "limit_price": "11.25", + "time_in_force": "day" + } + } + }, + "schema": { + "$ref": "#/components/schemas/PatchOrderRequest" + } + } + }, + "required": true + }, "responses": { "200": { "content": { "application/json": { + "examples": { + "Equity": { + "$ref": "#/components/examples/EquityOrderResponse" + }, + "IPO": { + "$ref": "#/components/examples/IPOOrderResponse" + }, + "Options": { + "$ref": "#/components/examples/OptionOrderResponse" + } + }, "schema": { - "$ref": "#/components/schemas/WhitelistedAddress" + "$ref": "#/components/schemas/Order" } } }, - "description": "An array of whitelisted objects" + "description": "Successful response\n\nThe new Order object with the new order ID." } }, - "summary": "An array of whitelisted addresses", + "summary": "Replace Order by ID", "tags": [ - "Crypto Perpetuals Funding (Beta)" + "Orders" ] - }, - "post": { - "description": "Submits a new whitelisted withdrawal address for your perpetuals wallets. The chain is derived from the supplied asset symbol and address, and the same address cannot be whitelisted twice on the same chain.\n\nWhere travel rule reporting applies to your account, address details are validated against the travel rule provider before being persisted.\n\nNote that whitelisted addresses must wait at least 24 hours before they can be used as a withdrawal destination.", - "operationId": "createWhitelistedPerpAddress", - "requestBody": { - "content": { - "application/json": { - "schema": { - "properties": { - "address": { - "description": "The address to be whitelisted", - "type": "string" - }, - "asset": { - "description": "Symbol of underlying asset for the whitelisted address", - "type": "string" - } - }, - "type": "object" - } + } + }, + "/v2/orders:by_client_order_id": { + "get": { + "description": "Retrieves a single order specified by the client order ID.", + "operationId": "getOrderByClientOrderId", + "parameters": [ + { + "description": "The client-assigned order ID.", + "in": "query", + "name": "client_order_id", + "required": true, + "schema": { + "type": "string" } - }, - "required": true - }, + } + ], "responses": { "200": { "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/WhitelistedAddress" + "$ref": "#/components/schemas/Order" } } }, - "description": "Successfully requested a whitelisted address" + "description": "Successfully retrieved the order matching the client_order_id." } }, - "summary": "Request a new whitelisted address", + "summary": "Get Order by Client Order ID", "tags": [ - "Crypto Perpetuals Funding (Beta)" + "Orders" ] } }, - "/v2/perpetuals/wallets/whitelists/{whitelisted_address_id}": { - "delete": { - "description": "Deletes a whitelisted withdrawal address from your perpetuals wallets by ID. Subsequent withdrawals targeting the deleted address will be rejected.", - "operationId": "deleteWhitelistedPerpAddress", - "responses": { - "200": { - "description": "Successfully deleted a whitelisted address" - } - }, - "summary": "Delete a whitelisted address", - "tags": [ - "Crypto Perpetuals Funding (Beta)" - ] - }, - "parameters": [ - { - "description": "The whitelisted address to delete", - "in": "path", - "name": "whitelisted_address_id", - "required": true, - "schema": { - "type": "string" - } - } - ] - }, "/v2/positions": { "delete": { "description": "Closes (liquidates) all of the account's open long and short positions. A response will be provided for each order that is attempted to be cancelled. If an order is no longer cancelable, the server will respond with status 500 and reject the request.", @@ -9118,8 +9276,13 @@ }, "/v2/tokenization/mint": { "post": { - "description": "This endpoint is used by an Authorized Participant to request the minting of a tokenized asset.", + "description": "This endpoint is used by an Authorized Participant to request the minting of a tokenized asset.\n\n**Idempotency**: When the `Idempotency-Key` header is supplied, this endpoint is\nidempotent. Multiple requests with the same key and identical request body will\ncreate only one mint request. A subsequent request returns the previously created\nrequest with the same response (no duplicate is created). If the same key is used\nwith a different request body, the API returns `422 Unprocessable Entity`.\n\n**Recommended for production**: Always supply `Idempotency-Key` when requesting a\nmint. This allows safe retries on timeouts, network errors, or 5xx responses\nwithout risking duplicate requests. Use a client-generated unique value (e.g. UUID).", "operationId": "postTokenizationMint", + "parameters": [ + { + "$ref": "#/components/parameters/LegacyIdempotencyKey" + } + ], "requestBody": { "content": { "application/json": { @@ -9153,7 +9316,229 @@ } } }, - "description": "Bad request (e.g. malformed input, insufficient position, or account not authorized to mint)." + "description": "Bad request (e.g. malformed input, insufficient position, or account not authorized to mint). Also returned when the `Idempotency-Key` header is malformed, or when replaying a request that was originally rejected." + }, + "401": { + "content": { + "application/json": { + "example": { + "code": 40110000, + "message": "unauthorized" + }, + "schema": { + "$ref": "#/components/schemas/Error" + } + } + }, + "description": "Authentication credentials are missing or invalid." + }, + "403": { + "content": { + "application/json": { + "example": { + "code": 40310000, + "message": "forbidden" + }, + "schema": { + "$ref": "#/components/schemas/Error" + } + } + }, + "description": "Caller is not authorized to perform this operation." + }, + "422": { + "content": { + "application/json": { + "example": { + "code": 42210001, + "message": "issuer is required, network is required" + }, + "schema": { + "$ref": "#/components/schemas/Error" + } + } + }, + "description": "One or more request parameters are missing or invalid, or the same `Idempotency-Key` was reused with a different request body." + } + }, + "summary": "Mint a Tokenized Asset", + "tags": [ + "Tokenization" + ] + } + }, + "/v2/tokenization/requests": { + "get": { + "description": "An Authorized Participant can use this endpoint to list the tokenization requests performed on the Instant Tokenization Network (ITN).", + "operationId": "getTokenizationRequests", + "parameters": [ + { + "description": "Tokenization request type to be queried", + "example": "mint", + "in": "query", + "name": "type", + "schema": { + "$ref": "#/components/schemas/TokenizationRequestType" + } + }, + { + "description": "Tokenization request status to be queried", + "example": "pending", + "in": "query", + "name": "status", + "schema": { + "$ref": "#/components/schemas/TokenizationRequestStatus" + } + }, + { + "description": "Underlying symbol of the tokenization requests to be queried", + "in": "query", + "name": "underlying_symbol", + "schema": { + "type": "string" + } + }, + { + "description": "Issuer of the tokenization requests to be queried", + "in": "query", + "name": "issuer", + "schema": { + "$ref": "#/components/schemas/TokenizationIssuer" + } + }, + { + "description": "Network of the tokenization requests to be queried", + "in": "query", + "name": "network", + "schema": { + "$ref": "#/components/schemas/TokenizationNetwork" + } + }, + { + "description": "The response will include only requests created after this timestamp (exclusive)", + "in": "query", + "name": "after", + "schema": { + "example": "2025-09-30T18:38:01.942282Z", + "format": "date-time", + "type": "string" + } + }, + { + "description": "The response will include only requests created before this timestamp (exclusive)", + "in": "query", + "name": "before", + "schema": { + "example": "2025-09-30T18:38:01.942282Z", + "format": "date-time", + "type": "string" + } + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "items": { + "$ref": "#/components/schemas/TokenizationRequest" + }, + "type": "array" + } + } + }, + "description": "Successful response\n\nA list of tokenization requests" + }, + "401": { + "content": { + "application/json": { + "example": { + "code": 40110000, + "message": "unauthorized" + }, + "schema": { + "$ref": "#/components/schemas/Error" + } + } + }, + "description": "Authentication credentials are missing or invalid." + }, + "403": { + "content": { + "application/json": { + "example": { + "code": 40310000, + "message": "forbidden" + }, + "schema": { + "$ref": "#/components/schemas/Error" + } + } + }, + "description": "Caller is not authorized to perform this operation." + }, + "422": { + "content": { + "application/json": { + "example": { + "code": 42210001, + "message": "failed to parse after" + }, + "schema": { + "$ref": "#/components/schemas/Error" + } + } + }, + "description": "One or more request parameters are missing or invalid." + } + }, + "summary": "List Tokenization Requests", + "tags": [ + "Tokenization" + ] + } + }, + "/v2/tokenization/requests/{tokenization_request_id}": { + "get": { + "description": "An Authorized Participant can use this endpoint to retrieve a single tokenization request, mint or redeem, by its `tokenization_request_id`.", + "operationId": "getTokenizationRequest", + "parameters": [ + { + "description": "Unique identifier of the tokenization request, as returned by the mint endpoint or the list-requests endpoint.", + "in": "path", + "name": "tokenization_request_id", + "required": true, + "schema": { + "format": "uuid", + "type": "string" + } + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "example": { + "created_at": "2025-11-04T18:38:01.942282Z", + "fees": "0.5", + "issuer": "xstocks", + "network": "solana", + "qty": "1.0", + "status": "completed", + "token_symbol": "TSLAx", + "tokenization_request_id": "5b1d6a3e-7f0a-4d2c-b8e1-9e6f1c0d2c4a", + "tx_hash": "5J7ZxK4QwR3vH2pY1aB6sN8cM9tD0fA2eXyVwUjL3kPqR4mZcF7nB1tA8sH6dG2pE", + "type": "mint", + "underlying_symbol": "TSLA", + "updated_at": "2025-11-04T18:38:42.117004Z", + "wallet_address": "5dXY1aH2tQpV3wXmJg6Z7c8B4nKvF9bA1pQrSt2uVwYxXz" + }, + "schema": { + "$ref": "#/components/schemas/TokenizationRequest" + } + } + }, + "description": "Successful response" }, "401": { "content": { @@ -9167,111 +9552,69 @@ } } }, - "description": "Authentication credentials are missing or invalid." + "description": "Authentication credentials are missing or invalid." + }, + "403": { + "content": { + "application/json": { + "example": { + "code": 40310000, + "message": "forbidden" + }, + "schema": { + "$ref": "#/components/schemas/Error" + } + } + }, + "description": "Caller is not authorized to perform this operation." }, - "403": { + "404": { "content": { "application/json": { "example": { - "code": 40310000, - "message": "forbidden" + "code": 40410000, + "message": "tokenization request not found" }, "schema": { "$ref": "#/components/schemas/Error" } } }, - "description": "Caller is not authorized to perform this operation." + "description": "No tokenization request with the supplied `tokenization_request_id` exists for the calling account." }, "422": { "content": { "application/json": { "example": { - "code": 40010001, - "message": "issuer is required, network is required" + "code": 42210001, + "message": "tokenization_request_id is not a valid UUID" }, "schema": { "$ref": "#/components/schemas/Error" } } }, - "description": "One or more request parameters are missing or invalid." + "description": "The `tokenization_request_id` path parameter is not a valid UUID." } }, - "summary": "Mint a Tokenized Asset", + "summary": "Get Tokenization Request by ID", "tags": [ "Tokenization" ] } }, - "/v2/tokenization/requests": { + "/v2/tokenization/requests:by_client_request_id": { "get": { - "description": "An Authorized Participant can use this endpoint to list the tokenization requests performed on the Instant Tokenization Network (ITN).", - "operationId": "getTokenizationRequests", + "description": "An Authorized Participant can use this endpoint to retrieve a single mint tokenization request by their own `client_request_id` -- the AP-supplied label originally passed on the mint request. If the AP has reused a `client_request_id` across multiple requests, the most recently created request is returned.", + "operationId": "getTokenizationRequestByClientRequestID", "parameters": [ { - "description": "Tokenization request type to be queried", - "example": "mint", - "in": "query", - "name": "type", - "schema": { - "$ref": "#/components/schemas/TokenizationRequestType" - } - }, - { - "description": "Tokenization request status to be queried", - "example": "pending", - "in": "query", - "name": "status", - "schema": { - "$ref": "#/components/schemas/TokenizationRequestStatus" - } - }, - { - "description": "Underlying symbol of the tokenization requests to be queried", - "in": "query", - "name": "underlying_symbol", - "schema": { - "type": "string" - } - }, - { - "description": "Issuer of the tokenization requests to be queried", - "in": "query", - "name": "issuer", - "schema": { - "enum": [ - "xstocks" - ], - "example": "xstocks", - "type": "string" - } - }, - { - "description": "Network of the tokenization requests to be queried", - "in": "query", - "name": "network", - "schema": { - "$ref": "#/components/schemas/TokenizationNetwork" - } - }, - { - "description": "The response will include only requests created after this timestamp (exclusive)", - "in": "query", - "name": "after", - "schema": { - "example": "2025-09-30T18:38:01.942282Z", - "format": "date-time", - "type": "string" - } - }, - { - "description": "The response will include only requests created before this timestamp (exclusive)", + "description": "AP-supplied request identifier originally passed on the mint request, echoed back on the tokenization-request response.", "in": "query", - "name": "before", + "name": "client_request_id", + "required": true, "schema": { - "example": "2025-09-30T18:38:01.942282Z", - "format": "date-time", + "example": "ap-request-2025-11-04-001", "type": "string" } } @@ -9280,15 +9623,27 @@ "200": { "content": { "application/json": { + "example": { + "created_at": "2025-11-04T18:38:01.942282Z", + "fees": "0.5", + "issuer": "xstocks", + "network": "solana", + "qty": "1.0", + "status": "completed", + "token_symbol": "TSLAx", + "tokenization_request_id": "5b1d6a3e-7f0a-4d2c-b8e1-9e6f1c0d2c4a", + "tx_hash": "5J7ZxK4QwR3vH2pY1aB6sN8cM9tD0fA2eXyVwUjL3kPqR4mZcF7nB1tA8sH6dG2pE", + "type": "mint", + "underlying_symbol": "TSLA", + "updated_at": "2025-11-04T18:38:42.117004Z", + "wallet_address": "5dXY1aH2tQpV3wXmJg6Z7c8B4nKvF9bA1pQrSt2uVwYxXz" + }, "schema": { - "items": { - "$ref": "#/components/schemas/TokenizationRequest" - }, - "type": "array" + "$ref": "#/components/schemas/TokenizationRequest" } } }, - "description": "Successful response\n\nA list of tokenization requests" + "description": "Successful response" }, "401": { "content": { @@ -9318,22 +9673,36 @@ }, "description": "Caller is not authorized to perform this operation." }, + "404": { + "content": { + "application/json": { + "example": { + "code": 40410000, + "message": "tokenization request not found" + }, + "schema": { + "$ref": "#/components/schemas/Error" + } + } + }, + "description": "No tokenization request with the supplied `client_request_id` exists for the calling account." + }, "422": { "content": { "application/json": { "example": { - "code": 40010001, - "message": "failed to parse after" + "code": 42210001, + "message": "client_request_id query parameter is required" }, "schema": { "$ref": "#/components/schemas/Error" } } }, - "description": "One or more request parameters are missing or invalid." + "description": "The `client_request_id` query parameter is missing." } }, - "summary": "List Tokenization Requests", + "summary": "Get Tokenization Request by `client_request_id`", "tags": [ "Tokenization" ] @@ -9470,6 +9839,7 @@ ] }, "post": { + "deprecated": true, "description": "Creates a withdrawal request. Note that outgoing withdrawals must be sent to a whitelisted address and you must whitelist addresses at least 24 hours in advance. If you attempt to withdraw funds to a non-whitelisted address then the transfer will be rejected.", "operationId": "createCryptoTransferForAccount", "requestBody": { @@ -9492,12 +9862,37 @@ } }, "description": "Successfully requested a transfer." + }, + "400": { + "content": { + "application/json": { + "examples": { + "TravelRuleValidationError": { + "$ref": "#/components/examples/TravelRuleValidationError" + }, + "WalletError": { + "value": { + "message": "asset cannot be empty" + } + } + }, + "schema": { + "$ref": "#/components/schemas/TravelRuleErrorResponse" + } + } + }, + "description": "The request or travel rule information is invalid." } }, "summary": "Request a New Withdrawal", "tags": [ "Crypto Funding" - ] + ], + "x-deprecation": { + "reason": "This endpoint is deprecated. Use the Alpaca web application to initiate withdrawals.", + "since": "2026-07-09", + "sunset": "2026-10-09" + } } }, "/v2/wallets/transfers/{transfer_id}": { @@ -9533,6 +9928,116 @@ } ] }, + "/v2/wallets/travel-rule/vasps": { + "get": { + "description": "Search for Virtual Asset Service Providers, or crypto exchanges, on Notabene's network.\nThis endpoint can be used to find the VASP DID for your beneficiary's exchange when submitting\ntravel rule information for your whitelisted wallets. Use the `q` parameter to search for exchanges.", + "operationId": "searchVASPs", + "parameters": [ + { + "description": "General search query string.", + "in": "query", + "name": "q", + "schema": { + "type": "string" + } + }, + { + "description": "Filter by VASP email domain.", + "in": "query", + "name": "emailDomain", + "schema": { + "type": "string" + } + }, + { + "description": "Filter by Chainalysis-specific VASP name.", + "in": "query", + "name": "chainalysisName", + "schema": { + "type": "string" + } + }, + { + "description": "Specify which fields to return (comma-separated).", + "in": "query", + "name": "fields", + "schema": { + "type": "string" + } + }, + { + "description": "The zero-based page number to retrieve.", + "in": "query", + "name": "page", + "schema": { + "default": 0, + "minimum": 0, + "type": "integer" + } + }, + { + "description": "Number of items per page.", + "in": "query", + "name": "per_page", + "schema": { + "default": 10, + "maximum": 100, + "minimum": 1, + "type": "integer" + } + }, + { + "description": "Field-based sort expression, such as name:ASC. Multiple expressions may be comma-separated. Defaults to name:ASC.", + "in": "query", + "name": "order", + "schema": { + "default": "name:ASC", + "example": "name:ASC", + "pattern": "^([ A-Za-z]+(:(ASC|DESC))?(:NULLS (FIRST|LAST))?(,?))+$", + "type": "string" + } + }, + { + "description": "Whether to include child/subsidiary entities.", + "in": "query", + "name": "includeSubsidiaryVASPs", + "schema": { + "default": false, + "type": "boolean" + } + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "example": { + "page": 1, + "pages": 1, + "total": 1, + "vasps": [ + { + "country": "US", + "did": "did:ethr:0xf1c002f9e7ca88018d6dcf1e86403fb2f5f055f4", + "id": "vasp-1", + "name": "Example Exchange" + } + ] + }, + "schema": { + "$ref": "#/components/schemas/SearchVASPsResponse" + } + } + }, + "description": "Successful request. Returns an array of VASPs or an empty list if no matches found." + } + }, + "summary": "Search for VASPs", + "tags": [ + "Crypto Funding" + ] + } + }, "/v2/wallets/whitelists": { "get": { "description": "Returns the list of whitelisted withdrawal addresses for your account.", @@ -9555,26 +10060,13 @@ ] }, "post": { - "description": "Submits a new whitelisted withdrawal address for your account. The chain is derived from the supplied asset symbol and address, and the same address cannot be whitelisted twice on the same chain.\n\nWhere travel rule reporting applies to your account, address details are validated against the travel rule provider before being persisted.\n\nNote that whitelisted addresses must wait at least 24 hours before they can be used as a withdrawal destination.", + "description": "Submits a new whitelisted withdrawal address for your account. The chain is derived from the supplied asset symbol and address, and the same address cannot be whitelisted twice on the same chain.\n\nWhere travel rule reporting applies to your account, supply travel rule information for the destination wallet. Use the `Search VASPs` endpoint to find the VASP DID for the destination exchange. For a self-hosted wallet, set `beneficiary_is_self_hosted` to true. If the exchange is not in the directory, provide its information in `beneficiary_manual_entry`.\n\nNote that whitelisted addresses must wait at least 24 hours before they can be used as a withdrawal destination.", "operationId": "createWhitelistedAddress", "requestBody": { "content": { "application/json": { "schema": { - "properties": { - "address": { - "description": "The address to be whitelisted", - "type": "string" - }, - "asset": { - "description": "Symbol of underlying asset for the whitelisted address", - "type": "string" - }, - "chain": { - "$ref": "#/components/schemas/CryptoChain" - } - }, - "type": "object" + "$ref": "#/components/schemas/CreateWhitelistedAddressRequest" } } }, @@ -9590,6 +10082,26 @@ } }, "description": "Successfully requested a whitelisted address" + }, + "400": { + "content": { + "application/json": { + "examples": { + "TravelRuleValidationError": { + "$ref": "#/components/examples/TravelRuleValidationError" + }, + "WalletError": { + "value": { + "message": "asset cannot be empty" + } + } + }, + "schema": { + "$ref": "#/components/schemas/TravelRuleErrorResponse" + } + } + }, + "description": "The request or travel rule information is invalid." } }, "summary": "Request a new whitelisted address", @@ -9624,6 +10136,64 @@ } ] }, + "/v2/wallets/whitelists/{whitelisted_address_id}/travel-rule-info": { + "parameters": [ + { + "description": "The whitelisted address update travel rule information for", + "in": "path", + "name": "whitelisted_address_id", + "required": true, + "schema": { + "format": "uuid", + "type": "string" + } + } + ], + "patch": { + "description": "You are required to supply travel rule information for the crypto wallets you're withdrawing to.\n\nIt is preferred that you use the `Search VASPs` endpoint to find the VASP DID for the exchange you are withdrawing to,\notherwise, if it's a self-hosted wallet, please set `beneficiary_is_self_hosted` to true.\n\nIf the exchange cannot be found in our directory, please supply the information manually using the `beneficiary_manual_entry` object.", + "operationId": "updateWhitelistedAddressTravelRuleInfo", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UpdateWhitelistedAddressTravelRuleInfoRequest" + } + } + }, + "description": "Travel rule information to associate with the whitelisted address.", + "required": true + }, + "responses": { + "200": { + "description": "Successfully updated travel rule information" + }, + "400": { + "content": { + "application/json": { + "examples": { + "TravelRuleValidationError": { + "$ref": "#/components/examples/TravelRuleValidationError" + }, + "WalletError": { + "value": { + "message": "travel_rule_info is missing" + } + } + }, + "schema": { + "$ref": "#/components/schemas/TravelRuleErrorResponse" + } + } + }, + "description": "The request or travel rule information is invalid." + } + }, + "summary": "Update travel rule information for a whitelisted wallet", + "tags": [ + "Crypto Funding" + ] + } + }, "/v2/watchlists": { "get": { "description": "Returns the list of watchlists registered under the account.", @@ -9668,7 +10238,7 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/UpdateWatchlistRequest" + "$ref": "#/components/schemas/CreateWatchlistRequest" } } }, @@ -9748,13 +10318,7 @@ "content": { "application/json": { "schema": { - "properties": { - "symbol": { - "description": "the symbol name to add to the watchlist", - "type": "string" - } - }, - "type": "object" + "$ref": "#/components/schemas/AddAssetToWatchlistRequest" } } } @@ -9923,13 +10487,7 @@ "content": { "application/json": { "schema": { - "properties": { - "symbol": { - "description": "the symbol name to add to the watchlist", - "type": "string" - } - }, - "type": "object" + "$ref": "#/components/schemas/AddAssetToWatchlistRequest" } } } @@ -10152,15 +10710,6 @@ { "name": "Crypto Funding" }, - { - "name": "Crypto Perpetuals Funding (Beta)" - }, - { - "name": "Crypto Perpetuals Account Vitals (Beta)" - }, - { - "name": "Crypto Perpetuals Leverage (Beta)" - }, { "description": "Request minting of tokenized assets and list tokenization requests on the Instant Tokenization Network (ITN).", "name": "Tokenization" From 74ca77afe313049d4cf71169d21391905be3e96b Mon Sep 17 00:00:00 2001 From: Jacob Peterson <14127217+Petersoj@users.noreply.github.com> Date: Sat, 26 Sep 2026 12:54:55 -0600 Subject: [PATCH 271/271] Update version to 11.0.0-pre.1 --- buildSrc/src/main/kotlin/ModuleCommon.kt | 2 +- docs/README.md | 22 +++++++++++----------- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/buildSrc/src/main/kotlin/ModuleCommon.kt b/buildSrc/src/main/kotlin/ModuleCommon.kt index 5704c22b..5feeb092 100644 --- a/buildSrc/src/main/kotlin/ModuleCommon.kt +++ b/buildSrc/src/main/kotlin/ModuleCommon.kt @@ -1,3 +1,3 @@ const val PROJECT_GROUP = "net.jacobpeterson.alpaca-java" -const val PROJECT_VERSION = "11.0.0-pre.0" +const val PROJECT_VERSION = "11.0.0-pre.1" const val JRELEASER_MAVEN_REPOSITORY_DIRECTORY = "jreleaser-staging" diff --git a/docs/README.md b/docs/README.md index 61ec84e3..3abdbb56 100644 --- a/docs/README.md +++ b/docs/README.md @@ -11,9 +11,9 @@ GitHub Discussions

- Maven Central + Maven Central - javadoc + javadoc
Java Version @@ -98,7 +98,7 @@ For `build.gradle.kts`: ```kotlin dependencies { - implementation("net.jacobpeterson.alpaca-java:trading:11.0.0-pre.0") + implementation("net.jacobpeterson.alpaca-java:trading:11.0.0-pre.1") } ``` @@ -106,7 +106,7 @@ For `build.gradle`: ```groovy dependencies { - implementation 'net.jacobpeterson.alpaca-java:trading:11.0.0-pre.0' + implementation 'net.jacobpeterson.alpaca-java:trading:11.0.0-pre.1' } ``` @@ -116,7 +116,7 @@ For `pom.xml`: net.jacobpeterson.alpaca-java trading - 11.0.0-pre.0 + 11.0.0-pre.1 ``` @@ -193,7 +193,7 @@ For `build.gradle.kts`: ```kotlin dependencies { - implementation("net.jacobpeterson.alpaca-java:market-data:11.0.0-pre.0") + implementation("net.jacobpeterson.alpaca-java:market-data:11.0.0-pre.1") } ``` @@ -201,7 +201,7 @@ For `build.gradle`: ```groovy dependencies { - implementation 'net.jacobpeterson.alpaca-java:market-data:11.0.0-pre.0' + implementation 'net.jacobpeterson.alpaca-java:market-data:11.0.0-pre.1' } ``` @@ -211,7 +211,7 @@ For `pom.xml`: net.jacobpeterson.alpaca-java market-data - 11.0.0-pre.0 + 11.0.0-pre.1 ``` @@ -257,7 +257,7 @@ For `build.gradle.kts`: ```kotlin dependencies { - implementation("net.jacobpeterson.alpaca-java:broker:11.0.0-pre.0") + implementation("net.jacobpeterson.alpaca-java:broker:11.0.0-pre.1") } ``` @@ -265,7 +265,7 @@ For `build.gradle`: ```groovy dependencies { - implementation 'net.jacobpeterson.alpaca-java:broker:11.0.0-pre.0' + implementation 'net.jacobpeterson.alpaca-java:broker:11.0.0-pre.1' } ``` @@ -275,7 +275,7 @@ For `pom.xml`: net.jacobpeterson.alpaca-java broker - 11.0.0-pre.0 + 11.0.0-pre.1 ```