# Project export: MarketMind

This document was generated by HackStack to give an AI agent context about a hackathon project. Sections are labeled with their provenance; content marked as truncated was cut to keep this document small.

## Project metadata

- Hackathon: Cal Hacks 12.0
- Tagline: Real-time stock market visualization application with AI-powered agent analysis.
- Devpost: https://devpost.com/software/marketmind-b6cy2q
- GitHub: https://github.com/AlexLuu1/MarketMind
- Video: https://www.youtube.com/embed/fEeDajzAcTY?enablejsapi=1&hl=en_US&rel=0&start=&version=3&wmode=transparent
- Result: winner (Elastic: Best use of the Elastic Agent Builder on a Serverless instance)
- Team: 2 GitHub contributor(s) — Alex Luu (8 commits), GilbertHarijanto (4 commits)

## Devpost submission (written by the team)

### Inspiration

Modern (novice) traders are flooded with fragmented, fast-moving information (price action, fundamentals, news/sentiment, and options/volatility) and lack a single, interpretable surface that converts these streams into trustworthy, real-time signals. Making fast, data-driven trading decisions becomes guesswork rather than insight. We wanted to bring order to that chaos by giving traders a clear and interpretable view of market energy in one place.

### What it does

MarketMind turns raw market data into simple, real-time visual signals. Six agents, each built with Elastic Agent Builder, analyze a different layer of the market: price movement, company fundamentals, news sentiment, volatility, etc. Their combined outputs appear as glowing pulses around each stock, showing whether momentum is building, risk is rising, or sentiment is shifting.

### How we built it

We built 6 agents connected to Elasticsearch, each reading from multiple indices and returning standardized JSON results. Every agent computes its own metrics, scoring, and reasoning before sending its output back to the MarketMind client. The frontend was built with React 19 and Next.js 16, using TypeScript, SVG physics-based visuals, and Chart.js for live candlestick charts. Yahoo Finance provided real-time data, while the app’s animation engine created the smooth pulsing and orbiting interactions that make the experience feel alive.

### Challenges we ran into

ESQL was powerful but strict, and small syntax differences broke queries. Aligning timestamps across datasets and keeping data fresh without overwhelming performance required multiple iterations. The hardest part was keeping every agent’s logic explainable while still running fast enough for real-time feedback.

### Accomplishments we're proud of

We successfully integrated real trading analysis techniques inside a fully explainable multi-agent system. The Oracle Network computes Technical Analysis indicators such as RSI, Bollinger Bands, and MACD to assess short-term price momentum. The Arbitrage Hunter applies Fundamental Analysis metrics including P/E Ratio and Beta to detect valuation imbalances. The Volatility Prophet uses Portfolio Optimization with Risk-Adjusted Allocation concepts like Modern Portfolio Theory and the Black-Litterman Model to contextualize volatility regimes. Bringing all of these together in one live system that is both interpretable and reactive is a major achievement.

### What we learned

We learned that modularity and explainability are essential for both trust and performance. By designing each agent as an independent analytical node with clear responsibilities, we reduced complexity while improving interpretability. We also discovered how visual design can make complex market models not only accessible but intuitive, helping users “see” patterns they might otherwise miss.

### What's next

Our next goal is to use realtime data sources to injest into Elasticsearch. This will provide better data to the agents which will result in better responses. We also aim to implement a user chat interface. This user agent will communicate with the Elastic agents using the A2A protocol to provide a response.

## README (from the GitHub repository)

# MarketMind

A real-time stock market visualization application with AI-powered agent analysis. MarketMind visualizes stock data through an interactive bubble interface where 6 unique AI agents continuously analyze stocks and provide trading signals through visual animations.

## Tech Stack

- **Next.js 16** (React 19)
- **Axios** for HTTP requests
- **SVG Animations** for pulse effects
- **Elastic Agent Builder API** for AI agent integration
- **Lava** for foundational model access


## Detected evidence (automated analysis)

