vike
Backtesting

The harness registry

What strategy.name may say — a name-to-boxed-strategy lookup over SimBroker that is the simulator half of a two-part registry, with seven names a live daemon cannot resolve and the written reason for each.

A profile's [strategy] name resolves through crates/vike-backtest/src/harness/registry.rs's strategy_by_name: a registry name plus its TOML params table becomes a Box<dyn Strategy<SimBroker>>. An unknown name is a validation error at load time — the harness fails the profile fast rather than silently no-opping on a typo. Dispatch through the box is deliberately dynamic: the harness runs one strategy per profile, so a single vtable indirection per bar or tick is noise next to loading and replaying the data slice.

Two halves of one registry

This file is the simulator half. The portable half moved down to crates/vike-strategy/src/registry.rs, whose strategy_by_name<B: HftBroker> is generic over the broker so the same table serves the backtest simulator and the live daemon. The reason is a dependency one: a return type naming SimBroker made "run the strategy I backtested" impossible for the headless daemon without depending on vike-backtest, which would drag the whole simulator — and, under the DataFusion feature, the Arrow tree — into the binary that signs real orders.

What stays here is what genuinely cannot leave: five reference strategies implemented against SimBroker by design, two portable strategies that reach this crate's own internal modules, and the rhai arm, whose vike-script dependency sits at the same layer rank as vike-strategy and so cannot be named from there. Everything else falls through to the portable registry, monomorphised at SimBroker.

STRATEGIES is the union of both halves, in this order:

buy_hold  rotation_top_k  bracket_per_symbol  gated_weights  caps_sizers_mask
tick_pair_mse  cheap_catch_updown_fair_value  sport_copy_follower  grid
dca_accumulate  spread_maker  gueant_maker  trailing_scalper  momentum
funding_carry  funding_capture  pairs_zscore

The order is wire-visible — the data service's strategy listing documents "in its declared order" — so the split kept it byte-identical rather than grouping the halves. Every name on it resolves with default params; backtest --list prints it, and the Studio consumes it as its native-strategy list. The ten portable names each have a page of their own, from buy_hold to pairs_zscore; the registry they come from is the portable registry.

The seven simulator-only names

vike_strategy::registry's SIMULATOR_ONLY carries, as data any consumer can read, the names that cannot leave vike-backtest and the reason for each — so a daemon refusing one can say why rather than "unknown strategy". Two distinct reasons, and the distinction matters:

namewhy it stays
rotation_top_ksimulator-bound: reads the SimBroker symbols/schedule fields directly
bracket_per_symbolsimulator-bound: measures the attached protective stop the SimBroker fill loop resolves
gated_weightssimulator-bound: drives the SimBroker cash-gate weight submit argument
caps_sizers_masksimulator-bound: drives the position sizer via SimBroker's raw=false submit path (a 999.0 sentinel size)
tick_pair_msesimulator-bound: measures SimBroker::position_of against the engine's latency shadow
cheap_catch_updown_fair_valuelives in the simulator crate: reaches its internal cheap_np_ask/fair_value modules — portable in principle, not yet moved
sport_copy_followerlives in the simulator crate: reads a signal series the replay harness synthesizes — portable in principle, not yet moved

The first five name simulator machinery that no registry work makes live; the last two are genuinely portable impl<B: Broker> strategies that merely live here, and moving them is future work rather than a law. The backtest side asserts the table names exactly its retained arms, so the two cannot drift. The same table renders simulator-only strategies.

The rhai arm

rhai resolves here and is deliberately absent from STRATEGIES: it requires a src param — the inline script source — so it is not default-resolvable, and listing it would break every consumer that enumerates the roster and resolves each name with empty params. A missing src is a fail-fast validation error; every other numeric param is a param(name, default) override, the same knobs a [sweep] varies, baked into the script scope at compile through RhaiStrategy::compile_with_params. A Rhai parse or compile error surfaces as a validation error at load, not a mid-backtest panic. Integers and floats are both accepted for a numeric knob, so size = 1 reads the same as size = 1.0.

Scripts do mount live — by path, not by this name

vike_strategy::registry's SCRIPT_ONLY classifies the name, not the script path. The daemon links vike-script directly and mounts a script by path, [strategy] rhai = "<path>", never through this registry; the inline-src arm here remains the backtest spelling. That scripts mount live at all is a recorded architecture verdict, and the SCRIPT_ONLY row carries it as the reason a refusal on the name redirects to the path spelling rather than claiming the strategy does not exist. See writing a strategy.

User strategies, consulted last

When neither half knows a name, the resolver consults the user registry — vike_user_strategies::user_strategy_by_name, compiled from user_data/strategies/rust and empty in any checkout without one — before giving up. Built-in arms are tried first, so a user folder can never shadow a registry name. The final error names both rosters, the built-ins and the user's, so an operator can tell "not compiled in" from "typo'd a built-in". Authoring one is authoring in Rust.

The five reference strategies are #[derive(Default)] with no harness-tunable knobs today — Default::default() is their documented constructor. cheap_catch_updown_fair_value and sport_copy_follower are genuinely param-driven and read their tables the way buy_hold does.

On this page