# Stock Indicators & Alpaca Account Info Implementation Plan > **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. **Goal:** Add a new `/stocks` route that displays stock indicators and show Alpaca account information on the home page. **Architecture:** - Backend: New API endpoint `/api/indicators` that fetches historic prices from Alpaca and calculates indicators (SMA, EMA, RSI, etc.). - Frontend: New route `/stocks` with a component that allows users to input a stock symbol, fetch indicators, and display them in a table. - Home page: Add a component showing Alpaca account balance, buying power, and positions. **Tech Stack:** React Router, TypeScript, TailwindCSS, Alpaca API, Node.js. --- ### Task 1: Create backend API endpoint for stock indicators **Files:** - Create: `app/api/indicators.ts` - Modify: `app/api/+types.ts` (add response type) - Test: `app/api/__tests__/indicators.test.ts` - [ ] **Step 1: Define response type** ```typescript // app/api/+types.ts export interface IndicatorData { symbol: string; indicators: { sma: number; ema: number; rsi: number; macd: number; // add more as needed }; } ``` - [ ] **Step 2: Implement the endpoint** ```typescript // app/api/indicators.ts import { NextResponse } from "@react-router/server"; import { type RequestHandler } from "./+types"; import { type IndicatorData } from "./+types"; export const GET: RequestHandler = async ({ request, params }) => { const url = new URL(request.url); const symbol = url.searchParams.get("symbol"); if (!symbol) { return NextResponse.json( { error: "Symbol is required" }, { status: 400 } ); } try { // Call Alpaca historic prices // Calculate indicators // Return JSON const data: IndicatorData = { symbol, indicators: { sma: 0, ema: 0, rsi: 0, macd: 0, }, }; return NextResponse.json(data); } catch (error) { return NextResponse.json( { error: "Failed to fetch indicators" }, { status: 500 } ); } }; ``` - [ ] **Step 3: Write test for endpoint** ```typescript // app/api/__tests__/indicators.test.ts import { GET } from "../indicators"; describe("GET /api/indicators", () => { it("returns indicators for a valid symbol", async () => { const req = new Request("http://localhost:5173/api/indicators?symbol=AAPL"); const res = await GET({ request: req, params: {} } as any); expect(res).toBeInstanceOf(Response); const json = await res.json(); expect(json).toHaveProperty("symbol"); expect(json).toHaveProperty("indicators"); }); it("returns error for missing symbol", async () => { const req = new Request("http://localhost:5173/api/indicators"); const res = await GET({ request: req, params: {} } as any); expect(res.status).toBe(400); }); }); ``` - [ ] **Step 4: Run tests** ```bash npm run typecheck && npm test ``` - [ ] **Step 5: Commit** ```bash git add app/api/indicators.ts app/api/+types.ts app/api/__tests__/indicators.test.ts git commit -m "feat: add indicators API endpoint" ``` ### Task 2: Create StockViewer component for the /stocks route **Files:** - Create: `app/components/StockViewer.tsx` - Create: `app/routes/stocks.tsx` - Modify: `app/routes.ts` (add new route) - Test: `app/components/__tests__/StockViewer.test.tsx` - [ ] **Step 1: Implement StockViewer component** ```tsx // app/components/StockViewer.tsx import { useState } from "react"; export default function StockViewer() { const [symbol, setSymbol] = useState(""); const [loading, setLoading] = useState(false); const [error, setError] = useState(null); const [indicators, setIndicators] = useState(null); const fetchIndicators = async (symbol: string) => { setLoading(true); setError(null); try { const res = await fetch(`/api/indicators?symbol=${symbol}`); if (!res.ok) throw new Error("Failed to fetch"); const data = await res.json(); setIndicators(data.indicators); } catch (err) { setError("Failed to fetch indicators"); } finally { setLoading(false); } }; return (

Stock Indicators

