Skip to main content

Examples

Runnable, end-to-end examples for @alpacahq/alpaca-trade-api, generated from the examples/ directory so the code below always matches the repo.

The examples import from the local source (../src) so they run straight from a clone; in your own app the import is simply from "@alpacahq/alpaca-trade-api" (shown in a comment at the top of each file). They are type-checked against the current SDK source as part of npm run typecheck, so they can't silently drift out of sync with the API.

Prerequisites

Export your credentials once:

export APCA_API_KEY_ID="your-key-id"
export APCA_API_SECRET_KEY="your-secret"

trading-bot.ts

Minimal paper trading bot.

Demonstrates: the Alpaca facade, reading the account (with the values money helpers), a market-data lookup, the ergonomic order builders (orders.limit), the trade-updates stream (with the awaitable auth handshake and reconnect-lifecycle listeners), submitAndWait (place an order and block until it reaches a terminal state), and typed-error handling branching on the ApiError subclasses.

APCA_API_KEY_ID=... APCA_API_SECRET_KEY=... npx tsx examples/trading-bot.ts
// In your own app this import is just:
// import { Alpaca, ApiError, FetchError, RateLimitError, values } from "@alpacahq/alpaca-trade-api";
import { Alpaca, ApiError, FetchError, RateLimitError, values } from "../src/index";

async function main(): Promise<void> {
const keyId = process.env.APCA_API_KEY_ID;
const secret = process.env.APCA_API_SECRET_KEY;
if (!keyId || !secret) {
console.error("Set APCA_API_KEY_ID and APCA_API_SECRET_KEY in the environment.");
process.exit(1);
}

const alpaca = new Alpaca({
keyId,
secret,
paper: true,
timeoutMs: 10_000,
retry: { maxRetries: 3 }, // covers transient 5xx and network errors on safe GETs, never order POSTs
});

// Money/quantity fields are wire-truthful strings; format them for display
// with the `values` helpers instead of printing the raw string.
const account = await alpaca.trading.account.getAccount();
console.log(
`account ${account.accountNumber} status=${account.status} ` +
`buyingPower=${values.formatMoney(account.buyingPower)} ` +
`equity=${values.formatMoney(account.equity)}`,
);

const price = await alpaca.marketData.getLatestPrice("AAPL");
console.log(`AAPL last trade: ${price ?? "n/a"}`);

// Stream order/account updates in the background.
const updates = alpaca.trading.stream();
updates.onTradeUpdate((u) => console.log(`trade update: ${u.event} ${u.order.symbol} -> ${u.order.status}`));
updates.onError((msg) => console.error("stream error:", msg));
// Observe the reconnect lifecycle (auto-reconnect with backoff is built in).
updates.onReconnecting((attempt) => console.warn(`stream reconnecting (attempt ${attempt})`));
// This means re-subscription was dispatched, not acknowledged by the server.
updates.onReconnected(() => console.info("stream reconnected; subscriptions dispatched"));
updates.onConnect(() => updates.subscribeTradeUpdates());
updates.connect();

// Await the authentication handshake (typed result; never throws). Bail out
// early on bad credentials instead of placing orders against a dead stream.
const auth = await updates.waitForAuthenticationResult(10_000);
if (!auth.authenticated) {
console.error(`trade-updates stream auth ${auth.status}: ${auth.message}${auth.code ? ` (code ${auth.code})` : ""}`);
updates.disconnect();
process.exit(1);
}

// A stable, unique client ID makes this placement auditable and provides
// the reconciliation key if the transport fails after Alpaca receives it.
const restingClientOrderId = newClientOrderId("resting-limit-aapl");

// Ergonomic order builder: a limit buy well below the market rests without
// filling. The typed `orders.limit` builder requires `limitPrice` at compile
// time and accepts `number | string` amounts. We place then cancel it to
// show both the builder and a raw generated method (`deleteOrderByOrderID`).
try {
let resting: Awaited<ReturnType<typeof alpaca.trading.orders.limit>>;
try {
resting = await alpaca.trading.orders.limit({
symbol: "AAPL",
qty: 1,
side: "buy",
limitPrice: Math.max(1, Math.floor((price ?? 100) * 0.5)),
clientOrderId: restingClientOrderId,
});
} catch (err) {
if (!(err instanceof FetchError)) throw err;

// The POST outcome is ambiguous. Reconcile by client ID before any
// further submission; a failed lookup is not proof that placement
// failed, so this example stops instead of risking another order.
try {
resting = await alpaca.trading.orders.getOrderByClientOrderId({
clientOrderId: restingClientOrderId,
});
console.warn(`reconciled ambiguous placement as order ${resting.id}`);
} catch (lookupError) {
reportError("limit order reconciliation", lookupError);
updates.disconnect();
return;
}
}
console.log(`placed resting limit order ${resting.id} @ ${resting.limitPrice}`);
if (resting.id) {
await alpaca.trading.orders.deleteOrderByOrderID({ orderId: resting.id });
console.log(`canceled ${resting.id}`);
}
} catch (err) {
reportError("limit order", err);
}

// `submitAndWait` waits for Alpaca's listening acknowledgement, places once
// per invocation, and never re-places on reconnect. It preserves this
// client ID and uses one deadline for subscription, REST, and terminal wait.
// Only this workflow makes one client-ID GET after an ambiguous FetchError;
// the generic order builders require the explicit recovery shown above.
try {
const order = await alpaca.trading.submitAndWait(
{
type: "market",
symbol: "AAPL",
qty: 1,
side: "buy",
clientOrderId: newClientOrderId("market-aapl"),
},
{ timeoutMs: 30_000, stream: updates },
);
console.log(`order ${order.id} reached ${order.status} (filledAvgPrice=${order.filledAvgPrice ?? "n/a"})`);
} catch (err) {
reportError("submitAndWait", err);
} finally {
updates.disconnect();
}
}

