15e49cb0f9
Run Tests / test (push) Failing after 8s
- Modified Alpaca Historical Bars tests to include range parameters in API requests. - Updated test descriptions for clarity. - Added cleanup step to delete test ticker after verification in stock database tests. - Adjusted Vitest configuration to exclude test files from coverage.
102 lines
3.8 KiB
TypeScript
102 lines
3.8 KiB
TypeScript
import Alpaca from "@alpacahq/alpaca-trade-api";
|
|
|
|
const alpaca = new Alpaca({
|
|
keyId: process.env.ALPACA_API_KEY!,
|
|
secretKey: process.env.ALPACA_SECRET_KEY!,
|
|
baseUrl: process.env.ALPACA_BASE_URL || "https://paper-api.alpaca.markets",
|
|
dataBaseUrl: process.env.ALPACA_DATA_URL || "https://data.alpaca.markets",
|
|
retryOnError: false,
|
|
});
|
|
|
|
export async function loader({ request, params }: { request: Request; params: { ticker: string } }) {
|
|
const ticker = params.ticker?.toUpperCase();
|
|
const url = new URL(request.url);
|
|
const timeframe = url.searchParams.get("timeframe") || "1D";
|
|
const range = url.searchParams.get("range") || "1M"; // 1D, 1W, 1M, 3M, 1Y, 3Y, ALL
|
|
|
|
if (!ticker) {
|
|
return Response.json({ error: "Ticker is required" }, { status: 400 });
|
|
}
|
|
|
|
try {
|
|
// Get latest trade for current price
|
|
let price = 0;
|
|
try {
|
|
const trade = await alpaca.getLatestTrade(ticker);
|
|
price = (trade as { Price?: number }).Price || 0;
|
|
} catch (tradeErr) {
|
|
console.error(`API quote/${ticker}: getLatestTrade failed`, tradeErr);
|
|
}
|
|
|
|
// Calculate start date based on range
|
|
const startDate = new Date();
|
|
const isIntraday = ["1Min", "5Min", "15Min", "30Min", "1H"].includes(timeframe);
|
|
|
|
if (range === "1D") {
|
|
startDate.setDate(startDate.getDate() - 1);
|
|
} else if (range === "1W") {
|
|
startDate.setDate(startDate.getDate() - 7);
|
|
} else if (range === "1M") {
|
|
startDate.setMonth(startDate.getMonth() - 1);
|
|
} else if (range === "3M") {
|
|
startDate.setMonth(startDate.getMonth() - 3);
|
|
} else if (range === "1Y") {
|
|
startDate.setFullYear(startDate.getFullYear() - 1);
|
|
} else if (range === "3Y") {
|
|
startDate.setFullYear(startDate.getFullYear() - 3);
|
|
} else if (range === "ALL") {
|
|
startDate.setFullYear(startDate.getFullYear() - 10); // Max 10 years
|
|
} else if (isIntraday) {
|
|
startDate.setDate(startDate.getDate() - 30); // Default 30 days for intraday
|
|
}
|
|
|
|
const barsOptions: any = { timeframe, limit: 1000 }; // High limit for time range
|
|
if (!isIntraday && range !== "ALL") {
|
|
barsOptions.start = startDate.toISOString().split('T')[0];
|
|
} else if (!isIntraday) {
|
|
barsOptions.start = startDate.toISOString().split('T')[0];
|
|
}
|
|
|
|
const bars = await alpaca.getBarsV2(ticker, barsOptions);
|
|
|
|
// Convert async generator to array
|
|
// Alpaca v2 API returns AlpacaBar with capitalized property names
|
|
const barsArray = [];
|
|
try {
|
|
for await (const bar of bars) {
|
|
barsArray.push(bar);
|
|
}
|
|
} catch (genErr) {
|
|
console.error(`API quote/${ticker}: error iterating bars`, genErr);
|
|
}
|
|
|
|
// Transform to chart format
|
|
const transformedBars = barsArray.map((bar: any) => {
|
|
// AlpacaBarV2 transforms lowercase to capitalized: o->OpenPrice, h->HighPrice, etc.
|
|
const open = typeof bar.OpenPrice === 'number' ? bar.OpenPrice : (typeof bar.o === 'number' ? bar.o : 0);
|
|
const high = typeof bar.HighPrice === 'number' ? bar.HighPrice : (typeof bar.h === 'number' ? bar.h : 0);
|
|
const low = typeof bar.LowPrice === 'number' ? bar.LowPrice : (typeof bar.l === 'number' ? bar.l : 0);
|
|
const close = typeof bar.ClosePrice === 'number' ? bar.ClosePrice : (typeof bar.c === 'number' ? bar.c : 0);
|
|
const timestamp = bar.Timestamp ?? bar.t;
|
|
const volume = typeof bar.Volume === 'number' ? bar.Volume : (typeof bar.v === 'number' ? bar.v : 0);
|
|
|
|
return {
|
|
t: timestamp,
|
|
o: open,
|
|
h: high,
|
|
l: low,
|
|
c: close,
|
|
v: volume,
|
|
};
|
|
}).filter((bar: any) => bar.o > 0 && bar.h > 0 && bar.l > 0 && bar.c > 0);
|
|
|
|
return Response.json({
|
|
ticker,
|
|
price,
|
|
bars: transformedBars,
|
|
});
|
|
} catch (error) {
|
|
console.error("Alpaca data error:", error);
|
|
return Response.json({ ticker, price: 0, bars: [] }, { status: 500 });
|
|
}
|
|
} |