Skip to content
Merged
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
19 changes: 19 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -39,3 +39,22 @@ jobs:
- run: ruff check .
- run: ruff format --check .
- run: pytest

dashboard:
name: dashboard (Node)
runs-on: ubuntu-latest
defaults:
run:
working-directory: dashboard
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: "22"
cache: npm
cache-dependency-path: dashboard/package-lock.json
- run: npm ci
- run: npm run lint
- run: npm run typecheck
- run: npm test
- run: npm run build
40 changes: 37 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,13 +37,13 @@ Early scaffolding. Roadmap:
- [x] Phase 1 — Vertical slice: sample tick CSV → order-flow imbalance → SQLite → API endpoint.
- [x] Phase 2 — More metrics (realized volatility, volume, VWAP), tests against known data.
- [x] Phase 3 — Trade journal model + Claude API behavioral agent.
- [ ] Phase 4 — Next.js dashboard.
- [x] Phase 4 — Next.js dashboard.
- [ ] Phase 5 — v0.1.0 release with sample-data demo.

## Running locally

Requires Rust (stable) and Python ≥ 3.11. All bundled data is synthetic — never real
trades or amounts.
Requires Rust (stable), Python ≥ 3.11 and Node ≥ 22 (for the dashboard). All bundled
data is synthetic — never real trades or amounts.

