Strategy Frontends

Give a strategy an end-user form with UI input and output nodes, then run it from the order ticket.

A frontend turns a strategy into something an end-user can drive without opening the editor: a form of fields they tweak, plus live readouts, rendered right in the order ticket. You build it by dropping ui.* nodes onto the canvas (Library → Order form).

UI input nodes

Each input node has an output port that feeds the graph, plus a label and an order (render row, ascending). It reads the end-user's value at evaluate time, falling back to its default.

NodeOutputRenders as
ui.number
value, reduceOnly
numeric field (math expressions allowed), clamped to [min, max]; as the order-form Size block its RO toggle is exposed on reduceOnly
ui.balance
value, percent
percent-of-balance slider → resolved USD (value is NaN until the form resolves an amount — no accidental zero-size orders)
ui.boolean
value
toggle switch
ui.string
value
free-text field (with optional placeholder)
ui.slider
value
diamond slider over [min, max], clamped
ui.select
value
dropdown over options; values may be numbers or text ("1,2,5", "1m,5m,1h", "fast=1,slow=2", "1 minute=1m,1 hour=1h"); end-user values are validated against the options
ui.coin
coin
market picker: lists every perp and spot market, typing filters; left on Current market it follows the chart
ui.side
side, opposite, sign
buy/sell or long/short toggle. Wire side into an exec.* side input; opposite is the other side (for the closing leg), sign is +1 for buy and −1 for sell
ui.button
pressed
action button — fires a one-shot true pulse (at bar close by default; see below)
ui.timeframe
timeframe
timeframe picker bound to the graph timeframe

UI output nodes

Each consumes one wired value and renders feedback:

NodeInputRenders as
ui.readout
value
live number (format: number / usd / pct); non-finite input renders as a dash (—)
ui.status
value
colored badge (e.g. LONG / FLAT / SHORT)
ui.gauge
value
progress bar between min and max; non-finite input renders as a dash

Buttons & firing

There are two ways to fire:

  • Run Strategy / Stop: the order ticket always has it. A strategy with no ui.button just starts/stops the runner.
  • ui.button action: emits a single true pulse on pressed, then false. Wire it into exec.* ("Buy now", "Close all") or any logic.*. Pressing a button while stopped starts the run (after the Run Strategy confirmation), then pulses. The emitOn param picks the tick: barClose (default) holds the press until the next bar-close tick, so a press between closes survives intra-bar ticks and fires exactly once at the close; instant consumes the press on the very next tick. For true click-to-trade, pair emitOn: instant with Only at bar close (fireOnBarClose) off on the exec node.

Any input field can be shown conditionally: set Visible when node to the id of another ui.* node (e.g. a toggle) and …equals to the value it must hold.

Preview in the designer

The right sidebar's Order form preview tab (shown once the graph has any ui.* node) renders the exact form the end-user sees, wired to local preview state. Hit Test (paper) to launch a paper run; field changes push live into the engine and output nodes stream back into the readouts. Paper testing happens here: the order form only runs strategies live.

In the order form

The order form's Pro dropdown lists Scale / TWAP / Burst and, under Your strategies, every executable strategy (Graph type = strategy) in your Studio library, read live from the graph, not a snapshot. Anything in your library shows up here, and editing it in Studio is reflected in the order form immediately. Removing a strategy from the library removes it from the order form. New strategy… at the bottom opens the strategy wizard. Selecting a strategy swaps the order-form body for its frontend form.

  • Field values (size, levels, etc.) persist to local storage per strategy, so your typed inputs survive across sessions.
  • Run Strategy starts it live after the same confirmation as Studio, where you pick This device or Borsa Cloud. Pressing a ui.button while stopped also starts it. Stop ends the run.
  • Changing a field while a run is active on this device pushes the new value into the engine live (no restart). At launch the current values are applied as the starting set.
  • Changing the timeframe in the order form writes back to the library graph, and stops a running strategy (run it again to apply).
  • If you edit a strategy in Studio while it runs, the ticket shows Edited since this run started. with Restart with changes. Open in Studio opens the graph for debugging.
  • If a field or node coin is set to Current market, it uses the order form's selected market when the run starts, and the run stays on that market if you switch charts. When the strategy trades another coin, the ticket says so. In Studio's preview and paper test, Current market is the market selected on the trade page.
  • A strategy runs once: if it already runs in another tab or in Borsa Cloud, the ticket shows where instead of a Run button.
  • On phones, starting a strategy closes the ticket and shows the Strategies tab.

Execution errors

When a running strategy hits an error — a live order rejected, a warmup/recompile failure, or a node error — or when a start/preflight check fails, a red Execution error banner appears in the strategy ticket (not just a transient toast), so the failure stays visible until you act on it.

How values reach the engine

Input nodes read ctx.uiParams[nodeId]. The runner calls engine.setUiParams(values) once at launch and again on every field change, so a slider tweak is a map write picked up on the next tick — no recompile. ui.button clicks call engine.pulseUi(nodeId). This makes a live run non-replayable (like any live run); backtests apply the values once and stay deterministic.

borsa-runner CLI overrides

Self-hosted borsa-runner accepts the same frontend values as the order-ticket form:

# List settable fields for a graph
borsa-runner --graph my.borsa.json --list-params

# Repeatable key=value (node id or label)
borsa-runner --graph my.borsa.json --mode backtest --param size=100 --param "Market"=ETH

# JSON file (merged first; later --param wins on conflicts)
borsa-runner --graph my.borsa.json --mode paper --user 0x... --params ui-values.json
Field kindCLI value
ui.number, ui.slider
number (42, 0.5) — clamped to the field's [min, max]
ui.balance
USD amount, or percent via nodeId:percent=50
ui.boolean
true / false
ui.string
free text
ui.select
option value or label (invalid options are rejected)
ui.coin
symbol (ETH) or $CURRENT_MARKET / current
ui.side
buy, sell, long, or short

ui.button cannot be set from the CLI — wire a default run path or use the web UI to pulse it.