vike
Strategies

Authoring in Rust

The seam a native strategy implements, the reader convention its parameters follow, and the folder that compiles a user strategy into your own binary.

A native strategy in Vike Trader is a type that implements Strategy over a broker bound. Choose the bound first, because it is what the type system uses to document where the strategy can run.

  • impl<B: Broker> Strategy<B> — PORTABLE. The same type runs in the backtest sweep harness and on a live mount unchanged. Grid, DcaAccumulate, FundingCapture, PairsZScore and BuyHold all take it — but the bound is a compile-time property, not a mount permission: funding_capture and pairs_zscore carry a written LIVE_CAPABLE reason the daemon refuses at profile load.
  • impl<B: HftBroker> Strategy<B> — portable, but only over a broker that can resolve a strategy-chosen TAG to a resting order and re-price or cancel it in place. SpreadMaker and TrailingScalper take this bound; a plain Broker-only engine simply cannot mount them.
  • impl Strategy<SimBroker> — a deliberate, load-bearing SECOND form, not a backlog item. It is how a strategy says "I drive simulator machinery" — a position sizer, the shared-cash gate, an attached protective stop, a symbol universe, a bar-count schedule — and the type is what stops it being mounted live. See simulator-only strategies.

What the reference strategies show

crates/vike-strategy/src/grid_dca.rs states the constraint that shapes most portable designs: the portable Broker surface is the common denominator of the two stacks, and it deliberately has NO cancel — only the concrete brokers expose one. Grid and DcaAccumulate are therefore designed to need none, and the module is honest about the price: a resting limit placed before a halt or a take-profit cannot be pulled, so Grid re-flattens every bar while halted and DcaAccumulate may re-fill a prior cycle's residual entries. Both are documented, bounded-per-cycle behaviours.

crates/vike-strategy/src/funding_capture.rs shows the other way out of the same constraint: it steers a NET position with market deltas and never rests a limit, so it needs no cancel at all. crates/vike-strategy/src/pairs.rs shows a strategy written as a MEASUREMENT INSTRUMENT rather than a product — its own header says to expect a negative result and that this is the successful outcome. None of these are parity-gated: they are net-new Rust surface with no Python twin, so what is tested is behaviour (does the ladder rest, fill, re-arm and stop as designed?) rather than an f64 oracle.

The parameter reader convention

Nearly every from_params in the workspace is a READER, not a schema: it reads a toml::Value, ignores what it does not recognise, and falls back to a compiled default for anything missing. Follow it — but understand what it costs. A lenient reader cannot fail, so an unrecognised key silently leaves its knob at the default, and so does a recognised key carrying a value of the wrong type. That is why the registry publishes PARAM_KEYS and PARAM_ROUTES for the built-in names, so a live mount can refuse the table instead of trading it. The exception worth knowing is vike_mm::SpreadMaker::from_params (behind the spread_maker and gueant_maker registry names): it returns a Result, and its eight enum keys — variance_mode, horizon_mode, price_domain, spread_model, reservation_model, spread_source, kappa_mode and style — refuse the table when PRESENT but naming no variant, or carrying a value that is not a string at all, rather than quietly running a different model than the profile names. Its numeric keys, and an absent key of any kind, stay lenient like everything else.

A strategy as a crate

vike-user-strategies is the compiled user-strategy HOST. Its build.rs scans

<workspace>/user_data/strategies/rust/<name>/
├─ <name>.rs        entry file — the stem MUST equal the folder name
├─ strategy.toml    optional: `live = true` opts into live mounting
└─ *.toml           presets (loaded by vike-studio-core, not by this scan)

and generates the registry the crate re-exports — USER_STRATEGIES, USER_LIVE_CAPABLE and user_strategy_by_name. The built-in registries consult it AFTER every built-in arm, so a user folder can never shadow a built-in name, and NO framework crate changes when a strategy is added: a new native strategy is a new folder, nothing else. VIKE_USER_DATA_DIR overrides the scanned root at build time. A folder name must be a lowercase Rust-identifier-safe token, because the generated module and match arm are built from it, and a malformed folder is a build ERROR naming the path — silence is how this tier spent months being mistaken for a working mechanism.

The entry file's contract is one function:

pub fn build<B: vike_model::HftBroker + 'static>(
    params: &toml::Value,
) -> Box<dyn vike_model::Strategy<B> + Send>

The separate crate is the load-bearing part. User entry files compile as modules of THAT crate — a separate compilation unit from the framework's own strategy layer — so they resolve only the platform's PUBLIC API, reached through its dependencies: vike-model, vike-strategy, vike-indicators and toml. An internals refactor can never break a user strategy, and a user strategy can never grow a load-bearing dependency on a crate-private detail.

Two things the tier does not do for you

The in-tree PARAM_KEYS and param-gate tables cover the REFERENCE set only. They do not cover user strategies, so a typo'd key in your own build silently reads as the compiled default. And strategy.toml's live = true is an explicit opt-in: an absent file or an absent key means sim-only, the default-deny posture.

With no user_data/ — every fresh clone, every CI checkout — the generated registry is EMPTY and the host crate is inert, answering None for every name. The pipeline is still proven on every CI run through a committed fixture tree. User code compiles only in a source checkout and into the operator's OWN binary; user_data/ is gitignored and never enters the repository.

For strategies written as files rather than as Rust, see scripting; for running what you have written, see backtesting.

On this page