├── .github ├── FUNDING.yml └── image │ └── logo.png ├── _config.yml ├── settings.gradle ├── .gitignore ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── src └── main │ └── java │ └── net │ └── jacobpeterson │ └── alpaca │ ├── websocket │ ├── marketdata │ │ ├── model │ │ │ ├── market_data_message.json │ │ │ └── control │ │ │ │ ├── success_message_type.json │ │ │ │ ├── success_message.json │ │ │ │ └── error_message.json │ │ ├── streams │ │ │ ├── crypto │ │ │ │ ├── model │ │ │ │ │ ├── trade │ │ │ │ │ │ ├── crypto_trade_taker_side.json │ │ │ │ │ │ └── crypto_trade_message.json │ │ │ │ │ ├── orderbook │ │ │ │ │ │ ├── crypto_order_book_entry.json │ │ │ │ │ │ └── crypto_order_book_message.json │ │ │ │ │ ├── crypto_market_data_message.json │ │ │ │ │ ├── crypto_market_data_message_type.json │ │ │ │ │ ├── quote │ │ │ │ │ │ └── crypto_quote_message.json │ │ │ │ │ ├── control │ │ │ │ │ │ └── crypto_subscriptions_message.json │ │ │ │ │ └── bar │ │ │ │ │ │ └── crypto_bar_message.json │ │ │ │ ├── CryptoMarketDataListenerAdapter.java │ │ │ │ ├── CryptoMarketDataListener.java │ │ │ │ ├── CryptoMarketDataWebsocketInterface.java │ │ │ │ └── CryptoMarketDataWebsocket.java │ │ │ ├── stock │ │ │ │ ├── model │ │ │ │ │ ├── tradecancelerror │ │ │ │ │ │ ├── stock_trade_cancel_error_action.json │ │ │ │ │ │ └── stock_trade_cancel_error_message.json │ │ │ │ │ ├── stock_market_data_message.json │ │ │ │ │ ├── stock_market_data_message_type.json │ │ │ │ │ ├── limituplimitdownband │ │ │ │ │ │ └── stock_limit_up_limit_down_band_message.json │ │ │ │ │ ├── tradingstatus │ │ │ │ │ │ └── stock_trading_status_message.json │ │ │ │ │ ├── trade │ │ │ │ │ │ └── stock_trade_message.json │ │ │ │ │ ├── bar │ │ │ │ │ │ └── stock_bar_message.json │ │ │ │ │ ├── control │ │ │ │ │ │ └── stock_subscriptions_message.json │ │ │ │ │ ├── quote │ │ │ │ │ │ └── stock_quote_message.json │ │ │ │ │ └── tradecorrection │ │ │ │ │ │ └── stock_trade_correction_message.json │ │ │ │ ├── StockMarketDataListenerAdapter.java │ │ │ │ ├── StockMarketDataListener.java │ │ │ │ ├── StockMarketDataWebsocketInterface.java │ │ │ │ └── StockMarketDataWebsocket.java │ │ │ └── news │ │ │ │ ├── model │ │ │ │ ├── news_market_data_message_type.json │ │ │ │ ├── control │ │ │ │ │ └── news_subscriptions_message.json │ │ │ │ ├── news_market_data_message.json │ │ │ │ └── news │ │ │ │ │ └── news_message.json │ │ │ │ ├── NewsMarketDataListenerAdapter.java │ │ │ │ ├── NewsMarketDataListener.java │ │ │ │ ├── NewsMarketDataWebsocketInterface.java │ │ │ │ └── NewsMarketDataWebsocket.java │ │ ├── MarketDataWebsocketInterface.java │ │ └── MarketDataWebsocket.java │ ├── updates │ │ ├── model │ │ │ ├── updates_message_type.json │ │ │ ├── updates_message.json │ │ │ ├── authorization │ │ │ │ ├── authorization_data.json │ │ │ │ └── authorization_message.json │ │ │ ├── listening │ │ │ │ ├── listening_data.json │ │ │ │ └── listening_message.json │ │ │ └── tradeupdate │ │ │ │ ├── trade_update_message.json │ │ │ │ ├── trade_update.json │ │ │ │ └── trade_update_event.json │ │ ├── UpdatesListener.java │ │ ├── UpdatesWebsocketInterface.java │ │ └── UpdatesWebsocket.java │ ├── AlpacaWebsocketStateListener.java │ ├── AlpacaWebsocketInterface.java │ └── AlpacaWebsocket.java │ ├── util │ ├── apitype │ │ ├── market_data_websocket_source_type.json │ │ ├── trader_api_endpoint_type.json │ │ └── broker_api_endpoint_type.json │ ├── apikey │ │ └── APIKeyUtil.java │ └── sse │ │ ├── SSERequest.java │ │ ├── SSEListener.java │ │ └── SSEListenerAdapter.java │ ├── rest │ ├── marketdata │ │ └── AlpacaMarketDataAPI.java │ ├── trader │ │ └── AlpacaTraderAPI.java │ └── broker │ │ ├── events │ │ └── EventsApiSSE.java │ │ └── AlpacaBrokerAPI.java │ └── AlpacaAPI.java ├── gradle.properties ├── LICENSE.txt ├── gradlew.bat ├── gradlew └── README.md /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | github: Petersoj -------------------------------------------------------------------------------- /_config.yml: -------------------------------------------------------------------------------- 1 | theme: jekyll-theme-tactile 2 | -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | rootProject.name = 'alpaca-java' 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /bin/ 2 | build/ 3 | .gradle/ 4 | .settings 5 | .DS_Store 6 | .idea/ 7 | -------------------------------------------------------------------------------- /.github/image/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Petersoj/alpaca-java/master/.github/image/logo.png -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Petersoj/alpaca-java/master/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/model/market_data_message.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "object", 3 | "properties": {} 4 | } 5 | -------------------------------------------------------------------------------- /src/main/java/net/jacobpeterson/alpaca/util/apitype/market_data_websocket_source_type.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "string", 3 | "enum": [ 4 | "iex", 5 | "sip" 6 | ] 7 | } 8 | -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | org.gradle.logging.level=info 2 | org.gradle.logging.stacktrace=all 3 | org.gradle.jvmargs=-XX:MaxRAMPercentage=100.0 -XX:MinHeapFreeRatio=5 -XX:MaxHeapFreeRatio=15 4 | -------------------------------------------------------------------------------- /src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/model/control/success_message_type.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "string", 3 | "enum": [ 4 | "success", 5 | "authenticated" 6 | ] 7 | } 8 | -------------------------------------------------------------------------------- /src/main/java/net/jacobpeterson/alpaca/util/apitype/trader_api_endpoint_type.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "string", 3 | "javaName": "TraderAPIEndpointType", 4 | "enum": [ 5 | "paper", 6 | "live" 7 | ] 8 | } 9 | -------------------------------------------------------------------------------- /src/main/java/net/jacobpeterson/alpaca/websocket/updates/model/updates_message_type.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "string", 3 | "enum": [ 4 | "listening", 5 | "authorization", 6 | "trade_updates" 7 | ] 8 | } 9 | -------------------------------------------------------------------------------- /src/main/java/net/jacobpeterson/alpaca/util/apitype/broker_api_endpoint_type.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "string", 3 | "javaName": "BrokerAPIEndpointType", 4 | "enum": [ 5 | "sandbox", 6 | "production" 7 | ] 8 | } 9 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-9.1.0-bin.zip 4 | networkTimeout=10000 5 | validateDistributionUrl=true 6 | zipStoreBase=GRADLE_USER_HOME 7 | zipStorePath=wrapper/dists 8 | -------------------------------------------------------------------------------- /src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/streams/crypto/model/trade/crypto_trade_taker_side.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "string", 3 | "enum": [ 4 | "B", 5 | "S" 6 | ], 7 | "javaEnums": [ 8 | { 9 | "name": "BUY" 10 | }, 11 | { 12 | "name": "SELL" 13 | } 14 | ] 15 | } 16 | -------------------------------------------------------------------------------- /src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/streams/stock/model/tradecancelerror/stock_trade_cancel_error_action.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "string", 3 | "enum": [ 4 | "C", 5 | "E" 6 | ], 7 | "javaEnums": [ 8 | { 9 | "name": "CANCEL" 10 | }, 11 | { 12 | "name": "ERROR" 13 | } 14 | ] 15 | } 16 | -------------------------------------------------------------------------------- /src/main/java/net/jacobpeterson/alpaca/websocket/updates/model/updates_message.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "object", 3 | "properties": { 4 | "stream": { 5 | "existingJavaType": "net.jacobpeterson.alpaca.model.websocket.updates.model.UpdatesMessageType", 6 | "title": "The {@link net.jacobpeterson.alpaca.model.websocket.updates.model.UpdatesMessageType}." 7 | } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /src/main/java/net/jacobpeterson/alpaca/websocket/updates/model/authorization/authorization_data.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "object", 3 | "properties": { 4 | "status": { 5 | "existingJavaType": "java.lang.String", 6 | "title": "The status." 7 | }, 8 | "action": { 9 | "existingJavaType": "java.lang.String", 10 | "title": "The desired action." 11 | } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/MarketDataWebsocketInterface.java: -------------------------------------------------------------------------------- 1 | package net.jacobpeterson.alpaca.websocket.marketdata; 2 | 3 | import net.jacobpeterson.alpaca.websocket.AlpacaWebsocketInterface; 4 | 5 | /** 6 | * {@link MarketDataWebsocketInterface} is an {@link AlpacaWebsocketInterface} for {@link MarketDataWebsocket}. 7 | */ 8 | public interface MarketDataWebsocketInterface extends AlpacaWebsocketInterface {} 9 | -------------------------------------------------------------------------------- /src/main/java/net/jacobpeterson/alpaca/websocket/updates/model/listening/listening_data.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "object", 3 | "properties": { 4 | "streams": { 5 | "existingJavaType": "java.util.Set", 6 | "title": "The {@link java.util.Set} of {@link net.jacobpeterson.alpaca.model.websocket.updates.model.UpdatesMessageType}s." 7 | } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/streams/crypto/model/orderbook/crypto_order_book_entry.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "object", 3 | "properties": { 4 | "p": { 5 | "existingJavaType": "java.lang.Double", 6 | "javaName": "price", 7 | "title": "The price." 8 | }, 9 | "s": { 10 | "existingJavaType": "java.lang.Double", 11 | "javaName": "size", 12 | "title": "The size." 13 | } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/streams/news/model/news_market_data_message_type.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "string", 3 | "enum": [ 4 | "success", 5 | "error", 6 | "subscription", 7 | "n" 8 | ], 9 | "javaEnums": [ 10 | { 11 | "name": "SUCCESS" 12 | }, 13 | { 14 | "name": "ERROR" 15 | }, 16 | { 17 | "name": "SUBSCRIPTION" 18 | }, 19 | { 20 | "name": "NEWS" 21 | } 22 | ] 23 | } 24 | -------------------------------------------------------------------------------- /src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/streams/news/model/control/news_subscriptions_message.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "object", 3 | "extends": { 4 | "existingJavaType": "net.jacobpeterson.alpaca.model.websocket.marketdata.streams.news.model.NewsMarketDataMessage" 5 | }, 6 | "properties": { 7 | "news": { 8 | "existingJavaType": "java.util.Set", 9 | "title": "The {@link java.util.Set} of symbols subscribed to news." 10 | } 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/model/control/success_message.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "object", 3 | "extends": { 4 | "existingJavaType": "net.jacobpeterson.alpaca.model.websocket.marketdata.model.MarketDataMessage" 5 | }, 6 | "properties": { 7 | "msg": { 8 | "existingJavaType": "net.jacobpeterson.alpaca.model.websocket.marketdata.model.control.SuccessMessageType", 9 | "javaName": "messageType", 10 | "title": "The success message type." 11 | } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/main/java/net/jacobpeterson/alpaca/websocket/updates/model/listening/listening_message.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "object", 3 | "extends": { 4 | "existingJavaType": "net.jacobpeterson.alpaca.model.websocket.updates.model.UpdatesMessage" 5 | }, 6 | "properties": { 7 | "data": { 8 | "existingJavaType": "net.jacobpeterson.alpaca.model.websocket.updates.model.listening.ListeningData", 9 | "title": "The {@link net.jacobpeterson.alpaca.model.websocket.updates.model.listening.ListeningData}." 10 | } 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/main/java/net/jacobpeterson/alpaca/websocket/updates/model/tradeupdate/trade_update_message.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "object", 3 | "extends": { 4 | "existingJavaType": "net.jacobpeterson.alpaca.model.websocket.updates.model.UpdatesMessage" 5 | }, 6 | "properties": { 7 | "data": { 8 | "existingJavaType": "net.jacobpeterson.alpaca.model.websocket.updates.model.tradeupdate.TradeUpdate", 9 | "title": "The {@link net.jacobpeterson.alpaca.model.websocket.updates.model.tradeupdate.TradeUpdate}." 10 | } 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/streams/news/NewsMarketDataListenerAdapter.java: -------------------------------------------------------------------------------- 1 | package net.jacobpeterson.alpaca.websocket.marketdata.streams.news; 2 | 3 | import net.jacobpeterson.alpaca.model.websocket.marketdata.streams.news.model.news.NewsMessage; 4 | 5 | /** 6 | * {@link NewsMarketDataListenerAdapter} is an adapter for {@link NewsMarketDataListener}. 7 | */ 8 | public class NewsMarketDataListenerAdapter implements NewsMarketDataListener { 9 | 10 | @Override 11 | public void onNews(NewsMessage news) {} 12 | } 13 | -------------------------------------------------------------------------------- /src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/model/control/error_message.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "object", 3 | "extends": { 4 | "existingJavaType": "net.jacobpeterson.alpaca.model.websocket.marketdata.model.MarketDataMessage" 5 | }, 6 | "properties": { 7 | "code": { 8 | "existingJavaType": "java.lang.Integer", 9 | "title": "The error code." 10 | }, 11 | "msg": { 12 | "existingJavaType": "java.lang.String", 13 | "javaName": "message", 14 | "title": "The error message." 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/main/java/net/jacobpeterson/alpaca/websocket/updates/model/authorization/authorization_message.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "object", 3 | "extends": { 4 | "existingJavaType": "net.jacobpeterson.alpaca.model.websocket.updates.model.UpdatesMessage" 5 | }, 6 | "properties": { 7 | "data": { 8 | "existingJavaType": "net.jacobpeterson.alpaca.model.websocket.updates.model.authorization.AuthorizationData", 9 | "title": "The {@link net.jacobpeterson.alpaca.model.websocket.updates.model.authorization.AuthorizationData}." 10 | } 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/streams/news/model/news_market_data_message.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "object", 3 | "extends": { 4 | "existingJavaType": "net.jacobpeterson.alpaca.model.websocket.marketdata.model.MarketDataMessage" 5 | }, 6 | "properties": { 7 | "T": { 8 | "existingJavaType": "net.jacobpeterson.alpaca.model.websocket.marketdata.streams.news.model.NewsMarketDataMessageType", 9 | "javaName": "messageType", 10 | "title": "The {@link net.jacobpeterson.alpaca.model.websocket.marketdata.streams.news.model.NewsMarketDataMessageType}." 11 | } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/streams/stock/model/stock_market_data_message.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "object", 3 | "extends": { 4 | "existingJavaType": "net.jacobpeterson.alpaca.model.websocket.marketdata.model.MarketDataMessage" 5 | }, 6 | "properties": { 7 | "T": { 8 | "existingJavaType": "net.jacobpeterson.alpaca.model.websocket.marketdata.streams.stock.model.StockMarketDataMessageType", 9 | "javaName": "messageType", 10 | "title": "The {@link net.jacobpeterson.alpaca.model.websocket.marketdata.streams.stock.model.StockMarketDataMessageType}." 11 | } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/streams/crypto/model/crypto_market_data_message.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "object", 3 | "extends": { 4 | "existingJavaType": "net.jacobpeterson.alpaca.model.websocket.marketdata.model.MarketDataMessage" 5 | }, 6 | "properties": { 7 | "T": { 8 | "existingJavaType": "net.jacobpeterson.alpaca.model.websocket.marketdata.streams.crypto.model.CryptoMarketDataMessageType", 9 | "javaName": "messageType", 10 | "title": "The {@link net.jacobpeterson.alpaca.model.websocket.marketdata.streams.crypto.model.CryptoMarketDataMessageType}." 11 | } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/main/java/net/jacobpeterson/alpaca/websocket/updates/UpdatesListener.java: -------------------------------------------------------------------------------- 1 | package net.jacobpeterson.alpaca.websocket.updates; 2 | 3 | import net.jacobpeterson.alpaca.model.websocket.updates.model.tradeupdate.TradeUpdateMessage; 4 | 5 | /** 6 | * {@link UpdatesListener} defines a listener interface for {@link UpdatesWebsocketInterface} messages. 7 | */ 8 | @FunctionalInterface 9 | public interface UpdatesListener { 10 | 11 | /** 12 | * Called when a {@link TradeUpdateMessage} is received. 13 | * 14 | * @param tradeUpdate the {@link TradeUpdateMessage} 15 | */ 16 | void onTradeUpdate(TradeUpdateMessage tradeUpdate); 17 | } 18 | -------------------------------------------------------------------------------- /src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/streams/news/NewsMarketDataListener.java: -------------------------------------------------------------------------------- 1 | package net.jacobpeterson.alpaca.websocket.marketdata.streams.news; 2 | 3 | import net.jacobpeterson.alpaca.model.websocket.marketdata.streams.news.model.news.NewsMessage; 4 | 5 | /** 6 | * {@link NewsMarketDataListener} defines a listener interface for {@link NewsMarketDataWebsocketInterface} messages. 7 | */ 8 | @FunctionalInterface 9 | public interface NewsMarketDataListener { 10 | 11 | /** 12 | * Called when a {@link NewsMessage} is received. 13 | * 14 | * @param news the {@link NewsMessage} 15 | */ 16 | void onNews(NewsMessage news); 17 | } 18 | -------------------------------------------------------------------------------- /src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/streams/crypto/model/crypto_market_data_message_type.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "string", 3 | "enum": [ 4 | "success", 5 | "error", 6 | "subscription", 7 | "t", 8 | "q", 9 | "b", 10 | "d", 11 | "u", 12 | "o" 13 | ], 14 | "javaEnums": [ 15 | { 16 | "name": "SUCCESS" 17 | }, 18 | { 19 | "name": "ERROR" 20 | }, 21 | { 22 | "name": "SUBSCRIPTION" 23 | }, 24 | { 25 | "name": "TRADES" 26 | }, 27 | { 28 | "name": "QUOTES" 29 | }, 30 | { 31 | "name": "MINUTE_BARS" 32 | }, 33 | { 34 | "name": "DAILY_BARS" 35 | }, 36 | { 37 | "name": "UPDATED_BARS" 38 | }, 39 | { 40 | "name": "ORDER_BOOKS" 41 | } 42 | ] 43 | } 44 | -------------------------------------------------------------------------------- /src/main/java/net/jacobpeterson/alpaca/util/apikey/APIKeyUtil.java: -------------------------------------------------------------------------------- 1 | package net.jacobpeterson.alpaca.util.apikey; 2 | 3 | import java.util.Base64; 4 | 5 | /** 6 | * {@link APIKeyUtil} is a utility class for Alpaca API keys. 7 | */ 8 | public class APIKeyUtil { 9 | 10 | /** 11 | * Creates a broker API auth key. 12 | * 13 | * @param brokerAPIKey the broker API key 14 | * @param brokerAPISecret the broker API secret 15 | * 16 | * @return the key {@link String} 17 | * 18 | * @see Alpaca Docs 19 | */ 20 | public static String createBrokerAPIAuthKey(String brokerAPIKey, String brokerAPISecret) { 21 | return Base64.getEncoder().encodeToString((brokerAPIKey + ":" + brokerAPISecret).getBytes()); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/main/java/net/jacobpeterson/alpaca/util/sse/SSERequest.java: -------------------------------------------------------------------------------- 1 | package net.jacobpeterson.alpaca.util.sse; 2 | 3 | import okhttp3.sse.EventSource; 4 | 5 | /** 6 | * {@link SSERequest} is a {@link EventSource} wrapper class. 7 | */ 8 | public class SSERequest { 9 | 10 | private final EventSource eventSource; 11 | 12 | /** 13 | * Instantiates a new {@link SSERequest}. 14 | * 15 | * @param eventSource the {@link EventSource} 16 | */ 17 | public SSERequest(EventSource eventSource) { 18 | this.eventSource = eventSource; 19 | } 20 | 21 | /** 22 | * Closes the internal {@link EventSource}. 23 | */ 24 | public void close() { 25 | eventSource.cancel(); 26 | } 27 | 28 | /** 29 | * Gets the internal {@link EventSource}. 30 | * 31 | * @return the {@link EventSource} 32 | */ 33 | public EventSource getInternalEventSource() { 34 | return eventSource; 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/main/java/net/jacobpeterson/alpaca/websocket/updates/UpdatesWebsocketInterface.java: -------------------------------------------------------------------------------- 1 | package net.jacobpeterson.alpaca.websocket.updates; 2 | 3 | import net.jacobpeterson.alpaca.model.websocket.updates.model.UpdatesMessageType; 4 | import net.jacobpeterson.alpaca.websocket.AlpacaWebsocketInterface; 5 | 6 | /** 7 | * {@link UpdatesWebsocketInterface} is an {@link AlpacaWebsocketInterface} for {@link UpdatesWebsocket}. 8 | */ 9 | public interface UpdatesWebsocketInterface extends AlpacaWebsocketInterface { 10 | 11 | /** 12 | * Sets the {@link UpdatesListener}. 13 | * 14 | * @param listener the {@link UpdatesListener} 15 | */ 16 | void setListener(UpdatesListener listener); 17 | 18 | /** 19 | * Subscribes to {@link UpdatesMessageType#TRADE_UPDATES}. 20 | * 21 | * @param subscribe true to subscribe, false otherwise 22 | */ 23 | void subscribeToTradeUpdates(boolean subscribe); 24 | } 25 | -------------------------------------------------------------------------------- /src/main/java/net/jacobpeterson/alpaca/util/sse/SSEListener.java: -------------------------------------------------------------------------------- 1 | package net.jacobpeterson.alpaca.util.sse; 2 | 3 | import okhttp3.Response; 4 | import org.jetbrains.annotations.NotNull; 5 | import org.jetbrains.annotations.Nullable; 6 | 7 | /** 8 | * {@link SSEListener} is a listener interface for server-sent events (SSE). 9 | * 10 | * @param the data type 11 | */ 12 | public interface SSEListener { 13 | 14 | /** 15 | * Called on SSE open. 16 | */ 17 | void onOpen(); 18 | 19 | /** 20 | * Called on SSE close. 21 | */ 22 | void onClose(); 23 | 24 | /** 25 | * Called on SSE error. 26 | * 27 | * @param throwable the {@link Throwable} 28 | * @param response the {@link Response} 29 | */ 30 | void onError(@Nullable Throwable throwable, @Nullable Response response); 31 | 32 | /** 33 | * Called on SSE message received. 34 | * 35 | * @param message the {@link T} message 36 | */ 37 | void onMessage(@NotNull T message); 38 | } 39 | -------------------------------------------------------------------------------- /src/main/java/net/jacobpeterson/alpaca/websocket/AlpacaWebsocketStateListener.java: -------------------------------------------------------------------------------- 1 | package net.jacobpeterson.alpaca.websocket; 2 | 3 | import okhttp3.Response; 4 | import okhttp3.WebSocket; 5 | 6 | /** 7 | * {@link AlpacaWebsocketStateListener} is an interface to listen for various state changes in an {@link WebSocket} 8 | * instance. 9 | */ 10 | public interface AlpacaWebsocketStateListener { 11 | 12 | /** 13 | * Called when the {@link WebSocket} is connected. 14 | * 15 | * @param response the HTTP websocket upgrade {@link Response} 16 | */ 17 | void onOpen(Response response); 18 | 19 | /** 20 | * Called when the {@link WebSocket} is disconnected. 21 | * 22 | * @param code the code 23 | * @param reason the reason 24 | */ 25 | void onClosed(int code, String reason); 26 | 27 | /** 28 | * Called when a {@link WebSocket} error has occurred. 29 | * 30 | * @param cause the cause {@link Throwable} 31 | */ 32 | void onFailure(Throwable cause); 33 | } 34 | -------------------------------------------------------------------------------- /src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/streams/stock/model/stock_market_data_message_type.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "string", 3 | "enum": [ 4 | "success", 5 | "error", 6 | "subscription", 7 | "t", 8 | "q", 9 | "b", 10 | "d", 11 | "u", 12 | "c", 13 | "x", 14 | "l", 15 | "s" 16 | ], 17 | "javaEnums": [ 18 | { 19 | "name": "SUCCESS" 20 | }, 21 | { 22 | "name": "ERROR" 23 | }, 24 | { 25 | "name": "SUBSCRIPTION" 26 | }, 27 | { 28 | "name": "TRADES" 29 | }, 30 | { 31 | "name": "QUOTES" 32 | }, 33 | { 34 | "name": "MINUTE_BARS" 35 | }, 36 | { 37 | "name": "DAILY_BARS" 38 | }, 39 | { 40 | "name": "UPDATED_BARS" 41 | }, 42 | { 43 | "name": "TRADE_CORRECTIONS" 44 | }, 45 | { 46 | "name": "TRADE_CANCEL_ERRORS" 47 | }, 48 | { 49 | "name": "LIMIT_UP_LIMIT_DOWN_BANDS" 50 | }, 51 | { 52 | "name": "TRADING_STATUSES" 53 | } 54 | ] 55 | } 56 | -------------------------------------------------------------------------------- /src/main/java/net/jacobpeterson/alpaca/util/sse/SSEListenerAdapter.java: -------------------------------------------------------------------------------- 1 | package net.jacobpeterson.alpaca.util.sse; 2 | 3 | import okhttp3.Response; 4 | import org.jetbrains.annotations.NotNull; 5 | import org.jetbrains.annotations.Nullable; 6 | import org.slf4j.Logger; 7 | import org.slf4j.LoggerFactory; 8 | 9 | /** 10 | * {@link SSEListenerAdapter} and adapter for {@link SSEListener}. 11 | * 12 | * @param the data type 13 | */ 14 | public class SSEListenerAdapter implements SSEListener { 15 | 16 | private static final Logger LOGGER = LoggerFactory.getLogger(SSEListenerAdapter.class); 17 | 18 | public void onOpen() { 19 | LOGGER.info("SSE connection opened."); 20 | } 21 | 22 | public void onClose() { 23 | LOGGER.info("SSE connection closed."); 24 | } 25 | 26 | public void onError(@Nullable Throwable throwable, @Nullable Response response) { 27 | LOGGER.error("SSE connection error! response={}", response, throwable); 28 | } 29 | 30 | public void onMessage(@NotNull T message) { 31 | LOGGER.info("SSE message received: message={}", message); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) Jacob Peterson 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/streams/news/model/news/news_message.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "object", 3 | "extends": { 4 | "existingJavaType": "net.jacobpeterson.alpaca.model.websocket.marketdata.streams.news.model.NewsMarketDataMessage" 5 | }, 6 | "properties": { 7 | "id": { 8 | "existingJavaType": "java.lang.Long" 9 | }, 10 | "source": { 11 | "existingJavaType": "java.lang.String" 12 | }, 13 | "headline": { 14 | "existingJavaType": "java.lang.String" 15 | }, 16 | "author": { 17 | "existingJavaType": "java.lang.String" 18 | }, 19 | "summary": { 20 | "existingJavaType": "java.lang.String" 21 | }, 22 | "content": { 23 | "existingJavaType": "java.lang.String" 24 | }, 25 | "url": { 26 | "existingJavaType": "java.lang.String" 27 | }, 28 | "symbols": { 29 | "existingJavaType": "java.util.Set" 30 | }, 31 | "created_at": { 32 | "existingJavaType": "java.time.OffsetDateTime" 33 | }, 34 | "updated_at": { 35 | "existingJavaType": "java.time.OffsetDateTime" 36 | } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/streams/crypto/model/quote/crypto_quote_message.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "object", 3 | "extends": { 4 | "existingJavaType": "net.jacobpeterson.alpaca.model.websocket.marketdata.streams.crypto.model.CryptoMarketDataMessage" 5 | }, 6 | "properties": { 7 | "S": { 8 | "existingJavaType": "java.lang.String", 9 | "javaName": "symbol", 10 | "title": "The symbol." 11 | }, 12 | "ap": { 13 | "existingJavaType": "java.lang.Double", 14 | "javaName": "askPrice", 15 | "title": "The ask price." 16 | }, 17 | "as": { 18 | "existingJavaType": "java.lang.Double", 19 | "javaName": "askSize", 20 | "title": "The ask size." 21 | }, 22 | "bp": { 23 | "existingJavaType": "java.lang.Double", 24 | "javaName": "bidPrice", 25 | "title": "The bid price." 26 | }, 27 | "bs": { 28 | "existingJavaType": "java.lang.Double", 29 | "javaName": "bidSize", 30 | "title": "The bid size." 31 | }, 32 | "t": { 33 | "existingJavaType": "java.time.OffsetDateTime", 34 | "javaName": "timestamp", 35 | "title": "The timestamp with nanosecond precision." 36 | } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/main/java/net/jacobpeterson/alpaca/websocket/updates/model/tradeupdate/trade_update.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "object", 3 | "properties": { 4 | "event": { 5 | "existingJavaType": "net.jacobpeterson.alpaca.model.websocket.updates.model.tradeupdate.TradeUpdateEvent", 6 | "title": "The {@link net.jacobpeterson.alpaca.model.websocket.updates.model.tradeupdate.TradeUpdateEvent}." 7 | }, 8 | "execution_id": { 9 | "existingJavaType": "java.lang.String", 10 | "title": "The execution ID." 11 | }, 12 | "price": { 13 | "existingJavaType": "java.lang.String", 14 | "title": "The price." 15 | }, 16 | "position_qty": { 17 | "existingJavaType": "java.lang.String", 18 | "javaName": "positionQuantity", 19 | "title": "The position quantity." 20 | }, 21 | "qty": { 22 | "existingJavaType": "java.lang.String", 23 | "javaName": "quantity", 24 | "title": "The quantity." 25 | }, 26 | "timestamp": { 27 | "existingJavaType": "java.time.OffsetDateTime", 28 | "title": "The timestamp." 29 | }, 30 | "order": { 31 | "existingJavaType": "net.jacobpeterson.alpaca.openapi.trader.model.Order", 32 | "title": "The {@link net.jacobpeterson.alpaca.openapi.trader.model.Order}." 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/streams/crypto/model/trade/crypto_trade_message.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "object", 3 | "extends": { 4 | "existingJavaType": "net.jacobpeterson.alpaca.model.websocket.marketdata.streams.crypto.model.CryptoMarketDataMessage" 5 | }, 6 | "properties": { 7 | "S": { 8 | "existingJavaType": "java.lang.String", 9 | "javaName": "symbol", 10 | "title": "The symbol." 11 | }, 12 | "i": { 13 | "existingJavaType": "java.lang.Long", 14 | "javaName": "tradeID", 15 | "title": "The trade ID." 16 | }, 17 | "p": { 18 | "existingJavaType": "java.lang.Double", 19 | "javaName": "price", 20 | "title": "The trade price." 21 | }, 22 | "s": { 23 | "existingJavaType": "java.lang.Double", 24 | "javaName": "size", 25 | "title": "The trade size." 26 | }, 27 | "t": { 28 | "existingJavaType": "java.time.OffsetDateTime", 29 | "javaName": "timestamp", 30 | "title": "The timestamp with nanosecond precision." 31 | }, 32 | "tks": { 33 | "existingJavaType": "net.jacobpeterson.alpaca.model.websocket.marketdata.streams.crypto.model.trade.CryptoTradeTakerSide", 34 | "javaName": "takerSide", 35 | "title": "The taker side." 36 | } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/streams/stock/model/limituplimitdownband/stock_limit_up_limit_down_band_message.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "object", 3 | "extends": { 4 | "existingJavaType": "net.jacobpeterson.alpaca.model.websocket.marketdata.streams.stock.model.StockMarketDataMessage" 5 | }, 6 | "properties": { 7 | "S": { 8 | "existingJavaType": "java.lang.String", 9 | "javaName": "symbol", 10 | "title": "The symbol." 11 | }, 12 | "u": { 13 | "existingJavaType": "java.lang.Double", 14 | "javaName": "limitUpPrice", 15 | "title": "The upper limit price band of a security." 16 | }, 17 | "d": { 18 | "existingJavaType": "java.lang.Double", 19 | "javaName": "limitDownPrice", 20 | "title": "The lower limit price band of a security." 21 | }, 22 | "i": { 23 | "existingJavaType": "java.lang.String", 24 | "javaName": "indicator", 25 | "title": "The indicator." 26 | }, 27 | "t": { 28 | "existingJavaType": "java.time.OffsetDateTime", 29 | "javaName": "timestamp", 30 | "title": "The timestamp with nanosecond precision." 31 | }, 32 | "z": { 33 | "existingJavaType": "java.lang.String", 34 | "javaName": "tape", 35 | "title": "The tape." 36 | } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/streams/crypto/CryptoMarketDataListenerAdapter.java: -------------------------------------------------------------------------------- 1 | package net.jacobpeterson.alpaca.websocket.marketdata.streams.crypto; 2 | 3 | import net.jacobpeterson.alpaca.model.websocket.marketdata.streams.crypto.model.bar.CryptoBarMessage; 4 | import net.jacobpeterson.alpaca.model.websocket.marketdata.streams.crypto.model.orderbook.CryptoOrderBookMessage; 5 | import net.jacobpeterson.alpaca.model.websocket.marketdata.streams.crypto.model.quote.CryptoQuoteMessage; 6 | import net.jacobpeterson.alpaca.model.websocket.marketdata.streams.crypto.model.trade.CryptoTradeMessage; 7 | 8 | /** 9 | * {@link CryptoMarketDataListenerAdapter} is an adapter for {@link CryptoMarketDataListener}. 10 | */ 11 | public class CryptoMarketDataListenerAdapter implements CryptoMarketDataListener { 12 | 13 | @Override 14 | public void onTrade(CryptoTradeMessage trade) {} 15 | 16 | @Override 17 | public void onQuote(CryptoQuoteMessage quote) {} 18 | 19 | @Override 20 | public void onMinuteBar(CryptoBarMessage bar) {} 21 | 22 | @Override 23 | public void onDailyBar(CryptoBarMessage bar) {} 24 | 25 | @Override 26 | public void onUpdatedBar(CryptoBarMessage bar) {} 27 | 28 | @Override 29 | public void onOrderBook(CryptoOrderBookMessage orderBook) {} 30 | } 31 | -------------------------------------------------------------------------------- /src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/streams/stock/model/tradingstatus/stock_trading_status_message.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "object", 3 | "extends": { 4 | "existingJavaType": "net.jacobpeterson.alpaca.model.websocket.marketdata.streams.stock.model.StockMarketDataMessage" 5 | }, 6 | "properties": { 7 | "S": { 8 | "existingJavaType": "java.lang.String", 9 | "javaName": "symbol", 10 | "title": "The symbol." 11 | }, 12 | "sc": { 13 | "existingJavaType": "java.lang.String", 14 | "javaName": "statusCode", 15 | "title": "The status code." 16 | }, 17 | "sm": { 18 | "existingJavaType": "java.lang.String", 19 | "javaName": "statusMessage", 20 | "title": "The status message." 21 | }, 22 | "rc": { 23 | "existingJavaType": "java.lang.String", 24 | "javaName": "reasonCode", 25 | "title": "The reason code." 26 | }, 27 | "rm": { 28 | "existingJavaType": "java.lang.String", 29 | "javaName": "reasonMessage", 30 | "title": "The reason message." 31 | }, 32 | "t": { 33 | "existingJavaType": "java.time.OffsetDateTime", 34 | "javaName": "timestamp", 35 | "title": "The timestamp with nanosecond precision." 36 | }, 37 | "z": { 38 | "existingJavaType": "java.lang.String", 39 | "javaName": "tape", 40 | "title": "The tape." 41 | } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/streams/crypto/model/control/crypto_subscriptions_message.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "object", 3 | "extends": { 4 | "existingJavaType": "net.jacobpeterson.alpaca.model.websocket.marketdata.streams.crypto.model.CryptoMarketDataMessage" 5 | }, 6 | "properties": { 7 | "trades": { 8 | "existingJavaType": "java.util.Set", 9 | "title": "The {@link java.util.Set} of symbols subscribed to trades." 10 | }, 11 | "quotes": { 12 | "existingJavaType": "java.util.Set", 13 | "title": "The {@link java.util.Set} of symbols subscribed to quotes." 14 | }, 15 | "bars": { 16 | "existingJavaType": "java.util.Set", 17 | "javaName": "minuteBars", 18 | "title": "The {@link java.util.Set} of symbols subscribed to minute bars." 19 | }, 20 | "dailyBars": { 21 | "existingJavaType": "java.util.Set", 22 | "title": "The {@link java.util.Set} of symbols subscribed to daily bars." 23 | }, 24 | "updatedBars": { 25 | "existingJavaType": "java.util.Set", 26 | "title": "The {@link java.util.Set} of symbols subscribed to updated bars." 27 | }, 28 | "orderbooks": { 29 | "existingJavaType": "java.util.Set", 30 | "javaName": "orderBooks", 31 | "title": "The {@link java.util.Set} of symbols subscribed to order books." 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/streams/stock/model/trade/stock_trade_message.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "object", 3 | "extends": { 4 | "existingJavaType": "net.jacobpeterson.alpaca.model.websocket.marketdata.streams.stock.model.StockMarketDataMessage" 5 | }, 6 | "properties": { 7 | "S": { 8 | "existingJavaType": "java.lang.String", 9 | "javaName": "symbol", 10 | "title": "The symbol." 11 | }, 12 | "i": { 13 | "existingJavaType": "java.lang.Long", 14 | "javaName": "tradeID", 15 | "title": "The trade ID." 16 | }, 17 | "x": { 18 | "existingJavaType": "java.lang.String", 19 | "javaName": "exchange", 20 | "title": "The exchange code where the trade occurred." 21 | }, 22 | "p": { 23 | "existingJavaType": "java.lang.Double", 24 | "javaName": "price", 25 | "title": "The trade price." 26 | }, 27 | "s": { 28 | "existingJavaType": "java.lang.Integer", 29 | "javaName": "size", 30 | "title": "The trade size." 31 | }, 32 | "c": { 33 | "existingJavaType": "java.util.Set", 34 | "javaName": "conditions", 35 | "title": "The {@link java.util.Set} of trade conditions." 36 | }, 37 | "t": { 38 | "existingJavaType": "java.time.OffsetDateTime", 39 | "javaName": "timestamp", 40 | "title": "The timestamp with nanosecond precision." 41 | }, 42 | "z": { 43 | "existingJavaType": "java.lang.String", 44 | "javaName": "tape", 45 | "title": "The tape." 46 | } 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/streams/news/NewsMarketDataWebsocketInterface.java: -------------------------------------------------------------------------------- 1 | package net.jacobpeterson.alpaca.websocket.marketdata.streams.news; 2 | 3 | import net.jacobpeterson.alpaca.model.websocket.marketdata.streams.news.model.NewsMarketDataMessageType; 4 | import net.jacobpeterson.alpaca.websocket.marketdata.MarketDataWebsocketInterface; 5 | 6 | import java.util.Set; 7 | 8 | /** 9 | * {@link NewsMarketDataWebsocketInterface} is a {@link MarketDataWebsocketInterface} for 10 | * {@link NewsMarketDataWebsocket}. 11 | */ 12 | public interface NewsMarketDataWebsocketInterface extends MarketDataWebsocketInterface { 13 | 14 | /** 15 | * Sets the {@link NewsMarketDataListener}. 16 | * 17 | * @param listener the {@link NewsMarketDataListener} 18 | */ 19 | void setListener(NewsMarketDataListener listener); 20 | 21 | /** 22 | * Subscribes the given symbols to {@link NewsMarketDataMessageType#NEWS}. This will remove all 23 | * previous {@link NewsMarketDataMessageType#NEWS} subscriptions. 24 | * 25 | * @param symbols the {@link Set} of symbols (use an asterisk ("*") as a wildcard to subscribe to all available 26 | * symbols) or null to unsubscribe from all symbols 27 | */ 28 | void setNewsSubscriptions(Set symbols); 29 | 30 | /** 31 | * Gets the current symbols subscribed to {@link NewsMarketDataMessageType#NEWS}. 32 | * 33 | * @return a {@link Set} of {@link String} symbols 34 | */ 35 | Set getNewsSubscriptions(); 36 | } 37 | -------------------------------------------------------------------------------- /src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/streams/stock/model/bar/stock_bar_message.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "object", 3 | "extends": { 4 | "existingJavaType": "net.jacobpeterson.alpaca.model.websocket.marketdata.streams.stock.model.StockMarketDataMessage" 5 | }, 6 | "properties": { 7 | "S": { 8 | "existingJavaType": "java.lang.String", 9 | "javaName": "symbol", 10 | "title": "The symbol." 11 | }, 12 | "o": { 13 | "existingJavaType": "java.lang.Double", 14 | "javaName": "open", 15 | "title": "The open price." 16 | }, 17 | "h": { 18 | "existingJavaType": "java.lang.Double", 19 | "javaName": "high", 20 | "title": "The high price." 21 | }, 22 | "l": { 23 | "existingJavaType": "java.lang.Double", 24 | "javaName": "low", 25 | "title": "The low price." 26 | }, 27 | "c": { 28 | "existingJavaType": "java.lang.Double", 29 | "javaName": "close", 30 | "title": "The close price." 31 | }, 32 | "t": { 33 | "existingJavaType": "java.time.OffsetDateTime", 34 | "javaName": "timestamp", 35 | "title": "The timestamp." 36 | }, 37 | "v": { 38 | "existingJavaType": "java.lang.Long", 39 | "javaName": "volume", 40 | "title": "The volume." 41 | }, 42 | "n": { 43 | "existingJavaType": "java.lang.Long", 44 | "javaName": "tradeCount", 45 | "title": "The trade count." 46 | }, 47 | "vw": { 48 | "existingJavaType": "java.lang.Double", 49 | "javaName": "vwap", 50 | "title": "The VWAP (Volume Weighted Average Price)." 51 | } 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/streams/crypto/model/bar/crypto_bar_message.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "object", 3 | "extends": { 4 | "existingJavaType": "net.jacobpeterson.alpaca.model.websocket.marketdata.streams.crypto.model.CryptoMarketDataMessage" 5 | }, 6 | "properties": { 7 | "S": { 8 | "existingJavaType": "java.lang.String", 9 | "javaName": "symbol", 10 | "title": "The symbol." 11 | }, 12 | "o": { 13 | "existingJavaType": "java.lang.Double", 14 | "javaName": "open", 15 | "title": "The open price." 16 | }, 17 | "h": { 18 | "existingJavaType": "java.lang.Double", 19 | "javaName": "high", 20 | "title": "The high price." 21 | }, 22 | "l": { 23 | "existingJavaType": "java.lang.Double", 24 | "javaName": "low", 25 | "title": "The low price." 26 | }, 27 | "c": { 28 | "existingJavaType": "java.lang.Double", 29 | "javaName": "close", 30 | "title": "The close price." 31 | }, 32 | "t": { 33 | "existingJavaType": "java.time.OffsetDateTime", 34 | "javaName": "timestamp", 35 | "title": "The timestamp." 36 | }, 37 | "v": { 38 | "existingJavaType": "java.lang.Long", 39 | "javaName": "volume", 40 | "title": "The volume." 41 | }, 42 | "n": { 43 | "existingJavaType": "java.lang.Long", 44 | "javaName": "tradeCount", 45 | "title": "The trade count." 46 | }, 47 | "vw": { 48 | "existingJavaType": "java.lang.Double", 49 | "javaName": "vwap", 50 | "title": "The VWAP (Volume Weighted Average Price)." 51 | } 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/streams/stock/model/tradecancelerror/stock_trade_cancel_error_message.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "object", 3 | "extends": { 4 | "existingJavaType": "net.jacobpeterson.alpaca.model.websocket.marketdata.streams.stock.model.StockMarketDataMessage" 5 | }, 6 | "properties": { 7 | "S": { 8 | "existingJavaType": "java.lang.String", 9 | "javaName": "symbol", 10 | "title": "The symbol." 11 | }, 12 | "i": { 13 | "existingJavaType": "java.lang.Long", 14 | "javaName": "tradeID", 15 | "title": "The trade ID." 16 | }, 17 | "x": { 18 | "existingJavaType": "java.lang.String", 19 | "javaName": "exchange", 20 | "title": "The exchange code where the trade cancel/error occurred." 21 | }, 22 | "p": { 23 | "existingJavaType": "java.lang.Double", 24 | "javaName": "price", 25 | "title": "The trade price." 26 | }, 27 | "s": { 28 | "existingJavaType": "java.lang.Integer", 29 | "javaName": "size", 30 | "title": "The trade size." 31 | }, 32 | "a": { 33 | "existingJavaType": "net.jacobpeterson.alpaca.model.websocket.marketdata.streams.stock.model.tradecancelerror.StockTradeCancelErrorAction", 34 | "javaName": "action", 35 | "title": "The trade cancel/error action." 36 | }, 37 | "t": { 38 | "existingJavaType": "java.time.OffsetDateTime", 39 | "javaName": "timestamp", 40 | "title": "The timestamp with nanosecond precision." 41 | }, 42 | "z": { 43 | "existingJavaType": "java.lang.String", 44 | "javaName": "tape", 45 | "title": "The tape." 46 | } 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/streams/stock/model/control/stock_subscriptions_message.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "object", 3 | "extends": { 4 | "existingJavaType": "net.jacobpeterson.alpaca.model.websocket.marketdata.streams.stock.model.StockMarketDataMessage" 5 | }, 6 | "properties": { 7 | "trades": { 8 | "existingJavaType": "java.util.Set", 9 | "title": "The {@link java.util.Set} of symbols subscribed to trades." 10 | }, 11 | "quotes": { 12 | "existingJavaType": "java.util.Set", 13 | "title": "The {@link java.util.Set} of symbols subscribed to quotes." 14 | }, 15 | "bars": { 16 | "existingJavaType": "java.util.Set", 17 | "javaName": "minuteBars", 18 | "title": "The {@link java.util.Set} of symbols subscribed to minute bars." 19 | }, 20 | "dailyBars": { 21 | "existingJavaType": "java.util.Set", 22 | "title": "The {@link java.util.Set} of symbols subscribed to daily bars." 23 | }, 24 | "updatedBars": { 25 | "existingJavaType": "java.util.Set", 26 | "title": "The {@link java.util.Set} of symbols subscribed to updated bars." 27 | }, 28 | "lulds": { 29 | "existingJavaType": "java.util.Set", 30 | "javaName": "limitUpLimitDownBands", 31 | "title": "The {@link java.util.Set} of symbols subscribed to limit up - limit down bands." 32 | }, 33 | "statuses": { 34 | "existingJavaType": "java.util.Set", 35 | "javaName": "tradingStatuses", 36 | "title": "The {@link java.util.Set} of symbols subscribed to trading statuses." 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/streams/crypto/model/orderbook/crypto_order_book_message.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "object", 3 | "extends": { 4 | "existingJavaType": "net.jacobpeterson.alpaca.model.websocket.marketdata.streams.crypto.model.CryptoMarketDataMessage" 5 | }, 6 | "properties": { 7 | "S": { 8 | "existingJavaType": "java.lang.String", 9 | "javaName": "symbol", 10 | "title": "The symbol." 11 | }, 12 | "a": { 13 | "existingJavaType": "java.util.Set", 14 | "javaName": "asks", 15 | "title": "The {@link java.util.Set} of {@link net.jacobpeterson.alpaca.model.websocket.marketdata.streams.crypto.model.orderbook.CryptoOrderBookEntry} asks." 16 | }, 17 | "b": { 18 | "existingJavaType": "java.util.Set", 19 | "javaName": "bids", 20 | "title": "The {@link java.util.Set} of {@link net.jacobpeterson.alpaca.model.websocket.marketdata.streams.crypto.model.orderbook.CryptoOrderBookEntry} bids." 21 | }, 22 | "t": { 23 | "existingJavaType": "java.time.OffsetDateTime", 24 | "javaName": "timestamp", 25 | "title": "The timestamp with nanosecond precision." 26 | }, 27 | "r": { 28 | "existingJavaType": "java.lang.Boolean", 29 | "javaName": "reset", 30 | "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." 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/streams/stock/model/quote/stock_quote_message.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "object", 3 | "extends": { 4 | "existingJavaType": "net.jacobpeterson.alpaca.model.websocket.marketdata.streams.stock.model.StockMarketDataMessage" 5 | }, 6 | "properties": { 7 | "S": { 8 | "existingJavaType": "java.lang.String", 9 | "javaName": "symbol", 10 | "title": "The symbol." 11 | }, 12 | "ax": { 13 | "existingJavaType": "java.lang.String", 14 | "javaName": "askExchange", 15 | "title": "The ask exchange code." 16 | }, 17 | "ap": { 18 | "existingJavaType": "java.lang.Double", 19 | "javaName": "askPrice", 20 | "title": "The ask price." 21 | }, 22 | "as": { 23 | "existingJavaType": "java.lang.Integer", 24 | "javaName": "askSize", 25 | "title": "The ask size." 26 | }, 27 | "bx": { 28 | "existingJavaType": "java.lang.String", 29 | "javaName": "bidExchange", 30 | "title": "The bid exchange code." 31 | }, 32 | "bp": { 33 | "existingJavaType": "java.lang.Double", 34 | "javaName": "bidPrice", 35 | "title": "The bid price." 36 | }, 37 | "bs": { 38 | "existingJavaType": "java.lang.Integer", 39 | "javaName": "bidSize", 40 | "title": "The bid size." 41 | }, 42 | "c": { 43 | "existingJavaType": "java.util.Set", 44 | "javaName": "conditions", 45 | "title": "The {@link java.util.Set} of quote conditions." 46 | }, 47 | "t": { 48 | "existingJavaType": "java.time.OffsetDateTime", 49 | "javaName": "timestamp", 50 | "title": "The timestamp with nanosecond precision." 51 | }, 52 | "z": { 53 | "existingJavaType": "java.lang.String", 54 | "javaName": "tape", 55 | "title": "The tape." 56 | } 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/streams/stock/StockMarketDataListenerAdapter.java: -------------------------------------------------------------------------------- 1 | package net.jacobpeterson.alpaca.websocket.marketdata.streams.stock; 2 | 3 | import net.jacobpeterson.alpaca.model.websocket.marketdata.streams.stock.model.bar.StockBarMessage; 4 | import net.jacobpeterson.alpaca.model.websocket.marketdata.streams.stock.model.limituplimitdownband.StockLimitUpLimitDownBandMessage; 5 | import net.jacobpeterson.alpaca.model.websocket.marketdata.streams.stock.model.quote.StockQuoteMessage; 6 | import net.jacobpeterson.alpaca.model.websocket.marketdata.streams.stock.model.trade.StockTradeMessage; 7 | import net.jacobpeterson.alpaca.model.websocket.marketdata.streams.stock.model.tradecancelerror.StockTradeCancelErrorMessage; 8 | import net.jacobpeterson.alpaca.model.websocket.marketdata.streams.stock.model.tradecorrection.StockTradeCorrectionMessage; 9 | import net.jacobpeterson.alpaca.model.websocket.marketdata.streams.stock.model.tradingstatus.StockTradingStatusMessage; 10 | 11 | /** 12 | * {@link StockMarketDataListenerAdapter} is an adapter for {@link StockMarketDataListener}. 13 | */ 14 | public class StockMarketDataListenerAdapter implements StockMarketDataListener { 15 | 16 | @Override 17 | public void onTrade(StockTradeMessage trade) {} 18 | 19 | @Override 20 | public void onQuote(StockQuoteMessage quote) {} 21 | 22 | @Override 23 | public void onMinuteBar(StockBarMessage bar) {} 24 | 25 | @Override 26 | public void onDailyBar(StockBarMessage bar) {} 27 | 28 | @Override 29 | public void onUpdatedBar(StockBarMessage bar) {} 30 | 31 | @Override 32 | public void onTradeCorrection(StockTradeCorrectionMessage tradeCorrection) {} 33 | 34 | @Override 35 | public void onTradeCancelError(StockTradeCancelErrorMessage tradeCancelError) {} 36 | 37 | @Override 38 | public void onLimitUpLimitDownBand(StockLimitUpLimitDownBandMessage limitUpLimitDownBand) {} 39 | 40 | @Override 41 | public void onTradingStatus(StockTradingStatusMessage tradingStatus) {} 42 | } 43 | -------------------------------------------------------------------------------- /src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/streams/crypto/CryptoMarketDataListener.java: -------------------------------------------------------------------------------- 1 | package net.jacobpeterson.alpaca.websocket.marketdata.streams.crypto; 2 | 3 | import net.jacobpeterson.alpaca.model.websocket.marketdata.streams.crypto.model.bar.CryptoBarMessage; 4 | import net.jacobpeterson.alpaca.model.websocket.marketdata.streams.crypto.model.orderbook.CryptoOrderBookMessage; 5 | import net.jacobpeterson.alpaca.model.websocket.marketdata.streams.crypto.model.quote.CryptoQuoteMessage; 6 | import net.jacobpeterson.alpaca.model.websocket.marketdata.streams.crypto.model.trade.CryptoTradeMessage; 7 | 8 | /** 9 | * {@link CryptoMarketDataListener} defines a listener interface for {@link CryptoMarketDataWebsocketInterface} 10 | * messages. 11 | */ 12 | public interface CryptoMarketDataListener { 13 | 14 | /** 15 | * Called when a {@link CryptoTradeMessage} is received. 16 | * 17 | * @param trade the {@link CryptoTradeMessage} 18 | */ 19 | void onTrade(CryptoTradeMessage trade); 20 | 21 | /** 22 | * Called when a {@link CryptoQuoteMessage} is received. 23 | * 24 | * @param quote the {@link CryptoQuoteMessage} 25 | */ 26 | void onQuote(CryptoQuoteMessage quote); 27 | 28 | /** 29 | * Called when a {@link CryptoBarMessage} is received. 30 | * 31 | * @param bar the {@link CryptoBarMessage} 32 | */ 33 | void onMinuteBar(CryptoBarMessage bar); 34 | 35 | /** 36 | * Called when a {@link CryptoBarMessage} is received. 37 | * 38 | * @param bar the {@link CryptoBarMessage} 39 | */ 40 | void onDailyBar(CryptoBarMessage bar); 41 | 42 | /** 43 | * Called when a {@link CryptoBarMessage} is received. 44 | * 45 | * @param bar the {@link CryptoBarMessage} 46 | */ 47 | void onUpdatedBar(CryptoBarMessage bar); 48 | 49 | /** 50 | * Called when a {@link CryptoOrderBookMessage} is received. 51 | * 52 | * @param orderBook the {@link CryptoOrderBookMessage} 53 | */ 54 | void onOrderBook(CryptoOrderBookMessage orderBook); 55 | } 56 | -------------------------------------------------------------------------------- /src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/streams/stock/model/tradecorrection/stock_trade_correction_message.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "object", 3 | "extends": { 4 | "existingJavaType": "net.jacobpeterson.alpaca.model.websocket.marketdata.streams.stock.model.StockMarketDataMessage" 5 | }, 6 | "properties": { 7 | "S": { 8 | "existingJavaType": "java.lang.String", 9 | "javaName": "symbol", 10 | "title": "The symbol." 11 | }, 12 | "x": { 13 | "existingJavaType": "java.lang.String", 14 | "javaName": "exchange", 15 | "title": "The exchange code where the trade correction occurred." 16 | }, 17 | "oi": { 18 | "existingJavaType": "java.lang.Integer", 19 | "javaName": "originalTradeID", 20 | "title": "The original trade ID." 21 | }, 22 | "op": { 23 | "existingJavaType": "java.lang.Double", 24 | "javaName": "originalPrice", 25 | "title": "The original trade price." 26 | }, 27 | "os": { 28 | "existingJavaType": "java.lang.Integer", 29 | "javaName": "originalSize", 30 | "title": "The original trade size." 31 | }, 32 | "oc": { 33 | "existingJavaType": "java.util.Set", 34 | "javaName": "originalConditions", 35 | "title": "The {@link java.util.Set} of original trade conditions." 36 | }, 37 | "ci": { 38 | "existingJavaType": "java.lang.Integer", 39 | "javaName": "correctedTradeID", 40 | "title": "The corrected trade ID." 41 | }, 42 | "cp": { 43 | "existingJavaType": "java.lang.Double", 44 | "javaName": "correctedPrice", 45 | "title": "The corrected trade price." 46 | }, 47 | "cs": { 48 | "existingJavaType": "java.lang.Integer", 49 | "javaName": "correctedSize", 50 | "title": "The corrected trade size." 51 | }, 52 | "cc": { 53 | "existingJavaType": "java.util.Set", 54 | "javaName": "correctedConditions", 55 | "title": "The {@link java.util.Set} of original trade conditions." 56 | }, 57 | "t": { 58 | "existingJavaType": "java.time.OffsetDateTime", 59 | "javaName": "timestamp", 60 | "title": "The timestamp with nanosecond precision." 61 | }, 62 | "z": { 63 | "existingJavaType": "java.lang.String", 64 | "javaName": "tape", 65 | "title": "The tape." 66 | } 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /src/main/java/net/jacobpeterson/alpaca/websocket/updates/model/tradeupdate/trade_update_event.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "string", 3 | "enum": [ 4 | "new", 5 | "fill", 6 | "partial_fill", 7 | "canceled", 8 | "expired", 9 | "done_for_day", 10 | "replaced", 11 | "rejected", 12 | "pending_new", 13 | "stopped", 14 | "pending_cancel", 15 | "pending_replace", 16 | "calculated", 17 | "suspended", 18 | "order_replace_rejected", 19 | "order_cancel_rejected" 20 | ], 21 | "javaEnums": [ 22 | { 23 | "description": "Sent when an order has been routed to exchanges for execution." 24 | }, 25 | { 26 | "description": "Sent when an order has been completely filled." 27 | }, 28 | { 29 | "description": "Sent when a number of shares less than the total remaining quantity on your order has been filled." 30 | }, 31 | { 32 | "description": "Sent when your requested cancellation of an order is processed." 33 | }, 34 | { 35 | "description": "Sent when an order has reached the end of its lifespan, as determined by the order’s time in force value." 36 | }, 37 | { 38 | "description": "Sent when the order is done executing for the day, and will not receive further updates until the next trading day." 39 | }, 40 | { 41 | "description": "Sent when your requested replacement of an order is processed." 42 | }, 43 | { 44 | "description": "Sent when your order has been rejected." 45 | }, 46 | { 47 | "description": "Sent when the order has been received by Alpaca and routed to the exchanges, but has not yet been accepted for execution." 48 | }, 49 | { 50 | "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." 51 | }, 52 | { 53 | "description": "Sent when the order is awaiting cancellation. Most cancellations will occur without the order entering this state." 54 | }, 55 | { 56 | "description": "Sent when the order is awaiting replacement." 57 | }, 58 | { 59 | "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." 60 | }, 61 | { 62 | "description": "Sent when the order has been suspended and is not eligible for trading." 63 | }, 64 | { 65 | "description": "Sent when the order replace has been rejected." 66 | }, 67 | { 68 | "description": "Sent when the order cancel has been rejected." 69 | } 70 | ] 71 | } 72 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @rem 2 | @rem Copyright 2015 the original author or authors. 3 | @rem 4 | @rem Licensed under the Apache License, Version 2.0 (the "License"); 5 | @rem you may not use this file except in compliance with the License. 6 | @rem You may obtain a copy of the License at 7 | @rem 8 | @rem https://www.apache.org/licenses/LICENSE-2.0 9 | @rem 10 | @rem Unless required by applicable law or agreed to in writing, software 11 | @rem distributed under the License is distributed on an "AS IS" BASIS, 12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | @rem See the License for the specific language governing permissions and 14 | @rem limitations under the License. 15 | @rem 16 | @rem SPDX-License-Identifier: Apache-2.0 17 | @rem 18 | 19 | @if "%DEBUG%"=="" @echo off 20 | @rem ########################################################################## 21 | @rem 22 | @rem Gradle startup script for Windows 23 | @rem 24 | @rem ########################################################################## 25 | 26 | @rem Set local scope for the variables with windows NT shell 27 | if "%OS%"=="Windows_NT" setlocal 28 | 29 | set DIRNAME=%~dp0 30 | if "%DIRNAME%"=="" set DIRNAME=. 31 | @rem This is normally unused 32 | set APP_BASE_NAME=%~n0 33 | set APP_HOME=%DIRNAME% 34 | 35 | @rem Resolve any "." and ".." in APP_HOME to make it shorter. 36 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi 37 | 38 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 39 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" 40 | 41 | @rem Find java.exe 42 | if defined JAVA_HOME goto findJavaFromJavaHome 43 | 44 | set JAVA_EXE=java.exe 45 | %JAVA_EXE% -version >NUL 2>&1 46 | if %ERRORLEVEL% equ 0 goto execute 47 | 48 | echo. 1>&2 49 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 50 | echo. 1>&2 51 | echo Please set the JAVA_HOME variable in your environment to match the 1>&2 52 | echo location of your Java installation. 1>&2 53 | 54 | goto fail 55 | 56 | :findJavaFromJavaHome 57 | set JAVA_HOME=%JAVA_HOME:"=% 58 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 59 | 60 | if exist "%JAVA_EXE%" goto execute 61 | 62 | echo. 1>&2 63 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 64 | echo. 1>&2 65 | echo Please set the JAVA_HOME variable in your environment to match the 1>&2 66 | echo location of your Java installation. 1>&2 67 | 68 | goto fail 69 | 70 | :execute 71 | @rem Setup the command line 72 | 73 | 74 | 75 | @rem Execute Gradle 76 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %* 77 | 78 | :end 79 | @rem End local scope for the variables with windows NT shell 80 | if %ERRORLEVEL% equ 0 goto mainEnd 81 | 82 | :fail 83 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 84 | rem the _cmd.exe /c_ return code! 85 | set EXIT_CODE=%ERRORLEVEL% 86 | if %EXIT_CODE% equ 0 set EXIT_CODE=1 87 | if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% 88 | exit /b %EXIT_CODE% 89 | 90 | :mainEnd 91 | if "%OS%"=="Windows_NT" endlocal 92 | 93 | :omega 94 | -------------------------------------------------------------------------------- /src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/streams/stock/StockMarketDataListener.java: -------------------------------------------------------------------------------- 1 | package net.jacobpeterson.alpaca.websocket.marketdata.streams.stock; 2 | 3 | import net.jacobpeterson.alpaca.model.websocket.marketdata.streams.stock.model.bar.StockBarMessage; 4 | import net.jacobpeterson.alpaca.model.websocket.marketdata.streams.stock.model.limituplimitdownband.StockLimitUpLimitDownBandMessage; 5 | import net.jacobpeterson.alpaca.model.websocket.marketdata.streams.stock.model.quote.StockQuoteMessage; 6 | import net.jacobpeterson.alpaca.model.websocket.marketdata.streams.stock.model.trade.StockTradeMessage; 7 | import net.jacobpeterson.alpaca.model.websocket.marketdata.streams.stock.model.tradecancelerror.StockTradeCancelErrorMessage; 8 | import net.jacobpeterson.alpaca.model.websocket.marketdata.streams.stock.model.tradecorrection.StockTradeCorrectionMessage; 9 | import net.jacobpeterson.alpaca.model.websocket.marketdata.streams.stock.model.tradingstatus.StockTradingStatusMessage; 10 | 11 | /** 12 | * {@link StockMarketDataListener} defines a listener interface for {@link StockMarketDataWebsocketInterface} messages. 13 | */ 14 | public interface StockMarketDataListener { 15 | 16 | /** 17 | * Called when a {@link StockTradeMessage} is received. 18 | * 19 | * @param trade the {@link StockTradeMessage} 20 | */ 21 | void onTrade(StockTradeMessage trade); 22 | 23 | /** 24 | * Called when a {@link StockQuoteMessage} is received. 25 | * 26 | * @param quote the {@link StockQuoteMessage} 27 | */ 28 | void onQuote(StockQuoteMessage quote); 29 | 30 | /** 31 | * Called when a {@link StockBarMessage} is received. 32 | * 33 | * @param bar the {@link StockBarMessage} 34 | */ 35 | void onMinuteBar(StockBarMessage bar); 36 | 37 | /** 38 | * Called when a {@link StockBarMessage} is received. 39 | * 40 | * @param bar the {@link StockBarMessage} 41 | */ 42 | void onDailyBar(StockBarMessage bar); 43 | 44 | /** 45 | * Called when a {@link StockBarMessage} is received. 46 | * 47 | * @param bar the {@link StockBarMessage} 48 | */ 49 | void onUpdatedBar(StockBarMessage bar); 50 | 51 | /** 52 | * Called when a {@link StockTradeCorrectionMessage} is received. 53 | * 54 | * @param tradeCorrection the {@link StockTradeCorrectionMessage} 55 | */ 56 | void onTradeCorrection(StockTradeCorrectionMessage tradeCorrection); 57 | 58 | /** 59 | * Called when a {@link StockTradeCancelErrorMessage} is received. 60 | * 61 | * @param tradeCancelError the {@link StockTradeCancelErrorMessage} 62 | */ 63 | void onTradeCancelError(StockTradeCancelErrorMessage tradeCancelError); 64 | 65 | /** 66 | * Called when a {@link StockLimitUpLimitDownBandMessage} is received. 67 | * 68 | * @param limitUpLimitDownBand the {@link StockLimitUpLimitDownBandMessage} 69 | */ 70 | void onLimitUpLimitDownBand(StockLimitUpLimitDownBandMessage limitUpLimitDownBand); 71 | 72 | /** 73 | * Called when a {link StockTradingStatusMessage} is received. 74 | * 75 | * @param tradingStatus the {@link StockTradingStatusMessage} 76 | */ 77 | void onTradingStatus(StockTradingStatusMessage tradingStatus); 78 | } 79 | -------------------------------------------------------------------------------- /src/main/java/net/jacobpeterson/alpaca/websocket/AlpacaWebsocketInterface.java: -------------------------------------------------------------------------------- 1 | package net.jacobpeterson.alpaca.websocket; 2 | 3 | import java.util.concurrent.ExecutionException; 4 | import java.util.concurrent.Future; 5 | import java.util.concurrent.TimeUnit; 6 | import java.util.concurrent.TimeoutException; 7 | 8 | /** 9 | * {@link AlpacaWebsocketInterface} defines an interface for Alpaca websockets. 10 | */ 11 | public interface AlpacaWebsocketInterface { 12 | 13 | /** 14 | * Connects this Websocket. 15 | */ 16 | void connect(); 17 | 18 | /** 19 | * Disconnects this Websocket. 20 | */ 21 | void disconnect(); 22 | 23 | /** 24 | * Returns true if this websocket is connected, false otherwise. 25 | * 26 | * @return a boolean 27 | */ 28 | boolean isConnected(); 29 | 30 | /** 31 | * Returns true if this websocket is authenticated, false otherwise. 32 | * 33 | * @return a boolean 34 | */ 35 | boolean isAuthenticated(); 36 | 37 | /** 38 | * Returns true if {@link #isConnected()} and {@link #isAuthenticated()}, false 39 | * otherwise. 40 | * 41 | * @return a boolean 42 | */ 43 | default boolean isValid() { 44 | return isConnected() && isAuthenticated(); 45 | } 46 | 47 | /** 48 | * Gets a {@link Boolean} {@link Future} that completes when an authentication message that is received after a new 49 | * websocket connection indicates successful authentication. 50 | *
51 | * Note that if this {@link AlpacaWebsocketInterface} is already authorized, the returned {@link Future} will likely 52 | * never complete. 53 | * 54 | * @return a {@link Boolean} {@link Future} 55 | */ 56 | Future getAuthorizationFuture(); 57 | 58 | /** 59 | * Waits for {@link #getAuthorizationFuture()} to complete and returns its value, except when timeout 60 | * time has elapsed, then this will return false. 61 | * 62 | * @param timeout the timeout time 63 | * @param unit the timeout {@link TimeUnit} 64 | * 65 | * @return a boolean 66 | */ 67 | default boolean waitForAuthorization(long timeout, TimeUnit unit) { 68 | try { 69 | return getAuthorizationFuture().get(timeout, unit); 70 | } catch (InterruptedException | ExecutionException | TimeoutException ignored) {} 71 | return false; 72 | } 73 | 74 | /** 75 | * Sets the {@link AlpacaWebsocketStateListener}. 76 | * 77 | * @param alpacaWebsocketStateListener the {@link AlpacaWebsocketStateListener} 78 | */ 79 | void setAlpacaWebsocketStateListener(AlpacaWebsocketStateListener alpacaWebsocketStateListener); 80 | 81 | /** 82 | * Returns true if this websocket automatically reconnects, false otherwise. 83 | * 84 | * @return a boolean 85 | */ 86 | boolean doesAutomaticallyReconnect(); 87 | 88 | /** 89 | * Sets whether to automatically reconnect and reauthenticate on a websocket failure. true by default. 90 | * 91 | * @param automaticallyReconnect true to automatically reconnect, false otherwise 92 | */ 93 | void setAutomaticallyReconnect(boolean automaticallyReconnect); 94 | } 95 | -------------------------------------------------------------------------------- /src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/streams/news/NewsMarketDataWebsocket.java: -------------------------------------------------------------------------------- 1 | package net.jacobpeterson.alpaca.websocket.marketdata.streams.news; 2 | 3 | import com.google.gson.JsonObject; 4 | import net.jacobpeterson.alpaca.model.websocket.marketdata.streams.news.model.NewsMarketDataMessageType; 5 | import net.jacobpeterson.alpaca.model.websocket.marketdata.streams.news.model.control.NewsSubscriptionsMessage; 6 | import net.jacobpeterson.alpaca.model.websocket.marketdata.streams.news.model.news.NewsMessage; 7 | import net.jacobpeterson.alpaca.websocket.marketdata.MarketDataWebsocket; 8 | import okhttp3.HttpUrl; 9 | import okhttp3.OkHttpClient; 10 | 11 | import java.util.Set; 12 | 13 | import static net.jacobpeterson.alpaca.model.websocket.marketdata.streams.news.model.NewsMarketDataMessageType.ERROR; 14 | import static net.jacobpeterson.alpaca.model.websocket.marketdata.streams.news.model.NewsMarketDataMessageType.SUBSCRIPTION; 15 | import static net.jacobpeterson.alpaca.model.websocket.marketdata.streams.news.model.NewsMarketDataMessageType.SUCCESS; 16 | import static net.jacobpeterson.alpaca.openapi.marketdata.JSON.getGson; 17 | 18 | /** 19 | * {@link NewsMarketDataWebsocket} is an implementation for {@link NewsMarketDataWebsocketInterface}. 20 | */ 21 | public class NewsMarketDataWebsocket 22 | extends MarketDataWebsocket 23 | implements NewsMarketDataWebsocketInterface { 24 | 25 | /** 26 | * Instantiates a new {@link NewsMarketDataWebsocket}. 27 | * 28 | * @param okHttpClient the {@link OkHttpClient} 29 | * @param traderKeyID the trader key ID 30 | * @param traderSecretKey the trader secret key 31 | * @param brokerAPIKey the broker API key 32 | * @param brokerAPISecret the broker API secret 33 | */ 34 | public NewsMarketDataWebsocket(OkHttpClient okHttpClient, String traderKeyID, String traderSecretKey, 35 | String brokerAPIKey, String brokerAPISecret) { 36 | super(okHttpClient, new HttpUrl.Builder() 37 | .scheme("https") 38 | .host("stream.data.alpaca.markets") 39 | .addPathSegments("v1beta1/news") 40 | .build(), 41 | "News", traderKeyID, traderSecretKey, brokerAPIKey, brokerAPISecret, 42 | NewsMarketDataMessageType.class, NewsSubscriptionsMessage.class); 43 | } 44 | 45 | @Override 46 | protected boolean isSuccessMessageType(NewsMarketDataMessageType messageType) { 47 | return messageType == SUCCESS; 48 | } 49 | 50 | @Override 51 | protected boolean isErrorMessageType(NewsMarketDataMessageType messageType) { 52 | return messageType == ERROR; 53 | } 54 | 55 | @Override 56 | protected boolean isSubscriptionMessageType(NewsMarketDataMessageType messageType) { 57 | return messageType == SUBSCRIPTION; 58 | } 59 | 60 | @Override 61 | protected void callListenerWithMessage(NewsMarketDataMessageType messageType, JsonObject messageObject) { 62 | if (messageType == NewsMarketDataMessageType.NEWS) { 63 | listener.onNews(getGson().fromJson(messageObject, NewsMessage.class)); 64 | } else { 65 | throw new UnsupportedOperationException(); 66 | } 67 | } 68 | 69 | @Override 70 | public void setListener(NewsMarketDataListener listener) { 71 | this.listener = listener; 72 | } 73 | 74 | @Override 75 | public void setNewsSubscriptions(Set symbols) { 76 | symbols = symbols == null ? Set.of() : symbols; 77 | setSubscriptions(getNewsSubscriptions(), symbols, 78 | set -> new NewsSubscriptionsMessage().withNews(set)); 79 | } 80 | 81 | @Override 82 | public Set getNewsSubscriptions() { 83 | return subscriptionsMessage == null ? Set.of() : subscriptionsMessage.getNews(); 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/streams/crypto/CryptoMarketDataWebsocketInterface.java: -------------------------------------------------------------------------------- 1 | package net.jacobpeterson.alpaca.websocket.marketdata.streams.crypto; 2 | 3 | import net.jacobpeterson.alpaca.model.websocket.marketdata.streams.crypto.model.CryptoMarketDataMessageType; 4 | import net.jacobpeterson.alpaca.websocket.marketdata.MarketDataWebsocketInterface; 5 | 6 | import java.util.Set; 7 | 8 | /** 9 | * {@link CryptoMarketDataWebsocketInterface} is a {@link MarketDataWebsocketInterface} for 10 | * {@link CryptoMarketDataWebsocket}. 11 | */ 12 | public interface CryptoMarketDataWebsocketInterface extends MarketDataWebsocketInterface { 13 | 14 | /** 15 | * Sets the {@link CryptoMarketDataListener}. 16 | * 17 | * @param listener the {@link CryptoMarketDataListener} 18 | */ 19 | void setListener(CryptoMarketDataListener listener); 20 | 21 | /** 22 | * Subscribes the given symbols to {@link CryptoMarketDataMessageType#TRADES}. This will remove all 23 | * previous {@link CryptoMarketDataMessageType#QUOTES} subscriptions. 24 | * 25 | * @param symbols the {@link Set} of symbols (use an asterisk ("*") as a wildcard to subscribe to all available 26 | * symbols) or null to unsubscribe from all symbols 27 | */ 28 | void setTradeSubscriptions(Set symbols); 29 | 30 | /** 31 | * Gets the current symbols subscribed to {@link CryptoMarketDataMessageType#TRADES}. 32 | * 33 | * @return a {@link Set} of {@link String} symbols 34 | */ 35 | Set getTradeSubscriptions(); 36 | 37 | /** 38 | * Subscribes the given symbols to {@link CryptoMarketDataMessageType#QUOTES}. This will remove all 39 | * previous {@link CryptoMarketDataMessageType#QUOTES} subscriptions. 40 | * 41 | * @param symbols the {@link Set} of symbols (use an asterisk ("*") as a wildcard to subscribe to all available 42 | * symbols) or null to unsubscribe from all symbols 43 | */ 44 | void setQuoteSubscriptions(Set symbols); 45 | 46 | /** 47 | * Gets the current symbols subscribed to {@link CryptoMarketDataMessageType#QUOTES}. 48 | * 49 | * @return a {@link Set} of {@link String} symbols 50 | */ 51 | Set getQuoteSubscriptions(); 52 | 53 | /** 54 | * Subscribes the given symbols to {@link CryptoMarketDataMessageType#MINUTE_BARS}. This will remove 55 | * all previous {@link CryptoMarketDataMessageType#QUOTES} subscriptions. 56 | * 57 | * @param symbols the {@link Set} of symbols (use an asterisk ("*") as a wildcard to subscribe to all available 58 | * symbols) or null to unsubscribe from all symbols 59 | */ 60 | void setMinuteBarSubscriptions(Set symbols); 61 | 62 | /** 63 | * Gets the current symbols subscribed to {@link CryptoMarketDataMessageType#MINUTE_BARS}. 64 | * 65 | * @return a {@link Set} of {@link String} symbols 66 | */ 67 | Set getMinuteBarSubscriptions(); 68 | 69 | /** 70 | * Subscribes the given symbols to {@link CryptoMarketDataMessageType#DAILY_BARS}. This will remove all 71 | * previous {@link CryptoMarketDataMessageType#QUOTES} subscriptions. 72 | * 73 | * @param symbols the {@link Set} of symbols (use an asterisk ("*") as a wildcard to subscribe to all available 74 | * symbols) or null to unsubscribe from all symbols 75 | */ 76 | void setDailyBarSubscriptions(Set symbols); 77 | 78 | /** 79 | * Gets the current symbols subscribed to {@link CryptoMarketDataMessageType#DAILY_BARS}. 80 | * 81 | * @return a {@link Set} of {@link String} symbols 82 | */ 83 | Set getDailyBarSubscriptions(); 84 | 85 | /** 86 | * Subscribes the given symbols to {@link CryptoMarketDataMessageType#UPDATED_BARS}. This will remove 87 | * all previous {@link CryptoMarketDataMessageType#QUOTES} subscriptions. 88 | * 89 | * @param symbols the {@link Set} of symbols (use an asterisk ("*") as a wildcard to subscribe to all available 90 | * symbols) or null to unsubscribe from all symbols 91 | */ 92 | void setUpdatedBarSubscriptions(Set symbols); 93 | 94 | /** 95 | * Gets the current symbols subscribed to {@link CryptoMarketDataMessageType#UPDATED_BARS}. 96 | * 97 | * @return a {@link Set} of {@link String} symbols 98 | */ 99 | Set getUpdatedBarSubscriptions(); 100 | 101 | /** 102 | * Subscribes the given symbols to {@link CryptoMarketDataMessageType#ORDER_BOOKS}. This will remove 103 | * all previous {@link CryptoMarketDataMessageType#QUOTES} subscriptions. 104 | * 105 | * @param symbols the {@link Set} of symbols (use an asterisk ("*") as a wildcard to subscribe to all available 106 | * symbols) or null to unsubscribe from all symbols 107 | */ 108 | void setOrderBookSubscriptions(Set symbols); 109 | 110 | /** 111 | * Gets the current symbols subscribed to {@link CryptoMarketDataMessageType#ORDER_BOOKS}. 112 | * 113 | * @return a {@link Set} of {@link String} symbols 114 | */ 115 | Set getOrderBookSubscriptions(); 116 | } 117 | -------------------------------------------------------------------------------- /src/main/java/net/jacobpeterson/alpaca/rest/marketdata/AlpacaMarketDataAPI.java: -------------------------------------------------------------------------------- 1 | package net.jacobpeterson.alpaca.rest.marketdata; 2 | 3 | import net.jacobpeterson.alpaca.openapi.marketdata.ApiClient; 4 | import net.jacobpeterson.alpaca.openapi.marketdata.api.CorporateActionsApi; 5 | import net.jacobpeterson.alpaca.openapi.marketdata.api.CryptoApi; 6 | import net.jacobpeterson.alpaca.openapi.marketdata.api.ForexApi; 7 | import net.jacobpeterson.alpaca.openapi.marketdata.api.LogosApi; 8 | import net.jacobpeterson.alpaca.openapi.marketdata.api.NewsApi; 9 | import net.jacobpeterson.alpaca.openapi.marketdata.api.OptionApi; 10 | import net.jacobpeterson.alpaca.openapi.marketdata.api.StockApi; 11 | import okhttp3.OkHttpClient; 12 | 13 | import static com.google.common.base.Preconditions.checkArgument; 14 | import static com.google.common.base.Preconditions.checkNotNull; 15 | import static net.jacobpeterson.alpaca.util.apikey.APIKeyUtil.createBrokerAPIAuthKey; 16 | 17 | /** 18 | * {@link AlpacaMarketDataAPI} is the class used to interface with the Alpaca Market Data API endpoints. This class is 19 | * thread-safe. 20 | */ 21 | public class AlpacaMarketDataAPI { 22 | 23 | private final ApiClient apiClient; 24 | private CorporateActionsApi corporateActions; 25 | private CryptoApi crypto; 26 | private ForexApi forex; 27 | private LogosApi logos; 28 | private NewsApi news; 29 | private OptionApi option; 30 | private StockApi stock; 31 | 32 | /** 33 | * Instantiates a new {@link AlpacaMarketDataAPI}. 34 | * 35 | * @param traderKeyID the Trader key ID 36 | * @param traderSecretKey the Trader secret key 37 | * @param brokerAPIKey the Broker API key 38 | * @param brokerAPISecret the Broker API secret 39 | * @param okHttpClient an existing {@link OkHttpClient} or null to create a new default instance 40 | */ 41 | public AlpacaMarketDataAPI(String traderKeyID, String traderSecretKey, 42 | String brokerAPIKey, String brokerAPISecret, OkHttpClient okHttpClient) { 43 | checkArgument((traderKeyID != null && traderSecretKey != null) ^ 44 | (brokerAPIKey != null && brokerAPISecret != null), 45 | "You must specify a (trader key ID and secret key) or an (broker API key and secret)!"); 46 | checkNotNull(okHttpClient); 47 | 48 | final boolean traderKeysGiven = traderKeyID != null && traderSecretKey != null; 49 | apiClient = new ApiClient(okHttpClient); 50 | apiClient.setServerIndex(traderKeysGiven ? 0 : 1); 51 | if (traderKeysGiven) { 52 | apiClient.addDefaultHeader("APCA-API-KEY-ID", traderKeyID); 53 | apiClient.addDefaultHeader("APCA-API-SECRET-KEY", traderSecretKey); 54 | } else { 55 | apiClient.addDefaultHeader("Authorization", "Basic " + 56 | createBrokerAPIAuthKey(brokerAPIKey, brokerAPISecret)); 57 | } 58 | } 59 | 60 | /** 61 | * Gets the internal {@link ApiClient}. 62 | * 63 | * @return the {@link ApiClient} 64 | */ 65 | public ApiClient getInternalAPIClient() { 66 | return apiClient; 67 | } 68 | 69 | /** 70 | * Gets the {@link CorporateActionsApi}. Lazily instantiated. 71 | * 72 | * @return the {@link CorporateActionsApi} 73 | */ 74 | public synchronized CorporateActionsApi corporateActions() { 75 | if (corporateActions == null) { 76 | corporateActions = new CorporateActionsApi(apiClient); 77 | } 78 | return corporateActions; 79 | } 80 | 81 | /** 82 | * Gets the {@link CryptoApi}. Lazily instantiated. 83 | * 84 | * @return the {@link CryptoApi} 85 | */ 86 | public synchronized CryptoApi crypto() { 87 | if (crypto == null) { 88 | crypto = new CryptoApi(apiClient); 89 | } 90 | return crypto; 91 | } 92 | 93 | /** 94 | * Gets the {@link ForexApi}. Lazily instantiated. 95 | * 96 | * @return the {@link ForexApi} 97 | */ 98 | public synchronized ForexApi forex() { 99 | if (forex == null) { 100 | forex = new ForexApi(apiClient); 101 | } 102 | return forex; 103 | } 104 | 105 | /** 106 | * Gets the {@link LogosApi}. Lazily instantiated. 107 | * 108 | * @return the {@link LogosApi} 109 | */ 110 | public synchronized LogosApi logos() { 111 | if (logos == null) { 112 | logos = new LogosApi(apiClient); 113 | } 114 | return logos; 115 | } 116 | 117 | /** 118 | * Gets the {@link NewsApi}. Lazily instantiated. 119 | * 120 | * @return the {@link NewsApi} 121 | */ 122 | public synchronized NewsApi news() { 123 | if (news == null) { 124 | news = new NewsApi(apiClient); 125 | } 126 | return news; 127 | } 128 | 129 | /** 130 | * Gets the {@link OptionApi}. Lazily instantiated. 131 | * 132 | * @return the {@link OptionApi} 133 | */ 134 | public synchronized OptionApi option() { 135 | if (option == null) { 136 | option = new OptionApi(apiClient); 137 | } 138 | return option; 139 | } 140 | 141 | /** 142 | * Gets the {@link StockApi}. Lazily instantiated. 143 | * 144 | * @return the {@link StockApi} 145 | */ 146 | public synchronized StockApi stock() { 147 | if (stock == null) { 148 | stock = new StockApi(apiClient); 149 | } 150 | return stock; 151 | } 152 | } 153 | -------------------------------------------------------------------------------- /src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/streams/stock/StockMarketDataWebsocketInterface.java: -------------------------------------------------------------------------------- 1 | package net.jacobpeterson.alpaca.websocket.marketdata.streams.stock; 2 | 3 | import net.jacobpeterson.alpaca.model.websocket.marketdata.streams.stock.model.StockMarketDataMessageType; 4 | import net.jacobpeterson.alpaca.websocket.marketdata.MarketDataWebsocketInterface; 5 | 6 | import java.util.Set; 7 | 8 | /** 9 | * {@link StockMarketDataWebsocketInterface} is a {@link MarketDataWebsocketInterface} for 10 | * {@link StockMarketDataWebsocket}. 11 | */ 12 | public interface StockMarketDataWebsocketInterface extends MarketDataWebsocketInterface { 13 | 14 | /** 15 | * Sets the {@link StockMarketDataListener}. 16 | * 17 | * @param listener the {@link StockMarketDataListener} 18 | */ 19 | void setListener(StockMarketDataListener listener); 20 | 21 | /** 22 | * Subscribes the given symbols to {@link StockMarketDataMessageType#TRADES}. This will remove all 23 | * previous {@link StockMarketDataMessageType#TRADES} subscriptions. 24 | *
25 | * Note that this will automatically apply the same symbols subscription list to the 26 | * {@link StockMarketDataMessageType#TRADE_CORRECTIONS} and {@link StockMarketDataMessageType#TRADE_CANCEL_ERRORS} 27 | * types. 28 | * 29 | * @param symbols the {@link Set} of symbols (use an asterisk ("*") as a wildcard to subscribe to all available 30 | * symbols) or null to unsubscribe from all symbols 31 | */ 32 | void setTradeSubscriptions(Set symbols); 33 | 34 | /** 35 | * Gets the current symbols subscribed to {@link StockMarketDataMessageType#TRADES}. 36 | * 37 | * @return a {@link Set} of {@link String} symbols 38 | */ 39 | Set getTradeSubscriptions(); 40 | 41 | /** 42 | * Subscribes the given symbols to {@link StockMarketDataMessageType#QUOTES}. This will remove all 43 | * previous {@link StockMarketDataMessageType#QUOTES} subscriptions. 44 | * 45 | * @param symbols the {@link Set} of symbols (use an asterisk ("*") as a wildcard to subscribe to all available 46 | * symbols) or null to unsubscribe from all symbols 47 | */ 48 | void setQuoteSubscriptions(Set symbols); 49 | 50 | /** 51 | * Gets the current symbols subscribed to {@link StockMarketDataMessageType#QUOTES}. 52 | * 53 | * @return a {@link Set} of {@link String} symbols 54 | */ 55 | Set getQuoteSubscriptions(); 56 | 57 | /** 58 | * Subscribes the given symbols to {@link StockMarketDataMessageType#MINUTE_BARS}. This will remove all 59 | * previous {@link StockMarketDataMessageType#MINUTE_BARS} subscriptions. 60 | * 61 | * @param symbols the {@link Set} of symbols (use an asterisk ("*") as a wildcard to subscribe to all available 62 | * symbols) or null to unsubscribe from all symbols 63 | */ 64 | void setMinuteBarSubscriptions(Set symbols); 65 | 66 | /** 67 | * Gets the current symbols subscribed to {@link StockMarketDataMessageType#MINUTE_BARS}. 68 | * 69 | * @return a {@link Set} of {@link String} symbols 70 | */ 71 | Set getMinuteBarSubscriptions(); 72 | 73 | /** 74 | * Subscribes the given symbols to {@link StockMarketDataMessageType#DAILY_BARS}. This will remove all 75 | * previous {@link StockMarketDataMessageType#DAILY_BARS} subscriptions. 76 | * 77 | * @param symbols the {@link Set} of symbols (use an asterisk ("*") as a wildcard to subscribe to all available 78 | * symbols) or null to unsubscribe from all symbols 79 | */ 80 | void setDailyBarSubscriptions(Set symbols); 81 | 82 | /** 83 | * Gets the current symbols subscribed to {@link StockMarketDataMessageType#DAILY_BARS}. 84 | * 85 | * @return a {@link Set} of {@link String} symbols 86 | */ 87 | Set getDailyBarSubscriptions(); 88 | 89 | /** 90 | * Subscribes the given symbols to {@link StockMarketDataMessageType#UPDATED_BARS}. This will remove 91 | * all previous {@link StockMarketDataMessageType#UPDATED_BARS} subscriptions. 92 | * 93 | * @param symbols the {@link Set} of symbols (use an asterisk ("*") as a wildcard to subscribe to all available 94 | * symbols) or null to unsubscribe from all symbols 95 | */ 96 | void setUpdatedBarSubscriptions(Set symbols); 97 | 98 | /** 99 | * Gets the current symbols subscribed to {@link StockMarketDataMessageType#UPDATED_BARS}. 100 | * 101 | * @return a {@link Set} of {@link String} symbols 102 | */ 103 | Set getUpdatedBarSubscriptions(); 104 | 105 | /** 106 | * Subscribes the given symbols to {@link StockMarketDataMessageType#LIMIT_UP_LIMIT_DOWN_BANDS}. This 107 | * will remove all previous {@link StockMarketDataMessageType#LIMIT_UP_LIMIT_DOWN_BANDS} subscriptions. 108 | * 109 | * @param symbols the {@link Set} of symbols (use an asterisk ("*") as a wildcard to subscribe to all available 110 | * symbols) or null to unsubscribe from all symbols 111 | */ 112 | void setLimitUpLimitDownBandSubscriptions(Set symbols); 113 | 114 | /** 115 | * Gets the current symbols subscribed to {@link StockMarketDataMessageType#LIMIT_UP_LIMIT_DOWN_BANDS}. 116 | * 117 | * @return a {@link Set} of {@link String} symbols 118 | */ 119 | Set getLimitUpLimitDownBandSubscriptions(); 120 | 121 | /** 122 | * Subscribes the given symbols to {@link StockMarketDataMessageType#TRADING_STATUSES}. This will 123 | * remove all previous {@link StockMarketDataMessageType#TRADING_STATUSES} subscriptions. 124 | * 125 | * @param symbols the {@link Set} of symbols (use an asterisk ("*") as a wildcard to subscribe to all available 126 | * symbols) or null to unsubscribe from all symbols 127 | */ 128 | void setTradingStatuseSubscriptions(Set symbols); 129 | 130 | /** 131 | * Gets the current symbols subscribed to {@link StockMarketDataMessageType#TRADING_STATUSES}. 132 | * 133 | * @return a {@link Set} of {@link String} symbols 134 | */ 135 | Set getTradingStatuseSubscriptions(); 136 | } 137 | -------------------------------------------------------------------------------- /src/main/java/net/jacobpeterson/alpaca/websocket/updates/UpdatesWebsocket.java: -------------------------------------------------------------------------------- 1 | package net.jacobpeterson.alpaca.websocket.updates; 2 | 3 | import com.google.gson.JsonArray; 4 | import com.google.gson.JsonObject; 5 | import net.jacobpeterson.alpaca.model.util.apitype.TraderAPIEndpointType; 6 | import net.jacobpeterson.alpaca.model.websocket.updates.model.UpdatesMessageType; 7 | import net.jacobpeterson.alpaca.model.websocket.updates.model.authorization.AuthorizationMessage; 8 | import net.jacobpeterson.alpaca.model.websocket.updates.model.tradeupdate.TradeUpdateMessage; 9 | import net.jacobpeterson.alpaca.websocket.AlpacaWebsocket; 10 | import okhttp3.HttpUrl; 11 | import okhttp3.OkHttpClient; 12 | import okhttp3.WebSocket; 13 | import okio.ByteString; 14 | import org.jetbrains.annotations.NotNull; 15 | import org.slf4j.Logger; 16 | import org.slf4j.LoggerFactory; 17 | 18 | import static com.google.gson.JsonParser.parseString; 19 | import static java.util.concurrent.TimeUnit.SECONDS; 20 | import static net.jacobpeterson.alpaca.model.websocket.updates.model.UpdatesMessageType.TRADE_UPDATES; 21 | import static net.jacobpeterson.alpaca.openapi.trader.JSON.getGson; 22 | 23 | /** 24 | * {@link UpdatesWebsocket} is an {@link AlpacaWebsocket} implementation and provides the 25 | * {@link UpdatesWebsocketInterface} interface for 26 | * Updates Streaming. 27 | */ 28 | public class UpdatesWebsocket extends AlpacaWebsocket implements UpdatesWebsocketInterface { 29 | 30 | private static final Logger LOGGER = LoggerFactory.getLogger(UpdatesWebsocket.class); 31 | 32 | @SuppressWarnings("UnnecessaryDefault") 33 | private static HttpUrl createWebsocketURL(TraderAPIEndpointType traderAPIEndpointType) { 34 | return new HttpUrl.Builder() 35 | .scheme("https") 36 | .host((switch (traderAPIEndpointType) { 37 | case LIVE -> "api"; 38 | case PAPER -> "paper-api"; 39 | default -> throw new UnsupportedOperationException(); 40 | }) + ".alpaca.markets") 41 | .addPathSegment("stream") 42 | .build(); 43 | } 44 | 45 | protected final String keyID; 46 | protected final String secretKey; 47 | protected final String oAuthToken; 48 | protected UpdatesListener listener; 49 | protected boolean listenToTradeUpdates; 50 | 51 | /** 52 | * Instantiates a new {@link UpdatesWebsocket}. 53 | * 54 | * @param okHttpClient the {@link OkHttpClient} 55 | * @param traderAPIEndpointType the {@link TraderAPIEndpointType} 56 | * @param keyID the key ID 57 | * @param secretKey the secret key 58 | * @param oAuthToken the OAuth token 59 | */ 60 | public UpdatesWebsocket(OkHttpClient okHttpClient, TraderAPIEndpointType traderAPIEndpointType, 61 | String keyID, String secretKey, String oAuthToken) { 62 | super(okHttpClient, createWebsocketURL(traderAPIEndpointType), "Trades Stream"); 63 | this.keyID = keyID; 64 | this.secretKey = secretKey; 65 | this.oAuthToken = oAuthToken; 66 | } 67 | 68 | @Override 69 | protected void cleanupState() { 70 | super.cleanupState(); 71 | } 72 | 73 | @Override 74 | protected void onConnection() { 75 | sendAuthenticationMessage(); 76 | } 77 | 78 | @Override 79 | protected void onReconnection() { 80 | sendAuthenticationMessage(); 81 | if (waitForAuthorization(5, SECONDS) && listenToTradeUpdates) { 82 | sendTradeUpdatesListenMessage(); 83 | } 84 | } 85 | 86 | @Override 87 | protected void sendAuthenticationMessage() { 88 | // Ensure that the authorization Future exists 89 | getAuthorizationFuture(); 90 | 91 | final JsonObject authObject = new JsonObject(); 92 | authObject.addProperty("action", "authenticate"); 93 | final JsonObject authData = new JsonObject(); 94 | if (oAuthToken != null) { 95 | authData.addProperty("oauth_token", oAuthToken); 96 | } else { 97 | authData.addProperty("key_id", keyID); 98 | authData.addProperty("secret_key", secretKey); 99 | } 100 | authObject.add("data", authData); 101 | 102 | LOGGER.info("{} websocket sending authentication message...", websocketName); 103 | sendWebsocketMessage(authObject.toString()); 104 | } 105 | 106 | @Override 107 | public void onMessage(@NotNull WebSocket webSocket, @NotNull ByteString byteString) { // Binary framing 108 | final String messageString = byteString.utf8(); 109 | LOGGER.trace("Websocket message received: message={}", messageString); 110 | 111 | // Parse JSON string and identify 'messageType' 112 | final JsonObject messageObject = parseString(messageString).getAsJsonObject(); 113 | final UpdatesMessageType messageType = 114 | getGson().fromJson(messageObject.get("stream"), UpdatesMessageType.class); 115 | 116 | // Deserialize message based on 'messageType' and call listener 117 | switch (messageType) { 118 | case AUTHORIZATION: 119 | final AuthorizationMessage authorizationMessage = 120 | getGson().fromJson(messageObject, AuthorizationMessage.class); 121 | authenticated = authorizationMessage.getData().getAction().equalsIgnoreCase("authenticate") && 122 | authorizationMessage.getData().getStatus().equalsIgnoreCase("authorized"); 123 | if (authenticationMessageFuture != null) { 124 | authenticationMessageFuture.complete(authenticated); 125 | } 126 | if (!authenticated) { 127 | throw new RuntimeException(websocketName + " websocket authentication failed!"); 128 | } else { 129 | LOGGER.info("{} websocket authenticated.", websocketName); 130 | } 131 | break; 132 | case LISTENING: 133 | break; 134 | case TRADE_UPDATES: 135 | if (listener != null) { 136 | listener.onTradeUpdate(getGson().fromJson(messageObject, TradeUpdateMessage.class)); 137 | } 138 | break; 139 | default: 140 | throw new UnsupportedOperationException(); 141 | } 142 | } 143 | 144 | @Override 145 | public void setListener(UpdatesListener listener) { 146 | this.listener = listener; 147 | } 148 | 149 | @Override 150 | public void subscribeToTradeUpdates(boolean subscribe) { 151 | listenToTradeUpdates = subscribe; 152 | sendTradeUpdatesListenMessage(); 153 | } 154 | 155 | private void sendTradeUpdatesListenMessage() { 156 | final JsonObject requestObject = new JsonObject(); 157 | requestObject.addProperty("action", "listen"); 158 | final JsonArray streamsArray = new JsonArray(); 159 | if (listenToTradeUpdates) { 160 | streamsArray.add(TRADE_UPDATES.toString()); 161 | } 162 | final JsonObject dataObject = new JsonObject(); 163 | dataObject.add("streams", streamsArray); 164 | requestObject.add("data", dataObject); 165 | 166 | sendWebsocketMessage(requestObject.toString()); 167 | LOGGER.info("Requested streams: streams={}.", streamsArray); 168 | } 169 | } 170 | -------------------------------------------------------------------------------- /src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/streams/crypto/CryptoMarketDataWebsocket.java: -------------------------------------------------------------------------------- 1 | package net.jacobpeterson.alpaca.websocket.marketdata.streams.crypto; 2 | 3 | import com.google.gson.JsonObject; 4 | import net.jacobpeterson.alpaca.model.websocket.marketdata.streams.crypto.model.CryptoMarketDataMessageType; 5 | import net.jacobpeterson.alpaca.model.websocket.marketdata.streams.crypto.model.bar.CryptoBarMessage; 6 | import net.jacobpeterson.alpaca.model.websocket.marketdata.streams.crypto.model.control.CryptoSubscriptionsMessage; 7 | import net.jacobpeterson.alpaca.model.websocket.marketdata.streams.crypto.model.orderbook.CryptoOrderBookMessage; 8 | import net.jacobpeterson.alpaca.model.websocket.marketdata.streams.crypto.model.quote.CryptoQuoteMessage; 9 | import net.jacobpeterson.alpaca.model.websocket.marketdata.streams.crypto.model.trade.CryptoTradeMessage; 10 | import net.jacobpeterson.alpaca.websocket.marketdata.MarketDataWebsocket; 11 | import okhttp3.HttpUrl; 12 | import okhttp3.OkHttpClient; 13 | 14 | import java.util.Set; 15 | 16 | import static net.jacobpeterson.alpaca.model.websocket.marketdata.streams.crypto.model.CryptoMarketDataMessageType.ERROR; 17 | import static net.jacobpeterson.alpaca.model.websocket.marketdata.streams.crypto.model.CryptoMarketDataMessageType.SUBSCRIPTION; 18 | import static net.jacobpeterson.alpaca.model.websocket.marketdata.streams.crypto.model.CryptoMarketDataMessageType.SUCCESS; 19 | import static net.jacobpeterson.alpaca.openapi.marketdata.JSON.getGson; 20 | 21 | /** 22 | * {@link CryptoMarketDataWebsocket} is an implementation for {@link CryptoMarketDataWebsocketInterface}. 23 | */ 24 | public class CryptoMarketDataWebsocket 25 | extends MarketDataWebsocket 26 | implements CryptoMarketDataWebsocketInterface { 27 | 28 | /** 29 | * Instantiates a new {@link CryptoMarketDataWebsocket}. 30 | * 31 | * @param okHttpClient the {@link OkHttpClient} 32 | * @param traderKeyID the trader key ID 33 | * @param traderSecretKey the trader secret key 34 | * @param brokerAPIKey the broker API key 35 | * @param brokerAPISecret the broker API secret 36 | */ 37 | public CryptoMarketDataWebsocket(OkHttpClient okHttpClient, String traderKeyID, String traderSecretKey, 38 | String brokerAPIKey, String brokerAPISecret) { 39 | super(okHttpClient, new HttpUrl.Builder() 40 | .scheme("https") 41 | .host("stream.data.alpaca.markets") 42 | .addPathSegments("v1beta3/crypto/us") 43 | .build(), 44 | "Crypto", traderKeyID, traderSecretKey, brokerAPIKey, brokerAPISecret, 45 | CryptoMarketDataMessageType.class, CryptoSubscriptionsMessage.class); 46 | } 47 | 48 | @Override 49 | protected boolean isSuccessMessageType(CryptoMarketDataMessageType messageType) { 50 | return messageType == SUCCESS; 51 | } 52 | 53 | @Override 54 | protected boolean isErrorMessageType(CryptoMarketDataMessageType messageType) { 55 | return messageType == ERROR; 56 | } 57 | 58 | @Override 59 | protected boolean isSubscriptionMessageType(CryptoMarketDataMessageType messageType) { 60 | return messageType == SUBSCRIPTION; 61 | } 62 | 63 | @Override 64 | protected void callListenerWithMessage(CryptoMarketDataMessageType messageType, JsonObject messageObject) { 65 | switch (messageType) { 66 | case TRADES: 67 | listener.onTrade(getGson().fromJson(messageObject, CryptoTradeMessage.class)); 68 | break; 69 | case QUOTES: 70 | listener.onQuote(getGson().fromJson(messageObject, CryptoQuoteMessage.class)); 71 | break; 72 | case MINUTE_BARS: 73 | listener.onMinuteBar(getGson().fromJson(messageObject, CryptoBarMessage.class)); 74 | break; 75 | case DAILY_BARS: 76 | listener.onDailyBar(getGson().fromJson(messageObject, CryptoBarMessage.class)); 77 | break; 78 | case UPDATED_BARS: 79 | listener.onUpdatedBar(getGson().fromJson(messageObject, CryptoBarMessage.class)); 80 | break; 81 | case ORDER_BOOKS: 82 | listener.onOrderBook(getGson().fromJson(messageObject, CryptoOrderBookMessage.class)); 83 | break; 84 | default: 85 | throw new UnsupportedOperationException(); 86 | } 87 | } 88 | 89 | @Override 90 | public void setListener(CryptoMarketDataListener listener) { 91 | this.listener = listener; 92 | } 93 | 94 | @Override 95 | public void setTradeSubscriptions(Set symbols) { 96 | symbols = symbols == null ? Set.of() : symbols; 97 | setSubscriptions(getTradeSubscriptions(), symbols, 98 | set -> newEmptyCryptoSubscriptionsMessage().withTrades(set)); 99 | } 100 | 101 | @Override 102 | public Set getTradeSubscriptions() { 103 | return subscriptionsMessage == null ? Set.of() : subscriptionsMessage.getTrades(); 104 | } 105 | 106 | @Override 107 | public void setQuoteSubscriptions(Set symbols) { 108 | symbols = symbols == null ? Set.of() : symbols; 109 | setSubscriptions(getQuoteSubscriptions(), symbols, 110 | set -> newEmptyCryptoSubscriptionsMessage().withQuotes(set)); 111 | } 112 | 113 | @Override 114 | public Set getQuoteSubscriptions() { 115 | return subscriptionsMessage == null ? Set.of() : subscriptionsMessage.getQuotes(); 116 | } 117 | 118 | @Override 119 | public void setMinuteBarSubscriptions(Set symbols) { 120 | symbols = symbols == null ? Set.of() : symbols; 121 | setSubscriptions(getMinuteBarSubscriptions(), symbols, 122 | set -> newEmptyCryptoSubscriptionsMessage().withMinuteBars(set)); 123 | } 124 | 125 | @Override 126 | public Set getMinuteBarSubscriptions() { 127 | return subscriptionsMessage == null ? Set.of() : subscriptionsMessage.getMinuteBars(); 128 | } 129 | 130 | @Override 131 | public void setDailyBarSubscriptions(Set symbols) { 132 | symbols = symbols == null ? Set.of() : symbols; 133 | setSubscriptions(getDailyBarSubscriptions(), symbols, 134 | set -> newEmptyCryptoSubscriptionsMessage().withDailyBars(set)); 135 | } 136 | 137 | @Override 138 | public Set getDailyBarSubscriptions() { 139 | return subscriptionsMessage == null ? Set.of() : subscriptionsMessage.getDailyBars(); 140 | } 141 | 142 | @Override 143 | public void setUpdatedBarSubscriptions(Set symbols) { 144 | symbols = symbols == null ? Set.of() : symbols; 145 | setSubscriptions(getUpdatedBarSubscriptions(), symbols, 146 | set -> newEmptyCryptoSubscriptionsMessage().withUpdatedBars(set)); 147 | } 148 | 149 | @Override 150 | public Set getUpdatedBarSubscriptions() { 151 | return subscriptionsMessage == null ? Set.of() : subscriptionsMessage.getUpdatedBars(); 152 | } 153 | 154 | @Override 155 | public void setOrderBookSubscriptions(Set symbols) { 156 | symbols = symbols == null ? Set.of() : symbols; 157 | setSubscriptions(getOrderBookSubscriptions(), symbols, 158 | set -> newEmptyCryptoSubscriptionsMessage().withOrderBooks(set)); 159 | } 160 | 161 | @Override 162 | public Set getOrderBookSubscriptions() { 163 | return subscriptionsMessage == null ? Set.of() : subscriptionsMessage.getOrderBooks(); 164 | } 165 | 166 | private CryptoSubscriptionsMessage newEmptyCryptoSubscriptionsMessage() { 167 | return new CryptoSubscriptionsMessage() 168 | .withTrades(null) 169 | .withQuotes(null) 170 | .withMinuteBars(null) 171 | .withDailyBars(null) 172 | .withUpdatedBars(null) 173 | .withOrderBooks(null); 174 | } 175 | } 176 | -------------------------------------------------------------------------------- /src/main/java/net/jacobpeterson/alpaca/rest/trader/AlpacaTraderAPI.java: -------------------------------------------------------------------------------- 1 | package net.jacobpeterson.alpaca.rest.trader; 2 | 3 | import net.jacobpeterson.alpaca.model.util.apitype.TraderAPIEndpointType; 4 | import net.jacobpeterson.alpaca.openapi.trader.ApiClient; 5 | import net.jacobpeterson.alpaca.openapi.trader.api.AccountActivitiesApi; 6 | import net.jacobpeterson.alpaca.openapi.trader.api.AccountConfigurationsApi; 7 | import net.jacobpeterson.alpaca.openapi.trader.api.AccountsApi; 8 | import net.jacobpeterson.alpaca.openapi.trader.api.AssetsApi; 9 | import net.jacobpeterson.alpaca.openapi.trader.api.CalendarApi; 10 | import net.jacobpeterson.alpaca.openapi.trader.api.ClockApi; 11 | import net.jacobpeterson.alpaca.openapi.trader.api.CorporateActionsApi; 12 | import net.jacobpeterson.alpaca.openapi.trader.api.OrdersApi; 13 | import net.jacobpeterson.alpaca.openapi.trader.api.PortfolioHistoryApi; 14 | import net.jacobpeterson.alpaca.openapi.trader.api.PositionsApi; 15 | import net.jacobpeterson.alpaca.openapi.trader.api.WatchlistsApi; 16 | import net.jacobpeterson.alpaca.openapi.trader.model.NonTradeActivities; 17 | import net.jacobpeterson.alpaca.openapi.trader.model.TradingActivities; 18 | import okhttp3.OkHttpClient; 19 | 20 | import static com.google.common.base.Preconditions.checkArgument; 21 | import static com.google.common.base.Preconditions.checkNotNull; 22 | 23 | /** 24 | * {@link AlpacaTraderAPI} is the class used to interface with the Alpaca Trader API endpoints. This class is 25 | * thread-safe. 26 | */ 27 | public class AlpacaTraderAPI { 28 | 29 | // Set validation predicates for 'anyOf' and 'oneOf' models 30 | static { 31 | // 'AccountConfigurationsApi' models 32 | TradingActivities.isValid = jsonElement -> jsonElement.getAsJsonObject().has("type"); 33 | NonTradeActivities.isValid = jsonElement -> !jsonElement.getAsJsonObject().has("type"); 34 | } 35 | 36 | private final ApiClient apiClient; 37 | private AccountActivitiesApi accountActivities; 38 | private AccountConfigurationsApi accountConfigurations; 39 | private AccountsApi accounts; 40 | private AssetsApi assets; 41 | private CalendarApi calendar; 42 | private ClockApi clock; 43 | private CorporateActionsApi traderCorporateActions; 44 | private OrdersApi orders; 45 | private PortfolioHistoryApi portfolioHistory; 46 | private PositionsApi positions; 47 | private WatchlistsApi watchlists; 48 | 49 | /** 50 | * Instantiates a new {@link AlpacaTraderAPI}. 51 | * 52 | * @param traderKeyID the Trader key ID 53 | * @param traderSecretKey the Trader secret key 54 | * @param traderOAuthToken the Trader OAuth token 55 | * @param traderAPIEndpointType the {@link TraderAPIEndpointType} 56 | * @param okHttpClient an existing {@link OkHttpClient} or null to create a new default 57 | * instance 58 | */ 59 | @SuppressWarnings("UnnecessaryDefault") 60 | public AlpacaTraderAPI(String traderKeyID, String traderSecretKey, String traderOAuthToken, 61 | TraderAPIEndpointType traderAPIEndpointType, OkHttpClient okHttpClient) { 62 | checkArgument((traderKeyID != null && traderSecretKey != null) ^ (traderOAuthToken != null), 63 | "You must specify a (trader key ID and secret key) or an (OAuth token)!"); 64 | checkNotNull(traderAPIEndpointType); 65 | checkNotNull(okHttpClient); 66 | 67 | apiClient = new ApiClient(okHttpClient); 68 | apiClient.setServerIndex(switch (traderAPIEndpointType) { 69 | case PAPER -> 0; 70 | case LIVE -> 1; 71 | default -> throw new UnsupportedOperationException(); 72 | }); 73 | if (traderKeyID != null && traderSecretKey != null) { 74 | apiClient.addDefaultHeader("APCA-API-KEY-ID", traderKeyID); 75 | apiClient.addDefaultHeader("APCA-API-SECRET-KEY", traderSecretKey); 76 | } else { 77 | apiClient.addDefaultHeader("Authorization", "Bearer " + traderOAuthToken); 78 | } 79 | } 80 | 81 | /** 82 | * Gets the internal {@link ApiClient}. 83 | * 84 | * @return the {@link ApiClient} 85 | */ 86 | public ApiClient getInternalAPIClient() { 87 | return apiClient; 88 | } 89 | 90 | /** 91 | * Gets the {@link AccountActivitiesApi}. Lazily instantiated. 92 | * 93 | * @return the {@link AccountActivitiesApi} 94 | */ 95 | public synchronized AccountActivitiesApi accountActivities() { 96 | if (accountActivities == null) { 97 | accountActivities = new AccountActivitiesApi(apiClient); 98 | } 99 | return accountActivities; 100 | } 101 | 102 | /** 103 | * Gets the {@link AccountConfigurationsApi}. Lazily instantiated. 104 | * 105 | * @return the {@link AccountConfigurationsApi} 106 | */ 107 | public synchronized AccountConfigurationsApi accountConfigurations() { 108 | if (accountConfigurations == null) { 109 | accountConfigurations = new AccountConfigurationsApi(apiClient); 110 | } 111 | return accountConfigurations; 112 | } 113 | 114 | /** 115 | * Gets the {@link AccountsApi}. Lazily instantiated. 116 | * 117 | * @return {@link AccountsApi} 118 | */ 119 | public synchronized AccountsApi accounts() { 120 | if (accounts == null) { 121 | accounts = new AccountsApi(apiClient); 122 | } 123 | return accounts; 124 | } 125 | 126 | /** 127 | * Gets the {@link AssetsApi}. Lazily instantiated. 128 | * 129 | * @return {@link AssetsApi} 130 | */ 131 | public synchronized AssetsApi assets() { 132 | if (assets == null) { 133 | assets = new AssetsApi(apiClient); 134 | } 135 | return assets; 136 | } 137 | 138 | /** 139 | * Gets the {@link CalendarApi}. Lazily instantiated. 140 | * 141 | * @return {@link CalendarApi} 142 | */ 143 | public synchronized CalendarApi calendar() { 144 | if (calendar == null) { 145 | calendar = new CalendarApi(apiClient); 146 | } 147 | return calendar; 148 | } 149 | 150 | /** 151 | * Gets the {@link ClockApi}. Lazily instantiated. 152 | * 153 | * @return {@link ClockApi} 154 | */ 155 | public synchronized ClockApi clock() { 156 | if (clock == null) { 157 | clock = new ClockApi(apiClient); 158 | } 159 | return clock; 160 | } 161 | 162 | /** 163 | * Gets the {@link CorporateActionsApi}. Lazily instantiated. 164 | * 165 | * @return the {@link CorporateActionsApi} 166 | */ 167 | public synchronized CorporateActionsApi traderCorporateActions() { 168 | if (traderCorporateActions == null) { 169 | traderCorporateActions = new CorporateActionsApi(apiClient); 170 | } 171 | return traderCorporateActions; 172 | } 173 | 174 | /** 175 | * Gets the {@link OrdersApi}. Lazily instantiated. 176 | * 177 | * @return {@link OrdersApi} 178 | */ 179 | public synchronized OrdersApi orders() { 180 | if (orders == null) { 181 | orders = new OrdersApi(apiClient); 182 | } 183 | return orders; 184 | } 185 | 186 | /** 187 | * Gets the {@link PortfolioHistoryApi}. Lazily instantiated. 188 | * 189 | * @return {@link PortfolioHistoryApi} 190 | */ 191 | public synchronized PortfolioHistoryApi portfolioHistory() { 192 | if (portfolioHistory == null) { 193 | portfolioHistory = new PortfolioHistoryApi(apiClient); 194 | } 195 | return portfolioHistory; 196 | } 197 | 198 | /** 199 | * Gets the {@link PositionsApi}. Lazily instantiated. 200 | * 201 | * @return {@link PositionsApi} 202 | */ 203 | public synchronized PositionsApi positions() { 204 | if (positions == null) { 205 | positions = new PositionsApi(apiClient); 206 | } 207 | return positions; 208 | } 209 | 210 | /** 211 | * Gets the {@link WatchlistsApi}. Lazily instantiated. 212 | * 213 | * @return {@link WatchlistsApi} 214 | */ 215 | public synchronized WatchlistsApi watchlists() { 216 | if (watchlists == null) { 217 | watchlists = new WatchlistsApi(apiClient); 218 | } 219 | return watchlists; 220 | } 221 | } 222 | -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # 4 | # Copyright © 2015 the original authors. 5 | # 6 | # Licensed under the Apache License, Version 2.0 (the "License"); 7 | # you may not use this file except in compliance with the License. 8 | # You may obtain a copy of the License at 9 | # 10 | # https://www.apache.org/licenses/LICENSE-2.0 11 | # 12 | # Unless required by applicable law or agreed to in writing, software 13 | # distributed under the License is distributed on an "AS IS" BASIS, 14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | # See the License for the specific language governing permissions and 16 | # limitations under the License. 17 | # 18 | # SPDX-License-Identifier: Apache-2.0 19 | # 20 | 21 | ############################################################################## 22 | # 23 | # Gradle start up script for POSIX generated by Gradle. 24 | # 25 | # Important for running: 26 | # 27 | # (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is 28 | # noncompliant, but you have some other compliant shell such as ksh or 29 | # bash, then to run this script, type that shell name before the whole 30 | # command line, like: 31 | # 32 | # ksh Gradle 33 | # 34 | # Busybox and similar reduced shells will NOT work, because this script 35 | # requires all of these POSIX shell features: 36 | # * functions; 37 | # * expansions «$var», «${var}», «${var:-default}», «${var+SET}», 38 | # «${var#prefix}», «${var%suffix}», and «$( cmd )»; 39 | # * compound commands having a testable exit status, especially «case»; 40 | # * various built-in commands including «command», «set», and «ulimit». 41 | # 42 | # Important for patching: 43 | # 44 | # (2) This script targets any POSIX shell, so it avoids extensions provided 45 | # by Bash, Ksh, etc; in particular arrays are avoided. 46 | # 47 | # The "traditional" practice of packing multiple parameters into a 48 | # space-separated string is a well documented source of bugs and security 49 | # problems, so this is (mostly) avoided, by progressively accumulating 50 | # options in "$@", and eventually passing that to Java. 51 | # 52 | # Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, 53 | # and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; 54 | # see the in-line comments for details. 55 | # 56 | # There are tweaks for specific operating systems such as AIX, CygWin, 57 | # Darwin, MinGW, and NonStop. 58 | # 59 | # (3) This script is generated from the Groovy template 60 | # https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt 61 | # within the Gradle project. 62 | # 63 | # You can find Gradle at https://github.com/gradle/gradle/. 64 | # 65 | ############################################################################## 66 | 67 | # Attempt to set APP_HOME 68 | 69 | # Resolve links: $0 may be a link 70 | app_path=$0 71 | 72 | # Need this for daisy-chained symlinks. 73 | while 74 | APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path 75 | [ -h "$app_path" ] 76 | do 77 | ls=$( ls -ld "$app_path" ) 78 | link=${ls#*' -> '} 79 | case $link in #( 80 | /*) app_path=$link ;; #( 81 | *) app_path=$APP_HOME$link ;; 82 | esac 83 | done 84 | 85 | # This is normally unused 86 | # shellcheck disable=SC2034 87 | APP_BASE_NAME=${0##*/} 88 | # Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) 89 | APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit 90 | 91 | # Use the maximum available, or set MAX_FD != -1 to use that value. 92 | MAX_FD=maximum 93 | 94 | warn () { 95 | echo "$*" 96 | } >&2 97 | 98 | die () { 99 | echo 100 | echo "$*" 101 | echo 102 | exit 1 103 | } >&2 104 | 105 | # OS specific support (must be 'true' or 'false'). 106 | cygwin=false 107 | msys=false 108 | darwin=false 109 | nonstop=false 110 | case "$( uname )" in #( 111 | CYGWIN* ) cygwin=true ;; #( 112 | Darwin* ) darwin=true ;; #( 113 | MSYS* | MINGW* ) msys=true ;; #( 114 | NONSTOP* ) nonstop=true ;; 115 | esac 116 | 117 | 118 | 119 | # Determine the Java command to use to start the JVM. 120 | if [ -n "$JAVA_HOME" ] ; then 121 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 122 | # IBM's JDK on AIX uses strange locations for the executables 123 | JAVACMD=$JAVA_HOME/jre/sh/java 124 | else 125 | JAVACMD=$JAVA_HOME/bin/java 126 | fi 127 | if [ ! -x "$JAVACMD" ] ; then 128 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 129 | 130 | Please set the JAVA_HOME variable in your environment to match the 131 | location of your Java installation." 132 | fi 133 | else 134 | JAVACMD=java 135 | if ! command -v java >/dev/null 2>&1 136 | then 137 | die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 138 | 139 | Please set the JAVA_HOME variable in your environment to match the 140 | location of your Java installation." 141 | fi 142 | fi 143 | 144 | # Increase the maximum file descriptors if we can. 145 | if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then 146 | case $MAX_FD in #( 147 | max*) 148 | # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. 149 | # shellcheck disable=SC2039,SC3045 150 | MAX_FD=$( ulimit -H -n ) || 151 | warn "Could not query maximum file descriptor limit" 152 | esac 153 | case $MAX_FD in #( 154 | '' | soft) :;; #( 155 | *) 156 | # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. 157 | # shellcheck disable=SC2039,SC3045 158 | ulimit -n "$MAX_FD" || 159 | warn "Could not set maximum file descriptor limit to $MAX_FD" 160 | esac 161 | fi 162 | 163 | # Collect all arguments for the java command, stacking in reverse order: 164 | # * args from the command line 165 | # * the main class name 166 | # * -classpath 167 | # * -D...appname settings 168 | # * --module-path (only if needed) 169 | # * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. 170 | 171 | # For Cygwin or MSYS, switch paths to Windows format before running java 172 | if "$cygwin" || "$msys" ; then 173 | APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) 174 | 175 | JAVACMD=$( cygpath --unix "$JAVACMD" ) 176 | 177 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 178 | for arg do 179 | if 180 | case $arg in #( 181 | -*) false ;; # don't mess with options #( 182 | /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath 183 | [ -e "$t" ] ;; #( 184 | *) false ;; 185 | esac 186 | then 187 | arg=$( cygpath --path --ignore --mixed "$arg" ) 188 | fi 189 | # Roll the args list around exactly as many times as the number of 190 | # args, so each arg winds up back in the position where it started, but 191 | # possibly modified. 192 | # 193 | # NB: a `for` loop captures its iteration list before it begins, so 194 | # changing the positional parameters here affects neither the number of 195 | # iterations, nor the values presented in `arg`. 196 | shift # remove old arg 197 | set -- "$@" "$arg" # push replacement arg 198 | done 199 | fi 200 | 201 | 202 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 203 | DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' 204 | 205 | # Collect all arguments for the java command: 206 | # * DEFAULT_JVM_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, 207 | # and any embedded shellness will be escaped. 208 | # * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be 209 | # treated as '${Hostname}' itself on the command line. 210 | 211 | set -- \ 212 | "-Dorg.gradle.appname=$APP_BASE_NAME" \ 213 | -jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \ 214 | "$@" 215 | 216 | # Stop when "xargs" is not available. 217 | if ! command -v xargs >/dev/null 2>&1 218 | then 219 | die "xargs is not available" 220 | fi 221 | 222 | # Use "xargs" to parse quoted args. 223 | # 224 | # With -n1 it outputs one arg per line, with the quotes and backslashes removed. 225 | # 226 | # In Bash we could simply go: 227 | # 228 | # readarray ARGS < <( xargs -n1 <<<"$var" ) && 229 | # set -- "${ARGS[@]}" "$@" 230 | # 231 | # but POSIX shell has neither arrays nor command substitution, so instead we 232 | # post-process each arg (as a line of input to sed) to backslash-escape any 233 | # character that might be a shell metacharacter, then use eval to reverse 234 | # that process (while maintaining the separation between arguments), and wrap 235 | # the whole thing up as a single "set" statement. 236 | # 237 | # This will of course break if any of these variables contains a newline or 238 | # an unmatched quote. 239 | # 240 | 241 | eval "set -- $( 242 | printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | 243 | xargs -n1 | 244 | sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | 245 | tr '\n' ' ' 246 | )" '"$@"' 247 | 248 | exec "$JAVACMD" "$@" 249 | -------------------------------------------------------------------------------- /src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/MarketDataWebsocket.java: -------------------------------------------------------------------------------- 1 | package net.jacobpeterson.alpaca.websocket.marketdata; 2 | 3 | import com.google.gson.JsonElement; 4 | import com.google.gson.JsonObject; 5 | import net.jacobpeterson.alpaca.model.websocket.marketdata.model.MarketDataMessage; 6 | import net.jacobpeterson.alpaca.model.websocket.marketdata.model.control.ErrorMessage; 7 | import net.jacobpeterson.alpaca.model.websocket.marketdata.model.control.SuccessMessage; 8 | import net.jacobpeterson.alpaca.websocket.AlpacaWebsocket; 9 | import okhttp3.HttpUrl; 10 | import okhttp3.OkHttpClient; 11 | import okhttp3.WebSocket; 12 | import org.jetbrains.annotations.NotNull; 13 | import org.slf4j.Logger; 14 | import org.slf4j.LoggerFactory; 15 | 16 | import java.util.Set; 17 | import java.util.function.Function; 18 | 19 | import static com.google.common.collect.Sets.difference; 20 | import static com.google.gson.JsonParser.parseString; 21 | import static java.util.concurrent.TimeUnit.SECONDS; 22 | import static net.jacobpeterson.alpaca.model.websocket.marketdata.model.control.SuccessMessageType.AUTHENTICATED; 23 | import static net.jacobpeterson.alpaca.openapi.marketdata.JSON.getGson; 24 | 25 | /** 26 | * {@link MarketDataWebsocket} is an abstract {@link AlpacaWebsocket} implementation for 27 | * Market Data Streaming. 28 | * 29 | * @param the 'message type' type 30 | * @param the 'subscription message' type 31 | * @param the 'listener' type 32 | */ 33 | public abstract class MarketDataWebsocket extends AlpacaWebsocket 34 | implements MarketDataWebsocketInterface { 35 | 36 | private static final Logger LOGGER = LoggerFactory.getLogger(MarketDataWebsocket.class); 37 | private static final Set AUTH_FAILURE_MESSAGES = Set.of("auth failed", "auth timeout", "not authenticated"); 38 | 39 | protected final String authKey; 40 | protected final String authSecret; 41 | protected final Class messageTypeClass; 42 | protected final Class subscriptionsMessageClass; 43 | protected S subscriptionsMessage; 44 | protected L listener; 45 | 46 | /** 47 | * Instantiates a new {@link MarketDataWebsocket}. 48 | * 49 | * @param okHttpClient the {@link OkHttpClient} 50 | * @param websocketURL the websocket {@link HttpUrl} 51 | * @param websocketMarketDataTypeName the websocket market data type name {@link String} 52 | * @param traderKeyID the trader key ID 53 | * @param traderSecretKey the trader secret key 54 | * @param brokerAPIKey the broker API key 55 | * @param brokerAPISecret the broker API secret 56 | * @param messageTypeClass the {@link T} message type {@link Class} 57 | * @param subscriptionsMessageClass the {@link S} subscription message {@link Class} 58 | */ 59 | protected MarketDataWebsocket(OkHttpClient okHttpClient, HttpUrl websocketURL, String websocketMarketDataTypeName, 60 | String traderKeyID, String traderSecretKey, String brokerAPIKey, String brokerAPISecret, 61 | Class messageTypeClass, Class subscriptionsMessageClass) { 62 | super(okHttpClient, websocketURL, websocketMarketDataTypeName + " Market Data"); 63 | final boolean traderKeysGiven = traderKeyID != null && traderSecretKey != null; 64 | this.authKey = traderKeysGiven ? traderKeyID : brokerAPIKey; 65 | this.authSecret = traderKeysGiven ? traderSecretKey : brokerAPISecret; 66 | this.messageTypeClass = messageTypeClass; 67 | this.subscriptionsMessageClass = subscriptionsMessageClass; 68 | } 69 | 70 | @Override 71 | protected void cleanupState() { 72 | super.cleanupState(); 73 | subscriptionsMessage = null; 74 | } 75 | 76 | @Override 77 | protected void onConnection() { 78 | sendAuthenticationMessage(); 79 | } 80 | 81 | @Override 82 | protected void onReconnection() { 83 | sendAuthenticationMessage(); 84 | if (waitForAuthorization(5, SECONDS) && subscriptionsMessage != null) { 85 | sendSubscriptionMessage(subscriptionsMessage, true); 86 | } 87 | } 88 | 89 | @Override 90 | protected void sendAuthenticationMessage() { 91 | // Ensure that the authorization Future exists 92 | getAuthorizationFuture(); 93 | 94 | final JsonObject authObject = new JsonObject(); 95 | authObject.addProperty("action", "auth"); 96 | authObject.addProperty("key", authKey); 97 | authObject.addProperty("secret", authSecret); 98 | 99 | LOGGER.info("{} websocket sending authentication message...", websocketName); 100 | sendWebsocketMessage(authObject.toString()); 101 | } 102 | 103 | @Override 104 | public void onMessage(@NotNull WebSocket webSocket, @NotNull String message) { // Text framing 105 | LOGGER.trace("Websocket message received: {}", message); 106 | 107 | // Loop through message array and handle each message according to its type 108 | for (JsonElement arrayElement : parseString(message).getAsJsonArray()) { 109 | final JsonObject messageObject = arrayElement.getAsJsonObject(); 110 | final T messageType = getGson().fromJson(messageObject.get("T"), messageTypeClass); 111 | if (isSuccessMessageType(messageType)) { 112 | final SuccessMessage successMessage = getGson().fromJson(messageObject, SuccessMessage.class); 113 | if (successMessage.getMessageType() == AUTHENTICATED) { 114 | LOGGER.info("{} websocket authenticated.", websocketName); 115 | authenticated = true; 116 | if (authenticationMessageFuture != null) { 117 | authenticationMessageFuture.complete(true); 118 | } 119 | } 120 | } else if (isErrorMessageType(messageType)) { 121 | final ErrorMessage errorMessage = getGson().fromJson(messageObject, ErrorMessage.class); 122 | if (AUTH_FAILURE_MESSAGES.contains(errorMessage.getMessage()) && authenticationMessageFuture != null) { 123 | authenticated = false; 124 | authenticationMessageFuture.complete(false); 125 | throw new RuntimeException(websocketName + " websocket authentication failed!"); 126 | } else { 127 | throw new RuntimeException(websocketName + " websocket error! Message: " + errorMessage); 128 | } 129 | } else if (isSubscriptionMessageType(messageType)) { 130 | subscriptionsMessage = getGson().fromJson(messageObject, subscriptionsMessageClass); 131 | } else if (listener != null) { 132 | callListenerWithMessage(messageType, messageObject); 133 | } 134 | } 135 | } 136 | 137 | /** 138 | * Sets the websocket stream's subscriptions for a specific message type. 139 | * 140 | * @param previousSubscriptions the previous subscriptions symbol {@link Set} 141 | * @param newSubscriptions the new subscriptions symbol {@link Set} 142 | * @param subscriptionUpdateObjectCreator the subscription update object creator {@link Function} 143 | */ 144 | protected void setSubscriptions(@NotNull Set previousSubscriptions, 145 | @NotNull Set newSubscriptions, @NotNull Function, S> subscriptionUpdateObjectCreator) { 146 | // Unsubscribe from previous subscriptions 147 | final Set unsubscribeSet = difference(previousSubscriptions, newSubscriptions); 148 | if (!unsubscribeSet.isEmpty()) { 149 | sendSubscriptionMessage(subscriptionUpdateObjectCreator.apply(unsubscribeSet), false); 150 | } 151 | // Subscribe to new subscriptions 152 | final Set subscribeSet = difference(newSubscriptions, previousSubscriptions); 153 | if (!subscribeSet.isEmpty()) { 154 | sendSubscriptionMessage(subscriptionUpdateObjectCreator.apply(subscribeSet), true); 155 | } 156 | } 157 | 158 | private void sendSubscriptionMessage(S subscriptionsMessage, boolean subscribe) { 159 | final JsonObject subscribeObject = getGson().toJsonTree(subscriptionsMessage).getAsJsonObject(); 160 | subscribeObject.addProperty("action", subscribe ? "subscribe" : "unsubscribe"); 161 | sendWebsocketMessage(getGson().toJson(subscribeObject)); 162 | } 163 | 164 | /** 165 | * Whether the given messageType is "success". 166 | * 167 | * @param messageType the message type 168 | * 169 | * @return a boolean 170 | */ 171 | protected abstract boolean isSuccessMessageType(T messageType); 172 | 173 | /** 174 | * Whether the given messageType is "error". 175 | * 176 | * @param messageType the message type 177 | * 178 | * @return a boolean 179 | */ 180 | protected abstract boolean isErrorMessageType(T messageType); 181 | 182 | /** 183 | * Whether the given messageType is "subscription". 184 | * 185 | * @param messageType the message type 186 | * 187 | * @return a boolean 188 | */ 189 | protected abstract boolean isSubscriptionMessageType(T messageType); 190 | 191 | /** 192 | * Calls the {@link #listener} with a {@link MarketDataMessage} from a {@link JsonObject}. 193 | * 194 | * @param messageType the message type 195 | * @param messageObject the message {@link JsonObject} 196 | */ 197 | protected abstract void callListenerWithMessage(T messageType, JsonObject messageObject); 198 | } 199 | -------------------------------------------------------------------------------- /src/main/java/net/jacobpeterson/alpaca/rest/broker/events/EventsApiSSE.java: -------------------------------------------------------------------------------- 1 | package net.jacobpeterson.alpaca.rest.broker.events; 2 | 3 | import com.google.gson.reflect.TypeToken; 4 | import net.jacobpeterson.alpaca.openapi.broker.ApiClient; 5 | import net.jacobpeterson.alpaca.openapi.broker.ApiException; 6 | import net.jacobpeterson.alpaca.openapi.broker.api.EventsApi; 7 | import net.jacobpeterson.alpaca.openapi.broker.model.AccountStatusEvent; 8 | import net.jacobpeterson.alpaca.openapi.broker.model.JournalStatusEvent; 9 | import net.jacobpeterson.alpaca.openapi.broker.model.NonTradeActivityEvent; 10 | import net.jacobpeterson.alpaca.openapi.broker.model.SubscribeToAdminActionSSE200ResponseInner; 11 | import net.jacobpeterson.alpaca.openapi.broker.model.TradeUpdateEvent; 12 | import net.jacobpeterson.alpaca.openapi.broker.model.TradeUpdateEventV2; 13 | import net.jacobpeterson.alpaca.openapi.broker.model.TransferStatusEvent; 14 | import net.jacobpeterson.alpaca.util.sse.SSEListener; 15 | import net.jacobpeterson.alpaca.util.sse.SSERequest; 16 | import okhttp3.Request; 17 | import okhttp3.Response; 18 | import okhttp3.sse.EventSource; 19 | import okhttp3.sse.EventSourceListener; 20 | import okhttp3.sse.EventSources; 21 | import org.jetbrains.annotations.NotNull; 22 | import org.jetbrains.annotations.Nullable; 23 | import org.slf4j.Logger; 24 | import org.slf4j.LoggerFactory; 25 | 26 | import java.lang.reflect.Type; 27 | import java.time.LocalDate; 28 | import java.time.OffsetDateTime; 29 | import java.util.UUID; 30 | 31 | import static java.util.concurrent.TimeUnit.SECONDS; 32 | import static net.jacobpeterson.alpaca.openapi.broker.JSON.getGson; 33 | 34 | /** 35 | * {@link EventsApiSSE} add SSE support to {@link EventsApi}. 36 | */ 37 | public class EventsApiSSE { 38 | 39 | private static final Logger LOGGER = LoggerFactory.getLogger(EventsApiSSE.class); 40 | 41 | private final EventsApi eventsAPI; 42 | private final EventSource.Factory eventSourceFactory; 43 | 44 | /** 45 | * Instantiates a new {@link EventsApiSSE}. 46 | * 47 | * @param apiClient the api client 48 | */ 49 | public EventsApiSSE(ApiClient apiClient) { 50 | eventsAPI = new EventsApi(apiClient); 51 | eventSourceFactory = EventSources.createFactory(apiClient.getHttpClient().newBuilder() // Shallow clone 52 | .readTimeout(0, SECONDS) 53 | .writeTimeout(0, SECONDS) 54 | .build()); 55 | } 56 | 57 | /** 58 | * See {@link EventsApi#getV1EventsNta(String, String, String, Integer, Integer, String, String, Boolean, UUID)}. 59 | * 60 | * @return a {@link SSERequest} 61 | */ 62 | public SSERequest subscribeToNonTradingActivitiesEvents(String id, String since, String until, Integer sinceId, 63 | Integer untilId, String sinceUlid, String untilUlid, Boolean includePreprocessing, UUID groupId, 64 | SSEListener sseListener) throws ApiException { 65 | final Request request = eventsAPI.getV1EventsNtaCall(id, since, until, sinceId, untilId, sinceUlid, untilUlid, 66 | includePreprocessing, groupId, null).request(); 67 | return new SSERequest(eventSourceFactory.newEventSource(request, createEventSourceListener(sseListener, 68 | new TypeToken() {}.getType()))); 69 | } 70 | 71 | /** 72 | * See {@link EventsApi#subscribeToAdminActionSSE(OffsetDateTime, OffsetDateTime, String, String)}. 73 | * 74 | * @return a {@link SSERequest} 75 | */ 76 | public SSERequest subscribeToAdminAction(OffsetDateTime since, OffsetDateTime until, String sinceId, 77 | String untilId, SSEListener sseListener) throws ApiException { 78 | final Request request = eventsAPI.subscribeToAdminActionSSECall(since, until, sinceId, untilId, null).request(); 79 | return new SSERequest(eventSourceFactory.newEventSource(request, createEventSourceListener(sseListener, 80 | new TypeToken() {}.getType()))); 81 | } 82 | 83 | /** 84 | * See 85 | * {@link EventsApi#subscribeToJournalStatusSSE(OffsetDateTime, OffsetDateTime, Integer, Integer, String, String, 86 | * String)}. 87 | * 88 | * @return a {@link SSERequest} 89 | */ 90 | public SSERequest subscribeToJournalStatus(OffsetDateTime since, OffsetDateTime until, Integer sinceId, 91 | Integer untilId, String sinceUlid, String untilUlid, String id, 92 | SSEListener sseListener) throws ApiException { 93 | final Request request = eventsAPI.subscribeToJournalStatusSSECall(since, until, sinceId, untilId, sinceUlid, 94 | untilUlid, id, null).request(); 95 | return new SSERequest(eventSourceFactory.newEventSource(request, createEventSourceListener(sseListener, 96 | new TypeToken() {}.getType()))); 97 | } 98 | 99 | /** 100 | * See {@link EventsApi#subscribeToTradeSSE(OffsetDateTime, OffsetDateTime, Integer, Integer, String, String)}. 101 | * 102 | * @return a {@link SSERequest} 103 | */ 104 | public SSERequest subscribeToTrade(OffsetDateTime since, OffsetDateTime until, Integer sinceId, 105 | Integer untilId, String sinceUlid, String untilUlid, SSEListener sseListener) 106 | throws ApiException { 107 | final Request request = eventsAPI.subscribeToTradeSSECall(since, until, sinceId, untilId, sinceUlid, untilUlid, 108 | null).request(); 109 | return new SSERequest(eventSourceFactory.newEventSource(request, createEventSourceListener(sseListener, 110 | new TypeToken() {}.getType()))); 111 | } 112 | 113 | /** 114 | * See {@link EventsApi#subscribeToTradeV2SSE(OffsetDateTime, OffsetDateTime, String, String)}. 115 | * 116 | * @return a {@link SSERequest} 117 | */ 118 | public SSERequest subscribeToTradeV2(OffsetDateTime since, OffsetDateTime until, String sinceId, 119 | String untilId, SSEListener sseListener) throws ApiException { 120 | final Request request = eventsAPI.subscribeToTradeV2SSECall(since, until, sinceId, untilId, null).request(); 121 | return new SSERequest(eventSourceFactory.newEventSource(request, createEventSourceListener(sseListener, 122 | new TypeToken() {}.getType()))); 123 | } 124 | 125 | /** 126 | * See 127 | * {@link EventsApi#subscribeToTransferStatusSSE(OffsetDateTime, OffsetDateTime, Integer, Integer, String, 128 | * String)}. 129 | * 130 | * @return a {@link SSERequest} 131 | */ 132 | public SSERequest subscribeToTransferStatus(OffsetDateTime since, OffsetDateTime until, Integer sinceId, 133 | Integer untilId, String sinceUlid, String untilUlid, SSEListener sseListener) 134 | throws ApiException { 135 | final Request request = eventsAPI.subscribeToTransferStatusSSECall(since, until, sinceId, untilId, sinceUlid, 136 | untilUlid, null).request(); 137 | return new SSERequest(eventSourceFactory.newEventSource(request, createEventSourceListener(sseListener, 138 | new TypeToken() {}.getType()))); 139 | } 140 | 141 | /** 142 | * See 143 | * {@link EventsApi#suscribeToAccountStatusSSE(LocalDate, LocalDate, Integer, Integer, String, String, String)}. 144 | * 145 | * @return a {@link SSERequest} 146 | */ 147 | public SSERequest subscribeToAccountStatus(LocalDate since, LocalDate until, Integer sinceId, Integer untilId, 148 | String sinceUlid, String untilUlid, String id, SSEListener sseListener) 149 | throws ApiException { 150 | final Request request = eventsAPI.suscribeToAccountStatusSSECall(since, until, sinceId, untilId, sinceUlid, 151 | untilUlid, id, null).request(); 152 | return new SSERequest(eventSourceFactory.newEventSource(request, createEventSourceListener(sseListener, 153 | new TypeToken() {}.getType()))); 154 | } 155 | 156 | private EventSourceListener createEventSourceListener(SSEListener sseListener, Type responseTypeToken) { 157 | return new EventSourceListener() { 158 | @Override 159 | public void onClosed(@NotNull EventSource eventSource) { 160 | LOGGER.info("Event source closed: eventSource={}", eventSource); 161 | sseListener.onClose(); 162 | } 163 | 164 | @Override 165 | public void onEvent(@NotNull EventSource eventSource, @Nullable String id, @Nullable String type, 166 | @NotNull String data) { 167 | LOGGER.trace("Event source event: eventSource={} id={}, type={}, data={}", eventSource, id, type, data); 168 | sseListener.onMessage(getGson().fromJson(data, responseTypeToken)); 169 | } 170 | 171 | @Override 172 | public void onFailure(@NotNull EventSource eventSource, @Nullable Throwable throwable, 173 | @Nullable Response response) { 174 | if (throwable != null && throwable.getMessage().equals("canceled")) { 175 | sseListener.onClose(); 176 | return; 177 | } 178 | LOGGER.error("Event source failure: eventSource={} throwable={}, response={}", 179 | eventSource, throwable, response); 180 | sseListener.onError(throwable, response); 181 | } 182 | 183 | @Override 184 | public void onOpen(@NotNull EventSource eventSource, @NotNull Response response) { 185 | LOGGER.info("Event source opened: {}", eventSource); 186 | sseListener.onOpen(); 187 | } 188 | }; 189 | } 190 | } 191 | -------------------------------------------------------------------------------- /src/main/java/net/jacobpeterson/alpaca/websocket/AlpacaWebsocket.java: -------------------------------------------------------------------------------- 1 | package net.jacobpeterson.alpaca.websocket; 2 | 3 | import okhttp3.HttpUrl; 4 | import okhttp3.OkHttpClient; 5 | import okhttp3.Request; 6 | import okhttp3.Response; 7 | import okhttp3.WebSocket; 8 | import okhttp3.WebSocketListener; 9 | import org.jetbrains.annotations.NotNull; 10 | import org.jetbrains.annotations.Nullable; 11 | import org.slf4j.Logger; 12 | import org.slf4j.LoggerFactory; 13 | 14 | import java.time.Duration; 15 | import java.util.concurrent.CompletableFuture; 16 | import java.util.concurrent.Future; 17 | 18 | import static com.google.common.base.Preconditions.checkNotNull; 19 | import static java.util.concurrent.ForkJoinPool.commonPool; 20 | 21 | /** 22 | * {@link AlpacaWebsocket} represents an abstract websocket for Alpaca. 23 | */ 24 | public abstract class AlpacaWebsocket extends WebSocketListener implements AlpacaWebsocketInterface { 25 | 26 | /** 27 | * Defines a websocket normal closure code. 28 | * 29 | * @see WebSocket#close(int, String) 30 | */ 31 | public static final int WEBSOCKET_NORMAL_CLOSURE_CODE = 1000; 32 | 33 | /** 34 | * Defines a websocket normal closure message. 35 | * 36 | * @see WebSocket#close(int, String) 37 | */ 38 | public static final String WEBSOCKET_NORMAL_CLOSURE_MESSAGE = "Normal closure"; 39 | 40 | /** 41 | * Defines the maximum number of reconnection attempts to be made by an {@link AlpacaWebsocket}. 42 | */ 43 | public static int MAX_RECONNECT_ATTEMPTS = 5; 44 | 45 | /** 46 | * Defines the sleep interval {@link Duration} between reconnection attempts made by an {@link AlpacaWebsocket}. 47 | */ 48 | public static Duration RECONNECT_SLEEP_INTERVAL = Duration.ofSeconds(1); 49 | 50 | private static final Logger LOGGER = LoggerFactory.getLogger(AlpacaWebsocket.class); 51 | 52 | protected final OkHttpClient okHttpClient; 53 | protected final HttpUrl websocketURL; 54 | protected final String websocketName; 55 | protected AlpacaWebsocketStateListener alpacaWebsocketStateListener; 56 | private WebSocket websocket; 57 | protected boolean connected; 58 | protected boolean authenticated; 59 | protected CompletableFuture authenticationMessageFuture; 60 | protected boolean intentionalClose; 61 | protected int reconnectAttempts; 62 | 63 | protected boolean automaticallyReconnect; 64 | 65 | /** 66 | * Instantiates a {@link AlpacaWebsocket}. 67 | * 68 | * @param okHttpClient the {@link OkHttpClient} 69 | * @param websocketURL the websocket {@link HttpUrl} 70 | * @param websocketName the websocket name 71 | */ 72 | protected AlpacaWebsocket(OkHttpClient okHttpClient, HttpUrl websocketURL, String websocketName) { 73 | checkNotNull(okHttpClient); 74 | checkNotNull(websocketURL); 75 | checkNotNull(websocketName); 76 | 77 | this.okHttpClient = okHttpClient; 78 | this.websocketURL = websocketURL; 79 | this.websocketName = websocketName; 80 | 81 | automaticallyReconnect = true; 82 | } 83 | 84 | @Override 85 | public void connect() { 86 | if (!isConnected()) { 87 | final Request websocketRequest = new Request.Builder() 88 | .url(websocketURL) 89 | .get() 90 | .build(); 91 | websocket = okHttpClient.newWebSocket(websocketRequest, this); 92 | } 93 | } 94 | 95 | @Override 96 | public void disconnect() { 97 | if (websocket != null && isConnected()) { 98 | intentionalClose = true; 99 | websocket.close(WEBSOCKET_NORMAL_CLOSURE_CODE, WEBSOCKET_NORMAL_CLOSURE_MESSAGE); 100 | } else { 101 | cleanupState(); 102 | } 103 | } 104 | 105 | @Override 106 | public boolean isConnected() { 107 | return connected; 108 | } 109 | 110 | @Override 111 | public boolean isAuthenticated() { 112 | return authenticated; 113 | } 114 | 115 | @Override 116 | public void onOpen(@NotNull WebSocket webSocket, @NotNull Response response) { 117 | connected = true; 118 | LOGGER.info("{} websocket response: response={}", websocketName, response); 119 | // Call 'onConnection' or 'onReconnection' async to avoid any potential deadlocking since this is called 120 | // in sync with 'onMessage' in OkHttp's 'WebSocketListener' 121 | commonPool().execute(() -> { 122 | if (reconnectAttempts > 0) { 123 | onReconnection(); 124 | } else { 125 | onConnection(); 126 | } 127 | }); 128 | if (alpacaWebsocketStateListener != null) { 129 | alpacaWebsocketStateListener.onOpen(response); 130 | } 131 | } 132 | 133 | @Override 134 | public void onClosed(@NotNull WebSocket webSocket, int code, @NotNull String reason) { 135 | connected = false; 136 | if (intentionalClose) { 137 | LOGGER.info("{} websocket closed. code={}, reason={}", websocketName, code, reason); 138 | cleanupState(); 139 | } else { 140 | LOGGER.error("{} websocket closed unintentionally! code={}, reason={}", websocketName, code, reason); 141 | handleReconnectionAttempt(); 142 | } 143 | if (alpacaWebsocketStateListener != null) { 144 | alpacaWebsocketStateListener.onClosed(code, reason); 145 | } 146 | } 147 | 148 | @Override 149 | public void onFailure(@NotNull WebSocket webSocket, @NotNull Throwable cause, @Nullable Response response) { 150 | if (intentionalClose) { 151 | onClosed(webSocket, WEBSOCKET_NORMAL_CLOSURE_CODE, WEBSOCKET_NORMAL_CLOSURE_MESSAGE); 152 | return; 153 | } 154 | 155 | LOGGER.error("{} websocket failure!", websocketName, cause); 156 | // A websocket failure occurs when either there is a connection failure or when the client throws 157 | // an exception when receiving a message. In either case, OkHttp will close the websocket connection, 158 | // so try to reopen it. 159 | connected = false; 160 | handleReconnectionAttempt(); 161 | if (alpacaWebsocketStateListener != null) { 162 | alpacaWebsocketStateListener.onFailure(cause); 163 | } 164 | } 165 | 166 | /** 167 | * Attempts to reconnect the disconnected {@link #websocket} asynchronously. 168 | */ 169 | private void handleReconnectionAttempt() { 170 | if (!automaticallyReconnect) { 171 | return; 172 | } 173 | if (reconnectAttempts < MAX_RECONNECT_ATTEMPTS) { 174 | LOGGER.info("Attempting to reconnect {} websocket in {} seconds... (attempt {} of {})", 175 | websocketName, RECONNECT_SLEEP_INTERVAL.toSeconds(), reconnectAttempts + 1, MAX_RECONNECT_ATTEMPTS); 176 | reconnectAttempts++; 177 | commonPool().execute(() -> { 178 | try { 179 | Thread.sleep(RECONNECT_SLEEP_INTERVAL.toMillis()); 180 | } catch (InterruptedException interruptedException) { 181 | return; 182 | } 183 | connect(); 184 | }); 185 | } else { 186 | LOGGER.error("Exhausted {} reconnection attempts. Not attempting to reconnect.", MAX_RECONNECT_ATTEMPTS); 187 | cleanupState(); 188 | } 189 | } 190 | 191 | /** 192 | * Cleans up this instance's state variables. 193 | */ 194 | protected void cleanupState() { 195 | websocket = null; 196 | connected = false; 197 | authenticated = false; 198 | if (authenticationMessageFuture != null && !authenticationMessageFuture.isDone()) { 199 | authenticationMessageFuture.complete(false); 200 | } 201 | authenticationMessageFuture = null; 202 | intentionalClose = false; 203 | reconnectAttempts = 0; 204 | } 205 | 206 | /** 207 | * Sends a message to the underlying {@link #websocket}. 208 | * 209 | * @param message the message 210 | */ 211 | protected void sendWebsocketMessage(String message) { 212 | if (!isConnected()) { 213 | throw new IllegalStateException("This websocket must be connected before send a message!"); 214 | } 215 | LOGGER.trace("Websocket message sent: {}", message); 216 | websocket.send(message); 217 | } 218 | 219 | /** 220 | * Called asynchronously when a websocket connection is made. 221 | */ 222 | protected abstract void onConnection(); 223 | 224 | /** 225 | * Called asynchronously when a websocket reconnection is made after unintentional disconnection. 226 | */ 227 | protected abstract void onReconnection(); 228 | 229 | /** 230 | * Sends an authentication message to authenticate this websocket stream. 231 | */ 232 | protected abstract void sendAuthenticationMessage(); 233 | 234 | @Override 235 | public Future getAuthorizationFuture() { 236 | if (authenticationMessageFuture == null) { 237 | authenticationMessageFuture = new CompletableFuture<>(); 238 | } 239 | return authenticationMessageFuture; 240 | } 241 | 242 | @Override 243 | public void setAlpacaWebsocketStateListener(AlpacaWebsocketStateListener alpacaWebsocketStateListener) { 244 | this.alpacaWebsocketStateListener = alpacaWebsocketStateListener; 245 | } 246 | 247 | @Override 248 | public boolean doesAutomaticallyReconnect() { 249 | return automaticallyReconnect; 250 | } 251 | 252 | @Override 253 | public void setAutomaticallyReconnect(boolean automaticallyReconnect) { 254 | this.automaticallyReconnect = automaticallyReconnect; 255 | } 256 | 257 | public OkHttpClient getOkHttpClient() { 258 | return okHttpClient; 259 | } 260 | 261 | public HttpUrl getWebsocketURL() { 262 | return websocketURL; 263 | } 264 | 265 | public WebSocket getWebsocket() { 266 | return websocket; 267 | } 268 | } 269 | -------------------------------------------------------------------------------- /src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/streams/stock/StockMarketDataWebsocket.java: -------------------------------------------------------------------------------- 1 | package net.jacobpeterson.alpaca.websocket.marketdata.streams.stock; 2 | 3 | import com.google.gson.JsonObject; 4 | import net.jacobpeterson.alpaca.model.util.apitype.MarketDataWebsocketSourceType; 5 | import net.jacobpeterson.alpaca.model.websocket.marketdata.streams.stock.model.StockMarketDataMessageType; 6 | import net.jacobpeterson.alpaca.model.websocket.marketdata.streams.stock.model.bar.StockBarMessage; 7 | import net.jacobpeterson.alpaca.model.websocket.marketdata.streams.stock.model.control.StockSubscriptionsMessage; 8 | import net.jacobpeterson.alpaca.model.websocket.marketdata.streams.stock.model.limituplimitdownband.StockLimitUpLimitDownBandMessage; 9 | import net.jacobpeterson.alpaca.model.websocket.marketdata.streams.stock.model.quote.StockQuoteMessage; 10 | import net.jacobpeterson.alpaca.model.websocket.marketdata.streams.stock.model.trade.StockTradeMessage; 11 | import net.jacobpeterson.alpaca.model.websocket.marketdata.streams.stock.model.tradecancelerror.StockTradeCancelErrorMessage; 12 | import net.jacobpeterson.alpaca.model.websocket.marketdata.streams.stock.model.tradecorrection.StockTradeCorrectionMessage; 13 | import net.jacobpeterson.alpaca.model.websocket.marketdata.streams.stock.model.tradingstatus.StockTradingStatusMessage; 14 | import net.jacobpeterson.alpaca.websocket.marketdata.MarketDataWebsocket; 15 | import okhttp3.HttpUrl; 16 | import okhttp3.OkHttpClient; 17 | 18 | import java.util.Set; 19 | 20 | import static net.jacobpeterson.alpaca.model.websocket.marketdata.streams.stock.model.StockMarketDataMessageType.ERROR; 21 | import static net.jacobpeterson.alpaca.model.websocket.marketdata.streams.stock.model.StockMarketDataMessageType.SUBSCRIPTION; 22 | import static net.jacobpeterson.alpaca.model.websocket.marketdata.streams.stock.model.StockMarketDataMessageType.SUCCESS; 23 | import static net.jacobpeterson.alpaca.openapi.marketdata.JSON.getGson; 24 | 25 | /** 26 | * {@link StockMarketDataWebsocket} is an implementation for {@link StockMarketDataWebsocketInterface}. 27 | */ 28 | public class StockMarketDataWebsocket 29 | extends MarketDataWebsocket 30 | implements StockMarketDataWebsocketInterface { 31 | 32 | private static HttpUrl createWebsocketURL(boolean isSandbox, 33 | MarketDataWebsocketSourceType marketDataWebsocketSourceType) { 34 | return new HttpUrl.Builder() 35 | .scheme("https") 36 | .host(isSandbox ? "stream.data.sandbox.alpaca.markets" : "stream.data.alpaca.markets") 37 | .addPathSegment("v2") 38 | .addPathSegment(marketDataWebsocketSourceType.toString()) 39 | .build(); 40 | } 41 | 42 | /** 43 | * Instantiates a new {@link StockMarketDataWebsocket}. 44 | * 45 | * @param okHttpClient the {@link OkHttpClient} 46 | * @param traderKeyID the trader key ID 47 | * @param traderSecretKey the trader secret key 48 | * @param brokerAPIKey the broker API key 49 | * @param brokerAPISecret the broker API secret 50 | * @param marketDataWebsocketSourceType the {@link MarketDataWebsocketSourceType} 51 | */ 52 | public StockMarketDataWebsocket(OkHttpClient okHttpClient, String traderKeyID, String traderSecretKey, 53 | String brokerAPIKey, String brokerAPISecret, MarketDataWebsocketSourceType marketDataWebsocketSourceType) { 54 | super(okHttpClient, 55 | createWebsocketURL(brokerAPIKey != null && brokerAPISecret != null, marketDataWebsocketSourceType), 56 | "Stock", traderKeyID, traderSecretKey, brokerAPIKey, brokerAPISecret, 57 | StockMarketDataMessageType.class, StockSubscriptionsMessage.class); 58 | } 59 | 60 | @Override 61 | protected boolean isSuccessMessageType(StockMarketDataMessageType messageType) { 62 | return messageType == SUCCESS; 63 | } 64 | 65 | @Override 66 | protected boolean isErrorMessageType(StockMarketDataMessageType messageType) { 67 | return messageType == ERROR; 68 | } 69 | 70 | @Override 71 | protected boolean isSubscriptionMessageType(StockMarketDataMessageType messageType) { 72 | return messageType == SUBSCRIPTION; 73 | } 74 | 75 | @Override 76 | protected void callListenerWithMessage(StockMarketDataMessageType messageType, JsonObject messageObject) { 77 | switch (messageType) { 78 | case TRADES: 79 | listener.onTrade(getGson().fromJson(messageObject, 80 | StockTradeMessage.class)); 81 | break; 82 | case QUOTES: 83 | listener.onQuote(getGson().fromJson(messageObject, 84 | StockQuoteMessage.class)); 85 | break; 86 | case MINUTE_BARS: 87 | listener.onMinuteBar(getGson().fromJson(messageObject, 88 | StockBarMessage.class)); 89 | break; 90 | case DAILY_BARS: 91 | listener.onDailyBar(getGson().fromJson(messageObject, 92 | StockBarMessage.class)); 93 | break; 94 | case UPDATED_BARS: 95 | listener.onUpdatedBar(getGson().fromJson(messageObject, 96 | StockBarMessage.class)); 97 | break; 98 | case TRADE_CORRECTIONS: 99 | listener.onTradeCorrection(getGson().fromJson(messageObject, 100 | StockTradeCorrectionMessage.class)); 101 | break; 102 | case TRADE_CANCEL_ERRORS: 103 | listener.onTradeCancelError(getGson().fromJson(messageObject, 104 | StockTradeCancelErrorMessage.class)); 105 | break; 106 | case LIMIT_UP_LIMIT_DOWN_BANDS: 107 | listener.onLimitUpLimitDownBand(getGson().fromJson(messageObject, 108 | StockLimitUpLimitDownBandMessage.class)); 109 | break; 110 | case TRADING_STATUSES: 111 | listener.onTradingStatus(getGson().fromJson(messageObject, 112 | StockTradingStatusMessage.class)); 113 | break; 114 | default: 115 | throw new UnsupportedOperationException(); 116 | } 117 | } 118 | 119 | @Override 120 | public void setListener(StockMarketDataListener listener) { 121 | this.listener = listener; 122 | } 123 | 124 | @Override 125 | public void setTradeSubscriptions(Set symbols) { 126 | symbols = symbols == null ? Set.of() : symbols; 127 | setSubscriptions(getTradeSubscriptions(), symbols, 128 | set -> newEmptyStockSubscriptionsMessage().withTrades(set)); 129 | } 130 | 131 | @Override 132 | public Set getTradeSubscriptions() { 133 | return subscriptionsMessage == null ? Set.of() : subscriptionsMessage.getTrades(); 134 | } 135 | 136 | @Override 137 | public void setQuoteSubscriptions(Set symbols) { 138 | symbols = symbols == null ? Set.of() : symbols; 139 | setSubscriptions(getQuoteSubscriptions(), symbols, 140 | set -> newEmptyStockSubscriptionsMessage().withQuotes(set)); 141 | } 142 | 143 | @Override 144 | public Set getQuoteSubscriptions() { 145 | return subscriptionsMessage == null ? Set.of() : subscriptionsMessage.getQuotes(); 146 | } 147 | 148 | @Override 149 | public void setMinuteBarSubscriptions(Set symbols) { 150 | symbols = symbols == null ? Set.of() : symbols; 151 | setSubscriptions(getMinuteBarSubscriptions(), symbols, 152 | set -> newEmptyStockSubscriptionsMessage().withMinuteBars(set)); 153 | } 154 | 155 | @Override 156 | public Set getMinuteBarSubscriptions() { 157 | return subscriptionsMessage == null ? Set.of() : subscriptionsMessage.getMinuteBars(); 158 | } 159 | 160 | @Override 161 | public void setDailyBarSubscriptions(Set symbols) { 162 | symbols = symbols == null ? Set.of() : symbols; 163 | setSubscriptions(getDailyBarSubscriptions(), symbols, 164 | set -> newEmptyStockSubscriptionsMessage().withDailyBars(set)); 165 | } 166 | 167 | @Override 168 | public Set getDailyBarSubscriptions() { 169 | return subscriptionsMessage == null ? Set.of() : subscriptionsMessage.getDailyBars(); 170 | } 171 | 172 | @Override 173 | public void setUpdatedBarSubscriptions(Set symbols) { 174 | symbols = symbols == null ? Set.of() : symbols; 175 | setSubscriptions(getUpdatedBarSubscriptions(), symbols, 176 | set -> newEmptyStockSubscriptionsMessage().withUpdatedBars(set)); 177 | } 178 | 179 | @Override 180 | public Set getUpdatedBarSubscriptions() { 181 | return subscriptionsMessage == null ? Set.of() : subscriptionsMessage.getUpdatedBars(); 182 | } 183 | 184 | @Override 185 | public void setLimitUpLimitDownBandSubscriptions(Set symbols) { 186 | symbols = symbols == null ? Set.of() : symbols; 187 | setSubscriptions(getLimitUpLimitDownBandSubscriptions(), symbols, 188 | set -> newEmptyStockSubscriptionsMessage().withLimitUpLimitDownBands(set)); 189 | } 190 | 191 | @Override 192 | public Set getLimitUpLimitDownBandSubscriptions() { 193 | return subscriptionsMessage == null ? Set.of() : subscriptionsMessage.getLimitUpLimitDownBands(); 194 | } 195 | 196 | @Override 197 | public void setTradingStatuseSubscriptions(Set symbols) { 198 | symbols = symbols == null ? Set.of() : symbols; 199 | setSubscriptions(getTradingStatuseSubscriptions(), symbols, 200 | set -> newEmptyStockSubscriptionsMessage().withTradingStatuses(set)); 201 | } 202 | 203 | @Override 204 | public Set getTradingStatuseSubscriptions() { 205 | return subscriptionsMessage == null ? Set.of() : subscriptionsMessage.getTradingStatuses(); 206 | } 207 | 208 | private StockSubscriptionsMessage newEmptyStockSubscriptionsMessage() { 209 | return new StockSubscriptionsMessage() 210 | .withTrades(null) 211 | .withQuotes(null) 212 | .withMinuteBars(null) 213 | .withDailyBars(null) 214 | .withUpdatedBars(null) 215 | .withLimitUpLimitDownBands(null) 216 | .withTradingStatuses(null); 217 | } 218 | } 219 | -------------------------------------------------------------------------------- /src/main/java/net/jacobpeterson/alpaca/rest/broker/AlpacaBrokerAPI.java: -------------------------------------------------------------------------------- 1 | package net.jacobpeterson.alpaca.rest.broker; 2 | 3 | import com.google.gson.JsonElement; 4 | import net.jacobpeterson.alpaca.model.util.apitype.BrokerAPIEndpointType; 5 | import net.jacobpeterson.alpaca.openapi.broker.ApiClient; 6 | import net.jacobpeterson.alpaca.openapi.broker.api.AccountsApi; 7 | import net.jacobpeterson.alpaca.openapi.broker.api.AssetsApi; 8 | import net.jacobpeterson.alpaca.openapi.broker.api.CalendarApi; 9 | import net.jacobpeterson.alpaca.openapi.broker.api.ClockApi; 10 | import net.jacobpeterson.alpaca.openapi.broker.api.CorporateActionsApi; 11 | import net.jacobpeterson.alpaca.openapi.broker.api.CountryInfoApi; 12 | import net.jacobpeterson.alpaca.openapi.broker.api.DocumentsApi; 13 | import net.jacobpeterson.alpaca.openapi.broker.api.FundingApi; 14 | import net.jacobpeterson.alpaca.openapi.broker.api.FundingWalletsApi; 15 | import net.jacobpeterson.alpaca.openapi.broker.api.JournalsApi; 16 | import net.jacobpeterson.alpaca.openapi.broker.api.KycApi; 17 | import net.jacobpeterson.alpaca.openapi.broker.api.LogosApi; 18 | import net.jacobpeterson.alpaca.openapi.broker.api.OAuthApi; 19 | import net.jacobpeterson.alpaca.openapi.broker.api.RebalancingApi; 20 | import net.jacobpeterson.alpaca.openapi.broker.api.ReportingApi; 21 | import net.jacobpeterson.alpaca.openapi.broker.api.TradingApi; 22 | import net.jacobpeterson.alpaca.openapi.broker.api.WatchlistApi; 23 | import net.jacobpeterson.alpaca.openapi.broker.model.AdminActionLegacyNote; 24 | import net.jacobpeterson.alpaca.openapi.broker.model.AdminActionLiquidation; 25 | import net.jacobpeterson.alpaca.openapi.broker.model.AdminActionTransactionCancel; 26 | import net.jacobpeterson.alpaca.openapi.broker.model.JNLC; 27 | import net.jacobpeterson.alpaca.openapi.broker.model.JNLS; 28 | import net.jacobpeterson.alpaca.openapi.broker.model.NonTradeActivity; 29 | import net.jacobpeterson.alpaca.openapi.broker.model.TradeActivity; 30 | import net.jacobpeterson.alpaca.rest.broker.events.EventsApiSSE; 31 | import okhttp3.OkHttpClient; 32 | 33 | import java.util.function.BiFunction; 34 | 35 | import static com.google.common.base.Preconditions.checkNotNull; 36 | import static net.jacobpeterson.alpaca.util.apikey.APIKeyUtil.createBrokerAPIAuthKey; 37 | 38 | /** 39 | * {@link AlpacaBrokerAPI} is the class used to interface with the Alpaca Broker API endpoints. This class is 40 | * thread-safe. 41 | */ 42 | public class AlpacaBrokerAPI { 43 | 44 | // Set validation predicates for 'anyOf' and 'oneOf' models 45 | static { 46 | // 'AccountsApi' models 47 | TradeActivity.isValid = jsonElement -> jsonElement.getAsJsonObject().has("type"); 48 | NonTradeActivity.isValid = jsonElement -> !jsonElement.getAsJsonObject().has("type"); 49 | 50 | // 'JournalsApi' models 51 | final BiFunction transferPredicate = (jnlc, jsonElement) -> { 52 | final String entryType = jsonElement.getAsJsonObject().get("entry_type").getAsString(); 53 | return jnlc ? entryType.equals("JNLC") : entryType.equals("JNLS"); 54 | }; 55 | JNLS.isValid = jsonElement -> transferPredicate.apply(true, jsonElement); 56 | JNLC.isValid = jsonElement -> transferPredicate.apply(false, jsonElement); 57 | 58 | // 'EventsApi' models 59 | AdminActionLegacyNote.isValid = jsonElement -> jsonElement.getAsJsonObject() 60 | .get("type").getAsString().contains("note_admin_event"); 61 | AdminActionLiquidation.isValid = jsonElement -> jsonElement.getAsJsonObject() 62 | .get("type").getAsString().equals("liquidation_admin_event"); 63 | AdminActionTransactionCancel.isValid = jsonElement -> jsonElement.getAsJsonObject() 64 | .get("type").getAsString().equals("transaction_cancel_admin_event"); 65 | } 66 | 67 | private final ApiClient apiClient; 68 | private AccountsApi accounts; 69 | private AssetsApi assets; 70 | private CalendarApi calendar; 71 | private ClockApi clock; 72 | private CorporateActionsApi corporateActions; 73 | private CountryInfoApi countryInfo; 74 | private DocumentsApi documents; 75 | private EventsApiSSE events; 76 | private FundingApi funding; 77 | private FundingWalletsApi fundingWallets; 78 | private JournalsApi journals; 79 | private KycApi kyc; 80 | private LogosApi logos; 81 | private OAuthApi oAuth; 82 | private RebalancingApi rebalancing; 83 | private ReportingApi reporting; 84 | private TradingApi trading; 85 | private WatchlistApi watchlist; 86 | 87 | /** 88 | * Instantiates a new {@link AlpacaBrokerAPI}. 89 | * 90 | * @param brokerAPIKey the Broker API key 91 | * @param brokerAPISecret the Broker API secret 92 | * @param brokerAPIEndpointType the {@link BrokerAPIEndpointType} 93 | * @param okHttpClient an existing {@link OkHttpClient} or null to create a new default 94 | * instance 95 | */ 96 | @SuppressWarnings("UnnecessaryDefault") 97 | public AlpacaBrokerAPI(String brokerAPIKey, String brokerAPISecret, BrokerAPIEndpointType brokerAPIEndpointType, 98 | OkHttpClient okHttpClient) { 99 | checkNotNull(brokerAPIKey); 100 | checkNotNull(brokerAPISecret); 101 | checkNotNull(brokerAPIEndpointType); 102 | checkNotNull(okHttpClient); 103 | 104 | apiClient = new ApiClient(okHttpClient); 105 | apiClient.setServerIndex(switch (brokerAPIEndpointType) { 106 | case SANDBOX -> 0; 107 | case PRODUCTION -> 1; 108 | default -> throw new UnsupportedOperationException(); 109 | }); 110 | apiClient.addDefaultHeader("Authorization", "Basic " + 111 | createBrokerAPIAuthKey(brokerAPIKey, brokerAPISecret)); 112 | } 113 | 114 | /** 115 | * Gets the internal {@link ApiClient}. 116 | * 117 | * @return the {@link ApiClient} 118 | */ 119 | public ApiClient getInternalAPIClient() { 120 | return apiClient; 121 | } 122 | 123 | /** 124 | * Gets the {@link AccountsApi}. Lazily instantiated. 125 | * 126 | * @return the {@link AccountsApi} 127 | */ 128 | public synchronized AccountsApi accounts() { 129 | if (accounts == null) { 130 | accounts = new AccountsApi(apiClient); 131 | } 132 | return accounts; 133 | } 134 | 135 | /** 136 | * Gets the {@link AssetsApi}. Lazily instantiated. 137 | * 138 | * @return the {@link AssetsApi} 139 | */ 140 | public synchronized AssetsApi assets() { 141 | if (assets == null) { 142 | assets = new AssetsApi(apiClient); 143 | } 144 | return assets; 145 | } 146 | 147 | /** 148 | * Gets the {@link CalendarApi}. Lazily instantiated. 149 | * 150 | * @return the {@link CalendarApi} 151 | */ 152 | public synchronized CalendarApi calendar() { 153 | if (calendar == null) { 154 | calendar = new CalendarApi(apiClient); 155 | } 156 | return calendar; 157 | } 158 | 159 | /** 160 | * Gets the {@link ClockApi}. Lazily instantiated. 161 | * 162 | * @return the {@link ClockApi} 163 | */ 164 | public synchronized ClockApi clock() { 165 | if (clock == null) { 166 | clock = new ClockApi(apiClient); 167 | } 168 | return clock; 169 | } 170 | 171 | /** 172 | * Gets the {@link CorporateActionsApi}. Lazily instantiated. 173 | * 174 | * @return the {@link CorporateActionsApi} 175 | */ 176 | public synchronized CorporateActionsApi corporateActions() { 177 | if (corporateActions == null) { 178 | corporateActions = new CorporateActionsApi(apiClient); 179 | } 180 | return corporateActions; 181 | } 182 | 183 | /** 184 | * Gets the {@link CountryInfoApi}. Lazily instantiated. 185 | * 186 | * @return the {@link CountryInfoApi} 187 | */ 188 | public synchronized CountryInfoApi countryInfo() { 189 | if (countryInfo == null) { 190 | countryInfo = new CountryInfoApi(apiClient); 191 | } 192 | return countryInfo; 193 | } 194 | 195 | /** 196 | * Gets the {@link DocumentsApi}. Lazily instantiated. 197 | * 198 | * @return the {@link DocumentsApi} 199 | */ 200 | public synchronized DocumentsApi documents() { 201 | if (documents == null) { 202 | documents = new DocumentsApi(apiClient); 203 | } 204 | return documents; 205 | } 206 | 207 | /** 208 | * Gets the {@link EventsApiSSE}. Lazily instantiated. 209 | * 210 | * @return the {@link EventsApiSSE} 211 | */ 212 | public synchronized EventsApiSSE events() { 213 | if (events == null) { 214 | events = new EventsApiSSE(apiClient); 215 | } 216 | return events; 217 | } 218 | 219 | /** 220 | * Gets the {@link FundingApi}. Lazily instantiated. 221 | * 222 | * @return the {@link FundingApi} 223 | */ 224 | public synchronized FundingApi funding() { 225 | if (funding == null) { 226 | funding = new FundingApi(apiClient); 227 | } 228 | return funding; 229 | } 230 | 231 | /** 232 | * Gets the {@link FundingWalletsApi}. Lazily instantiated. 233 | * 234 | * @return the {@link FundingWalletsApi} 235 | */ 236 | public synchronized FundingWalletsApi fundingWallets() { 237 | if (fundingWallets == null) { 238 | fundingWallets = new FundingWalletsApi(apiClient); 239 | } 240 | return fundingWallets; 241 | } 242 | 243 | /** 244 | * Gets the {@link JournalsApi}. Lazily instantiated. 245 | * 246 | * @return the {@link JournalsApi} 247 | */ 248 | public synchronized JournalsApi journals() { 249 | if (journals == null) { 250 | journals = new JournalsApi(apiClient); 251 | } 252 | return journals; 253 | } 254 | 255 | /** 256 | * Gets the {@link KycApi}. Lazily instantiated. 257 | * 258 | * @return the {@link KycApi} 259 | */ 260 | public synchronized KycApi kyc() { 261 | if (kyc == null) { 262 | kyc = new KycApi(apiClient); 263 | } 264 | return kyc; 265 | } 266 | 267 | /** 268 | * Gets the {@link LogosApi}. Lazily instantiated. 269 | * 270 | * @return the {@link LogosApi} 271 | */ 272 | public synchronized LogosApi logos() { 273 | if (logos == null) { 274 | logos = new LogosApi(apiClient); 275 | } 276 | return logos; 277 | } 278 | 279 | /** 280 | * Gets the {@link OAuthApi}. Lazily instantiated. 281 | * 282 | * @return the {@link OAuthApi} 283 | */ 284 | public synchronized OAuthApi oAuth() { 285 | if (oAuth == null) { 286 | oAuth = new OAuthApi(apiClient); 287 | } 288 | return oAuth; 289 | } 290 | 291 | /** 292 | * Gets the {@link RebalancingApi}. Lazily instantiated. 293 | * 294 | * @return the {@link RebalancingApi} 295 | */ 296 | public synchronized RebalancingApi rebalancing() { 297 | if (rebalancing == null) { 298 | rebalancing = new RebalancingApi(apiClient); 299 | } 300 | return rebalancing; 301 | } 302 | 303 | /** 304 | * Gets the {@link ReportingApi}. Lazily instantiated. 305 | * 306 | * @return the {@link ReportingApi} 307 | */ 308 | public synchronized ReportingApi reporting() { 309 | if (reporting == null) { 310 | reporting = new ReportingApi(apiClient); 311 | } 312 | return reporting; 313 | } 314 | 315 | /** 316 | * Gets the {@link TradingApi}. Lazily instantiated. 317 | * 318 | * @return the {@link TradingApi} 319 | */ 320 | public synchronized TradingApi trading() { 321 | if (trading == null) { 322 | trading = new TradingApi(apiClient); 323 | } 324 | return trading; 325 | } 326 | 327 | /** 328 | * Gets the {@link WatchlistApi}. Lazily instantiated. 329 | * 330 | * @return the {@link WatchlistApi} 331 | */ 332 | public synchronized WatchlistApi watchlist() { 333 | if (watchlist == null) { 334 | watchlist = new WatchlistApi(apiClient); 335 | } 336 | return watchlist; 337 | } 338 | } 339 | -------------------------------------------------------------------------------- /src/main/java/net/jacobpeterson/alpaca/AlpacaAPI.java: -------------------------------------------------------------------------------- 1 | package net.jacobpeterson.alpaca; 2 | 3 | import net.jacobpeterson.alpaca.model.util.apitype.BrokerAPIEndpointType; 4 | import net.jacobpeterson.alpaca.model.util.apitype.MarketDataWebsocketSourceType; 5 | import net.jacobpeterson.alpaca.model.util.apitype.TraderAPIEndpointType; 6 | import net.jacobpeterson.alpaca.rest.broker.AlpacaBrokerAPI; 7 | import net.jacobpeterson.alpaca.rest.marketdata.AlpacaMarketDataAPI; 8 | import net.jacobpeterson.alpaca.rest.trader.AlpacaTraderAPI; 9 | import net.jacobpeterson.alpaca.websocket.marketdata.streams.crypto.CryptoMarketDataWebsocket; 10 | import net.jacobpeterson.alpaca.websocket.marketdata.streams.crypto.CryptoMarketDataWebsocketInterface; 11 | import net.jacobpeterson.alpaca.websocket.marketdata.streams.news.NewsMarketDataWebsocket; 12 | import net.jacobpeterson.alpaca.websocket.marketdata.streams.news.NewsMarketDataWebsocketInterface; 13 | import net.jacobpeterson.alpaca.websocket.marketdata.streams.stock.StockMarketDataWebsocket; 14 | import net.jacobpeterson.alpaca.websocket.marketdata.streams.stock.StockMarketDataWebsocketInterface; 15 | import net.jacobpeterson.alpaca.websocket.updates.UpdatesWebsocket; 16 | import net.jacobpeterson.alpaca.websocket.updates.UpdatesWebsocketInterface; 17 | import okhttp3.OkHttpClient; 18 | import okhttp3.logging.HttpLoggingInterceptor; 19 | import org.slf4j.Logger; 20 | import org.slf4j.LoggerFactory; 21 | 22 | import static net.jacobpeterson.alpaca.model.util.apitype.BrokerAPIEndpointType.SANDBOX; 23 | import static net.jacobpeterson.alpaca.model.util.apitype.MarketDataWebsocketSourceType.IEX; 24 | import static net.jacobpeterson.alpaca.model.util.apitype.TraderAPIEndpointType.PAPER; 25 | import static okhttp3.logging.HttpLoggingInterceptor.Level.BODY; 26 | 27 | /** 28 | * {@link AlpacaAPI} is the main class used to interface with the various Alpaca API endpoints. If you are using the 29 | * Trading or Market Data APIs for a single Alpaca account or if you are using the Broker API, you will generally only 30 | * need one instance of this class. However, if you are using the Trading API with OAuth to act on behalf of an Alpaca 31 | * account, this class is optimized so that it can be instantiated quickly, especially when an existing 32 | * {@link OkHttpClient} is given in the constructor. Additionally, all API endpoint instances are instantiated lazily. 33 | * This class is thread-safe. 34 | * 35 | * @see Alpaca Docs 36 | */ 37 | public class AlpacaAPI { 38 | 39 | private static final Logger LOGGER = LoggerFactory.getLogger(AlpacaAPI.class); 40 | 41 | private final String traderKeyID; 42 | private final String traderSecretKey; 43 | private final String traderOAuthToken; 44 | private final TraderAPIEndpointType traderAPIEndpointType; 45 | private final MarketDataWebsocketSourceType marketDataWebsocketSourceType; 46 | private final String brokerAPIKey; 47 | private final String brokerAPISecret; 48 | private final BrokerAPIEndpointType brokerAPIEndpointType; 49 | private final OkHttpClient okHttpClient; 50 | 51 | private AlpacaTraderAPI trader; 52 | private AlpacaMarketDataAPI marketData; 53 | private AlpacaBrokerAPI broker; 54 | private UpdatesWebsocket updatesWebsocket; 55 | private StockMarketDataWebsocket stockMarketDataWebsocket; 56 | private CryptoMarketDataWebsocket cryptoMarketDataWebsocket; 57 | private NewsMarketDataWebsocket newsMarketDataWebsocket; 58 | 59 | /** 60 | * Instantiates a new {@link AlpacaAPI}. Use this constructor if you are using the Trading or Market Data APIs for a 61 | * single Alpaca account. 62 | * 63 | * @param traderKeyID the Trader key ID 64 | * @param traderSecretKey the Trader secret key 65 | * @param traderAPIEndpointType the {@link TraderAPIEndpointType} 66 | * @param marketDataWebsocketSourceType the {@link MarketDataWebsocketSourceType} 67 | */ 68 | public AlpacaAPI(String traderKeyID, String traderSecretKey, TraderAPIEndpointType traderAPIEndpointType, 69 | MarketDataWebsocketSourceType marketDataWebsocketSourceType) { 70 | this(traderKeyID, traderSecretKey, null, traderAPIEndpointType, marketDataWebsocketSourceType, null, null, null, 71 | null); 72 | } 73 | 74 | /** 75 | * Instantiates a new {@link AlpacaAPI}. Use this constructor if you are using the Trading or Market Data APIs for a 76 | * single Alpaca account and a custom {@link OkHttpClient} instance. 77 | * 78 | * @param traderKeyID the Trader key ID 79 | * @param traderSecretKey the Trader secret key 80 | * @param traderAPIEndpointType the {@link TraderAPIEndpointType} 81 | * @param marketDataWebsocketSourceType the {@link MarketDataWebsocketSourceType} 82 | * @param okHttpClient an existing {@link OkHttpClient} or null to create a new 83 | * default instance 84 | */ 85 | public AlpacaAPI(String traderKeyID, String traderSecretKey, TraderAPIEndpointType traderAPIEndpointType, 86 | MarketDataWebsocketSourceType marketDataWebsocketSourceType, OkHttpClient okHttpClient) { 87 | this(traderKeyID, traderSecretKey, null, traderAPIEndpointType, marketDataWebsocketSourceType, null, null, null, 88 | okHttpClient); 89 | } 90 | 91 | /** 92 | * Instantiates a new {@link AlpacaAPI}. Use this constructor if you are using the Trading API with OAuth to act on 93 | * behalf of an Alpaca account. 94 | * 95 | * @param traderOAuthToken the Trader OAuth token 96 | * @param traderAPIEndpointType the {@link TraderAPIEndpointType} 97 | */ 98 | public AlpacaAPI(String traderOAuthToken, TraderAPIEndpointType traderAPIEndpointType) { 99 | this(null, null, traderOAuthToken, traderAPIEndpointType, null, null, null, null, null); 100 | } 101 | 102 | /** 103 | * Instantiates a new {@link AlpacaAPI}. Use this constructor if you are using the Trading API with OAuth to act on 104 | * behalf of an Alpaca account and a custom {@link OkHttpClient} instance. 105 | * 106 | * @param traderOAuthToken the Trader OAuth token 107 | * @param traderAPIEndpointType the {@link TraderAPIEndpointType} 108 | * @param okHttpClient an existing {@link OkHttpClient} or null to create a new default 109 | * instance 110 | */ 111 | public AlpacaAPI(String traderOAuthToken, TraderAPIEndpointType traderAPIEndpointType, OkHttpClient okHttpClient) { 112 | this(null, null, traderOAuthToken, traderAPIEndpointType, null, null, null, null, okHttpClient); 113 | } 114 | 115 | /** 116 | * Instantiates a new {@link AlpacaAPI}. Use this constructor if you are using the Broker API. 117 | * 118 | * @param brokerAPIKey the Broker API key 119 | * @param brokerAPISecret the Broker API secret 120 | * @param brokerAPIEndpointType the {@link BrokerAPIEndpointType} 121 | */ 122 | public AlpacaAPI(String brokerAPIKey, String brokerAPISecret, BrokerAPIEndpointType brokerAPIEndpointType) { 123 | this(null, null, null, null, null, brokerAPIKey, brokerAPISecret, brokerAPIEndpointType, null); 124 | } 125 | 126 | /** 127 | * Instantiates a new {@link AlpacaAPI}. Use this constructor if you are using the Broker API and a custom 128 | * {@link OkHttpClient} instance. 129 | * 130 | * @param brokerAPIKey the Broker API key 131 | * @param brokerAPISecret the Broker API secret 132 | * @param brokerAPIEndpointType the {@link BrokerAPIEndpointType} 133 | * @param okHttpClient an existing {@link OkHttpClient} or null to create a new default 134 | * instance 135 | */ 136 | public AlpacaAPI(String brokerAPIKey, String brokerAPISecret, BrokerAPIEndpointType brokerAPIEndpointType, 137 | OkHttpClient okHttpClient) { 138 | this(null, null, null, null, null, brokerAPIKey, brokerAPISecret, brokerAPIEndpointType, okHttpClient); 139 | } 140 | 141 | /** 142 | * Instantiates a new {@link AlpacaAPI}. 143 | * 144 | * @param traderKeyID the Trader key ID 145 | * @param traderSecretKey the Trader secret key 146 | * @param traderOAuthToken the Trader OAuth token 147 | * @param traderAPIEndpointType the {@link TraderAPIEndpointType} 148 | * @param marketDataWebsocketSourceType the {@link MarketDataWebsocketSourceType} 149 | * @param brokerAPIKey the Broker API key 150 | * @param brokerAPISecret the Broker API secret 151 | * @param brokerAPIEndpointType the {@link BrokerAPIEndpointType} 152 | * @param okHttpClient an existing {@link OkHttpClient} or null to create a new 153 | * default instance 154 | */ 155 | public AlpacaAPI(String traderKeyID, String traderSecretKey, 156 | String traderOAuthToken, TraderAPIEndpointType traderAPIEndpointType, 157 | MarketDataWebsocketSourceType marketDataWebsocketSourceType, 158 | String brokerAPIKey, String brokerAPISecret, BrokerAPIEndpointType brokerAPIEndpointType, 159 | OkHttpClient okHttpClient) { 160 | this.traderKeyID = traderKeyID; 161 | this.traderSecretKey = traderSecretKey; 162 | this.traderOAuthToken = traderOAuthToken; 163 | this.traderAPIEndpointType = traderAPIEndpointType != null ? 164 | traderAPIEndpointType : PAPER; 165 | this.marketDataWebsocketSourceType = marketDataWebsocketSourceType != null ? 166 | marketDataWebsocketSourceType : IEX; 167 | this.brokerAPIKey = brokerAPIKey; 168 | this.brokerAPISecret = brokerAPISecret; 169 | this.brokerAPIEndpointType = brokerAPIEndpointType != null ? 170 | brokerAPIEndpointType : SANDBOX; 171 | 172 | // Create default OkHttpClient instance 173 | if (okHttpClient == null) { 174 | final OkHttpClient.Builder clientBuilder = new OkHttpClient.Builder(); 175 | if (LOGGER.isDebugEnabled()) { 176 | final HttpLoggingInterceptor loggingInterceptor = new HttpLoggingInterceptor(LOGGER::debug); 177 | loggingInterceptor.setLevel(BODY); 178 | clientBuilder.addInterceptor(loggingInterceptor); 179 | } 180 | okHttpClient = clientBuilder.build(); 181 | } 182 | this.okHttpClient = okHttpClient; 183 | } 184 | 185 | /** 186 | * Gets the {@link OkHttpClient}. 187 | * 188 | * @return the {@link OkHttpClient} 189 | */ 190 | public OkHttpClient getOkHttpClient() { 191 | return okHttpClient; 192 | } 193 | 194 | /** 195 | * Gets the {@link AlpacaTraderAPI}. Lazily instantiated. 196 | * 197 | * @return the {@link AlpacaTraderAPI} 198 | */ 199 | public synchronized AlpacaTraderAPI trader() { 200 | if (trader == null) { 201 | trader = new AlpacaTraderAPI(traderKeyID, traderSecretKey, traderOAuthToken, traderAPIEndpointType, 202 | okHttpClient); 203 | } 204 | return trader; 205 | } 206 | 207 | /** 208 | * Gets the {@link AlpacaMarketDataAPI}. Lazily instantiated. 209 | * 210 | * @return the {@link AlpacaMarketDataAPI} 211 | */ 212 | public synchronized AlpacaMarketDataAPI marketData() { 213 | if (marketData == null) { 214 | marketData = new AlpacaMarketDataAPI(traderKeyID, traderSecretKey, brokerAPIKey, brokerAPISecret, 215 | okHttpClient); 216 | } 217 | return marketData; 218 | } 219 | 220 | /** 221 | * Gets the {@link AlpacaBrokerAPI}. Lazily instantiated. 222 | * 223 | * @return the {@link AlpacaBrokerAPI} 224 | */ 225 | public synchronized AlpacaBrokerAPI broker() { 226 | if (broker == null) { 227 | broker = new AlpacaBrokerAPI(brokerAPIKey, brokerAPISecret, brokerAPIEndpointType, okHttpClient); 228 | } 229 | return broker; 230 | } 231 | 232 | /** 233 | * Gets the {@link UpdatesWebsocketInterface}. Lazily instantiated. 234 | * 235 | * @return the {@link UpdatesWebsocketInterface} 236 | */ 237 | public synchronized UpdatesWebsocketInterface updatesStream() { 238 | if (updatesWebsocket == null) { 239 | updatesWebsocket = new UpdatesWebsocket(okHttpClient, traderAPIEndpointType, 240 | traderKeyID, traderSecretKey, traderOAuthToken); 241 | } 242 | return updatesWebsocket; 243 | } 244 | 245 | /** 246 | * Gets the {@link StockMarketDataWebsocketInterface}. Lazily instantiated. 247 | * 248 | * @return the {@link StockMarketDataWebsocketInterface} 249 | */ 250 | public synchronized StockMarketDataWebsocketInterface stockMarketDataStream() { 251 | if (stockMarketDataWebsocket == null) { 252 | stockMarketDataWebsocket = new StockMarketDataWebsocket(okHttpClient, 253 | traderKeyID, traderSecretKey, brokerAPIKey, brokerAPISecret, marketDataWebsocketSourceType); 254 | } 255 | return stockMarketDataWebsocket; 256 | } 257 | 258 | /** 259 | * Gets the {@link CryptoMarketDataWebsocketInterface}. Lazily instantiated. 260 | * 261 | * @return the {@link CryptoMarketDataWebsocketInterface} 262 | */ 263 | public synchronized CryptoMarketDataWebsocketInterface cryptoMarketDataStream() { 264 | if (cryptoMarketDataWebsocket == null) { 265 | cryptoMarketDataWebsocket = new CryptoMarketDataWebsocket(okHttpClient, 266 | traderKeyID, traderSecretKey, brokerAPIKey, brokerAPISecret); 267 | } 268 | return cryptoMarketDataWebsocket; 269 | } 270 | 271 | /** 272 | * Gets the {@link NewsMarketDataWebsocketInterface}. Lazily instantiated. 273 | * 274 | * @return the {@link NewsMarketDataWebsocketInterface} 275 | */ 276 | public synchronized NewsMarketDataWebsocketInterface newsMarketDataStream() { 277 | if (newsMarketDataWebsocket == null) { 278 | newsMarketDataWebsocket = new NewsMarketDataWebsocket(okHttpClient, 279 | traderKeyID, traderSecretKey, brokerAPIKey, brokerAPISecret); 280 | } 281 | return newsMarketDataWebsocket; 282 | } 283 | 284 | /** 285 | * Creates a {@link Builder} for {@link AlpacaAPI}. 286 | * 287 | * @return the {@link Builder} 288 | */ 289 | public static Builder builder() { 290 | return new Builder(); 291 | } 292 | 293 | /** 294 | * A builder for {@link AlpacaAPI} 295 | */ 296 | public static final class Builder { 297 | 298 | private String traderKeyID; 299 | private String traderSecretKey; 300 | private String traderOAuthToken; 301 | private TraderAPIEndpointType traderAPIEndpointType; 302 | private MarketDataWebsocketSourceType marketDataWebsocketSourceType; 303 | private String brokerAPIKey; 304 | private String brokerAPISecret; 305 | private BrokerAPIEndpointType brokerAPIEndpointType; 306 | private OkHttpClient okHttpClient; 307 | 308 | private Builder() {} 309 | 310 | public Builder withTraderKeyID(String traderKeyID) { 311 | this.traderKeyID = traderKeyID; 312 | return this; 313 | } 314 | 315 | public Builder withTraderSecretKey(String traderSecretKey) { 316 | this.traderSecretKey = traderSecretKey; 317 | return this; 318 | } 319 | 320 | public Builder withTraderOAuthToken(String traderOAuthToken) { 321 | this.traderOAuthToken = traderOAuthToken; 322 | return this; 323 | } 324 | 325 | public Builder withTraderAPIEndpointType(TraderAPIEndpointType traderAPIEndpointType) { 326 | this.traderAPIEndpointType = traderAPIEndpointType; 327 | return this; 328 | } 329 | 330 | public Builder withMarketDataWebsocketSourceType(MarketDataWebsocketSourceType marketDataWebsocketSourceType) { 331 | this.marketDataWebsocketSourceType = marketDataWebsocketSourceType; 332 | return this; 333 | } 334 | 335 | public Builder withBrokerAPIKey(String brokerAPIKey) { 336 | this.brokerAPIKey = brokerAPIKey; 337 | return this; 338 | } 339 | 340 | public Builder withBrokerAPISecret(String brokerAPISecret) { 341 | this.brokerAPISecret = brokerAPISecret; 342 | return this; 343 | } 344 | 345 | public Builder withBrokerAPIEndpointType(BrokerAPIEndpointType brokerAPIEndpointType) { 346 | this.brokerAPIEndpointType = brokerAPIEndpointType; 347 | return this; 348 | } 349 | 350 | public Builder withOkHttpClient(OkHttpClient okHttpClient) { 351 | this.okHttpClient = okHttpClient; 352 | return this; 353 | } 354 | 355 | public AlpacaAPI build() { 356 | return new AlpacaAPI(traderKeyID, traderSecretKey, traderOAuthToken, traderAPIEndpointType, 357 | marketDataWebsocketSourceType, brokerAPIKey, brokerAPISecret, brokerAPIEndpointType, okHttpClient); 358 | } 359 | } 360 | } 361 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

Logo

2 |

3 | GitHub Repository 4 | Maven Central 5 | Javadocs 6 | GitHub 7 |

8 | 9 | # Overview 10 | 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. 11 | 12 | Give this repository a star ⭐ if it helped you build a trading algorithm in Java! 13 | 14 | # Gradle and Maven Integration 15 | If you are using Gradle as your build tool, add the following dependency to your `build.gradle` file: 16 | 17 | ``` 18 | implementation group: "net.jacobpeterson.alpaca", name: "alpaca-java", version: "10.0.1" 19 | ``` 20 | 21 | If you are using Maven as your build tool, add the following dependency to your `pom.xml` file: 22 | 23 | ``` 24 | 25 | net.jacobpeterson.alpaca 26 | alpaca-java 27 | 10.0.1 28 | 29 | ``` 30 | 31 | 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. 32 | 33 | # Logger 34 | 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/). 35 | 36 | # `BigDecimal` vs. `Double` 37 | 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. 38 | 39 | # Examples 40 | 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. 41 | 42 | ## [`AlpacaAPI`](src/main/java/net/jacobpeterson/alpaca/AlpacaAPI.java) 43 | [`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. 44 | 45 | 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). 46 | 47 | Example usage: 48 | 49 | Use this code if you are using the Trading or Market Data APIs for a single Alpaca account: 50 | ```java 51 | final String keyID = ""; 52 | final String secretKey = ""; 53 | final TraderAPIEndpointType endpointType = TraderAPIEndpointType.PAPER; // or 'LIVE' 54 | final MarketDataWebsocketSourceType sourceType = MarketDataWebsocketSourceType.IEX; // or 'SIP' 55 | final AlpacaAPI alpacaAPI = new AlpacaAPI(keyID, secretKey, endpointType, sourceType); 56 | ``` 57 | 58 | Use this code if you are using the Trading API with OAuth to act on behalf of an Alpaca account: 59 | ```java 60 | final String oAuthToken = ""; 61 | final TraderAPIEndpointType endpointType = TraderAPIEndpointType.PAPER; // or 'LIVE' 62 | final AlpacaAPI alpacaAPI = new AlpacaAPI(oAuthToken, endpointType); 63 | ``` 64 | 65 | Use this code if you are using the Broker API: 66 | ```java 67 | final String brokerAPIKey = ""; 68 | final String brokerAPISecret = ""; 69 | final BrokerAPIEndpointType endpointType = BrokerAPIEndpointType.SANDBOX; // or 'PRODUCTION' 70 | final AlpacaAPI alpacaAPI = new AlpacaAPI(brokerAPIKey, brokerAPISecret, endpointType); 71 | ``` 72 | 73 | ## [`Trader API`](src/main/java/net/jacobpeterson/alpaca/rest/trader/AlpacaTraderAPI.java) 74 | 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. 75 | 76 | Example usage: 77 | ```java 78 | // Place a market order to buy one share of Apple 79 | final Order openingOrder = alpacaAPI.trader().orders() 80 | .postOrder(new PostOrderRequest() 81 | .symbol("AAPL") 82 | .qty("1") 83 | .side(OrderSide.BUY) 84 | .type(OrderType.MARKET) 85 | .timeInForce(TimeInForce.GTC)); 86 | System.out.println("Opening Apple order: " + openingOrder); 87 | 88 | // Wait for massive gains 89 | Thread.sleep(10_000); 90 | 91 | // Close the Apple position 92 | final Order closingOrder = alpacaAPI.trader().positions() 93 | .deleteOpenPosition("AAPL", null, new BigDecimal("100")); 94 | System.out.println("Closing Apple order: " + openingOrder); 95 | 96 | // Wait for closing trade to fill 97 | Thread.sleep(10_000); 98 | 99 | // Print out PnL 100 | final String openFillPrice = alpacaAPI.trader().orders() 101 | .getOrderByOrderID(UUID.fromString(openingOrder.getId()), false) 102 | .getFilledAvgPrice(); 103 | final String closeFillPrice = alpacaAPI.trader().orders() 104 | .getOrderByOrderID(UUID.fromString(closingOrder.getId()), false) 105 | .getFilledAvgPrice(); 106 | System.out.println("PnL from Apple trade: " + 107 | new BigDecimal(closeFillPrice).subtract(new BigDecimal(openFillPrice))); 108 | ``` 109 | 110 | ## [`Market Data API`](src/main/java/net/jacobpeterson/alpaca/rest/marketdata/AlpacaMarketDataAPI.java) 111 | 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. 112 | 113 | Example usage: 114 | ```java 115 | // Print out the latest Tesla trade 116 | final StockTrade latestTSLATrade = alpacaAPI.marketData().stock() 117 | .stockLatestTradeSingle("TSLA", StockFeed.IEX, null).getTrade(); 118 | System.out.printf("Latest TSLA trade: price=%s, size=%s\n", 119 | latestTSLATrade.getP(), latestTSLATrade.getS()); 120 | 121 | // Print out the highest Bitcoin ask price on the order book 122 | final CryptoOrderbook latestBitcoinOrderBooks = alpacaAPI.marketData().crypto() 123 | .cryptoLatestOrderbooks(CryptoLoc.US, "BTC/USD").getOrderbooks().get("BTC/USD"); 124 | final Double highestBitcoinAskPrice = latestBitcoinOrderBooks.getA().stream() 125 | .map(CryptoOrderbookEntry::getP) 126 | .max(Double::compare) 127 | .orElse(null); 128 | System.out.println("Bitcoin highest ask price: " + highestBitcoinAskPrice); 129 | 130 | // Print out the latest Microsoft option trade 131 | final String latestMSFTOptionTrade = alpacaAPI.marketData().option() 132 | .optionChain("MSFT").getSnapshots().entrySet().stream() 133 | .filter(entry -> entry.getValue().getLatestTrade() != null) 134 | .map(entry -> Map.entry(entry.getKey(), entry.getValue().getLatestTrade())) 135 | .max(Comparator.comparing(entry -> entry.getValue().getT())) 136 | .map(entry -> String.format("ticker=%s, time=%s, price=%s", 137 | entry.getKey(), entry.getValue().getT(), entry.getValue().getP())) 138 | .orElse(null); 139 | System.out.println("Latest Microsoft option trade: " + latestMSFTOptionTrade); 140 | ``` 141 | 142 | ## [`Broker API`](src/main/java/net/jacobpeterson/alpaca/rest/broker/AlpacaBrokerAPI.java) 143 | 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. 144 | 145 | Example usage: 146 | ```java 147 | // Listen to the trade events (SSE) and print them out 148 | final SSERequest sseRequest = alpacaAPI.broker().events() 149 | .subscribeToTradeV2(null, null, null, null, new SSEListenerAdapter<>()); 150 | 151 | // Wait for SSE channel to be ready 152 | Thread.sleep(2000); 153 | 154 | // Buy one share of GME for an account 155 | alpacaAPI.broker().trading() 156 | .createOrderForAccount(UUID.fromString(""), 157 | new CreateOrderRequest() 158 | .symbol("GME") 159 | .qty(BigDecimal.ONE) 160 | .side(OrderSide.SELL) 161 | .timeInForce(TimeInForce.GTC) 162 | .type(OrderType.MARKET)); 163 | 164 | // Wait to be filled 165 | Thread.sleep(2000); 166 | 167 | // Close the SSE stream and the OkHttpClient to exit cleanly 168 | sseRequest.close(); 169 | ``` 170 | 171 | ## [`Updates Stream`](src/main/java/net/jacobpeterson/alpaca/websocket/updates/UpdatesWebsocket.java) 172 | 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. 173 | 174 | Example usage: 175 | ```java 176 | // Connect to the 'updates' stream and wait until it's authorized 177 | alpacaAPI.updatesStream().connect(); 178 | if (!alpacaAPI.updatesStream().waitForAuthorization(5, TimeUnit.SECONDS)) { 179 | throw new RuntimeException(); 180 | } 181 | 182 | // Print out trade updates 183 | alpacaAPI.updatesStream().setListener(System.out::println); 184 | alpacaAPI.updatesStream().subscribeToTradeUpdates(true); 185 | 186 | // Place a trade 187 | alpacaAPI.trader().orders().postOrder(new PostOrderRequest() 188 | .symbol("AAPL") 189 | .qty("1") 190 | .side(OrderSide.BUY) 191 | .type(OrderType.MARKET) 192 | .timeInForce(TimeInForce.GTC)); 193 | 194 | // Wait a few seconds 195 | Thread.sleep(5000); 196 | 197 | // Close the trade 198 | alpacaAPI.trader().positions().deleteAllOpenPositions(true); 199 | 200 | // Wait a few seconds 201 | Thread.sleep(5000); 202 | 203 | // Disconnect the 'updates' stream and exit cleanly 204 | alpacaAPI.updatesStream().disconnect(); 205 | ``` 206 | 207 | ## [`Market Data Stream`](src/main/java/net/jacobpeterson/alpaca/websocket/marketdata/MarketDataWebsocket.java) 208 | 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. 209 | 210 | Example usage: 211 | ```java 212 | // Connect to the 'stock market data' stream and wait until it's authorized 213 | alpacaAPI.stockMarketDataStream().connect(); 214 | if (!alpacaAPI.stockMarketDataStream().waitForAuthorization(5, TimeUnit.SECONDS)) { 215 | throw new RuntimeException(); 216 | } 217 | 218 | // Print out trade messages 219 | alpacaAPI.stockMarketDataStream().setListener(new StockMarketDataListenerAdapter() { 220 | @Override 221 | public void onTrade(StockTradeMessage trade) { 222 | System.out.println("Received trade: " + trade); 223 | } 224 | }); 225 | 226 | // Subscribe to AAPL trades 227 | alpacaAPI.stockMarketDataStream().setTradeSubscriptions(Set.of("AAPL")); 228 | System.out.println("Subscribed to Apple trades."); 229 | 230 | // Wait a few seconds 231 | Thread.sleep(5000); 232 | 233 | // Unsubscribe from AAPL and subscribe to TSLA and MSFT 234 | alpacaAPI.stockMarketDataStream().setTradeSubscriptions(Set.of("TSLA", "MSFT")); 235 | System.out.println("Subscribed to Tesla and Microsoft trades."); 236 | 237 | // Wait a few seconds 238 | Thread.sleep(5000); 239 | 240 | // Disconnect the 'stock market data' stream and exit cleanly 241 | alpacaAPI.stockMarketDataStream().disconnect(); 242 | ``` 243 | 244 | # Building 245 | To build this project yourself, clone this repository and run: 246 | ``` 247 | ./gradlew build 248 | ``` 249 | 250 | To install the built artifacts to your local Maven repository on your machine (the `~/.m2/` directory), run: 251 | ``` 252 | ./gradlew publishToMavenLocal 253 | ``` 254 | 255 | # TODO 256 | - Implement Options market data. 257 | - Implement better reconnect logic for Websockets and SSE streaming. 258 | - Implement Unit Testing for REST API and Websocket streaming (both live and mocked). 259 | - 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. 260 | 261 | # Contributing 262 | Contributions are welcome! 263 | 264 | Do the following before starting your pull request: 265 | 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. 266 | 2. Use the `dev` branch as the base branch in your pull request. 267 | --------------------------------------------------------------------------------