Security & Execution Limits
When embedding OpenPine in a host application, user-supplied Pine scripts execute inside the VM. The VM enforces configurable limits to protect the host process from runaway scripts.
Why Limits Matter
Pine scripts can contain loops (for, while, for..in) that may iterate arbitrarily many times depending on user input or data. Without limits, a malicious or buggy script could hang the host process indefinitely. OpenPine's execution limits are checked per bar so the overhead is predictable.
Configuring Limits
Pass an ExecutionLimits value to InstanceBuilder::with_execution_limits before calling build():
use openpine_vm::{ExecutionLimits, Instance, TimeFrame};
let limits = ExecutionLimits::default()
.with_max_loop_iterations_per_bar(1_000_000);
let mut instance = Instance::builder(provider, source, TimeFrame::days(1), "NASDAQ:AAPL")
.with_execution_limits(limits)
.build().await?;When the limit is exceeded the VM raises a runtime error (Error::Exception) that you handle the same way as any other runtime exception.
Available Limits
max_loop_iterations_per_bar
| Property | Value |
|---|---|
| Default | 500,000 |
| Scope | All for, while, and for..in loops share a single budget per bar |
| Reset | Counter resets at the start of each bar execution |
| Disable | Set to u64::MAX to remove the limit |
All loop types count against the same budget within a single bar. For example, a script with two nested for loops each iterating 1,000 times consumes 1,000,000 iterations — exceeding the default budget of 500,000.
// Raise the limit for compute-heavy scripts
let limits = ExecutionLimits::default()
.with_max_loop_iterations_per_bar(2_000_000);
// Disable the limit entirely (not recommended for untrusted scripts)
let limits = ExecutionLimits::default()
.with_max_loop_iterations_per_bar(u64::MAX);max_security_depth
| Property | Value |
|---|---|
| Default | 3 |
| Scope | Maximum nesting depth of request.security() calls |
| Disable | Set to 0 to prohibit request.security() entirely |
Controls how many levels deep request.security() calls may nest. A depth of 1 means a security expression may not itself call request.security(). The default of 3 matches TradingView's behaviour.
// Allow only one level of request.security() (no nesting)
let limits = ExecutionLimits::default()
.with_max_security_depth(1);
// Prohibit request.security() entirely
let limits = ExecutionLimits::default()
.with_max_security_depth(0);max_security_calls
| Property | Value |
|---|---|
| Default | 40 |
| Scope | Maximum number of unique (symbol, timeframe) pairs across all request.security() call sites |
| Disable | Set to 0 to prohibit request.security() entirely |
Each distinct (symbol, timeframe) combination creates a separate data stream. Multiple call sites that share the same pair count as one. The default of 40 matches TradingView's behaviour.
// Tighten the limit for resource-constrained environments
let limits = ExecutionLimits::default()
.with_max_security_calls(10);Handling Limit Violations
A limit violation surfaces as Error::Exception, the same type used for runtime.error() and other runtime errors:
match instance.run_to_end("NASDAQ:AAPL", TimeFrame::days(1)).await {
Err(openpine_vm::Error::Exception(e)) => {
eprintln!("Script error: {}", e.display());
}
_ => {}
}See Error Handling for full details on handling runtime errors.
Recommendations
- Untrusted scripts: keep the default limits or lower them. Consider setting
max_security_depthto1or0ifrequest.security()is not needed. - Known scripts: raise limits only if you have confirmed the script requires more resources.
- Server-side execution: consider running each
Instancein a dedicated thread with an OS-level timeout as a secondary safeguard, since the loop limit only guards against excessive iteration counts, not other sources of long-running computation.
Next Steps
- Instance Builder — full list of builder options
- Error Handling — handling runtime exceptions