diff --git a/.env.example b/.env.example
index be20b62..d42f715 100644
--- a/.env.example
+++ b/.env.example
@@ -6,10 +6,6 @@ REDIS_URL=redis://127.0.0.1:6379
# Options ingest
OPTIONS_INGEST_ADAPTER=synthetic
ALPACA_API_KEY=
-ALPACA_API_KEY_ID=
-ALPACA_KEY_ID=
-ALPACA_API_SECRET_KEY=
-ALPACA_SECRET_KEY=
ALPACA_REST_URL=https://data.alpaca.markets
ALPACA_WS_BASE_URL=wss://stream.data.alpaca.markets/v1beta1
ALPACA_FEED=indicative
diff --git a/.github/workflows/docs-pages.yml b/.github/workflows/docs-pages.yml
deleted file mode 100644
index 9c4db98..0000000
--- a/.github/workflows/docs-pages.yml
+++ /dev/null
@@ -1,56 +0,0 @@
-name: Publish Docs
-
-on:
- push:
- branches:
- - main
- paths:
- - "docs/**"
- - "scripts/generate-docs-index.mjs"
- - ".github/workflows/docs-pages.yml"
- workflow_dispatch:
-
-permissions:
- contents: read
- pages: write
- id-token: write
-
-concurrency:
- group: "pages"
- cancel-in-progress: true
-
-jobs:
- build:
- runs-on: ubuntu-latest
- steps:
- - name: Checkout
- uses: actions/checkout@v4
-
- - name: Configure Pages
- uses: actions/configure-pages@v5
-
- - name: Build docs index
- run: node scripts/generate-docs-index.mjs
-
- - name: Prepare static site payload
- run: |
- mkdir -p site/docs
- cp -R docs/. site/docs/
- printf '%s\n' '
Islandflow DocsContinue to docs' > site/index.html
- touch site/.nojekyll
-
- - name: Upload Pages artifact
- uses: actions/upload-pages-artifact@v3
- with:
- path: site
-
- deploy:
- environment:
- name: github-pages
- url: ${{ steps.deployment.outputs.page_url }}
- needs: build
- runs-on: ubuntu-latest
- steps:
- - name: Deploy to GitHub Pages
- id: deployment
- uses: actions/deploy-pages@v4
diff --git a/AGENTS server.md b/AGENTS server.md
deleted file mode 100644
index 08a484a..0000000
--- a/AGENTS server.md
+++ /dev/null
@@ -1,174 +0,0 @@
-
-## Beads Issue Tracker
-
-This project uses **bd (beads)** for issue tracking. Run `bd prime` to see full workflow context and commands.
-
-### Quick Reference
-
-```bash
-bd ready # Find available work
-bd show # View issue details
-bd update --claim # Claim work
-bd close # Complete work
-```
-
-### Rules
-
-- Use `bd` for ALL task tracking — do NOT use TodoWrite, TaskCreate, or markdown TODO lists
-- Run `bd prime` for detailed command reference and session close protocol
-- Use `bd remember` for persistent knowledge — do NOT use MEMORY.md files
-
-## Session Completion
-
-**When ending a work session**, you MUST complete ALL steps below. Work is NOT complete until `git push` succeeds.
-
-**MANDATORY WORKFLOW:**
-
-1. **File issues for remaining work** - Create issues for anything that needs follow-up
-2. **Run quality gates** (if code changed) - Tests, linters, builds
-3. **Update issue status** - Close finished work, update in-progress items
-4. **PUSH TO REMOTE** - This is MANDATORY:
- ```bash
- git pull --rebase
- bd dolt push
- git push
- git status # MUST show "up to date with origin"
- ```
-5. **Clean up** - Clear stashes, prune remote branches
-6. **Verify** - All changes committed AND pushed
-7. **Hand off** - Provide context for next session
-
-**CRITICAL RULES:**
-- Work is NOT complete until `git push` succeeds
-- NEVER stop before pushing - that leaves work stranded locally
-- NEVER say "ready to push when you are" - YOU must push
-- If push fails, resolve and retry until it succeeds
-
-
-## Minimal Repo Operating Instructions
-
-This is a Bun + TypeScript monorepo for an event-sourced market-data pipeline:
-- Flow: ingest services publish to NATS/JetStream, compute/candles derive events, API serves REST/WS, web consumes live/replay streams.
-- Main folders: `services/*` (runtime services), `packages/*` (shared libs/types/storage), `apps/web` (Next.js UI).
-- Infra dependency: local dev assumes Docker services (NATS, ClickHouse, Redis) are available.
-
-Use these repo-specific commands:
-- Install deps: `bun install`
-- Start full stack: `bun run dev`
-- Start infra only: `bun run dev:infra`
-- Start backend services only: `bun run dev:services`
-- Start web only: `bun run dev:web`
-
-Testing and validation in this repo are Bun-first:
-- Run tests: `bun test`
-- Run scoped tests: `bun test services/compute/tests` (or another package/service path)
-- Validate web production build when UI code changes: `bun --cwd=apps/web run build`
-
-Working style that avoids common problems here:
-- Prefer editing in the touched workspace (`services/`, `packages/`, `apps/web`) and keep shared contract changes in `packages/types`.
-- Keep `.env` aligned with `.env.example`; adapters default to synthetic modes for local development.
-- Dev runners persist child PID state in `.tmp/`; if a previous run crashed, restart via the standard `bun run dev*` commands so stale processes are cleaned up.
-
-## Required Turn Documentation
-
-At the end of every completed implementation task, before final handoff, create a user-readable HTML document describing the work.
-
-This documentation is mandatory whenever code, configuration, tests, or project files were changed.
-
-### Location
-
-Save the document in:
-
-```text
-docs/turns/
-```
-## Important: If you are not working inside a git repository, save the document to `~/dev/docs/turns/`
-
-Use a clear timestamped filename:
-
-```text
-docs/turns/YYYY-MM-DD-short-task-name.html
-```
-
-Example:
-
-```text
-docs/turns/2026-05-14-add-market-replay-controls.html
-```
-
-### Format
-
-Use the impeccable skill to structure the document as clean, readable HTML.
-
-If the impeccable skill is unavailable, still create a well-structured standalone HTML file with:
-
-- A concise summary at the top
-- A detailed explanation of what changed
-- Relevant context or background
-- Specific code snippets or examples when helpful
-- Issues, limitations, tradeoffs, or mitigations
-- Validation performed, including tests, builds, linters, or manual checks
-- Any remaining follow-up work, with corresponding Beads issue IDs when applicable
-
-### Required Sections
-
-Each turn document must include these sections:
-
-1. **Summary**
-2. **Changes Made**
-3. **Context**
-4. **Important Implementation Details**
-5. **Relevant Diff Snippets**
-6. **Expected Impact for End-Users**
-7. **Validation**
-8. **Issues, Limitations, and Mitigations**
-9. **Follow-up Work**
-
-### Completion Rule
-
-A task is not complete until:
-
-1. The Beads workflow is updated
-2. The turn document is created in `docs/turns`
-3. Relevant quality gates have passed or failures are documented
-4. Changes are committed
-5. `bd dolt push` succeeds
-6. `git push` succeeds
-7. `git status` shows the branch is up to date with origin
-
-For trivial changes, the document may be brief, but it must still exist and clearly explain what changed and how it was validated.
-
-## Plan Mode Documentation
-
-When working in plan mode, do not modify implementation files.
-
-At the end of plan mode, provide a concise summary of the plan and ask the user whether they want to proceed with implementation.
-
-If the user asks to save the plan, create a user-readable HTML plan document in:
-
-```text
-docs/plans/
-```
-
-Use a clear timestamped filename:
-
-```text
-docs/plans/YYYY-MM-DD-short-plan-name.html
-```
-
-The plan document should be labeled clearly as a plan and should include:
-
-1. **Plan Summary**
-2. **Goals**
-3. **Proposed Changes**
-4. **Relevant Context**
-5. **Implementation Steps**
-6. **Risks, Limitations, and Mitigations**
-7. **Open Questions**
-
-Always do the following when you finish a task, finish the beads workflow and and make a commit:
-- Document the changes in a user-readable format
-- Use the impeccable skill to structure the document as HTML
-- Create a clear, concise summary of the changes at the top, followed by a detailed description of the changes, including any relevant context or background as well as specific code snippets or examples.
-- Note any relevant issues or limitations that were addressed or mitigated by the changes.
-- The HTML file should be stored in the `docs/turns` directory. It should include the current date and time, as well as a brief explanation of changes. e.g. docs/turns/YYYY-MM-DD-{description}.html
diff --git a/AGENTS.md b/AGENTS.md
index fe8ffca..3ab1cf0 100644
--- a/AGENTS.md
+++ b/AGENTS.md
@@ -97,11 +97,9 @@ docs/turns/2026-05-14-add-market-replay-controls.html
### Format
-Use the `impeccable` skill to structure and style the document as clean, readable HTML.
+Use the impeccable skill to structure the document as clean, readable HTML.
-For this repository, `impeccable` is the styling and layout authority for turn documents when available. Do not apply global non-repo computer-task house styling to repository turn documents.
-
-If the `impeccable` skill is unavailable or blocked by an actual tool/file error, still create a well-structured standalone HTML file with:
+If the impeccable skill is unavailable, still create a well-structured standalone HTML file with:
- A concise summary at the top
- A detailed explanation of what changed
@@ -119,11 +117,10 @@ Each turn document must include these sections:
2. **Changes Made**
3. **Context**
4. **Important Implementation Details**
-5. **Relevant Diff Snippets**
-6. **Expected Impact for End-Users**
-7. **Validation**
-8. **Issues, Limitations, and Mitigations**
-9. **Follow-up Work**
+5. **Expected Impact for End-Users**
+5. **Validation**
+6. **Issues, Limitations, and Mitigations**
+7. **Follow-up Work**
### Completion Rule
diff --git a/README.md b/README.md
index 6b3b7fc..50063d9 100644
--- a/README.md
+++ b/README.md
@@ -6,12 +6,11 @@
> **Pre-alpha warning** This project is in an early pre-alpha state. It will not perform consistently or as expected, and APIs, behavior, and data contracts may change without notice.
-Islandflow is a Bun + TypeScript monorepo for a personal-use, event-sourced market microstructure research platform focused on:
+This repository contains a Bun + TypeScript monorepo for a personal-use, event-sourced market microstructure research platform focused on:
- options prints + NBBO,
- off-exchange equity prints,
-- market news context,
-- explainable smart-money flow classification,
+- explainable rule-based flow classification,
- deterministic replay,
- evidence-linked UI inspection.
@@ -20,176 +19,124 @@ Islandflow is a Bun + TypeScript monorepo for a personal-use, event-sourced mark
Implemented now:
- Bun workspaces with shared packages for schemas, bus, config, observability, and ClickHouse access.
-- Infra orchestration via Docker Compose for local NATS JetStream, ClickHouse, and Redis.
-- Options ingest service with synthetic, Alpaca options, IBKR bridge, and Databento historical replay adapters.
-- Equities ingest service with synthetic and Alpaca equities trades/quotes adapters.
-- News ingest service for Alpaca news backfill and websocket publication.
-- Compute service for deterministic parent-event reconstruction, flow packets, NBBO quality features, rolling baselines, smart-money profile scoring, compatibility classifier hits, alerts, inferred dark-style events, and equity print-to-quote joins.
-- Candles service for server-side equity candle aggregation, ClickHouse persistence, optional Redis hot cache, and NATS publication.
-- Replay service for deterministic ClickHouse-to-NATS republishing with multi-stream merge, stable tie-break ordering, speed, start, and end controls.
-- API service with REST endpoints, cursor pagination, replay/history endpoints, live hot-cache hydration, and WebSocket channels for options, NBBO, equities, quotes, joins, flow, classifier hits, alerts, smart-money events, inferred dark, candles, and news.
-- Next.js web app upgraded to Next.js `16.2.6`, React `19.2.0`, and React DOM `19.2.0`.
-- Evidence-centric terminal UI, live/replay controls, chart-focused routes, news view, profile-aware smart-money display, and alert-context hydration.
-- Thin Electron desktop shell in `apps/desktop` that can wrap the hosted app or local web UI.
-- Refdata + EOD enricher service entrypoints are present, with refdata able to validate or refresh the event-calendar cache.
+- Infra orchestration via Docker Compose (NATS JetStream, ClickHouse, Redis).
+- Options ingest service with adapters:
+ - synthetic stream,
+ - Alpaca options (dev-focused, bounded contracts),
+ - IBKR bridge (Python sidecar),
+ - Databento historical replay adapter (Python sidecar).
+- Equities ingest service with adapters:
+ - synthetic stream,
+ - Alpaca equities trades/quotes.
+- Compute service:
+ - deterministic option print clustering into `FlowPacket`s,
+ - NBBO join quality features and aggressor-mix metrics,
+ - rolling baselines in Redis,
+ - structure summarization and structure packet emission,
+ - rule-based classifiers + confidence-scored alert events,
+ - dark-style inferred events from equity prints/quotes,
+ - equity print-to-quote join events.
+- Candles service:
+ - server-side equity candle aggregation,
+ - ClickHouse persistence,
+ - optional Redis hot cache,
+ - NATS publication.
+- Replay service:
+ - deterministic republishing from ClickHouse to NATS,
+ - multi-stream merge with stable tie-break ordering,
+ - speed/start/end controls.
+- API service:
+ - REST endpoints for recent + cursor pagination,
+ - REST range endpoints for chart windows,
+ - REST replay-oriented endpoints,
+ - WebSocket channels for options, NBBO, equities, quotes, joins, flow, classifier hits, alerts, inferred dark, and candles.
+- Next.js web app:
+ - live tape/workspace views,
+ - replay controls and status,
+ - signals and chart-focused routes,
+ - evidence-centric terminal UI.
+- Refdata + EOD enricher service entrypoints are present but currently scaffolds (lifecycle/logging only).
Planned / not yet complete:
- production-grade licensed feed integrations and entitlement workflow,
- richer refdata/corp-action enrichment,
- secure deployment/auth hardening,
-- native deployment unit templates and rollback helpers,
-- signed/notarized desktop distribution and richer desktop-native features,
-- deeper calibration workflows from `PLAN.md` and `SMART_MONEY_REBUILD_PLAN.md`.
+- deeper structure + calibration workflows from `PLAN.md`.
## Core Principles
-- **Explainability first**: inferred outputs are evidence-backed and human-readable.
-- **Event sourcing**: raw and derived events persist to support replay.
-- **Determinism**: replay behavior tracks live pipeline logic.
-- **Microstructure awareness**: bounded joins, confidence scoring, and explicit uncertainty.
-- **Taxonomy over folklore**: "smart money" is modeled as participant-style hypotheses, not a single binary label.
-- **Bun-first tooling**: runtime, package management, scripts, and tests use Bun.
-
-## Smart-Money Classification Taxonomy
-
-Islandflow now emits first-class `SmartMoneyEvent` records instead of treating old classifier hits as the final semantic object. `FlowPacket` remains the clustering bridge, while smart-money events carry typed features, profile scores, confidence bands, directions, reason codes, abstention state, and suppression reasons.
-
-Public profile IDs:
-
-| Profile ID | Meaning | Common evidence |
-| --- | --- | --- |
-| `institutional_directional` | Large directional parent flow with stronger institutional-style conviction. | premium, size, sweep/burst behavior, aggressor imbalance, quote quality, not short-dated retail-chase context |
-| `retail_whale` | Large retail-style speculative bursts, often short-dated or attention-driven. | short-dated OTM concentration, burst prints, IV shock, lower premium than institutional blocks |
-| `event_driven` | Flow aligned to known upcoming events. | event-calendar proximity, expiry after event, pre-event concentration, spread/IV pressure |
-| `vol_seller` | Premium-selling or short-volatility structure evidence. | sell-side premium, straddles/strangles, neutral direction |
-| `arbitrage` | Multi-leg or symmetric structures with low directional exposure. | matched leg symmetry, same-size legs, near-flat directional bias |
-| `hedge_reactive` | Hedge or dealer-reaction style flow around short-dated ATM/gamma context. | 0-2 DTE, near-ATM contracts, underlying move linkage, size |
-
-Compatibility surfaces remain in place:
-
-- `ClassifierHitEvent` is derived from `SmartMoneyEvent.primary_profile_id`.
-- `AlertEvent` may include `primary_profile_id` and `profile_scores`.
-- Legacy classifier and alert endpoints still work.
-
-Primary smart-money access paths:
-
-```text
-/flow/smart-money
-/history/smart-money
-/replay/smart-money
-/ws/smart-money
-```
-
-The classifier intentionally abstains when evidence is weak or quote context is stale/missing. Suppression guards cover stale quotes, complex/special prints, retail-frenzy directional confusion, hedge-reactive short-dated ATM contexts, and arbitrage symmetry.
+- **Explainability first** — inferred outputs are evidence-backed and human-readable.
+- **Event sourcing** — raw and derived events persist to support replay.
+- **Determinism** — replay behavior tracks live pipeline logic.
+- **Microstructure awareness** — bounded joins, confidence scoring, and explicit uncertainty.
+- **Bun-first tooling** — runtime/package/scripts all use Bun.
## Monorepo Layout
- `apps/web` — Next.js UI shell/routes.
-- `apps/desktop` — Electron desktop shell that loads the hosted or local Islandflow app.
+- `apps/desktop` — Electron desktop shell that loads the hosted Islandflow app.
- `services/ingest-options` — options print/NBBO ingest adapters.
- `services/ingest-equities` — equity print/quote ingest adapters.
-- `services/ingest-news` — Alpaca news backfill and websocket ingest.
-- `services/compute` — parent-event reconstruction, flow packets, smart-money scoring, alerts, inferred dark.
+- `services/compute` — clustering, structures, classifiers, alerts, inferred dark.
- `services/candles` — server-side candle aggregation + cache.
-- `services/replay` — ClickHouse to NATS replay streamer.
+- `services/replay` — ClickHouse → NATS replay streamer.
- `services/api` — REST + WebSocket gateway.
-- `services/refdata` — event-calendar validation/provider refresh scaffolding.
+- `services/refdata` — scaffold service.
- `services/eod-enricher` — scaffold service.
- `packages/types` — shared event schemas/types.
- `packages/storage` — ClickHouse tables/queries.
- `packages/bus` — NATS/JetStream helpers.
- `packages/config` — env parsing.
- `packages/observability` — logger + metrics facade.
-- `deployment/docker` — supported VPS Docker Compose runtime.
-- `deployment/native` — experimental host-native Bun + systemd deployment notes.
## Build and Run
Install dependencies:
-```bash
-bun install
-```
+- `bun install`
Start infrastructure only:
-```bash
-bun run dev:infra
-```
+- `docker compose up -d`
Create env file:
-```bash
-cp .env.example .env
-```
+- copy `.env.example` to `.env` and set provider credentials as needed.
Start infra + all services + web:
-```bash
-bun run dev
-```
+- `bun run dev`
-Start services only, assuming infra is already running:
+Start services only (assumes infra is already running):
-```bash
-bun run dev:services
-```
+- `bun run dev:services`
Start web only:
-```bash
-bun run dev:web
-```
+- `bun run dev:web`
Recommended fast iteration loop:
-```bash
-bun run dev:infra
-bun run dev:services
-bun run dev:web
-```
+- `bun run dev:infra` for Docker-backed infra only
+- `bun run dev:services` for native Bun backend services
+- `bun run dev:web` for the local Next.js UI
-This keeps Docker in the local workflow where it helps most, for NATS, ClickHouse, and Redis, while keeping the app services in native Bun/Next.js loops.
+This keeps Docker in the local workflow where it helps most (NATS, ClickHouse, Redis) without forcing the app services themselves into slower container rebuild/restart loops.
## Deployment Workflow
-Docker remains the supported and recommended path for the current VPS.
-
-```bash
-./deploy main
-./deploy main --runtime docker
-./deploy current-branch
-./deploy current-branch --runtime docker
-```
-
-Important deployment notes:
-
-- Run the deploy helper from the local repo checkout, not from the VPS shell.
-- Do not run the repo-root `docker-compose.yml` on the VPS. It is local infra only and can create duplicate exposed NATS, ClickHouse, and Redis containers on the server.
-- The Docker stack lives in `deployment/docker` and is separate from local development infra.
-- Partial deploys are supported with `--web-only`, `--api-only`, `--services-only`, `--workers-only`, `--fast`, `--no-build`, and `--force-recreate`.
-- `--fast` defaults to a services-only Docker rollout when no explicit scope is provided and trims public API route-suite verification while preserving remote service health checks.
-- `./deploy current-branch` requires a clean local working tree and pushes the branch before moving the server checkout.
-- The helper has Forgejo-aware remote resolution for deployments and branch pushes.
-- When run from `/home/delta/islandflow` on the VPS itself, `./deploy` can execute locally instead of SSHing back into the same server.
-- Native deployment is opt-in and experimental:
-
-```bash
-./deploy main --runtime native
-./deploy current-branch --runtime native
-```
-
-Native deployment expects Bun, systemd units, host-reachable infra, and deliberate reverse-proxy changes. Native deploys are intended primarily for worker-only fast iteration until the public edge is cut over deliberately.
-
-Read more:
-
-- `deployment/docker/README.md`
-- `deployment/native/README.md`
+- `./deploy main` keeps the current VPS Docker rollout path as the default and recommended path.
+- Do not run the repo-root `docker-compose.yml` on the VPS. That file is for local infra only and can create duplicate exposed NATS, ClickHouse, and Redis containers on the server.
+- `./deploy main --runtime native` targets an experimental host-native Bun + systemd deployment.
+- `./deploy current-branch` and `./deploy current-branch --runtime native` keep branch deploys available during the transition, but Docker remains the supported path for the current VPS.
+- Partial deploys are supported with `--web-only`, `--api-only`, `--services-only`, and `--no-build`.
+- Docker runtime details live in `deployment/docker/README.md`.
+- Native runtime expectations and prerequisites live in `deployment/native/README.md`.
## Desktop Shell
-Islandflow includes a thin Electron desktop shell in `apps/desktop`.
+Islandflow also includes a thin Electron desktop shell in `apps/desktop`.
What it is:
@@ -197,35 +144,37 @@ What it is:
- a native app window plus packaging/distribution shell,
- a way to run the existing web UI inside Electron without local backend services.
-What it is not yet:
+What it is not:
- a bundled backend runtime,
-- a packaged local Next.js frontend,
-- a desktop feature layer with notifications, preferences, auto-updates, signing, or notarization.
+- a packaged local Next.js frontend in v1,
+- a desktop feature layer with notifications, preferences, or auto-updates yet.
Run the desktop shell against a local web UI:
-```bash
-bun run dev:desktop
-```
+- `bun run dev:desktop`
+
+This starts the local Next.js app, defaults `NEXT_PUBLIC_API_URL` to `https://flow.deltaisland.io` unless you already set it, waits for port `3000`, and then launches Electron against `http://127.0.0.1:3000`.
Run the desktop shell directly against the hosted app:
-```bash
-bun run dev:desktop:remote
-```
+- `bun run dev:desktop:remote`
Package the desktop shell:
-```bash
-bun run package:desktop
-bun run make:desktop
-```
+- `bun run package:desktop`
+- `bun run make:desktop`
Desktop-specific environment:
- `ISLANDFLOW_DESKTOP_START_URL` is only used by the Electron shell and is restricted to trusted Islandflow app origins.
-- `NEXT_PUBLIC_API_URL` remains the web app API/WebSocket origin control and usually points at `https://flow.deltaisland.io` when developing local UI inside Electron.
+- `NEXT_PUBLIC_API_URL` remains the web app's API/WebSocket origin control and should usually point at `https://flow.deltaisland.io` when developing the local UI inside Electron.
+
+Current desktop limitations:
+
+- v1 builds are unsigned internal macOS artifacts only,
+- Forge currently makes a simple zip distributable for the current host architecture,
+- signing, notarization, auto-updates, remembered window state, and richer native integrations are intentionally deferred.
## Environment Configuration
@@ -247,31 +196,32 @@ All runtime configuration comes from `.env`.
| `OPTIONS_INGEST_ADAPTER` | `synthetic` | Options ingest source: `synthetic`, `alpaca`, `ibkr`, or `databento`. |
| `EQUITIES_INGEST_ADAPTER` | `synthetic` | Equities ingest source: `synthetic` or `alpaca`. |
| `EMIT_INTERVAL_MS` | `1000` | Emit cadence for synthetic ingest adapters. |
-| `SYNTHETIC_MARKET_MODE` | `realistic` | Shared synthetic profile: `realistic`, `active`, or `firehose`. |
-| `SYNTHETIC_OPTIONS_MODE` | empty | Options-only synthetic profile override. |
-| `SYNTHETIC_EQUITIES_MODE` | empty | Equities-only synthetic profile override. |
+| `SYNTHETIC_MARKET_MODE` | `realistic` | Shared synthetic profile (`realistic`, `active`, `firehose`) used when per-service override is unset. |
+| `SYNTHETIC_OPTIONS_MODE` | empty | Options-only synthetic profile override; falls back to `SYNTHETIC_MARKET_MODE`. |
+| `SYNTHETIC_EQUITIES_MODE` | empty | Equities-only synthetic profile override; falls back to `SYNTHETIC_MARKET_MODE`. |
-### Alpaca and news configuration
+Synthetic profile intent:
+- `realistic`: default local mode with lower synthetic burstiness/noise.
+- `active`: busier demo flow while still readable.
+- `firehose`: stress mode for throughput/backpressure/hot-window behavior.
+
+### Options ingest adapter configuration
| Variable | Default | What it controls |
| --- | --- | --- |
-| `ALPACA_API_KEY` | empty | Legacy single-token fallback kept for older Alpaca setups. Prefer explicit key ID + secret vars for current Alpaca auth. |
-| `ALPACA_API_KEY_ID` | empty | Preferred Alpaca key ID used for market-data REST and websocket auth. |
-| `ALPACA_KEY_ID` | empty | Alternate name accepted for the Alpaca key ID. |
-| `ALPACA_API_SECRET_KEY` | empty | Preferred Alpaca secret key paired with `ALPACA_API_KEY_ID`. |
-| `ALPACA_SECRET_KEY` | empty | Alternate name accepted for the Alpaca secret key. |
-| `ALPACA_REST_URL` | `https://data.alpaca.markets` | Alpaca REST base URL. |
-| `ALPACA_WS_BASE_URL` | `wss://stream.data.alpaca.markets/v1beta1` for options, `wss://stream.data.alpaca.markets` for equities/news | Alpaca websocket base URL. |
-| `ALPACA_FEED` | `indicative` | Options feed tier: `indicative` or `opra`. |
+| `ALPACA_API_KEY` | empty | Single-token Alpaca API auth for options/equities adapters. Use this when your account provides one API key value. |
+| `ALPACA_REST_URL` | `https://data.alpaca.markets` | Alpaca REST base URL for contract discovery/reference calls. |
+| `ALPACA_WS_BASE_URL` | `wss://stream.data.alpaca.markets/v1beta1` (options), `wss://stream.data.alpaca.markets` (equities) | Alpaca websocket base URL. |
+| `ALPACA_FEED` | `indicative` | Options feed tier for Alpaca options (`indicative` or `opra`). |
| `ALPACA_UNDERLYINGS` | `SPY,NVDA,AAPL` | Comma-separated symbols targeted by Alpaca ingest. |
| `ALPACA_STRIKES_PER_SIDE` | `8` | Contracts selected per side of spot for Alpaca options chain sampling. |
| `ALPACA_MAX_DTE_DAYS` | `30` | Max days-to-expiry included for Alpaca options contract selection. |
| `ALPACA_MONEYNESS_PCT` | `0.06` | Primary moneyness filter for Alpaca options contract selection. |
| `ALPACA_MONEYNESS_FALLBACK_PCT` | `0.1` | Wider fallback moneyness filter if candidate set is too sparse. |
| `ALPACA_MAX_QUOTES` | `200` | Upper bound on selected Alpaca options contracts/quotes per cycle. |
-| `ALPACA_EQUITIES_FEED` | `iex` | Alpaca equities feed: `iex` or `sip`. |
-| `ALPACA_NEWS_BACKFILL_LIMIT` | `50` | Alpaca news stories fetched on startup, capped at 50 by the Alpaca News API. |
-| `ALPACA_NEWS_WEBSOCKET_PATH` | `/v1beta1/news` | Alpaca news websocket path. |
+| `ALPACA_EQUITIES_FEED` | `iex` | Alpaca equities feed (`iex` free tier, `sip` paid consolidated feed). |
+
+For Alpaca adapters, configure `ALPACA_API_KEY`.
### Databento replay adapter configuration
@@ -286,7 +236,7 @@ All runtime configuration comes from `.env`.
| `DATABENTO_SYMBOLS` | `ALL` | Symbol selection forwarded to Databento sidecar query. |
| `DATABENTO_STYPE_IN` | `raw_symbol` | Databento input symbology type. |
| `DATABENTO_STYPE_OUT` | `raw_symbol` | Databento output symbology type. |
-| `DATABENTO_LIMIT` | `0` | Max Databento records, where `0` means no explicit limit. |
+| `DATABENTO_LIMIT` | `0` | Max Databento records (`0` means no explicit limit). |
| `DATABENTO_PRICE_SCALE` | `1` | Multiplier applied to decoded prices from sidecar output. |
| `DATABENTO_PYTHON_BIN` | `python3` | Python executable used to run Databento sidecar script. |
@@ -298,9 +248,9 @@ All runtime configuration comes from `.env`.
| `IBKR_PORT` | `7497` | TWS/Gateway port for IBKR bridge. |
| `IBKR_CLIENT_ID` | `0` | IBKR client id used by the bridge connection. |
| `IBKR_SYMBOL` | `SPY` | Underlying symbol requested from IBKR. |
-| `IBKR_EXPIRY` | `20250117` | Option expiry requested from IBKR. |
+| `IBKR_EXPIRY` | `20250117` | Option expiry (YYYYMMDD) requested from IBKR. |
| `IBKR_STRIKE` | `450` | Strike requested from IBKR. |
-| `IBKR_RIGHT` | `C` | Option side: `C` or `P`. |
+| `IBKR_RIGHT` | `C` | Option side (`C` or `P`). |
| `IBKR_EXCHANGE` | `SMART` | IBKR exchange routing code. |
| `IBKR_CURRENCY` | `USD` | Contract currency. |
| `IBKR_PYTHON_BIN` | `python3` | Python executable used for IBKR sidecar. |
@@ -309,77 +259,133 @@ All runtime configuration comes from `.env`.
| Variable | Default | What it controls |
| --- | --- | --- |
-| `OPTIONS_SIGNAL_MODE` | `smart-money` | Signal pass policy: `smart-money`, `balanced`, or `all`. |
+| `OPTIONS_SIGNAL_MODE` | `smart-money` | Signal pass policy (`smart-money`, `balanced`, `all`) for options prints. |
| `OPTIONS_SIGNAL_MIN_NOTIONAL` | `10000` | Base minimum notional for most signal candidates. |
| `OPTIONS_SIGNAL_ETF_MIN_NOTIONAL` | `50000` | ETF-specific minimum notional for signal inclusion. |
-| `OPTIONS_SIGNAL_BID_SIDE_MIN_NOTIONAL` | `25000` | Minimum notional for bid-side or sweep/ISO thresholds. |
+| `OPTIONS_SIGNAL_BID_SIDE_MIN_NOTIONAL` | `25000` | Minimum notional for bid-side (`B`/`BB`) or sweep/ISO thresholds. |
| `OPTIONS_SIGNAL_MID_MIN_NOTIONAL` | `20000` | Minimum notional for non-sweep/non-ISO `MID` prints. |
| `OPTIONS_SIGNAL_NBBO_MAX_AGE_MS` | `1500` | NBBO freshness threshold used during signal classification. |
-| `OPTIONS_SIGNAL_ETF_UNDERLYINGS` | `SPY,QQQ,IWM,DIA,TLT,GLD,SLV,XLF,XLE,XLV,XLI,XLP,XLU,XLY,SMH,ARKK` | ETF underlyings treated specially by signal filters. |
+| `OPTIONS_SIGNAL_ETF_UNDERLYINGS` | `SPY,QQQ,IWM,DIA,TLT,GLD,SLV,XLF,XLE,XLV,XLI,XLP,XLU,XLY,SMH,ARKK` | Comma-separated underlyings treated as ETFs by signal filters. |
-Default `smart-money` policy rejects lower-information prints and keeps higher-confidence, higher-notional, sweep-style flow. `balanced` lowers thresholds. `all` bypasses filtering.
+Default `smart-money` policy rejects lower-information prints and keeps high-confidence/high-notional/sweep-style flow; `balanced` lowers thresholds; `all` bypasses filtering.
-### Compute, classifier, and dark-inference configuration
+### Compute/classifier/dark-inference configuration
| Variable | Default | What it controls |
| --- | --- | --- |
-| `CLUSTER_WINDOW_MS` | `500` | Time window used to cluster nearby option prints into packet candidates. |
-| `COMPUTE_DELIVER_POLICY` | `new` | Consumer start policy for compute subscriptions. |
-| `COMPUTE_CONSUMER_RESET` | `false` | Resets durable consumer position for compute on startup when true. |
+| `CLUSTER_WINDOW_MS` | `500` | Time window used to cluster nearby option prints into a packet candidate. |
+| `COMPUTE_DELIVER_POLICY` | `new` | Consumer start policy for compute stream subscriptions (`new`, `all`, `last`, `last_per_subject`). |
+| `COMPUTE_CONSUMER_RESET` | `false` | If true, resets durable consumer position for compute on startup. |
| `NBBO_MAX_AGE_MS` | `1000` | Max NBBO age accepted when enriching option prints in compute. |
| `ROLLING_WINDOW_SIZE` | `50` | Number of observations retained per rolling metric key. |
| `ROLLING_TTL_SEC` | `86400` | Redis TTL for rolling metric keys. |
| `EQUITY_QUOTE_MAX_AGE_MS` | `1000` | Max quote staleness when joining equity prints for inference. |
| `DARK_INFER_WINDOW_MS` | `60000` | Sliding window length for dark-style inference accumulation. |
-| `DARK_INFER_COOLDOWN_MS` | `30000` | Cooldown before repeated dark inferences for same symbol/pattern. |
-| `SMART_MONEY_EVENT_CALENDAR_PATH` | empty | Optional JSON event-calendar file used by compute. |
-| `REFDATA_EVENT_CALENDAR_PATH` | empty | Optional JSON event-calendar path for refdata; falls back to `SMART_MONEY_EVENT_CALENDAR_PATH`. |
-| `REFDATA_EVENT_CALENDAR_PROVIDER` | empty | Set to `alpha_vantage` to refresh event-calendar cache from Alpha Vantage. |
-| `ALPHA_VANTAGE_API_KEY` | empty | Alpha Vantage key for provider-backed event-calendar refresh. |
+| `DARK_INFER_COOLDOWN_MS` | `30000` | Cooldown before emitting repeated dark inferences for same symbol/pattern. |
+| `DARK_INFER_MIN_BLOCK_SIZE` | `2000` | Minimum single-print size for block-style dark inference evidence. |
+| `DARK_INFER_MIN_ACCUM_SIZE` | `3000` | Minimum aggregate size for accumulation-style dark inference evidence. |
+| `DARK_INFER_MIN_ACCUM_COUNT` | `4` | Minimum print count for accumulation-style dark inference. |
+| `DARK_INFER_MIN_PRINT_SIZE` | `200` | Minimum print size considered as dark inference evidence. |
+| `DARK_INFER_MAX_EVIDENCE` | `20` | Max evidence items attached to one inferred dark event. |
+| `DARK_INFER_MAX_SPREAD_PCT` | `0.005` | Maximum spread percentage allowed for dark inference confidence. |
+| `CLASSIFIER_SWEEP_MIN_PREMIUM` | `40000` | Minimum premium to trigger sweep classifier logic. |
+| `CLASSIFIER_SWEEP_MIN_COUNT` | `3` | Minimum child prints in cluster for sweep classifier hit. |
+| `CLASSIFIER_SWEEP_MIN_PREMIUM_Z` | `2` | Min premium z-score for sweep classifier confirmation. |
+| `CLASSIFIER_SPIKE_MIN_PREMIUM` | `20000` | Minimum premium for spike classifier logic. |
+| `CLASSIFIER_SPIKE_MIN_SIZE` | `400` | Minimum total size for spike classifier logic. |
+| `CLASSIFIER_SPIKE_MIN_PREMIUM_Z` | `2.5` | Min premium z-score for spike classifier confirmation. |
+| `CLASSIFIER_SPIKE_MIN_SIZE_Z` | `2` | Min size z-score for spike classifier confirmation. |
+| `CLASSIFIER_Z_MIN_SAMPLES` | `12` | Minimum rolling sample count before z-score gating applies. |
+| `CLASSIFIER_MIN_NBBO_COVERAGE` | `0.5` | Required fraction of prints in cluster with valid NBBO context. |
+| `CLASSIFIER_MIN_AGGRESSOR_RATIO` | `0.55` | Minimum aggressor-side ratio for classifier confidence. |
+| `CLASSIFIER_0DTE_MAX_ATM_PCT` | `0.01` | Max distance-from-ATM to qualify as near-ATM 0DTE event. |
+| `CLASSIFIER_0DTE_MIN_PREMIUM` | `20000` | Minimum premium for 0DTE classifier events. |
+| `CLASSIFIER_0DTE_MIN_SIZE` | `400` | Minimum size for 0DTE classifier events. |
+| `SMART_MONEY_EVENT_CALENDAR_PATH` | empty | Optional JSON event-calendar file used by compute to enrich event-driven smart-money profile features. |
+| `REFDATA_EVENT_CALENDAR_PATH` | empty | Optional JSON event-calendar file for refdata service startup validation; falls back to `SMART_MONEY_EVENT_CALENDAR_PATH` when unset. |
+| `REFDATA_EVENT_CALENDAR_PROVIDER` | empty | Set to `alpha_vantage` to have refdata refresh the calendar cache from Alpha Vantage. |
+| `ALPHA_VANTAGE_API_KEY` | empty | Alpha Vantage key used when `REFDATA_EVENT_CALENDAR_PROVIDER=alpha_vantage`. |
+| `ALPHA_VANTAGE_EARNINGS_HORIZON` | `3month` | Alpha Vantage earnings horizon: `3month`, `6month`, or `12month`. |
+| `ALPHA_VANTAGE_EARNINGS_SYMBOL` | empty | Optional single-symbol Alpha Vantage earnings query; empty fetches the full scheduled earnings list. |
+| `REFDATA_EVENT_CALENDAR_REFRESH_MS` | `86400000` | Refdata refresh cadence for provider-backed event-calendar cache writes. |
-### API, live cache, and web client
+Event-calendar rows may use `symbol`, `underlying`, or `underlying_id`; `event_date`, `event_time`, or `event_ts`; and `announced_ts`, `available_ts`, `as_of_ts`, or `created_ts`. Compute only uses events already available at the packet timestamp, so missing or unavailable rows leave event-alignment features as neutral `null` values.
+
+### Candle service configuration
+
+| Variable | Default | What it controls |
+| --- | --- | --- |
+| `CANDLE_INTERVALS_MS` | `60000,300000` | Comma-separated candle intervals generated from equity prints. |
+| `CANDLE_MAX_LATE_MS` | `0` | Allowed lateness for out-of-order prints before candle rejection/roll policy applies. |
+| `CANDLE_CACHE_LIMIT` | `2000` | Max cached candles per `(underlying, interval)` in Redis (`0` disables cache). |
+| `CANDLE_DELIVER_POLICY` | `new` | Consumer start policy for candle service (`new`, `all`, `last`, `last_per_subject`). |
+| `CANDLE_CONSUMER_RESET` | `false` | If true, resets candle durable consumer position on startup. |
+
+### API + live cache configuration
| Variable | Default | What it controls |
| --- | --- | --- |
| `API_PORT` | `4000` | API service listen port. |
-| `REST_DEFAULT_LIMIT` | `200` | Default REST record count. |
-| `API_DELIVER_POLICY` | `new` | JetStream consumer start policy used by API live subscribers. |
-| `API_CONSUMER_RESET` | `false` | Resets/recreates API live durable consumers on startup when true. |
-| `LIVE_LIMIT_DEFAULT` | `1000` | Optional generic live cache depth default. |
-| `LIVE_LIMIT_FLOW` | `500` | Live cache depth for flow packet events unless overridden. |
-| `LIVE_LIMIT_SMART_MONEY` | `300` | Live cache depth for smart-money events unless overridden. |
-| `LIVE_LIMIT_OPTIONS` | `1000` | Live cache depth for options channel unless overridden. |
-| `LIVE_LIMIT_ALERTS` | `300` | Live cache depth for alerts channel unless overridden. |
-| `LIVE_LIMIT_NEWS` | `100` | Live cache depth for news channel unless overridden. |
-| `NEXT_PUBLIC_API_URL` | auto-detected in browser, `http://127.0.0.1:4000` fallback | Explicit base URL for API/WS calls from the web app. |
-| `NEXT_PUBLIC_LIVE_HOT_WINDOW` | `600` | Max hot-window items retained for non-options live streams in UI state. |
-| `NEXT_PUBLIC_LIVE_HOT_WINDOW_OPTIONS` | `1200` | Dedicated max hot-window items retained for options prints. |
-| `NEXT_PUBLIC_NBBO_MAX_AGE_MS` | `1000` | Frontend NBBO staleness threshold. |
-| `NEXT_PUBLIC_FLOW_FILTER_PRESET` | `smart-money` | Default flow filter preset: `smart-money`, `balanced`, or `all`. |
+| `REST_DEFAULT_LIMIT` | `200` | Default record count when a REST endpoint omits `limit`. |
+| `API_DELIVER_POLICY` | `new` | JetStream consumer start policy used by API live subscribers (`new`, `all`, `last`, `last_per_subject`). |
+| `API_CONSUMER_RESET` | `false` | If true, API resets/recreates its live durable consumers on startup. |
+| `LIVE_LIMIT_OPTIONS` | `10000` | In-memory/Redis live cache depth for options channel (clamped `1..100000`). |
+| `LIVE_LIMIT_NBBO` | `10000` | Live cache depth for options NBBO channel (clamped `1..100000`). |
+| `LIVE_LIMIT_EQUITIES` | `10000` | Live cache depth for equities channel (clamped `1..100000`). |
+| `LIVE_LIMIT_EQUITY_QUOTES` | `10000` | Live cache depth for equity quotes channel (clamped `1..100000`). |
+| `LIVE_LIMIT_EQUITY_JOINS` | `10000` | Live cache depth for equity join channel (clamped `1..100000`). |
+| `LIVE_LIMIT_FLOW` | `10000` | Live cache depth for flow packet channel (clamped `1..100000`). |
+| `LIVE_LIMIT_CLASSIFIER_HITS` | `10000` | Live cache depth for classifier hits channel (clamped `1..100000`). |
+| `LIVE_LIMIT_ALERTS` | `10000` | Live cache depth for alerts channel (clamped `1..100000`). |
+| `LIVE_LIMIT_INFERRED_DARK` | `10000` | Live cache depth for inferred dark channel (clamped `1..100000`). |
+
+### Web client configuration (`NEXT_PUBLIC_*`)
+
+| Variable | Default | What it controls |
+| --- | --- | --- |
+| `NEXT_PUBLIC_API_URL` | auto-detected (`window.location.origin` in browser; `http://127.0.0.1:4000` fallback) | Explicit base URL for API/WS calls from the web app. |
+| `NEXT_PUBLIC_LIVE_HOT_WINDOW` | `2000` | Max hot-window items retained for non-options live streams in UI state (`100..100000`). |
+| `NEXT_PUBLIC_LIVE_HOT_WINDOW_OPTIONS` | `25000` | Dedicated max hot-window items retained for options prints (`100..100000`). |
+| `NEXT_PUBLIC_NBBO_MAX_AGE_MS` | `1000` | Frontend NBBO staleness threshold used for UI status/placement logic. |
+| `NEXT_PUBLIC_LIVE_EQUITIES_SILENT_WARNING_MS` | `25000` | Delay before warning when equities stream is quiet (`5000..300000`). |
+| `NEXT_PUBLIC_PINNED_EVIDENCE_TTL_MS` | `1200000` | TTL for pinned evidence objects in UI (`60000..7200000`). |
+| `NEXT_PUBLIC_PINNED_EVIDENCE_MAX_ITEMS` | `4000` | Maximum pinned evidence cache size in UI (`100..50000`). |
+| `NEXT_PUBLIC_FLOW_FILTER_PRESET` | `smart-money` | Default flow filter preset applied on page load (`smart-money`, `balanced`, `all`). |
### Replay and testing controls
| Variable | Default | What it controls |
| --- | --- | --- |
-| `REPLAY_ENABLED` | `false` | Starts replay service in `bun run dev` when truthy. |
-| `REPLAY_STREAMS` | `options,nbbo,equities,equity-quotes` | Replay stream selection. |
-| `REPLAY_START_TS` | `0` | Replay lower-bound timestamp. |
-| `REPLAY_END_TS` | `0` | Replay upper-bound timestamp. |
-| `REPLAY_SPEED` | `1` | Replay speed multiplier. |
-| `REPLAY_BATCH_SIZE` | `200` | Batch fetch size per stream. |
-| `REPLAY_LOG_EVERY` | `1000` | Progress log interval. |
+| `REPLAY_ENABLED` | `false` | Dev-script toggle: starts replay service in `bun run dev` when truthy. |
+| `REPLAY_STREAMS` | `options,nbbo,equities,equity-quotes` | Replay stream selection (`all` or comma list of supported aliases). |
+| `REPLAY_START_TS` | `0` | Replay lower-bound timestamp; `0` means from earliest stored data. |
+| `REPLAY_END_TS` | `0` | Replay upper-bound timestamp; `0` means no explicit end bound. |
+| `REPLAY_SPEED` | `1` | Replay speed multiplier relative to original event timing. |
+| `REPLAY_BATCH_SIZE` | `200` | Batch fetch size per replay stream pull. |
+| `REPLAY_LOG_EVERY` | `1000` | Progress log interval (emitted event count). |
| `TESTING_MODE` | `false` | Enables ingest publish throttling for deterministic/lower-volume test runs. |
| `TESTING_THROTTLE_MS` | `200` | Minimum delay between emitted events while `TESTING_MODE=true`. |
## Quick Notes
-- Python dependencies are required only for IBKR/Databento sidecars: `services/ingest-options/py/requirements.txt`.
+- Python dependencies are required only for IBKR/Databento sidecars (`services/ingest-options/py/requirements.txt`).
- Candle construction is server-side; the client consumes prebuilt OHLC events.
-- Option prints persist as enriched raw rows and can be queried as `view=signal` or `view=raw`.
-- The default Tape page options/packets posture is stock-only, hides `B` / `BB`, keeps calls and puts visible, and applies in-memory min-notional controls immediately.
-- Live retention uses ClickHouse for durable server history, Redis for bounded hot cache, and browser state for rendering windows/preferences.
-- Alert and drawer evidence is pinned and hydrated by id/trace so details remain inspectable after hot-window eviction.
-- Firehose readiness keeps raw ingest for storage/replay, routes default compute/UI through filtered signals, and keeps subscription contracts ready for server-side selective delivery.
+- Option prints now persist as enriched raw rows and can be queried as either:
+ - `view=signal` — default live/UI path and compute input.
+ - `view=raw` — audit/debug path that preserves every stored print.
+- The default Tape page options/packets posture is now stock-only, hides `B` / `BB`, keeps calls and puts visible, and applies in-memory min-notional controls immediately.
+- Live retention uses a two-tier model:
+ - ClickHouse is durable server history; Redis is a bounded hot cache per live generic channel.
+ - `LIVE_LIMIT_*` controls initial snapshot/hot-cache depth, not total persisted history.
+ - Browser state is only a rendering window and UI preferences, not a market-data database.
+ - Devices connected to the same API hydrate from the same server-seen history.
+ - UI keeps a bounded hot window for rendering performance around the signal view rather than raw noise.
+ - Options prints can use a deeper dedicated cap via `NEXT_PUBLIC_LIVE_HOT_WINDOW_OPTIONS` without raising every other feed.
+ - Alert/drawer evidence is pinned and hydrated by id/trace so details remain inspectable after hot-window eviction.
+- Firehose-readiness strategy:
+ - preserve raw ingest for storage/replay,
+ - feed compute and default live UI from the filtered signal path,
+ - add filterable live subscription contracts now so selective delivery can move server-side without reshaping the protocol later.
- This repository is for personal, non-redistributed usage.
## Useful Examples
diff --git a/apps/web/app/globals.css b/apps/web/app/globals.css
index 13c0ff4..d9a291a 100644
--- a/apps/web/app/globals.css
+++ b/apps/web/app/globals.css
@@ -727,17 +727,12 @@ h3 {
grid-template-columns: repeat(3, minmax(0, 1fr));
}
-.page-grid-news {
- grid-template-columns: minmax(0, 1fr);
-}
-
.page-grid-settings {
grid-template-columns: minmax(0, 1.25fr) minmax(320px, 0.9fr);
align-items: start;
}
.page-grid-home > :nth-child(3),
-.page-grid-home > :nth-child(4),
.page-grid-tape > :nth-child(1),
.page-grid-replay > :nth-child(1) {
grid-column: 1 / -1;
@@ -966,7 +961,6 @@ h3 {
}
.page-grid-home > :nth-child(3),
-.page-grid-home > :nth-child(4),
.page-grid-replay > :not(:first-child) {
height: clamp(430px, 58vh, 760px);
}
@@ -2100,72 +2094,6 @@ h3 {
gap: 10px;
}
-.terminal-link-button {
- text-decoration: none;
-}
-
-.news-list {
- display: flex;
- flex-direction: column;
- gap: 10px;
-}
-
-.news-row {
- width: 100%;
- display: flex;
- flex-direction: column;
- gap: 8px;
- padding: 14px 16px;
- border: 1px solid var(--border);
- border-radius: 12px;
- background: oklch(0.18 0.012 250 / 0.6);
- color: var(--text);
- text-align: left;
- transition: border-color 150ms ease, background 150ms ease;
-}
-
-.news-row:hover {
- border-color: var(--accent-soft);
- background: oklch(0.2 0.015 250 / 0.75);
-}
-
-.news-row-head,
-.news-row-meta {
- display: flex;
- align-items: center;
- justify-content: space-between;
- gap: 10px;
- flex-wrap: wrap;
-}
-
-.news-row h3 {
- margin: 0;
- font-size: 0.96rem;
- font-weight: 600;
-}
-
-.news-row-time {
- color: var(--text-dim);
- font-family: var(--font-mono), monospace;
- font-size: 0.78rem;
-}
-
-.news-row-meta {
- color: var(--text-dim);
- font-size: 0.78rem;
-}
-
-.news-drawer-body a {
- color: var(--accent);
-}
-
-.news-drawer-body p,
-.news-drawer-body ul,
-.news-drawer-body ol,
-.news-drawer-body blockquote {
- margin: 0 0 12px;
-}
-
.synthetic-status-grid strong,
.synthetic-hit-row strong {
font-family: var(--font-mono), monospace;
@@ -2384,7 +2312,6 @@ h3 {
}
.page-grid-home > :nth-child(3),
- .page-grid-home > :nth-child(4),
.page-grid-tape > :nth-child(1),
.page-grid-replay > :nth-child(1) {
grid-column: auto;
@@ -2394,7 +2321,6 @@ h3 {
.page-grid-home > :nth-child(1),
.page-grid-home > :nth-child(2),
.page-grid-home > :nth-child(3),
- .page-grid-home > :nth-child(4),
.page-grid-signals > .terminal-pane,
.page-grid-replay > :not(:first-child),
.page-grid-tape > :first-child,
diff --git a/apps/web/app/news/page.tsx b/apps/web/app/news/page.tsx
deleted file mode 100644
index 7e06aa8..0000000
--- a/apps/web/app/news/page.tsx
+++ /dev/null
@@ -1,7 +0,0 @@
-import { NewsRoute } from "../terminal";
-
-export const dynamic = "force-dynamic";
-
-export default function Page() {
- return ;
-}
diff --git a/apps/web/app/terminal.test.ts b/apps/web/app/terminal.test.ts
index f039c95..9297f1b 100644
--- a/apps/web/app/terminal.test.ts
+++ b/apps/web/app/terminal.test.ts
@@ -43,8 +43,6 @@ import {
shouldClearOptionFocusSeed,
smartMoneyProfileLabel,
smartMoneyToneForProfile,
- getAlertFlowPacketRefs,
- resolveAlertFlowPacket,
statusLabel,
toggleFilterValue
} from "./terminal";
@@ -135,33 +133,6 @@ describe("alert context hydration helpers", () => {
expect(evidence.prints.get("print:1")?.execution_nbbo_bid).toBe(1.2);
expect(evidence.prints.get("print:1")?.execution_underlying_spot).toBe(450.05);
});
-
- it("finds flow-packet refs even when they are not first in alert evidence", () => {
- const alert = makeAlert({
- evidence_refs: ["smartmoney:single_leg_event:flowpacket:1", "flowpacket:1", "print:1"]
- });
-
- expect(getAlertFlowPacketRefs(alert)).toEqual(["flowpacket:1"]);
- });
-
- it("resolves the primary alert flow packet from hydrated historical context", () => {
- const packet = {
- trace_id: "flowpacket:1",
- id: "flowpacket:1",
- members: ["print:1"],
- source_ts: 1,
- ingest_ts: 2,
- seq: 1,
- features: {},
- join_quality: {}
- } as any;
- const alert = makeAlert({
- evidence_refs: ["smartmoney:single_leg_event:flowpacket:1", "flowpacket:1", "print:1"]
- });
- const packets = new Map([[packet.id, packet]]);
-
- expect(resolveAlertFlowPacket(alert, packets)).toBe(packet);
- });
});
describe("live manifest", () => {
@@ -276,15 +247,6 @@ describe("live manifest", () => {
]);
});
- it("includes news subscriptions on home and /news", () => {
- expect(getLiveManifest("/", "SPY", 60000, buildDefaultFlowFilters()).map((subscription) => subscription.channel)).toContain(
- "news"
- );
- expect(getLiveManifest("/news", "SPY", 60000, buildDefaultFlowFilters()).map((subscription) => subscription.channel)).toEqual([
- "news"
- ]);
- });
-
it("scopes /charts subscriptions to chart channels only", () => {
const channels = getLiveManifest("/charts", "SPY", 60000, buildDefaultFlowFilters()).map(
(subscription) => subscription.channel
@@ -470,13 +432,6 @@ describe("route feature map", () => {
expect(features.alerts).toBe(false);
});
- it("maps /news to the dedicated news pane", () => {
- const features = getRouteFeatures("/news");
- expect(features.news).toBe(true);
- expect(features.showNewsPane).toBe(true);
- expect(features.showAlertsPane).toBe(false);
- });
-
it("maps /replay to replay panes and dependencies", () => {
const features = getRouteFeatures("/replay");
expect(features.showReplayConsole).toBe(true);
@@ -528,7 +483,6 @@ describe("terminal navigation", () => {
expect(NAV_ITEMS).toEqual([
{ href: "/", label: "Home" },
{ href: "/tape", label: "Tape" },
- { href: "/news", label: "News" },
{ href: "/signals", label: "Signals" },
{ href: "/charts", label: "Charts" },
{ href: "/replay", label: "Replay" },
diff --git a/apps/web/app/terminal.tsx b/apps/web/app/terminal.tsx
index c26a486..b7bfba1 100644
--- a/apps/web/app/terminal.tsx
+++ b/apps/web/app/terminal.tsx
@@ -33,7 +33,6 @@ import type {
LiveServerMessage,
LiveHotChannelHealthMap,
LiveSubscription,
- NewsStory,
OptionFlowFilters,
OptionFlowView,
OptionNbboSide,
@@ -166,7 +165,6 @@ type RouteFeatures = {
nbbo: boolean;
equities: boolean;
flow: boolean;
- news: boolean;
alerts: boolean;
smartMoney: boolean;
classifierHits: boolean;
@@ -177,7 +175,6 @@ type RouteFeatures = {
showOptionsPane: boolean;
showEquitiesPane: boolean;
showFlowPane: boolean;
- showNewsPane: boolean;
showAlertsPane: boolean;
showClassifierPane: boolean;
showDarkPane: boolean;
@@ -197,7 +194,6 @@ export const getRouteFeatures = (pathname: string): RouteFeatures => {
const includeEquitiesFallback = shouldIncludeEquitiesForDarkUnderlyingFallback();
const normalizedPath =
pathname === "/tape" ||
- pathname === "/news" ||
pathname === "/signals" ||
pathname === "/charts" ||
pathname === "/replay" ||
@@ -212,7 +208,6 @@ export const getRouteFeatures = (pathname: string): RouteFeatures => {
nbbo: true,
equities: true,
flow: true,
- news: false,
alerts: false,
smartMoney: false,
classifierHits: false,
@@ -223,7 +218,6 @@ export const getRouteFeatures = (pathname: string): RouteFeatures => {
showOptionsPane: true,
showEquitiesPane: true,
showFlowPane: true,
- showNewsPane: false,
showAlertsPane: false,
showClassifierPane: false,
showDarkPane: false,
@@ -234,41 +228,12 @@ export const getRouteFeatures = (pathname: string): RouteFeatures => {
needsAlertEvidencePrefetch: false,
needsDarkUnderlying: false
};
- case "/news":
- return {
- options: false,
- nbbo: false,
- equities: false,
- flow: false,
- news: true,
- alerts: false,
- smartMoney: false,
- classifierHits: false,
- inferredDark: false,
- equityJoins: false,
- equityCandles: false,
- equityOverlay: false,
- showOptionsPane: false,
- showEquitiesPane: false,
- showFlowPane: false,
- showNewsPane: true,
- showAlertsPane: false,
- showClassifierPane: false,
- showDarkPane: false,
- showChartPane: false,
- showFocusPane: false,
- showReplayConsole: false,
- needsClassifierDecor: false,
- needsAlertEvidencePrefetch: false,
- needsDarkUnderlying: false
- };
case "/signals":
return {
options: false,
nbbo: false,
equities: includeEquitiesFallback,
flow: false,
- news: false,
alerts: true,
smartMoney: true,
classifierHits: true,
@@ -279,7 +244,6 @@ export const getRouteFeatures = (pathname: string): RouteFeatures => {
showOptionsPane: false,
showEquitiesPane: false,
showFlowPane: false,
- showNewsPane: false,
showAlertsPane: true,
showClassifierPane: true,
showDarkPane: true,
@@ -296,7 +260,6 @@ export const getRouteFeatures = (pathname: string): RouteFeatures => {
nbbo: false,
equities: includeEquitiesFallback,
flow: false,
- news: false,
alerts: false,
smartMoney: true,
classifierHits: false,
@@ -307,7 +270,6 @@ export const getRouteFeatures = (pathname: string): RouteFeatures => {
showOptionsPane: false,
showEquitiesPane: false,
showFlowPane: false,
- showNewsPane: false,
showAlertsPane: false,
showClassifierPane: false,
showDarkPane: false,
@@ -324,7 +286,6 @@ export const getRouteFeatures = (pathname: string): RouteFeatures => {
nbbo: false,
equities: false,
flow: false,
- news: false,
alerts: false,
smartMoney: false,
classifierHits: false,
@@ -335,7 +296,6 @@ export const getRouteFeatures = (pathname: string): RouteFeatures => {
showOptionsPane: true,
showEquitiesPane: false,
showFlowPane: true,
- showNewsPane: false,
showAlertsPane: true,
showClassifierPane: false,
showDarkPane: false,
@@ -352,7 +312,6 @@ export const getRouteFeatures = (pathname: string): RouteFeatures => {
nbbo: false,
equities: false,
flow: false,
- news: false,
alerts: false,
smartMoney: false,
classifierHits: false,
@@ -363,7 +322,6 @@ export const getRouteFeatures = (pathname: string): RouteFeatures => {
showOptionsPane: false,
showEquitiesPane: false,
showFlowPane: false,
- showNewsPane: false,
showAlertsPane: false,
showClassifierPane: false,
showDarkPane: false,
@@ -381,7 +339,6 @@ export const getRouteFeatures = (pathname: string): RouteFeatures => {
nbbo: false,
equities: true,
flow: false,
- news: true,
alerts: true,
smartMoney: true,
classifierHits: false,
@@ -392,7 +349,6 @@ export const getRouteFeatures = (pathname: string): RouteFeatures => {
showOptionsPane: false,
showEquitiesPane: true,
showFlowPane: false,
- showNewsPane: true,
showAlertsPane: true,
showClassifierPane: false,
showDarkPane: false,
@@ -410,7 +366,6 @@ const EMPTY_ALERT_EVENTS: AlertEvent[] = [];
const EMPTY_CLASSIFIER_HIT_EVENTS: ClassifierHitEvent[] = [];
const EMPTY_SMART_MONEY_EVENTS: SmartMoneyEvent[] = [];
const EMPTY_INFERRED_DARK_EVENTS: InferredDarkEvent[] = [];
-const EMPTY_NEWS_STORIES: NewsStory[] = [];
type CandlestickSeries = ReturnType;
@@ -1273,44 +1228,6 @@ const formatDateTime = (ts: number): string => {
return `${date.toLocaleDateString()} ${date.toLocaleTimeString()}`;
};
-const isSameLocalDay = (left: number, right: number): boolean => {
- const a = new Date(left);
- const b = new Date(right);
- return (
- a.getFullYear() === b.getFullYear() &&
- a.getMonth() === b.getMonth() &&
- a.getDate() === b.getDate()
- );
-};
-
-export const formatNewsTimestamp = (ts: number, now = Date.now()): string => {
- const date = new Date(ts);
- return isSameLocalDay(ts, now)
- ? date.toLocaleTimeString([], { hour: "numeric", minute: "2-digit" })
- : date.toLocaleString([], { month: "short", day: "numeric", hour: "numeric", minute: "2-digit" });
-};
-
-const sanitizeNewsHtml = (value: string): { html: string; fallbackText: string; sanitized: boolean } => {
- const fallbackText = value
- .replace(/
-