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.
| Node | Output | Renders 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:
| Node | Input | Renders 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.buttonjust starts/stops the runner. ui.buttonaction: emits a singletruepulse onpressed, thenfalse. Wire it intoexec.*("Buy now", "Close all") or anylogic.*. Pressing a button while stopped starts the run (after the Run Strategy confirmation), then pulses. TheemitOnparam 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;instantconsumes the press on the very next tick. For true click-to-trade, pairemitOn: instantwith 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.buttonwhile 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 kind | CLI 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.