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.
| Step | What happens |
|---|---|
| 1. Runner tick | The runner wakes up every 2 seconds and claims any projects that are due. |
| 2. Kline readiness | Skips the symbol if the primary timeframe klines are not yet bootstrapped or are stale (> 3 min old). |
| 3. Conditions checked | System conditions (trade hours, max daily trades) are checked. Strategy code runs in a sandboxed VM. |
| 4. TRADE_TRIGGER logged | When HP.buy/sell is called, the mark price and position state are snapshotted synchronously and written to the log. |
| 5. Order placed | The broker (Paper or Live) executes the order. Paper fills immediately; Live sends a MARKET order to Binance. |
| 6. TRADE_EXECUTED logged | Fill price, quantity, fees, and slippage vs. trigger price are recorded. |
| 7. Trade row updated | Both 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.
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.
Pre-flight checks
Before executing any trade in live mode, the runner performs these checks:
| Check | Failure behavior |
|---|---|
| Wallet credentials present | Falls back to paper mode for the current tick and logs an error. |
| Binance connectivity test | Sends a lightweight connectivity probe before the first trade. Skips the symbol if it fails. |
| Credential decryption | Falls 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:
| Priority | Source | Why |
|---|---|---|
| 1 | 1m closes | Most granular — updated every minute |
| 2 | 5m closes | Fallback if 1m is unavailable |
| 3 | 15m closes | Fallback |
| 4 | 1h closes | Fallback |
| 5 | 4h closes | Last 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.
| Property | Value |
|---|---|
| Data source | Binance public REST API |
| History window | 30 days (configurable via KLINE_RETENTION_DAYS) |
| Max candles in memory | 5,000 per symbol/interval pair (configurable) |
| Refresh interval | Default 60 seconds (configurable via env) |
| Staleness threshold | 3 minutes — runs are skipped if the 1m close is older |
| Bootstrap | Klines 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:
| Field | Type | Description |
|---|---|---|
condition | string | Human label, e.g. "RSI(14)" or "AND" |
value | string | Actual runtime value at decision time |
rule | string | The comparison rule, e.g. "RSI(14) < 30", or "—" for context rows |
result | boolean | null | Runtime boolean result. null = context row (display only) |
grouped | boolean? | true = sub-condition inside an AND/OR group |
groupSummary | "AND" | "OR" | undefined | Present 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:
| Panel | Contents |
|---|---|
| TRADE_TRIGGER | The 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_EXECUTED | Fill 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. |
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.
| Side | Positive slippage means |
|---|---|
| BUY | Filled higher than trigger price (paid more than expected) |
| SELL | Filled 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.