Skip to content
Single .md

Error catalog ​

Exceptions the engine may raise, with typical cause and fix. Search for the error string in the log and locate it in the table.

Quick table ​

CategoryWhen it firesSeverity
SecurityErrorForbidden import, builtin, or constructHigh - code outside the contract
TimeoutErrorExecution exceeded the time limitMedium - slow logic
MemoryErrorExceeded the memory limitMedium - uncontrolled accumulation
ProtocolErrorProtocol violated (missing action, invalid JSON) or output desync from an over-budget frameHigh - fatal, aborts the run
ProcessErrorEngine execution environment failedHigh - infra
RuntimeErrorClassic Python error (IndexError, ZeroDiv, etc.)Medium - bug in the script
WorkerPoolTimeoutQueue full; request did not acquire a permitLow - retry
UnknownErrorUncategorized errorInvestigate

SecurityError ​

The engine rejected the code before execution. The script did not run.

Cause 1 - Forbidden import ​

SecurityError: Import not allowed: os

Fix: the editor's import allowlist is exactly numpy, pandas, pandas_ta, talib, math, json, datetime. Indicator helpers do not need an import — Indicator, Signal, and ta are pre-injected globals. tesstrade_indicators is not importable from the editor; using it raises this error. See sandbox limits.

Cause 2 - Blocked builtin ​

SecurityError: Forbidden function call: eval
SecurityError: Forbidden function: dir

Fix: banned builtins include open, exec, eval, __import__, input, exit, dir, vars, globals, locals. No substitute; remove from the logic.

The second form (Forbidden function: <name>, no "call") fires on the bare identifier — these builtins are reserved names even as plain variables, because a stored name could alias the builtin. dir = 0 fails exactly like dir() does. Rename the variable: dir → direction, exit → exit_price, input → user_input, file → file_name, vars → variables. Dict keys and strings (c["open"], "exit") and prefixed names (exit_r, dir_up) are fine — only the exact bare identifier is rejected. See sandbox limits.

Cause 3 - Dunder attribute ​

SecurityError: Forbidden attribute: <__dunder__>

Fix: __xxx__ attributes are not available to scripts. Trading logic never needs them — to check a type, use isinstance(x, T).

Cause 4 - Lambda ​

SecurityError: Lambda functions are forbidden

Fix: replace with a def function:

python
# Incorrect
sorted_items = sorted(items, key=lambda x: x[1])

# Correct
def _key(x):
    return x[1]
sorted_items = sorted(items, key=_key)

Cause 5 - Code too large, too complex, or too nested ​

SecurityError: Code too large: <N> bytes (max 102400)
SecurityError: Code too complex: <N> AST nodes (max 10000)
SecurityError: Code nesting too deep (max 50)

Fix: split into smaller functions. The configured limits are hard to exceed in reasonable scripts.

Cause 6 - Restricted construct (global, nonlocal, while True, del) ​

SecurityError: Global statement forbidden
SecurityError: Nonlocal statement forbidden
SecurityError: Delete statement forbidden
SecurityError: Infinite loop forbidden: while True

Fix: rewrite the function without the construct.

  • global / nonlocal: pass values explicitly or persist through sdk.state.
  • while True: use a finite for ... in range(...) or guard the loop with a counter.
  • del statement: re-bind the variable to None.

TimeoutError ​

TimeoutError: Execution exceeded the time limit

The engine enforces a per-bar time budget (default 800ms). A single bar exceeding the budget produces a TimeoutError for that bar; the backtest continues and only aborts when transient failures cross 5% of total bars (with at least 5 absolute failures). If the run finishes with a log line X/Y bar callbacks failed transiently, the strategy is within tolerance.

⚠️ A persistently slow frame is not just a tolerated TimeoutError. When a bar overruns the per-bar budget, the worker's reader abandons the late response and the request/response protocol can desynchronize — the next read then consumes an out-of-order line and the backtest dies with ProtocolError: Failed to parse persistent strategy output JSON: data did not match any variant of untagged enum StrategyOutput. Unlike a TimeoutError, this ProtocolError is fatal and is not covered by the 5% tolerance — it aborts the whole run immediately. The only reliable cure is to keep every frame O(1) per bar (incremental indicators), not to rely on the tolerance.