/** Human-readable strategy prefix plus collision-resistant Node 20 UUID. */
function newClientOrderId(purpose: string): string {
return `${purpose}-${globalThis.crypto.randomUUID()}`;
}

/** Branch on the typed-error subclasses; always log the request id on an ApiError. */
function reportError(label: string, err: unknown): void {
if (err instanceof RateLimitError) {
console.error(`${label} rate limited; retry in ${err.retryAfterMs ?? "?"}ms (request ${err.requestId})`);
} else if (err instanceof ApiError) {
console.error(`${label} rejected: HTTP ${err.status} ${err.code ?? ""} ${err.message} (request ${err.requestId})`);
} else {
console.error(`${label} failed:`, (err as Error).message);
}
}

main().catch((err) => {
console.error(err);
process.exit(1);
});

marketdata-backend.ts

Minimal market-data backend for a visualization frontend.

  • GET /stream Server-Sent Events of live bars for the configured symbols.
  • GET /price?symbol=AAPL Latest trade price (REST).
  • GET /bars?symbol=AAPL Historical bars as canonical Bars (REST, auto-paginated).
  • GET /candles?symbol=AAPL Historical bars as chart-ready columnar Candles.

Demonstrates: a long-lived market-data WebSocket (with reconnect-lifecycle listeners) fanned out to many HTTP clients, plus REST helpers and auto-pagination - no extra web framework. The live /stream bars and the historical /bars share ONE shape (Bar), so a frontend can backfill history then append live updates without remapping. Upstream failures are surfaced as typed ApiErrors, mapped to the right HTTP status with Alpaca's request id for debugging.

APCA_API_KEY_ID=... APCA_API_SECRET_KEY=... npx tsx examples/marketdata-backend.ts
curl -N http://localhost:8080/stream
curl http://localhost:8080/price?symbol=AAPL
import * as http from "node:http";
// In your own app this import is just: import { Alpaca, TimeFrame, ApiError } from "@alpacahq/alpaca-trade-api";
import { Alpaca, TimeFrame, ApiError } from "../src/index";

const keyId = process.env.APCA_API_KEY_ID;
const secret = process.env.APCA_API_SECRET_KEY;
if (!keyId || !secret) {
console.error("Set APCA_API_KEY_ID and APCA_API_SECRET_KEY in the environment.");
process.exit(1);
}

const PORT = Number(process.env.PORT ?? 8080);
const SYMBOLS = (process.env.SYMBOLS ?? "AAPL,MSFT").split(",");

const alpaca = new Alpaca({ keyId, secret });

// Fan live bars out to every connected SSE client.
const clients = new Set<http.ServerResponse>();
const stream = alpaca.marketData.stockStream({ feed: "iex" });
stream.onBar((bar) => {
const frame = `data: ${JSON.stringify(bar)}\n\n`;
for (const res of clients) res.write(frame);
});
stream.onError((msg) => console.error("stream error:", msg));
// The client auto-reconnects with backoff and dispatches subscriptions again
// after authentication; onReconnected does not imply server acknowledgement.
stream.onReconnecting((attempt) => console.warn(`market-data stream reconnecting (attempt ${attempt})`));
stream.onReconnected(() => console.info("market-data stream reconnected; subscriptions dispatched"));
stream.onConnect(() => stream.subscribeForBars(SYMBOLS));
stream.connect();

function sendJson(res: http.ServerResponse, status: number, body: unknown): void {
res.writeHead(status, { "Content-Type": "application/json" });
res.end(JSON.stringify(body));
}

const server = http.createServer(async (req, res) => {
const url = new URL(req.url ?? "/", `http://localhost:${PORT}`);
try {
if (url.pathname === "/stream") {
res.writeHead(200, {
"Content-Type": "text/event-stream",
"Cache-Control": "no-cache",
Connection: "keep-alive",
});
res.write(": connected\n\n");
clients.add(res);
req.on("close", () => clients.delete(res));
return;
}
if (url.pathname === "/price") {
const symbol = url.searchParams.get("symbol") ?? SYMBOLS[0];
const price = await alpaca.marketData.getLatestPrice(symbol);
sendJson(res, 200, { symbol, price });
return;
}
if (url.pathname === "/bars") {
const symbol = url.searchParams.get("symbol") ?? SYMBOLS[0];
// Canonical Bar[] - the same shape the /stream bars arrive in.
const bars = await alpaca.marketData.getStockBars({
symbols: [symbol],
timeframe: TimeFrame.Day,
start: new Date(url.searchParams.get("start") ?? "2024-01-01"),
});
sendJson(res, 200, bars);
return;
}
if (url.pathname === "/candles") {
const symbol = url.searchParams.get("symbol") ?? SYMBOLS[0];
// Columnar { time[], open[], high[], low[], close[], volume[] } for charts.
const candles = await alpaca.marketData.getStockCandles({
symbols: [symbol],
timeframe: TimeFrame.Day,
start: new Date(url.searchParams.get("start") ?? "2024-01-01"),
});
sendJson(res, 200, candles);
return;
}
sendJson(res, 404, { error: "not found" });
} catch (err) {
// Map an upstream Alpaca failure to its real status, and surface the
// request id so the failure is traceable in Alpaca's systems.
if (err instanceof ApiError) {
sendJson(res, err.status, { error: err.message, code: err.code, requestId: err.requestId });
} else {
sendJson(res, 500, { error: (err as Error).message });
}
}
});

server.listen(PORT, () => {
console.log(`listening on http://localhost:${PORT} (streaming bars for ${SYMBOLS.join(", ")})`);
});