/Documentation

Building

Strategy Validation

Last updated April 20, 2026

Norena validates your strategy at multiple stages — at compile time, before each run, and during broker execution. Understanding these checks helps you build strategies that are logically sound and won't produce unexpected behavior in live execution.

Overview

Validation in Norena happens in three layers:

1
Compile time

When you click Compile — block structure, legacy detection, timeframe extraction.

2
Pre-run guards

Before each tick — trade hours, daily limits, kline freshness, settings validity.

3
Broker guards

At order placement — duplicate position, no-position sell, minimum order size.

None of these layers crash your strategy silently. Every check that fails produces a structured log entry explaining exactly what was rejected and why. The Logs tab is your first stop when a strategy is not trading as expected.

Compile-Time Checks

Compilation happens in the browser the moment you click Compile. The Blockly code generator processes your workspace and produces JavaScript. If the workspace contains issues, they are surfaced at this stage — before any code is saved or run.

Legacy Block Detection

Blocks that have been superseded by improved versions are flagged as legacy. When the generator encounters a legacy block, it injects a throw statement into the compiled JS targeting that block's position in the code. This means:

  • The compilation itself succeeds — the JS is stored.
  • At runtime, the strategy immediately errors when the legacy block is encountered.
  • The error log identifies the exact block type that needs to be replaced.
⚠️
If you see a "legacy block" error in Logs, go to the Strategy Builder, find the highlighted block, delete it, and replace it with the current version from the toolbox. Then recompile.

Empty Workspace

Compiling an empty workspace produces valid but empty JavaScript. The runner detects this at runtime (no code to execute) and logs a "No code compiled" info entry. No trade is attempted and the run is marked as completed normally — it just does nothing.

The same applies to a workspace that has blocks but no reachable HP.buy() orHP.sell() calls — the strategy runs, evaluates conditions, and logs the evaluation results with "No trade conditions met."

Runtime Guards

Before strategy code executes on each tick, the runner evaluates a set of guards. If any guard fails, the tick is skipped and the reason is logged. These guards exist to prevent obviously invalid or risky execution states from reaching the strategy code.

Trade Hours Gate

If you have configured a trade hours window (e.g. 09:00–17:00 UTC), the runner checks the current UTC time before executing. Ticks outside the window are skipped with a "Outside trade hours" info log. The condition is logged as a condition row in the evaluation table so it appears in strategy analytics.

SettingBehavior
No hours configuredRuns 24/7. No time gate is applied.
Hours window set, overnight enabledRuns during the window which may cross midnight UTC. E.g. 22:00–06:00 is valid.
Disable weekends checkedAn additional day-of-week check runs. Saturday and Sunday ticks are skipped entirely.

Max Daily Trades

If Max Trades Per Day is set, the runner counts the number of BUY orders placed since 00:00 UTC for the current project + symbol. If the limit is already reached, the tick is skipped with a "Daily trade limit reached" warning. SELL orders do not count toward this limit — exits are never blocked by the daily cap.

Kline Freshness Check

Before executing strategy code, the runner verifies that the latest 1m candle in the kline cache is no older than 3 minutes. If the data is stale (e.g. due to a brief Binance API outage), the tick is skipped with a "Stale klines" warning. This prevents signals from being generated on outdated market data.

If the kline series for a symbol has not been bootstrapped yet (e.g. a newly added symbol), the tick is also skipped until the initial data fetch completes.

Broker-Level Guards

After strategy code runs, any resulting HP.buy() or HP.sell()calls are routed through the broker, which applies its own validation:

ScenarioBroker responseLogged as
BUY while position already openOrder silently rejectedTRADE_EXECUTED with status: REJECTED, reason: "Position already open"
SELL with no open positionOrder silently rejectedTRADE_EXECUTED with status: REJECTED, reason: "No open position"
SELL amount rounds to zero unitsOrder silently rejectedTRADE_EXECUTED with status: REJECTED, reason: "Zero quantity"
Live mode: wallet credential errorOrder skipped, mode falls back to paperError log with decryption failure message
Live mode: Binance API errorOrder fails, error is loggedError log with Binance error code and message

Rejected orders still produce a TRADE_TRIGGER log — so you can see what conditions were met even for trades that were not executed. This helps diagnose logic where conditions are evaluating correctly but the broker is preventing execution.

Common Logic Warnings

These are patterns in strategy logic that are technically valid but often indicate a mistake. The runner won't stop them — but you should understand their behavior.

PatternWhat happensLikely intent vs. reality
BUY with no SELL conditionStrategy enters positions and never exits. Position stays open indefinitely.You may have intended to always hold, or may have forgotten the exit branch.
SELL with no BUY conditionSELL is always rejected (no position to close). Logs "No open position" every tick.Exit-only strategies have no effect unless a position was manually opened.
Take Profit and Stop Loss only, no other exitValid. TP and SL are evaluated every tick. Only fires when price crosses the threshold.Works as intended, but the strategy will hold indefinitely if TP/SL is never reached.
Condition always true (e.g. RSI > 0)The condition passes every tick. Strategy will BUY on every tick that a position isn't open.If the intent was "always buy", this works. If the intent was to filter signals, the condition is wrong.
Condition always false (e.g. RSI > 200)The condition never passes. The branch body never executes. No trades are placed.The Condition Removal Impact analytics module will flag this condition as having 0% truth rate.
Multiple BUY blocks in the same tickFirst BUY executes. Second BUY is rejected by broker ("Position already open").Only one position is possible per symbol. Multiple BUY branches should be inside if / else if structure.

Sandbox Errors

If strategy code throws an unhandled exception inside the sandbox (e.g. a divide-by-zero, a null dereference, or a timeout), the runner catches the error and logs it as anERROR-level entry with the exception message and stack trace (where available).

The run is marked as error for that tick, but the project continues running on subsequent ticks. A single error tick does not stop a running strategy.

💡
Turn on Advanced Logging in Settings to see more detailed evaluation output on every tick, including which conditions are passing and failing even when no trade fires. This is the fastest way to diagnose a strategy that runs without errors but never trades.

The most common sandbox errors:

ErrorCauseFix
Execution timeoutStrategy took more than 5 seconds to evaluate. Usually caused by extremely large lookback periods or complex nested loops in custom math blocks.Reduce lookback periods. Simplify logic. Remove unnecessary complexity.
Legacy block encountered: [BlockType]A block that was previously compiled is now flagged as deprecated.Replace the named block in the Strategy Builder and recompile.
Indicator returned NaNAn indicator was computed on too few bars (warm-up period not complete) or on an invalid input.This resolves automatically after enough bars have been processed. If it persists, check the indicator parameters.