Indexed codebase: 42 recognized source files, 237 KB.
- CSS (language) — detected in the code
- Next.js (technology) — detected in the code
- React (technology) — detected in the code
- TypeScript (language) — detected in the code

## Codebase structure (from repository index)

### Files (46 of 46)

```
.env.example
.gitignore
next.config.ts
package.json
README.md
src/app/api/agents/query/route.ts
src/app/api/portfolio/route.ts
src/app/api/stock/fundamental/route.ts
src/app/api/stock/historical/route.ts
src/app/api/stock/indicators/route.ts
src/app/api/stock/predict/route.ts
src/app/api/stock/price/route.ts
src/app/globals.css
src/app/layout.tsx
src/app/page.tsx
src/components/AgentBubble.tsx
src/components/Canvas.tsx
src/components/ChartSidebar.tsx
src/components/IndicatorTogglePanel.tsx
src/components/LeftSidebar.tsx
src/components/PortfolioDashboard.tsx
src/components/PredictionPanel.tsx
src/components/shared/index.ts
src/components/shared/LoadingState.tsx
src/components/shared/Panel.tsx
src/components/shared/SignalBadge.tsx
src/components/StockBubble.tsx
src/components/StockBubbleHoverPanel.tsx
src/components/StockChart.tsx
src/components/StockSelector.tsx
src/components/VolumeAnalysisPanel.tsx
src/constants/api.ts
src/constants/canvas.ts
src/constants/index.ts
src/constants/theme.ts
src/hooks/useAgentPolling.ts
src/hooks/useStockPrice.ts
src/types/index.ts
src/utils/agents.ts
src/utils/fundamentalAnalysis.ts
src/utils/portfolioManagement.ts
src/utils/pricePrediction.ts
src/utils/stockData.ts
src/utils/technicalAnalysis.ts
src/utils/volumeAnalysis.ts
tsconfig.json
```

### Dependencies

- package.json: @types/node@^20, @types/react@^19, @types/react-dom@^19, axios@^1.12.2, chart.js@^4.5.1, next@16.0.0, react@19.2.0, react-chartjs-2@^5.3.0, react-dom@19.2.0, typescript@^5, yahoo-finance2@^3.10.0

### Recent commits (newest first)

- Fix bugs
- Refactor and cleanup code
- Add neural activation and improvements
- added delay
- added prediction model, and improved UI/UX a lot
- Fix agent vert pathing
- Fixed most UI bugs
- Added more trading features
- Implement 2 new agents and improve polling
- Improved UI and add more features
- Bootstrap app with core components
- Initial commit from Create Next App

## Key source files (fetched from GitHub, selected and truncated for size)

### package.json

```
{
  "name": "market-mind",
  "version": "0.1.0",
  "private": true,
  "scripts": {
    "dev": "next dev",
    "build": "next build",
    "start": "next start"
  },
  "dependencies": {
    "axios": "^1.12.2",
    "chart.js": "^4.5.1",
    "next": "16.0.0",
    "react": "19.2.0",
    "react-chartjs-2": "^5.3.0",
    "react-dom": "19.2.0",
    "yahoo-finance2": "^3.10.0"
  },
  "devDependencies": {
    "@types/node": "^20",
    "@types/react": "^19",
    "@types/react-dom": "^19",
    "typescript": "^5"
  }
}

```

### src/constants/index.ts

```typescript
/**
 * Central export for all constants
 */

export * from './theme';
export * from './canvas';
export * from './api';

```

### src/app/layout.tsx

```typescript
import type { Metadata } from "next";
import "./globals.css";

export const metadata: Metadata = {
  title: "MarketMind",
  description: "Real-time stock market trading with AI agents",
};

export default function RootLayout({
  children,
}: Readonly<{
  children: React.ReactNode;
}>) {
  return (
    <html lang="en">
      <body>{children}</body>
    </html>
  );
}

```

### src/types/index.ts

