Reference
One core, several front ends — how the desktop app, the daemon, the CLI and the data service relate, and where each one's flags, binaries and settings live.
Vike Trader is not one program with a window bolted on. It is one core and several front ends that observe it, so the question of which binary to run is the question of which front end you want.
The core
crates/vike-core/src/lib.rs is the single-writer live core: one thread owns all mutable trading
state, ingest arrives through the vike-exec lanes, and readers take a lossy arc-swap
CoreSnapshot and never back-pressure the writer. Producer lanes live in vike_exec::lanes, so
venue bridges never depend on the core crate at all. Nothing that renders, serves or reports gets
to touch that state directly — every front end below is a reader of published snapshots and a
sender of commands.
The front ends
The desktop app (crates/vike-desktop/src/main.rs) is a native egui multi-window shell. Market data
flows feed threads to core ingest, the core-owned bar cache to the arc-swap snapshot, and the GUI
converts each changed snapshot into its render model: "the GUI is a lossy observer; it never
touches the core's state directly." The shipped build compiles out the whole
local-core, DataFusion and venue-bridge subsystem, leaving an observer client. See
the GUI section and vike-desktop.
The daemon (crates/vike-tradehub/src/lib.rs) is the same core, headless. Its testable
internals live in a library so integration tests can drive them; the slim bin composes them into a
process lifecycle — log init, node build, stdio control, bounded teardown. Two modules are the
shape worth knowing: publish projects CoreSnapshot into WireSnapshot and runs the one
publisher thread that fans coalesced frames to subscribers over bounded, drop-oldest mailboxes,
never touching the hot fold; server is the authenticated node server, where a Scope::Control
peer's request is lowered into the core's real Command/OrderIntent — and only when the daemon
threads a command sink in. See the node protocol and
vike-tradehub.
The CLI (crates/vike-cli/src/lib.rs) is a subcommand dispatcher in the git/cargo style. It
routes and never grows command logic. run resolves settings first, so a removed environment
variable is refused before any verb executes, and the credential-store read is lazy: no verb
opens the file that holds every venue key on the box until it needs it — the node-facing arms
(trade, mcp, strategy-status) for their node keys, and config show, config check and
secrets list for their own disclosure reads. A backtest or provenance command never opens it at
all. config check makes the exit code the product, which is what the shipped service units put in
their ExecStartPre=. See the CLI reference.
The data service (crates/vike-datahub/src/lib.rs) inverts the direction: a client sends a
small backtest profile over a localhost socket, the server runs the existing vike-backtest
engine next to the data, and returns only the report. The heavy history never crosses the wire.
It owns no engine — it is assembly of crates that already exist — and its transport is blocking
std::net, thread-per-connection, with length-prefixed JSON frames: no async runtime, no second
HTTP stack. See vike-datahub.
Library plus thin bin — and one executable that carries them all
Each tool's body lives in its crate's library and its main.rs is a shim, so a second dispatcher
can reach the same code without a static copy of it. That dispatcher is crates/vike/src/lib.rs,
and the reason is measured: 2026-09-01, release profile — vike-tradehub 128 MB, backtest
106 MB, vike-datahub 107 MB, while vike-cli is 9.6 MB and tearsheet 1.9 MB. The gap is one
static copy of DataFusion/arrow/parquet per binary, and an image that ships all of them pays it
every time. Folding the tools into one executable links that closure once.
One tool per process
Every tool owns process-global state that assumes it is alone — logging init where only the first
configuration wins, signal handlers, a scripting one-shot, a dozen OnceLocks. A run that executed
two tools in one process would silently give the second the first one's logging and signal
handlers, so the dispatcher refuses a second call rather than documenting the hazard, and
crates/vike-ops/tests/multicall_gate.rs holds the refusal by reading the source.
The dispatcher itself does nothing: no boot, no logging, no signal handler, no project resolution.
That restraint is load-bearing. Every tool calls the shared startup sequence for itself, and a
dispatcher that also resolved the project would make one process walk for its settings twice —
where the second walk is blind to VIKE_SETTINGS_DIR and answers from whatever directory the
process happens to sit above. What it does pass down is a context carrying the one environment
sweep and the one working directory for the whole process.
Every front end starts the same way
crates/vike-boot/src/lib.rs owns the startup sequence once, because five composition roots used
to hand-assemble the same ordered steps: refuse a removed environment variable, resolve
<project>/settings, load the credentials, load the settings, derive the project-relative paths,
build the log subscriber, then disclose what was resolved. One walk decides, and the log
subscriber comes last deliberately — the log directory and both log levels are themselves settings,
so a subscriber built first could only ever honour the environment. That crate therefore logs
nothing at all and returns everything as data, which is also what lets a binary whose stdout is a
protocol choose its stream.
In this section
- CLI — the dispatcher and one page per verb.
- Binaries — every bin target in the workspace, derived at build time.
- Settings files — the four TOML files and
alerts.json, key by key, with each key's consumer. - Environment variables — the registry, and the variables refused at startup.
Troubleshooting
Symptom, cause and fix for the failures Vike Trader reports, sectioned by component and each fix naming the module that owns it.
Environment variables
The risk ceilings have no environment layer at all, three variables are refused at startup rather than ignored, and every read the workspace performs is declared in a CI-gated registry.