Imported from hyungwookchoi/quatro (
AGENTS.md). Install upstream withnpx skills add hyungwookchoi/quatro. Copyright stays with the author.
AGENTS.md
This file provides guidance to Codex (Codex.ai/code) when working with code in this repository.
Project Overview
Quatro is a Next.js 16 stock screener web application that monitors stock watchlists and sends real-time alerts via Telegram. Users authenticate with Telegram, configure custom alert conditions (RSI thresholds, YTD performance, drop from high), and receive notifications when stocks meet their criteria.
Package Manager: pnpm (v10.23.0+)
Common Commands
# Development
pnpm dev # Start development server on localhost:3000
pnpm build # Build production bundle
pnpm start # Start production server
pnpm lint # Run ESLint
# Database (Drizzle ORM with PostgreSQL)
pnpm db:pull # Pull schema from database
pnpm db:push # Push schema changes to database
pnpm db:studio # Open Drizzle Studio GUI
Architecture
Tech Stack
- Framework: Next.js 16 (App Router, React 19)
- Database: PostgreSQL with Drizzle ORM
- Authentication: NextAuth.js with Telegram OAuth
- Financial Data: yahoo-finance2 API
- Notifications: Telegram Bot API
- UI: Tailwind CSS, Radix UI components, shadcn/ui
- State: Zustand (store directory is currently empty)
Project Structure
app/
├── api/ # Next.js API routes
│ ├── auth/[...nextauth]/ # NextAuth handlers
│ ├── watchlist/ # CRUD for user watchlists
│ ├── stock/[ticker]/ # Individual stock data
│ ├── stocks/ # Bulk stock operations
│ │ ├── search/ # Stock search
│ │ └── prices/ # Batch price updates
│ ├── market-indices/ # Market index data
│ ├── screen/ # Cron-triggered screener job
│ └── telegram/ # Telegram bot webhooks & auth
├── actions/ # Server actions
├── auth-provider.tsx # NextAuth session provider
├── layout.tsx # Root layout with providers
└── page.tsx # Home dashboard
components/
├── ui/ # shadcn/ui components (Radix-based)
├── dashboard/ # Dashboard-specific components
│ ├── WatchlistTable.tsx # Main watchlist display
│ ├── StockSearch.tsx # Stock search & add
│ ├── AlertSettingsDialog.tsx # Alert condition editor
│ ├── DashboardCards.tsx # Stats cards
│ ├── MarketChart.tsx # Stock price charts
│ └── MarketIndices.tsx # Market index display
├── auth/
│ └── TelegramLoginButton.tsx # Telegram OAuth widget
└── layout/
├── AppLayout.tsx # Main layout with sidebar
├── Sidebar.tsx # Navigation sidebar
└── MobileHeader.tsx # Mobile header
db/
└── schema.ts # Drizzle schema definitions
lib/
├── db.ts # Database connection
├── finance.ts # Yahoo Finance integration
├── telegram.ts # Telegram bot API
├── alert-schema.ts # Alert condition types & validation
└── utils.ts # Utility functions
hooks/
├── use-interval.ts # Polling interval hook
└── use-mobile.ts # Mobile detection hook
Key Architecture Patterns
Authentication Flow
- User clicks Telegram login button (uses official Telegram widget script loaded in root layout)
- Telegram widget redirects to NextAuth credential provider
- NextAuth creates/updates subscriber in database and establishes JWT session
- Session includes custom
idfield from database subscriber record
Data Flow
-
Dashboard (app/page.tsx):
- Loads user's watchlist from
/api/watchliston mount - Fetches initial stock data for each watchlist item
- Polls
/api/stocks/pricesevery 1 second for price updates - Uses React state to manage watchlist data
- Loads user's watchlist from
-
Screener Job (app/api/screen/route.ts):
- Protected by
CRON_SECRETauthorization header (intended for Vercel Cron) - Fetches all watchlist items with
alertEnabled: true - Groups by ticker to minimize API calls
- Evaluates custom alert conditions per user
- Sends Telegram messages via Bot API when conditions trigger
- Protected by
-
Stock Data Pipeline:
- yahoo-finance2 provides quotes, historical data, and search
- lib/finance.ts calculates derived metrics:
- RSI (14-period) from historical closing prices
- YTD return from Jan 1st opening price
- Drop from 52-week high
- Historical data fetch uses
yahooFinance.chart()(switched from deprecated historical API)
Database Schema (db/schema.ts)
subscribers - User accounts from Telegram
chatId(unique): Telegram chat IDisWebUser: Distinguishes web users from bot-only usersfirstName,username,photoUrl: Telegram profile data
watchlist - User stock watchlists
subscriberId: Foreign key to subscribersticker,name,exchange: Stock identificationalertEnabled: Toggle for screener monitoringalertConditions: JSON string of custom alert thresholds
authSessions - Telegram OAuth session tokens
token: UUID for auth flowstatus: pending | verified | expired- Used by Telegram bot for authenticating web users
Alert System
- Alert conditions stored as JSON in
watchlist.alertConditions - Schema defined in lib/alert-schema.ts with Zod validation
- Default conditions: RSI 30-70, YTD below -10%, drop from high below -15%
- Users customize via
AlertSettingsDialogcomponent - Conditions evaluated in screener job using
checkAlertCondition()
API Route Patterns
All API routes follow REST conventions:
- GET
/api/watchlist- List user's watchlist - POST
/api/watchlist- Add stock to watchlist - PATCH
/api/watchlist- Update alert settings - DELETE
/api/watchlist?ticker=AAPL- Remove stock
Protected routes verify session with:
import { authOptions } from '@/app/api/auth/[...nextauth]/route';
import { getServerSession } from 'next-auth';
const session = await getServerSession(authOptions);
if (!session?.user?.id) {
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
}
Environment Variables
Required in .env.local:
DATABASE_URL= # PostgreSQL connection string
NEXTAUTH_SECRET= # NextAuth encryption key
NEXTAUTH_URL= # Application URL
TELEGRAM_BOT_TOKEN= # Bot API token for sending messages
CRON_SECRET= # Authorization for /api/screen endpoint
Development Workflow
Adding New Stock Metrics
- Update
StockDatainterface in lib/finance.ts - Calculate metric in
getStockData()function - Add to alert conditions schema in lib/alert-schema.ts if needed
- Update
WatchlistTablecomponent to display metric - Update screener job to evaluate new alert conditions
Modifying Database Schema
- Edit db/schema.ts
- Run
pnpm db:pushto apply changes - Update TypeScript types (Drizzle auto-generates these)
- Migrations stored in drizzle/ directory
Adding UI Components
- Use existing shadcn/ui components from components/ui/
- Follow Radix UI patterns for accessibility
- Use Tailwind utility classes (no CSS modules)
- Mobile-responsive by default (check hooks/use-mobile.ts for breakpoint logic)
Important Implementation Details
- Real-time Updates: Dashboard polls prices every 1 second using
use-intervalhook (not WebSocket) - Telegram Bot: Messages sent via HTTP POST to Bot API (no webhooks for sending)
- Session Storage: JWT-based sessions (not database sessions)
- Price Data: All financial data from Yahoo Finance (no API key required)
- Watchlist Limit: Hard-coded 20 stock maximum per user
- Historical Data: Fetches 60 days or YTD (whichever is earlier) for RSI/YTD calculations
Testing & Debugging
- Check browser console for client-side errors
- Check server logs (
pnpm devoutput) for API failures - Use
pnpm db:studioto inspect database directly - Telegram message failures logged with ❌ emoji prefix
- Yahoo Finance API errors return null - always check return values
Deployment
- Configured for Vercel deployment
- Vercel Analytics integrated in root layout
- Set up Vercel Cron Jobs to trigger
/api/screenendpoint - Ensure all environment variables set in Vercel dashboard