```typescript
export interface Agent {
  id: string;
  connector_id: string;
  name: string;
  color: string;
  question: (symbol: string) => string;
}

export interface Stock {
  symbol: string;
  company: string;
  position: { x: number; y: number };
}

export interface AgentResponse {
  agentId: string;
  success: boolean;
  signal: 'bullish' | 'bearish' | 'neutral';
  confidence: number; // 0-1
  rawResponse?: string;
  reasoning?: string;
}

export interface Pulse {
  agentId: string;
  color: 'green' | 'red';
  timestamp: number;
}

export interface AgentSignalState {
  agentId: string;
  signal: 'bullish' | 'bearish' | 'neutral';
  color: 'green' | 'red' | null;
  confidence: number;
  rawResponse?: string;
  reasoning?: string;
  lastUpdated: number;
}

export interface AgentQueryResponse {
  success: boolean;
  timestamp: number;
  agents: AgentResponse[];
}

// Technical Analysis Types
export interface CandlestickData {
  time: string;
  open: number;
  high: number;
  low: number;
  close: number;
  volume: number;
}

export interface TechnicalIndicators {
  rsi: number;
  bollingerBands: {
    upper: number;
    middle: number;
    lower: number;
  };
  macd: {
    macdLine: number;
    signalLine: number;
    histogram: number;
  };
  sma20?: number;
  sma50?: number;
}

export interface TechnicalSignal {
  indicator: 'rsi' | 'bollinger' | 'macd';
  signal: 'bullish' | 'bearish' | 'neutral';
  strength: number; // 0-1
  value: number;
  timestamp: number;
}

// Fundamental Analysis Types
export interface FundamentalData {
  peRatio: number | null;
  forwardPE: number | null;
  beta: number | null;
  marketCap: number | null;
  enterpriseValue: number | null;
  priceToBook: number | null;
  priceToSales: number | null;
  dividendYield: number | null;
  eps: number | null;
  epsForward: number | null;
}

export interface FundamentalSignal {
  metric: 'pe' | 'beta' | 'valuation';
  signal: 'bullish' | 'bearish' | 'neutral';
  strength: number; // 0-1
  value: number;
  interpretation: string;
  timestamp: number;
}

// Portfolio Management Types
export interface PortfolioPosition {
  symbol: string;
  company: string;
  shares: number;
  averagePrice: number;
  currentPrice: number;
  marketValue: number;
  unrealizedPnL: number;
  unrealizedPnLPercent: number;
  weight: number; // Portfolio weight percentage
}

export interface Portfolio {
  id: string;
  name: string;
  positions: PortfolioPosition[];
  totalValue: number;
  totalPnL: number;
  totalPnLPercent: number;
  lastUpdated: number;
}

export interface PortfolioOptimization {
  optimalWeights: { [symbol: string]: number };
  expectedReturn: number;
  expectedVolatility: number;
  sharpeRatio: number;
  efficientFrontier: Array<{ return: number; volatility: number }>;
}

export interface PortfolioSignal {
  type: 'allocation' | 'rebalance' | 'risk';
  signal: 'bullish' | 'bearish' | 'neutral';
  strength: number; // 0-1
  recommendation: string;
  timestamp: number;
}

// Chart Indicator Types
export interface IndicatorToggle {
  id: string;
  name: string;
  enabled: boolean;
  color: string;
  type: 'line' | 'band' | 'histogram';
}

export interface ChartIndicators {
  rsi: boolean;
  bollingerBands: boolean;
  macd: boolean;
  sma20: boolean;
  sma50: boolean;
  volume: boolean;
}

// Volume Analysis Types
export interface VolumePattern {
  type: 'spike' | 'decline' | 'accumulation' | 'distribution' | 'breakout' | 'normal';
  strength: number; // 0-1
  description: string;
  timestamp: number;
}

export interface VolumeAnalysis {
  averageVolume: number;
  currentVolume: number;
  volumeRatio: number; // current / average
  patterns: VolumePattern[];
  signal: 'bullish' | 'bearish' | 'neutral';
}

// Price Prediction Types
export interface PredictionPoint {
  time: string;
  price: number;
  confidence: number; // 0-1
}

export interface ConfidenceInterval {
  lower: number;
  upper: number;
  confidence: number; // 0.95 for 95%
}

export interface PredictionResult {
  symbol: string;
  timeHorizon: '1d' | '1w' | '1m';
  predictions: PredictionPoint[];
  confidenceInterval: ConfidenceInterval;
  model: 'linear' | 'polynomial' | 'exponential';
  accuracy: number; // R² score
  lastUpdated: number;
  features: {
    dataPoints: number;
    rSquared: number;
    standardError: number;
    trend: 'bullish' | 'bearish' | 'neutral';
  };
}

```