```bash
# 1. (Optional) regenerate the sample tape — deterministic, seeded
Expand Down Expand Up @@ -96,11 +96,21 @@ against the stored entry too, so updating just one timestamp can't leave the exi
entry. Invalid bodies return 422. If Claude declines or returns no structured analysis,
`/journal/analyze` returns 502.

Phase 4 puts a dashboard on top: metric charts (order-flow imbalance, realized
volatility, volume, VWAP) and the journal + behavioral-analysis view.

```bash
# 8. Dashboard (Next.js) — proxies /api/backend/* to the API from step 3
cd dashboard && npm install && npm run dev
# open http://localhost:3000 — set BACKEND_URL if the API is not on localhost:8000
```

Run the checks the same way CI does:

```bash
cd engine && cargo fmt --check && cargo clippy --all-targets -- -D warnings && cargo test
cd backtest && ruff check . && ruff format --check . && pytest
cd dashboard && npm run lint && npm run typecheck && npm test && npm run build
```

Verify the whole Phase 1–3 pipeline end to end (tape → engine → metrics → journal → regime
Expand Down Expand Up @@ -205,3 +215,27 @@ calls were made by me — as the project progresses.
that a 1.0 could break; `fastapi`/`uvicorn` stay lower-bounded so CI keeps exercising latest.
A full `uv.lock` was considered and deferred (it would mean migrating CI off pip); the stale
`uv.lock` entry in `.gitattributes`, for a tool the project doesn't use, was removed.
- **Recharts for the dashboard charts** (Phase 4, mine): chosen over TradingView's
`lightweight-charts` (canvas, candlestick-first, imperative) and visx (lower-level than this
needs). The bucketed metrics are at most a few thousand SVG points, and declarative React
components keep the four charts maintainable. One rule shaped them: **no dual axes** — VWAP
(a price) gets its own frame instead of riding a second y-scale on the volume chart, and the
buy↔sell diverging pair was machine-validated for CVD separation and contrast against the
dark surface.
- **Same-origin proxy instead of CORS** (Phase 4, Claude's proposal, accepted): the dashboard
rewrites `/api/backend/*` to the backend (`BACKEND_URL`, default `localhost:8000`), so the
browser never makes a cross-origin request and the FastAPI layer ships exactly as Phase 1–3
left it — no middleware, no origin allowlist to maintain.
- **`*_ns` timestamps as BigInt end-to-end** (Phase 4, Claude's proposal, accepted): response
bodies are parsed with the ES2025 reviver source access (`context.source`) so `*_ns` integers
materialize as exact `BigInt`s, and request bodies serialize them back as unquoted integers
via `JSON.rawJSON` — the pydantic models expect `int`, so quoting them as strings was not an
option. Nanoseconds are truncated to milliseconds only for chart axes. This closes the
Phase 3 note that deferred the > 2^53 JSON-integer problem to the dashboard; the parser fails
loudly on runtimes without source access (Node ≥ 22, evergreen browsers) rather than silently
rounding.
- **Journal UI scope: read + create + analyze** (Phase 4, mine): the dashboard lists entries
with their regime join, logs new trades (client-side mirror of the `exited_at_ns ≥
entered_at_ns` rule, server 422 details surfaced verbatim) and triggers the behavioral agent
behind an explicit button — one Claude call per click, with the 503 no-key and 502 no-analysis
states told apart. `PATCH`/`DELETE` stay API-only until the journal view earns editing.
41 changes: 41 additions & 0 deletions dashboard/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.

# dependencies
/node_modules
/.pnp
.pnp.*
.yarn/*
!.yarn/patches
!.yarn/plugins
!.yarn/releases
!.yarn/versions

# testing
/coverage

# next.js
/.next/
/out/

# production
/build

# misc
.DS_Store
*.pem

# debug
npm-debug.log*
yarn-debug.log*
yarn-error.log*
.pnpm-debug.log*

# env files (can opt-in for committing if needed)
.env*

# vercel
.vercel

# typescript
*.tsbuildinfo
next-env.d.ts
5 changes: 5 additions & 0 deletions dashboard/AGENTS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
<!-- BEGIN:nextjs-agent-rules -->
# This is NOT the Next.js you know

This version has breaking changes — APIs, conventions, and file structure may all differ from your training data. Read the relevant guide in `node_modules/next/dist/docs/` before writing any code. Heed deprecation notices.
<!-- END:nextjs-agent-rules -->
1 change: 1 addition & 0 deletions dashboard/CLAUDE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
@AGENTS.md
48 changes: 42 additions & 6 deletions dashboard/README.md
Original file line number Diff line number Diff line change
@@ -1,10 +1,46 @@
# dashboard/

Visualization dashboard (Next.js + TypeScript). It gets scaffolded in **Phase 4** of
the project plan — keeping this folder as a placeholder until then avoids dumping
thousands of `create-next-app` generated lines into the scaffolding phase.
Next.js (App Router, TypeScript, Tailwind) visualization layer over the
`backtest/` API — Phase 4 of the project plan.

It will consume the `backtest/` API to display:
- **Metrics** (`/`): stat tiles for the latest window plus charts for order-flow
imbalance (diverging bars), realized volatility, buy/sell volume and VWAP.
- **Journal** (`/journal`): trades joined to the market regime at entry, a
log-a-trade form, and the Claude behavioral-analysis panel.

- Microstructure metric charts (order-flow imbalance, realized volatility).
- The trade journal with the behavioral agent's observations.
## Running it

Requires Node ≥ 22. The dashboard talks to the FastAPI backend, so start that
first (see the repo root README: engine → `metrics.db` → `uvicorn
backtest.api:app`).

```bash
npm install
npm run dev # http://localhost:3000
```

Requests to `/api/backend/*` are rewritten server-side to the backend —
`BACKEND_URL` overrides the default `http://localhost:8000`. The backend needs
no CORS configuration because the browser only ever sees this app's origin.
`ANTHROPIC_API_KEY` is only needed by the *backend*, and only for the
journal's Analyze button; everything else works without it.

## Nanosecond timestamps

The API serializes every `*_ns` field as a JSON integer, and those epochs
(~1.76e18) exceed `Number.MAX_SAFE_INTEGER`. `src/lib/nsjson.ts` therefore
parses response bodies with the ES2025 reviver source access (`context.source`)
and materializes `*_ns` fields as `BigInt`, and serializes them back as
unquoted integers via `JSON.rawJSON`. Timestamps are truncated to milliseconds
(`nsToMs`) only for chart axes, where float precision is enough.

## Checks

Same gate CI runs:

```bash
npm run lint
npm run typecheck
npm test # Vitest + Testing Library
npm run build
```
18 changes: 18 additions & 0 deletions dashboard/eslint.config.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { defineConfig, globalIgnores } from "eslint/config";
import nextVitals from "eslint-config-next/core-web-vitals";
import nextTs from "eslint-config-next/typescript";

const eslintConfig = defineConfig([
...nextVitals,
...nextTs,
// Override default ignores of eslint-config-next.
globalIgnores([
// Default ignores of eslint-config-next:
".next/**",
"out/**",
"build/**",
"next-env.d.ts",
]),
]);

export default eslintConfig;
16 changes: 16 additions & 0 deletions dashboard/next.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import type { NextConfig } from "next";

const nextConfig: NextConfig = {
// Same-origin proxy to the FastAPI backend: the backend serves no CORS
// headers, so the browser only ever talks to this app's origin.
async rewrites() {
return [
{
source: "/api/backend/:path*",
destination: `${process.env.BACKEND_URL ?? "http://localhost:8000"}/:path*`,
},
];
},
};

export default nextConfig;
Loading
Loading