vike-tradehub
The unattended live trading daemon — a TOML profile, newline-JSON stdio, an optional authenticated node server, and a bounded teardown.
The headless trading daemon. It runs a strategy on a server with no GUI, controlled by a TOML
profile plus newline-JSON vike_exec::Commands on stdin, emits a periodic one-line JSON
snapshot summary on stdout, and survives an SSH disconnect — it is a systemd service, detached from
the login session (crates/vike-tradehub/src/tradehub_cli.rs; deploy/vike-tradehub.service is
the shipped unit).
usage: vike-tradehub --config <profile.toml> [--profile <run_profile.toml>]
--config PATH the daemon profile TOML (venue / token_id / A-S mount shape) — REQUIRED
--profile PATH the operator-budget RunProfile TOML ([risk] ceilings); absent, $VIKE_RUN_PROFILE
-h, --help print this and exit 0
-V, --version print the version and exit 0The bin is a thin shim over vike_tradehub::tradehub_cli::run; it is the composition root and
holds what a library may not — the one std::env::vars() sweep and the one current_dir(), both
handed to run (crates/vike-tradehub/src/main.rs).
Paper is the default; live is a flag
By default the daemon mounts the resolved strategy on the production live core with the paper
exchange as its ExecutionClient — through vike_run::build_paper_strategy_core_with, so there is
zero real-money, credential or geo risk and no live feed. The tradehub_live flag
(<project>/settings/flags.toml, still overridden by VIKE_TRADEHUB_LIVE) instead mounts the same
strategy on the real vike_run::build_node core, with per-venue credential-gated exec and the
venue's own live feed. Off is byte-identical to the pure-paper daemon.
The live path is defended by five gates and fails loud, never soft: the master flag; per-venue
credentials (absent creds keep that venue paper); the venue's own second gate
(HYPERLIQUID_MAINNET); crates/vike-tradehub/src/config.rs's LIVE_WIRED_VENUES, which
hard-errors on an unwired venue rather than falling back to paper silently; and the profile's
symbol checked against vike_run::WIRED_MARKETS by DaemonProfile::validate_for_live, because
make_engine wires no extra symbols and a foreign symbol on a wired venue would have its orders
and fills dropped at accepts_symbol.
No policy.toml means an all-paper daemon
policy.venues is the per-venue arming ceiling, read at the top of vike_mount::make_engine
above the credential read, and its default is paper for every venue. A box with no policy.toml
mounts all paper, whatever the credential store holds. vike_mount::venue_arming_migration warns
once at startup, naming the file, the [venues] key and every venue it refused. See
policy.toml.
Which strategy is a profile field
[strategy] name = "…" plus [strategy.params] — deliberately the same table
vike_backtest::harness::BacktestProfile has — resolves through the shared registry
vike_strategy::strategy_by_name, so the profile that was backtested is the profile that trades.
Absent [strategy], the mount is the Avellaneda–Stoikov vike_mm::SpreadMaker built from the
profile's own maker fields by vike_run::build_maker. A Rhai script mounts by path
([strategy] rhai = "<path>", mutually exclusive with name), and the daemon logs the source's
sha256 and path at INFO — the journal's answer to which code traded.
DaemonProfile::validate refuses, at profile load, a name that is unknown, simulator-only
(vike_strategy::SIMULATOR_ONLY), or one that would resolve and then never trade
(vike_strategy::LIVE_CAPABLE carries a written reason for every "no"). Four further refusals
cover [strategy.params], each the case the previous one passes: unknown_params (a key no reader
reads), mistyped_params (a value no reader can take), misrouted_params (a key the mount
overrides — including the ones naming which instrument and which venue real orders go to), and
unarmable_params (a table that passes all three and still describes no order at all). The mount
then echoes the resolved configuration through DaemonProfile::effective_params.
[[mounts]] is an array of the single-mount blocks, so strategies sharing a venue account share a
process; each row mounts under a derived controller id
(DaemonProfile::derived_controller_id), and two rows deriving one id are refused at load.
Control surfaces
With config.tradehub_addr (or VIKE_TRADEHUB_ADDR) set and a VIKE_TRADEHUB_OBSERVE_KEY in the
credential store — absent either, start_observe_server starts nothing and the daemon keeps
trading headless — the daemon also starts an authenticated node server so a laptop GUI can watch
this node's snapshots — see observe mode and
the node protocol — and, behind the tradehub_control flag plus
a VIKE_TRADEHUB_CONTROL_KEY, trade it.
crates/vike-tradehub/src/server.rs's accept_command is the one acceptance path, shared by
every control surface, so the rate token, the notional cap, the rationale sanitizer, the lowering,
the single-writer lane and the audit record are identical by construction rather than by review. A
remote submit must carry a pre-minted client_order_id or it is refused: a remote peer whose id
the runtime minted has no stable handle to cancel or dedup by. The optional rationale is remote
free text landing verbatim in a structured JSON log line, so
crates/vike-tradehub/src/audit.rs's sanitize_reason is mandatory at the call site.
Reachability is the outer barrier
Everything in server.rs is authorization — none of it is confidentiality or integrity. The
handshake is plaintext and authenticates the connection, not each frame. There is deliberately no
TLS: the listener stays on loopback and an SSH tunnel supplies both properties. Three things hold
that up, each stating its own reasoning — bind_exposure (a non-loopback bind is refused unless
explicitly opted in), MAX_CONNECTIONS and HANDSHAKE_MAX_FRAME_LEN.
The publisher (crates/vike-tradehub/src/publish.rs) reads only the coalesced arc-swap snapshot
cell, on its own thread, and pushes shared frame bytes into each subscriber's bounded, drop-oldest
mailbox — it must never enter the core fold. The opt-in
Telegram channel is a second remote write surface behind an
off-by-default Cargo feature and four runtime gates; it shares accept_command with the TCP path.
Lifecycle and stop
vike_ops::stop::install_handlers is the first statement of run, above argv and the settings
load (crates/vike-tradehub/src/tradehub_cli.rs's run). The gate behind it is narrower than
that: crates/vike-ops/tests/graceful_stop_pin.rs's
the_tradehub_handler_is_installed_before_anything_can_place_an_order checks by position only that
the install precedes every mount call, because between a mount returning and the handler
installing, the maker may already hold resting orders at the venue while SIGTERM still carries the
OS default disposition. One stop flag has many triggers: the
shutdown/quit word on stdin, Ctrl-D on a TTY, SIGTERM/SIGINT, the Windows console control
events, and on Windows the appearance of <state>/ plus vike_ops::stop::STOP_FILE_NAME. A
non-TTY EOF does not stop the daemon — it keeps trading headless. The teardown is bounded by
vike_ops::shutdown::run_with_deadline wrapping vike_core::CoreHandle::shutdown_and_join.
stdout is protocol-only — newline-JSON summaries and acks; every log and diagnostic rides the
vike_log file/stderr layer. resolve_settings runs before the log subscriber is built, because
the log directory and both log levels are themselves settings; it returns its warnings as data for
main to emit once a subscriber exists. A removed environment variable refuses startup outright.
Reconciliation defaults to quarantine here rather than to the
workspace default: under hybrid a freshly restarted daemon would auto-apply position drift,
rewriting position size and booking realized PnL at the venue's price before anyone has confirmed
the venue's report is complete. Its health gate is fed by the feeds this mount actually wired
(recon_feed_statuses_of): a CEX venue contributes its own status handle and is gated on it, while
every venue that contributes none — hyperliquid and polymarket among them — reads Healthy.
Alerting rides the periodic summary tick off-fold, and the rules file
is the gate. record-feeds and materialize are the crate's two DataFusion arms, both off by
default to keep the daemon DataFusion-free.
Binaries
Every binary target in the Vike Trader workspace — 46 across 17 crates — derived from cargo metadata and classified: its own page, its collector family, or an internal harness.
vike-datahub
The headless compute-to-data backtest server — a profile in, a report out, with the history never crossing the wire.