Cause 1 - Heavy loop ​

python
# Incorrect: O(n^2) on a list of 10000 candles
for i in range(len(sdk.candles)):
    for j in range(len(sdk.candles)):
        ...

Fix: use only the last N bars (sdk.candles[-period:]). Vectorize with numpy when possible.

Cause 2 - pd.DataFrame(sdk.candles) on every call ​

Building a DataFrame is costly. Fix: use a list comprehension: closes = [c["close"] for c in sdk.candles].

Cause 3 - Indicator recomputed from scratch over the full history on every bar ​

Fix: compute over a bounded sdk.candles[-N:] window with the injected Indicator global (no import) — a fixed lookback keeps each bar O(1) in history length and converges to the full-history value — or keep an incremental accumulator in sdk.state (see persistent state) that updates from only the newest close. Never call an indicator over the whole growing sdk.candles each bar.

python
LOOKBACK = 300  # fixed window >> period → O(1) in history length

def on_bar_strategy(sdk, params):
    period = int(params.get("period", 14))
    if len(sdk.candles) < period + 2:
        return
    rsi = Indicator.rsi(sdk.candles[-LOOKBACK:], period)[-1]  # bounded, no import
    if rsi is None:
        return
    # ... trading logic

MemoryError ​

MemoryError: Memory limit exceeded

Cause - Lists growing without a limit in sdk.state ​

python
# Incorrect: memory leak
sdk.state["all_closes"] = sdk.state.get("all_closes", []) + [...]

Fix: bound the size:

python
buf = sdk.state.setdefault("closes", [])
buf.append(new_value)
if len(buf) > 500:
    buf[:] = buf[-500:]

ProtocolError ​

The SDK contract was violated.

Cause 1 - sdk.buy() without action ​

ProtocolError: Strict Mode: buy() / sell() exige action explícita

The runtime emits this message in Portuguese ("requires explicit action"); search the log for the exact string above. Fix: always pass action=:

python
sdk.buy(action="buy_to_open", qty=1, order_type="market")

Cause 2 - No entry point defined ​

ProtocolError: Strict Mode. Your strategy must define a function
'main(df=None, sdk=None, params={})', 'on_bar(sdk)', ...

Fix: define main() at the root level of the file. See The main() dispatcher.

Cause 3 - Non-serializable return ​

ProtocolError: Unable to serialize return value

Fix: return only dict, list, str, int, float, bool, None. Convert numpy arrays with .tolist(). Convert NaN to None.

Cause 4 - Output shape does not match the study contract ​

ProtocolError: Failed to parse PyO3 study output: invalid type: sequence, expected a string

The returned dict has a field of the wrong TYPE for the study contract. The classic case is per-bar plot coloring: colorSeries in a plot declaration is a string key into the result's top-level colors map — putting the color array itself inside the plot triggers exactly this error ("sequence" = the array, "expected a string" = the key).

Fix: declare "colorSeries": "<series_name>" in the plot and return "colors": {"<series_name>": [per-bar colors]} at the top level of the result. See plots & series and declaration.

Cause 5 - Persistent output desync (over-budget frame) ​

ProtocolError: Failed to parse persistent strategy output JSON: data did not match any variant of untagged enum StrategyOutput

ProtocolError: Failed to parse persistent strategy output JSON: data did not match any variant of untagged enum StrategyOutput — The persistent request/response protocol desynchronized, almost always because a frame overran the per-bar time budget (typically an O(n²) indicator recomputed over the full sdk.candles history on every bar). Unlike TimeoutError, this error is fatal: it is not per-bar-recoverable and aborts the backtest immediately, bypassing the 5% tolerance. Fix: make every frame O(1) — compute over a bounded sdk.candles[-N:] window with the injected Indicator global (no import), or keep an incremental accumulator in sdk.state; never rescan the whole sdk.candles each bar.

ProcessError ​

ProcessError: <message>

