UNPKG

@vicary/alpaca-sdk

Version:

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

158 lines (157 loc) 5.61 kB
// NOT CLEANED UP; TYPES MISSING FOR CALLBACKS; BARELY FUNCTIONAL import * as dntShim from "../_dnt.shims.js"; const baseURLs = { data: "wss://stream.data.alpaca.markets", data_sandbox: "wss://stream.data.sandbox.alpaca.markets", data_test: "wss://stream.data.alpaca.markets/v2/test", account: "wss://api.alpaca.markets", account_paper: "wss://paper-api.alpaca.markets", }; export const createStream = (options) => { const { type, version = "v2", feed = "iex", autoReconnect = true, maxRetries = 5, retryDelay = 3000, } = options; const key = options.key || dntShim.Deno.env.get("APCA_KEY_ID"); const secret = options.secret || dntShim.Deno.env.get("APCA_KEY_SECRET"); if (!key || !secret) { console.error("API key and secret are required."); return; } let url; if (type === "data" || type === "data_sandbox") { url = `${baseURLs[type]}/${version}/${feed}`; } else if (type === "data_test") { url = baseURLs[type]; } else { url = `${baseURLs[type]}/stream`; } console.log(url); let socket = null; let retries = 0; const eventCallbacks = {}; const activeStreams = new Set(); let isAuthenticated = false; const handle = (message) => { const event = message.stream; if (event && eventCallbacks[event]) { eventCallbacks[event].forEach((callback) => callback(message)); } else { console.debug("Unhandled message:", message); } }; const connect = () => { if (!autoReconnect || (maxRetries !== undefined && retries >= maxRetries)) { console.debug("Auto-reconnect is disabled or max retries reached."); socket?.close(); return; } socket = new WebSocket(url); socket.onopen = () => { console.debug("WebSocket connection established. Sending authentication message."); socket?.send(JSON.stringify({ action: "auth", key: key, secret: secret, })); }; socket.onclose = () => { console.debug("WebSocket connection closed. Attempting to reconnect..."); retries++; setTimeout(connect, retryDelay); }; socket.onerror = (error) => { console.debug("WebSocket encountered an error:", error); socket?.close(); }; socket.onmessage = ({ data }) => { if (typeof data === "string") { console.log("Received text message:", data); try { const result = JSON.parse(data); if (result.stream === "authorization" && result.data.status === "authorized") { isAuthenticated = true; sendListenMessage(); } handle(result); } catch (error) { console.debug("Error parsing text message:", error); } } else if (data instanceof Blob) { console.log("Received binary message:", data); const reader = new FileReader(); reader.onload = function () { if (typeof reader.result === "string") { try { const result = JSON.parse(reader.result); if (result.stream === "authorization" && result.data.status === "authorized") { isAuthenticated = true; sendListenMessage(); } handle(result); } catch (error) { console.debug("Error parsing binary message:", error); } } }; reader.readAsText(data); } else { console.debug("Unknown message type:", data); } }; }; connect(); const sendListenMessage = () => { if (socket && socket.readyState === WebSocket.OPEN && isAuthenticated) { console.log("Sending listen message:", Array.from(activeStreams)); socket.send(JSON.stringify({ action: "listen", data: { streams: Array.from(activeStreams), }, })); } else { console.debug("Socket is not open or not authenticated. Cannot send listen message."); } }; const subscribe = (event, callback) => { console.log("Subscribing to event:", event); if (!eventCallbacks[event]) { eventCallbacks[event] = []; } eventCallbacks[event].push(callback); activeStreams.add(event); sendListenMessage(); }; const unsubscribe = (event) => { if (eventCallbacks[event]) { delete eventCallbacks[event]; activeStreams.delete(event); sendListenMessage(); } }; return { socket, close: () => socket?.close(), subscribe, unsubscribe, }; }; // const stream = createStream({ // type: "account_paper", // key: "key", // secret: "secret", // autoReconnect: true, // maxRetries: 5, // retryDelay: 3000, // }); // stream.subscribe("trade_updates", (data) => { // // trade update received // });