UNPKG

@vicary/alpaca-sdk

Version:

A TypeScript SDK for the https://alpaca.markets REST API and WebSocket streams.

73 lines (72 loc) 3.08 kB
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) }), {}); };