### src/app/page.tsx

```typescript
'use client';

import { useState } from 'react';
import { Stock, ChartIndicators } from '@/types';
import Canvas from '@/components/Canvas';
import StockSelector from '@/components/StockSelector';
import ChartSidebar from '@/components/ChartSidebar';
import LeftSidebar from '@/components/LeftSidebar';
import PortfolioDashboard from '@/components/PortfolioDashboard';
import { STOCK_OFFSET } from '@/constants';

export default function Home() {
  const [stocks, setStocks] = useState<Stock[]>([]);
  const [sidebarOpen, setSidebarOpen] = useState(false);
  const [selectedStock, setSelectedStock] = useState<Stock | null>(null);
  const [portfolioOpen, setPortfolioOpen] = useState(false);
  const [chartIndicators, setChartIndicators] = useState<ChartIndicators>({
    rsi: false,
    bollingerBands: false,
    macd: false,
    sma20: false,
    sma50: false,
    volume: false,
  });
  const [showPrediction, setShowPrediction] = useState(false);
  const [predictionTimeHorizon, setPredictionTimeHorizon] = useState<'1d' | '1w' | '1m'>('1w');

  const handleAddStock = (symbol: string, company: string) => {
    // Check if stock already exists
    if (stocks.some((s) => s.symbol === symbol)) {
      alert('Stock already added!');
      return;
    }

    // Add new stock with initial position in the center
    // Add small offset for multiple stocks to avoid overlap
    const offsetX = (stocks.length % STOCK_OFFSET.modulo) * STOCK_OFFSET.horizontal - STOCK_OFFSET.horizontal; // -50, 0, 50
    const offsetY = Math.floor(stocks.length / STOCK_OFFSET.modulo) * STOCK_OFFSET.vertical; // 0, 50, 100...
    
    const newStock: Stock = {
      symbol,
      company,
      position: {
        x: window.innerWidth / 2 + offsetX, // Center horizontally with offset
        y: window.innerHeight / 2 + offsetY, // Center vertically with offset
      },
    };

    setStocks((prev) => [...prev, newStock]);
  };

  const handlePositionChange = (symbol: string, position: { x: number; y: number }) => {
    setStocks((prev) =>
      prev.map((stock) =>
        stock.symbol === symbol ? { ...stock, position } : stock
      )
    );
  };

  const handleRemove = (symbol: string) => {
    setStocks((prev) => prev.filter((stock) => stock.symbol !== symbol));
  };

  const handleStockSelect = (stock: Stock) => {
    setSelectedStock(stock);
    setSidebarOpen(true);
  };

  const handleSidebarClose = () => {
    setSidebarOpen(false);
    setSelectedStock(null);
  };

  const handleOpenPortfolio = () => {
    setPortfolioOpen(true);
  };

  const handleClosePortfolio = () => {
    setPortfolioOpen(false);
  };

  const handleIndicatorToggle = (indicator: keyof ChartIndicators) => {
    setChartIndicators(prev => ({
      ...prev,
      [indicator]: !prev[indicator]
    }));
  };

  const handleResetIndicators = () => {
    setChartIndicators({
      rsi: false,
      bollingerBands: false,
      macd: false,
      sma20: false,
      sma50: false,
      volume: false,
    });
  };

  const handleTogglePrediction = (enabled: boolean) => {
    setShowPrediction(enabled);
  };

  const handleTimeHorizonChange = (horizon: '1d' | '1w' | '1m') => {
    setPredictionTimeHorizon(horizon);
  };

  return (
    <main style={{ width: '100vw', height: '100vh', overflow: 'hidden', position: 'relative' }}>
      <Canvas
        stocks={stocks}
        onPositionChange={handlePositionChange}
        onRemove={handleRemove}
        onStockSelect={handleStockSelect}
        selectedStock={selectedStock}
      />
      <StockSelector onAddStock={handleAddStock} onOpenPortfolio={handleOpenPortfolio} />
      <LeftSidebar
        isOpen={sidebarOpen}
        selectedStock={selectedStock}
        chartIndicators={chartIndicators}
        onIndicatorToggle={handleIndicatorToggle}
        onResetIndicators={handleResetIndicators}
        showPrediction={showPrediction}
        predictionTimeHorizon={predictionTimeHorizon}
        onTogglePrediction={handleTogglePrediction}
        onTimeHorizonChange={handleTimeHorizonChange}
      />
      <ChartSidebar
        isOpen={sidebarOpen}
        selectedStock={selectedStock}
        onClose={handleSidebarClose}
        chartIndicators={chartIndicators}
        showPrediction={showPrediction}
        predictionTimeHorizon={predictionTimeHorizon}
      />
      <PortfolioDashboard
        isOpen={portfolioOpen}
        onClose={handleClosePortfolio}
      />
    </main>
  );
}

```

