# Project export: TickerMaster

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: TreeHacks 2026
- Tagline: Know when a stock is a good buy or a goodbye. Test your trading strategies against AI panelists backed with the latest sentiment signals.
- Devpost: https://devpost.com/software/tickermaster
- GitHub: https://github.com/arijitchakma79/TickerMaster
- Demo: https://tickermasterfinance.vercel.app/
- Video: https://www.youtube.com/embed/3-6M2NmMRMI?enablejsapi=1&hl=en_US&rel=0&start=&version=3&wmode=transparent
- Team: 4 GitHub contributor(s) — msamanthaf (27 commits), Jeffrey Gong (18 commits), arijitchakma79 (6 commits), Cursor (1 commits)

## Devpost submission (written by the team)

### Inspiration

One tweet can move a stock overnight, and as beginner retail traders, we often follow advices and FOMO with little to no real research while large institutions have Bloomberg terminals on their hands. Approximately 70% to 90% of retail traders lose money over the long term regardless of market conditions, according to US SEC. TickerMaster is a sandbox of financial AI agents that lets you test strategies, learn trading fundamentals, and understand sentiment-driven moves before you place real world orders.

### What it does

TickerMaster has 3 core features: Research: Input any ticker and get a live, cited brief that combines: Market data and technical context Perplexity-powered catalyst synthesis Social sentiment from X/Reddit Prediction-market context (Kalshi/Polymarket) Research: Input any ticker and get a live, cited brief that combines: Market data and technical context Perplexity-powered catalyst synthesis Social sentiment from X/Reddit Prediction-market context (Kalshi/Polymarket) Simulation: Run a multi-agent trading arena where different AI personas react to: Volatility regimes Breaking narrative shifts Each other’s behavior Portfolio/risk constraints. Simulation: Run a multi-agent trading arena where different AI personas react to: Volatility regimes Breaking narrative shifts Each other’s behavior Portfolio/risk constraints. Tracker: Set watchlists and alerts that continuously monitor your tickers and notify you when key signals hit. You can also video call with our AI avatar that will support you as a broker agent 24/7. Tracker: Set watchlists and alerts that continuously monitor your tickers and notify you when key signals hit. You can also video call with our AI avatar that will support you as a broker agent 24/7.

### How we built it

Architecture: Frontend: React + TypeScript (Vite) Backend: FastAPI + WebSockets Data/Auth: Supabase Deployment: Vercel (frontend) + cloud backend service Sponsor/tool integrations: Modal Inference : Persona inference workflows Modal Sandbox: Isolated simulation execution Perplexity Sonar: Cited research synthesis OpenAI: Commentary, explanation, and educational post-analysis Browserbase/ Stagehand (integration path) : Automated web data workflows HeyGen: Conversational broker-avatar UX Engineering highlights: Source-aware research pipeline with fallback behavior Real-time event streaming over WebSockets Agent orchestration for simulation + tracker systems Caching/rate-limit controls and production guardrails

### Challenges we ran into

