Alpaca streaming websocket C program example

  1. Performance: C programs are typically faster than Python programs because they are compiled to machine code, whereas Python is an interpreted language. This can be especially important when dealing with high-frequency data streams, where the performance of the program can directly impact the ability to process and analyze the data in real-time.
  2. Lower memory footprint: C programs generally have a lower memory footprint than Python programs. This can be an advantage when running on systems with limited resources, such as embedded systems or IoT devices.
  3. Fine-grained control: C provides more fine-grained control over system resources, such as memory management and hardware access. This can be beneficial when dealing with low-level tasks, such as interacting with a WebSocket service at a low level, optimizing network communication, or handling specific hardware requirements.
  4. Portability: C is a highly portable language, allowing the same code to run on a wide variety of platforms and architectures with minimal changes. This can be advantageous when deploying the program on different systems or when targeting specific hardware configurations.
  5. Integration with other C libraries: There are many high-performance C libraries available for various tasks, such as networking, cryptography, and data processing. By using C, you can directly integrate these libraries into your program, potentially improving performance and reducing development time. Additionally, if you already have existing C code or libraries that you want to use, it can be easier to integrate them in a C-based WebSocket client than a Python-based one.

I typically compile python scripts to create executable code, which typically is faster. I’ve had problems compiling some asynchronous code with websockets and often experience lags when executing standard python code in real-time.

Here is a starter C program, which connects to Alpaca’s data streaming services. This will give you the basic idea of how to fetch trades, quotes, and bars from the service, using either sip or iex options. You can fetch data for all available symbols by using the “*” command-line argument (with quotes). Be prepared for a lot of data !

I have not yet parsed the JSON output or extended the code to store the fetched data in memory, process the results, and execute trades. The purpose of this is to generate a basic template in C for further work.

Here’s the C program:

--------------------------------------------- cut here -------------------------------

/*

“alpaca_websocket.c”: This C program connects to Alpaca’s WebSocket API and
subscribes to real-time trade, quote, and bar data for specified symbols. It
prints the received data to the console. The program allows the user to choose
between SIP or IEX data source.
The program requires the APCA_API_KEY_ID and APCA_API_SECRET_KEY environment
variables to be set, which are used for authentication.
Usage: alpaca_websocket [-t trades] [-q quotes] [-b bars] [-s sip]
Options:
-t trades : Comma-separated list of trade symbols, or “" for all trades (with quotes).
-q quotes : Comma-separated list of quote symbols, or "
” for all quotes (with quotes).
-b bars : Comma-separated list of bar symbols, or “*” for all bars (with quotes).
-s sip : Choose the data source. Allowed values are ‘sip’ (default) or ‘iex’.

To exit the program, press Ctrl+C.

To compile the program and install the required libraries, follow these steps:

Install required packages:
sudo apt-get update
sudo apt-get install -y gcc make libssl-dev libwebsockets-dev libcjson-dev

Compile the program:
gcc -o alpaca_websocket alpaca_websocket.c -lwebsockets -lssl -lcrypto -lcjson

Run the compiled program:
./alpaca_websocket -t -q -b

*/

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <signal.h>
#include <libwebsockets.h>
#include <unistd.h>
#include <cjson/cJSON.h>
#include <getopt.h>

// Global variable to check if the program is interrupted (e.g., by Ctrl+C)
static int interrupted = 0;