### src/components/shared/index.ts

```typescript
/**
 * Central export for shared components
 */

export { default as Panel } from './Panel';
export { default as SignalBadge } from './SignalBadge';
export { default as LoadingState } from './LoadingState';

```

### src/app/api/portfolio/route.ts

```typescript
import { NextRequest, NextResponse } from 'next/server';
import { Portfolio, PortfolioPosition } from '@/types';
import { createPortfolio, updatePortfolioPrices, calculateMPT, generatePortfolioSignals } from '@/utils/portfolioManagement';

// In-memory portfolio storage (in production, this would be a database)
let portfolios: Portfolio[] = [];

export async function GET(request: NextRequest) {
  try {
    const { searchParams } = new URL(request.url);
    const action = searchParams.get('action');
    const portfolioId = searchParams.get('id');

    if (action === 'list') {
      return NextResponse.json({ portfolios });
    }

    if (action === 'get' && portfolioId) {
      const portfolio = portfolios.find(p => p.id === portfolioId);
      if (!portfolio) {
        return NextResponse.json(
          { error: 'Portfolio not found' },
          { status: 404 }
        );
      }
      return NextResponse.json({ portfolio });
    }

    return NextResponse.json(
      { error: 'Invalid action or missing parameters' },
      { status: 400 }
    );
  } catch (error) {
    console.error('Error in portfolio GET:', error);
    return NextResponse.json(
      { error: 'Failed to fetch portfolio data' },
      { status: 500 }
    );
  }
}

export async function POST(request: NextRequest) {
  try {
    const body = await request.json();
    const { action, portfolioId, name, positions } = body;

    if (action === 'create') {
      if (!name || !positions || !Array.isArray(positions)) {
        return NextResponse.json(
          { error: 'Name and positions array are required' },
          { status: 400 }
        );
      }

      // Validate positions
      const validPositions: PortfolioPosition[] = positions.map((pos: any) => ({
        symbol: pos.symbol,
        company: pos.company || pos.symbol,
        shares: Number(pos.shares) || 0,
        averagePrice: Number(pos.averagePrice) || 0,
        currentPrice: Number(pos.currentPrice) || 0,
        marketValue: (Number(pos.shares) || 0) * (Number(pos.currentPrice) || 0),
        unrealizedPnL: ((Number(pos.shares) || 0) * (Number(pos.currentPrice) || 0)) - 
                       ((Number(pos.shares) || 0) * (Number(pos.averagePrice) || 0)),
        unrealizedPnLPercent: 0, // Will be calculated
        weight: 0 // Will be calculated
      }));

      // Calculate P&L percentages
      validPositions.forEach(pos => {
        const costBasis = pos.shares * pos.averagePrice;
        pos.unrealizedPnLPercent = costBasis > 0 ? (pos.unrealizedPnL / costBasis) * 100 : 0;
      });

      const portfolio = createPortfolio(name, validPositions);
      portfolios.push(portfolio);

      return NextResponse.json({ portfolio });
    }

    if (action === 'update' && portfolioId) {
      const portfolio = portfolios.find(p => p.id === portfolioId);
      if (!portfolio) {
        return NextResponse.json(
          { error: 'Portfolio not found' },
          { status: 404 }
        );
      }

      const updatedPortfolio = await updatePortfolioPrices(portfolio);
      const portfolioIndex = portfolios.findIndex(p => p.id === portfolioId);
      portfolios[portfolioIndex] = updatedPortfolio;

      return NextResponse.json({ portfolio: updatedPortfolio });
    }

    if (action === 'analyze' && portfolioId) {
      const portfolio = portfolios.find(p => p.id === portfolioId);
      if (!portfolio) {
        return NextResponse.json(
          { error: 'Portfolio not found' },
          { status: 404 }
        );
      }

      const updatedPortfolio = await updatePortfolioPrices(portfolio);
      const mpt = calculateMPT(updatedPortfolio);
      const signals = generatePortfolioSignals(updatedPortfolio);

      return NextResponse.json({
        portfolio: updatedPortfolio,
        mpt,
        signals
      });
    }

    return NextResponse.json(
      { error: 'Invalid action or missing parameters' },
      { status: 400 }
    );
  } catch (error) {
    console.error('Error in portfolio POST:', error);
    return NextResponse.json(
      { error: 'Failed to process portfolio request' },
      { status: 500 }
    );
  }
}

export async function DELETE(request: NextRequest) {
  try {
    const { searchParams } = new URL(request.url);
    const portfolioId = searchParams.get('id');

    if (!portfolioId) {
      return NextResponse.json(
        { error: 'Portfolio ID is required' },
        { status: 400 }
      );
    }

    const portfolioIndex = portfolios.findIndex(p => p.id === portfolioId);
    if (portfolioIndex === -1) {
      return NextResponse.json(
        { error: 'Portfolio not found' },
        { status: 404 }
      );
    }

    portfolios.splice(portfolioIndex, 1);
    return NextResponse.json({ success: true });
  } catch (error) {
    console.error('Error in portfolio DELETE:', error);
    return NextResponse.json(
      { error: 'Failed to delete portfolio' },
      { status: 500 }
    );
  }
}

```

