/Documentation

Execution

Execution & Data

Last updated April 20, 2026

How Norena executes orders, manages market data, and tracks every trade with structured logs.

Execution Overview

Every project on Norena has an execution mode — either Paper or Live — configured in its Settings tab. The mode determines where orders go: a simulated paper ledger or a real Binance account via the exchange API.

Regardless of mode, the execution flow is the same: the runner evaluates the strategy on a 2-second loop, the strategy calls HP.buy() or HP.sell(), and the broker fills the order. Two structured log entries are written for every trade — aTRADE_TRIGGER before the order is placed and a TRADE_EXECUTEDafter it is filled.

StepWhat happens
1. Runner tickThe runner wakes up every 2 seconds and claims any projects that are due.
2. Kline readinessSkips the symbol if the primary timeframe klines are not yet bootstrapped or are stale (> 3 min old).
3. Conditions checkedSystem conditions (trade hours, max daily trades) are checked. Strategy code runs in a sandboxed VM.
4. TRADE_TRIGGER loggedWhen HP.buy/sell is called, the mark price and position state are snapshotted synchronously and written to the log.
5. Order placedThe broker (Paper or Live) executes the order. Paper fills immediately; Live sends a MARKET order to Binance.
6. TRADE_EXECUTED loggedFill price, quantity, fees, and slippage vs. trigger price are recorded.
7. Trade row updatedBoth log IDs (trigger_log_id, executed_log_id) are attached to the trade row in the database.

Paper Mode

Paper mode simulates trading with no real capital at risk. It is the default mode for all new projects. Orders are filled instantly at the current mark price with no slippage and zero fees, providing an ideal baseline to test strategy logic.

Paper positions are tracked in the same project_positions table as live positions, and every trade appears in the Positions tab exactly as it would in live mode. The equity curve and performance metrics in Backtesting also use paper-equivalent fill assumptions.

Paper mode is not a guarantee of live performance. Paper fills at mark price with no fees, while live mode fills at the best available market price and incurs Binance trading fees. Always backtest before going live and run paper for a meaningful period first.

Live Mode

In live mode, every HP.buy() and HP.sell() call places a realMARKET order on Binance using the API credentials stored in the configured wallet. Live mode requires a wallet to be set in the project's Settings tab.

Live mode places real orders with real funds. MARKET orders fill immediately at the best available price and cannot be cancelled. Make sure you have thoroughly tested your strategy in paper mode and backtest before enabling live mode.

Pre-flight checks

Before executing any trade in live mode, the runner performs these checks:

CheckFailure behavior
Wallet credentials presentFalls back to paper mode for the current tick and logs an error.
Binance connectivity testSends a lightweight connectivity probe before the first trade. Skips the symbol if it fails.
Credential decryptionFalls back to paper and logs a security warning if decryption fails.

Wallet management

API credentials are stored encrypted in Settings → Wallets. The runner decrypts them at runtime and zeroes them from memory immediately after use. Credentials are never stored in plaintext in the database.

To configure live mode: create a wallet in Settings → Wallets with your Binance API key and secret, then select it in the project's Settings tab alongside Mode: Live.

Mark Price

The mark price is the current price used for trade fills in paper mode, for evaluating Take Profit and Stop Loss conditions, and for computing Position Value. It is derived from the most recent close in the kline cache using this priority order:

PrioritySourceWhy
11m closesMost granular — updated every minute
25m closesFallback if 1m is unavailable
315m closesFallback
41h closesFallback
54h closesLast resort

The mark price is sampled synchronously at the exact moment HP.buy() orHP.sell() is called inside the strategy. This is decision-time capture — no indicators are re-evaluated. This price is recorded in the TRADE_TRIGGER log and used to compute slippage after the order fills.

Kline Data

The live runner maintains an in-memory kline cache populated by theInMemoryKlineManager. No database is involved in serving klines to the runner — data is fetched directly from Binance and kept in memory.

PropertyValue
Data sourceBinance public REST API
History window30 days (configurable via KLINE_RETENTION_DAYS)
Max candles in memory5,000 per symbol/interval pair (configurable)
Refresh intervalDefault 60 seconds (configurable via env)
Staleness threshold3 minutes — runs are skipped if the 1m close is older
BootstrapKlines are fetched when a symbol becomes active. The first few ticks after activation are skipped until data is ready.

