Feat(api): support fetching bars from paper or live Alpaca (default paper) via alpacaClient.fetchBars\n\nCo-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

This commit is contained in:
2026-05-16 18:08:15 +02:00
parent e88deac193
commit 74ebf0b6e3
2 changed files with 93 additions and 26 deletions
+85
View File
@@ -0,0 +1,85 @@
import Alpaca from "@alpacahq/alpaca-trade-api";
function makeAlpaca(mode: 'paper' | 'live' = 'paper') {
const isLive = mode === 'live';
const keyId = isLive ? (process.env.ALPACA_API_KEY_LIVE || process.env.ALPACA_API_KEY) : process.env.ALPACA_API_KEY;
const secretKey = isLive ? (process.env.ALPACA_SECRET_KEY_LIVE || process.env.ALPACA_SECRET_KEY) : process.env.ALPACA_SECRET_KEY;
const baseUrl = isLive ? (process.env.ALPACA_LIVE_BASE_URL || "https://api.alpaca.markets") : (process.env.ALPACA_BASE_URL || "https://paper-api.alpaca.markets");
const dataBaseUrl = isLive ? (process.env.ALPACA_LIVE_DATA_URL || "https://data.alpaca.markets") : (process.env.ALPACA_DATA_URL || "https://data.alpaca.markets");
return new Alpaca({
keyId,
secretKey,
baseUrl,
dataBaseUrl,
retryOnError: false,
});
}
export async function fetchAccount(mode: 'paper' | 'live' = 'paper') {
try {
const client = makeAlpaca(mode);
const account = await client.getAccount();
return {
cash: parseFloat(account.cash),
buying_power: parseFloat(account.buying_power),
portfolio_value: parseFloat(account.portfolio_value),
};
} catch (err: any) {
console.error("alpacaClient: fetchAccount failed:", err);
throw new Error(err?.message || String(err));
}
}
export async function fetchRecentCloses(ticker: string, days = 30, mode: 'paper' | 'live' = 'paper') {
try {
const client = makeAlpaca(mode);
const startDate = new Date();
startDate.setDate(startDate.getDate() - days);
const barsIter = await client.getBarsV2(ticker, { timeframe: "1D", start: startDate.toISOString().split("T")[0], limit: 1000 });
const barsArray: any[] = [];
for await (const b of barsIter) barsArray.push(b);
const closes = barsArray.map((bar: any) => (typeof bar.ClosePrice === 'number' ? bar.ClosePrice : (typeof bar.c === 'number' ? bar.c : 0))).filter((c: number) => c > 0);
if (closes.length) return closes;
// fallback to latest trade
try {
const trade: any = await client.getLatestTrade(ticker);
const price = trade?.Price || trade?.price || 0;
if (price) return [price];
} catch (tErr) {
console.warn("alpacaClient: getLatestTrade fallback failed:", tErr);
}
throw new Error("No recent price data available from Alpaca");
} catch (err: any) {
console.error("alpacaClient: fetchRecentCloses failed:", err);
throw new Error(err?.message || String(err));
}
}
export async function fetchLatestBar(ticker: string, timeframe = '1Min', mode: 'paper' | 'live' = 'paper') {
try {
const client = makeAlpaca(mode);
const barsIter = await client.getBarsV2(ticker, { timeframe, limit: 1 });
const barsArr: any[] = [];
for await (const b of barsIter) barsArr.push(b);
const last = barsArr[barsArr.length - 1];
return last || null;
} catch (err: any) {
console.error('alpacaClient: fetchLatestBar failed:', err);
throw new Error(err?.message || String(err));
}
}
export async function fetchBars(ticker: string, timeframe = '1D', options: any = {}, mode: 'paper' | 'live' = 'paper') {
try {
const client = makeAlpaca(mode);
const barsIter = await client.getBarsV2(ticker, { timeframe, ...options });
const barsArr: any[] = [];
for await (const b of barsIter) barsArr.push(b);
return barsArr;
} catch (err: any) {
console.error('alpacaClient: fetchBars failed:', err);
throw new Error(err?.message || String(err));
}
}