### src/app/api/stock/fundamental/route.ts

```typescript
import { NextRequest, NextResponse } from 'next/server';
import YahooFinance from 'yahoo-finance2';
import { generateFundamentalSignals } from '@/utils/fundamentalAnalysis';

const yahooFinance = new YahooFinance();

export async function GET(request: NextRequest) {
  try {
    const { searchParams } = new URL(request.url);
    const symbol = searchParams.get('symbol');

    if (!symbol) {
      return NextResponse.json(
        { error: 'Stock symbol is required' },
        { status: 400 }
      );
    }

    const quote = await yahooFinance.quote(symbol);
    
    // Extract fundamental data
    const fundamentalData = {
      peRatio: quote.trailingPE || null,
      forwardPE: quote.forwardPE || null,
      beta: quote.beta || null,
      marketCap: quote.marketCap || null,
      enterpriseValue: quote.enterpriseValue || null,
      priceToBook: quote.priceToBook || null,
      priceToSales: quote.priceToSalesTrailing12Months || null,
      dividendYield: quote.dividendYield || null,
      eps: quote.trailingEps || null,
      epsForward: quote.forwardEps || null,
    };

    // Generate fundamental signals
    const signals = generateFundamentalSignals(fundamentalData);

    return NextResponse.json({
      symbol,
      fundamentalData,
      signals,
      timestamp: Date.now()
    });
  } catch (error) {
    console.error('Error fetching fundamental analysis:', error);
    return NextResponse.json(
      { error: 'Failed to fetch fundamental analysis' },
      { status: 500 }
    );
  }
}

```

