Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion .claude/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
"enabledPlugins": {
"frontend-design@claude-plugins-official": true,
"context7@claude-plugins-official": true,
"playwright@claude-plugins-official": true
"playwright@claude-plugins-official": true,
"independent-reviewer@emmanuel-tools": true
}
}
2 changes: 1 addition & 1 deletion CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,6 @@

All project documentation is in the `planning` directory.

The key document is PLAN.md included in full below; the market data component has been completed and is summarized in the file `planning/MARKET_DATA_SUMMARY.md` with more details in the `planning/archive` folder. Consult these docs only when required. The remainder of the platform is still to be developed.
The key document is PLAN.md included in full below; the market data component has been completed in `backend/app/market/` and is specified in `planning/MARKET_INTERFACE.md`, `planning/MARKET_SIMULATOR.md` and `planning/MASSIVE_API.md`. Consult these docs only when required. The remainder of the platform is still to be developed.

@planning/PLAN.md
61 changes: 21 additions & 40 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,61 +1,42 @@
# FinAlly — AI Trading Workstation

A visually stunning AI-powered trading workstation that streams live market data, simulates portfolio trading, and integrates an LLM chat assistant that can analyze positions and execute trades via natural language.
An AI-powered trading workstation: live market data, a simulated $10k portfolio, and an LLM chat assistant that can analyze positions and execute trades. Built by coding agents as the capstone of an agentic AI coding course.

Built entirely by coding agents as a capstone project for an agentic AI coding course.
## Status

## Features
- **Done:** market data subsystem (GBM simulator, Massive API client, price cache, SSE stream) in `backend/app/market/`
- **To do:** portfolio, watchlist and chat APIs, database, Next.js frontend, Docker packaging

- **Live price streaming** via SSE with green/red flash animations
- **Simulated portfolio** — $10k virtual cash, market orders, instant fills
- **Portfolio visualizations** — heatmap (treemap), P&L chart, positions table
- **AI chat assistant** — analyzes holdings, suggests and auto-executes trades
- **Watchlist management** — track tickers manually or via AI
- **Dark terminal aesthetic** — Bloomberg-inspired, data-dense layout
The full specification is in [planning/PLAN.md](planning/PLAN.md).

## Architecture

Single Docker container serving everything on port 8000:
One Docker container on port 8000:

- **Frontend**: Next.js (static export) with TypeScript and Tailwind CSS
- **Backend**: FastAPI (Python/uv) with SSE streaming
- **Database**: SQLite with lazy initialization
- **AI**: LiteLLM → OpenRouter (Cerebras inference) with structured outputs
- **Market data**: Built-in GBM simulator (default) or Massive API (optional)
- **Frontend:** Next.js static export (TypeScript, Tailwind)
- **Backend:** FastAPI managed with `uv`, SSE for live prices
- **Database:** SQLite, lazily initialized
- **AI:** LiteLLM → OpenRouter (Cerebras) with structured outputs
- **Market data:** built-in simulator by default, Massive API if a key is set

## Quick Start
## Development

```bash
# Clone and configure
cp .env.example .env
# Add your OPENROUTER_API_KEY to .env

# Run with Docker
docker build -t finally .
docker run -v finally-data:/app/db -p 8000:8000 --env-file .env finally

# Open http://localhost:8000
cd backend
uv sync --extra dev
uv run pytest # run tests
uv run uvicorn app.main:app --reload # serve on :8000; SSE at /api/stream/prices
```

## Environment Variables

Set in `.env` at the project root:

| Variable | Required | Description |
|---|---|---|
| `OPENROUTER_API_KEY` | Yes | OpenRouter API key for AI chat |
| `MASSIVE_API_KEY` | No | Massive (Polygon.io) key for real market data; omit to use simulator |
| `LLM_MOCK` | No | Set `true` for deterministic mock LLM responses (testing) |

## Project Structure

```
finally/
├── frontend/ # Next.js static export
├── backend/ # FastAPI uv project
├── planning/ # Project documentation and agent contracts
├── test/ # Playwright E2E tests
├── db/ # SQLite volume mount (runtime)
└── scripts/ # Start/stop helpers
```
| `OPENROUTER_API_KEY` | Yes | OpenRouter key for AI chat |
| `MASSIVE_API_KEY` | No | Real market data; omit to use the simulator |
| `LLM_MOCK` | No | `true` for deterministic mock LLM responses |

## License

Expand Down
59 changes: 0 additions & 59 deletions backend/CLAUDE.md

