vike
Strategies

The portable registry

Name to boxed strategy, generic over the broker — the one table that serves both the simulator and the live daemon, and the four refusals it makes possible.

vike_strategy::registry resolves a strategy NAME plus a TOML params table to a boxed strategy — strategy_by_name<B: HftBroker> returns Box<dyn Strategy<B> + Send> — and the bound is the whole point. HftBroker is a sub-trait of Broker, and BOTH the simulator's SimBroker and the live core's LiveBroker implement it, so ONE table serves a backtest and a live mount. The box is + Send because the live core moves the strategy onto its own thread; it unsize-coerces to the plain Box<dyn Strategy<SimBroker>> the simulator's callers want. It did not always live here: the registry used to be the backtester's, returning a type only the simulator can name, so a daemon could not run a strategy it had backtested without linking the whole simulator. The fix was the BOUND, not a new crate.

PORTABLE_STRATEGIES is the roster of ten names, kept in step with the resolver's own match by registry_lists_every_match_arm. Two further tables state, as DATA rather than prose, what is NOT here: SIMULATOR_ONLY (seven names, each with its written reason — see simulator-only strategies) and SCRIPT_ONLY (the inline-source rhai arm; scripts mount live by PATH instead). capability(name) folds all three into the one answer a caller needs before mounting: Live, NotLive(reason), SimulatorOnly(reason), or Unknown.

Resolving is not trading

LIVE_CAPABLE is the per-name live-mount verdict, one row per name with a written reason for every "no", machine-checked exhaustive against the roster by live_capable_table_is_exhaustive. It exists because the failure mode is a SILENT NO-OP rather than a compile error or a panic: a strategy whose input never arrives mounts cleanly, logs nothing unusual, and simply never submits. Each "no" names the missing input — a funding series no live kline lane carries, a second mount leg no mount surface declares — so flipping a row is a factual claim somebody can check.

Four refusals over one params table

A from_params reader almost never fails: every one in the workspace but the Avellaneda–Stoikov maker's ignores what it does not recognise, so qtyy = 0.005 is not an error — it is qty at the strategy's compiled default. (The one exception is narrow: vike_mm::SpreadMaker::from_params, which both spread_maker and gueant_maker resolve through, refuses a PRESENT enum key — spread_model, kappa_mode, style and five siblings — whose value names no variant or is not a string at all, and the registry raises RegistryError::BadParams so the profile stops at load. An absent key still keeps its default, and its numeric keys stay as lenient as everyone else's.) In a backtest a knob at its compiled default is a wrong chart; on a mount it is a live order at a size nobody typed, behind at most the optional policy.max_notional_per_order ceiling. crates/vike-tradehub/src/config.rs's DaemonProfile::validate_strategy is the consumer that refuses, with the SAME strictness on a paper mount as on a live one — a rehearsal that silently ran different parameters would conceal exactly what it is for.

ReaderThe case it catches
unknown_paramsa key no reader reads
mistyped_paramsa declared key whose VALUE no reader can take
misrouted_paramsa key that is read, well-typed, and then OVERRIDDEN BY THE MOUNT
unarmable_paramsa whole table that passes all three and still describes no order at all

A wrong type fails as silently as a wrong key

Every accessor but the maker's eight enum readers is an and_then(Value::as_…), which yields None for a value of the wrong type exactly as it does for an absent one, so .unwrap_or(default) lands on the compiled default either way. size = "2" — quoting a number, the most ordinary TOML slip there is — mounts size = 1 just as surely as sizee = 2 does. Those eight refuse a wrongly-typed value outright, and the maker's numeric knobs default as silently as everyone else's. That is why PARAM_KEYS declares a ParamType per key, not just a name.

PARAM_KEYS names, per strategy, exactly the keys its reader looks at and the TOML type that reader accepts; crates/vike-strategy/tests/param_keys_gate.rs scans the readers' own source in BOTH directions, keys and types alike. A row may decline to enumerate — the Avellaneda–Stoikov maker's roughly 60-key bag does, with a written reason — and its consumer then owes its own stricter rule. The daemon's is to refuse a [strategy.params] table under those two names outright and build the maker from the profile's own fields instead, which is strictly stronger than key-checking.

Route keys

PARAM_ROUTES declares which declared keys name a ROUTE — an instrument, a venue, or a SYMBOL = "venue" routing table — rather than a knob of the strategy's own arithmetic. A route key is the one kind of declared key that can be well-spelled, well-typed, genuinely read and still configure nothing: crates/vike-core/src/runtime/strategy_drive.rs's resolve_intent_symbol and resolve_intent_venue stamp the MOUNT's own pair onto every intent while no mount declares legs, which is every mount this workspace builds. MEASURED on prod2 before misrouted_params existed: a buy_hold profile whose params named a different symbol than the mount loaded, announced the params symbol, and filled on the mounted one. Multi-leg names are declined rather than judged — their keys name legs, not this mount's market — and are refused by NAME one step earlier anyway.

The honest echo

resolved_params re-runs the same reader the mount used and reports what each declared key LANDED ON. That is not the same as echoing the profile: a reader may clamp, fall back on an unrecognised string, drop a row, or supply a default the profile never mentions. Printing the operator's own TOML back is a claim about the operator, and it is false in exactly the case worth logging.

One class stays deliberately un-refused: a key that passes all four checks and is still read by nobody, because another key in the same table sent the reader down a different branch. Closing that class is a test rather than a table — crates/vike-strategy/tests/param_gates.rs's an_ungated_key_moves_the_strategy drives every enumerated strategy over a scripted market and fails, by name, when a declared key does not change the call trace.

On this page