Risk Limits & Sandbox
How Pyx protects your capital and isolates your scripts.
Risk limits
Every script task has configurable risk limits enforced in Rust, outside the Luau VM. Your script cannot bypass, modify, or inspect these limits - they're applied at the execution layer before orders reach the exchange.
Configuration
Set these when creating a task:
| Limit | Type | Description |
|---|---|---|
max_allocated_usd | number | Maximum total capital (price × size) across all open orders and positions. Orders that would exceed this are rejected. |
max_open_orders | number | Maximum total active orders across all tokens. |
max_in_flight | number | Maximum orders that are submitted but not yet confirmed (pending). |
max_in_flight_per_token | number | Same as above, per token. |
max_active_per_token | number | Maximum active/ open orders per token. |
max_position_per_token | number | Maximum position size per token, in shares. |
order_timeout_ms | number | Milliseconds before a placed order is considered timed out and cleaned up. This includes partially filled orders. |
min_ms_between_orders | number | Minimum delay between order submissions. Prevents rapid-fire placement. |
What happens when a limit is hit?
When pyx.buy() or pyx.sell() enqueues an order that would violate a limit:
- The order is rejected before reaching the exchange
- A warning is logged:
"Order failed: [reason]" - Your script continues running - rejected orders don't crash the task
- The spend/allocation tracking is unchanged (rejected orders don't reserve capital)
Recommended starting limits
For a new strategy in testing:
{
"max_allocated_usd": 100.0,
"max_open_orders": 8,
"max_in_flight": 4,
"max_in_flight_per_token": 2,
"max_active_per_token": 4,
"max_position_per_token": 100.0,
"order_timeout_ms": 30000,
"min_ms_between_orders": 500
}These are conservative. As you gain confidence in your strategy, you can widen them.
Sandbox
Your script runs in a sandboxed Luau VM with several layers of protection.
Available standard libraries
| Library | Available | Notes |
|---|---|---|
math | Yes | math.floor, math.abs, math.max, math.random, etc. |
string | Yes | string.format, string.sub, string.find, etc. |
table | Yes | table.insert, table.remove, table.sort, etc. |
os | No | Removed entirely |
io | No | Removed entirely |
debug | No | Removed entirely |
package / require | No | Removed entirely |
dofile / loadfile | No | Removed entirely |
load / string.dump | No | Removed - prevents bytecode attacks |
Memory limit
Each VM is capped at 8 MB of memory. If your script exceeds this (e.g., by building an unbounded table), the VM throws a memory error. Design your data structures with bounds:
-- Good: bounded history
table.insert(prices, value)
if #prices > 200 then
table.remove(prices, 1)
end
-- Bad: unbounded growth
table.insert(all_prices_ever, value) -- will eventually hit 8MBIf you need higher limits, please reach out to us.
Execution timeout
Callback timeouts are callback-specific:
on_price(ctx): 25ms timeouton_spot_price(ctx): 25ms timeouton_book(ctx): 25ms timeouton_tick(ctx): 150ms timeouton_fill(fill): 50ms timeouton_placed(order): 50ms timeouton_cancelled(order): 50ms timeouton_split_complete(ctx): 50ms timeouton_merge_complete(ctx): 50ms timeout
The timeout resets for each callback - a 20ms on_price followed by a 40ms on_fill is fine.
If a callback times out:
- The current call is aborted
- Any orders enqueued before the timeout are still submitted
- The task usually continues running - but 5 Luau errors within 10 seconds will auto-stop the task with status
error - The error is logged
Frozen globals
All standard library tables and the pyx module are read-only. You cannot:
-- These all fail silently or error
math.floor = nil
string.format = my_format
pyx.buy = function() endYour global writes go to a local environment table. You can create and modify your own globals freely, but you can't tamper with builtins or the trading API.
No cross-task access
Each task has its own isolated VM. Task A cannot read task B's variables, positions, or orders. Even if they subscribe to the same markets, they're completely independent.