@vicary/alpaca-sdk
Version:
A TypeScript SDK for the https://alpaca.markets REST API and WebSocket streams.
73 lines (72 loc) • 3.08 kB
JavaScript
import * as dntShim from "../_dnt.shims.js";
import * as marketData from "../api/marketData.js";
import * as trade from "../api/trade.js";
import { createTokenBucket } from "./createTokenBucket.js";
export const baseURLs = {
live: "https://api.alpaca.markets",
paper: "https://paper-api.alpaca.markets",
marketData: "https://data.alpaca.markets",
};
export const createClient = (options) => {
const { key = "", secret = "", accessToken = "", paper = true, } = {
key: options.key || dntShim.Deno.env.get("APCA_KEY_ID"),
secret: options.secret || dntShim.Deno.env.get("APCA_KEY_SECRET"),
accessToken: options.accessToken || dntShim.Deno.env.get("APCA_ACCESS_TOKEN"),
};
// Check if credentials are provided
if (!accessToken && (!key || !secret)) {
throw new Error("Missing credentials (need accessToken or key/secret)");
}
const baseURL = options.baseURL || (paper ? baseURLs.paper : baseURLs.live);
// Create a token bucket for rate limiting
const bucket = createTokenBucket(options.tokenBucket);
// Throttled request function that respects the token bucket
const request = async ({ method = "GET", path, params, data, }) => {
await new Promise((resolve) => {
// Poll the token bucket every second
const timer = setInterval(() => {
// If a token is available, resolve the promise
if (bucket.take(1)) {
clearInterval(timer);
resolve(true);
}
}, 1000);
});
// Construct the URL
const url = new URL(path, baseURL);
if (params) {
// Append query parameters to the URL
url.search = new URLSearchParams(Object.entries(params)).toString();
}
// Construct the headers
const headers = new Headers({
"Content-Type": "application/json",
});
if (accessToken) {
// Use the access token for authentication
headers.set("Authorization", `Bearer ${accessToken}`);
}
else {
// Use the API key and secret for authentication
headers.set("APCA-API-KEY-ID", key);
headers.set("APCA-API-SECRET-KEY", secret);
}
// Make the request
return fetch(url, {
method,
headers,
body: data ? JSON.stringify(data) : null,
}).then(async (response) => {
if (!response.ok) {
// The response will contain an error message (usually)
throw new Error(await response.text());
}
// Parse the response and cast it to the expected type
return response.json().catch(() => ({}));
});
};
// Create a context object to pass to the client factory
const context = { options, request };
// Return an object with all methods
return [...Object.values(trade), ...Object.values(marketData)].reduce((prev, fn) => ({ ...prev, [fn.name]: fn(context) }), {});
};