Files

613 lines
16 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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
```bash
npx create-next-app@latest invest-copilot \
--typescript \
--tailwind \
--app \
--src-dir \
--import-alias "@/*" \
--turbopack \
--use-npm
```
### Task 5: Install core dependencies
```bash
# 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`:
```yaml
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`:
```yaml
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:
```yaml
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
```bash
mkdir -p src/data-pipeline
cd src/data-pipeline
pip install yfinance finnhub-python requests beautifulsoup4
```
Create `src/data-pipeline/ingest_prices.py`:
```python
"""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`:
```python
"""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)** |