This file was deleted.

66 changes: 21 additions & 45 deletions backend/README.md
Original file line number Diff line number Diff line change
@@ -1,55 +1,31 @@
# FinAlly Backend

FastAPI backend for the FinAlly AI Trading Workstation.

## Structure

- `app/` - Application code
- `market/` - Market data subsystem
- `models.py` - PriceUpdate dataclass
- `cache.py` - Thread-safe price cache
- `interface.py` - MarketDataSource abstract interface
- `simulator.py` - GBM-based market simulator
- `massive_client.py` - Massive/Polygon.io API client
- `factory.py` - Data source factory
- `stream.py` - SSE streaming endpoint
- `seed_prices.py` - Default ticker prices and parameters

- `tests/` - Unit and integration tests
- `market/` - Market data tests

## Running Tests
FastAPI app managed with `uv`. Currently contains the market data subsystem.

```bash
# Install dependencies
uv sync --dev

# Run all tests
uv sync --extra dev
uv run pytest

# Run with coverage
uv run pytest --cov=app --cov-report=html

# Run specific test file
uv run pytest tests/market/test_simulator.py

# Run with verbose output
uv run pytest -v
uv run ruff check . && uv run ruff format --check .
uv run uvicorn app.main:app --reload
```

## Environment Variables

- `MASSIVE_API_KEY` - Optional. If set, use real market data from Massive API. If not set, use the built-in simulator.
## Layout

## Development

```bash
# Install dependencies
uv sync --dev
```
app/
├── main.py # FastAPI app: lifespan starts the market source, /api/health
└── market/
├── models.py # PriceUpdate
├── cache.py # PriceCache (thread-safe, version counter)
├── interface.py # MarketDataSource ABC
├── seed_prices.py # simulator seed prices, GBM params, sectors
├── simulator.py # GBMSimulator + SimulatorDataSource
├── massive_client.py# MassiveDataSource (snapshot, free-plan EOD fallback)
├── factory.py # picks the source from MASSIVE_API_KEY
└── stream.py # GET /api/stream/prices (SSE)
```

# Run linter
uv run ruff check .
Design: `planning/MARKET_INTERFACE.md`, `planning/MARKET_SIMULATOR.md`, `planning/MASSIVE_API.md`.

# Format code
uv run ruff format .
```
`app/main.py` tracks the ten default tickers until the database layer provides
watchlist ∪ open positions.
2 changes: 1 addition & 1 deletion backend/app/__init__.py
Original file line number Diff line number Diff line change
@@ -1 +1 @@
"""FinAlly backend application."""
"""FinAlly backend."""
29 changes: 29 additions & 0 deletions backend/app/main.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
"""FastAPI application: market data wiring and health check."""

from contextlib import asynccontextmanager

from fastapi import FastAPI

from app.market import PriceCache, create_market_data_source, create_stream_router

# Replaced by watchlist ∪ positions from SQLite once the database layer exists.
DEFAULT_TICKERS = ["AAPL", "GOOGL", "MSFT", "AMZN", "TSLA", "NVDA", "META", "JPM", "V", "NFLX"]

price_cache = PriceCache()
market_source = create_market_data_source(price_cache)


@asynccontextmanager
async def lifespan(app: FastAPI):
await market_source.start(DEFAULT_TICKERS)
yield
await market_source.stop()


app = FastAPI(title="FinAlly", lifespan=lifespan)
app.include_router(create_stream_router(price_cache))


@app.get("/api/health")
async def health() -> dict:
return {"status": "ok", "tickers": market_source.get_tickers()}
14 changes: 3 additions & 11 deletions backend/app/market/__init__.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,4 @@
"""Market data subsystem for FinAlly.

Public API:
PriceUpdate - Immutable price snapshot dataclass
PriceCache - Thread-safe in-memory price store
MarketDataSource - Abstract interface for data providers
create_market_data_source - Factory that selects simulator or Massive
create_stream_router - FastAPI router factory for SSE endpoint
"""
"""Market data: one interface, a GBM simulator and a Massive REST poller, feeding a shared PriceCache."""

from .cache import PriceCache
from .factory import create_market_data_source
Expand All @@ -15,9 +7,9 @@
from .stream import create_stream_router

__all__ = [
"PriceUpdate",
"PriceCache",
"MarketDataSource",
"PriceCache",
"PriceUpdate",
"create_market_data_source",
"create_stream_router",
]
Loading