In a perfect world, Alpaca would have a handcrafted and maintained SDK in every language seen by the community. Unfortunately, this isn’t possible. We currently have four SDKs (Python, JS, Go, C#) that we actively maintain, as well as a list of actively maintained community-driven SDKs on Alpaca Docs to help developers get started.

While our currently maintained SDKs seem to be enough for a majority of our user base, we understand that it can be frustrating if we don’t support your language directly. This means having to either update your client code or wait on the community-maintained ones to catch up when we release changes to our APIs. To combat this issue and to create a general documentation tool, we’ve started maintaining a collection of OpenAPI specification (OAS) files here on GitHub that describe our API surfaces.

The goal of this article is to show how you can use our OAS files to generate a client for our Market Data API in any of the supported languages that the OpenAPI Generator Project maintains.

Choose a supported OAS language and do the initial setup

Love it or hate it, Java is still one of the biggest languages in terms of usage [1], and it's a great example of a language that fits this niche of people wanting to use it. However, the investment of a full SDK is, at the time of writing, not worth it. In this article, we’ll generate a Java client for Alpaca Market Data API. The steps we follow to generate the client will be analogous for other languages, but make sure to check out language-specific details on the OAS generators list.

Before getting started with this tutorial, you’ll need:

  • An Alpaca account
  • If you don’t already have one, sign up for a free account here
  • A Java SDK install
  • An IDE in which you can build and run Java code
  • Npm (this is the recommended way to install the generator tool and what we’ll be doing in the article but you can optionally download the jar files and run them yourself if you don’t wish to have npm on your system)

Installing OAS Generator and generating the client

Step 1 - Install the generator tools

We’ll start off by installing the tools as they suggest here on their site:

# install the latest version of "openapi-generator-cli"
npm install @openapitools/openapi-generator-cli -g

Step 2 - Prepare the build directory

The generator tool focuses on generating what is essentially meant to be a complete package. Keep in mind that, while useful for generating packages in a CI environment, when developing locally you may not want all of what gets generated. In this example, we’ll keep everything that’s generated.

  • In an empty folder of your choice, create an oas-build directory.
$ mkdir oas-build
  • In your editor of choice, create a file oas-config.yaml within the root folder and add the following to it:
# install the latest version of "openapi-generator-cli"
npm install @openapitools/openapi-generator-cli -g

This will specify some instructions for how the client is setup. More on these options can be found here.

  • Your file structure should now look like this:
$ tree .
.
├── oas-build
└── oas-config.yaml

1 directory, 1 file

Step 3 - Running the generator

Let’s run the generator! The CLI tool supports remote spec files so we’re going to use a URL to get the current version of the Market Data API Spec. Alternatively you could download it locally and pass in a path to that file.

$ openapi-generator-cli generate -g java -c oas-config.yaml -o oas-build/ -i https://raw.githubusercontent.com/alpacahq/alpaca-docs/master/oas/data/openapi.yaml

Great! The client is now fully generated and is ready to be installed in your next project. If you navigate into the oas-build folder you’ll find the README. This will be our guide for the next few steps.

Installing the client in your project

There are a few different options available when installing the Java API client library. In this article, we’ll be installing to a local Maven repository. As instructed by the README, this setup takes two steps.

To install, simply run the command

$ mvn clean install

inside the oas-build folder. You’ll know you did it right if it says “BUILD SUCCESS” in your terminal. Next, add the following dependency to the project’s POM:

<dependency>
  <groupId>org.openapitools</groupId>
  <artifactId>openapi-java-client</artifactId>
  <version>2.0.0</version>
  <scope>compile</scope>
</dependency>

Our project is now fully set up! You should build your project here so as not to run into issues in the next section. Now we can get to the good stuff - writing the code to make requests with this client.

Integrating the market data API into your project

Imports, authentication, and client instantiation

The README contains the example java file that we’ll be building off of. We’ll start by creating a file called “Example.java” inside src/main/java.

Paste the contents of the README example inside here. If you know what endpoints you’d like to use in your example, you should import the clients now. In our case, we’ll be using the crypto data client, news data client, and stock data client. The OAS generator is not perfect, so the imports we pasted in may be slightly off. Ensure that your imports are routed properly and that your date library is correct.

// Import classes:
import Alpaca.ApiClient;
import Alpaca.ApiException;
import Alpaca.Configuration;
import Alpaca.auth.*;
import AlpacaModels.*;
import org.openapitools.client.api.CryptoPricingDataApiApi;
import org.openapitools.client.api.NewsApi;
import org.openapitools.client.api.StockPricingDataApiApi;
import java.time.OffsetDateTime;

Then we’ll clean up the main function. We’ll want to delete the lines that aren’t part of the initial setup. Here is what it should look like:

public class Example {
    public static void main(String[] args) {
        ApiClient defaultClient = Configuration.getDefaultApiClient();
        defaultClient.setBasePath("https://data.alpaca.markets");

        // Configure API key authorization: API_Key
        ApiKeyAuth API_Key = (ApiKeyAuth) defaultClient.getAuthentication("API_Key");
        API_Key.setApiKey("YOUR API KEY");

        // Configure API key authorization: API_Secret
        ApiKeyAuth API_Secret = (ApiKeyAuth) defaultClient.getAuthentication("API_Secret");
        API_Secret.setApiKey("YOUR API KEY");

        // Instantiating API clients
        CryptoPricingDataApiApi apiInstance = new CryptoPricingDataApiApi(defaultClient);

    }
}

Before we go any further, we’ll need to authenticate with our API keys. If you haven’t signed up for an account yet, do that here. To find your API keys, navigate to your dashboard and click on this view button. This will reveal your authentication information, and you should now paste these values into your code.

Next, we’ll instantiate all the clients we’ve imported and define the variables will be shared amongst them. The three endpoints implemented in this article are get crypto bar data, get news data, and get stock snapshot. First, let’s instantiate these three clients.

 // Instantiating API clients
CryptoPricingDataApiApi cryptoApiInstance = new CryptoPricingDataApiApi(defaultClient);
NewsApi newsApiInstance = new NewsApi(defaultClient);
StockPricingDataApiApi stockApiInstance = new StockPricingDataApiApi(defaultClient);

Now we’ll define the common variables. To read about exactly what parameters each endpoint can utilize, visit the historical API reference. The variables common to our endpoints are timeframe, start, end, limit, and page token.

// Shared variables
String timeframe = "1Hour";
OffsetDateTime start = OffsetDateTime.parse("2021-01-01T00:00Z");
OffsetDateTime end = OffsetDateTime.parse("2021-02-01T00:00Z");
Integer limit = 1;
String pageToken = null;

The first endpoint we’ll tackle is getting crypto bar data.

Implementing the Get Crypto Bars API

Before we start writing the function, refer to the source file that we’ve imported. The goal of our function is to take in all the correct parameters and return a BarsResponse object from the method getBarsForCryptoSymbol. We’ll write proper error handling for any ApiException.

Write this function inside your Example class so we can call it in main.

public static BarsResponse getCryptoBars(String symbol, String timeframe, OffsetDateTime start, OffsetDateTime end, String exchanges, Integer limit, String pageToken, CryptoPricingDataApiApi cryptoApiInstance) {
   try {
       BarsResponse response = cryptoApiInstance.getBarsForCryptoSymbol(symbol, timeframe, start, end, exchanges, limit, pageToken);
       return response;
   } catch (ApiException e) {
       System.err.println("Exception when calling CryptoPricingDataApiApi#getBarsForCryptoSymbol");
       System.err.println("Status code: " + e.getCode());
       System.err.println("Reason: " + e.getResponseBody());
       System.err.println("Response headers: " + e.getResponseHeaders());
       e.printStackTrace();
   }
   return null;
}

Then we can simply define the remaining parameters we need to pass in, and then call the function.

// Getting bar data for Bitcoin
String exchanges = "CBSE";
String cryptoSymbol = "BTCUSD";
BarsResponse cryptoBars = getCryptoBars(cryptoSymbol, timeframe, start, end, exchanges, limit, pageToken, cryptoApiInstance);
System.out.println(cryptoBars);
class BarsResponse {
    bars: [class Bar {
        t: 2021-01-01T00:00Z
        x: CBSE
        o: 28990.08
        h: 29104.05
        l: 28751.82
        c: 29066.58
        v: 1244.40080852
        n: 11754
        vw: 28963.551226
    }]
    symbol: BTCUSD
    nextPageToken: QlRDVVNEfE18MjAyMS0wMS0wMVQwMDowMDowMC4wMDAwMDAwMDBafENCU0U=
    currency: null
}

Implementing the Get News API

Once again, we’ll refer to the imported source code to determine exactly what parameters need to be passed in. The goal of this function is to return a GetNewsResponse according to our parameters while taking care of any ApiException.

Just like in the last method, we’ll write this function inside of the Example class so we can call it in main.

public static GetNewsResponse getNews(String symbols, OffsetDateTime start, OffsetDateTime end, Integer limit, String sort, Boolean includeContent, Boolean excludeContentless, String pageToken, NewsApi newsApiInstance) {
        try {
            GetNewsResponse response = newsApiInstance.getNews(symbols, start, end, limit, sort, includeContent, excludeContentless, pageToken);
            return response;
        } catch (ApiException e) {
            System.err.println("Exception when calling NewsApi#getNews");
            System.err.println("Status code: " + e.getCode());
            System.err.println("Reason: " + e.getResponseBody());
            System.err.println("Response headers: " + e.getResponseHeaders());
            e.printStackTrace();
        }
        return null;
    }

Now all that’s left is specifying the remaining variables and calling the function.

// Getting news for GameStop and Tesla
String newsSymbols = "GME,TSLA";
String sort = "DESC";
Boolean includeContent = null;
Boolean excludeContentless = null;
GetNewsResponse news = getNews(newsSymbols, start, end, limit, sort, includeContent, excludeContentless, pageToken, newsApiInstance);
System.out.println(news);
class GetNewsResponse {
    news: [class News {
        id: 19411343
        headline: Robinhood Shortens Its Restricted-Securities List To 8 Stocks
        author: Gary Anglebrandt
        createdAt: 2021-01-31T22:41:25Z
        updatedAt: 2021-01-31T22:41:27Z
        summary: Robinhood has updated its list of restricted stocks, and the list has grown much shorter than the 50 stocks it was at on Friday.
        content: null
        url: https://www.benzinga.com/news/21/01/19411343/robinhood-shortens-its-restricted-securities-list-to-8-stocks
        images: [class NewsImage {
            size: large
            url: https://cdn.benzinga.com/files/imagecache/2048x1536xUP/images/story/2012/image-asset_0_2_0.png
        }, class NewsImage {
            size: small
            url: https://cdn.benzinga.com/files/imagecache/1024x768xUP/images/story/2012/image-asset_0_2_0.png
        }, class NewsImage {
            size: thumb
            url: https://cdn.benzinga.com/files/imagecache/250x187xUP/images/story/2012/image-asset_0_2_0.png
        }]
        symbols: [AMC, GME, NOK]
        source: 
    }]
    nextPageToken: MTYxMjEzMjg4NzAwMDAwMDAwMHwxOTQxMTM0Mw==

Implementing the Get Stock Snapshot API

The same as in the previous APIs, we’ll check out the source to determine what parameters we need to be passed in. The goal of this function is to return a Snapshot object from the given parameters while catching any ApiException.

We’ll define this function in the same place as the others, inside the Example class.

public static Snapshot getStockSnapshot(String symbol, String feed, String currency, StockPricingDataApiApi stockApiInstance) {
        try {
            Snapshot response = stockApiInstance.getSnapshotForStockSymbol(symbol, feed, currency);
            return response;
        } catch (ApiException e) {
            System.err.println("Exception when calling StockPricingDataApiApi#getSnapshotForStockSymbol");
            System.err.println("Status code: " + e.getCode());
            System.err.println("Reason: " + e.getResponseBody());
            System.err.println("Response headers: " + e.getResponseHeaders());
            e.printStackTrace();
        }
        return null;

    }

Defining the remaining parameters and calling the function, we’ll get the snapshot.

// Getting bar data for SPY
String stockSymbol = "SPY";
String feed = "IEX";
String currency = "USD";
Snapshot spySnapshot = getStockSnapshot(stockSymbol, feed, currency, stockApiInstance);
System.out.println(spySnapshot );
class Snapshot {
    latestTrade: class Trade {
        t: 2022-04-04T17:06:41.392365722Z
        x: V
        p: 454.81
        s: 100
        c: [ ]
        i: 55445633652808
        z: B
        tks: null
    }
    latestQuote: class Quote {
        t: 2022-04-04T17:06:56.734248197Z
        ax: V
        ap: 454.87
        as: 5
        bx: V
        bp: 454.82
        bs: 5
        c: [R]
        x: null
        z: B
    }
    minuteBar: class Bar {
        t: 2022-04-04T17:05Z
        x: null
        o: 454.875
        h: 454.875
        l: 454.835
        c: 454.835
        v: 725
        n: 9
        vw: 454.860841
    }
    dailyBar: class Bar {
        t: 2022-04-04T04:00Z
        x: null
        o: 453.07
        h: 455.7
        l: 452.29
        c: 454.835
        v: 473343
        n: 4058
        vw: 454.464733
    }
    prevDailyBar: class Bar {
        t: 2022-04-01T04:00Z
        x: null
        o: 453.28
        h: 453.43
        l: 449.19
        c: 452.93
        v: 1242458
        n: 9035
        vw: 451.5527
    }
    currency: null
}

Limitations of this client

This client is powerful and great for providing an inclusive developer experience, but it’s not without drawbacks.

The first drawback that is specific to the client we’ve just generated is that it’s only for market data endpoints. If you want to make trades, that requires generating another client and following this process again for your new client.

Also, OAS does not support describing anything that isn’t REST. This means that you can’t generate a live data feed via WebSocket client with this method.

Conclusion

In summary, we’ve seen how OAS empowers developers by providing a technology-agnostic tool to generate API interfaces. This article walked through the process of generating one such interface and implementing it in an example project. I hope that if your language of choice is not part of the officially maintained SDKs, you now feel confident in building up your own client.

References

[1] - “Stack overflow developer survey 2021,” Stack Overflow. [Online]. Available: https://insights.stackoverflow.com/survey/2021#most-popular-technologies-language. [Accessed: 01-Apr-2022].


Please note that this article is for informational purposes only. The example above is for illustrative purposes only. Actual crypto prices may vary depending on the market price at that particular time. Alpaca Crypto LLC does not recommend any specific cryptocurrencies.

Cryptocurrency is highly speculative in nature, involves a high degree of risks, such as volatile market price swings, market manipulation, flash crashes, and cybersecurity risks. Cryptocurrency is not regulated or is lightly regulated in most countries. Cryptocurrency trading can lead to large, immediate and permanent loss of financial value. You should have appropriate knowledge and experience before engaging in cryptocurrency trading. For additional information please click here.

Cryptocurrency services are made available by Alpaca Crypto LLC ("Alpaca Crypto"), a FinCEN registered money services business (NMLS # 2160858), and a wholly-owned subsidiary of AlpacaDB, Inc. Alpaca Crypto is not a member of SIPC or FINRA. Cryptocurrencies are not stocks and your cryptocurrency investments are not protected by either FDIC or SIPC. Please see the Disclosure Library for more information.

This is not an offer, solicitation of an offer, or advice to buy or sell cryptocurrencies, or open a cryptocurrency account in any jurisdiction where Alpaca Crypto is not registered or licensed, as applicable.