setSymbol(e.target.value)} placeholder="Enter stock symbol" className="border p-2 mr-2" />
{error &&

{error}

} {indicators && (

Results for {symbol}

{Object.entries(indicators).map(([key, value]) => ( ))}
Indicator Value
{key.toUpperCase()} {value.toFixed(2)}
)}
); } ``` - [ ] **Step 2: Create route component** ```tsx // app/routes/stocks.tsx import StockViewer from "../components/StockViewer"; export default function Stocks() { return ; } ``` - [ ] **Step 3: Add route to routes.ts** ```tsx // app/routes.ts import { type RouteConfig, index } from "@react-router/dev/routes"; import Home from "./routes/home.tsx"; import Stocks from "./routes/stocks.tsx"; export default [ index("routes/home.tsx", Home), index("routes/stocks.tsx", Stocks), ] satisfies RouteConfig; ``` - [ ] **Step 4: Write test for StockViewer** ```tsx // app/components/__tests__/StockViewer.test.tsx import { render, screen, waitFor } from "@testing-library/react"; import userEvent from "@testing-library/user-event"; import StockViewer from "../StockViewer"; jest.mock("react", () => ({ ...jest.requireActual("react"), useState: jest.fn(), })); describe("StockViewer", () => { it("displays indicators after fetching", async () => { // Mock fetch const mockData = { indicators: { sma: 100, ema: 120, rsi: 50, macd: 0.5 } }; global.fetch = jest.fn(() => Promise.resolve({ ok: true, json: () => Promise.resolve(mockData), }) ) as any; render(); const input = screen.getByPlaceholderText("Enter stock symbol"); const button = screen.getByText("Get Indicators"); await userEvent.type(input, "AAPL"); await userEvent.click(button); await waitFor(() => { expect(screen.getByText("Results for AAPL")).toBeInTheDocument(); }); }); }); ``` - [ ] **Step 5: Run tests** ```bash npm run typecheck && npm test ``` - [ ] **Step 6: Commit** ```bash git add app/components/StockViewer.tsx app/routes/stocks.tsx app/routes.ts app/components/__tests__/StockViewer.test.tsx git commit -m "feat: add stocks route with indicator viewer" ``` ### Task 3: Add Alpaca account info component to home page **Files:** - Create: `app/components/AlpacaAccountInfo.tsx` - Modify: `app/routes/home.tsx` - Test: `app/components/__tests__/AlpacaAccountInfo.test.tsx` - [ ] **Step 1: Implement AlpacaAccountInfo component** ```tsx // app/components/AlpacaAccountInfo.tsx import { useState, useEffect } from "react"; export default function AlpacaAccountInfo() { const [account, setAccount] = useState(null); const [error, setError] = useState(null); useEffect(() => { const fetchAccount = async () => { try { const res = await fetch("/api/alpaca/account"); if (!res.ok) throw new Error("Failed to fetch"); const data = await res.json(); setAccount(data); } catch (err) { setError("Failed to fetch account info"); } }; fetchAccount(); }, []); if (error) return

{error}

; if (!account) return

Loading account...

