fix: consolidate 3 fetchBars calls into 1 per stock and add 500ms delay between sequential loads to avoid rate limiting
This commit is contained in:
+13
-3
@@ -210,20 +210,29 @@ export default function Analyze() {
|
|||||||
loadPortfolio();
|
loadPortfolio();
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
// Auto-load indicators for stocks that don't have them yet
|
// Auto-load indicators for stocks that don't have them yet (sequential with delay to avoid rate limits)
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const unloaded = stocks.filter((s) => !s.indicatorsLoading && s.indicators.rsi == null);
|
const unloaded = stocks.filter((s) => !s.indicatorsLoading && s.indicators.rsi == null);
|
||||||
if (unloaded.length === 0) return;
|
if (unloaded.length === 0) return;
|
||||||
|
|
||||||
unloaded.forEach(async (stock) => {
|
let cancelled = false;
|
||||||
|
const loadSequential = async () => {
|
||||||
|
for (const stock of unloaded) {
|
||||||
|
if (cancelled) break;
|
||||||
setStocks((s) => s.map((st) => st.id === stock.id ? { ...st, indicatorsLoading: true } : st));
|
setStocks((s) => s.map((st) => st.id === stock.id ? { ...st, indicatorsLoading: true } : st));
|
||||||
const indicators = await loadIndicators(stock.ticker);
|
const indicators = await loadIndicators(stock.ticker);
|
||||||
|
if (cancelled) break;
|
||||||
if (indicators) {
|
if (indicators) {
|
||||||
setStocks((s) => s.map((st) => st.id === stock.id ? { ...st, indicators, indicatorsLoading: false } : st));
|
setStocks((s) => s.map((st) => st.id === stock.id ? { ...st, indicators, indicatorsLoading: false } : st));
|
||||||
} else {
|
} else {
|
||||||
setStocks((s) => s.map((st) => st.id === stock.id ? { ...st, indicatorsLoading: false } : st));
|
setStocks((s) => s.map((st) => st.id === stock.id ? { ...st, indicatorsLoading: false } : st));
|
||||||
}
|
}
|
||||||
});
|
// 500ms delay between each stock to avoid rate limiting
|
||||||
|
await new Promise((r) => setTimeout(r, 500));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
loadSequential();
|
||||||
|
return () => { cancelled = true; };
|
||||||
}, [stocks]);
|
}, [stocks]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -411,6 +420,7 @@ export default function Analyze() {
|
|||||||
} else {
|
} else {
|
||||||
setStocks((s) => s.map((st) => st.id === stock.id ? { ...st, indicatorsLoading: false } : st));
|
setStocks((s) => s.map((st) => st.id === stock.id ? { ...st, indicatorsLoading: false } : st));
|
||||||
}
|
}
|
||||||
|
await new Promise((r) => setTimeout(r, 500));
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -2,46 +2,25 @@ import { type IndicatorData } from "../../types";
|
|||||||
import { calculateSMA, calculateEMA, calculateRSI, calculateMACD, calculateBollingerBands, calculateATR, calculateVolumeAvg } from "../../utils/indicators";
|
import { calculateSMA, calculateEMA, calculateRSI, calculateMACD, calculateBollingerBands, calculateATR, calculateVolumeAvg } from "../../utils/indicators";
|
||||||
import alpacaService from "../../lib/alpacaClient";
|
import alpacaService from "../../lib/alpacaClient";
|
||||||
|
|
||||||
async function fetchHistoricPrices(symbol: string): Promise<number[]> {
|
async function fetchBarsOnce(symbol: string): Promise<{ prices: number[]; volumes: number[]; highs: number[]; lows: number[] }> {
|
||||||
try {
|
|
||||||
const bars = await alpacaService.fetchBars(symbol, "1Day", { limit: 60 });
|
|
||||||
return bars.map((b: any) => {
|
|
||||||
const c = b.ClosePrice ?? b.c ?? 0;
|
|
||||||
return typeof c === "number" ? c : 0;
|
|
||||||
}).filter((p: number) => p > 0);
|
|
||||||
} catch (e) {
|
|
||||||
console.error(`Failed to fetch bars for ${symbol}:`, e);
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async function fetchVolumes(symbol: string): Promise<number[]> {
|
|
||||||
try {
|
|
||||||
const bars = await alpacaService.fetchBars(symbol, "1Day", { limit: 60 });
|
|
||||||
return bars.map((b: any) => {
|
|
||||||
const v = b.Volume ?? b.v ?? 0;
|
|
||||||
return typeof v === "number" ? v : 0;
|
|
||||||
}).filter((v: number) => v > 0);
|
|
||||||
} catch (e) {
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async function fetchHighLow(symbol: string): Promise<{ highs: number[]; lows: number[] }> {
|
|
||||||
try {
|
|
||||||
const bars = await alpacaService.fetchBars(symbol, "1Day", { limit: 60 });
|
const bars = await alpacaService.fetchBars(symbol, "1Day", { limit: 60 });
|
||||||
|
const prices: number[] = [];
|
||||||
|
const volumes: number[] = [];
|
||||||
const highs: number[] = [];
|
const highs: number[] = [];
|
||||||
const lows: number[] = [];
|
const lows: number[] = [];
|
||||||
|
|
||||||
for (const b of bars) {
|
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 h = b.HighPrice ?? b.h ?? 0;
|
||||||
const l = b.LowPrice ?? b.l ?? 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 h === "number" && h > 0) highs.push(h);
|
||||||
if (typeof l === "number" && l > 0) lows.push(l);
|
if (typeof l === "number" && l > 0) lows.push(l);
|
||||||
}
|
}
|
||||||
return { highs, lows };
|
|
||||||
} catch (e) {
|
return { prices, volumes, highs, lows };
|
||||||
return { highs: [], lows: [] };
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function loader({ request }: { request: Request }) {
|
export async function loader({ request }: { request: Request }) {
|
||||||
@@ -53,14 +32,11 @@ export async function loader({ request }: { request: Request }) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const prices = await fetchHistoricPrices(symbol.toUpperCase());
|
const { prices, volumes, highs, lows } = await fetchBarsOnce(symbol.toUpperCase());
|
||||||
if (prices.length < 26) {
|
if (prices.length < 26) {
|
||||||
return Response.json({ error: "Insufficient price data" }, { status: 404 });
|
return Response.json({ error: "Insufficient price data" }, { status: 404 });
|
||||||
}
|
}
|
||||||
|
|
||||||
const volumes = await fetchVolumes(symbol.toUpperCase());
|
|
||||||
const { highs, lows } = await fetchHighLow(symbol.toUpperCase());
|
|
||||||
|
|
||||||
const sma20 = calculateSMA(prices, 20);
|
const sma20 = calculateSMA(prices, 20);
|
||||||
const sma50 = prices.length >= 50 ? calculateSMA(prices, 50) : 0;
|
const sma50 = prices.length >= 50 ? calculateSMA(prices, 50) : 0;
|
||||||
const ema12 = calculateEMA(prices, 12);
|
const ema12 = calculateEMA(prices, 12);
|
||||||
|
|||||||
Reference in New Issue
Block a user