One teammate dropped/had to leave :( Managing API limits especially with hosting/deployment. Hence, ⚠️ disclaimer: Our deployed Vercel website might have hit the token limit by the time you check it out. Come stop by our booth, where we will demo TickerMaster live!

### Accomplishments we're proud of

Built an end-to-end “retail Bloomberg sandbox” in hackathon time Shipped a working multi-agent simulation system Delivered citation-backed research summaries from multiple signal types Implemented persistent tracker workflows with alert context Created a product that teaches process, not just predictions

### What we learned

Running inference and sandboxes on Modal.

### What's next

Live Brokerage Calls: Users can execute real trades with a nostalgic NYSE floor-style voice flow, where an AI broker calls out the order, confirms risk checks, and submits it in real time. Better portfolio-level risk analytics and scenario testing Deeper explainability for “why this signal matters now” Smarter agent memory and adaptive strategy tuning Reliability upgrades for always-on production performance DISCLAIMERS: TickerMaster is educational and not investment advice. This application is resource-intensive and performs frequent reads/writes and large data pulls across multiple services. Our backend is currently deployed on a free-tier plan, so you may experience slow load times, rate limits, temporary downtime, or delayed updates—especially during peak traffic. If the site is bottlenecked when you try it, please stop by our booth for a live demo of TickerMaster!

## README (from the GitHub repository)

# TickerMaster MVP (TreeHacks 2026)

TickerMaster is a real-time sandbox for learning trading dynamics through AI agents and market intelligence feeds.

Core product surfaces:
1. `Research`: Perplexity Sonar + X + Reddit + prediction-market context.
2. `Simulation`: Multi-agent arena with order-book impact, slippage, delayed news propagation, and crash regimes.
3. `Tracker`: Real-time watchlist with valuation metrics, spike detection, and alert pipeline.

## Stack
- Backend: `FastAPI` + `WebSockets`
- Market Data: `Alpaca` (primary) + `Finnhub` (fallback)
- Frontend: `React` + `TypeScript` + `Vite` + `Recharts`
- Agent models: `OpenRouter` (open-source model default: `meta-llama/llama-3.1-8b-instruct`)
- Commentary model: `OpenAI`

## Monorepo Layout
```text
TickerMaster/
  backend/
    app/
      main.py
      schemas.py
      routers/
      services/
    requirements.txt
    .env.example
  frontend/
    src/
      components/
      hooks/
      lib/
    package.json
    .env.example
  .env.example
  .gitignore
```

## Step-by-Step Startup

### 1) Create `.env` in repo root
Create `/TickerMaster/.env` and include at minimum:

```env
# Supabase
SUPABASE_URL=https://<your-project>.supabase.co
SUPABASE_KEY=<your-publishable-key>
SUPABASE_SERVICE_KEY=<your-secret-service-role-key>
DATABASE_URL=postgresql://postgres:<password>@db.<project>.supabase.co:5432/postgres

# Backend URL
BACKEND_URL=http://localhost:8000
```

Add your API keys for Alpaca / Finnhub / Perplexity / OpenAI / OpenRouter / X / Browserbase / Modal as needed.
For SMS notifications, also configure Twilio:
- `TWILIO_ACCOUNT_SID`
- `TWILIO_AUTH_TOKEN`
- `TWILIO_FROM_NUMBER`
- optional fallback recipient `TWILIO_DEFAULT_TO_NUMBER`

For Modal sandbox runtime, also set:
- `MODAL_SIMULATION_APP_NAME` (default `tickermaster-simulation`)
- `MODAL_SANDBOX_TIMEOUT_SECONDS` (default `600`)
- `MODAL_SANDBOX_IDLE_TIMEOUT_SECONDS` (default `120`)
- `MODAL_INFERENCE_FUNCTION_NAME` (default `agent_inference`)
- `MODAL_INFERENCE_TIMEOUT_SECONDS` (default `15`)

To enable Modal inference function:
```bash
modal secret create tickermaster-secrets OPENROUTER_API_KEY=<your-openrouter-key>
modal deploy simulation/modal_inference.py
```

For frontend auth, add these in `frontend/.env`:
```bash
VITE_API_URL=http://localhost:8000
VITE_SUPABASE_URL=https://<your-project>.supabase.co
VITE_SUPABASE_ANON_KEY=<your-publishable-key>
```

### 2) Apply database schema in Supabase
In Supabase Dashboard:
1. Open `SQL Editor`.
2. Paste contents of `supabase/schema.sql`.
3. Run it once.

This creates tables like `research_cache`, `agent_activity`, `simulations`, `tracker_agents`, `tracker_alerts`, `watchlist`, and `favorite_stocks`.

### 3) Start backend (Terminal A)
```bash
cd backend
python3 -m venv .venv
source .venv/bin/activate
pip install -r requirements.txt
uvicorn app.main:app --host 0.0.0.0 --port 8000 --reload
```

Backend URL: `http://localhost:8000`

### 4) Start frontend (Terminal B)
```bash
cd frontend
npm install
npm run dev
```

Frontend URL: `http://localhost:5173`

### 5) Verify backend is healthy
```bash
curl http://localhost:8000/api/health
curl http://localhost:8000/api/ticker/NVDA/quote
curl http://localhost:8000/api/ticker/NVDA/ai-research
curl http://localhost:8000/api/ticker/NVDA/sentiment
curl "http://localhost:8000/api/prediction-markets?query=fed"
curl http://localhost:8000/api/ticker/NVDA/x-sentiment
```

### 6) Optional: verify Supabase writes
Use Supabase SQL Editor or REST to confirm rows are being inserted into:
- `research_cache`
- `agent_activity`
- `simulations`
- `tracker_alerts`

If cache writes appear but activity/alerts do not, confirm backend is using `SUPABASE_SERVICE_KEY` (not only publishable key).

## API Highlights
- `POST /research/analyze`
- `GET /research/candles/{ticker}`
- `POST /simulation/start`
- `POST /simulation/stop/{session_id}`
- `GET /simulation/sessions`
- `POST /simulation/modal/sandbox`
- `GET /simulation/modal/cron-health`
- `GET /tracker/snapshot`
- `POST /tracker/watchlist`
- `POST /tracker/alerts`
- `POST /tracker/poll`
- `POST /api/tracker/agents/nl-create` (natural-language tracker agent creation)
- `POST /api/tracker/agents/{agent_id}/interact` (manager chat + tool routing)
- `POST /chat/commentary`
- `GET /integrations`
- `WS /ws/stream?channels=global,simulation,tracker`

## How the MVP Maps to Sponsor Tool Requirements

### Research
- Perplexity Sonar API for catalyst synthesis.
- X API and Reddit API ingestion for public sentiment flow.
- Kalshi + Polymarket adapters for prediction-market context.
- Finance graphing via Alpaca/Finnhub candles and metric tables.
- Tool links exposed in UI for Morningstar / Reuters / J.P. Morgan / Alpaca / Finnhub.

### Simulation
- Natural-language sandbox trigger endpoint for Modal (`/simulation/modal/sandbox`).
- Backend now launches Modal sandboxes through the Modal Python SDK (`modal==1.3.3`) when credentials are present.
- Simulation sessions started with `inference_runtime=modal` first call a Modal function for agent decisions, then fall back to direct OpenRouter if Modal inference is unavailable.
- OpenRouter-powered agents with user-defined parameters:
  - personality
  - model
  - aggressiveness
  - risk limit
  - trade size
- Realism mechanics:
  - order book spread + market impact
  - execution slippage
  - delayed news diffusion (quant first, retail lag)
  - crash regimes sampled from S&P 500 return distribution

### Tracker Pipeline
- `Trigger`: periodic polling loop for price/volume anomalies (cron-ready for Modal).
- `Investigate`: Perplexity Sonar explains likely catalysts.
- `Analyze`: Cerebras or NVIDIA NIM synthesizes high-signal narrative.
- `Notify`: Poke Recipe/MCP handoff payload (`npx poke` workflow, no direct Poke HTTP API dependency).

### Poke Setup (TreeHacks)
Run this once from repo root to wrap TickerMaster as a Poke MCP Recipe:
```bash
npx poke
```
Then open Kitchen to test/deploy your Recipe and wire alert payloads:
- Kitchen: https://poke.com/kitchen
- Recipes docs: https://poke.com/docs/recipes

## Required External Links
- OpenAI: https://platform.openai.com/
- OpenRouter: https://openrouter.ai/
- Perplexity Sonar: https://docs.perplexity.ai/
- X API: https://developer.x.com/en/docs
- Reddit API: https://www.reddit.com/dev/api/
- Kalshi API: https://docs.kalshi.com/
- Polymarket: https://docs.polymarket.com/
- Modal Sandbox: https://modal.com/docs/guide/sandbox
- Modal Cron: https://modal.com/docs/guide/cron
- Poke Docs: https://poke.com/docs/recipes
- Poke Kitchen: https://poke.com/kitchen
- Poke npm package: https://www.npmjs.com/package/poke
- Interaction Company: https://interaction.co/
- Cerebras API: https://inference-docs.cerebras.ai/
- NVIDIA NIM: https://build.nvidia.com/
- Morningstar: https://www.morningstar.com/
- Reuters Markets: https://www.reuters.com/markets/
- J.P. Morgan Insights: https://www.jpmorgan.com/insights
- Alpaca Market Data: https://docs.alpaca.markets/docs/about-market-data-api
- Finnhub: https://finnhub.io/

## Notes
- This MVP is educational and not investment advice.
- Most integrations gracefully fall back to synthetic/demo responses if keys are missing.
- For production: persist state, secure auth, rate-limit providers, and harden retry/backoff logic.


## Detected evidence (automated analysis)

Indexed codebase: 79 recognized source files, 1002 KB.
- CSS (language) — detected in the code
- FastAPI (technology) — detected in the code
- HTML (language) — detected in the code
- Python (language) — detected in the code
- React (technology) — detected in the code
- SQL (language) — detected in the code
- Supabase (technology) — detected in the code
- TypeScript (language) — detected in the code
- OpenAI (technology) — claimed on Devpost, not found in the code
- AI coding agent: Cursor — evidence: commit authorship or trailers

## Codebase structure (from repository index)

### Files (87 of 87)

```
.gitignore
backend/app/__init__.py
backend/app/config.py
backend/app/main.py
backend/app/mcp/__init__.py
backend/app/mcp/tracker_tools_server.py
backend/app/routers/__init__.py
backend/app/routers/api.py
backend/app/routers/chat.py
backend/app/routers/research.py
backend/app/routers/simulation.py
backend/app/routers/system.py
backend/app/routers/tracker.py
backend/app/schemas.py
backend/app/services/__init__.py
backend/app/services/activity_stream.py
backend/app/services/agent_logger.py
backend/app/services/browserbase_scraper.py
backend/app/services/database.py
backend/app/services/llm.py
backend/app/services/macro.py
backend/app/services/market_data.py
backend/app/services/mcp_tool_router.py
backend/app/services/modal_engine.py
backend/app/services/modal_integration.py
backend/app/services/notifications.py
backend/app/services/persona_training.py
backend/app/services/prediction_markets.py
backend/app/services/reddit_client.py
backend/app/services/research_cache.py
backend/app/services/sentiment.py
backend/app/services/simulation_agents.py
backend/app/services/simulation_store.py
backend/app/services/simulation.py
backend/app/services/tracker_csv.py
backend/app/services/tracker_repository.py
backend/app/services/tracker.py
backend/app/services/user_context.py
backend/app/services/user_preferences.py
backend/app/ws_manager.py
backend/requirements.txt
backend/scripts/validate_env.py
backend/tests/test_api_hardening.py
backend/tests/test_auth_and_tracker_security.py
backend/tests/test_deploy_smoke.py
backend/tests/test_market_movers_snapshot.py
backend/tests/test_prediction_market_links.py
backend/tests/test_prediction_market_relevance.py
backend/tests/test_simulation_agents_fallback.py
frontend/index.html
frontend/next.config.mjs
frontend/package.json
frontend/src/App.tsx
frontend/src/components/ChatPanel.tsx
frontend/src/components/EventRail.tsx
frontend/src/components/IntegrationStatus.tsx
frontend/src/components/ResearchPanel.tsx
frontend/src/components/ResearchRail.tsx
frontend/src/components/SimulationPanel.tsx
frontend/src/components/StockChart.tsx
frontend/src/components/TrackerPanel.tsx
frontend/src/components/TrackerPrefsRail.tsx
frontend/src/components/WatchlistBar.tsx
frontend/src/env.d.ts
frontend/src/hooks/useSocket.ts
frontend/src/lib/api.ts
frontend/src/lib/format.ts
frontend/src/lib/tickerDirectory.ts
frontend/src/lib/tickerInput.ts
frontend/src/lib/types.ts
frontend/src/main.tsx
frontend/src/styles.css
frontend/src/types.d.ts
frontend/tsconfig.json
frontend/vite.config.ts
Makefile
modal_inference.py
Modal_Sponsor_Documentation.md
package.json
Perplexity_sponsor_documentation.md
README.md
scripts/pre_deploy_check.sh
simulation/modal_inference.py
supabase/rls_patch.sql
supabase/schema.sql
tech_implementatino.txt
tracker/modal_cron.py
```

### Dependencies

- backend/requirements.txt: fastapi@==0.115.8, fredapi@==0.5.2, httpx@==0.28.1, modal@==1.3.3, nltk@==3.9.1, numpy@==2.2.3, pandas@==2.2.3, poke@==0.1.1, praw@==7.8.1, pydantic-settings@==2.8.1, python-dotenv@==1.0.1, requests-oauthlib@==2.0.0, supabase@==2.28.0, uvicorn[standard]@==0.34.0
- frontend/package.json: @types/lodash@^4.17.23, @types/react@^18.3.18, @types/react-dom@^18.3.5, @vitejs/plugin-react@^4.3.4, axios@^1.8.2, clsx@^2.1.1, jspdf@^4.1.0, lightweight-charts@^5.1.0, react@^18.3.1, react-dom@^18.3.1, recharts@^2.15.1, typescript@^5.7.3, vite@^6.1.0

### Recent commits (newest first)

- Merge pull request #6 from arijitchakma79/mcp
- Now fixed market grid
- fixes for supabase
- Fix market grid
- fixes
- Merge origin/main into mcp
- Trigger Railway redeploy with dotenv fix
- attempted poke
- changes
- test
- added tracker agent
- added tracker agent
- Merge pull request #5 from arijitchakma79/modal
- Fix modal
- sonar
- added whatsapp text feature
- Modal doc
- added whatsapp notification connection
- main_temp
- added x and fixed reddit

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

### Modal_Sponsor_Documentation.md

```markdown
# Modal APIs (Sponsor)

This app uses Modal in two ways:
1. `Inference` (Financial AI agents' persona setup and trading decisions).
2. `Sandbox` (Runs AI-generated simulation code in an isolated cloud runtime).

## 1) Inference

### What it is used for
- We look at public SEC 13F filings for known firms/personas.
- Modal infers each persona's behavior settings from that data.
- During the simulation, Modal keeps deciding what each persona should do (buy, sell, hold, size, confidence, reason).

### Technical details
- Persona parameter inference function: `persona_param_inference`
- Decision refinement function: `agent_inference`
- Key outputs:
  - persona params: `strategy_prompt`, `aggressiveness`, `risk_limit`, `trade_size`
  - decision payload: `side`, `quantity`, `confidence`, `target_ticker`, `rationale`

### Data source
- Public SEC 13F datasets (SEC filings) and shared strategies.

### Flow
```mermaid
flowchart LR
  A[SEC 13F public data] --> B[Modal: persona_param_inference]
  B --> C[Persona params set]
  C --> D[Modal: agent_inference]
  D --> E[Decision output]
  E --> F[Trade execution + broadcast]
```

### Can be found at
- `simulation/modal_inference.py`
- `backend/app/services/persona_training.py`
- `backend/app/services/modal_engine.py`
- `backend/app/services/simulation.py`

### Screenshots
![Inference simulation view](frontend/src/images/simulation.png)
![Inference dashboard](frontend/src/images/inference_dashboard.png)

## 2) Sandbox

### What it is used for
- A Modal sandbox is launched when a user starts a trading simulation.
- The backend first generates Python strategy code from the user/session prompt.
- That generated code is executed inside the sandbox (isolated runtime), and the output is returned.

### Technical details
- API endpoint: `POST /simulation/modal/sandbox`
- Backend launcher: `spin_modal_sandbox(...)`
- Returns:
  - `sandbox_id`
  - `app_id`
  - `dashboard_url`
  - `generated_code_preview`
  - `execution_result_preview` / `execution_error`

### Flow
```mermaid
flowchart LR
  A[Start Trading] --> B[POST /simulation/modal/sandbox]
  B --> C[Generate Python strategy code from prompt]
  C --> D[Create Modal Sandbox]
  D --> E[Execute generated code in isolated runtime]
```

### Can be found at
- `backend/app/services/modal_integration.py`
- `backend/app/routers/simulation.py`
- `frontend/src/components/SimulationPanel.tsx`

### Screenshot
![Sandbox dashboard](frontend/src/images/sandbox_dashboard.png)

```

### Perplexity_sponsor_documentation.md

```markdown
# Perplexity Sonar Sponsor Documentation

TickerMaster uses the Perplexity Sonar API as the "market catalyst reasoning" layer across three product surfaces:
1. Research Workbench (structured bullish/bearish catalysts + citations).
2. Simulation Arena (live catalyst bullets injected into the simulated news stream).
3. Tracker (one-paragraph catalyst investigation when watchlist triggers fire).

## 1) Research Workbench (Primary Sonar Use)

### What it is used for
- Generate a concise, structured catalyst brief for a ticker (last ~7 days):
  - Bullish catalysts
  - Bearish catalysts
  - What to watch next
- Extract and display Perplexity citations as clickable sources in the UI.
- Derive a sentiment score from the Sonar summary and blend it into a composite signal.

### Endpoints and flow
```mermaid
flowchart LR
  A[User clicks Run Research] --> B[POST /research/analyze]
  B --> C[run_research()]
  C --> D[_perplexity_summary()]
  D --> E[Perplexity Sonar API: /chat/completions]
  E --> F[summary markdown + citations URLs]
  F --> G[UI renders Perplexity section + Sources links]
```

### What we store/return
- `summary`: sanitized markdown with headings + bullets.
- `links`: extracted from `citations` (first ~6 URLs).
- `score`: sentiment score derived from the summary text.
- `source_breakdown`: includes a row with `source="Perplexity Sonar"`.

### Code references
- Backend Sonar call + citation extraction: `backend/app/services/sentiment.py`
- Research endpoint: `backend/app/routers/research.py`
- Aggregated ticker bundle endpoints (also call `run_research`): `backend/app/routers/api.py`
- UI rendering of Perplexity section: `frontend/src/components/ResearchPanel.tsx`
- Caching layer (reduces repeated Sonar calls): `backend/app/services/research_cache.py`

### Screenshots
![Research Sonar](frontend/src/images/sonar1.png)

## 2) Simulation Arena (Live Catalyst Bullets)

### What it is used for
- Fetch "latest market-moving headlines or catalysts" as short bullet lines.
- Inject those lines into the simulation's news stream as events with `source="Perplexity Sonar"`.
- The simulation engine uses this news stream to compute a "news bias" signal that influences agent decisions.

### Flow
```mermaid
flowchart LR
  Sim[Simulation loop] --> Sonar[Perplexity Sonar]
  Sonar --> News[News bullets]
  News --> Agents[Agents react]
```

### Code references
- Sonar news fetch in simulation engine: `backend/app/services/simulation.py`


### Screenshots
![Simulation Sonar](frontend/src/images/sonar2.png)

## 3) Tracker (Trigger Investigation)

### What it is used for
- When a tracker agent detects a trigger (price/volume/sentiment), we ask Sonar for a single-paragraph explanation:
  - "Explain probable catalysts for this stock move"
- That investigation text is used as context for an alert narrative.

### Flow
```mermaid
flowchart LR
  Trigger[Tracker trigger] --> Sonar[Perplexity Sonar]
  Sonar --> Alert[Alert explanation]
```

### Code references
- Sonar inves
[truncated — 49 more characters]
```

### package.json

```
{
  "name": "tickermaster",
  "private": true,
  "scripts": {
    "dev": "npm --prefix frontend run dev",
    "build": "npm --prefix frontend run build",
    "start": "npm --prefix frontend run start",
    "lint": "npm --prefix frontend run lint"
  }
}

```

### backend/requirements.txt

```
fastapi==0.115.8
uvicorn[standard]==0.34.0
pydantic-settings==2.8.1
httpx==0.28.1
numpy==2.2.3
pandas==2.2.3
python-dotenv==1.0.1
supabase==2.28.0
praw==7.8.1
nltk==3.9.1
fredapi==0.5.2
requests-oauthlib==2.0.0
modal==1.3.3
poke==0.1.1

```

### frontend/package.json

```
{
  "name": "tickermaster-frontend",
  "private": true,
  "version": "0.1.0",
  "type": "module",
  "scripts": {
    "dev": "vite",
    "build": "tsc --noEmit && vite build",
    "preview": "vite preview"
  },
  "dependencies": {
    "axios": "^1.8.2",
    "clsx": "^2.1.1",
    "jspdf": "^4.1.0",
    "lightweight-charts": "^5.1.0",
    "react": "^18.3.1",
    "react-dom": "^18.3.1",
    "recharts": "^2.15.1"
  },
  "devDependencies": {
    "@types/lodash": "^4.17.23",
    "@types/react": "^18.3.18",
    "@types/react-dom": "^18.3.5",
    "@vitejs/plugin-react": "^4.3.4",
    "typescript": "^5.7.3",
    "vite": "^6.1.0"
  }
}

```

### frontend/src/main.tsx

```typescript
import React from "react";
import ReactDOM from "react-dom/client";
import App from "./App";
import "./styles.css";

ReactDOM.createRoot(document.getElementById("root") as HTMLElement).render(
  <React.StrictMode>
    <App />
  </React.StrictMode>
);

```

### backend/app/main.py

```python
from __future__ import annotations

import asyncio
import logging
import sys
from contextlib import asynccontextmanager
from datetime import datetime, timezone
from typing import Any

from fastapi import FastAPI, WebSocket, WebSocketDisconnect
from fastapi.middleware.cors import CORSMiddleware

from app.config import get_settings
from app.routers import api, chat, research, simulation, system, tracker
from app.services.activity_stream import set_ws_manager
from app.services.mcp_tool_router import shutdown_tracker_mcp_router
from app.services.simulation import SimulationOrchestrator
from app.services.tracker import TrackerService
from app.services.tracker_csv import ensure_tracker_storage_buckets
from app.ws_manager import WSManager

logger = logging.getLogger(__name__)

# Avoid noisy WinError 10054 callback traces from Proactor transport shutdown
# when local clients disconnect abruptly after successful responses.
if sys.platform.startswith("win"):
    try:
        asyncio.set_event_loop_policy(asyncio.WindowsSelectorEventLoopPolicy())
    except Exception:
        pass

@asynccontextmanager
async def lifespan(app: FastAPI):
    settings = get_settings()
    ws_manager = WSManager()
    orchestrator = SimulationOrchestrator(settings, ws_manager)
    tracker_service = TrackerService(settings, ws_manager, orchestrator=orchestrator)

    app.state.settings = settings
    app.state.ws_manager = ws_manager
    app.state.orchestrator = orchestrator
    app.state.tracker = tracker_service
    set_ws_manager(ws_manager)
    try:
        buckets_ready = await asyncio.to_thread(ensure_tracker_storage_buckets)
        if not buckets_ready:
            logger.warning("One or more tracker storage buckets are not ready on startup.")
    except Exception:
        logger.exception("Failed to ensure Supabase tracker storage buckets on startup.")

    try:
        await tracker_service.start()
    except Exception:
        logger.exception("Failed to start tracker service")

    try:
        yield
    finally:
        try:
            await tracker_service.stop()
        except Exception:
            logger.exception("Failed to stop tracker service")

        session_ids: list[Any]
        try:
            session_ids = list(orchestrator.sessions.keys())  # type: ignore[union-attr]
        except Exception:
            session_ids = list(orchestrator.sessions)  # type: ignore[arg-type]

        for session_id in session_ids:
            try:
                await orchestrator.stop(session_id)
            except Exception:
                logger.exception("Failed to stop simulation session %s", session_id)

        try:
            await shutdown_tracker_mcp_router()
        except Exception:
            logger.exception("Failed to shutdown tracker MCP router")


settings = get_settings()
app = FastAPI(title=settings.app_name, version="0.1.0", lifespan=lifespan)

app.add_middleware(
    CORSMiddleware,
    allow_origins=settings.frontend_origins,
    allow_credentials=True,
    allow_methods=["GET", "POST", "PUT", "PATCH", "DELETE", "OPTIONS"],
    allow_headers=["Authorization", "Content-Type", "X-User-Id"],
)

app.include_router(system.router)
app.include_router(api.router)
app.include_router(research.router)
app.include_router(simulation.router)
app.include_router(tracker.router)
app.include_router(chat.router)


@app.websocket("/ws/stream")
async def websocket_stream(websocket: WebSocket):
    allowed_channels = {"global", "simulation", "tracker", "agents"}
    channels_param = websocket.query_params.get("channels", "global,simulation,tracker")
    requested_channels = {channel.strip() for channel in channels_param.split(",") if channel.strip()}
    channels = {channel for channel in requested_channels if channel in allowed_channels} or {"global"}

    manager: WSManager = websocket.app.state.ws_manager
    await manager.connect(websocket, channels=channels)

    await manager.broadcast(
        {
            "channel": "global",
            "type": "socket_join",
            "timestamp": datetime.now(timezone.utc).isoformat(),
            "channels": sorted(channels),
        }
    )

    try:
        while True:
            raw = await websocket.receive_text()
            if raw.lower().strip() in {"ping", "heartbeat"}:
                await websocket.send_json({"type": "pong", "timestamp": datetime.now(timezone.utc).isoformat()})
            else:
                await manager.broadcast(
                    {
                        "channel": "global",
                        "type": "user_event",
                        "payload": raw,
                        "timestamp": datetime.now(timezone.utc).isoformat(),
                    }
                )
    except WebSocketDisconnect:
        await manager.disconnect(websocket)
    except Exception:
        logger.exception("Unhandled websocket stream error")
        await manager.disconnect(websocket)


@app.get("/")
async def root():
    return {"app": settings.app_name, "status": "running"}

```

### frontend/src/App.tsx

```typescript
import {
  useEffect,
  useMemo,
  useRef,
  useState,
  type ChangeEvent,
  type PointerEvent as ReactPointerEvent,
} from "react";
import ResearchRail from "./components/ResearchRail";
import ResearchPanel from "./components/ResearchPanel";
import SimulationPanel from "./components/SimulationPanel";
import TrackerPrefsRail from "./components/TrackerPrefsRail";
import TrackerPanel from "./components/TrackerPanel";
import {
  getAuthSession,
  getFavoriteStocks,
  getUserProfile,
  getWatchlist,
  isAuthConfigured,
  setFavoriteStocks,
  setWatchlist as setTrackerWatchlist,
  signInWithPassword,
  signOut,
  signUpWithPassword,
  subscribeAuthSession,
  updateUserPreferences,
} from "./lib/api";
import { useSocket } from "./hooks/useSocket";
import brandLogo from "./images/TickerMaster.png";
import moonIcon from "./images/moon.png";
import sunIcon from "./images/sun.png";

type Tab = "research" | "simulation" | "tracker";
type Theme = "light" | "dark";
type AuthMode = "sign_in" | "sign_up";
type UserProfile = {
  display_name?: string;
  avatar_url?: string;
  email?: string;
  require_username_setup?: boolean;
  username_locked?: boolean;
};
const LANDING_VIDEO_SRC = "/videoplayback (1).mp4";
const METADATA_LOGO_SRC = "/logo.png";

function tabFromQuery(): Tab {
  const params = new URLSearchParams(window.location.search);
  const value = params.get("tab");
  if (value === "research" || value === "simulation" || value === "tracker")
    return value;
  return "simulation";
}

function tickerFromQuery() {
  return "";
}

function normalizeWatchlist(tickers: string[]) {
  const cleaned = tickers
    .map((symbol) => symbol.trim().toUpperCase())
    .filter(Boolean);
  return Array.from(new Set(cleaned));
}

function shouldRequireUsername(
  profile: UserProfile | null,
  authEmail?: string,
) {
  if (!profile) return true;
  const displayName = (profile.display_name ?? "").trim();
  const email = (profile.email ?? authEmail ?? "").trim();
  if (!displayName) return true;
  return Boolean(email && displayName.toLowerCase() === email.toLowerCase());
}

async function cropAvatarToDataUrl(
  source: string,
  zoom: number,
  offsetX: number,
  offsetY: number,
): Promise<string> {
  const image = new Image();
  image.src = source;
  await image.decode();

  const size = 320;
  const canvas = document.createElement("canvas");
  canvas.width = size;
  canvas.height = size;
  const context = canvas.getContext("2d");
  if (!context) throw new Error("Unable to prepare avatar crop.");

  context.clearRect(0, 0, size, size);
  const baseScale = Math.max(size / image.width, size / image.height);
  const drawScale = baseScale * zoom;
  const drawWidth = image.width * drawScale;
  const drawHeight = image.height * drawScale;
  const centerX = (size - drawWidth) / 2 + offsetX;
  const centerY = (size - drawHeight) / 2 + offsetY;

  context.drawImage(image, centerX, centerY, drawWidth, drawHeight);
  return canvas.toDataURL("image/jpeg", 0.9);
}

function clampAvatarOffset(value: number) {
  return Math.max(-180, Math.min(180, value));
}

export default function App() {
  const [tab, setTab] = useState<Tab>(tabFromQuery());
  const [ticker, setTicker] = useState("");
  const [watchlist, setWatchlist] = useState<string[]>([]);
  const [favoriteStocks, setFavoriteStocksState] = useState<string[]>([]);
  const [theme, setTheme] = useState<Theme>(() => {
    const stored = window.localStorage.getItem("tickermaster-theme");
    if (stored === "light" || stored === "dark") return stored;
    return window.matchMedia("(prefers-color-scheme: dark)").matches
      ? "dark"
      : "light";
  });

  const {
    connected,
    events,
    lastSimulationTick,
    lastSimulationLifecycle,
    lastTrackerSnapshot,
  } = useSocket();
  const [authSession, setAuthSessionState] = useState(getAuthSession());
  const [authMode, setAuthMode] = useState<AuthMode>("sign_in");
  const [authEmail, setAuthEmail] = useState("");
  const [authPassword, setAuthPassword] = useState("");
  const [authLoading, setAuthLoading] = useState(false);
  const [authError, setAuthError] = useState("");
  const [authModalOpen, setAuthModalOpen] = useState(false);
  const [workspaceLoading, setWorkspaceLoading] = useState(false);
  const [awaitingEmailConfirm, setAwaitingEmailConfirm] = useState(false);
  const [userProfile, setUserProfile] = useState<UserProfile | null>(null);
  const [usernameSetupOpen, setUsernameSetupOpen] = useState(false);
  const [usernameInput, setUsernameInput] = useState("");
  const [profileError, setProfileError] = useState("");
  const [profileModalOpen, setProfileModalOpen] = useState(false);
  const [profileSaving, setProfileSaving] = useState(false);
  const [avatarCropSource, setAvatarCropSource] = useState<string | null>(null);
  const [avatarZoom, setAvatarZoom] = useState(1);
  const [avatarOffsetX, setAvatarOffsetX] = useState(0);
  const [avatarOffsetY, setAvatarOffsetY] = useState(0);
  const [avatarDragging, setAvatarDragging] = useState(false);
  const avatarDragRef = useRef<{
    pointerId: number;
    startX: number;
    startY: number;
    originX: number;
    originY: number;
  } | null>(null);

  useEffect(() => {
    const unsubscribe = subscribeAuthSession((session) => {
      setAuthSessionState(session);
    });
    return () => unsubscribe();
  }, []);

  useEffect(() => {
    let active = true;
    const hydrateWorkspace = async () => {
      if (authSession?.user?.id) {
        setWorkspaceLoading(true);
      }
      try {
        const [serverWatchlist, favoriteSymbols, profilePayload] =
          await Promise.all([
            getWatchlist().catch(() => []),
            authSession?.user?.id
              ? getFavoriteStocks().catch(() => [])
              : Promise.resolve([]),
            authSession?.user?.id
              ? getUserProfile().catch(() => ({
                  user_id: null,
                  profile: null,
                  require_username_setup: false,
                
[truncated — 33976 more characters]
```

### modal_inference.py

```python
"""
Convenience Modal entrypoint.

The main implementation lives in `simulation/modal_inference.py` to keep Modal-specific code isolated.
Deploy either file with:
  modal deploy simulation/modal_inference.py
or
  modal deploy modal_inference.py
"""

from simulation.modal_inference import app, agent_inference, persona_param_inference  # noqa: F401


```

### frontend/vite.config.ts

```typescript
import { defineConfig } from "vite";
import react from "@vitejs/plugin-react";

export default defineConfig({
  plugins: [react()],
  server: {
    port: 5173,
    host: "0.0.0.0"
  }
});

```

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