feat: rewrite analyze page with technical indicators column and signal summary
This commit is contained in:
@@ -1,18 +1,47 @@
|
||||
import { type IndicatorData } from "../../types";
|
||||
import {
|
||||
calculateSMA,
|
||||
calculateEMA,
|
||||
calculateRSI,
|
||||
calculateMACD,
|
||||
} from "../../utils/indicators";
|
||||
import { calculateSMA, calculateEMA, calculateRSI, calculateMACD, calculateBollingerBands, calculateATR, calculateVolumeAvg } from "../../utils/indicators";
|
||||
import alpacaService from "../../lib/alpacaClient";
|
||||
|
||||
// Replace with actual Alpaca API call
|
||||
async function fetchHistoricPrices(symbol: string): Promise<number[]> {
|
||||
return [
|
||||
150.0, 152.3, 151.8, 153.5, 155.0, 154.2, 156.7, 158.1, 157.5, 159.0,
|
||||
160.2, 158.9, 161.5, 163.0, 162.5, 164.8, 166.3, 165.0, 167.5, 169.0,
|
||||
168.2, 170.5, 172.0, 171.5, 173.2,
|
||||
];
|
||||
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 highs: number[] = [];
|
||||
const lows: number[] = [];
|
||||
for (const b of bars) {
|
||||
const h = b.HighPrice ?? b.h ?? 0;
|
||||
const l = b.LowPrice ?? b.l ?? 0;
|
||||
if (typeof h === "number" && h > 0) highs.push(h);
|
||||
if (typeof l === "number" && l > 0) lows.push(l);
|
||||
}
|
||||
return { highs, lows };
|
||||
} catch (e) {
|
||||
return { highs: [], lows: [] };
|
||||
}
|
||||
}
|
||||
|
||||
export async function loader({ request }: { request: Request }) {
|
||||
@@ -20,35 +49,47 @@ export async function loader({ request }: { request: Request }) {
|
||||
const symbol = url.searchParams.get("symbol");
|
||||
|
||||
if (!symbol) {
|
||||
return Response.json(
|
||||
{ error: "Symbol is required" },
|
||||
{ status: 400 }
|
||||
);
|
||||
return Response.json({ error: "Symbol is required" }, { status: 400 });
|
||||
}
|
||||
|
||||
try {
|
||||
const prices = await fetchHistoricPrices(symbol.toUpperCase());
|
||||
if (prices.length === 0) {
|
||||
return Response.json(
|
||||
{ error: "No price data found" },
|
||||
{ status: 404 }
|
||||
);
|
||||
if (prices.length < 26) {
|
||||
return Response.json({ error: "Insufficient price data" }, { status: 404 });
|
||||
}
|
||||
|
||||
const sma = calculateSMA(prices);
|
||||
const ema = calculateEMA(prices);
|
||||
const rsi = calculateRSI(prices);
|
||||
const volumes = await fetchVolumes(symbol.toUpperCase());
|
||||
const { highs, lows } = await fetchHighLow(symbol.toUpperCase());
|
||||
|
||||
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, ema, rsi, macd },
|
||||
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) {
|
||||
return Response.json(
|
||||
{ error: "Failed to fetch indicators" },
|
||||
{ status: 500 }
|
||||
);
|
||||
console.error("Indicators error:", error);
|
||||
return Response.json({ error: "Failed to fetch indicators" }, { status: 500 });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user