Infrastructure problem: the engine's execution environment failed unexpectedly. Not caused by user code.

Fix: report to support. Occurs rarely.

RuntimeError ​

Classic Python exception not handled by the script:

RuntimeError: IndexError: list index out of range
RuntimeError: ValueError: could not convert string to float
RuntimeError: ZeroDivisionError: division by zero
RuntimeError: KeyError: 'close'

Cause - False assumption about data ​

python
# Incorrect: assumes candles has at least 20 items
sma = sum(sdk.candles[-20:][i]["close"] for i in range(20)) / 20

If sdk.candles has 5 items, an index out of range occurs.

Fix: always validate:

python
if len(sdk.candles) < 20:
    return
sma = sum(c["close"] for c in sdk.candles[-20:]) / 20

Cause - Division by zero ​

python
# Incorrect: when avg_loss == 0, breaks
rs = avg_gain / avg_loss

Fix:

python
if avg_loss == 0:
    return 100.0
rs = avg_gain / avg_loss

Cause - Key missing ​

python
# Incorrect: if the candle does not have 'volume'
vol = sdk.candles[-1]["volume"]

Fix:

python
vol = sdk.candles[-1].get("volume", 0.0)

WorkerPoolTimeout ​

WorkerPoolTimeout: timed out waiting for Python worker

The sandbox has a finite pool of Python workers. When many requests arrive, the queue fills and new requests wait. If the wait exceeds the configured timeout, the request fails.

Fix: this is transient. Retry after a few seconds. If persistent, the backend is overloaded; wait for capacity to free up.

UnknownError ​

UnknownError: <message>

Fallback when the engine has not categorized the exception. It usually coincides with a Python RuntimeError.

Fix: inspect the message. If it contains a Python traceback, treat it as RuntimeError.

Silent rendering issues (no exception) ​

Some misconfigurations do not raise an error — the script runs, the engine accepts the return value, but the chart is empty or wrong. The most common:

SymptomLikely causeFix
Legend chip appears, line is invisibleOscillator declared with pane: "overlay" (default). On a high-priced asset, the line collapses against y=0Add "pane": "new" and "scale": "right" to the DECLARATION
Width or color of a plot ignoredField lineWidth (legacy) or 8-digit hex (#RRGGBBAA)Use "width": 2 and 6-digit hex ("#RRGGBB"); area transparency is automatic
Reference levels (0, 70, 30) don't render with the right baselineConstant series used instead of levelsMove the constants into DECLARATION["levels"]
Plot drawn but pane field of declaration looks ignoredStyle override saved with the indicator (legacy style.pane) is fighting the declarationRe-save the indicator with the current declaration; the editor strips legacy style pane on save

Quick diagnostic table ​

MessageLikely categoryFirst step
"Import not allowed"SecurityErrorRemove the import
"Forbidden function call"SecurityErrorRemove the use
"Forbidden function: X" (no "call")SecurityErrorX used as a variable name — the identifier is reserved; rename it (dir → direction)
"Lambda"SecurityErrorReplace with def
"Strict Mode ... must define a function"ProtocolErrorAdd main()
"exige action explícita"ProtocolErrorAdd action=
"did not match any variant of untagged enum StrategyOutput"ProtocolError (fatal)Make every frame O(1) — see Cause 4
"execution exceeded time limit"TimeoutErrorOptimize the loop
"memory limit exceeded"MemoryErrorBound lists in state
"IndexError" / "list out of range"RuntimeErrorCheck len(sdk.candles)
"ZeroDivisionError"RuntimeErrorCheck divisor != 0
"KeyError"RuntimeErrorUse .get() with a default
"timed out waiting for worker"WorkerPoolTimeoutRetry

Debugging in the live editor ​

When seeing an error in the "Errors" tab:

  1. Read the full message, including traceback if present.
  2. Locate the line indicated by the traceback.
  3. Test hypotheses with print():
    python
    print(f"DEBUG: sdk.position={sdk.position}, len(candles)={len(sdk.candles)}")
  4. Re-run and inspect the logs.

Next steps ​