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:
When you click Compile — block structure, legacy detection, timeframe extraction.
Before each tick — trade hours, daily limits, kline freshness, settings validity.
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.
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.
| Setting | Behavior |
|---|---|
| No hours configured | Runs 24/7. No time gate is applied. |
| Hours window set, overnight enabled | Runs during the window which may cross midnight UTC. E.g. 22:00–06:00 is valid. |
| Disable weekends checked | An 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:
| Scenario | Broker response | Logged as |
|---|---|---|
| BUY while position already open | Order silently rejected | TRADE_EXECUTED with status: REJECTED, reason: "Position already open" |
| SELL with no open position | Order silently rejected | TRADE_EXECUTED with status: REJECTED, reason: "No open position" |
| SELL amount rounds to zero units | Order silently rejected | TRADE_EXECUTED with status: REJECTED, reason: "Zero quantity" |
| Live mode: wallet credential error | Order skipped, mode falls back to paper | Error log with decryption failure message |
| Live mode: Binance API error | Order fails, error is logged | Error 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.
| Pattern | What happens | Likely intent vs. reality |
|---|---|---|
| BUY with no SELL condition | Strategy 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 condition | SELL 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 exit | Valid. 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 tick | First 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.
The most common sandbox errors:
| Error | Cause | Fix |
|---|---|---|
Execution timeout | Strategy 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 NaN | An 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. |