### src/app/api/stock/indicators/route.ts

```typescript
import { NextRequest, NextResponse } from 'next/server';
import YahooFinance from 'yahoo-finance2';
import { calculateAllIndicators, generateTechnicalSignals } from '@/utils/technicalAnalysis';

const yahooFinance = new YahooFinance();

export async function GET(request: NextRequest) {
  try {
    const { searchParams } = new URL(request.url);
    const symbol = searchParams.get('symbol');

    if (!symbol) {
      return NextResponse.json(
        { error: 'Stock symbol is required' },
        { status: 400 }
      );
    }

    // Get 60 days of data for technical indicators
    const historical = await yahooFinance.historical(symbol, {
      period1: new Date(Date.now() - 60 * 24 * 60 * 60 * 1000), // 60 days ago
      period2: new Date(),
      interval: '1d' as '1d' | '1wk' | '1mo'
    });
    
    const candlestickData = historical.map((item: any) => ({
      time: item.date.toISOString(),
      open: Number(item.open.toFixed(2)),
      high: Number(item.high.toFixed(2)),
      low: Number(item.low.toFixed(2)),
      close: Number(item.close.toFixed(2)),
      volume: item.volume || 0,
    }));

    // Calculate technical indicators
    const indicators = calculateAllIndicators(candlestickData);
    const signals = generateTechnicalSignals(candlestickData);

    return NextResponse.json({
      symbol,
      indicators,
      signals,
      data: candlestickData,
      timestamp: Date.now()
    });
  } catch (error) {
    console.error('Error fetching technical indicators:', error);
    return NextResponse.json(
      { error: 'Failed to fetch technical indicators' },
      { status: 500 }
    );
  }
}

```

### src/app/api/agents/query/route.ts

```typescript
import { NextRequest, NextResponse } from 'next/server';
import axios from 'axios';
import { AGENTS, analyzeResponse, generateMockResponse } from '@/utils/agents';
import { AgentResponse } from '@/types';

export async function POST(request: NextRequest) {
  try {
    const { symbol } = await request.json();

    if (!symbol) {
      return NextResponse.json(
        { error: 'Stock symbol is required' },
        { status: 400 }
      );
    }

    const queryAgent = async (agent: typeof AGENTS[0]): Promise<AgentResponse> => {
      try {
        const response = await axios.post(
          process.env.ELASTIC_API_URL!,
          {
            input: agent.question(symbol),
            agent_id: agent.id,
            connector_id: agent.connector_id,
          },
          {
            headers: {
              'Authorization': `ApiKey ${process.env.ELASTIC_API_KEY!}`,
              'Content-Type': 'application/json',
              'kbn-xsrf': 'true',
            },
            timeout: 300000,
          }
        );

        const responseText = typeof response.data === 'string' 
          ? response.data 
          : JSON.stringify(response.data);

        const analysis = analyzeResponse(responseText);

        return {
          agentId: agent.id,
          success: true,
          signal: analysis.signal,
          confidence: analysis.confidence,
          rawResponse: responseText,
          reasoning: analysis.reasoning,
        };
      } catch (error) {
        return generateMockResponse(agent.id);
      }
    };

    const agentResponses = await Promise.all(AGENTS.map(queryAgent));

    return NextResponse.json({
      success: true,
      timestamp: Date.now(),
      agents: agentResponses,
    });
  } catch (error) {
    console.error('Error in agent query:', error);
    return NextResponse.json(
      { error: 'Failed to query agents' },
      { status: 500 }
    );
  }
}

```

[32 more indexed source files omitted to keep this export small. The full file list is in the Codebase structure section above.]