// WebSocket callback function for Alpaca’s API
static int callback_alpaca(
struct lws *wsi,
enum lws_callback_reasons reason,
void *user,
void *in,
size_t len) {

switch (reason) {
// Connection established
case LWS_CALLBACK_CLIENT_ESTABLISHED: {
puts(“Connected to Alpaca WebSocket server.”);

  // Get the environment variables for the API key and secret
  char *apca_api_key_id = getenv("APCA_API_KEY_ID");
  char *apca_api_secret_key = getenv("APCA_API_SECRET_KEY");
  if (!apca_api_key_id || !apca_api_secret_key) {
    fprintf(stderr, "Error: APCA_API_KEY_ID and/or APCA_API_SECRET_KEY environment variables not set.\n");
    interrupted = 1;
    break;
  }

  // Create the authentication JSON message
  cJSON *auth_message_json = cJSON_CreateObject();
  cJSON_AddStringToObject(auth_message_json, "action", "auth");
  cJSON_AddStringToObject(auth_message_json, "key", apca_api_key_id);
  cJSON_AddStringToObject(auth_message_json, "secret", apca_api_secret_key);
  char *auth_message = cJSON_PrintUnformatted(auth_message_json);

  if (!auth_message_json) {
    fprintf(stderr, "Error creating cJSON object for auth_message_json.\n");
    return -1;
  }

  // Send the authentication message
  unsigned char buf[LWS_PRE + strlen(auth_message)];
  memcpy(&buf[LWS_PRE], auth_message, strlen(auth_message));
  lws_write(wsi, &buf[LWS_PRE], strlen(auth_message), LWS_WRITE_TEXT);

  // Create the subscription JSON message
  cJSON *subscription_message_json = cJSON_CreateObject();
  cJSON_AddStringToObject(subscription_message_json, "action", "subscribe");
  if (user) {
    cJSON *params = (cJSON *)user;
    cJSON *trades = cJSON_GetObjectItem(params, "trades");
    cJSON *quotes = cJSON_GetObjectItem(params, "quotes");
    cJSON *bars = cJSON_GetObjectItem(params, "bars");

if (trades && cJSON_GetArraySize(trades) > 0) {
  cJSON_AddItemToObject(subscription_message_json, "trades", cJSON_Duplicate(trades, 1));
}
if (quotes && cJSON_GetArraySize(quotes) > 0) {
  cJSON_AddItemToObject(subscription_message_json, "quotes", cJSON_Duplicate(quotes, 1));
}
if (bars && cJSON_GetArraySize(bars) > 0) {
  cJSON_AddItemToObject(subscription_message_json, "bars", cJSON_Duplicate(bars, 1));
}
  }

  // Convert the subscription_message_json object to a string
  char *subscription_message = cJSON_PrintUnformatted(subscription_message_json);

  // Print the subscription message
  printf("Sending subscription message: %s\n", subscription_message);

  // Send the subscription message
  unsigned char sub_buf[LWS_PRE + strlen(subscription_message)];
  memcpy(&sub_buf[LWS_PRE], subscription_message, strlen(subscription_message));
  lws_write(wsi, &sub_buf[LWS_PRE], strlen(subscription_message), LWS_WRITE_TEXT);

  // Free the allocated JSON objects and strings
  cJSON_Delete(auth_message_json);
  cJSON_Delete(subscription_message_json);
  free(auth_message);
  free(subscription_message);

  break;
}
// Data received
case LWS_CALLBACK_CLIENT_RECEIVE: {
  printf("Received data: %s\n", (char *)in);
  break;
}
// Connection closed
case LWS_CALLBACK_CLIENT_CLOSED: {
  puts("Connection closed.");
  interrupted = 1;
  break;
}
default:
  break;

}
return 0;
}

// WebSocket protocols
static struct lws_protocols protocols = {
{“alpaca”, callback_alpaca, 0, 0},
{NULL, NULL, 0, 0}};

// Signal handler for SIGINT (e.g., Ctrl+C)
static void sigint_handler(int sig) {
interrupted = 1;
}

// Function to convert a string to uppercase
void to_upper(char *str) {
for (int i = 0; str[i]; i++) {
str[i] = toupper((unsigned char) str[i]);
}
}

// Function to parse the input symbols (e.g., trades, quotes, bars)
cJSON *parse_symbols(const char *symbols_str) {
cJSON *symbols = cJSON_CreateArray();

if (strcmp(symbols_str, ““) == 0) {
cJSON_AddItemToArray(symbols, cJSON_CreateString(”
”));
} else {
char *str = strdup(symbols_str);
char *token = strtok(str, “,”);

while (token) {
  to_upper(token);  // Convert the token to upper-case
  cJSON_AddItemToArray(symbols, cJSON_CreateString(token));
  token = strtok(NULL, ",");
}

free(str);

}

return symbols;
}

