Files

16 KiB
Raw Permalink Blame History

Invest Copilot — Phase 1: Design-Driven Foundation

Goal: Establish the project foundation, generate UI directions via Open Design, set up the development environment, and define all API contracts.

Duration: Weeks 1-3

Deliverables: Clickable UI prototype, API specification, deployed dev environment, CI/CD pipeline

Week 1: Visual Identity & UI Prototypes

Day 1-2: Design Direction via Open Design

Task 1: Generate 3 visual directions

Use Open Design (or equivalent) with these prompts:

Direction A: "Dark Finance Professional"
- Deep navy background (#0a1628) with electric blue accents (#3b82f6)
- Data-dense layout (like Bloomberg but modern)
- Monospace numbers (JetBrains Mono), sans-serif labels (Inter)
- Green/red for gains/losses with +/− icons (not color alone)
- Reference: Bloomberg meets Linear

Direction B: "Minimal Intelligence"
- Near-black background (#0d0d0d) with subtle blue tints
- Maximum white space between data elements
- Large typography for key metrics
- Charts as the hero element
- Reference: Apple Health meets Koyfin

Direction C: "Modern Terminal"
- Dark gray background (#1a1a1a) with accent colors
- Terminal-inspired typography and borders
- Monospace throughout
- Command-like navigation
- Reference: TradingView dark mode meets VS Code

Task 2: Select direction and create design tokens

Based on the generated directions, create tailwind.config.ts with:

  • Custom color palette (OKLch-based)
  • Font families (Inter + JetBrains Mono)
  • Spacing scale
  • Breakpoint definitions
  • Animation definitions

Task 3: Generate key screen prototypes

Generate HTML/CSS prototypes for:

  1. Dashboard (watchlist overview)
  2. Stock profile (search → profile)
  3. Sector rotation dashboard

Day 3-5: Component Library

Generate reusable components:

  • StockCard, PriceChart placeholder, SEC Filing Card
  • Insider Trade Row, Strategy Rule Builder, Alert Badge
  • Sector Heatmap, Watchlist Table, Navigation

Week 2: Development Environment

Task 4: Initialize Next.js 15 project

npx create-next-app@latest invest-copilot \
  --typescript \
  --tailwind \
  --app \
  --src-dir \
  --import-alias "@/*" \
  --turbopack \
  --use-npm

Task 5: Install core dependencies

# State management
npm install zustand

# UI primitives
npm install @radix-ui/react-dialog @radix-ui/react-dropdown-menu
npm install @radix-ui/react-tabs @radix-ui/react-tooltip
npm install @radix-ui/react-select @radix-ui/react-switch

# Charts
npm install lightweight-charts

# Data fetching
npm install @tanstack/react-query

# Forms
npm install react-hook-form zod @hookform/resolvers

# PWA
npm install next-pwa

# Icons
npm install lucide-react

# Date handling
npm install date-fns

# Development
npm install -D @types/node @types/react @types/react-dom
npm install -D eslint @typescript-eslint/parser @typescript-eslint/eslint-plugin
npm install -D prettier prettier-plugin-tailwindcss

Task 6: Set up Docker Compose for infrastructure

Create docker-compose.dev.yml:

services:
  postgres:
    image: timescale/timescaledb:latest-pg16
    environment:
      POSTGRES_DB: invest_copilot
      POSTGRES_USER: dev
      POSTGRES_PASSWORD: dev_password
    ports:
      - "5432:5432"
    volumes:
      - pgdata:/var/lib/postgresql/data

  redis:
    image: redis:7-alpine
    ports:
      - "6379:6379"
    volumes:
      - redisdata:/data

  minio:
    image: minio/minio
    command: server /data --console-address ":9001"
    environment:
      MINIO_ROOT_USER: dev
      MINIO_ROOT_PASSWORD: dev_password
    ports:
      - "9000:9000"
      - "9001:9001"
    volumes:
      - miniodata:/data

volumes:
  pgdata:
  redisdata:
  miniodata:

Task 7: Set up CI/CD with GitHub Actions

Create .github/workflows/ci.yml:

name: CI

on:
  push:
    branches: [main]
  pull_request:
    branches: [main]

jobs:
  lint-and-test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: 22
          cache: npm
      - run: npm ci
      - run: npm run lint
      - run: npm run build

Week 3: API Contracts & Data Pipeline

Task 8: Define OpenAPI/Swagger specification

Create docs/api/openapi.yaml with:

openapi: 3.1.0
info:
  title: Invest Copilot API
  version: 0.1.0
  description: AI-native investment research and portfolio copilot

paths:
  # Stock Search & Profile
  /api/v1/search:
    get:
      summary: Search stocks by ticker or name
      parameters:
        - name: q
          in: query
          required: true
          schema: { type: string }
        - name: limit
          in: query
          schema: { type: integer, default: 10, maximum: 50 }
      responses:
        200:
          description: Search results

  /api/v1/stocks/{ticker}:
    get:
      summary: Get full stock profile
      parameters:
        - name: ticker
          in: path
          required: true
          schema: { type: string }
      responses:
        200:
          description: Stock profile with all data

  /api/v1/stocks/{ticker}/peers:
    get:
      summary: Get peer group and relative performance
      parameters:
        - name: ticker
          in: path
          required: true
          schema: { type: string }
      responses:
        200:
          description: Peer group with relative strength

  /api/v1/stocks/{ticker}/price/history:
    get:
      summary: Get historical prices
      parameters:
        - name: ticker
          in: path
          required: true
          schema: { type: string }
        - name: interval
          in: query
          schema: { type: string, enum: [1m, 5m, 15m, 30m, 1h, 1d, 1w, 1M] }
        - name: start
          in: query
          schema: { type: string, format: date-time }
        - name: end
          in: query
          schema: { type: string, format: date-time }
      responses:
        200:
          description: Price history

  /api/v1/stocks/{ticker}/sec-filings:
    get:
      summary: Get SEC filings
      parameters:
        - name: ticker
          in: path
          required: true
          schema: { type: string }
        - name: form_type
          in: query
          schema: { type: string, enum: [10-K, 10-Q, 8-K, 4, 13F, 13D, 13G] }
        - name: limit
          in: query
          schema: { type: integer, default: 20 }
      responses:
        200:
          description: SEC filings

  /api/v1/stocks/{ticker}/insider-trades:
    get:
      summary: Get insider trades
      parameters:
        - name: ticker
          in: path
          required: true
          schema: { type: string }
        - name: limit
          in: query
          schema: { type: integer, default: 20 }
      responses:
        200:
          description: Insider trades

  /api/v1/stocks/{ticker}/sentiment:
    get:
      summary: Get sentiment signals
      parameters:
        - name: ticker
          in: path
          required: true
          schema: { type: string }
      responses:
        200:
          description: Sentiment signals from all sources

  # Watchlists
  /api/v1/watchlists:
    get:
      summary: Get user's watchlists
      responses:
        200:
          description: List of watchlists
    post:
      summary: Create a new watchlist
      requestBody:
        content:
          application/json:
            schema:
              type: object
              required: [name]
              properties:
                name: { type: string }
                description: { type: string }
      responses:
        201:
          description: Watchlist created

  /api/v1/watchlists/{id}:
    get:
      summary: Get watchlist with items and prices
    put:
      summary: Update watchlist name
    delete:
      summary: Delete watchlist

  /api/v1/watchlists/{id}/items:
    get:
      summary: Get watchlist items with live prices
    post:
      summary: Add ticker to watchlist
    delete:
      summary: Remove ticker from watchlist

  # Strategies
  /api/v1/strategies:
    get:
      summary: Get user's strategies
    post:
      summary: Create a new strategy
  /api/v1/strategies/{id}:
    get:
      summary: Get strategy details
    put:
      summary: Update strategy
    delete:
      summary: Delete strategy

  # Screener
  /api/v1/screeners:
    get:
      summary: Get user's screeners
    post:
      summary: Create a new screener
  /api/v1/screeners/{id}/run:
    post:
      summary: Run screener and get results
  /api/v1/screeners/{id}/results:
    get:
      summary: Get screener results

  # Sector Rotation
  /api/v1/sectors/rotation:
    get:
      summary: Get current sector rotation analysis
    post:
      summary: Trigger manual rotation scan
  /api/v1/sectors/rotation/history:
    get:
      summary: Get historical rotation data

  # Alerts
  /api/v1/alerts:
    get:
      summary: Get user's alerts
    delete:
      summary: Clear resolved alerts
  /api/v1/alerts/{id}/resolve:
    post:
      summary: Mark alert as resolved
  /api/v1/alerts/{id}/dismiss:
    post:
      summary: Dismiss alert

  # Real-time (SSE)
  /api/v1/stream/alerts:
    get:
      summary: Server-Sent Events stream for real-time alerts
      responses:
        200:
          description: SSE stream
          content:
            text/event-stream:
              schema:
                type: string

Task 9: Set up Python data pipeline

mkdir -p src/data-pipeline
cd src/data-pipeline
pip install yfinance finnhub-python requests beautifulsoup4

Create src/data-pipeline/ingest_prices.py:

"""Price data ingestion using yfinance (free tier)."""
import yfinance as yf
import psycopg2
from datetime import datetime, timedelta

def fetch_prices(ticker: str, period: str = "5y") -> list[dict]:
    """Fetch OHLCV data using yfinance."""
    tk = yf.Ticker(ticker)
    df = tk.history(period=period, auto_adjust=True)
    records = []
    for date, row in df.iterrows():
        records.append({
            "ticker": ticker,
            "date": date,
            "open": row["Open"],
            "high": row["High"],
            "low": row["Low"],
            "close": row["Close"],
            "volume": int(row["Volume"]),
            "adjusted_close": row["Close"],  # adjusted = close for now
        })
    return records

def save_prices(records: list[dict]) -> None:
    """Save prices to PostgreSQL/TimescaleDB."""
    conn = psycopg2.connect(
        host="localhost",
        database="invest_copilot",
        user="dev",
        password="dev_password"
    )
    cur = conn.cursor()
    for r in records:
        cur.execute("""
            INSERT INTO prices (ticker, date, open, high, low, close, volume, adjusted_close)
            VALUES (%s, %s, %s, %s, %s, %s, %s, %s)
            ON CONFLICT (ticker, date) DO UPDATE SET
                open = EXCLUDED.open,
                high = EXCLUDED.high,
                low = EXCLUDED.low,
                close = EXCLUDED.close,
                volume = EXCLUDED.volume,
                adjusted_close = EXCLUDED.adjusted_close
        """, (
            r["ticker"], r["date"], r["open"], r["high"], r["low"],
            r["close"], r["volume"], r["adjusted_close"]
        ))
    conn.commit()
    cur.close()
    conn.close()

if __name__ == "__main__":
    import sys
    tickers = sys.argv[1:] if len(sys.argv) > 1 else ["AAPL", "MSFT", "GOOGL", "AMZN", "META"]
    for ticker in tickers:
        print(f"Ingesting {ticker}...")
        records = fetch_prices(ticker)
        save_prices(records)
        print(f"  Saved {len(records)} records")

Task 10: Set up SEC EDGAR ingestion

Create src/data-pipeline/ingest_sec.py:

"""SEC EDGAR filing ingestion."""
import requests
import json
from datetime import datetime

EDGAR_BASE = "https://data.sec.gov"

def get_cik(ticker: str) -> str | None:
    """Get CIK number for a ticker."""
    url = f"{EDGAR_BASE}/cgi-bin/browse-edgar?action=getcompany&CIK=TICKER&type=&date=coverage&date_range=365&count=1"
    url = url.replace("TICKER", ticker.upper())
    resp = requests.get(url, timeout=10)
    # Parse HTML to extract CIK
    import re
    match = re.search(r'CIK=(0{0,10}\d{6,10})', resp.text)
    return match.group(1) if match else None

def get_filings(cik: str, form_type: str | None = None, limit: int = 20) -> list[dict]:
    """Get filings for a CIK."""
    url = f"{EDGAR_BASE}/submissions/CIK{int(cik):010d}.json"
    resp = requests.get(url, timeout=10)
    data = resp.json()
    
    filings = []
    for i in range(min(limit * 20, len(data["filings"]["recent"]["form"]))):
        form = data["filings"]["recent"]["form"][i]
        if form_type and form != form_type:
            continue
        filings.append({
            "cik": cik,
            "form_type": form,
            "filing_date": data["filings"]["recent"]["filingDate"][i],
            "report_date": data["filings"]["recent"]["reportDate"][i],
            "accession_number": data["filings"]["recent"]["accessionNumber"][i],
            "primary_document": data["filings"]["recent"]["primaryDocument"][i],
            "url": f"{EDGAR_BASE}/Archives/edgar/data/{int(cik):010d}/"
                   f"{data['filings']['recent']['accessionNumber'][i].replace('-', '')}/"
                   f"{data['filings']['recent']['primaryDocument'][i]}",
        })
        if len([f for f in filings if "form_type" in f]) >= limit:
            break
    return filings

if __name__ == "__main__":
    # Test: Get filings for AAPL
    cik = get_cik("AAPL")
    print(f"AAPL CIK: {cik}")
    if cik:
        filings = get_filings(cik, limit=5)
        print(json.dumps(filings, indent=2))

Task 11: Create project structure

invest-copilot/
├── docs/                          # Documentation (we already have this)
│   ├── 00-vision.md
│   ├── 01-architecture.md
│   ├── 02-data-model.md
│   ├── 03-domain-model.md
│   ├── 04-open-design-integration.md
│   ├── 05-phase1-plan.md
│   └── api/
│       └── openapi.yaml
├── src/
│   ├── frontend/                  # Next.js app
│   │   ├── app/
│   │   ├── components/
│   │   ├── lib/
│   │   ├── styles/
│   │   └── store/                 # Zustand stores
│   ├── backend/                   # Node.js API (future)
│   │   ├── src/
│   │   └── tests/
│   ├── data-pipeline/             # Python ingestion scripts
│   │   ├── ingest_prices.py
│   │   ├── ingest_sec.py
│   │   ├── ingest_sentiment.py
│   │   └── sector_rotation.py
│   └── shared/                    # Shared types (TypeScript)
│       └── types.ts
├── docker-compose.dev.yml
├── docker-compose.prod.yml        # (future)
├── .github/
│   └── workflows/
│       ├── ci.yml
│       └── deploy.yml             # (future)
├── prisma/                        # (future, for DDD repo implementations)
├── .eslintrc.json
├── .prettierrc
├── next.config.mjs
├── tailwind.config.ts
├── tsconfig.json
├── package.json
└── README.md

Acceptance Criteria for Phase 1

  • 3 visual directions generated via Open Design (or manual)
  • Selected design direction converted to TailwindCSS design tokens
  • Next.js 15 project running with TypeScript, TailwindCSS, Radix UI
  • Docker Compose dev environment with PostgreSQL+TimescaleDB, Redis, MinIO
  • GitHub Actions CI pipeline (lint + build)
  • Complete OpenAPI specification for all endpoints
  • Python data pipeline ingesting prices for at least 10 tickers
  • SEC EDGAR ingestion working for at least 5 companies
  • Dashboard UI mockup working with static data
  • Stock search UI mockup working with static data
  • Stock profile UI mockup working with static data
  • README.md with project overview, setup instructions, and architecture

Pre-Phase 2 Checklist

Before moving to Phase 2 (Backend Foundation):

  • All Phase 1 acceptance criteria met
  • Design direction finalized and approved
  • API contracts reviewed and agreed upon
  • Data pipeline tested and verified
  • Dev environment stable and documented

Resource Estimation

Task Effort
Visual directions + design tokens 2 days
Component library 3 days
Next.js project setup 0.5 days
Docker Compose infrastructure 0.5 days
CI/CD pipeline 0.5 days
OpenAPI specification 1 day
Price data pipeline 2 days
SEC EDGAR pipeline 2 days
UI mockups (3 screens) 3 days
README + documentation 1 day
Total ~16 days (3 weeks)