Scripting
A Rhai script as a real strategy in Vike Trader — the two-part script contract, the user-written indicator seam beside it, where the files live, and what mounts a script live.
A Rhai script in Vike Trader is not a sandboxed toy beside the real strategies; it is one.
crates/vike-script/src/lib.rs describes the crate as running a Rhai script as a
vike_model::Strategy<B> — the same seam the compiled Rust strategies use, at a millisecond
cadence — and every engine that drives strategies, backtest and live alike, drives a script the
same way. This tree covers that crate and one more: the script engine is vike-script, while the
user-owned strategy files and their presets live in vike-studio-core/src/user_strategies/. They
are documented together because the reader is the person writing a strategy, whichever crate
ends up loading it.
The contract, in two halves
The lib.rs header states the script contract as two halves, both about calling an indicator.
What a script may call. RHAI_INDICATORS is the host-bound indicator set, derived from
vike_indicators::registry() rather than hand-listed: almost the whole catalog, minus the entries
unbound_reason rejects because a scalar binding of them would return a silently wrong number.
Each bound indicator has one form per argument count, from zero (every registry default) up to its
own parameter count, and unbound_reason says why a missing one is missing. The engine
walks the rules; the cheatsheet is the scannable version.
How it must call it. An indicator function must be called unconditionally on every on_bar
invocation to keep its streaming state correct. The header is blunt about the alternative: an
indicator referenced behind an if silently skips whichever bars it was not called on, desyncing
it from the bar series. That is a consequence of the fed-once-per-bar cache in engine.rs's
indicator_value, and it is the one rule in this tree that fails without an error.
Defining an indicator, not just calling one
The other seam is RhaiIndicator in indicator.rs. A file under user_data/indicators/<name>.rhai
compiles to a real vike_indicators::Indicator — fed-once-per-bar state, vectorize parity by
construction, and no access to the order verbs — and RhaiStrategy::compile_with_indicators binds
it beside the built-ins. The two halves above then apply to it unchanged, including multi-output: a
file declaring fn outputs() { ["upper", "mid", "lower"] } gets its lines through the same
line_fn_name the built-ins use (user_line_accessors is the line_accessors twin), under the
same rule for whether the bare name binds at all. A file that declares no outputs() is
single-output and unchanged.
Because it is a real Indicator, the same compiled prototype also plots: user_meta wraps one as
a &'static vike_indicators::IndicatorMeta whose factory clones it, which is what the chart's ƒx
picker mounts. Nothing about the script contract changes for that — a file written for a strategy
is already a chart study — except one optional addition, fn overlay(), the author's answer to
"price pane, or my own?". Writing an indicator is the
contract; Loading indicators is how the folder reaches a
process.
Where a script runs
A strategy script is compiled in three contexts — the backtest harness, the live daemon and offline validation — and they reach the same engine.
- Backtest. The harness registry's
rhaiarm incrates/vike-backtest/src/harness/registry.rs'sstrategy_by_namecompiles an inlinesrcparam into aRhaiStrategy<SimBroker>;vike-cli backtest --scriptis what puts a file's source there. See the harness registry. - Live. Rhai scripts are live-mountable on
vike-tradehub, named explicitly by path in the reviewable profile.crates/vike-tradehub/src/config.rs's header spells it —[strategy] rhai = "<path>", "mutually exclusive withname", "the script named EXPLICITLY by path in this reviewable file, its compiled source's sha256 logged at INFO as the audit trail" — so nothing is auto-discovered, andname = "rhai"is refused with a message pointing at the path spelling. Four rails hold that mount, and the fourth is the one this tree leans on:crates/vike-tradehub/Cargo.toml'svike-scriptdependency comment records that the daemon compiles through "the SAME resource-limited engine the backtest arm builds (vike-script's onebuild_engine— this daemon registers no host function of its own)", so the host functions a script may call live are the backtest's. The one gap is not the engine's but the process's: the user-indicator setbuild_enginebinds is process-wide and installed by the BINARY, and this daemon installs none (the four that do are on Loading indicators), so auser_data/indicators/file avike-cli backtestscript can call is a function-not-found here. The same comment calls a scripting engine inside the binary that signs real orders a supply-chain decision made deliberately, and names the Python/JS refusal beside it — a refusal by class, a product statement rather than a deferral. - Validation.
vike-cli mcp'svalidate_strategytool compiles a script throughdiscover_params, which the tool's own doc calls the same error surface as mounting it.
A script cannot import, and cannot print to stdout
build_engine installs a DummyModuleResolver, so import "…" as m; is refused: a default Rhai
engine resolves imports from the process's working directory, which for the daemon is the project
holding the credential store. redirect_script_output points print/debug at the tracing
facade instead of the host's stdout, because two hosts — vike-cli mcp and vike-tradehub — use
stdout as a protocol. Both are argued in The engine.
Where the files live
A user's strategies are files, not rows in a blob — crates/vike-studio-core/src/user_strategies/mod.rs
opens with exactly that sentence, and its layout is one folder per strategy under
user_data/strategies/rhai/<name>/<name>.rhai, presets as .toml beside the entry, and
strategies/rust/<builtin>/ holding presets for a strategy whose code is in the binary.
User strategies is the directory scan and its named
diagnostics; Presets is how <strategy>/<preset>.toml becomes the
params a strategy is constructed with; Migration is the one-time
move off the Studio's studio_strategies.json blob.
Where a script is written
The Studio's editor is where a strategy script is edited in the app.
The other interpreted tier in the product — a Rhai study — is a
different thing on a different engine: crates/vike-studio-core/src/rhai_study/bind.rs registers
a store-reading surface for research, not the order verbs, and its operation ceiling is set for a
whole window rather than a bar. A strategy trades; a study answers a question about data.
The pages
| page | what it is |
|---|---|
| The engine | the resource-limited rhai::Engine, what is registered on it, and what a script may not do |
| The order vocabulary | the Intent verbs and the ScriptCtx they are recorded into |
| Writing a strategy | the compiled-script strategy: hooks, param(), the one-time top level, fail-safe |
| Writing an indicator | a Rhai indicator as a real Indicator |
| Loading indicators | user_data/indicators/*.rhai, the report, and which binaries install it |
| User strategies | the strategy tree and its diagnostics |
| Presets | <strategy>/<preset>.toml to constructed params |
| Migration | off the JSON blob, once |
| Limits | the ceilings the engine enforces and what hitting one looks like |
| Cheatsheet | every host function and verb on one page |
Pairs z-score (pairs_zscore)
The pairs_zscore portable strategy: what it trades, every parameter key with its TOML type, and whether it mounts live today — rendered from the strategy registry.
The engine
The resource-limited rhai::Engine a strategy script runs in — what build_engine registers, what it refuses, why an unbound indicator is unbound, and where a script's print goes.