// Function to print the help message with usage instructions
void print_help(const char program_name) {
fprintf(stderr, “Usage: %s [-t trades] [-q quotes] [-b bars]\n”, program_name);
fprintf(stderr, “\n”);
fprintf(stderr, “Options:\n”);
fprintf(stderr, " -t trades : Comma-separated list of trade symbols, or "
" for all trades (with quotes).\n");
fprintf(stderr, " -q quotes : Comma-separated list of quote symbols, or "" for all quotes (with quotes).\n");
fprintf(stderr, " -b bars : Comma-separated list of bar symbols, or "
" for all bars (with quotes).\n");
fprintf(stderr, " -s sip : Choose the data source. Allowed values are ‘sip’ (default) or ‘iex’.\n");
fprintf(stderr, “\n”);
}

int main(int argc, char *argv) {
cJSON *params = cJSON_CreateObject();
int opt;

// If no command-line arguments are provided, print the help message and exit
if (argc == 1) {
print_help(argv[0]);
exit(EXIT_FAILURE);
}

// Default WebSocket path for SIP data source
char *path = “/v2/sip”;

// Parse the command-line options
while ((opt = getopt(argc, argv, “t:q:b:s:”)) != -1) {
switch (opt) {
case ‘t’:
cJSON_AddItemToObject(params, “trades”, parse_symbols(optarg));
break;
case ‘q’:
cJSON_AddItemToObject(params, “quotes”, parse_symbols(optarg));
break;
case ‘b’:
cJSON_AddItemToObject(params, “bars”, parse_symbols(optarg));
break;
case ‘s’:
if (strcmp(optarg, “sip”) == 0) {
path = “/v2/sip”;
} else if (strcmp(optarg, “iex”) == 0) {
path = “/v2/iex”;
} else {
fprintf(stderr, “Invalid value for -s option. Allowed values are ‘sip’ or ‘iex’.\n”);
exit(EXIT_FAILURE);
}
break;
default:
print_help(argv[0]);
exit(EXIT_FAILURE);
}
}

// Set the SIGINT signal handler
signal(SIGINT, sigint_handler);

// Create a WebSocket context
struct lws_context_creation_info info;
memset(&info, 0, sizeof(info));
info.options = LWS_SERVER_OPTION_DO_SSL_GLOBAL_INIT;
info.port = CONTEXT_PORT_NO_LISTEN;
info.protocols = protocols;
info.fd_limit_per_thread = 1;

struct lws_context *context = lws_create_context(&info);
if (!context) {
fprintf(stderr, “Error creating WebSocket context.\n”);
return -1;
}

// Set up the connection information
struct lws_client_connect_info ccinfo;
memset(&ccinfo, 0, sizeof(ccinfo));
ccinfo.context = context;
ccinfo.address = “stream.data.alpaca.markets”;
ccinfo.port = 443;
ccinfo.path = path;
ccinfo.host = ccinfo.address;
ccinfo.origin = ccinfo.address;
ccinfo.protocol = “alpaca”;
ccinfo.ssl_connection = LCCSCF_USE_SSL;
ccinfo.userdata = params;

// Connect to the WebSocket server
struct lws *wsi = lws_client_connect_via_info(&ccinfo);
if (!wsi) {
fprintf(stderr, “Error connecting to WebSocket server.\n”);
lws_context_destroy(context);
return -1;
}

// Main event loop: process WebSocket events until interrupted
while (!interrupted) {
lws_service(context, 1000);
}

// Clean up: destroy the WebSocket context and delete the cJSON object
lws_context_destroy(context);
cJSON_Delete(params);

// Exit the program
return 0;
}

Best Regards,

Joe O.

I created a public GitHub repository and separated the code into a reusable C library, header file, and main program. I’ve also parsed the streaming bar, trade, and quote data for further processing.

This is a simple example for using real-time, streaming websockets in C and parsing the received data with the jansson library.

Here is the command to clone the repository:

git clone https://github.com/JOravetz/Alpaca_C_Programs.git