72 lines
2.5 KiB
TypeScript
72 lines
2.5 KiB
TypeScript
import { type IndicatorData } from "../../types";
|
|
import { calculateSMA, calculateEMA, calculateRSI, calculateMACD, calculateBollingerBands, calculateATR, calculateVolumeAvg } from "../../utils/indicators";
|
|
import alpacaService from "../../lib/alpacaClient";
|
|
|
|
async function fetchBarsOnce(symbol: string): Promise<{ prices: number[]; volumes: number[]; highs: number[]; lows: number[] }> {
|
|
const bars = await alpacaService.fetchBars(symbol, "1Day", { limit: 60 });
|
|
const prices: number[] = [];
|
|
const volumes: number[] = [];
|
|
const highs: number[] = [];
|
|
const lows: number[] = [];
|
|
|
|
for (const b of bars) {
|
|
const c = b.ClosePrice ?? b.c ?? 0;
|
|
const v = b.Volume ?? b.v ?? 0;
|
|
const h = b.HighPrice ?? b.h ?? 0;
|
|
const l = b.LowPrice ?? b.l ?? 0;
|
|
if (typeof c === "number" && c > 0) prices.push(c);
|
|
if (typeof v === "number" && v > 0) volumes.push(v);
|
|
if (typeof h === "number" && h > 0) highs.push(h);
|
|
if (typeof l === "number" && l > 0) lows.push(l);
|
|
}
|
|
|
|
return { prices, volumes, highs, lows };
|
|
}
|
|
|
|
export async function loader({ request }: { request: Request }) {
|
|
const url = new URL(request.url);
|
|
const symbol = url.searchParams.get("symbol");
|
|
|
|
if (!symbol) {
|
|
return Response.json({ error: "Symbol is required" }, { status: 400 });
|
|
}
|
|
|
|
try {
|
|
const { prices, volumes, highs, lows } = await fetchBarsOnce(symbol.toUpperCase());
|
|
if (prices.length < 26) {
|
|
return Response.json({ error: "Insufficient price data" }, { status: 404 });
|
|
}
|
|
|
|
const sma20 = calculateSMA(prices, 20);
|
|
const sma50 = prices.length >= 50 ? calculateSMA(prices, 50) : 0;
|
|
const ema12 = calculateEMA(prices, 12);
|
|
const ema26 = calculateEMA(prices, 26);
|
|
const rsi14 = calculateRSI(prices, 14);
|
|
const macd = calculateMACD(prices);
|
|
const bb = calculateBollingerBands(prices, 20);
|
|
const atr = highs.length > 0 && lows.length > 0 ? calculateATR(highs, lows, prices, 14) : 0;
|
|
const avgVol = volumes.length > 0 ? calculateVolumeAvg(volumes, 20) : 0;
|
|
|
|
const data: IndicatorData = {
|
|
symbol: symbol.toUpperCase(),
|
|
indicators: {
|
|
sma: sma20,
|
|
sma50,
|
|
ema12,
|
|
ema26,
|
|
rsi: rsi14,
|
|
macd: macd.histogram,
|
|
bbUpper: bb.upper,
|
|
bbLower: bb.lower,
|
|
bbMiddle: bb.middle,
|
|
atr,
|
|
avgVolume: avgVol,
|
|
},
|
|
};
|
|
return Response.json(data);
|
|
} catch (error) {
|
|
console.error("Indicators error:", error);
|
|
return Response.json({ error: "Failed to fetch indicators" }, { status: 500 });
|
|
}
|
|
}
|