; return (

Alpaca Account

Balance: ${account.cash}

Buying Power: ${account.buying_power}

Portfolio Value: ${account.portfolio_value}

); } ``` - [ ] **Step 2: Add component to home page** ```tsx // app/routes/home.tsx import { Welcome } from "../welcome/welcome"; import AlpacaAccountInfo from "../components/AlpacaAccountInfo"; export default function Home() { return ( <>
); } ``` - [ ] **Step 3: Create backend endpoint for Alpaca account** ```typescript // app/api/alpaca/account.ts import { NextResponse } from "@react-router/server"; import { type RequestHandler } from "./+types"; export const GET: RequestHandler = async () => { // Call Alpaca API to get account info // Return JSON with cash, buying_power, portfolio_value const account = { cash: 10000, buying_power: 20000, portfolio_value: 30000, }; return NextResponse.json(account); }; ``` - [ ] **Step 4: Add type for Alpaca account** ```typescript // app/api/+types.ts export interface AlpacaAccount { cash: number; buying_power: number; portfolio_value: number; } ``` - [ ] **Step 5: Write test for AlpacaAccountInfo** ```tsx // app/components/__tests__/AlpacaAccountInfo.test.tsx import { render, screen, waitFor } from "@testing-library/react"; import AlpacaAccountInfo from "../AlpacaAccountInfo"; jest.mock("react", () => ({ ...jest.requireActual("react"), useState: jest.fn(), useEffect: jest.fn(), })); describe("AlpacaAccountInfo", () => { it("displays account info", async () => { // Mock fetch global.fetch = jest.fn(() => Promise.resolve({ json: () => Promise.resolve({ cash: 10000, buying_power: 20000, portfolio_value: 30000 }), }) ) as any; render(); await waitFor(() => { expect(screen.getByText("Alpaca Account")).toBeInTheDocument(); }); }); }); ``` - [ ] **Step 6: Run tests** ```bash npm run typecheck && npm test ``` - [ ] **Step 7: Commit** ```bash git add app/components/AlpacaAccountInfo.tsx app/api/alpaca/account.ts app/api/+types.ts app/routes/home.tsx app/components/__tests__/AlpacaAccountInfo.test.tsx git commit -m "feat: add Alpaca account info to home page" ``` ### Task 4: Update backend to calculate indicators (add indicator calculation logic) **Files:** - Create: `app/utils/indicators.ts` - Modify: `app/api/indicators.ts` - Test: `app/utils/__tests__/indicators.test.ts` - [ ] **Step 1: Implement indicator calculation functions** ```typescript // app/utils/indicators.ts export function calculateSMA(prices: number[], period: number = 20): number { if (prices.length < period) return 0; const sum = prices.slice(0, period).reduce((a, b) => a + b, 0); return sum / period; } export function calculateEMA(prices: number[], period: number = 20): number { if (prices.length < period) return 0; const multiplier = 2 / (period + 1); let ema = prices[period - 1]; for (let i = period; i < prices.length; i++) { ema = prices[i] * multiplier + ema * (1 - multiplier); } return ema; } export function calculateRSI(prices: number[], period: number = 14): number { if (prices.length < period + 1) return 0; let gains = 0; let losses = 0; for (let i = 1; i <= period; i++) { const diff = prices[i] - prices[i - 1]; if (diff > 0) gains += diff; else losses -= diff; } const avgGain = gains / period; const avgLoss = losses / period; const rs = avgGain / avgLoss; return 100 - (100 / (1 + rs)); } export function calculateMACD(prices: number[], fastPeriod: number = 12, slowPeriod: number = 26, signalPeriod: number = 9): number { if (prices.length < slowPeriod) return 0; const emaFast = calculateEMA(prices, fastPeriod); const emaSlow = calculateEMA(prices, slowPeriod); const macdLine = emaFast - emaSlow; // Signal line (EMA of MACD line) const signal = calculateEMA([macdLine], signalPeriod); return macdLine - signal; } ``` - [ ] **Step 2: Use these functions in indicators endpoint** ```typescript // app/api/indicators.ts import { calculateSMA, calculateEMA, calculateRSI, calculateMACD } from "../utils/indicators"; export const GET: RequestHandler = async ({ request, params }) => { const url = new URL(request.url); const symbol = url.searchParams.get("symbol"); if (!symbol) { return NextResponse.json( { error: "Symbol is required" }, { status: 400 } ); } try { // Fetch historic prices from Alpaca const prices = await fetchHistoricPrices(symbol); // Calculate indicators const sma = calculateSMA(prices); const ema = calculateEMA(prices); const rsi = calculateRSI(prices); const macd = calculateMACD(prices); const data = { symbol, indicators: { sma, ema, rsi, macd }, }; return NextResponse.json(data); } catch (error) { return NextResponse.json( { error: "Failed to fetch indicators" }, { status: 500 } ); } }; async function fetchHistoricPrices(symbol: string): Promise { // Implement Alpaca API call // Return array of closing prices return [100, 102, 101, 105, 107, 110, 108, 115, 120, 119]; } ``` - [ ] **Step 3: Write tests for indicator calculations** ```typescript // app/utils/__tests__/indicators.test.ts import { calculateSMA, calculateEMA, calculateRSI, calculateMACD } from "../indicators"; describe("Indicator calculations", () => { it("calculates SMA correctly", () => { const prices = [1, 2, 3, 4, 5]; expect(calculateSMA(prices, 3)).toBe(3); }); it("calculates EMA correctly", () => { const prices = [1, 2, 3, 4, 5]; expect(calculateEMA(prices, 3)).toBeCloseTo(3.5); }); it("calculates RSI correctly", () => { const prices = [100, 102, 101, 105, 107, 110, 108, 115, 120, 119]; expect(calculateRSI(prices, 5)).toBeCloseTo(66.7); }); it("calculates MACD correctly", () => { const prices = [100, 102, 101, 105, 107, 110, 108, 115, 120, 119]; expect(calculateMACD(prices)).toBeCloseTo(2.5); }); }); ``` - [ ] **Step 4: Run tests** ```bash npm run typecheck && npm test ``` - [ ] **Step 5: Commit** ```bash git add app/utils/indicators.ts app/api/indicators.ts app/utils/__tests__/indicators.test.ts git commit -m "feat: add indicator calculation utilities" ``` ### Task 5: Final integration and testing **Files:** - Modify: `package.json` (add test script if needed) - Modify: `vite.config.ts` (optional proxy) - [ ] **Step 1: Ensure Vite proxy is set up for API calls** ```typescript // vite.config.ts import { defineConfig } from "vite"; import react from "@vitejs/plugin-react"; export default defineConfig({ plugins: [react()], server: { proxy: { "/api": { target: "http://localhost:5173", changeOrigin: true, }, }, }, }); ``` - [ ] **Step 2: Run the full development server** ```bash npm run dev ``` - [ ] **Step 3: Verify the new route works** - Navigate to `http://localhost:5173/stocks` - Enter a symbol (e.g., AAPL) and confirm indicators appear - [ ] **Step 4: Verify Alpaca account info on home page** - Visit `http://localhost:5173/` and confirm account info displays - [ ] **Step 5: Run all tests** ```bash npm test ``` - [ ] **Step 6: Run typecheck** ```bash npm run typecheck ``` - [ ] **Step 7: Commit final changes** ```bash git add vite.config.ts git commit -m "chore: configure Vite proxy for API calls" ``` --- **Plan complete and saved to `docs/superpowers/plans/2026-05-12-stocks-route-plan.md`. Two execution options:** **1. Subagent-Driven (recommended)** - I dispatch a fresh subagent per task, review between tasks, fast iteration **2. Inline Execution** - Execute tasks in this session using executing-plans, batch execution with checkpoints **Which approach?**