Studio Overview
Visual node-based strategy editor on Hyperliquid — typed dataflow graphs with deterministic execution, backtesting, paper + live trading, custom chart indicators, end-user frontends, marketplace, and self-hosted runner.
Desktop-only node editor at /studio. On a phone it shows Studio needs a bigger screen: run saved strategies from the trade ticket's Pro menu instead. Strategies are typed dataflow graphs evaluated by a deterministic tick engine on Hyperliquid market data. The same JSON graph runs in the browser (backtest / paper / live), in Borsa Cloud, or in borsa-runner (self-host).
This page is the overview. See also:
- Core Concepts — port types, signal-vs-level, the two-tick clock, per-node timeframe, the risk barrier. Read this first.
- Your First Strategy — build + backtest an EMA cross in five minutes.
- Node Reference — full spec of every node.
- Indicators — the indicator library + custom chart indicators.
- Strategy Frontends — give a strategy an end-user form in the order ticket.
- Backtest Modeling — how fills, fees, slippage, and funding are simulated.
- Examples & Templates: the 12 shipped templates and patterns.
- FAQ & Troubleshooting · Cheatsheet
Marketplace at /marketplace.
Layout
- Toolbar: New (template gallery), File (Open…, New ▸ From template… / With the wizard… / Blank canvas / Blank helper / Blank indicator, Publish…, Export, Run with borsa-runner…, Import…), Edit (Undo, Redo, Revert to loaded state, Use current market for all coins), View (zoom, wire style), the Backtest / Paper / Live switch and Run.
- Library: drag any node onto the canvas. Searchable, grouped by category (Data, Indicator, Logic, State, Risk, Execution, Debug, Order form, Developer), plus your Helpers.
- Canvas: pan = wheel, Space-drag or Alt-drag; zoom = ⌘/Ctrl + wheel; connect by dragging from a port.
Ffits,Deleteremoves selection,?lists every shortcut. - Inspector / Order form preview (right sidebar tabs): the Inspector edits the selected node's params, or graph-wide settings when nothing is selected (name, graph type, backtest range, starting equity, seed, markets). Selecting a node always switches to the Inspector. The Order form preview tab (shown when the graph has
ui.*nodes) previews the end-user form, see Strategy Frontends. - Bottom panel: Timeline (equity) · Plots · Orders · Console · Runs · Issues.
Graph kind
Set the Graph type in the Inspector (no node selected → graph settings):
- strategy — runnable (backtest / paper / live).
- helper — reusable subgraph, invoked from other graphs via
helper.input/helper.outputmarkers. - indicator — its
debug.plotnodes render on the TradingView chart. See Indicator Graphs.
Two kinds of tick
| Tick | When | ctx.barClosed | Stateful indicators commit? |
|---|---|---|---|
Live | each ws BBO + candle update | false | no |
Bar-close | timeframe boundary | true | yes |
Stateful nodes recompute on every tick so live values flow, but only mutate state when the bar closes. Replays + paper + live are deterministic at the bar. Full model in Core Concepts.
Coins default to Current market. Every node coin param starts as Current market, so a strategy trades the market it is started on. It may instead be pinned to a symbol (BTC, ETH, a spot pair like HYPE/USDC, …), picked from a searchable market list. The Inspector's Markets section shows what Current market resolves to now and lists pinned coins. Studio paper and live runs use the market selected on the trade page; backtests (and the CLI runner) use the fallback market set there. Edit → Use current market for all coins converts an older graph in one undoable step. A run started from the order form stays on the market it started on, even if you switch charts. Runners subscribe from the graph's coin params (and any Coin Field pick).
Per-node timeframe. data.candles, clock.bar, and every indicator carry an optional timeframe override: empty inherits the graph timeframe; a coarser value (an integer multiple of the base) resamples that node onto its own higher-timeframe bars — a 4h EMA inside a 1h strategy. See Core Concepts.
Execution gating. exec.* nodes have fireOnBarClose (default true); they no-op on intra-bar ticks. Wire clock.bar AND-gates for explicit control, or clock.everyMs for intra-bar timers (then set fireOnBarClose: false). Fire inputs are edge-armed by default (fireMode: "edge"): a held-true condition fires one order on its rising edge, not one per bar. Use logic.edge for explicit event conversion, or fireMode: "level" to restore fire-every-eligible-tick behaviour.
Modes
Backtest. Historical candle replay over the graph's configured range, in a worker. The run timeframe comes from the graph's data.timeframe node when present (legacy graphs fall back to the saved runtime timeframe). Streams equity, orders, plots, console output, and issues into the bottom panel.
Paper. Live ws data, simulated fills. Seeds positions + equity from clearinghouseState if a wallet is connected. PaperAdapter fills at crossed BBO or mid + slippage. Does not subscribe to userFills — paper drift is isolated from the real account.
Live. Same engine. Signs orders via the Hyperliquid agent-wallet flow. Subscribes to userFills + clearinghouseState so engine state stays in sync. Run button shows Run Strategy; the confirmation dialog summarizes strategy, traded markets, timeframe and wallet, lists anything that blocks the start, and lets you pick where it runs:
- This device: runs in this tab and stops when the tab closes.
- Borsa Cloud: runs 24/7 on Borsa's servers, signed by a Borsa Cloud agent you approve once. Needs a minimum points balance (the dialog shows it).
Stop disarms. Browser runs live in a module-scoped registry, so they survive navigating away from /studio and are observable from the /strategies tab. A strategy runs at most once: a second tab, or a strategy already running in Borsa Cloud, is refused with a message saying where it runs. A paper run is restored after a page refresh; an interrupted live run asks Resume live or Discard.
Strategy executors subscribe to the order book in fast mode (5 levels / 0.5s) so fills stay responsive after Hyperliquid's push-frequency changes.
Runs tab
Every finished run (backtest, paper or live) is kept in the bottom panel's Runs tab, the last 10 per strategy, with return, max drawdown, trades, win rate and an equity sparkline. A run is marked stale once the graph is edited. Tick two rows to compare them: overlaid equity curves, a stat table, and What changed (field values, timeframe, graph edits).
Library sync
Your Studio library is saved on Borsa's servers per wallet, so every device signed in with the same wallet sees the same strategies. Changes upload a moment after you make them; other devices pick them up on start, every 30 seconds and when the app regains focus. Deleting a strategy removes it on every device. When two devices edit the same graph, the later change wins. Sync uses this device's approved trading agent and never asks for a signature: a device starts syncing once its agent is approved.
Helpers
A graph becomes a helper when you set its type to helper (or drop a helper.input / helper.output marker). Other graphs see it in the Library under Helpers and can drag it in. Invocations expand inline at compile time, with cycle detection. Helpers can invoke other helpers. The marketplace publishes them like strategies.
dev.js
Body receives inputs (a/b/c/d), persistent state, params, and ctx (now, barClosed, mode, candle, history, bbo, position, equity, Math, Number). Returns a number, or { out, plot1, plot2, plot3 }. Parse / runtime errors land in Console. Scripts are capped at 1M loop iterations per tick (a runaway while (true) is aborted, not hung). Marketplace publish rejects graphs containing dev.js, and file imports containing it are blocked by the same untrusted-graph guard — no remote-code execution.
Validation
Surfaces in five places: red-bordered nodes/edges on canvas, an inspector callout when a flagged node is selected, the Issues tab, the toolbar N errors pill, and a pre-run toast that blocks execution.
Common errors:
Only one Timeframe node is allowed per graph.— keep a singledata.timeframe.Type mismatch: boolean → signal<boolean>.— port types are strict; level→signal is rejected. Convert withlogic.edge.Timeframe override '<x>' must be coarser than and a multiple of the strategy timeframe.— per-node overrides only go to higher, aligned timeframes.Cycle detected involving node …— cycles must go throughstate.*ordev.js.Indicator graph has no Plot node.— an indicator-kind graph needs adebug.plot.
See the FAQ & Troubleshooting for fixes.
Export & import
Export downloads the current graph as <name>.borsa.json in a versioned envelope ({ schema: "borsa-strategy/v1", graph }). Import opens a file picker and adds it to your library with a fresh id — imported files pass the same untrusted-graph guard as marketplace forks, so a shared file containing dev.js is rejected. Use export to back up, share, or feed into the self-hosted runner.
Marketplace
File → Publish… → wallet signs borsa-publish:${versionHash} → POST to the backend. The server verifies the signature, recomputes the canonical hash, rejects duplicates and any graph containing dev.js, and stores it immutably. /marketplace browses + searches by tag/title/author; Fork copies a graph into your local Studio with a fresh id (original author stays credited). Versions are immutable: bump version to push a revision.
The publish dialog asks for title, summary, version, licence, visibility, tags and a risk disclosure, and can embed self-reported backtest stats (it reuses your latest backtest of the current graph, or Run backtest). After the first publish the File menu shows Publish update… once the graph has changed.
The marketplace currently lists Borsa's official strategies (Lever up, Rotate, De-lever, Hedge, Take-profit ladder, Rebalance basket, Ratio rotation, TWAP vs VWAP, Break-even stop, Volume TWAP). They are ordinary Studio graphs: Add to Library to run them from the order form, or Fork to Studio (edit) to see and change how they are wired. See Strategy Examples.
Visibility — public or private. At publish time you choose Public (listed in the marketplace) or Private / unlisted (hidden from marketplace browse, search, and sort, but still loadable by its version-hash link and shown in your library's Published tab). The Published tab marks unlisted items with a Private badge.
Editable listings. After publishing, the publisher can edit the listing in place — title, summary, tags, risk disclosure, licence, classification, and the public/private flag — via a signed update from the Edit button on the Published tab. The graph stays immutable: any change to the graph itself is a new-version publish with a new version hash, not an edit.
Self-host with borsa-runner
Single binary that runs any exported strategy on your own infra, using the same engine as the browser.
borsa-runner --graph my.borsa.json --user 0xMain --mode paper
BORSA_PRIVATE_KEY=0x... borsa-runner --graph my.borsa.json --user 0xMain --mode live
# Frontend (ui.*) values — same fields as the order-ticket form
borsa-runner --graph my.borsa.json --list-params
borsa-runner --graph my.borsa.json --mode backtest --param size=100 --params ui-values.json
| Mode | Wallet | Builder approval | Submits orders |
|---|---|---|---|
backtest | not needed | not checked | no |
paper | optional (seeds account state) | not checked | no |
live | required | required | yes |
Revenue model. The binary is free. Borsa earns from the builder code attached to every order. Live mode queries Hyperliquid's maxBuilderFee at startup and refuses to start without approval. Wallet model: BORSA_PRIVATE_KEY=0xAgent signs orders; the main key never touches the runner. Distribution: signed binaries for linux-x64, linux-arm64, darwin-x64, darwin-arm64, windows-x64 are published via GitHub Releases (not from app.borsa.cc). The in-app downloads page at /downloads/latest lists the latest release's binaries per platform with SHA-256 checksums, fetched live from that release's manifest.
Anti-patterns
Math.random()/Date.now()insidedev.js— usectx.rngandctx.now. Wall-clock leaks break replay.exec.*withfireMode: "level"wired from a live signal withfireOnBarClose: falseand no clock gate — fires on every ws tick. (The defaultfireMode: "edge"protects you: a held-true signal fires once.)- Cycles outside explicit-state nodes — rejected at compile.
- Hundreds of nodes on one canvas — factor into helpers.
- Importing graph JSON from a stranger without inspecting it — the guard blocks
dev.js, but the graph can still trade however its author wired it. Same trust as code you wrote.