All timeframes needed by any active strategy are loaded — the runner extracts the required intervals from the @hornpub-timeframes manifest comment in the compiled JS. Strategies using multiple timeframes will have all required series available simultaneously.

Trade Log Structure

Every trade produces two structured log entries stored in project_logswith a detail_json payload. The trade row in project_tradeslinks to both via trigger_log_id and executed_log_id.

TRADE_TRIGGER log

Captures the decision-time snapshot: why the trade was triggered, what conditions were evaluated, and the exact state of the world at that moment.

{
  "kind": "trade_trigger",
  "side": "BUY" | "SELL",
  "symbol": "BTCUSDT",
  "interval": "1h",
  "price_at_trigger": 67240.50,
  "trigger": {
    "primary_reason": "BUY signal detected",
    "rows": [
      {
        "condition": "RSI(14)",
        "value": "27.84",
        "rule": "RSI(14) < 30",
        "result": true
      },
      {
        "condition": "EMA(50)",
        "value": "66800.00",
        "rule": "—",
        "result": null
      },
      {
        "condition": "Price",
        "value": "67240.50",
        "rule": "Price > EMA(50)",
        "result": true
      }
    ],
    "context": {
      "position_before": null
    }
  }
}

TRADE_EXECUTED log

Captures the fill result: actual price, quantity, fees, order status, and slippage computed relative to the trigger price.

{
  "kind": "trade_executed",
  "side": "BUY",
  "symbol": "BTCUSDT",
  "order_type": "MARKET",
  "requested_qty": 0.001486,
  "filled_qty": 0.001486,
  "filled_price": 67265.00,
  "status": "FILLED",
  "exchange_order_id": "1234567890",
  "fees": {
    "amount": 0.0000149,
    "asset": "BNB"
  },
  "slippage": {
    "basis": "trigger_price",
    "trigger_price": 67240.50,
    "filled_price": 67265.00,
    "slippage_abs": 24.50,
    "slippage_pct": 0.0364
  },
  "position_after": {
    "qty": 0.001486,
    "entry_price": 67265.00
  }
}

Condition row format

Each entry in the trigger.rows array describes one condition evaluated at decision time:

FieldTypeDescription
conditionstringHuman label, e.g. "RSI(14)" or "AND"
valuestringActual runtime value at decision time
rulestringThe comparison rule, e.g. "RSI(14) < 30", or "—" for context rows
resultboolean | nullRuntime boolean result. null = context row (display only)
groupedboolean?true = sub-condition inside an AND/OR group
groupSummary"AND" | "OR" | undefinedPresent on AND/OR summary rows

Trade Detail Modal

In the project's Positions tab, clicking any trade row opens the Trade Detail Modal — a full breakdown of what happened on that trade.

The modal displays two panels side by side:

PanelContents
TRADE_TRIGGERThe conditions table from the TRADE_TRIGGER log. Shows every boolean condition evaluated when the trade decision was made, with the actual runtime value, the comparison rule, and whether it passed or failed. Condition rows from AND/OR groups are visually grouped with a summary row.
TRADE_EXECUTEDFill details: filled price, quantity, fees (amount + asset), order status, exchange order ID, slippage (absolute and percentage vs. trigger price), and position state after the fill.
The Trade Detail Modal is available for all trades going forward. Older trades that predate the structured logging system will display partial information where available, and no conditions table if the trigger log was not captured.

Slippage Tracking

Slippage measures the difference between the price at the moment your strategy decided to trade (trigger_price) and the actual price the order was filled at (filled_price). It is computed immediately after execution while the trigger price is still in scope.

The sign convention is consistent: positive slippage means the outcome was worse than expected for the trader — bought higher or sold lower than anticipated.

SidePositive slippage means
BUYFilled higher than trigger price (paid more than expected)
SELLFilled lower than trigger price (received less than expected)

In paper mode, fills occur at the mark price (same as the trigger price), so slippage is always zero. Slippage only appears in live mode where MARKET orders fill at real exchange prices.