Files
AITrader/app/routes/analyze.ticker.tsx
T
henry b4076f89b6 fix: add bars data for TradingView chart from Alpaca
- Modify quote.ts to fetch historical bars for chart data
- Update analyze.ticker.tsx to pass bars data to TradingViewChart
- Chart now displays candlestick data from Alpaca API
2026-05-14 11:19:22 +02:00

106 lines
4.3 KiB
TypeScript

import { useLoaderData } from "react-router";
import TradingViewChart from "../components/TradingViewChart";
import Navbar from "../components/Navbar";
export const meta = () => [{ title: "Stock Detail - AITrader" }];
interface LoaderData {
ticker: string;
position: number | null;
orders: any[];
bars: any[];
}
export async function loader({ params, request }: { params: { ticker: string }; request: Request }) {
const ticker = params.ticker?.toUpperCase() || "";
// Build base URL from request for server-side fetches
const url = new URL(request.url);
const baseUrl = `${url.protocol}//${url.host}`;
// Fetch position
const posRes = await fetch(`${baseUrl}/api/alpaca/positions`);
const positions = posRes.ok ? await posRes.json() : [];
const position = positions.find((p: any) => p.ticker === ticker)?.qty ?? null;
// Fetch orders
const ordRes = await fetch(`${baseUrl}/api/alpaca/orders`);
const ordersData = ordRes.ok ? await ordRes.json() : { orders: [] };
const orders = ordersData.orders?.filter((o: any) => o.symbol === ticker) || [];
// Fetch bars for chart
const barsRes = await fetch(`${baseUrl}/api/alpaca/quote/${ticker}`);
const barsData = barsRes.ok ? await barsRes.json() : null;
const bars = barsData?.bars || [];
return Response.json({ ticker, position, orders });
}
export default function StockDetail() {
const { ticker, position, orders, bars } = useLoaderData() as LoaderData;
// Convert Alpaca bars to TradingView format
const chartData = bars?.map((bar: any) => ({
time: bar.t,
open: bar.o,
high: bar.h,
low: bar.l,
close: bar.c,
})) || [];
return (
<div className="min-h-screen bg-gradient-to-br from-gray-50 to-blue-50">
<Navbar />
<div className="mx-auto max-w-7xl px-6 py-8">
<h1 className="text-3xl font-bold text-gray-900 mb-6">{ticker} Detail</h1>
<TradingViewChart ticker={ticker} data={chartData} />
<div className="mt-6 bg-white rounded-xl shadow-lg p-6 border border-gray-200">
<h2 className="text-xl font-bold text-gray-900 mb-4">Position</h2>
<p className="text-gray-600">{position ? `Quantity: ${position} shares` : "No position held"}</p>
</div>
<div className="mt-6 bg-white rounded-xl shadow-lg p-6 border border-gray-200">
<h2 className="text-xl font-bold text-gray-900 mb-4">Recent Orders</h2>
{orders.length === 0 ? (
<p className="text-gray-500">No orders found for {ticker}</p>
) : (
<div className="overflow-x-auto">
<table className="w-full text-sm">
<thead>
<tr className="border-b border-gray-200">
<th className="text-left py-2 px-3 font-medium text-gray-700">Side</th>
<th className="text-left py-2 px-3 font-medium text-gray-700">Qty</th>
<th className="text-left py-2 px-3 font-medium text-gray-700">Status</th>
<th className="text-left py-2 px-3 font-medium text-gray-700">Filled Price</th>
<th className="text-left py-2 px-3 font-medium text-gray-700">Filled At</th>
</tr>
</thead>
<tbody>
{orders.map((order: any, i: number) => (
<tr key={order.id || i} className="border-b border-gray-100">
<td className="py-2 px-3">
<span className={order.side === "buy" ? "text-green-600" : "text-red-600"}>
{order.side?.toUpperCase()}
</span>
</td>
<td className="py-2 px-3 text-gray-900">{order.qty}</td>
<td className="py-2 px-3 text-gray-900">{order.status}</td>
<td className="py-2 px-3 text-gray-900">
{order.filled_avg_price ? `$${parseFloat(order.filled_avg_price).toFixed(2)}` : "-"}
</td>
<td className="py-2 px-3 text-gray-600">
{order.filled_at ? new Date(order.filled_at).toLocaleDateString() : "-"}
</td>
</tr>
))}
</tbody>
</table>
</div>
)}
</div>
</div>
</div>
);
}