/Documentation

Platform

Platform Architecture

Last updated April 20, 2026

Norena is a fullstack TypeScript platform. The frontend handles the strategy builder, backtest engine, and UI. A separate backend runner process handles live execution. A shared Supabase database ties everything together.

System Overview

ComponentTechnologyResponsibility
FrontendNext.js 14 (App Router), React, TypeScriptUI, strategy builder, backtest engine (runs in-browser), user authentication
Execution RunnerNode.js, TypeScript (hornpub-runner)Live strategy execution, kline data management, order placement, structured logging
DatabaseSupabase (PostgreSQL + RLS + Auth)Persistent storage for all user data, projects, trades, logs, and wallets
ExchangeBinance REST APIMarket data (klines) and order execution for live mode

The frontend and runner are fully decoupled. The runner never calls the frontend and the frontend never calls the runner directly — they communicate only through the shared database. This means the runner can be deployed independently, scaled horizontally, or replaced without touching the frontend.

Frontend

The Next.js frontend handles everything the user sees and interacts with:

🧱
Strategy Builder

Google Blockly workspace. Custom block definitions, custom JavaScript generators, live compilation to JS with timeframe manifest injection.

📊
Backtest Engine

Runs entirely client-side — no server round-trip. Fetches Binance klines directly, runs strategy in a Function() sandbox, computes 8 analytics modules.

📈
Live Dashboard

Real-time view of project logs, positions, and trades via Supabase Realtime subscriptions.

âš™ī¸
Settings & Wallets

Project configuration UI. Wallet management (encrypted credentials). All settings persisted to the database and read by the runner on each tick.

The frontend uses Next.js server components for data-fetching pages (authentication, initial project state) and client components for interactive elements (the Blockly editor, backtest charts, live log stream). Authentication is handled by Supabase Auth with server-side session management.

Execution Runner

The hornpub-runner is a standalone Node.js process — not part of the Next.js app. It runs independently and communicates with Supabase as its only external interface (apart from Binance for klines and orders).

Key responsibilities:

  • Project scheduling — claims due projects via a database RPC function on a 2-second poll loop.
  • Kline management — maintains an in-memory KlineCache fed by InMemoryKlineManager, which fetches and refreshes data from Binance independently of project execution.
  • Strategy execution — runs compiled JS per-project, per-symbol in a vm sandbox with the full strategy API surface.
  • Broker abstraction — PaperBroker (simulated fills) and LiveBroker (Binance MARKET orders) share the same interface, making mode-switching transparent to strategy code.
  • Structured logging — writes TRADE_TRIGGER and TRADE_EXECUTED log pairs for every trade, with full condition rows and slippage data.
â„šī¸
The runner and backtest engine share the same indicator library.createIndicators() is used in both. This ensures that backtest results accurately reflect live execution behavior — same calculations, same data sources, same edge cases.

Database

Norena uses Supabase (PostgreSQL) as its primary datastore. Supabase provides the database, row-level security policies, authentication, and realtime subscriptions — all in one managed service.

Row-level security is enforced at the database level so that every query — whether from the frontend's user session or the runner's service key — respects data ownership. Users can only access their own rows; the runner always scopes queries by owner_id.

Kline Data System

Live strategy execution uses an in-memory kline cache that is populated and maintained by the InMemoryKlineManager. This cache is completely separate from Supabase — market data never touches the database.

ComponentRole
KlineCacheIn-memory store. Holds the last N candles per symbol + interval. Provides O(1) series access by exchange/symbol/timeframe key.
InMemoryKlineManagerBackground process that queries Supabase for active project symbols, then fetches and continuously refreshes those series from Binance. Runs independently of project execution.
Binance REST APISource of all kline data. Fetched directly — no intermediate caching layer.

The manager discovers which symbols and timeframes to maintain by scanning active projects for their configured symbols and their compiled JS's timeframe manifests. When a new project goes live, its required series are automatically added to the refresh schedule.

In the backtest engine (frontend), klines are fetched on-demand directly from Binance when the user clicks Run Backtest. No cache is used — every backtest starts with a fresh fetch.

Scheduling

Project scheduling is managed through the database, not a cron job or external scheduler. Each project has a next_run_at timestamp. A PostgreSQL function (claim_due_projects) atomically selects and claims projects wherenext_run_at <= now() and updates their timestamp tonow() + interval_seconds in the same transaction.

This design provides several guarantees:

  • Exactly-once claiming — no two runner instances can claim the same project simultaneously.
  • No external scheduler dependency — the runner and database together are sufficient; no cron or queue service is needed.
  • Catchup prevention — if the runner is briefly offline, projects will be claimed once when it restarts, not for every missed interval.

Data Model Overview

The core data model is intentionally small. These are the primary tables:

TableDescriptionKey fields
usersManaged by Supabase Auth. Contains email, created_at, and auth metadata.id (UUID), email
projectsEach strategy project. Stores compiled JS, symbols, settings, and scheduling state.id, owner_id, generated_js, symbols, settings_json, interval_seconds, next_run_at, status
project_runsOne row per execution tick. Tracks run status, timing, and error summaries.id, project_id, status, mode, started_at, finished_at
project_positionsOpen and closed positions per project + symbol. One open position max per pair.id, project_id, symbol, status, entry_price, qty, close_price
project_tradesEvery buy and sell event. Linked to a position and to trigger/executed log rows.id, project_id, side, symbol, fill_price, qty, fee, trigger_log_id, executed_log_id
project_logsAll log entries: info, warn, error, and structured TRADE_TRIGGER/TRADE_EXECUTED entries with detail_json.id, project_id, user_id, level, message, meta, detail_json
walletsEncrypted Binance API credentials per user.id, owner_id, label, api_key_enc, api_secret_enc
notificationsIn-app notifications for trade events, errors, and system messages.id, user_id, type, message, read, created_at

There is no separate strategies table — strategy data (compiled JS, workspace JSON) lives directly on the projects row. This keeps the model simple and queries fast for the common case of reading a project to run it.